diff --git a/.drone.yml b/.drone.yml index 2a7173b361..f9890d57e9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -522,7 +522,7 @@ steps: image: plugins/s3:1 settings: acl: public-read - bucket: releases + bucket: gitea-artifacts endpoint: https://storage.gitea.io path_style: true source: "dist/release/*" @@ -543,7 +543,7 @@ steps: image: plugins/s3:1 settings: acl: public-read - bucket: releases + bucket: gitea-artifacts endpoint: https://storage.gitea.io path_style: true source: "dist/release/*" @@ -618,7 +618,7 @@ steps: image: plugins/s3:1 settings: acl: public-read - bucket: releases + bucket: gitea-artifacts endpoint: https://storage.gitea.io path_style: true source: "dist/release/*" @@ -709,7 +709,7 @@ steps: - name: publish pull: always - image: plugins/docker:linux-amd64 + image: techknowlogick/drone-docker:latest settings: auto_tag: true auto_tag_suffix: linux-amd64 @@ -726,7 +726,7 @@ steps: - pull_request - name: publish-rootless - image: plugins/docker:linux-amd64 + image: techknowlogick/drone-docker:latest settings: dockerfile: Dockerfile.rootless auto_tag: true @@ -764,7 +764,7 @@ trigger: steps: - name: dryrun pull: always - image: plugins/docker:linux-arm64 + image: techknowlogick/drone-docker:latest settings: dry_run: true repo: gitea/gitea @@ -806,7 +806,7 @@ steps: - name: publish pull: always - image: plugins/docker:linux-arm64 + image: techknowlogick/drone-docker:latest settings: auto_tag: true auto_tag_suffix: linux-arm64 @@ -826,7 +826,7 @@ steps: - pull_request - name: publish-rootless - image: plugins/docker:linux-arm64 + image: techknowlogick/drone-docker:latest settings: dockerfile: Dockerfile.rootless auto_tag: true diff --git a/.golangci.yml b/.golangci.yml index 88168af222..0d7f90e263 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -110,3 +110,7 @@ issues: - text: "exitAfterDefer:" linters: - gocritic + - path: modules/graceful/manager_windows.go + linters: + - staticcheck + text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead." diff --git a/CHANGELOG.md b/CHANGELOG.md index ae0e6b4fe9..a0d6c3158f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,190 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). -## [1.14.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.14.0) - 2021-03-19 +## [1.14.7](https://github.com/go-gitea/gitea/releases/tag/v1.14.7) - 2021-09-02 + +* BUGFIXES + * Add missing gitRepo close at GetDiffRangeWithWhitespaceBehavior (Partial #16894) (#16896) + * Fix wiki raw commit diff/patch view (#16891) (#16893) + * Ensure wiki repos are all closed (#16886) (#16889) + * Upgrade xorm to v1.2.2 (#16663) & Add test to ensure that dumping of login sources remains correct (#16847) (#16849) + * Recreate Tables should Recreate indexes on MySQL (#16718) (#16740) + +## [1.14.6](https://github.com/go-gitea/gitea/releases/tag/v1.14.6) - 2021-08-04 + +* SECURITY + * Bump github.com/markbates/goth from v1.67.1 to v1.68.0 (#16538) (#16540) + * Switch to maintained JWT lib (#16532) (#16535) + * Upgrade to latest version of golang-jwt (as forked for 1.14) (#16590) (#16607) +* BUGFIXES + * Add basic edit ldap auth test & actually fix #16252 (#16465) (#16495) + * Make cancel from CatFileBatch and CatFileBatchCheck wait for the command to end (#16479) (#16481) + +## [1.14.5](https://github.com/go-gitea/gitea/releases/tag/v1.14.5) - 2021-07-16 + +* SECURITY + * Hide mirror passwords on repo settings page (#16022) (#16355) + * Update bluemonday to v1.0.15 (#16379) (#16380) +* BUGFIXES + * Retry rename on lock induced failures (#16435) (#16439) + * Validate issue index before querying DB (#16406) (#16410) + * Fix crash following ldap authentication update (#16447) (#16449) +* ENHANCEMENTS + * Redirect on bad CSRF instead of presenting bad page (#14937) (#16378) + +## [1.14.4](https://github.com/go-gitea/gitea/releases/tag/v1.14.4) - 2021-07-06 + +* BUGFIXES + * Fix relative links in postprocessed images (#16334) (#16340) + * Fix list_options GetStartEnd (#16303) (#16305) + * Fix API to use author for commits instead of committer (#16276) (#16277) + * Handle misencoding of login_source cfg in mssql (#16268) (#16275) + * Fixed issues not updated by commits (#16254) (#16261) + * Improve efficiency in FindRenderizableReferenceNumeric and getReference (#16251) (#16255) + * Use html.Parse rather than html.ParseFragment (#16223) (#16225) + * Fix milestone counters on new issue (#16183) (#16224) + * reqOrgMembership calls need to be preceded by reqToken (#16198) (#16219) + +## [1.14.3](https://github.com/go-gitea/gitea/releases/tag/v1.14.3) - 2021-06-10 + +* SECURITY + * Encrypt migration credentials at rest (#15895) (#16187) + * Only check access tokens if they are likely to be tokens (#16164) (#16171) + * Add missing SameSite settings for the i_like_gitea cookie (#16037) (#16039) + * Fix setting of SameSite on cookies (#15989) (#15991) +* API + * Repository object only count releases as releases (#16184) (#16190) + * EditOrg respect RepoAdminChangeTeamAccess option (#16184) (#16190) + * Fix overly strict edit pr permissions (#15900) (#16081) +* BUGFIXES + * Run processors on whole of text (#16155) (#16185) + * Class `issue-keyword` is being incorrectly stripped off spans (#16163) (#16172) + * Fix language switch for install page (#16043) (#16128) + * Fix bug on getIssueIDsByRepoID (#16119) (#16124) + * Set self-adjusting deadline for connection writing (#16068) (#16123) + * Fix http path bug (#16117) (#16120) + * Fix data URI scramble (#16098) (#16118) + * Merge all deleteBranch as one function and also fix bug when delete branch don't close related PRs (#16067) (#16097) + * git migration: don't prompt interactively for clone credentials (#15902) (#16082) + * Fix case change in ownernames (#16045) (#16050) + * Don't manipulate input params in email notification (#16011) (#16033) + * Remove branch URL before IssueRefURL (#15968) (#15970) + * Fix layout of milestone view (#15927) (#15940) + * GitHub Migration, migrate draft releases too (#15884) (#15888) + * Close the gitrepo when deleting the repository (#15876) (#15887) + * Upgrade xorm to v1.1.0 (#15869) (#15885) + * Fix blame row height alignment (#15863) (#15883) + * Fix error message when saving generated LOCAL_ROOT_URL config (#15880) (#15882) + * Backport Fix LFS commit finder not working (#15856) (#15874) + * Stop calling WriteHeader in Write (#15862) (#15873) + * Add timeout to writing to responses (#15831) (#15872) + * Return go-get info on subdirs (#15642) (#15871) + * Restore PAM user autocreation functionality (#15825) (#15867) + * Fix truncate utf8 string (#15828) (#15854) + * Fix bound address/port for caddy's certmagic library (#15758) (#15848) + * Upgrade unrolled/render to v1.1.1 (#15845) (#15846) + * Queue manager FlushAll can loop rapidly - add delay (#15733) (#15840) + * Tagger can be empty, as can Commit and Author - tolerate this (#15835) (#15839) + * Set autocomplete off on branches selector (#15809) (#15833) + * Add missing error to Doctor log (#15813) (#15824) + * Move restore repo to internal router and invoke from command to avoid open the same db file or queues files (#15790) (#15816) +* ENHANCEMENTS + * Removable media support to snap package (#16136) (#16138) + * Move sans-serif fallback font higher than emoji fonts (#15855) (#15892) +* DOCKER + * Only write config in environment-to-ini if there are changes (#15861) (#15868) + * Only offer hostcertificates if they exist (#15849) (#15853) + +## [1.14.2](https://github.com/go-gitea/gitea/releases/tag/v1.14.2) - 2021-05-08 + +* API + * Make change repo settings work on empty repos (#15778) (#15789) + * Add pull "merged" notification subject status to API (#15344) (#15654) +* BUGFIXES + * Ensure that ctx.Written is checked after issues(...) calls (#15797) (#15798) + * Use pulls in commit graph unless pulls are disabled (#15734 & #15740 & #15774) (#15775) + * Set GIT_DIR correctly if it is not set (#15751) (#15769) + * Fix bug where repositories appear unadopted (#15757) (#15767) + * Not show `ref-in-new-issue` pop when issue was disabled (#15761) (#15765) + * Drop back to use IsAnInteractiveSession for SVC (#15749) (#15762) + * Fix setting version table in dump (#15753) (#15759) + * Fix close button change on delete in simplemde area (#15737) (#15747) + * Defer closing the gitrepo until the end of the wrapped context functions (#15653) (#15746) + * Fix some ui bug about draft release (#15137) (#15745) + * Only log Error on getLastCommitStatus error to let pull list still be visible (#15716) (#15715) + * Move tooltip down to allow selection of Remove File on error (#15672) (#15714) + * Fix setting redis db path (#15698) (#15708) + * Fix DB session cleanup (#15697) (#15700) + * Fixed several activation bugs (#15473) (#15685) + * Delete references if repository gets deleted (#15681) (#15684) + * Fix orphaned objects deletion bug (#15657) (#15683) + * Delete protected branch if repository gets removed (#15658) (#15676) + * Remove spurious set name from eventsource.sharedworker.js (#15643) (#15652) + * Not update updated uinx for `git gc` (#15637) (#15641) + * Fix commit graph author link (#15627) (#15630) + * Fix webhook timeout bug (#15613) (#15621) + * Resolve panic on failed interface conversion in migration v156 (#15604) (#15610) + * Fix missing storage init (#15589) (#15598) + * If the default branch is not present do not report error on stats indexing (#15546 & #15583) (#15594) + * Fix lfs management find (#15537) (#15578) + * Fix NPE on view commit with notes (#15561) (#15573) + * Fix bug on commit graph (#15517) (#15530) + * Send size to /avatars if requested (#15459) (#15528) + * Prevent migration 156 failure if tag commit missing (#15519) (#15527) +* ENHANCEMENTS + * Display conflict-free merge messages for pull requests (#15773) (#15796) + * Exponential Backoff for ByteFIFO (#15724) (#15793) + * Issue list alignment tweaks (#15483) (#15766) + * Implement delete release attachments and update release attachments' name (#14130) (#15666) + * Add placeholder text to deploy key textarea (#15575) (#15576) + * Project board improvements (#15429) (#15560) + * Repo branch page: label size, PR ref, new PR button alignment (#15363) (#15365) +* MISC + * Fix webkit calendar icon color on arc-green (#15713) (#15728) + * Performance improvement for last commit cache and show-ref (#15455) (#15701) + * Bump unrolled/render to v1.1.0 (#15581) (#15608) + * Add ETag header (#15370) (#15552) + +## [1.14.1](https://github.com/go-gitea/gitea/releases/tag/v1.14.1) - 2021-04-15 + +* BUGFIXES + * Fix bug clone wiki (#15499) (#15502) + * Github Migration ignore rate limit, if not enabled (#15490) (#15495) + * Use subdir for URL (#15446) (#15493) + * Query the DB for the hash before inserting in to email_hash (#15457) (#15491) + * Ensure review dismissal only dismisses the correct review (#15477) (#15489) + * Use index of the supported tags to choose user lang (#15452) (#15488) + * Fix wrong file link in code search page (#15466) (#15486) + * Quick template fix for built-in SSH server in admin config (#15464) (#15481) + * Prevent superfluous response.WriteHeader (#15456) (#15476) + * Fix ambiguous argument error on tags (#15432) (#15474) + * Add created_unix instead of expiry to migration (#15458) (#15463) + * Fix repository search (#15428) (#15442) + * Prevent NPE on avatar direct rendering if federated avatars disabled (#15434) (#15439) + * Fix wiki clone urls (#15430) (#15431) + * Fix dingtalk icon url at webhook (#15417) (#15426) + * Standardise icon on projects PR page (#15387) (#15408) +* ENHANCEMENTS + * Add option to skip LFS/attachment files for `dump` (#15407) (#15492) + * Clone panel fixes (#15436) + * Use semantic dropdown for code search query type (#15276) (#15364) +* BUILD + * Build go-git variants for windows (#15482) (#15487) + * Lock down build-images dependencies (Partial #15479) (#15480) +* MISC + * Performance improvement for list pull requests (#15447) (#15500) + * Fix potential copy lfs records failure when fork a repository (#15441) (#15485) + +## [1.14.0](https://github.com/go-gitea/gitea/releases/tag/v1.14.0) - 2021-04-11 * SECURITY * Respect approved email domain list for externally validated user registration (#15014) * Add reverse proxy configuration support for remote IP address detection (#14959) * Ensure validation occurs on clone addresses too (#14994) - * Fix several render issues highlighted during fuzzing (#14986) * BREAKING + * Fix double 'push tag' action feed (#15078) (#15083) + * Remove possible resource leak (#15067) (#15082) + * Handle unauthorized user events gracefully (#15071) (#15074) * Restore Access.log following migration to Chi framework (Stops access logging of /api/internal routes) (#14475) * Migrate from Macaron to Chi framework (#14293) * Deprecate building for mips (#14174) @@ -42,6 +218,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Dump github/gitlab/gitea repository data to a local directory and restore to gitea (#12244) * Create Rootless Docker image (#10154) * API + * Speedup issue search (#15179) (#15192) * Get pull, return head branch sha, even if deleted (#14931) * Export LFS & TimeTracking function status (#14753) * Show Gitea version in swagger (#14654) @@ -66,6 +243,20 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Add more filters to issues search (#13514) * Add review request api (#11355) * BUGFIXES + * Fix delete nonexist oauth application 500 and prevent deadlock (#15384) (#15396) + * Always set the merge base used to merge the commit (#15352) (#15385) + * Upgrade to bluemonday 1.0.7 (#15379) (#15380) + * Turn RepoRef and RepoAssignment back into func(*Context) (#15372) (#15377) + * Move FCGI req.URL.Path fix-up to the FCGI listener (#15292) (#15361) + * Show diff on rename with diff changes (#15338) (#15339) + * Fix handling of logout event (#15323) (#15337) + * Fix CanCreateRepo check (#15311) (#15321) + * Fix xorm log stack level (#15285) (#15316) + * Fix bug in Wrap (#15302) (#15309) + * Drop the event source if we are unauthorized (#15275) (#15280) + * Backport Fix graph pagination (#15225) (#15249) + * Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15200) + * should run RetrieveRepoMetas() for empty pr (#15187) (#15190) * Move setting to enable closing issue via commit in non default branch to repo settings (#14965) * Show correct issues for team dashboard (#14952) * Ensure that new pull request button works on forked forks owned by owner of the root and reduce ambiguity (#14932) @@ -122,6 +313,9 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Use GO variable in go-check target (#13146) (#13147) * ENHANCEMENTS * UI style improvements + * Dropzone styling improvements (#15291) (#15374) + * Add size to Save function (#15264) (#15270) + * Monaco improvements (#15333) (#15345) * Support .mailmap in code activity stats (#15009) * Sort release attachments by name (#15008) * Add ui.explore settings to control view of explore pages (#14094) @@ -267,6 +461,52 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Reduce make verbosity (#13803) * Add git command error directory on log (#13194) +## [1.13.7](https://github.com/go-gitea/gitea/releases/tag/v1.13.7) - 2021-04-07 + +* SECURITY + * Update to bluemonday-1.0.6 (#15294) (#15298) + * Clusterfuzz found another way (#15160) (#15169) +* API + * Fix wrong user returned in API (#15139) (#15150) +* BUGFIXES + * Add 'fonts' into 'KnownPublicEntries' (#15188) (#15317) + * Speed up `enry.IsVendor` (#15213) (#15246) + * Response 404 for diff/patch of a commit that not exist (#15221) (#15238) + * Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15201) +* MISC + * Add size to Save function (#15264) (#15271) + +## [1.13.6](https://github.com/go-gitea/gitea/releases/tag/v1.13.6) - 2021-03-23 + +* SECURITY + * Fix bug on avatar middleware (#15124) (#15125) + * Fix another clusterfuzz identified issue (#15096) (#15114) +* API + * Fix nil pointer exception in get pull reviews API (#15106) +* BUGFIXES + * Fix markdown rendering in milestone content (#15056) (#15092) + +## [1.13.5](https://github.com/go-gitea/gitea/releases/tag/v1.13.5) - 2021-03-21 + +* SECURITY + * Update to goldmark 1.3.3 (#15059) (#15061) + * Another clusterfuzz spotted issue (#15032) (#15034) +* API + * Fix set milestone on PR creation (#14981) (#15001) + * Prevent panic when editing forked repos by API (#14960) (#14963) +* BUGFIXES + * Fix bug when upload on web (#15042) (#15055) + * Delete Labels & IssueLabels on Repo Delete too (#15039) (#15051) + * Fix postgres ID sequences broken by recreate-table (#15015) (#15029) + * Fix several render issues (#14986) (#15013) + * Make sure sibling images get a link too (#14979) (#14995) + * Fix Anchor jumping with escaped query components (#14969) (#14977) + * Fix release mail html template (#14976) + * Fix excluding more than two labels on issues list (#14962) (#14973) + * Don't mark each comment poster as OP (#14971) (#14972) + * Add "captcha" to list of reserved usernames (#14930) + * Re-enable import local paths after reversion from #13610 (#14925) (#14927) + ## [1.13.4](https://github.com/go-gitea/gitea/releases/tag/v1.13.4) - 2021-03-07 * SECURITY diff --git a/Makefile b/Makefile index 00bdbab259..c6b20744b3 100644 --- a/Makefile +++ b/Makefile @@ -577,6 +577,9 @@ release-windows: | $(DIST_DIRS) $(GO) install src.techknowlogick.com/xgo@latest; \ fi CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . +ifeq (,$(findstring gogit,$(TAGS))) + CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . +endif ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif @@ -699,8 +702,8 @@ generate-gitignore: GO111MODULE=on $(GO) run build/generate-gitignores.go .PHONY: generate-images -generate-images: - npm install --no-save --no-package-lock fabric imagemin-zopfli +generate-images: | node_modules + npm install --no-save --no-package-lock fabric@4 imagemin-zopfli@7 node build/generate-images.js $(TAGS) .PHONY: generate-manpage diff --git a/cmd/admin.go b/cmd/admin.go index 3ddf8eddf9..988a8d5033 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -21,6 +21,7 @@ import ( pwd "code.gitea.io/gitea/modules/password" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "github.com/urfave/cli" ) @@ -489,6 +490,10 @@ func runDeleteUser(c *cli.Context) error { return err } + if err := storage.Init(); err != nil { + return err + } + var err error var user *models.User if c.IsSet("email") { diff --git a/cmd/dump.go b/cmd/dump.go index 43997c8da8..458f9f91e0 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -129,6 +129,14 @@ It can be used for backup and capture Gitea server image to send to maintainer`, Name: "skip-custom-dir", Usage: "Skip custom directory", }, + cli.BoolFlag{ + Name: "skip-lfs-data", + Usage: "Skip LFS data", + }, + cli.BoolFlag{ + Name: "skip-attachment-data", + Usage: "Skip attachment data", + }, cli.GenericFlag{ Name: "type", Value: outputTypeEnum, @@ -214,7 +222,9 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include repositories: %v", err) } - if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error { + if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { + log.Info("Skip dumping LFS data") + } else if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error { info, err := object.Stat() if err != nil { return err @@ -313,7 +323,9 @@ func runDump(ctx *cli.Context) error { } } - if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error { + if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { + log.Info("Skip dumping attachment data") + } else if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error { info, err := object.Stat() if err != nil { return err diff --git a/cmd/embedded.go b/cmd/embedded.go index 363b85c066..528f32402e 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" "github.com/urfave/cli" @@ -271,7 +272,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error { } else if !fi.Mode().IsRegular() { return fmt.Errorf("%s already exists, but it's not a regular file", dest) } else if rename { - if err := os.Rename(dest, dest+".bak"); err != nil { + if err := util.Rename(dest, dest+".bak"); err != nil { return fmt.Errorf("Error creating backup for %s: %v", dest, err) } // Attempt to respect file permissions mask (even if user:group will be set anew) diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 541995879b..b832471928 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -5,15 +5,12 @@ package cmd import ( - "context" - "strings" + "errors" + "net/http" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/migrations" - "code.gitea.io/gitea/modules/migrations/base" + "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/storage" - pull_service "code.gitea.io/gitea/services/pull" "github.com/urfave/cli" ) @@ -50,70 +47,18 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme } func runRestoreRepository(ctx *cli.Context) error { - if err := initDB(); err != nil { - return err - } + setting.NewContext() - 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) - setting.InitDBConfig() - - if err := storage.Init(); err != nil { - return err - } - - if err := pull_service.Init(); err != nil { - return err - } - - var opts = base.MigrateOptions{ - RepoName: ctx.String("repo_name"), - } - - if len(ctx.String("units")) == 0 { - opts.Wiki = true - opts.Issues = true - opts.Milestones = true - opts.Labels = true - opts.Releases = true - opts.Comments = true - opts.PullRequests = true - opts.ReleaseAssets = true - } else { - units := strings.Split(ctx.String("units"), ",") - for _, unit := range units { - switch strings.ToLower(unit) { - case "wiki": - opts.Wiki = true - case "issues": - opts.Issues = true - case "milestones": - opts.Milestones = true - case "labels": - opts.Labels = true - case "releases": - opts.Releases = true - case "release_assets": - opts.ReleaseAssets = true - case "comments": - opts.Comments = true - case "pull_requests": - opts.PullRequests = true - } - } - } - - if err := migrations.RestoreRepository( - context.Background(), + statusCode, errStr := private.RestoreRepo( ctx.String("repo_dir"), ctx.String("owner_name"), ctx.String("repo_name"), - ); err != nil { - log.Fatal("Failed to restore repository: %v", err) - return err + ctx.StringSlice("units"), + ) + if statusCode == http.StatusOK { + return nil } - return nil + log.Fatal("Failed to restore repository: %v", errStr) + return errors.New(errStr) } diff --git a/cmd/serv.go b/cmd/serv.go index a8db623e16..cc9ef6db19 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -23,7 +23,7 @@ import ( "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/dgrijalva/jwt-go" + "github.com/golang-jwt/jwt" jsoniter "github.com/json-iterator/go" "github.com/kballard/go-shellquote" "github.com/urfave/cli" diff --git a/cmd/web.go b/cmd/web.go index 423917ba4e..3a5c36833b 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -175,7 +175,7 @@ func setPort(port string) error { cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) if err := cfg.SaveTo(setting.CustomConf); err != nil { - return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err) + return fmt.Errorf("Error saving generated LOCAL_ROOT_URL to custom config: %v", err) } } return nil diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index 5db065818a..91ac024ddb 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -9,9 +9,11 @@ import ( "net" "net/http" "net/http/fcgi" + "strings" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" ) func runHTTP(network, listenAddr, name string, m http.Handler) error { @@ -48,7 +50,12 @@ func runFCGI(network, listenAddr, name string, m http.Handler) error { fcgiServer := graceful.NewServer(network, listenAddr, name) err := fcgiServer.ListenAndServe(func(listener net.Listener) error { - return fcgi.Serve(listener, m) + return fcgi.Serve(listener, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if setting.AppSubURL != "" { + req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL) + } + m.ServeHTTP(resp, req) + })) }) if err != nil { log.Fatal("Failed to start FCGI main server: %v", err) diff --git a/cmd/web_letsencrypt.go b/cmd/web_letsencrypt.go index 7754393729..4d4d54419d 100644 --- a/cmd/web_letsencrypt.go +++ b/cmd/web_letsencrypt.go @@ -6,6 +6,7 @@ package cmd import ( "net/http" + "strconv" "strings" "code.gitea.io/gitea/modules/log" @@ -22,6 +23,15 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) // TODO: these are placeholders until we add options for each in settings with appropriate warning enableHTTPChallenge := true enableTLSALPNChallenge := true + altHTTPPort := 0 + altTLSALPNPort := 0 + + if p, err := strconv.Atoi(setting.PortToRedirect); err == nil { + altHTTPPort = p + } + if p, err := strconv.Atoi(setting.HTTPPort); err == nil { + altTLSALPNPort = p + } magic := certmagic.NewDefault() magic.Storage = &certmagic.FileStorage{Path: directory} @@ -30,6 +40,9 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) Agreed: setting.LetsEncryptTOS, DisableHTTPChallenge: !enableHTTPChallenge, DisableTLSALPNChallenge: !enableTLSALPNChallenge, + ListenHost: setting.HTTPAddr, + AltTLSALPNPort: altTLSALPNPort, + AltHTTPPort: altHTTPPort, }) magic.Issuer = myACME diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index 74379e26af..aade251902 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -110,6 +110,8 @@ func runEnvironmentToIni(c *cli.Context) error { } cfg.NameMapper = ini.SnackCase + changed := false + prefix := c.String("prefix") + "__" for _, kv := range os.Environ() { @@ -143,15 +145,21 @@ func runEnvironmentToIni(c *cli.Context) error { continue } } + oldValue := key.Value() + if !changed && oldValue != value { + changed = true + } key.SetValue(value) } destination := c.String("out") if len(destination) == 0 { destination = setting.CustomConf } - err = cfg.SaveTo(destination) - if err != nil { - return err + if destination != setting.CustomConf || changed { + err = cfg.SaveTo(destination) + if err != nil { + return err + } } if c.Bool("clear") { for _, kv := range os.Environ() { diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index fe4fec7e92..2dc13ce59c 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -281,6 +281,10 @@ HTTP_PORT = 3000 ; PORT_TO_REDIRECT. REDIRECT_OTHER_PORT = false PORT_TO_REDIRECT = 80 +; Timeout for any write to the connection. (Set to 0 to disable all timeouts.) +PER_WRITE_TIMEOUT = 30s +; Timeout per Kb written to connections. +PER_WRITE_PER_KB_TIMEOUT = 30s ; Permission for unix socket UNIX_SOCKET_PERMISSION = 666 ; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. diff --git a/docker/root/etc/s6/openssh/setup b/docker/root/etc/s6/openssh/setup index 2a5eb9b09f..5601994d66 100755 --- a/docker/root/etc/s6/openssh/setup +++ b/docker/root/etc/s6/openssh/setup @@ -24,9 +24,29 @@ if [ ! -f /data/ssh/ssh_host_ecdsa_key ]; then ssh-keygen -t ecdsa -b 256 -f /data/ssh/ssh_host_ecdsa_key -N "" > /dev/null fi +if [ -e /data/ssh/ssh_host_ed25519_cert ]; then + SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_cert"} +fi + +if [ -e /data/ssh/ssh_host_rsa_cert ]; then + SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_cert"} +fi + +if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then + SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"} +fi + +if [ -e /data/ssh/ssh_host_dsa_cert ]; then + SSH_DSA_CERT=${SSH_DSA_CERT:-"/data/ssh/ssh_host_dsa_cert"} +fi + if [ -d /etc/ssh ]; then SSH_PORT=${SSH_PORT:-"22"} \ SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \ + SSH_ED25519_CERT="${SSH_ED25519_CERT:+"HostCertificate "}${SSH_ED25519_CERT}" \ + SSH_RSA_CERT="${SSH_RSA_CERT:+"HostCertificate "}${SSH_RSA_CERT}" \ + SSH_ECDSA_CERT="${SSH_ECDSA_CERT:+"HostCertificate "}${SSH_ECDSA_CERT}" \ + SSH_DSA_CERT="${SSH_DSA_CERT:+"HostCertificate "}${SSH_DSA_CERT}" \ envsubst < /etc/templates/sshd_config > /etc/ssh/sshd_config chmod 0644 /etc/ssh/sshd_config diff --git a/docker/root/etc/templates/sshd_config b/docker/root/etc/templates/sshd_config index 26e26feb41..84a27b9190 100644 --- a/docker/root/etc/templates/sshd_config +++ b/docker/root/etc/templates/sshd_config @@ -8,13 +8,13 @@ ListenAddress :: LogLevel INFO HostKey /data/ssh/ssh_host_ed25519_key -HostCertificate /data/ssh/ssh_host_ed25519_cert +${SSH_ED25519_CERT} HostKey /data/ssh/ssh_host_rsa_key -HostCertificate /data/ssh/ssh_host_rsa_cert +${SSH_RSA_CERT} HostKey /data/ssh/ssh_host_ecdsa_key -HostCertificate /data/ssh/ssh_host_ecdsa_cert +${SSH_ECDSA_CERT} HostKey /data/ssh/ssh_host_dsa_key -HostCertificate /data/ssh/ssh_host_dsa_cert +${SSH_DSA_CERT} AuthorizedKeysFile .ssh/authorized_keys AuthorizedPrincipalsFile .ssh/authorized_principals diff --git a/docs/Makefile b/docs/Makefile index 487e16cf6f..68afe03e75 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -31,4 +31,4 @@ update: $(THEME) $(THEME): $(THEME)/theme.toml $(THEME)/theme.toml: mkdir -p $$(dirname $@) - curl -s $(ARCHIVE) | tar xz -C $$(dirname $@) + curl -L -s $(ARCHIVE) | tar xz -C $$(dirname $@) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index e32112f025..10775515b8 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -237,6 +237,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. most cases you do not need to change the default value. Alter it only if your SSH server node is not the same as HTTP node. Do not set this variable if `PROTOCOL` is set to `unix`. +- `PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the connection. (Set to 0 to + disable all timeouts.) +- `PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to connections. - `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. @@ -260,6 +263,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `SSH_KEY_TEST_PATH`: **/tmp**: Directory to create temporary files in when testing public keys using ssh-keygen, default is the system temporary directory. - `SSH_KEYGEN_PATH`: **ssh-keygen**: Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call. - `SSH_EXPOSE_ANONYMOUS`: **false**: Enable exposure of SSH clone URL to anonymous visitors, default is false. +- `SSH_PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the SSH connections. (Set to + 0 to disable all timeouts.) +- `SSH_PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to SSH connections. - `MINIMUM_KEY_SIZE_CHECK`: **true**: Indicate whether to check minimum key size with corresponding type. - `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures. diff --git a/go.mod b/go.mod index a4a5706ccf..afd557a46b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( cloud.google.com/go v0.78.0 // indirect code.gitea.io/gitea-vet v0.2.1 - code.gitea.io/sdk/gitea v0.13.2 + code.gitea.io/sdk/gitea v0.14.0 gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7 gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e @@ -27,8 +27,7 @@ require ( github.com/couchbase/gomemcached v0.1.2 // indirect github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/denisenkom/go-mssqldb v0.9.0 - github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/denisenkom/go-mssqldb v0.10.0 github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dustin/go-humanize v1.0.0 github.com/editorconfig/editorconfig-core-go/v2 v2.4.1 @@ -46,13 +45,14 @@ require ( github.com/go-openapi/errors v0.20.0 // indirect github.com/go-openapi/validate v0.20.2 // indirect github.com/go-redis/redis/v8 v8.6.0 - github.com/go-sql-driver/mysql v1.5.0 + github.com/go-sql-driver/mysql v1.6.0 github.com/go-swagger/go-swagger v0.26.1 github.com/go-testfixtures/testfixtures/v3 v3.5.0 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 + github.com/golang-jwt/jwt v3.2.1+incompatible github.com/golang/snappy v0.0.3 // indirect github.com/google/go-github/v32 v32.1.0 github.com/google/uuid v1.2.0 @@ -67,26 +67,26 @@ require ( github.com/issue9/assert v1.3.2 // indirect github.com/issue9/identicon v1.0.1 github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 - github.com/json-iterator/go v1.1.10 + github.com/json-iterator/go v1.1.11 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 github.com/klauspost/compress v1.11.8 github.com/klauspost/pgzip v1.2.5 // indirect github.com/lafriks/xormstore v1.4.0 - github.com/lib/pq v1.9.0 + github.com/lib/pq v1.10.2 github.com/libdns/libdns v0.2.0 // indirect github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 github.com/mailru/easyjson v0.7.7 // indirect - github.com/markbates/goth v1.67.1 - github.com/mattn/go-isatty v0.0.12 - github.com/mattn/go-runewidth v0.0.10 // indirect - github.com/mattn/go-sqlite3 v1.14.6 + github.com/markbates/goth v1.68.0 + github.com/mattn/go-isatty v0.0.13 + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-sqlite3 v1.14.8 github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 github.com/mgechev/revive v1.0.3 github.com/mholt/acmez v0.1.3 // indirect github.com/mholt/archiver/v3 v3.5.0 - github.com/microcosm-cc/bluemonday v1.0.4 + github.com/microcosm-cc/bluemonday v1.0.15 github.com/miekg/dns v1.1.40 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/minio-go/v7 v7.0.10 @@ -107,7 +107,6 @@ require ( github.com/prometheus/common v0.18.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/quasoft/websspi v1.0.0 - github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sergi/go-diff v1.1.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect @@ -122,24 +121,24 @@ require ( github.com/unknwon/com v1.0.1 github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae - github.com/unrolled/render v1.0.3 + github.com/unrolled/render v1.1.1 github.com/urfave/cli v1.22.5 github.com/willf/bitset v1.1.11 // indirect github.com/xanzy/go-gitlab v0.44.0 github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/yohcop/openid-go v1.0.0 - github.com/yuin/goldmark v1.3.2 + github.com/yuin/goldmark v1.3.3 github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 github.com/yuin/goldmark-meta v1.0.0 go.jolheiser.com/hcaptcha v0.0.4 go.jolheiser.com/pwn v0.0.3 go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.16.0 // indirect - golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e + golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 - golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 - golang.org/x/text v0.3.5 + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 + golang.org/x/text v0.3.6 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/tools v0.1.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect @@ -149,9 +148,9 @@ require ( mvdan.cc/xurls/v2 v2.2.0 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 xorm.io/builder v0.3.9 - xorm.io/xorm v1.0.7 + xorm.io/xorm v1.2.2 ) replace github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4 -replace github.com/microcosm-cc/bluemonday => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8 +replace github.com/golang-jwt/jwt v3.2.1+incompatible => github.com/zeripath/jwt v3.2.2-go1.14+incompatible diff --git a/go.sum b/go.sum index 860d807f96..b155da9875 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s= code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA= -code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg= +code.gitea.io/sdk/gitea v0.14.0 h1:m4J352I3p9+bmJUfS+g0odeQzBY/5OXP91Gv6D4fnJ0= +code.gitea.io/sdk/gitea v0.14.0/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7 h1:xCVJPY823C8RWpgMabTw2kOglDrg0iS3GcQU6wdwHkU= gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7/go.mod h1:AyfTrwtfYN54R/HmVvMYPnSTenH5bVoyh8x6tBluxEA= @@ -63,6 +63,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= @@ -127,8 +129,9 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= @@ -196,8 +199,6 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ= github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0= -github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= -github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -250,9 +251,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= -github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= @@ -449,8 +449,9 @@ github.com/go-redis/redis/v8 v8.6.0 h1:swqbqOrxaPztsj2Hf1p94M3YAgl7hYEpcw21z299h github.com/go-redis/redis/v8 v8.6.0/go.mod h1:DQ9q4Rk2HtwkrwVrdgmphoOQDMfpvcd/nHEwRsicg8s= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-swagger/go-swagger v0.26.1 h1:1XUWLnH6hKxHzeKjJfA2gHkSqcT1Zgi4q/PZp2hDdN8= @@ -485,8 +486,11 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= +github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -657,12 +661,18 @@ github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= +github.com/jackc/pgconn v1.9.0 h1:gqibKSTJup/ahCsNKyMZAniPuZEfIqfXFc8FOWVYR+Q= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd h1:eDErF6V/JPJON/B7s68BxwHgfmyOntHJQ8IOaz0x4R8= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= @@ -671,25 +681,40 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M= github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 h1:Q3tB+ExeflWUW7AFcAhXqk40s9mnNYLk1nOkKNZ5GnU= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgtype v1.8.0 h1:iFVCcVhYlw0PulYCVoguRGm0SE9guIcPcccnLzHj8bA= +github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/pgx/v4 v4.12.0 h1:xiP3TdnkwyslWNp77yE5XAPfxAsU9RMFDe0c1SwN8h4= +github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc= github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -709,8 +734,9 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -769,15 +795,13 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= -github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.0 h1:ewg3ByWrdUrxrje8ChPVMBNcotg7H9LQYg+u5De2RzI= github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8 h1:1omo92DLtxQu6VwVPSZAmduHaK5zssed6cvkHyl1XOg= -github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ= github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ= @@ -796,8 +820,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= -github.com/markbates/goth v1.67.1 h1:gU5B0pzHVyhnJPwGynfFnkfvaQ39C1Sy+ewdl+bhAOw= -github.com/markbates/goth v1.67.1/go.mod h1:EyLFHGU5ySr2GXRDyJH5nu2dA7parbC8QwIYW/rGcWg= +github.com/markbates/goth v1.68.0 h1:90sKvjRAKHcl9V2uC9x/PJXeD78cFPiBsyP1xVhoQfA= +github.com/markbates/goth v1.68.0/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -812,17 +836,19 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 h1:QASJXOGm2RZ5Ardbc86qNFvby9AqkLDibfChMtAg5QM= @@ -834,6 +860,8 @@ github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk= github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= +github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY= +github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= @@ -998,7 +1026,8 @@ github.com/quasoft/websspi v1.0.0 h1:5nDgdM5xSur9s+B5w2xQ5kxf5nUGqgFgU4W0aDLZ8Mw github.com/quasoft/websspi v1.0.0/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1022,8 +1051,10 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -1115,8 +1146,8 @@ github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0 github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs= github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae/go.mod h1:1fdkY6xxl6ExVs2QFv7R0F5IRZHKA8RahhB9fMC9RvM= -github.com/unrolled/render v1.0.3 h1:baO+NG1bZSF2WR4zwh+0bMWauWky7DVrTOfvE2w+aFo= -github.com/unrolled/render v1.0.3/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= +github.com/unrolled/render v1.1.1 h1:FpzNzkvlJQIlVdVaqeVBGWiCS8gpbmjtrKpDmCn6p64= +github.com/unrolled/render v1.1.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= @@ -1145,13 +1176,15 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.2 h1:YjHC5TgyMmHpicTgEqDN0Q96Xo8K6tLXPnmNOHXCgs0= -github.com/yuin/goldmark v1.3.2/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0= +github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio= github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo= github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM= github.com/yuin/goldmark-meta v1.0.0/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/zeripath/jwt v3.2.2-go1.14+incompatible h1:jqxA3KuCQRLn0lHdt1G8t1EUJ92FmRUFnXHghVvJLJs= +github.com/zeripath/jwt v3.2.2-go1.14+incompatible/go.mod h1:pYPrRXN84mQC6u5c/08icdKllASIBEOurvsTPbDurLs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1222,6 +1255,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1231,8 +1265,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1321,8 +1357,9 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1418,8 +1455,9 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 h1:V066+OYJ66oTjnhm4Yrn7SXIwSCiDQJxpBxmvqb1N1c= -golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1429,8 +1467,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1501,6 +1540,7 @@ golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1667,6 +1707,36 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= +modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= +modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= +modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= +modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w= +modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A= +modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/tcl v1.5.5 h1:N03RwthgTR/l/eQvz3UjfYnvVVj1G2sZqzFGfoD4HE4= +modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= +modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A= mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -1680,5 +1750,5 @@ xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= -xorm.io/xorm v1.0.7 h1:26yBTDVI+CfQpVz2Y88fISh+aiJXIPP4eNoTJlwzsC4= -xorm.io/xorm v1.0.7/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= +xorm.io/xorm v1.2.2 h1:FFBOEvJ++8fYBA9cywf2sxDVmFktl1SpJzTAG1ab06Y= +xorm.io/xorm v1.2.2/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0= diff --git a/integrations/api_helper_for_declarative_test.go b/integrations/api_helper_for_declarative_test.go index 913fce1577..2edf80123d 100644 --- a/integrations/api_helper_for_declarative_test.go +++ b/integrations/api_helper_for_declarative_test.go @@ -239,6 +239,26 @@ func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBra } } +func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) { + return func(t *testing.T) (api.PullRequest, error) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s", + owner, repo, index, ctx.Token) + req := NewRequest(t, http.MethodGet, urlStr) + + expected := 200 + if ctx.ExpectedCode != 0 { + expected = ctx.ExpectedCode + } + resp := ctx.Session.MakeRequest(t, req, expected) + + json := jsoniter.ConfigCompatibleWithStandardLibrary + decoder := json.NewDecoder(resp.Body) + pr := api.PullRequest{} + err := decoder.Decode(&pr) + return pr, err + } +} + func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) { return func(t *testing.T) { urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", diff --git a/integrations/api_oauth2_apps_test.go b/integrations/api_oauth2_apps_test.go index 998043a6fb..0ba56b6c9f 100644 --- a/integrations/api_oauth2_apps_test.go +++ b/integrations/api_oauth2_apps_test.go @@ -92,6 +92,10 @@ func testAPIDeleteOAuth2Application(t *testing.T) { session.MakeRequest(t, req, http.StatusNoContent) models.AssertNotExistsBean(t, &models.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name}) + + // Delete again will return not found + req = NewRequest(t, "DELETE", urlStr) + session.MakeRequest(t, req, http.StatusNotFound) } func testAPIGetOAuth2Application(t *testing.T) { diff --git a/integrations/api_repo_edit_test.go b/integrations/api_repo_edit_test.go index c1b513d075..618c1f0ad0 100644 --- a/integrations/api_repo_edit_test.go +++ b/integrations/api_repo_edit_test.go @@ -130,11 +130,14 @@ func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption { func TestAPIRepoEdit(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { + bFalse, bTrue := false, true + user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo + repo15 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) // empty repo repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo // Get user2's token @@ -286,9 +289,8 @@ func TestAPIRepoEdit(t *testing.T) { // Test making a repo public that is private repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.True(t, repo16.IsPrivate) - private := false repoEditOption = &api.EditRepoOption{ - Private: &private, + Private: &bFalse, } url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) @@ -296,11 +298,24 @@ func TestAPIRepoEdit(t *testing.T) { repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.False(t, repo16.IsPrivate) // Make it private again - private = true - repoEditOption.Private = &private + repoEditOption.Private = &bTrue req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) + // Test to change empty repo + assert.False(t, repo15.IsArchived) + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo15.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ + Archived: &bTrue, + }) + _ = session.MakeRequest(t, req, http.StatusOK) + repo15 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) + assert.True(t, repo15.IsArchived) + req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ + Archived: &bFalse, + }) + _ = session.MakeRequest(t, req, http.StatusOK) + // Test using org repo "user3/repo3" where user2 is a collaborator origRepoEditOption = getRepoEditOptionFromRepo(repo3) repoEditOption = getNewRepoEditOption(origRepoEditOption) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index cfd3b58d64..d600d20e48 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -223,7 +223,7 @@ func TestAPIViewRepo(t *testing.T) { DecodeJSON(t, resp, &repo) assert.EqualValues(t, 1, repo.ID) assert.EqualValues(t, "repo1", repo.Name) - assert.EqualValues(t, 2, repo.Releases) + assert.EqualValues(t, 1, repo.Releases) assert.EqualValues(t, 1, repo.OpenIssues) assert.EqualValues(t, 3, repo.OpenPulls) diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go index d893854470..46aa2b84ec 100644 --- a/integrations/api_team_test.go +++ b/integrations/api_team_test.go @@ -144,7 +144,9 @@ func TestAPITeamSearch(t *testing.T) { var results TeamSearchResults session := loginUser(t, user.Name) + csrf := GetCSRF(t, session, "/"+org.Name) req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team") + req.Header.Add("X-Csrf-Token", csrf) resp := session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &results) assert.NotEmpty(t, results.Data) @@ -154,7 +156,9 @@ func TestAPITeamSearch(t *testing.T) { // no access if not organization member user5 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) session = loginUser(t, user5.Name) + csrf = GetCSRF(t, session, "/"+org.Name) req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team") + req.Header.Add("X-Csrf-Token", csrf) resp = session.MakeRequest(t, req, http.StatusForbidden) } diff --git a/integrations/attachment_test.go b/integrations/attachment_test.go index a28e38b990..052f27605a 100644 --- a/integrations/attachment_test.go +++ b/integrations/attachment_test.go @@ -122,7 +122,7 @@ func TestGetAttachment(t *testing.T) { t.Run(tc.name, func(t *testing.T) { //Write empty file to be available for response if tc.createFile { - _, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world")) + _, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"), -1) assert.NoError(t, err) } //Actual test diff --git a/integrations/auth_ldap_test.go b/integrations/auth_ldap_test.go index 4d82c092e7..59f5195123 100644 --- a/integrations/auth_ldap_test.go +++ b/integrations/auth_ldap_test.go @@ -144,6 +144,60 @@ func TestLDAPUserSignin(t *testing.T) { assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text()) } +func TestLDAPAuthChange(t *testing.T) { + defer prepareTestEnv(t)() + addAuthSourceLDAP(t, "") + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/auths") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + href, exists := doc.Find("table.table td a").Attr("href") + if !exists { + assert.True(t, exists, "No authentication source found") + return + } + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + csrf := doc.GetCSRF() + host, _ := doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com") + + req = NewRequestWithValues(t, "POST", href, map[string]string{ + "_csrf": csrf, + "type": "2", + "name": "ldap", + "host": getLDAPServerHost(), + "port": "389", + "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", + "bind_password": "password", + "user_base": "ou=people,dc=planetexpress,dc=com", + "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", + "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", + "restricted_filter": "(uid=leela)", + "attribute_username": "uid", + "attribute_name": "givenName", + "attribute_surname": "sn", + "attribute_mail": "mail", + "attribute_ssh_public_key": "", + "is_sync_enabled": "on", + "is_active": "on", + }) + session.MakeRequest(t, req, http.StatusFound) + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + host, _ = doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com") +} + func TestLDAPUserSync(t *testing.T) { if skipLDAPTests() { t.Skip() diff --git a/integrations/git_smart_http_test.go b/integrations/git_smart_http_test.go new file mode 100644 index 0000000000..9a4e3689c1 --- /dev/null +++ b/integrations/git_smart_http_test.go @@ -0,0 +1,69 @@ +// 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 integrations + +import ( + "io/ioutil" + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGitSmartHTTP(t *testing.T) { + onGiteaRun(t, testGitSmartHTTP) +} + +func testGitSmartHTTP(t *testing.T, u *url.URL) { + var kases = []struct { + p string + code int + }{ + { + p: "user2/repo1/info/refs", + code: 200, + }, + { + p: "user2/repo1/HEAD", + code: 200, + }, + { + p: "user2/repo1/objects/info/alternates", + code: 404, + }, + { + p: "user2/repo1/objects/info/http-alternates", + code: 404, + }, + { + p: "user2/repo1/../../custom/conf/app.ini", + code: 404, + }, + { + p: "user2/repo1/objects/info/../../../../custom/conf/app.ini", + code: 404, + }, + { + p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, + code: 400, + }, + } + + for _, kase := range kases { + t.Run(kase.p, func(t *testing.T) { + p := u.String() + kase.p + req, err := http.NewRequest("GET", p, nil) + assert.NoError(t, err) + req.SetBasicAuth("user2", userPassword) + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + assert.EqualValues(t, kase.code, resp.StatusCode) + _, err = ioutil.ReadAll(resp.Body) + assert.NoError(t, err) + }) + } +} diff --git a/integrations/git_test.go b/integrations/git_test.go index d8e7c4fe51..b7a3f5b276 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -5,6 +5,7 @@ package integrations import ( + "encoding/hex" "fmt" "io/ioutil" "math/rand" @@ -208,13 +209,13 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s // Request raw paths req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little)) - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, littleSize, resp.Body.Len()) + resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, littleSize, resp.Length) setting.CheckLFSVersion() if setting.LFS.StartServer { req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) - resp = session.MakeRequest(t, req, http.StatusOK) + resp := session.MakeRequest(t, req, http.StatusOK) assert.NotEqual(t, littleSize, resp.Body.Len()) assert.LessOrEqual(t, resp.Body.Len(), 1024) if resp.Body.Len() != littleSize && resp.Body.Len() <= 1024 { @@ -224,12 +225,12 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s if !testing.Short() { req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big)) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, bigSize, resp.Body.Len()) + resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, bigSize, resp.Length) if setting.LFS.StartServer { req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS)) - resp = session.MakeRequest(t, req, http.StatusOK) + resp := session.MakeRequest(t, req, http.StatusOK) assert.NotEqual(t, bigSize, resp.Body.Len()) if resp.Body.Len() != bigSize && resp.Body.Len() <= 1024 { assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier) @@ -450,27 +451,35 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) // Then get the diff string - var diffStr string + var diffHash string + var diffLength int t.Run("GetDiff", func(t *testing.T) { req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index)) - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) - diffStr = resp.Body.String() + resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) + diffHash = string(resp.Hash.Sum(nil)) + diffLength = resp.Length }) // Now: Merge the PR & make sure that doesn't break the PR page or change its diff t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)) t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) - t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr)) + t.Run("CheckPR", func(t *testing.T) { + oldMergeBase := pr.MergeBase + pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) + assert.NoError(t, err) + assert.Equal(t, oldMergeBase, pr2.MergeBase) + }) + t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) // Then: Delete the head branch & make sure that doesn't break the PR page or change its diff t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch)) t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) - t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr)) + t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) // Delete the head repository & make sure that doesn't break the PR page or change its diff t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx)) t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) - t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffStr)) + t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) } } @@ -514,20 +523,15 @@ func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing. } } -func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffStr string) func(t *testing.T) { +func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) { return func(t *testing.T) { req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) - expectedMaxLen := len(diffStr) - if expectedMaxLen > 800 { - expectedMaxLen = 800 - } - actual := resp.Body.String() - actualMaxLen := len(actual) - if actualMaxLen > 800 { - actualMaxLen = 800 - } - assert.Equal(t, diffStr, actual, "Unexpected change in the diff string: expected: %s but was actually: %s", diffStr[:expectedMaxLen], actual[:actualMaxLen]) + resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) + actual := string(resp.Hash.Sum(nil)) + actualLength := resp.Length + + equal := diffHash == actual + assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength) } } diff --git a/integrations/goget_test.go b/integrations/goget_test.go new file mode 100644 index 0000000000..1003d71023 --- /dev/null +++ b/integrations/goget_test.go @@ -0,0 +1,35 @@ +// 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 integrations + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" +) + +func TestGoGet(t *testing.T) { + defer prepareTestEnv(t)() + + req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1") + resp := MakeRequest(t, req, http.StatusOK) + + expected := fmt.Sprintf(` + + + + + + + go get --insecure %[1]s:%[2]s/blah/glah + + +`, setting.Domain, setting.HTTPPort, setting.AppURL) + + assert.Equal(t, expected, resp.Body.String()) +} diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 10331a1560..74227416c4 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -9,6 +9,8 @@ import ( "context" "database/sql" "fmt" + "hash" + "hash/fnv" "io" "net/http" "net/http/cookiejar" @@ -58,6 +60,26 @@ func NewNilResponseRecorder() *NilResponseRecorder { } } +type NilResponseHashSumRecorder struct { + httptest.ResponseRecorder + Hash hash.Hash + Length int +} + +func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) { + _, _ = n.Hash.Write(b) + n.Length += len(b) + return len(b), nil +} + +// NewRecorder returns an initialized ResponseRecorder. +func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder { + return &NilResponseHashSumRecorder{ + Hash: fnv.New32(), + ResponseRecorder: *httptest.NewRecorder(), + } +} + func TestMain(m *testing.M) { defer log.Close() @@ -284,6 +306,23 @@ func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, req *http.Req return resp } +func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder { + t.Helper() + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + for _, c := range s.jar.Cookies(baseURL) { + req.AddCookie(c) + } + resp := MakeRequestNilResponseHashSumRecorder(t, req, expectedStatus) + + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} + s.jar.SetCookies(baseURL, cr.Cookies()) + + return resp +} + const userPassword = "password" var loginSessionCache = make(map[string]*TestSession, 10) @@ -429,6 +468,19 @@ func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedSta return recorder } +func MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder { + t.Helper() + recorder := NewNilResponseHashSumRecorder() + c.ServeHTTP(recorder, req) + if expectedStatus != NoExpectedStatus { + if !assert.EqualValues(t, expectedStatus, recorder.Code, + "Request: %s %s", req.Method, req.URL.String()) { + logUnexpectedResponse(t, &recorder.ResponseRecorder) + } + } + return recorder +} + // logUnexpectedResponse logs the contents of an unexpected response. func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) { t.Helper() diff --git a/integrations/release_test.go b/integrations/release_test.go index a14ad8434e..365bc04d87 100644 --- a/integrations/release_test.go +++ b/integrations/release_test.go @@ -10,9 +10,11 @@ import ( "testing" "time" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" "github.com/unknwon/i18n" ) @@ -83,7 +85,7 @@ func TestCreateRelease(t *testing.T) { session := loginUser(t, "user2") createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 2) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 3) } func TestCreateReleasePreRelease(t *testing.T) { @@ -92,7 +94,7 @@ func TestCreateReleasePreRelease(t *testing.T) { session := loginUser(t, "user2") createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 2) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 3) } func TestCreateReleaseDraft(t *testing.T) { @@ -101,7 +103,7 @@ func TestCreateReleaseDraft(t *testing.T) { session := loginUser(t, "user2") createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 2) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 3) } func TestCreateReleasePaging(t *testing.T) { @@ -127,3 +129,80 @@ func TestCreateReleasePaging(t *testing.T) { session2 := loginUser(t, "user4") checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", i18n.Tr("en", "repo.release.stable"), 10) } + +func TestViewReleaseListNoLogin(t *testing.T) { + defer prepareTestEnv(t)() + + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + + link := repo.Link() + "/releases" + + req := NewRequest(t, "GET", link) + rsp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, rsp.Body) + releases := htmlDoc.Find("#release-list li.ui.grid") + assert.Equal(t, 1, releases.Length()) + + links := make([]string, 0, 5) + releases.Each(func(i int, s *goquery.Selection) { + link, exist := s.Find(".release-list-title a").Attr("href") + if !exist { + return + } + links = append(links, link) + }) + + assert.EqualValues(t, []string{"/user2/repo1/releases/tag/v1.1"}, links) +} + +func TestViewReleaseListLogin(t *testing.T) { + defer prepareTestEnv(t)() + + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + + link := repo.Link() + "/releases" + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", link) + rsp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, rsp.Body) + releases := htmlDoc.Find("#release-list li.ui.grid") + assert.Equal(t, 2, releases.Length()) + + links := make([]string, 0, 5) + releases.Each(func(i int, s *goquery.Selection) { + link, exist := s.Find(".release-list-title a").Attr("href") + if !exist { + return + } + links = append(links, link) + }) + + assert.EqualValues(t, []string{"/user2/repo1/releases/tag/draft-release", + "/user2/repo1/releases/tag/v1.1"}, links) +} + +func TestViewTagsList(t *testing.T) { + defer prepareTestEnv(t)() + + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + + link := repo.Link() + "/tags" + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", link) + rsp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, rsp.Body) + tags := htmlDoc.Find(".tag-list tr") + assert.Equal(t, 2, tags.Length()) + + tagNames := make([]string, 0, 5) + tags.Each(func(i int, s *goquery.Selection) { + tagNames = append(tagNames, s.Find(".tag a.df.ac").Text()) + }) + + assert.EqualValues(t, []string{"delete-tag", "v1.1"}, tagNames) +} diff --git a/integrations/repo_branch_test.go b/integrations/repo_branch_test.go index de4e668987..af5c475ea7 100644 --- a/integrations/repo_branch_test.go +++ b/integrations/repo_branch_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" @@ -134,5 +135,13 @@ func TestCreateBranchInvalidCSRF(t *testing.T) { "_csrf": "fake_csrf", "new_branch_name": "test", }) - session.MakeRequest(t, req, http.StatusBadRequest) + resp := session.MakeRequest(t, req, http.StatusFound) + loc := resp.Header().Get("Location") + assert.Equal(t, setting.AppSubURL+"/", loc) + resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, + "Bad Request: Invalid CSRF token", + strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), + ) } diff --git a/models/action.go b/models/action.go index 49a6efdc98..2a84133bf8 100644 --- a/models/action.go +++ b/models/action.go @@ -382,7 +382,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { } if opts.Date != "" { - dateLow, err := time.Parse("2006-01-02", opts.Date) + dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation) if err != nil { log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err) } else { diff --git a/models/attachment.go b/models/attachment.go index 478b4cd0d2..e12609f810 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -85,7 +85,7 @@ func (a *Attachment) LinkedRepository() (*Repository, UnitType, error) { func NewAttachment(attach *Attachment, buf []byte, file io.Reader) (_ *Attachment, err error) { attach.UUID = gouuid.New().String() - size, err := storage.Attachments.Save(attach.RelativePath(), io.MultiReader(bytes.NewReader(buf), file)) + size, err := storage.Attachments.Save(attach.RelativePath(), io.MultiReader(bytes.NewReader(buf), file), -1) if err != nil { return nil, fmt.Errorf("Create: %v", err) } @@ -125,8 +125,8 @@ func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { } // GetAttachmentsByUUIDs returns attachment by given UUID list. -func GetAttachmentsByUUIDs(uuids []string) ([]*Attachment, error) { - return getAttachmentsByUUIDs(x, uuids) +func GetAttachmentsByUUIDs(ctx DBContext, uuids []string) ([]*Attachment, error) { + return getAttachmentsByUUIDs(ctx.e, uuids) } func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { @@ -183,12 +183,12 @@ func getAttachmentByReleaseIDFileName(e Engine, releaseID int64, fileName string // DeleteAttachment deletes the given attachment and optionally the associated file. func DeleteAttachment(a *Attachment, remove bool) error { - _, err := DeleteAttachments([]*Attachment{a}, remove) + _, err := DeleteAttachments(DefaultDBContext(), []*Attachment{a}, remove) return err } // DeleteAttachments deletes the given attachments and optionally the associated files. -func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { +func DeleteAttachments(ctx DBContext, attachments []*Attachment, remove bool) (int, error) { if len(attachments) == 0 { return 0, nil } @@ -198,7 +198,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { ids = append(ids, a.ID) } - cnt, err := x.In("id", ids).NoAutoCondition().Delete(attachments[0]) + cnt, err := ctx.e.In("id", ids).NoAutoCondition().Delete(attachments[0]) if err != nil { return 0, err } @@ -220,7 +220,7 @@ func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) { return 0, err } - return DeleteAttachments(attachments, remove) + return DeleteAttachments(DefaultDBContext(), attachments, remove) } // DeleteAttachmentsByComment deletes all attachments associated with the given comment. @@ -230,7 +230,7 @@ func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) { return 0, err } - return DeleteAttachments(attachments, remove) + return DeleteAttachments(DefaultDBContext(), attachments, remove) } // UpdateAttachment updates the given attachment in database @@ -238,6 +238,15 @@ func UpdateAttachment(atta *Attachment) error { return updateAttachment(x, atta) } +// UpdateAttachmentByUUID Updates attachment via uuid +func UpdateAttachmentByUUID(ctx DBContext, attach *Attachment, cols ...string) error { + if attach.UUID == "" { + return fmt.Errorf("Attachement uuid should not blank") + } + _, err := ctx.e.Where("uuid=?", attach.UUID).Cols(cols...).Update(attach) + return err +} + func updateAttachment(e Engine, atta *Attachment) error { var sess *xorm.Session if atta.ID != 0 && atta.UUID == "" { diff --git a/models/attachment_test.go b/models/attachment_test.go index e36a5977ee..fa7fd3471b 100644 --- a/models/attachment_test.go +++ b/models/attachment_test.go @@ -120,7 +120,7 @@ func TestUpdateAttachment(t *testing.T) { func TestGetAttachmentsByUUIDs(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - attachList, err := GetAttachmentsByUUIDs([]string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) + attachList, err := GetAttachmentsByUUIDs(DefaultDBContext(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) assert.NoError(t, err) assert.Equal(t, 2, len(attachList)) assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID) diff --git a/models/avatar.go b/models/avatar.go index 166ca337ca..de416a1dba 100644 --- a/models/avatar.go +++ b/models/avatar.go @@ -81,7 +81,7 @@ func LibravatarURL(email string) (*url.URL, error) { } // HashedAvatarLink returns an avatar link for a provided email -func HashedAvatarLink(email string) string { +func HashedAvatarLink(email string, size int) string { lowerEmail := strings.ToLower(strings.TrimSpace(email)) sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail))) _, _ = cache.GetString("Avatar:"+sum, func() (string, error) { @@ -96,6 +96,11 @@ func HashedAvatarLink(email string) string { // we don't care about any DB problem just return the lowerEmail return lowerEmail, nil } + has, err := sess.Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash)) + if has || err != nil { + // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time + return lowerEmail, nil + } _, _ = sess.Insert(emailHash) if err := sess.Commit(); err != nil { // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time @@ -103,6 +108,9 @@ func HashedAvatarLink(email string) string { } return lowerEmail, nil }) + if size > 0 { + return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) + "?size=" + strconv.Itoa(size) + } return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) } @@ -124,7 +132,7 @@ func SizedAvatarLink(email string, size int) string { // This is the slow path that would need to call LibravatarURL() which // does DNS lookups. Avoid it by issuing a redirect so we don't block // the template render with network requests. - return HashedAvatarLink(email) + return HashedAvatarLink(email, size) } else if !setting.DisableGravatar { // copy GravatarSourceURL, because we will modify its Path. copyOfGravatarSourceURL := *setting.GravatarSourceURL diff --git a/models/consistency.go b/models/consistency.go index 9d76d26bbb..db5811ccd9 100644 --- a/models/consistency.go +++ b/models/consistency.go @@ -141,6 +141,12 @@ func (milestone *Milestone) checkForConsistency(t *testing.T) { actual := getCount(t, x.Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID}) assert.EqualValues(t, milestone.NumClosedIssues, actual, "Unexpected number of closed issues for milestone %+v", milestone) + + completeness := 0 + if milestone.NumIssues > 0 { + completeness = milestone.NumClosedIssues * 100 / milestone.NumIssues + } + assert.Equal(t, completeness, milestone.Completeness) } func (label *Label) checkForConsistency(t *testing.T) { @@ -296,11 +302,15 @@ func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) { // DeleteOrphanedObjects delete subjects with have no existing refobject anymore func DeleteOrphanedObjects(subject, refobject, joinCond string) error { - _, err := x.In("id", builder.Select("`"+subject+"`.id"). + subQuery := builder.Select("`"+subject+"`.id"). From("`"+subject+"`"). Join("LEFT", "`"+refobject+"`", joinCond). - Where(builder.IsNull{"`" + refobject + "`.id"})). - Delete("`" + subject + "`") + Where(builder.IsNull{"`" + refobject + "`.id"}) + sql, args, err := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`").ToSQL() + if err != nil { + return err + } + _, err = x.Exec(append([]interface{}{sql}, args...)...) return err } @@ -338,7 +348,7 @@ func FixCommentTypeLabelWithEmptyLabel() (int64, error) { // CountCommentTypeLabelWithOutsideLabels count label comments with outside label func CountCommentTypeLabelWithOutsideLabels() (int64, error) { - return x.Where("comment.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND repository.owner_id != label.org_id))", CommentTypeLabel). + return x.Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel). Table("comment"). Join("inner", "label", "label.id = comment.label_id"). Join("inner", "issue", "issue.id = comment.issue_id "). @@ -354,8 +364,9 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) { FROM comment AS com INNER JOIN label ON com.label_id = label.id INNER JOIN issue on issue.id = com.issue_id + INNER JOIN repository ON issue.repo_id = repository.id WHERE - com.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repo.owner_id)) + com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)) ) AS il_too)`, CommentTypeLabel) if err != nil { return 0, err @@ -366,9 +377,9 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) { // CountIssueLabelWithOutsideLabels count label comments with outside label func CountIssueLabelWithOutsideLabels() (int64, error) { - return x.Where(builder.Expr("issue.repo_id != label.repo_id OR (label.repo_id = 0 AND repository.owner_id != label.org_id)")). + return x.Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")). Table("issue_label"). - Join("inner", "label", "issue_label.id = label.id "). + Join("inner", "label", "issue_label.label_id = label.id "). Join("inner", "issue", "issue.id = issue_label.issue_id "). Join("inner", "repository", "issue.repo_id = repository.id"). Count(new(IssueLabel)) @@ -380,11 +391,11 @@ func FixIssueLabelWithOutsideLabels() (int64, error) { SELECT il_too.id FROM ( SELECT il_too_too.id FROM issue_label AS il_too_too - INNER JOIN label ON il_too_too.id = label.id + INNER JOIN label ON il_too_too.label_id = label.id INNER JOIN issue on issue.id = il_too_too.issue_id INNER JOIN repository on repository.id = issue.repo_id WHERE - issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id) + (label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id) ) AS il_too )`) if err != nil { diff --git a/models/consistency_test.go b/models/consistency_test.go new file mode 100644 index 0000000000..0a91d9f3da --- /dev/null +++ b/models/consistency_test.go @@ -0,0 +1,32 @@ +// Copyright 2021 Gitea. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeleteOrphanedObjects(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + countBefore, err := x.Count(&PullRequest{}) + assert.NoError(t, err) + + _, err = x.Insert(&PullRequest{IssueID: 1000}, &PullRequest{IssueID: 1001}, &PullRequest{IssueID: 1003}) + assert.NoError(t, err) + + orphaned, err := CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id") + assert.NoError(t, err) + assert.EqualValues(t, 3, orphaned) + + err = DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id") + assert.NoError(t, err) + + countAfter, err := x.Count(&PullRequest{}) + assert.NoError(t, err) + assert.EqualValues(t, countBefore, countAfter) +} diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml index 8d3f5840ef..5e577d3fdd 100644 --- a/models/fixtures/release.yml +++ b/models/fixtures/release.yml @@ -43,3 +43,15 @@ is_tag: true created_unix: 946684800 +- + id: 4 + repo_id: 1 + publisher_id: 2 + tag_name: "draft-release" + lower_tag_name: "draft-release" + target: "master" + title: "draft-release" + is_draft: true + is_prerelease: false + is_tag: false + created_unix: 1619524806 diff --git a/models/issue.go b/models/issue.go index 6912df6c28..f115d76217 100644 --- a/models/issue.go +++ b/models/issue.go @@ -648,8 +648,10 @@ func (issue *Issue) doChangeStatus(e *xorm.Session, doer *User, isMergePull bool } // Update issue count of milestone - if err := updateMilestoneClosedNum(e, issue.MilestoneID); err != nil { - return nil, err + if issue.MilestoneID > 0 { + if err := updateMilestoneCounters(e, issue.MilestoneID); err != nil { + return nil, err + } } if err := issue.updateClosedNum(e); err != nil { @@ -912,7 +914,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { opts.Issue.Index = inserted.Index if opts.Issue.MilestoneID > 0 { - if _, err = e.Exec("UPDATE `milestone` SET num_issues=num_issues+1 WHERE id=?", opts.Issue.MilestoneID); err != nil { + if err := updateMilestoneCounters(e, opts.Issue.MilestoneID); err != nil { return err } @@ -1032,6 +1034,9 @@ func newIssueAttempt(repo *Repository, issue *Issue, labelIDs []int64, uuids []s // GetIssueByIndex returns raw issue without loading attributes by index in a repository. func GetIssueByIndex(repoID, index int64) (*Issue, error) { + if index < 1 { + return nil, ErrIssueNotExist{} + } issue := &Issue{ RepoID: repoID, Index: index, @@ -1086,7 +1091,7 @@ func getIssuesByIDs(e Engine, issueIDs []int64) ([]*Issue, error) { func getIssueIDsByRepoID(e Engine, repoID int64) ([]int64, error) { ids := make([]int64, 0, 10) - err := e.Table("issue").Where("repo_id = ?", repoID).Find(&ids) + err := e.Table("issue").Cols("id").Where("repo_id = ?", repoID).Find(&ids) return ids, err } diff --git a/models/issue_list.go b/models/issue_list.go index 0ac25fc690..87ad318dcf 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -53,6 +53,9 @@ func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) { for _, issue := range issues { issue.Repo = repoMaps[issue.RepoID] + if issue.PullRequest != nil { + issue.PullRequest.BaseRepo = issue.Repo + } } return valuesRepository(repoMaps), nil } @@ -516,6 +519,11 @@ func (issues IssueList) LoadDiscussComments() error { return issues.loadComments(x, builder.Eq{"comment.type": CommentTypeComment}) } +// LoadPullRequests loads pull requests +func (issues IssueList) LoadPullRequests() error { + return issues.loadPullRequests(x) +} + // GetApprovalCounts returns a map of issue ID to slice of approval counts // FIXME: only returns official counts due to double counting of non-official approvals func (issues IssueList) GetApprovalCounts() (map[int64][]*ReviewCount, error) { diff --git a/models/issue_milestone.go b/models/issue_milestone.go index ec3cbb91db..e639b50352 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -129,8 +129,12 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) // GetMilestoneByID returns the milestone via id . func GetMilestoneByID(id int64) (*Milestone, error) { + return getMilestoneByID(x, id) +} + +func getMilestoneByID(e Engine, id int64) (*Milestone, error) { var m Milestone - has, err := x.ID(id).Get(&m) + has, err := e.ID(id).Get(&m) if err != nil { return nil, err } else if !has { @@ -155,10 +159,6 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error { return err } - if err := updateMilestoneCompleteness(sess, m.ID); err != nil { - return err - } - // if IsClosed changed, update milestone numbers of repository if oldIsClosed != m.IsClosed { if err := updateRepoMilestoneNum(sess, m.RepoID); err != nil { @@ -171,23 +171,31 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error { func updateMilestone(e Engine, m *Milestone) error { m.Name = strings.TrimSpace(m.Name) - _, err := e.ID(m.ID).AllCols(). + _, err := e.ID(m.ID).AllCols().Update(m) + if err != nil { + return err + } + return updateMilestoneCounters(e, m.ID) +} + +// updateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness +func updateMilestoneCounters(e Engine, id int64) error { + _, err := e.ID(id). SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( - builder.Eq{"milestone_id": m.ID}, + builder.Eq{"milestone_id": id}, )). SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where( builder.Eq{ - "milestone_id": m.ID, + "milestone_id": id, "is_closed": true, }, )). - Update(m) - return err -} - -func updateMilestoneCompleteness(e Engine, milestoneID int64) error { - _, err := e.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?", - milestoneID, + Update(&Milestone{}) + if err != nil { + return err + } + _, err = e.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?", + id, ) return err } @@ -256,25 +264,15 @@ func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilesto } if oldMilestoneID > 0 { - if err := updateMilestoneTotalNum(e, oldMilestoneID); err != nil { + if err := updateMilestoneCounters(e, oldMilestoneID); err != nil { return err } - if issue.IsClosed { - if err := updateMilestoneClosedNum(e, oldMilestoneID); err != nil { - return err - } - } } if issue.MilestoneID > 0 { - if err := updateMilestoneTotalNum(e, issue.MilestoneID); err != nil { + if err := updateMilestoneCounters(e, issue.MilestoneID); err != nil { return err } - if issue.IsClosed { - if err := updateMilestoneClosedNum(e, issue.MilestoneID); err != nil { - return err - } - } } if oldMilestoneID > 0 || issue.MilestoneID > 0 { @@ -558,29 +556,6 @@ func updateRepoMilestoneNum(e Engine, repoID int64) error { return err } -func updateMilestoneTotalNum(e Engine, milestoneID int64) (err error) { - if _, err = e.Exec("UPDATE `milestone` SET num_issues=(SELECT count(*) FROM issue WHERE milestone_id=?) WHERE id=?", - milestoneID, - milestoneID, - ); err != nil { - return - } - - return updateMilestoneCompleteness(e, milestoneID) -} - -func updateMilestoneClosedNum(e Engine, milestoneID int64) (err error) { - if _, err = e.Exec("UPDATE `milestone` SET num_closed_issues=(SELECT count(*) FROM issue WHERE milestone_id=? AND is_closed=?) WHERE id=?", - milestoneID, - true, - milestoneID, - ); err != nil { - return - } - - return updateMilestoneCompleteness(e, milestoneID) -} - // _____ _ _ _____ _ // |_ _| __ __ _ ___| | _____ __| |_ _(_)_ __ ___ ___ ___ // | || '__/ _` |/ __| |/ / _ \/ _` | | | | | '_ ` _ \ / _ \/ __| diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index af264aa274..5406129884 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -215,7 +215,7 @@ func TestChangeMilestoneStatus(t *testing.T) { CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) } -func TestUpdateMilestoneClosedNum(t *testing.T) { +func TestUpdateMilestoneCounters(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) issue := AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1}, "is_closed=0").(*Issue) @@ -224,14 +224,14 @@ func TestUpdateMilestoneClosedNum(t *testing.T) { issue.ClosedUnix = timeutil.TimeStampNow() _, err := x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue) assert.NoError(t, err) - assert.NoError(t, updateMilestoneClosedNum(x, issue.MilestoneID)) + assert.NoError(t, updateMilestoneCounters(x, issue.MilestoneID)) CheckConsistencyFor(t, &Milestone{}) issue.IsClosed = false issue.ClosedUnix = 0 _, err = x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue) assert.NoError(t, err) - assert.NoError(t, updateMilestoneClosedNum(x, issue.MilestoneID)) + assert.NoError(t, updateMilestoneCounters(x, issue.MilestoneID)) CheckConsistencyFor(t, &Milestone{}) } diff --git a/models/issue_test.go b/models/issue_test.go index c21b1d6ae9..b612ab267b 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -36,6 +36,14 @@ func TestIssue_ReplaceLabels(t *testing.T) { testSuccess(1, []int64{}) } +func Test_GetIssueIDsByRepoID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + ids, err := GetIssueIDsByRepoID(1) + assert.NoError(t, err) + assert.Len(t, ids, 5) +} + func TestIssueAPIURL(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) diff --git a/models/list_options.go b/models/list_options.go index 9cccd05465..ff02933f9b 100644 --- a/models/list_options.go +++ b/models/list_options.go @@ -41,7 +41,7 @@ func (opts *ListOptions) setEnginePagination(e Engine) Engine { func (opts *ListOptions) GetStartEnd() (start, end int) { opts.setDefaultValues() start = (opts.Page - 1) * opts.PageSize - end = start + opts.Page + end = start + opts.PageSize return } diff --git a/models/log.go b/models/log.go index 942dbc744a..caeab094bd 100644 --- a/models/log.go +++ b/models/log.go @@ -26,6 +26,8 @@ func NewXORMLogger(showSQL bool) xormlog.Logger { } } +const stackLevel = 8 + // Log a message with defined skip and at logging level func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) error { return l.logger.Log(skip+1, level, format, v...) @@ -33,42 +35,42 @@ func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...inter // Debug show debug log func (l *XORMLogBridge) Debug(v ...interface{}) { - _ = l.Log(2, log.DEBUG, fmt.Sprint(v...)) + _ = l.Log(stackLevel, log.DEBUG, fmt.Sprint(v...)) } // Debugf show debug log func (l *XORMLogBridge) Debugf(format string, v ...interface{}) { - _ = l.Log(2, log.DEBUG, format, v...) + _ = l.Log(stackLevel, log.DEBUG, format, v...) } // Error show error log func (l *XORMLogBridge) Error(v ...interface{}) { - _ = l.Log(2, log.ERROR, fmt.Sprint(v...)) + _ = l.Log(stackLevel, log.ERROR, fmt.Sprint(v...)) } // Errorf show error log func (l *XORMLogBridge) Errorf(format string, v ...interface{}) { - _ = l.Log(2, log.ERROR, format, v...) + _ = l.Log(stackLevel, log.ERROR, format, v...) } // Info show information level log func (l *XORMLogBridge) Info(v ...interface{}) { - _ = l.Log(2, log.INFO, fmt.Sprint(v...)) + _ = l.Log(stackLevel, log.INFO, fmt.Sprint(v...)) } // Infof show information level log func (l *XORMLogBridge) Infof(format string, v ...interface{}) { - _ = l.Log(2, log.INFO, format, v...) + _ = l.Log(stackLevel, log.INFO, format, v...) } // Warn show warning log func (l *XORMLogBridge) Warn(v ...interface{}) { - _ = l.Log(2, log.WARN, fmt.Sprint(v...)) + _ = l.Log(stackLevel, log.WARN, fmt.Sprint(v...)) } // Warnf show warnning log func (l *XORMLogBridge) Warnf(format string, v ...interface{}) { - _ = l.Log(2, log.WARN, format, v...) + _ = l.Log(stackLevel, log.WARN, format, v...) } // Level get logger level diff --git a/models/login_source.go b/models/login_source.go index fd977e20a5..a8b569fff0 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -7,6 +7,7 @@ package models import ( "crypto/tls" + "encoding/binary" "errors" "fmt" "net/smtp" @@ -21,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + gouuid "github.com/google/uuid" jsoniter "github.com/json-iterator/go" "xorm.io/xorm" @@ -68,6 +70,36 @@ var ( _ convert.Conversion = &SSPIConfig{} ) +// jsonUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's +// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe. +func jsonUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error { + json := jsoniter.ConfigCompatibleWithStandardLibrary + err := json.Unmarshal(bs, v) + if err != nil { + ok := true + rs := []byte{} + temp := make([]byte, 2) + for _, rn := range string(bs) { + if rn > 0xffff { + ok = false + break + } + binary.LittleEndian.PutUint16(temp, uint16(rn)) + rs = append(rs, temp...) + } + if ok { + if rs[0] == 0xff && rs[1] == 0xfe { + rs = rs[2:] + } + err = json.Unmarshal(rs, v) + } + } + if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe { + err = json.Unmarshal(bs[2:], v) + } + return err +} + // LDAPConfig holds configuration for LDAP login source. type LDAPConfig struct { *ldap.Source @@ -75,8 +107,7 @@ type LDAPConfig struct { // FromDB fills up a LDAPConfig from serialized format. func (cfg *LDAPConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, &cfg) } // ToDB exports a LDAPConfig to a serialized format. @@ -103,8 +134,7 @@ type SMTPConfig struct { // FromDB fills up an SMTPConfig from serialized format. func (cfg *SMTPConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, cfg) + return jsonUnmarshalHandleDoubleEncode(bs, cfg) } // ToDB exports an SMTPConfig to a serialized format. @@ -116,12 +146,12 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) { // PAMConfig holds configuration for the PAM login source. type PAMConfig struct { ServiceName string // pam service (e.g. system-auth) + EmailDomain string } // FromDB fills up a PAMConfig from serialized format. func (cfg *PAMConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, cfg) } // ToDB exports a PAMConfig to a serialized format. @@ -142,8 +172,7 @@ type OAuth2Config struct { // FromDB fills up an OAuth2Config from serialized format. func (cfg *OAuth2Config) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, cfg) + return jsonUnmarshalHandleDoubleEncode(bs, cfg) } // ToDB exports an SMTPConfig to a serialized format. @@ -163,8 +192,7 @@ type SSPIConfig struct { // FromDB fills up an SSPIConfig from serialized format. func (cfg *SSPIConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, cfg) + return jsonUnmarshalHandleDoubleEncode(bs, cfg) } // ToDB exports an SSPIConfig to a serialized format. @@ -696,15 +724,26 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon // Allow PAM sources with `@` in their name, like from Active Directory username := pamLogin + email := pamLogin idx := strings.Index(pamLogin, "@") if idx > -1 { username = pamLogin[:idx] } + if ValidateEmail(email) != nil { + if cfg.EmailDomain != "" { + email = fmt.Sprintf("%s@%s", username, cfg.EmailDomain) + } else { + email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress) + } + if ValidateEmail(email) != nil { + email = gouuid.New().String() + "@localhost" + } + } user = &User{ LowerName: strings.ToLower(username), Name: username, - Email: pamLogin, + Email: email, Passwd: password, LoginType: LoginPAM, LoginSource: sourceID, diff --git a/models/migrate.go b/models/migrate.go index 9e44f32e54..28b6707473 100644 --- a/models/migrate.go +++ b/models/migrate.go @@ -39,6 +39,7 @@ func InsertMilestones(ms ...*Milestone) (err error) { // InsertIssues insert issues to database func InsertIssues(issues ...*Issue) error { sess := x.NewSession() + defer sess.Close() if err := sess.Begin(); err != nil { return err } @@ -194,6 +195,7 @@ func InsertPullRequests(prs ...*PullRequest) error { // InsertReleases migrates release func InsertReleases(rels ...*Release) error { sess := x.NewSession() + defer sess.Close() if err := sess.Begin(); err != nil { return err } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 2c85ebdfd7..ab1a41d4ce 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -565,11 +565,26 @@ func recreateTable(sess *xorm.Session, bean interface{}) error { return err } + if err := sess.Table(tempTableName).DropIndexes(bean); err != nil { + log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err) + return err + } + // SQLite and MySQL will move all the constraints from the temporary table to the new table if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil { log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) return err } + + if err := sess.Table(tableName).CreateIndexes(bean); err != nil { + log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err) + return err + } + + if err := sess.Table(tableName).CreateUniques(bean); err != nil { + log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err) + return err + } case setting.Database.UsePostgreSQL: var originalSequences []string type sequenceData struct { diff --git a/models/migrations/v156.go b/models/migrations/v156.go index 071735f704..7158d7bb6b 100644 --- a/models/migrations/v156.go +++ b/models/migrations/v156.go @@ -88,6 +88,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error { repo = new(Repository) has, err := sess.ID(release.RepoID).Get(repo) if err != nil { + log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err) return err } else if !has { log.Warn("Release[%d] is orphaned and refers to non-existing repository %d", release.ID, release.RepoID) @@ -99,28 +100,55 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error { // v120.go migration may not have been run correctly - we'll just replicate it here // because this appears to be a common-ish problem. if _, err := sess.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)"); err != nil { + log.Error("Error whilst updating repository[%d] owner name", repo.ID) return err } if _, err := sess.ID(release.RepoID).Get(repo); err != nil { + log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err) return err } } gitRepo, err = git.OpenRepository(repoPath(repo.OwnerName, repo.Name)) if err != nil { + log.Error("Error whilst opening git repo for [%d]%s/%s. Error: %v", repo.ID, repo.OwnerName, repo.Name, err) return err } } commit, err := gitRepo.GetTagCommit(release.TagName) if err != nil { + if git.IsErrNotExist(err) { + log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name) + continue + } + log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err) return fmt.Errorf("GetTagCommit: %v", err) } + if commit.Author.Email == "" { + log.Warn("Tag: %s in Repo[%d]%s/%s does not have a tagger.", release.TagName, repo.ID, repo.OwnerName, repo.Name) + commit, err = gitRepo.GetCommit(commit.ID.String()) + if err != nil { + if git.IsErrNotExist(err) { + log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name) + continue + } + log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err) + return fmt.Errorf("GetCommit: %v", err) + } + } + + if commit.Author.Email == "" { + log.Warn("Tag: %s in Repo[%d]%s/%s does not have a Tagger and its underlying commit does not have an Author either!", release.TagName, repo.ID, repo.OwnerName, repo.Name) + continue + } + if user == nil || !strings.EqualFold(user.Email, commit.Author.Email) { user = new(User) _, err = sess.Where("email=?", commit.Author.Email).Get(user) if err != nil { + log.Error("Error whilst getting commit author by email: %s for Tag: %s in [%d]%s/%s. Error: %v", commit.Author.Email, release.TagName, repo.ID, repo.OwnerName, repo.Name, err) return err } @@ -133,6 +161,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error { release.PublisherID = user.ID if _, err := sess.ID(release.ID).Cols("publisher_id").Update(release); err != nil { + log.Error("Error whilst updating publisher[%d] for release[%d] with tag name %s. Error: %v", release.PublisherID, release.ID, release.TagName, err) return err } } diff --git a/models/migrations/v161.go b/models/migrations/v161.go index 4ca9f01218..283c44464c 100644 --- a/models/migrations/v161.go +++ b/models/migrations/v161.go @@ -5,6 +5,8 @@ package migrations import ( + "context" + "xorm.io/xorm" ) @@ -40,8 +42,17 @@ func convertTaskTypeToString(x *xorm.Engine) error { return err } + // to keep the migration could be rerun + exist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "hook_task", "type") + if err != nil { + return err + } + if !exist { + return nil + } + for i, s := range hookTaskTypes { - if _, err := x.Exec("UPDATE hook_task set typ = ? where type=?", s, i); err != nil { + if _, err := x.Exec("UPDATE hook_task set typ = ? where `type`=?", s, i); err != nil { return err } } diff --git a/models/migrations/v172.go b/models/migrations/v172.go index 51f83bcc91..125522a4b8 100644 --- a/models/migrations/v172.go +++ b/models/migrations/v172.go @@ -12,9 +12,9 @@ import ( func addSessionTable(x *xorm.Engine) error { type Session struct { - Key string `xorm:"pk CHAR(16)"` - Data []byte `xorm:"BLOB"` - CreatedUnix timeutil.TimeStamp + Key string `xorm:"pk CHAR(16)"` + Data []byte `xorm:"BLOB"` + Expiry timeutil.TimeStamp } return x.Sync2(new(Session)) } diff --git a/models/migrations/v176.go b/models/migrations/v176.go index ff6587508d..cec23e08f4 100644 --- a/models/migrations/v176.go +++ b/models/migrations/v176.go @@ -48,11 +48,11 @@ func removeInvalidLabels(x *xorm.Engine) error { SELECT il_too.id FROM ( SELECT il_too_too.id FROM issue_label AS il_too_too - INNER JOIN label ON il_too_too.id = label.id + INNER JOIN label ON il_too_too.label_id = label.id INNER JOIN issue on issue.id = il_too_too.issue_id INNER JOIN repository on repository.id = issue.repo_id WHERE - issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id) + (label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id) ) AS il_too )`); err != nil { return err } @@ -65,7 +65,7 @@ func removeInvalidLabels(x *xorm.Engine) error { INNER JOIN issue on issue.id = com.issue_id INNER JOIN repository on repository.id = issue.repo_id WHERE - com.type = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != repository.owner_id)) + com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)) ) AS il_too)`, 7); err != nil { return err } diff --git a/models/models.go b/models/models.go index 05cafccd14..3dedbadd89 100644 --- a/models/models.go +++ b/models/models.go @@ -33,7 +33,7 @@ type Engine interface { Table(tableNameOrBean interface{}) *xorm.Session Count(...interface{}) (int64, error) Decr(column string, arg ...interface{}) *xorm.Session - Delete(interface{}) (int64, error) + Delete(...interface{}) (int64, error) Exec(...interface{}) (sql.Result, error) Find(interface{}, ...interface{}) error Get(interface{}) (bool, error) @@ -319,7 +319,7 @@ func DumpDatabase(filePath, dbType string) error { ID int64 `xorm:"pk autoincr"` Version int64 } - t, err := x.TableInfo(Version{}) + t, err := x.TableInfo(&Version{}) if err != nil { return err } diff --git a/models/models_test.go b/models/models_test.go index 2441ad7fb0..626856df7d 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -8,9 +8,12 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" + "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/setting" + "xorm.io/xorm/schemas" "github.com/stretchr/testify/assert" ) @@ -25,10 +28,33 @@ func TestDumpDatabase(t *testing.T) { ID int64 `xorm:"pk autoincr"` Version int64 } - assert.NoError(t, x.Sync2(Version{})) + assert.NoError(t, x.Sync2(new(Version))) for _, dbName := range setting.SupportedDatabases { dbType := setting.GetDBTypeByName(dbName) assert.NoError(t, DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType)) } } + +func TestDumpLoginSource(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + loginSourceSchema, err := x.TableInfo(new(LoginSource)) + assert.NoError(t, err) + + CreateLoginSource(&LoginSource{ + Type: LoginOAuth2, + Name: "TestSource", + IsActived: false, + Cfg: &OAuth2Config{ + Provider: "TestSourceProvider", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }) + + sb := new(strings.Builder) + + x.DumpTables([]*schemas.Table{loginSourceSchema}, sb) + + assert.Contains(t, sb.String(), `"Provider":"TestSourceProvider"`) +} diff --git a/models/oauth2_application.go b/models/oauth2_application.go index 1b544e4e9e..3581f79004 100644 --- a/models/oauth2_application.go +++ b/models/oauth2_application.go @@ -17,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/dgrijalva/jwt-go" + "github.com/golang-jwt/jwt" uuid "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "xorm.io/xorm" @@ -235,7 +235,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error { if deleted, err := sess.Delete(&OAuth2Application{ID: id, UID: userid}); err != nil { return err } else if deleted == 0 { - return fmt.Errorf("cannot find oauth2 application") + return ErrOAuthApplicationNotFound{ID: id} } codes := make([]*OAuth2AuthorizationCode, 0) // delete correlating auth codes @@ -261,6 +261,7 @@ func deleteOAuth2Application(sess *xorm.Session, id, userid int64) error { // DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app. func DeleteOAuth2Application(id, userid int64) error { sess := x.NewSession() + defer sess.Close() if err := sess.Begin(); err != nil { return err } diff --git a/models/pull.go b/models/pull.go index 47e699e192..a1fd7c3e41 100644 --- a/models/pull.go +++ b/models/pull.go @@ -212,12 +212,21 @@ func (pr *PullRequest) GetDefaultMergeMessage() string { log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err) return "" } - - if pr.BaseRepoID == pr.HeadRepoID { - return fmt.Sprintf("Merge pull request '%s' (#%d) from %s into %s", pr.Issue.Title, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch) + if err := pr.LoadBaseRepo(); err != nil { + log.Error("LoadBaseRepo: %v", err) + return "" } - return fmt.Sprintf("Merge pull request '%s' (#%d) from %s:%s into %s", pr.Issue.Title, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch) + issueReference := "#" + if pr.BaseRepo.UnitEnabled(UnitTypeExternalTracker) { + issueReference = "!" + } + + if pr.BaseRepoID == pr.HeadRepoID { + return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch) + } + + return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch) } // ReviewCount represents a count of Reviews @@ -406,7 +415,8 @@ func (pr *PullRequest) SetMerged() (bool, error) { return false, fmt.Errorf("Issue.changeStatus: %v", err) } - if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil { + // We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging. + if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil { return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err) } diff --git a/models/pull_test.go b/models/pull_test.go index 3cc6abfec7..5eaeb60e67 100644 --- a/models/pull_test.go +++ b/models/pull_test.go @@ -234,3 +234,36 @@ func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) { pr.Issue.Title = "[wip] " + original assert.Equal(t, "[wip]", pr.GetWorkInProgressPrefix()) } + +func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) + + assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage()) + + pr.BaseRepoID = 1 + pr.HeadRepoID = 2 + assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage()) +} + +func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + externalTracker := RepoUnit{ + Type: UnitTypeExternalTracker, + Config: &ExternalTrackerConfig{ + ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}", + }, + } + baseRepo := &Repository{Name: "testRepo", ID: 1} + baseRepo.Owner = &User{Name: "testOwner"} + baseRepo.Units = []*RepoUnit{&externalTracker} + + pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest) + + assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage()) + + pr.BaseRepoID = 1 + pr.HeadRepoID = 2 + assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage()) +} diff --git a/models/release.go b/models/release.go index 751d08d4d8..13b8f17218 100644 --- a/models/release.go +++ b/models/release.go @@ -6,6 +6,7 @@ package models import ( + "errors" "fmt" "sort" "strings" @@ -117,17 +118,20 @@ func UpdateRelease(ctx DBContext, rel *Release) error { } // AddReleaseAttachments adds a release attachments -func AddReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) { +func AddReleaseAttachments(ctx DBContext, releaseID int64, attachmentUUIDs []string) (err error) { // Check attachments - attachments, err := GetAttachmentsByUUIDs(attachmentUUIDs) + attachments, err := getAttachmentsByUUIDs(ctx.e, attachmentUUIDs) if err != nil { return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err) } for i := range attachments { + if attachments[i].ReleaseID != 0 { + return errors.New("release permission denied") + } attachments[i].ReleaseID = releaseID // No assign value could be 0, so ignore AllCols(). - if _, err = x.ID(attachments[i].ID).Update(attachments[i]); err != nil { + if _, err = ctx.e.ID(attachments[i].ID).Update(attachments[i]); err != nil { return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err) } } diff --git a/models/repo.go b/models/repo.go index 329e63cc42..f0e451be4e 100644 --- a/models/repo.go +++ b/models/repo.go @@ -749,7 +749,7 @@ func (repo *Repository) updateSize(e Engine) error { } repo.Size = size - _, err = e.ID(repo.ID).Cols("size").Update(repo) + _, err = e.ID(repo.ID).Cols("size").NoAutoTime().Update(repo) return err } @@ -1215,7 +1215,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err } newRepoPath := RepoPath(repo.Owner.Name, newRepoName) - if err = os.Rename(repo.RepoPath(), newRepoPath); err != nil { + if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil { return fmt.Errorf("rename repository directory: %v", err) } @@ -1226,7 +1226,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err return err } if isExist { - if err = os.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil { + if err = util.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil { return fmt.Errorf("rename repository wiki: %v", err) } } @@ -1349,6 +1349,26 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { return sess.Commit() } +// UpdateRepositoryOwnerNames updates repository owner_names (this should only be used when the ownerName has changed case) +func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { + if ownerID == 0 { + return nil + } + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if _, err := sess.Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{ + OwnerName: ownerName, + }); err != nil { + return err + } + + return sess.Commit() +} + // UpdateRepositoryUpdatedTime updates a repository's updated time func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { _, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) @@ -1454,23 +1474,26 @@ func DeleteRepository(doer *User, uid, repoID int64) error { if err := deleteBeans(sess, &Access{RepoID: repo.ID}, &Action{RepoID: repo.ID}, - &Watch{RepoID: repoID}, - &Star{RepoID: repoID}, - &Mirror{RepoID: repoID}, - &Milestone{RepoID: repoID}, - &Release{RepoID: repoID}, &Collaboration{RepoID: repoID}, - &PullRequest{BaseRepoID: repoID}, - &RepoUnit{RepoID: repoID}, - &RepoRedirect{RedirectRepoID: repoID}, - &Webhook{RepoID: repoID}, - &HookTask{RepoID: repoID}, - &Notification{RepoID: repoID}, - &CommitStatus{RepoID: repoID}, - &RepoIndexerStatus{RepoID: repoID}, - &LanguageStat{RepoID: repoID}, &Comment{RefRepoID: repoID}, + &CommitStatus{RepoID: repoID}, + &DeletedBranch{RepoID: repoID}, + &HookTask{RepoID: repoID}, + &LFSLock{RepoID: repoID}, + &LanguageStat{RepoID: repoID}, + &Milestone{RepoID: repoID}, + &Mirror{RepoID: repoID}, + &Notification{RepoID: repoID}, + &ProtectedBranch{RepoID: repoID}, + &PullRequest{BaseRepoID: repoID}, + &Release{RepoID: repoID}, + &RepoIndexerStatus{RepoID: repoID}, + &RepoRedirect{RedirectRepoID: repoID}, + &RepoUnit{RepoID: repoID}, + &Star{RepoID: repoID}, &Task{RepoID: repoID}, + &Watch{RepoID: repoID}, + &Webhook{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %v", err) } @@ -1486,10 +1509,6 @@ func DeleteRepository(doer *User, uid, repoID int64) error { return err } - if _, err := sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil { - return err - } - if repo.IsFork { if _, err := sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { return fmt.Errorf("decrease fork count: %v", err) diff --git a/models/repo_list.go b/models/repo_list.go index 1e06f2511e..74bc256190 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm" ) // RepositoryListDefaultPageSize is the default number of repositories @@ -363,6 +364,35 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { // SearchRepositoryByCondition search repositories by condition func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) { + sess, count, err := searchRepositoryByCondition(opts, cond) + if err != nil { + return nil, 0, err + } + defer sess.Close() + + defaultSize := 50 + if opts.PageSize > 0 { + defaultSize = opts.PageSize + } + repos := make(RepositoryList, 0, defaultSize) + if err := sess.Find(&repos); err != nil { + return nil, 0, fmt.Errorf("Repo: %v", err) + } + + if opts.PageSize <= 0 { + count = int64(len(repos)) + } + + if loadAttributes { + if err := repos.loadAttributes(sess); err != nil { + return nil, 0, fmt.Errorf("LoadAttributes: %v", err) + } + } + + return repos, count, nil +} + +func searchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond) (*xorm.Session, int64, error) { if opts.Page <= 0 { opts.Page = 1 } @@ -376,31 +406,24 @@ func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loa } sess := x.NewSession() - defer sess.Close() - count, err := sess. - Where(cond). - Count(new(Repository)) - if err != nil { - return nil, 0, fmt.Errorf("Count: %v", err) + var count int64 + if opts.PageSize > 0 { + var err error + count, err = sess. + Where(cond). + Count(new(Repository)) + if err != nil { + _ = sess.Close() + return nil, 0, fmt.Errorf("Count: %v", err) + } } - repos := make(RepositoryList, 0, opts.PageSize) sess.Where(cond).OrderBy(opts.OrderBy.String()) if opts.PageSize > 0 { sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) } - if err = sess.Find(&repos); err != nil { - return nil, 0, fmt.Errorf("Repo: %v", err) - } - - if loadAttributes { - if err = repos.loadAttributes(sess); err != nil { - return nil, 0, fmt.Errorf("LoadAttributes: %v", err) - } - } - - return repos, count, nil + return sess, count, nil } // accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible @@ -456,6 +479,33 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err return SearchRepository(opts) } +// SearchRepositoryIDs takes keyword and part of repository name to search, +// it returns results in given range and number of total results. +func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { + opts.IncludeDescription = false + + cond := SearchRepositoryCondition(opts) + + sess, count, err := searchRepositoryByCondition(opts, cond) + if err != nil { + return nil, 0, err + } + defer sess.Close() + + defaultSize := 50 + if opts.PageSize > 0 { + defaultSize = opts.PageSize + } + + ids := make([]int64, 0, defaultSize) + err = sess.Select("id").Table("repository").Find(&ids) + if opts.PageSize <= 0 { + count = int64(len(ids)) + } + + return ids, count, err +} + // AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. func AccessibleRepoIDsQuery(user *User) *builder.Builder { // NB: Please note this code needs to still work if user is nil diff --git a/models/repo_transfer.go b/models/repo_transfer.go index c1326a1ac6..d7ef0a8ca6 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -210,13 +210,13 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e } if repoRenamed { - if err := os.Rename(RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name)); err != nil { + if err := util.Rename(RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name)); err != nil { log.Critical("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name), err) } } if wikiRenamed { - if err := os.Rename(WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name)); err != nil { + if err := util.Rename(WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name)); err != nil { log.Critical("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name), err) } } @@ -330,10 +330,10 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e SELECT il_too.id FROM ( SELECT il_too_too.id FROM issue_label AS il_too_too - INNER JOIN label ON il_too_too.id = label.id + INNER JOIN label ON il_too_too.label_id = label.id INNER JOIN issue on issue.id = il_too_too.issue_id WHERE - issue.repo_id = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != ?)) + issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?)) ) AS il_too )`, repo.ID, newOwner.ID); err != nil { return fmt.Errorf("Unable to remove old org labels: %v", err) } @@ -343,9 +343,9 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e SELECT com.id FROM comment AS com INNER JOIN label ON com.label_id = label.id - INNER JOIN issue on issue.id = com.issue_id + INNER JOIN issue ON issue.id = com.issue_id WHERE - com.type = ? AND issue.repo_id = ? AND (issue.repo_id != label.repo_id OR (label.repo_id = 0 AND label.org_id != ?)) + com.type = ? AND issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?)) ) AS il_too)`, CommentTypeLabel, repo.ID, newOwner.ID); err != nil { return fmt.Errorf("Unable to remove old org label comments: %v", err) } @@ -358,7 +358,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e return fmt.Errorf("Failed to create dir %s: %v", dir, err) } - if err := os.Rename(RepoPath(oldOwner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { + if err := util.Rename(RepoPath(oldOwner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { return fmt.Errorf("rename repository directory: %v", err) } repoRenamed = true @@ -370,7 +370,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) return err } else if isExist { - if err := os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { + if err := util.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { return fmt.Errorf("rename repository wiki: %v", err) } wikiRenamed = true diff --git a/models/repo_unit.go b/models/repo_unit.go index 7702697ffa..8953362c9e 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -28,8 +28,7 @@ type UnitConfig struct{} // FromDB fills up a UnitConfig from serialized format. func (cfg *UnitConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, &cfg) } // ToDB exports a UnitConfig to a serialized format. @@ -45,8 +44,7 @@ type ExternalWikiConfig struct { // FromDB fills up a ExternalWikiConfig from serialized format. func (cfg *ExternalWikiConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, &cfg) } // ToDB exports a ExternalWikiConfig to a serialized format. @@ -64,8 +62,7 @@ type ExternalTrackerConfig struct { // FromDB fills up a ExternalTrackerConfig from serialized format. func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, &cfg) } // ToDB exports a ExternalTrackerConfig to a serialized format. @@ -83,8 +80,7 @@ type IssuesConfig struct { // FromDB fills up a IssuesConfig from serialized format. func (cfg *IssuesConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, &cfg) } // ToDB exports a IssuesConfig to a serialized format. @@ -106,8 +102,7 @@ type PullRequestsConfig struct { // FromDB fills up a PullRequestsConfig from serialized format. func (cfg *PullRequestsConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalHandleDoubleEncode(bs, &cfg) } // ToDB exports a PullRequestsConfig to a serialized format. diff --git a/models/review.go b/models/review.go index 702e9634ef..343621c0fa 100644 --- a/models/review.go +++ b/models/review.go @@ -566,7 +566,11 @@ func DismissReview(review *Review, isDismiss bool) (err error) { review.Dismissed = isDismiss - _, err = x.Cols("dismissed").Update(review) + if review.ID == 0 { + return ErrReviewNotExist{} + } + + _, err = x.ID(review.ID).Cols("dismissed").Update(review) return } diff --git a/models/review_test.go b/models/review_test.go index 4f049b45e3..accc184193 100644 --- a/models/review_test.go +++ b/models/review_test.go @@ -143,11 +143,57 @@ func TestGetReviewersByIssueID(t *testing.T) { } func TestDismissReview(t *testing.T) { - review1 := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - review2 := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) - assert.NoError(t, DismissReview(review1, true)) - assert.NoError(t, DismissReview(review2, true)) - assert.NoError(t, DismissReview(review2, true)) - assert.NoError(t, DismissReview(review2, false)) - assert.NoError(t, DismissReview(review2, false)) + assert.NoError(t, PrepareTestDatabase()) + + rejectReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + approveReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 8}).(*Review) + assert.False(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(rejectReviewExample, true)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, true)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, true)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, false)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, false)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(rejectReviewExample, false)) + assert.False(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(approveReviewExample, true)) + assert.False(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.True(t, approveReviewExample.Dismissed) + } diff --git a/models/session.go b/models/session.go index fe363ee487..b2e4837bed 100644 --- a/models/session.go +++ b/models/session.go @@ -117,6 +117,6 @@ func CountSessions() (int64, error) { // CleanupSessions cleans up expired sessions func CleanupSessions(maxLifetime int64) error { - _, err := x.Where("created_unix <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) + _, err := x.Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) return err } diff --git a/models/ssh_key.go b/models/ssh_key.go index 9f9c33e848..493de9cd88 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -834,7 +834,7 @@ func rewriteAllPublicKeys(e Engine) error { } t.Close() - return os.Rename(tmpPath, fPath) + return util.Rename(tmpPath, fPath) } // RegeneratePublicKeys regenerates the authorized_keys file @@ -1316,7 +1316,7 @@ func rewriteAllPrincipalKeys(e Engine) error { } t.Close() - return os.Rename(tmpPath, fPath) + return util.Rename(tmpPath, fPath) } // ListPrincipalKeys returns a list of principals belongs to given user. diff --git a/models/task.go b/models/task.go index 8d4bfbf076..a4ab65b5e5 100644 --- a/models/task.go +++ b/models/task.go @@ -8,8 +8,11 @@ import ( "fmt" migration "code.gitea.io/gitea/modules/migrations/base" + "code.gitea.io/gitea/modules/secret" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" jsoniter "github.com/json-iterator/go" "xorm.io/builder" @@ -110,6 +113,24 @@ func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) { if err != nil { return nil, err } + + // decrypt credentials + if opts.CloneAddrEncrypted != "" { + if opts.CloneAddr, err = secret.DecryptSecret(setting.SecretKey, opts.CloneAddrEncrypted); err != nil { + return nil, err + } + } + if opts.AuthPasswordEncrypted != "" { + if opts.AuthPassword, err = secret.DecryptSecret(setting.SecretKey, opts.AuthPasswordEncrypted); err != nil { + return nil, err + } + } + if opts.AuthTokenEncrypted != "" { + if opts.AuthToken, err = secret.DecryptSecret(setting.SecretKey, opts.AuthTokenEncrypted); err != nil { + return nil, err + } + } + return &opts, nil } return nil, fmt.Errorf("Task type is %s, not Migrate Repo", task.Type.Name()) @@ -205,12 +226,31 @@ func createTask(e Engine, task *Task) error { func FinishMigrateTask(task *Task) error { task.Status = structs.TaskStatusFinished task.EndTime = timeutil.TimeStampNow() + + // delete credentials when we're done, they're a liability. + conf, err := task.MigrateConfig() + if err != nil { + return err + } + conf.AuthPassword = "" + conf.AuthToken = "" + conf.CloneAddr = util.SanitizeURLCredentials(conf.CloneAddr, true) + conf.AuthPasswordEncrypted = "" + conf.AuthTokenEncrypted = "" + conf.CloneAddrEncrypted = "" + json := jsoniter.ConfigCompatibleWithStandardLibrary + confBytes, err := json.Marshal(conf) + if err != nil { + return err + } + task.PayloadContent = string(confBytes) + sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err } - if _, err := sess.ID(task.ID).Cols("status", "end_time").Update(task); err != nil { + if _, err := sess.ID(task.ID).Cols("status", "end_time", "payload_content").Update(task); err != nil { return err } diff --git a/models/token.go b/models/token.go index 1245098df0..49541b122a 100644 --- a/models/token.go +++ b/models/token.go @@ -57,9 +57,15 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { if token == "" { return nil, ErrAccessTokenEmpty{} } - if len(token) < 8 { + // A token is defined as being SHA1 sum these are 40 hexadecimal bytes long + if len(token) != 40 { return nil, ErrAccessTokenNotExist{token} } + for _, x := range []byte(token) { + if x < '0' || (x > '9' && x < 'a') || x > 'f' { + return nil, ErrAccessTokenNotExist{token} + } + } var tokens []AccessToken lastEight := token[len(token)-8:] err := x.Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens) diff --git a/models/user.go b/models/user.go index 098f6af2b3..e751bcd38b 100644 --- a/models/user.go +++ b/models/user.go @@ -239,10 +239,10 @@ func (u *User) GetEmail() string { return u.Email } -// GetAllUsers returns a slice of all users found in DB. +// GetAllUsers returns a slice of all individual users found in DB. func GetAllUsers() ([]*User, error) { users := make([]*User, 0) - return users, x.OrderBy("id").Find(&users) + return users, x.OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) } // IsLocal returns true if user login type is LoginPlain. @@ -1011,7 +1011,7 @@ func ChangeUserName(u *User, newUserName string) (err error) { } // Do not fail if directory does not exist - if err = os.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { + if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { return fmt.Errorf("Rename user directory: %v", err) } @@ -1020,7 +1020,7 @@ func ChangeUserName(u *User, newUserName string) (err error) { } if err = sess.Commit(); err != nil { - if err2 := os.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) { + if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) { log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2) return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2) } diff --git a/models/user_avatar.go b/models/user_avatar.go index 871e176599..d336684a27 100644 --- a/models/user_avatar.go +++ b/models/user_avatar.go @@ -82,6 +82,9 @@ func (u *User) RealSizedAvatarLink(size int) string { if u.Avatar == "" { return DefaultAvatarLink() } + if size > 0 { + return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size) + } return setting.AppSubURL + "/avatars/" + u.Avatar case setting.DisableGravatar, setting.OfflineMode: if u.Avatar == "" { @@ -89,7 +92,9 @@ func (u *User) RealSizedAvatarLink(size int) string { log.Error("GenerateRandomAvatar: %v", err) } } - + if size > 0 { + return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size) + } return setting.AppSubURL + "/avatars/" + u.Avatar } return SizedAvatarLink(u.AvatarEmail, size) diff --git a/modules/analyze/vendor.go b/modules/analyze/vendor.go new file mode 100644 index 0000000000..12ae8dbd80 --- /dev/null +++ b/modules/analyze/vendor.go @@ -0,0 +1,70 @@ +// 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 analyze + +import ( + "regexp" + "sort" + "strings" + + "github.com/go-enry/go-enry/v2/data" +) + +var isVendorRegExp *regexp.Regexp + +func init() { + matchers := data.VendorMatchers + + caretStrings := make([]string, 0, 10) + caretShareStrings := make([]string, 0, 10) + + matcherStrings := make([]string, 0, len(matchers)) + for _, matcher := range matchers { + str := matcher.String() + if str[0] == '^' { + caretStrings = append(caretStrings, str[1:]) + } else if str[0:5] == "(^|/)" { + caretShareStrings = append(caretShareStrings, str[5:]) + } else { + matcherStrings = append(matcherStrings, str) + } + } + + sort.Strings(caretShareStrings) + sort.Strings(caretStrings) + sort.Strings(matcherStrings) + + sb := &strings.Builder{} + sb.WriteString("(?:^(?:") + sb.WriteString(caretStrings[0]) + for _, matcher := range caretStrings[1:] { + sb.WriteString(")|(?:") + sb.WriteString(matcher) + } + sb.WriteString("))") + sb.WriteString("|") + sb.WriteString("(?:(?:^|/)(?:") + sb.WriteString(caretShareStrings[0]) + for _, matcher := range caretShareStrings[1:] { + sb.WriteString(")|(?:") + sb.WriteString(matcher) + } + sb.WriteString("))") + sb.WriteString("|") + sb.WriteString("(?:") + sb.WriteString(matcherStrings[0]) + for _, matcher := range matcherStrings[1:] { + sb.WriteString(")|(?:") + sb.WriteString(matcher) + } + sb.WriteString(")") + combined := sb.String() + isVendorRegExp = regexp.MustCompile(combined) +} + +// IsVendor returns whether or not path is a vendor path. +func IsVendor(path string) bool { + return isVendorRegExp.MatchString(path) +} diff --git a/modules/analyze/vendor_test.go b/modules/analyze/vendor_test.go new file mode 100644 index 0000000000..2784e49d34 --- /dev/null +++ b/modules/analyze/vendor_test.go @@ -0,0 +1,42 @@ +// 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 analyze + +import "testing" + +func TestIsVendor(t *testing.T) { + tests := []struct { + path string + want bool + }{ + {"cache/", true}, + {"random/cache/", true}, + {"cache", false}, + {"dependencies/", true}, + {"Dependencies/", true}, + {"dependency/", false}, + {"dist/", true}, + {"dist", false}, + {"random/dist/", true}, + {"random/dist", false}, + {"deps/", true}, + {"configure", true}, + {"a/configure", true}, + {"config.guess", true}, + {"config.guess/", false}, + {".vscode/", true}, + {"doc/_build/", true}, + {"a/docs/_build/", true}, + {"a/dasdocs/_build-vsdoc.js", true}, + {"a/dasdocs/_build-vsdoc.j", false}, + } + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + if got := IsVendor(tt.path); got != tt.want { + t.Errorf("IsVendor() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/base/tool.go b/modules/base/tool.go index d721d47e9d..c9530473e2 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -21,6 +21,7 @@ import ( "strings" "time" "unicode" + "unicode/utf8" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -213,19 +214,19 @@ func EllipsisString(str string, length int) string { if length <= 3 { return "..." } - if len(str) <= length { + if utf8.RuneCountInString(str) <= length { return str } - return str[:length-3] + "..." + return string([]rune(str)[:length-3]) + "..." } // TruncateString returns a truncated string with given limit, // it returns input string if length is not reached limit. func TruncateString(str string, limit int) string { - if len(str) < limit { + if utf8.RuneCountInString(str) < limit { return str } - return str[:limit] + return string([]rune(str)[:limit]) } // StringsToInt64s converts a slice of string to a slice of int64. diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index b6baeb8c3c..fcd3ca296a 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -170,6 +170,10 @@ func TestEllipsisString(t *testing.T) { assert.Equal(t, "fo...", EllipsisString("foobar", 5)) assert.Equal(t, "foobar", EllipsisString("foobar", 6)) assert.Equal(t, "foobar", EllipsisString("foobar", 10)) + assert.Equal(t, "测...", EllipsisString("测试文本一二三四", 4)) + assert.Equal(t, "测试...", EllipsisString("测试文本一二三四", 5)) + assert.Equal(t, "测试文...", EllipsisString("测试文本一二三四", 6)) + assert.Equal(t, "测试文本一二三四", EllipsisString("测试文本一二三四", 10)) } func TestTruncateString(t *testing.T) { @@ -181,6 +185,10 @@ func TestTruncateString(t *testing.T) { assert.Equal(t, "fooba", TruncateString("foobar", 5)) assert.Equal(t, "foobar", TruncateString("foobar", 6)) assert.Equal(t, "foobar", TruncateString("foobar", 7)) + assert.Equal(t, "测试文本", TruncateString("测试文本一二三四", 4)) + assert.Equal(t, "测试文本一", TruncateString("测试文本一二三四", 5)) + assert.Equal(t, "测试文本一二", TruncateString("测试文本一二三四", 6)) + assert.Equal(t, "测试文本一二三", TruncateString("测试文本一二三四", 7)) } func TestStringsToInt64s(t *testing.T) { diff --git a/modules/context/csrf.go b/modules/context/csrf.go index ba0e9f6cde..8d179ca904 100644 --- a/modules/context/csrf.go +++ b/modules/context/csrf.go @@ -22,6 +22,7 @@ import ( "net/http" "time" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" "github.com/unknwon/com" @@ -266,7 +267,12 @@ func Validate(ctx *Context, x CSRF) { -1, x.GetCookiePath(), x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too? - x.Error(ctx.Resp) + if middleware.IsAPIPath(ctx.Req) { + x.Error(ctx.Resp) + return + } + ctx.Flash.Error(ctx.Tr("error.invalid_csrf")) + ctx.Redirect(setting.AppSubURL + "/") } return } @@ -277,10 +283,19 @@ func Validate(ctx *Context, x CSRF) { -1, x.GetCookiePath(), x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too? - x.Error(ctx.Resp) + if middleware.IsAPIPath(ctx.Req) { + x.Error(ctx.Resp) + return + } + ctx.Flash.Error(ctx.Tr("error.invalid_csrf")) + ctx.Redirect(setting.AppSubURL + "/") } return } - - http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest) + if middleware.IsAPIPath(ctx.Req) { + http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest) + return + } + ctx.Flash.Error(ctx.Tr("error.missing_csrf")) + ctx.Redirect(setting.AppSubURL + "/") } diff --git a/modules/context/repo.go b/modules/context/repo.go index ba3cfe9bf2..c1f60a1362 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -6,9 +6,9 @@ package context import ( + "context" "fmt" "io/ioutil" - "net/http" "net/url" "path" "strings" @@ -394,239 +394,233 @@ func RepoIDAssignment() func(ctx *Context) { } // RepoAssignment returns a middleware to handle repository assignment -func RepoAssignment() func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var ( - owner *models.User - err error - ctx = GetContext(req) - ) +func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { + var ( + owner *models.User + err error + ) - userName := ctx.Params(":username") - repoName := ctx.Params(":reponame") - repoName = strings.TrimSuffix(repoName, ".git") + userName := ctx.Params(":username") + repoName := ctx.Params(":reponame") + repoName = strings.TrimSuffix(repoName, ".git") - // Check if the user is the same as the repository owner - if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { - owner = ctx.User + // Check if the user is the same as the repository owner + if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { + owner = ctx.User + } else { + owner, err = models.GetUserByName(userName) + if err != nil { + if models.IsErrUserNotExist(err) { + if ctx.Query("go-get") == "1" { + EarlyResponseForGoGetMeta(ctx) + return + } + ctx.NotFound("GetUserByName", nil) } else { - owner, err = models.GetUserByName(userName) - if err != nil { - if models.IsErrUserNotExist(err) { - if ctx.Query("go-get") == "1" { - EarlyResponseForGoGetMeta(ctx) - return - } - ctx.NotFound("GetUserByName", nil) - } else { - ctx.ServerError("GetUserByName", err) - } - return - } + ctx.ServerError("GetUserByName", err) } - ctx.Repo.Owner = owner - ctx.Data["Username"] = ctx.Repo.Owner.Name - - // Get repository. - repo, err := models.GetRepositoryByName(owner.ID, repoName) - if err != nil { - if models.IsErrRepoNotExist(err) { - redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName) - if err == nil { - RedirectToRepo(ctx, redirectRepoID) - } else if models.IsErrRepoRedirectNotExist(err) { - if ctx.Query("go-get") == "1" { - EarlyResponseForGoGetMeta(ctx) - return - } - ctx.NotFound("GetRepositoryByName", nil) - } else { - ctx.ServerError("LookupRepoRedirect", err) - } - } else { - ctx.ServerError("GetRepositoryByName", err) - } - return - } - repo.Owner = owner - - repoAssignment(ctx, repo) - if ctx.Written() { - return - } - - ctx.Repo.RepoLink = repo.Link() - ctx.Data["RepoLink"] = ctx.Repo.RepoLink - ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name - - unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker) - if err == nil { - ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL - } - - ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ - IncludeTags: true, - }) - if err != nil { - ctx.ServerError("GetReleaseCountByRepoID", err) - return - } - ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{}) - if err != nil { - ctx.ServerError("GetReleaseCountByRepoID", err) - return - } - - ctx.Data["Title"] = owner.Name + "/" + repo.Name - ctx.Data["Repository"] = repo - ctx.Data["Owner"] = ctx.Repo.Repository.Owner - ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() - ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() - ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization() - ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode) - ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues) - ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) - - if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil { - ctx.ServerError("CanUserFork", err) - return - } - - ctx.Data["DisableSSH"] = setting.SSH.Disabled - ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous - ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit - ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled - ctx.Data["CloneLink"] = repo.CloneLink() - ctx.Data["WikiCloneLink"] = repo.WikiCloneLink() - - if ctx.IsSigned { - ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID) - ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID) - } - - if repo.IsFork { - RetrieveBaseRepo(ctx, repo) - if ctx.Written() { - return - } - } - - if repo.IsGenerated() { - RetrieveTemplateRepo(ctx, repo) - if ctx.Written() { - return - } - } - - // Disable everything when the repo is being created - if ctx.Repo.Repository.IsBeingCreated() { - ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch - return - } - - gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName)) - if err != nil { - ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err) - return - } - ctx.Repo.GitRepo = gitRepo - - // We opened it, we should close it - defer func() { - // If it's been set to nil then assume someone else has closed it. - if ctx.Repo.GitRepo != nil { - ctx.Repo.GitRepo.Close() - } - }() - - // Stop at this point when the repo is empty. - if ctx.Repo.Repository.IsEmpty { - ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch - next.ServeHTTP(w, req) - return - } - - tags, err := ctx.Repo.GitRepo.GetTags() - if err != nil { - ctx.ServerError("GetTags", err) - return - } - ctx.Data["Tags"] = tags - - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) - if err != nil { - ctx.ServerError("GetBranches", err) - return - } - ctx.Data["Branches"] = brs - ctx.Data["BranchesCount"] = len(brs) - - ctx.Data["TagName"] = ctx.Repo.TagName - - // If not branch selected, try default one. - // If default branch doesn't exists, fall back to some other branch. - if len(ctx.Repo.BranchName) == 0 { - if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) { - ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch - } else if len(brs) > 0 { - ctx.Repo.BranchName = brs[0] - } - } - ctx.Data["BranchName"] = ctx.Repo.BranchName - ctx.Data["CommitID"] = ctx.Repo.CommitID - - // People who have push access or have forked repository can propose a new pull request. - canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) - canCompare := false - - // Pull request is allowed if this is a fork repository - // and base repository accepts pull requests. - if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() { - canCompare = true - ctx.Data["BaseRepo"] = repo.BaseRepo - ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo - ctx.Repo.PullRequest.Allowed = canPush - ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName - } else if repo.AllowsPulls() { - // Or, this is repository accepts pull requests between branches. - canCompare = true - ctx.Data["BaseRepo"] = repo - ctx.Repo.PullRequest.BaseRepo = repo - ctx.Repo.PullRequest.Allowed = canPush - ctx.Repo.PullRequest.SameRepo = true - ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName - } - ctx.Data["CanCompareOrPull"] = canCompare - ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest - - if ctx.Repo.Repository.Status == models.RepositoryPendingTransfer { - repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository) - if err != nil { - ctx.ServerError("GetPendingRepositoryTransfer", err) - return - } - - if err := repoTransfer.LoadAttributes(); err != nil { - ctx.ServerError("LoadRecipient", err) - return - } - - ctx.Data["RepoTransfer"] = repoTransfer - if ctx.User != nil { - ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User) - } - } - - if ctx.Query("go-get") == "1" { - ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name) - prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName) - ctx.Data["GoDocDirectory"] = prefix + "{/dir}" - ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" - } - next.ServeHTTP(w, req) - }) + return + } } + ctx.Repo.Owner = owner + ctx.Data["Username"] = ctx.Repo.Owner.Name + + // Get repository. + repo, err := models.GetRepositoryByName(owner.ID, repoName) + if err != nil { + if models.IsErrRepoNotExist(err) { + redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName) + if err == nil { + RedirectToRepo(ctx, redirectRepoID) + } else if models.IsErrRepoRedirectNotExist(err) { + if ctx.Query("go-get") == "1" { + EarlyResponseForGoGetMeta(ctx) + return + } + ctx.NotFound("GetRepositoryByName", nil) + } else { + ctx.ServerError("LookupRepoRedirect", err) + } + } else { + ctx.ServerError("GetRepositoryByName", err) + } + return + } + repo.Owner = owner + + repoAssignment(ctx, repo) + if ctx.Written() { + return + } + + ctx.Repo.RepoLink = repo.Link() + ctx.Data["RepoLink"] = ctx.Repo.RepoLink + ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name + + unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker) + if err == nil { + ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL + } + + ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ + IncludeTags: true, + }) + if err != nil { + ctx.ServerError("GetReleaseCountByRepoID", err) + return + } + ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{}) + if err != nil { + ctx.ServerError("GetReleaseCountByRepoID", err) + return + } + + ctx.Data["Title"] = owner.Name + "/" + repo.Name + ctx.Data["Repository"] = repo + ctx.Data["Owner"] = ctx.Repo.Repository.Owner + ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() + ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() + ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization() + ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode) + ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues) + ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) + + if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil { + ctx.ServerError("CanUserFork", err) + return + } + + ctx.Data["DisableSSH"] = setting.SSH.Disabled + ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous + ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit + ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled + ctx.Data["CloneLink"] = repo.CloneLink() + ctx.Data["WikiCloneLink"] = repo.WikiCloneLink() + + if ctx.IsSigned { + ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID) + ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID) + } + + if repo.IsFork { + RetrieveBaseRepo(ctx, repo) + if ctx.Written() { + return + } + } + + if repo.IsGenerated() { + RetrieveTemplateRepo(ctx, repo) + if ctx.Written() { + return + } + } + + // Disable everything when the repo is being created + if ctx.Repo.Repository.IsBeingCreated() { + ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch + return + } + + gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName)) + if err != nil { + ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err) + return + } + ctx.Repo.GitRepo = gitRepo + + // We opened it, we should close it + cancel = func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + } + + // Stop at this point when the repo is empty. + if ctx.Repo.Repository.IsEmpty { + ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch + return + } + + tags, err := ctx.Repo.GitRepo.GetTags() + if err != nil { + ctx.ServerError("GetTags", err) + return + } + ctx.Data["Tags"] = tags + + brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) + if err != nil { + ctx.ServerError("GetBranches", err) + return + } + ctx.Data["Branches"] = brs + ctx.Data["BranchesCount"] = len(brs) + + ctx.Data["TagName"] = ctx.Repo.TagName + + // If not branch selected, try default one. + // If default branch doesn't exists, fall back to some other branch. + if len(ctx.Repo.BranchName) == 0 { + if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) { + ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch + } else if len(brs) > 0 { + ctx.Repo.BranchName = brs[0] + } + } + ctx.Data["BranchName"] = ctx.Repo.BranchName + ctx.Data["CommitID"] = ctx.Repo.CommitID + + // People who have push access or have forked repository can propose a new pull request. + canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) + canCompare := false + + // Pull request is allowed if this is a fork repository + // and base repository accepts pull requests. + if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() { + canCompare = true + ctx.Data["BaseRepo"] = repo.BaseRepo + ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo + ctx.Repo.PullRequest.Allowed = canPush + ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName + } else if repo.AllowsPulls() { + // Or, this is repository accepts pull requests between branches. + canCompare = true + ctx.Data["BaseRepo"] = repo + ctx.Repo.PullRequest.BaseRepo = repo + ctx.Repo.PullRequest.Allowed = canPush + ctx.Repo.PullRequest.SameRepo = true + ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName + } + ctx.Data["CanCompareOrPull"] = canCompare + ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest + + if ctx.Repo.Repository.Status == models.RepositoryPendingTransfer { + repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository) + if err != nil { + ctx.ServerError("GetPendingRepositoryTransfer", err) + return + } + + if err := repoTransfer.LoadAttributes(); err != nil { + ctx.ServerError("LoadRecipient", err) + return + } + + ctx.Data["RepoTransfer"] = repoTransfer + if ctx.User != nil { + ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User) + } + } + + if ctx.Query("go-get") == "1" { + ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name) + prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName) + ctx.Data["GoDocDirectory"] = prefix + "{/dir}" + ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" + } + return } // RepoRefType type of repo reference @@ -651,7 +645,7 @@ const ( // RepoRef handles repository reference names when the ref name is not // explicitly given -func RepoRef() func(http.Handler) http.Handler { +func RepoRef() func(*Context) context.CancelFunc { // since no ref name is explicitly specified, ok to just use branch return RepoRefByType(RepoRefBranch) } @@ -730,130 +724,129 @@ func getRefName(ctx *Context, pathType RepoRefType) string { // RepoRefByType handles repository reference name for a specific type // of repository reference -func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx := GetContext(req) - // Empty repository does not have reference information. - if ctx.Repo.Repository.IsEmpty { +func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc { + return func(ctx *Context) (cancel context.CancelFunc) { + // Empty repository does not have reference information. + if ctx.Repo.Repository.IsEmpty { + return + } + + var ( + refName string + err error + ) + + if ctx.Repo.GitRepo == nil { + repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) + ctx.Repo.GitRepo, err = git.OpenRepository(repoPath) + if err != nil { + ctx.ServerError("RepoRef Invalid repo "+repoPath, err) return } + // We opened it, we should close it + cancel = func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + } + } - var ( - refName string - err error - ) - - if ctx.Repo.GitRepo == nil { - repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) - ctx.Repo.GitRepo, err = git.OpenRepository(repoPath) + // Get default branch. + if len(ctx.Params("*")) == 0 { + refName = ctx.Repo.Repository.DefaultBranch + ctx.Repo.BranchName = refName + if !ctx.Repo.GitRepo.IsBranchExist(refName) { + brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) if err != nil { - ctx.ServerError("RepoRef Invalid repo "+repoPath, err) + ctx.ServerError("GetBranches", err) + return + } else if len(brs) == 0 { + err = fmt.Errorf("No branches in non-empty repository %s", + ctx.Repo.GitRepo.Path) + ctx.ServerError("GetBranches", err) return } - // We opened it, we should close it - defer func() { - // If it's been set to nil then assume someone else has closed it. - if ctx.Repo.GitRepo != nil { - ctx.Repo.GitRepo.Close() - } - }() + refName = brs[0] } + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) + if err != nil { + ctx.ServerError("GetBranchCommit", err) + return + } + ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() + ctx.Repo.IsViewBranch = true + + } else { + refName = getRefName(ctx, refType) + ctx.Repo.BranchName = refName + if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) { + ctx.Repo.IsViewBranch = true - // Get default branch. - if len(ctx.Params("*")) == 0 { - refName = ctx.Repo.Repository.DefaultBranch - ctx.Repo.BranchName = refName - if !ctx.Repo.GitRepo.IsBranchExist(refName) { - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0) - if err != nil { - ctx.ServerError("GetBranches", err) - return - } else if len(brs) == 0 { - err = fmt.Errorf("No branches in non-empty repository %s", - ctx.Repo.GitRepo.Path) - ctx.ServerError("GetBranches", err) - return - } - refName = brs[0] - } ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) if err != nil { ctx.ServerError("GetBranchCommit", err) return } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - ctx.Repo.IsViewBranch = true + } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) { + ctx.Repo.IsViewTag = true + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName) + if err != nil { + ctx.ServerError("GetTagCommit", err) + return + } + ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() + } else if len(refName) >= 7 && len(refName) <= 40 { + ctx.Repo.IsViewCommit = true + ctx.Repo.CommitID = refName + + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) + if err != nil { + ctx.NotFound("GetCommit", err) + return + } + // If short commit ID add canonical link header + if len(refName) < 40 { + ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", + util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1)))) + } } else { - refName = getRefName(ctx, refType) - ctx.Repo.BranchName = refName - if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) { - ctx.Repo.IsViewBranch = true - - ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - - } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) { - ctx.Repo.IsViewTag = true - ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName) - if err != nil { - ctx.ServerError("GetTagCommit", err) - return - } - ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if len(refName) >= 7 && len(refName) <= 40 { - ctx.Repo.IsViewCommit = true - ctx.Repo.CommitID = refName - - ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) - if err != nil { - ctx.NotFound("GetCommit", err) - return - } - // If short commit ID add canonical link header - if len(refName) < 40 { - ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", - util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1)))) - } - } else { - ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) + if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] { return } - - if refType == RepoRefLegacy { - // redirect from old URL scheme to new URL scheme - ctx.Redirect(path.Join( - setting.AppSubURL, - strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), - ctx.Repo.BranchNameSubURL(), - ctx.Repo.TreePath)) - return - } - } - - ctx.Data["BranchName"] = ctx.Repo.BranchName - ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() - ctx.Data["CommitID"] = ctx.Repo.CommitID - ctx.Data["TreePath"] = ctx.Repo.TreePath - ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch - ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag - ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit - ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() - - ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() - if err != nil { - ctx.ServerError("GetCommitsCount", err) + ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) return } - ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount - next.ServeHTTP(w, req) - }) + if refType == RepoRefLegacy { + // redirect from old URL scheme to new URL scheme + ctx.Redirect(path.Join( + setting.AppSubURL, + strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), + ctx.Repo.BranchNameSubURL(), + ctx.Repo.TreePath)) + return + } + } + + ctx.Data["BranchName"] = ctx.Repo.BranchName + ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() + ctx.Data["CommitID"] = ctx.Repo.CommitID + ctx.Data["TreePath"] = ctx.Repo.TreePath + ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch + ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag + ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit + ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() + + ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() + if err != nil { + ctx.ServerError("GetCommitsCount", err) + return + } + ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount + return } } diff --git a/modules/context/response.go b/modules/context/response.go index bdbbb97af7..a20fc63536 100644 --- a/modules/context/response.go +++ b/modules/context/response.go @@ -4,7 +4,9 @@ package context -import "net/http" +import ( + "net/http" +) // ResponseWriter represents a response writer for HTTP type ResponseWriter interface { @@ -47,7 +49,7 @@ func (r *Response) Write(bs []byte) (int, error) { return size, err } if r.status == 0 { - r.WriteHeader(200) + r.status = http.StatusOK } return size, nil } @@ -60,8 +62,10 @@ func (r *Response) WriteHeader(statusCode int) { } r.beforeExecuted = true } - r.status = statusCode - r.ResponseWriter.WriteHeader(statusCode) + if r.status == 0 { + r.status = statusCode + r.ResponseWriter.WriteHeader(statusCode) + } } // Flush flush cached data diff --git a/modules/convert/git_commit.go b/modules/convert/git_commit.go index 4e30ec2c0b..4234e1d986 100644 --- a/modules/convert/git_commit.go +++ b/modules/convert/git_commit.go @@ -155,8 +155,8 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string] URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), Author: &api.CommitUser{ Identity: api.Identity{ - Name: commit.Committer.Name, - Email: commit.Committer.Email, + Name: commit.Author.Name, + Email: commit.Author.Email, }, Date: commit.Author.When.Format(time.RFC3339), }, diff --git a/modules/convert/notification.go b/modules/convert/notification.go index 49abe01253..cc941678b6 100644 --- a/modules/convert/notification.go +++ b/modules/convert/notification.go @@ -47,6 +47,11 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread { if err == nil && comment != nil { result.Subject.LatestCommentURL = comment.APIURL() } + + pr, _ := n.Issue.GetPullRequest() + if pr != nil && pr.HasMerged { + result.Subject.State = "merged" + } } case models.NotificationSourceCommit: result.Subject = &api.NotificationSubject{ diff --git a/modules/convert/pull_review.go b/modules/convert/pull_review.go index d1d6e767d4..418cb711dc 100644 --- a/modules/convert/pull_review.go +++ b/modules/convert/pull_review.go @@ -85,18 +85,17 @@ func ToPullReviewCommentList(review *models.Review, doer *models.User) ([]*api.P apiComments := make([]*api.PullReviewComment, 0, len(review.CodeComments)) - auth := false - if doer != nil { - auth = doer.IsAdmin || doer.ID == review.ReviewerID - } - for _, lines := range review.CodeComments { for _, comments := range lines { for _, comment := range comments { + auth := false + if doer != nil { + auth = doer.IsAdmin || doer.ID == comment.Poster.ID + } apiComment := &api.PullReviewComment{ ID: comment.ID, Body: comment.Content, - Reviewer: ToUser(review.Reviewer, doer != nil, auth), + Reviewer: ToUser(comment.Poster, doer != nil, auth), ReviewID: review.ID, Created: comment.CreatedUnix.AsTime(), Updated: comment.UpdatedUnix.AsTime(), diff --git a/modules/convert/repository.go b/modules/convert/repository.go index 813201ca68..be76538306 100644 --- a/modules/convert/repository.go +++ b/modules/convert/repository.go @@ -89,7 +89,7 @@ func innerToRepo(repo *models.Repository, mode models.AccessMode, isParent bool) return nil } - numReleases, _ := models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{IncludeDrafts: false, IncludeTags: true}) + numReleases, _ := models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false}) mirrorInterval := "" if repo.IsMirror { diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go index 8c960cb4a8..23e8331e77 100644 --- a/modules/doctor/dbconsistency.go +++ b/modules/doctor/dbconsistency.go @@ -23,13 +23,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find labels without existing repo or org count, err := models.CountOrphanedLabels() if err != nil { - logger.Critical("Error: %v whilst counting orphaned labels") + logger.Critical("Error: %v whilst counting orphaned labels", err) return err } if count > 0 { if autofix { if err = models.DeleteOrphanedLabels(); err != nil { - logger.Critical("Error: %v whilst deleting orphaned labels") + logger.Critical("Error: %v whilst deleting orphaned labels", err) return err } logger.Info("%d labels without existing repository/organisation deleted", count) @@ -41,13 +41,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find IssueLabels without existing label count, err = models.CountOrphanedIssueLabels() if err != nil { - logger.Critical("Error: %v whilst counting orphaned issue_labels") + logger.Critical("Error: %v whilst counting orphaned issue_labels", err) return err } if count > 0 { if autofix { if err = models.DeleteOrphanedIssueLabels(); err != nil { - logger.Critical("Error: %v whilst deleting orphaned issue_labels") + logger.Critical("Error: %v whilst deleting orphaned issue_labels", err) return err } logger.Info("%d issue_labels without existing label deleted", count) @@ -59,13 +59,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find issues without existing repository count, err = models.CountOrphanedIssues() if err != nil { - logger.Critical("Error: %v whilst counting orphaned issues") + logger.Critical("Error: %v whilst counting orphaned issues", err) return err } if count > 0 { if autofix { if err = models.DeleteOrphanedIssues(); err != nil { - logger.Critical("Error: %v whilst deleting orphaned issues") + logger.Critical("Error: %v whilst deleting orphaned issues", err) return err } logger.Info("%d issues without existing repository deleted", count) @@ -77,13 +77,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find pulls without existing issues count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id") if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects") + logger.Critical("Error: %v whilst counting orphaned objects", err) return err } if count > 0 { if autofix { if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects") + logger.Critical("Error: %v whilst deleting orphaned objects", err) return err } logger.Info("%d pull requests without existing issue deleted", count) @@ -95,13 +95,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find tracked times without existing issues/pulls count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id") if err != nil { - logger.Critical("Error: %v whilst counting orphaned objects") + logger.Critical("Error: %v whilst counting orphaned objects", err) return err } if count > 0 { if autofix { if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil { - logger.Critical("Error: %v whilst deleting orphaned objects") + logger.Critical("Error: %v whilst deleting orphaned objects", err) return err } logger.Info("%d tracked times without existing issue deleted", count) @@ -113,14 +113,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find null archived repositories count, err = models.CountNullArchivedRepository() if err != nil { - logger.Critical("Error: %v whilst counting null archived repositories") + logger.Critical("Error: %v whilst counting null archived repositories", err) return err } if count > 0 { if autofix { updatedCount, err := models.FixNullArchivedRepository() if err != nil { - logger.Critical("Error: %v whilst fixing null archived repositories") + logger.Critical("Error: %v whilst fixing null archived repositories", err) return err } logger.Info("%d repositories with null is_archived updated", updatedCount) @@ -132,14 +132,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { // find label comments with empty labels count, err = models.CountCommentTypeLabelWithEmptyLabel() if err != nil { - logger.Critical("Error: %v whilst counting label comments with empty labels") + logger.Critical("Error: %v whilst counting label comments with empty labels", err) return err } if count > 0 { if autofix { updatedCount, err := models.FixCommentTypeLabelWithEmptyLabel() if err != nil { - logger.Critical("Error: %v whilst removing label comments with empty labels") + logger.Critical("Error: %v whilst removing label comments with empty labels", err) return err } logger.Info("%d label comments with empty labels removed", updatedCount) @@ -191,13 +191,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { if setting.Database.UsePostgreSQL { count, err = models.CountBadSequences() if err != nil { - logger.Critical("Error: %v whilst checking sequence values") + logger.Critical("Error: %v whilst checking sequence values", err) + return err } if count > 0 { if autofix { err := models.FixBadSequences() if err != nil { - logger.Critical("Error: %v whilst attempting to fix sequences") + logger.Critical("Error: %v whilst attempting to fix sequences", err) return err } logger.Info("%d sequences updated", count) @@ -207,6 +208,60 @@ func checkDBConsistency(logger log.Logger, autofix bool) error { } } + // find protected branches without existing repository + count, err = models.CountOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id") + if err != nil { + logger.Critical("Error: %v whilst counting orphaned objects", err) + return err + } + if count > 0 { + if autofix { + if err = models.DeleteOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id"); err != nil { + logger.Critical("Error: %v whilst deleting orphaned objects", err) + return err + } + logger.Info("%d protected branches without existing repository deleted", count) + } else { + logger.Warn("%d protected branches without existing repository", count) + } + } + + // find deleted branches without existing repository + count, err = models.CountOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id") + if err != nil { + logger.Critical("Error: %v whilst counting orphaned objects", err) + return err + } + if count > 0 { + if autofix { + if err = models.DeleteOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id"); err != nil { + logger.Critical("Error: %v whilst deleting orphaned objects", err) + return err + } + logger.Info("%d deleted branches without existing repository deleted", count) + } else { + logger.Warn("%d deleted branches without existing repository", count) + } + } + + // find LFS locks without existing repository + count, err = models.CountOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id") + if err != nil { + logger.Critical("Error: %v whilst counting orphaned objects", err) + return err + } + if count > 0 { + if autofix { + if err = models.DeleteOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id"); err != nil { + logger.Critical("Error: %v whilst deleting orphaned objects", err) + return err + } + logger.Info("%d LFS locks without existing repository deleted", count) + } else { + logger.Warn("%d LFS locks without existing repository", count) + } + } + return nil } diff --git a/modules/doctor/dbversion.go b/modules/doctor/dbversion.go index f82cf72093..c5cac3bf91 100644 --- a/modules/doctor/dbversion.go +++ b/modules/doctor/dbversion.go @@ -23,7 +23,7 @@ func checkDBVersion(logger log.Logger, autofix bool) error { err = models.NewEngine(context.Background(), migrations.Migrate) if err != nil { - logger.Critical("Error: %v during migration") + logger.Critical("Error: %v during migration", err) } return err } diff --git a/modules/emoji/emoji.go b/modules/emoji/emoji.go index 01fb764ce3..85df2d6973 100644 --- a/modules/emoji/emoji.go +++ b/modules/emoji/emoji.go @@ -6,6 +6,7 @@ package emoji import ( + "io" "sort" "strings" "sync" @@ -145,6 +146,8 @@ func (n *rememberSecondWriteWriter) Write(p []byte) (int, error) { if n.writecount == 2 { n.idx = n.pos n.end = n.pos + len(p) + n.pos += len(p) + return len(p), io.EOF } n.pos += len(p) return len(p), nil @@ -155,6 +158,8 @@ func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) { if n.writecount == 2 { n.idx = n.pos n.end = n.pos + len(s) + n.pos += len(s) + return len(s), io.EOF } n.pos += len(s) return len(s), nil diff --git a/modules/forms/auth_form.go b/modules/forms/auth_form.go index 7cf6b9fcd5..30621cadff 100644 --- a/modules/forms/auth_form.go +++ b/modules/forms/auth_form.go @@ -51,6 +51,7 @@ type AuthenticationForm struct { TLS bool SkipVerify bool PAMServiceName string + PAMEmailDomain string Oauth2Provider string Oauth2Key string Oauth2Secret string diff --git a/modules/generate/generate.go b/modules/generate/generate.go index 304ad87f21..f030091a38 100644 --- a/modules/generate/generate.go +++ b/modules/generate/generate.go @@ -12,7 +12,7 @@ import ( "math/big" "time" - "github.com/dgrijalva/jwt-go" + "github.com/golang-jwt/jwt" ) // GetRandomString generate random string by specify chars. diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 6014508b93..a3288872f1 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -7,6 +7,7 @@ package git import ( "bufio" "bytes" + "context" "io" "math" "strconv" @@ -15,20 +16,24 @@ import ( // CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function func CatFileBatch(repoPath string) (*io.PipeWriter, *bufio.Reader, func()) { - // Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary. + // We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary. // so let's create a batch stdin and stdout batchStdinReader, batchStdinWriter := io.Pipe() batchStdoutReader, batchStdoutWriter := io.Pipe() + ctx, ctxCancel := context.WithCancel(DefaultContext) + closed := make(chan struct{}) cancel := func() { _ = batchStdinReader.Close() _ = batchStdinWriter.Close() _ = batchStdoutReader.Close() _ = batchStdoutWriter.Close() + ctxCancel() + <-closed } go func() { stderr := strings.Builder{} - err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader) + err := NewCommandContext(ctx, "cat-file", "--batch").RunInDirFullPipeline(repoPath, batchStdoutWriter, &stderr, batchStdinReader) if err != nil { _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String())) @@ -36,10 +41,11 @@ func CatFileBatch(repoPath string) (*io.PipeWriter, *bufio.Reader, func()) { _ = batchStdoutWriter.Close() _ = batchStdinReader.Close() } + close(closed) }() // For simplicities sake we'll us a buffered reader to read from the cat-file --batch - batchReader := bufio.NewReader(batchStdoutReader) + batchReader := bufio.NewReaderSize(batchStdoutReader, 32*1024) return batchStdinWriter, batchReader, cancel } @@ -149,17 +155,18 @@ headerLoop: // constant hextable to help quickly convert between 20byte and 40byte hashes const hextable = "0123456789abcdef" -// to40ByteSHA converts a 20-byte SHA in a 40-byte slice into a 40-byte sha in place -// without allocations. This is at least 100x quicker that hex.EncodeToString -// NB This requires that sha is a 40-byte slice -func to40ByteSHA(sha []byte) []byte { +// To40ByteSHA converts a 20-byte SHA into a 40-byte sha. Input and output can be the +// same 40 byte slice to support in place conversion without allocations. +// This is at least 100x quicker that hex.EncodeToString +// NB This requires that out is a 40-byte slice +func To40ByteSHA(sha, out []byte) []byte { for i := 19; i >= 0; i-- { v := sha[i] vhi, vlo := v>>4, v&0x0f shi, slo := hextable[vhi], hextable[vlo] - sha[i*2], sha[i*2+1] = shi, slo + out[i*2], out[i*2+1] = shi, slo } - return sha + return out } // ParseTreeLineSkipMode reads an entry from a tree in a cat-file --batch stream diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 9043de5955..d02251ed90 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -7,6 +7,7 @@ package git import ( "io/ioutil" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -14,32 +15,15 @@ import ( ) func TestBlob_Data(t *testing.T) { - output := `Copyright (c) 2016 The Gitea Authors -Copyright (c) 2015 The Gogs Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -` - repo, err := OpenRepository("../../.git") - assert.NoError(t, err) + output := "file2\n" + bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") + repo, err := OpenRepository(bareRepo1Path) + if !assert.NoError(t, err) { + t.Fatal() + } defer repo.Close() - testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") + testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375") assert.NoError(t, err) r, err := testBlob.DataAsync() @@ -53,13 +37,14 @@ THE SOFTWARE. } func Benchmark_Blob_Data(b *testing.B) { - repo, err := OpenRepository("../../.git") + bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") + repo, err := OpenRepository(bareRepo1Path) if err != nil { b.Fatal(err) } defer repo.Close() - testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") + testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375") if err != nil { b.Fatal(err) } diff --git a/modules/git/command.go b/modules/git/command.go index fe25895462..ef78464d5f 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -124,12 +124,18 @@ func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time. cmd := exec.CommandContext(ctx, c.name, c.args...) if env == nil { - cmd.Env = append(os.Environ(), fmt.Sprintf("LC_ALL=%s", DefaultLocale)) + cmd.Env = os.Environ() } else { cmd.Env = env - cmd.Env = append(cmd.Env, fmt.Sprintf("LC_ALL=%s", DefaultLocale)) } + cmd.Env = append( + cmd.Env, + fmt.Sprintf("LC_ALL=%s", DefaultLocale), + // avoid prompting for credentials interactively, supported since git v2.3 + "GIT_TERMINAL_PROMPT=0", + ) + // TODO: verify if this is still needed in golang 1.15 if goVersionLessThan115 { cmd.Env = append(cmd.Env, "GODEBUG=asyncpreemptoff=1") diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index b844468c8c..38bad84082 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -102,10 +102,13 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache *LastCo } func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) { + wr, rd, cancel := CatFileBatch(cache.repo.Path) + defer cancel() + var unHitEntryPaths []string var results = make(map[string]*Commit) for _, p := range paths { - lastCommit, err := cache.Get(commitID, path.Join(treePath, p)) + lastCommit, err := cache.Get(commitID, path.Join(treePath, p), wr, rd) if err != nil { return nil, nil, err } @@ -300,7 +303,7 @@ revListLoop: commits[0] = string(commitID) } } - treeID = to40ByteSHA(treeID) + treeID = To40ByteSHA(treeID, treeID) _, err = batchStdinWriter.Write(treeID) if err != nil { return nil, err diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go index a4d15b6bad..3c1f6f5ffd 100644 --- a/modules/git/commit_reader.go +++ b/modules/git/commit_reader.go @@ -17,7 +17,9 @@ import ( // If used as part of a cat-file --batch stream you need to limit the reader to the correct size func CommitFromReader(gitRepo *Repository, sha SHA1, reader io.Reader) (*Commit, error) { commit := &Commit{ - ID: sha, + ID: sha, + Author: &Signature{}, + Committer: &Signature{}, } payloadSB := new(strings.Builder) diff --git a/modules/git/diff.go b/modules/git/diff.go index 6faad1c3c0..5da53568e5 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -47,7 +47,7 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { commit, err := repo.GetCommit(endCommit) if err != nil { - return fmt.Errorf("GetCommit: %v", err) + return err } fileArgs := make([]string, 0) if len(file) > 0 { diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go index 0e52d16538..0a1babb112 100644 --- a/modules/git/last_commit_cache_nogogit.go +++ b/modules/git/last_commit_cache_nogogit.go @@ -7,6 +7,8 @@ package git import ( + "bufio" + "io" "path" ) @@ -34,7 +36,7 @@ func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl func() int64, } // Get get the last commit information by commit id and entry path -func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) { +func (c *LastCommitCache) Get(ref, entryPath string, wr *io.PipeWriter, rd *bufio.Reader) (interface{}, error) { v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath)) if vs, ok := v.(string); ok { log("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs) @@ -46,7 +48,10 @@ func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) { if err != nil { return nil, err } - commit, err := c.repo.getCommit(id) + if _, err := wr.Write([]byte(vs + "\n")); err != nil { + return nil, err + } + commit, err := c.repo.getCommitFromBatchReader(rd, id) if err != nil { return nil, err } diff --git a/modules/git/notes_nogogit.go b/modules/git/notes_nogogit.go index 613efd2e0e..1379e50853 100644 --- a/modules/git/notes_nogogit.go +++ b/modules/git/notes_nogogit.go @@ -8,6 +8,7 @@ package git import ( "io/ioutil" + "strings" ) // GetNote retrieves the git-notes data for a given commit. @@ -49,7 +50,13 @@ func GetNote(repo *Repository, commitID string, note *Note) error { } note.Message = d - lastCommits, err := GetLastCommitForPaths(notes, "", []string{path}) + treePath := "" + if idx := strings.LastIndex(path, "/"); idx > -1 { + treePath = path[:idx] + path = path[idx+1:] + } + + lastCommits, err := GetLastCommitForPaths(notes, treePath, []string{path}) if err != nil { return err } diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index f6faa3a48a..9b5909948c 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -43,8 +43,6 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { basePath := repo.Path - hashStr := hash.String() - // Use rev-list to provide us with all commits in order revListReader, revListWriter := io.Pipe() defer func() { @@ -74,7 +72,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { fnameBuf := make([]byte, 4096) modeBuf := make([]byte, 40) - workingShaBuf := make([]byte, 40) + workingShaBuf := make([]byte, 20) for scan.Scan() { // Get the next commit ID @@ -127,12 +125,12 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { case "tree": var n int64 for n < size { - mode, fname, sha, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf) + mode, fname, sha20byte, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf) if err != nil { return nil, err } n += int64(count) - if bytes.Equal(sha, []byte(hashStr)) { + if bytes.Equal(sha20byte, hash[:]) { result := LFSResult{ Name: curPath + string(fname), SHA: curCommit.ID.String(), @@ -142,7 +140,9 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { } resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result } else if string(mode) == git.EntryModeTree.String() { - trees = append(trees, sha) + sha40Byte := make([]byte, 40) + git.To40ByteSHA(sha20byte, sha40Byte) + trees = append(trees, sha40Byte) paths = append(paths, curPath+string(fname)+"/") } } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index ea0aeeb35d..5e2db34fd1 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -21,14 +21,7 @@ func (repo *Repository) GetBranchCommitID(name string) (string, error) { // GetTagCommitID returns last commit ID string of given tag. func (repo *Repository) GetTagCommitID(name string) (string, error) { - stdout, err := NewCommand("rev-list", "-n", "1", TagPrefix+name).RunInDir(repo.Path) - if err != nil { - if strings.Contains(err.Error(), "unknown revision or path") { - return "", ErrNotExist{name, ""} - } - return "", err - } - return strings.TrimSpace(stdout), nil + return repo.GetRefCommitID(TagPrefix + name) } // ConvertToSHA1 returns a Hash object from a potential ID string diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index 0a92de1703..df56b26b01 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -9,9 +9,10 @@ package git import ( "bufio" "errors" - "fmt" "io" "io/ioutil" + "os" + "path/filepath" "strings" ) @@ -34,6 +35,18 @@ func (repo *Repository) ResolveReference(name string) (string, error) { // GetRefCommitID returns the last commit ID string of given reference (branch or tag). func (repo *Repository) GetRefCommitID(name string) (string, error) { + if strings.HasPrefix(name, "refs/") { + // We're gonna try just reading the ref file as this is likely to be quicker than other options + fileInfo, err := os.Lstat(filepath.Join(repo.Path, name)) + if err == nil && fileInfo.Mode().IsRegular() && fileInfo.Size() == 41 { + ref, err := ioutil.ReadFile(filepath.Join(repo.Path, name)) + + if err == nil && SHAPattern.Match(ref[:40]) && ref[40] == '\n' { + return string(ref[:40]), nil + } + } + } + stdout, err := NewCommand("show-ref", "--verify", "--hash", name).RunInDir(repo.Path) if err != nil { if strings.Contains(err.Error(), "not a valid ref") { @@ -69,6 +82,11 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { }() bufReader := bufio.NewReader(stdoutReader) + + return repo.getCommitFromBatchReader(bufReader, id) +} + +func (repo *Repository) getCommitFromBatchReader(bufReader *bufio.Reader, id SHA1) (*Commit, error) { _, typ, size, err := ReadBatchLine(bufReader) if err != nil { if errors.Is(err, io.EOF) { @@ -106,7 +124,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { case "commit": return CommitFromReader(repo, id, io.LimitReader(bufReader, size)) default: - _ = stdoutReader.CloseWithError(fmt.Errorf("unknown typ: %s", typ)) log("Unknown typ: %s", typ) return nil, ErrNotExist{ ID: id.String(), diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go index b5a235921c..20a7b061f2 100644 --- a/modules/git/repo_language_stats_gogit.go +++ b/modules/git/repo_language_stats_gogit.go @@ -43,7 +43,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err sizes := make(map[string]int64) err = tree.Files().ForEach(func(f *object.File) error { - if f.Size == 0 || enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) || + if f.Size == 0 || analyze.IsVendor(f.Name) || enry.IsDotFile(f.Name) || enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) { return nil } diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go index a929d7953b..3f197f8d74 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/repo_language_stats_nogogit.go @@ -67,7 +67,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err for _, f := range entries { contentBuf.Reset() content = contentBuf.Bytes() - if f.Size() == 0 || enry.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) || + if f.Size() == 0 || analyze.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) || enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) { continue } diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index 0b6986764c..9517783e6b 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -7,23 +7,18 @@ package git import ( "path/filepath" "testing" - "time" "github.com/stretchr/testify/assert" ) func TestGetLatestCommitTime(t *testing.T) { - lct, err := GetLatestCommitTime(".") + bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") + lct, err := GetLatestCommitTime(bareRepo1Path) assert.NoError(t, err) - // Time is in the past - now := time.Now() - assert.True(t, lct.Unix() < now.Unix(), "%d not smaller than %d", lct, now) - // Time is after Mon Oct 23 03:52:09 2017 +0300 + // Time is Sun Jul 21 22:43:13 2019 +0200 // which is the time of commit - // d47b98c44c9a6472e44ab80efe65235e11c6da2a - refTime, err := time.Parse("Mon Jan 02 15:04:05 2006 -0700", "Mon Oct 23 03:52:09 2017 +0300") - assert.NoError(t, err) - assert.True(t, lct.Unix() > refTime.Unix(), "%d not greater than %d", lct, refTime) + // feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master) + assert.EqualValues(t, 1563741793, lct.Unix()) } func TestRepoIsEmpty(t *testing.T) { diff --git a/modules/git/tag.go b/modules/git/tag.go index 0323cc42ed..23f09e25b6 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -35,6 +35,7 @@ func (tag *Tag) Commit() (*Commit, error) { // \n\n separate headers from message func parseTagData(data []byte) (*Tag, error) { tag := new(Tag) + tag.Tagger = &Signature{} // we now have the contents of the commit object. Let's investigate... nextline := 0 l: diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go index 2ae38188a6..ec47f0ad84 100644 --- a/modules/gitgraph/graph_models.go +++ b/modules/gitgraph/graph_models.go @@ -7,6 +7,7 @@ package gitgraph import ( "bytes" "fmt" + "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" @@ -216,10 +217,10 @@ func newRefsFromRefNames(refNames []byte) []git.Reference { continue } refName := string(refNameBytes) - if refName[0:5] == "tag: " { - refName = refName[5:] - } else if refName[0:8] == "HEAD -> " { - refName = refName[8:] + if strings.HasPrefix(refName, "tag: ") { + refName = strings.TrimPrefix(refName, "tag: ") + } else if strings.HasPrefix(refName, "HEAD -> ") { + refName = strings.TrimPrefix(refName, "HEAD -> ") } refs = append(refs, git.Reference{ Name: refName, diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index 1ebdaa1970..14923c2a9b 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -68,17 +68,21 @@ func (g *Manager) start() { // Set the running state g.setState(stateRunning) if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip { + log.Trace("Skipping SVC check as SKIP_MINWINSVC is set") return } // Make SVC process run := svc.Run - isInteractive, err := svc.IsWindowsService() + + //lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile + isAnInteractiveSession, err := svc.IsAnInteractiveSession() if err != nil { - log.Error("Unable to ascertain if running as an Interactive Session: %v", err) + log.Error("Unable to ascertain if running as an Windows Service: %v", err) return } - if isInteractive { + if isAnInteractiveSession { + log.Trace("Not running a service ... using the debug SVC manager") run = debug.Run } go func() { @@ -94,38 +98,49 @@ func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, statu status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)} } + log.Trace("Awaiting server start-up") // Now need to wait for everything to start... if !g.awaitServer(setting.StartupTimeout) { + log.Trace("... start-up failed ... Stopped") return false, 1 } + log.Trace("Sending Running state to SVC") + // We need to implement some way of svc.AcceptParamChange/svc.ParamChange status <- svc.Status{ State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode, } + log.Trace("Started") + waitTime := 30 * time.Second loop: for { select { case <-g.ctx.Done(): + log.Trace("Shutting down") g.DoGracefulShutdown() waitTime += setting.GracefulHammerTime break loop case <-g.shutdownRequested: + log.Trace("Shutting down") waitTime += setting.GracefulHammerTime break loop case change := <-changes: switch change.Cmd { case svc.Interrogate: + log.Trace("SVC sent interrogate") status <- change.CurrentStatus case svc.Stop, svc.Shutdown: + log.Trace("SVC requested shutdown - shutting down") g.DoGracefulShutdown() waitTime += setting.GracefulHammerTime break loop case hammerCode: + log.Trace("SVC requested hammer - shutting down and hammering immediately") g.DoGracefulShutdown() g.DoImmediateHammer() break loop @@ -134,6 +149,8 @@ loop: } } } + + log.Trace("Sending StopPending state to SVC") status <- svc.Status{ State: svc.StopPending, WaitHint: uint32(waitTime / time.Millisecond), @@ -145,8 +162,10 @@ hammerLoop: case change := <-changes: switch change.Cmd { case svc.Interrogate: + log.Trace("SVC sent interrogate") status <- change.CurrentStatus case svc.Stop, svc.Shutdown, hammerCmd: + log.Trace("SVC requested hammer - hammering immediately") g.DoImmediateHammer() break hammerLoop default: @@ -156,6 +175,8 @@ hammerLoop: break hammerLoop } } + + log.Trace("Stopped") return false, 0 } diff --git a/modules/graceful/server.go b/modules/graceful/server.go index c5021a9ba5..704aa8a2b7 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -17,6 +17,7 @@ import ( "time" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" ) var ( @@ -26,6 +27,10 @@ var ( DefaultWriteTimeOut time.Duration // DefaultMaxHeaderBytes default max header bytes DefaultMaxHeaderBytes int + // PerWriteWriteTimeout timeout for writes + PerWriteWriteTimeout = 30 * time.Second + // PerWriteWriteTimeoutKbTime is a timeout taking account of how much there is to be written + PerWriteWriteTimeoutKbTime = 10 * time.Second ) func init() { @@ -37,14 +42,16 @@ type ServeFunction = func(net.Listener) error // Server represents our graceful server type Server struct { - network string - address string - listener net.Listener - wg sync.WaitGroup - state state - lock *sync.RWMutex - BeforeBegin func(network, address string) - OnShutdown func() + network string + address string + listener net.Listener + wg sync.WaitGroup + state state + lock *sync.RWMutex + BeforeBegin func(network, address string) + OnShutdown func() + PerWriteTimeout time.Duration + PerWritePerKbTimeout time.Duration } // NewServer creates a server on network at provided address @@ -55,11 +62,13 @@ func NewServer(network, address, name string) *Server { log.Info("Starting new %s server: %s:%s on PID: %d", name, network, address, os.Getpid()) } srv := &Server{ - wg: sync.WaitGroup{}, - state: stateInit, - lock: &sync.RWMutex{}, - network: network, - address: address, + wg: sync.WaitGroup{}, + state: stateInit, + lock: &sync.RWMutex{}, + network: network, + address: address, + PerWriteTimeout: setting.PerWriteTimeout, + PerWritePerKbTimeout: setting.PerWritePerKbTimeout, } srv.BeforeBegin = func(network, addr string) { @@ -221,9 +230,11 @@ func (wl *wrappedListener) Accept() (net.Conn, error) { closed := int32(0) c = wrappedConn{ - Conn: c, - server: wl.server, - closed: &closed, + Conn: c, + server: wl.server, + closed: &closed, + perWriteTimeout: wl.server.PerWriteTimeout, + perWritePerKbTimeout: wl.server.PerWritePerKbTimeout, } wl.server.wg.Add(1) @@ -246,8 +257,25 @@ func (wl *wrappedListener) File() (*os.File, error) { type wrappedConn struct { net.Conn - server *Server - closed *int32 + server *Server + closed *int32 + deadline time.Time + perWriteTimeout time.Duration + perWritePerKbTimeout time.Duration +} + +func (w wrappedConn) Write(p []byte) (n int, err error) { + if w.perWriteTimeout > 0 { + minTimeout := time.Duration(len(p)/1024) * w.perWritePerKbTimeout + minDeadline := time.Now().Add(minTimeout).Add(w.perWriteTimeout) + + w.deadline = w.deadline.Add(minTimeout) + if minDeadline.After(w.deadline) { + w.deadline = minDeadline + } + _ = w.Conn.SetWriteDeadline(w.deadline) + } + return w.Conn.Write(p) } func (w wrappedConn) Close() error { diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index cf35cef129..f5e3906be6 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" "code.gitea.io/gitea/modules/setting" @@ -26,11 +27,13 @@ func GetCacheControl() string { // generateETag generates an ETag based on size, filename and file modification time func generateETag(fi os.FileInfo) string { etag := fmt.Sprint(fi.Size()) + fi.Name() + fi.ModTime().UTC().Format(http.TimeFormat) - return base64.StdEncoding.EncodeToString([]byte(etag)) + return `"` + base64.StdEncoding.EncodeToString([]byte(etag)) + `"` } // HandleTimeCache handles time-based caching for a HTTP request func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { + w.Header().Set("Cache-Control", GetCacheControl()) + ifModifiedSince := req.Header.Get("If-Modified-Since") if ifModifiedSince != "" { t, err := time.Parse(http.TimeFormat, ifModifiedSince) @@ -40,20 +43,40 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) ( } } - w.Header().Set("Cache-Control", GetCacheControl()) w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat)) return false } -// HandleEtagCache handles ETag-based caching for a HTTP request -func HandleEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { +// HandleFileETagCache handles ETag-based caching for a HTTP request +func HandleFileETagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { etag := generateETag(fi) - if req.Header.Get("If-None-Match") == etag { - w.WriteHeader(http.StatusNotModified) - return true - } + return HandleGenericETagCache(req, w, etag) +} +// HandleGenericETagCache handles ETag-based caching for a HTTP request. +// It returns true if the request was handled. +func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) { + if len(etag) > 0 { + w.Header().Set("Etag", etag) + if checkIfNoneMatchIsValid(req, etag) { + w.WriteHeader(http.StatusNotModified) + return true + } + } w.Header().Set("Cache-Control", GetCacheControl()) - w.Header().Set("ETag", etag) + return false +} + +// checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag +func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { + ifNoneMatch := req.Header.Get("If-None-Match") + if len(ifNoneMatch) > 0 { + for _, item := range strings.Split(ifNoneMatch, ",") { + item = strings.TrimSpace(item) + if item == etag { + return true + } + } + } return false } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go new file mode 100644 index 0000000000..fe5ca17956 --- /dev/null +++ b/modules/httpcache/httpcache_test.go @@ -0,0 +1,144 @@ +// 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 httpcache + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type mockFileInfo struct { +} + +func (m mockFileInfo) Name() string { return "gitea.test" } +func (m mockFileInfo) Size() int64 { return int64(10) } +func (m mockFileInfo) Mode() os.FileMode { return os.ModePerm } +func (m mockFileInfo) ModTime() time.Time { return time.Time{} } +func (m mockFileInfo) IsDir() bool { return false } +func (m mockFileInfo) Sys() interface{} { return nil } + +func TestHandleFileETagCache(t *testing.T) { + fi := mockFileInfo{} + etag := `"MTBnaXRlYS50ZXN0TW9uLCAwMSBKYW4gMDAwMSAwMDowMDowMCBHTVQ="` + + t.Run("No_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + handled := HandleFileETagCache(req, w, fi) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Wrong_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag"`) + + handled := HandleFileETagCache(req, w, fi) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Correct_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", etag) + + handled := HandleFileETagCache(req, w, fi) + + assert.True(t, handled) + assert.Len(t, w.Header(), 1) + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + assert.Equal(t, http.StatusNotModified, w.Code) + }) +} + +func TestHandleGenericETagCache(t *testing.T) { + etag := `"test"` + + t.Run("No_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + handled := HandleGenericETagCache(req, w, etag) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Wrong_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag"`) + + handled := HandleGenericETagCache(req, w, etag) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Correct_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", etag) + + handled := HandleGenericETagCache(req, w, etag) + + assert.True(t, handled) + assert.Len(t, w.Header(), 1) + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + assert.Equal(t, http.StatusNotModified, w.Code) + }) + t.Run("Multiple_Wrong_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag", "wrong etag "`) + + handled := HandleGenericETagCache(req, w, etag) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Multiple_Correct_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag", `+etag) + + handled := HandleGenericETagCache(req, w, etag) + + assert.True(t, handled) + assert.Len(t, w.Header(), 1) + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + assert.Equal(t, http.StatusNotModified, w.Code) + }) +} diff --git a/modules/httplib/httplib.go b/modules/httplib/httplib.go index 62f284d2e1..294ad0b70b 100644 --- a/modules/httplib/httplib.go +++ b/modules/httplib/httplib.go @@ -325,7 +325,7 @@ func (r *Request) getResponse() (*http.Response, error) { trans = &http.Transport{ TLSClientConfig: r.setting.TLSClientConfig, Proxy: proxy, - Dial: TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout), + Dial: TimeoutDialer(r.setting.ConnectTimeout), } } else if t, ok := trans.(*http.Transport); ok { if t.TLSClientConfig == nil { @@ -335,7 +335,7 @@ func (r *Request) getResponse() (*http.Response, error) { t.Proxy = r.setting.Proxy } if t.Dial == nil { - t.Dial = TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout) + t.Dial = TimeoutDialer(r.setting.ConnectTimeout) } } @@ -352,6 +352,7 @@ func (r *Request) getResponse() (*http.Response, error) { client := &http.Client{ Transport: trans, Jar: jar, + Timeout: r.setting.ReadWriteTimeout, } if len(r.setting.UserAgent) > 0 && len(r.req.Header.Get("User-Agent")) == 0 { @@ -457,12 +458,12 @@ func (r *Request) Response() (*http.Response, error) { } // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. -func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { +func TimeoutDialer(cTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { return func(netw, addr string) (net.Conn, error) { conn, err := net.DialTimeout(netw, addr, cTimeout) if err != nil { return nil, err } - return conn, conn.SetDeadline(time.Now().Add(rwTimeout)) + return conn, nil } } diff --git a/modules/indexer/code/bleve.go b/modules/indexer/code/bleve.go index 573ea8b88c..416adeea74 100644 --- a/modules/indexer/code/bleve.go +++ b/modules/indexer/code/bleve.go @@ -178,7 +178,7 @@ func NewBleveIndexer(indexDir string) (*BleveIndexer, bool, error) { func (b *BleveIndexer) addUpdate(batchWriter *io.PipeWriter, batchReader *bufio.Reader, commitSha string, update fileUpdate, repo *models.Repository, batch rupture.FlushingBatch) error { // Ignore vendored files in code search - if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) { + if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) { return nil } diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go index 5327eb1e51..ebb7910fdc 100644 --- a/modules/indexer/code/elastic_search.go +++ b/modules/indexer/code/elastic_search.go @@ -177,7 +177,7 @@ func (b *ElasticSearchIndexer) init() (bool, error) { func (b *ElasticSearchIndexer) addUpdate(batchWriter *io.PipeWriter, batchReader *bufio.Reader, sha string, update fileUpdate, repo *models.Repository) ([]elastic.BulkableRequest, error) { // Ignore vendored files in code search - if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) { + if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) { return nil, nil } diff --git a/modules/indexer/stats/db.go b/modules/indexer/stats/db.go index bc3fbc13d8..976bf2d632 100644 --- a/modules/indexer/stats/db.go +++ b/modules/indexer/stats/db.go @@ -38,7 +38,11 @@ func (db *DBIndexer) Index(id int64) error { // Get latest commit for default branch commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch) if err != nil { - log.Error("Unable to get commit ID for defaultbranch %s in %s", repo.DefaultBranch, repo.RepoPath()) + if git.IsErrBranchNotExist(err) || git.IsErrNotExist((err)) { + log.Debug("Unable to get commit ID for defaultbranch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath()) + return nil + } + log.Error("Unable to get commit ID for defaultbranch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err) return err } diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go index 788ef5b9a6..520caa4c99 100644 --- a/modules/lfs/content_store.go +++ b/modules/lfs/content_store.go @@ -44,24 +44,13 @@ type ContentStore struct { } // Get takes a Meta object and retrieves the content from the store, returning -// it as an io.Reader. If fromByte > 0, the reader starts from that byte -func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) { +// it as an io.ReadSeekCloser. +func (s *ContentStore) Get(meta *models.LFSMetaObject) (storage.Object, error) { f, err := s.Open(meta.RelativePath()) if err != nil { log.Error("Whilst trying to read LFS OID[%s]: Unable to open Error: %v", meta.Oid, err) return nil, err } - if fromByte > 0 { - if fromByte >= meta.Size { - return nil, ErrRangeNotSatisfiable{ - FromByte: fromByte, - } - } - _, err = f.Seek(fromByte, io.SeekStart) - if err != nil { - log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err) - } - } return f, err } @@ -74,7 +63,7 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error { // now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then // the errors returned by the newHashingReader should percolate up to here - written, err := s.Save(p, wrappedRd) + written, err := s.Save(p, wrappedRd, meta.Size) if err != nil { log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, p, err) return err diff --git a/modules/lfs/pointers.go b/modules/lfs/pointers.go index c6fbf090e5..692c81f583 100644 --- a/modules/lfs/pointers.go +++ b/modules/lfs/pointers.go @@ -67,5 +67,5 @@ func IsPointerFile(buf *[]byte) *models.LFSMetaObject { // ReadMetaObject will read a models.LFSMetaObject and return a reader func ReadMetaObject(meta *models.LFSMetaObject) (io.ReadCloser, error) { contentStore := &ContentStore{ObjectStorage: storage.LFS} - return contentStore.Get(meta, 0) + return contentStore.Get(meta) } diff --git a/modules/lfs/server.go b/modules/lfs/server.go index 45cba9d9b7..6ebf7964ea 100644 --- a/modules/lfs/server.go +++ b/modules/lfs/server.go @@ -21,7 +21,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" - "github.com/dgrijalva/jwt-go" + "github.com/golang-jwt/jwt" jsoniter "github.com/json-iterator/go" ) @@ -175,6 +175,11 @@ func getContentHandler(ctx *context.Context) { statusCode = 206 fromByte, _ = strconv.ParseInt(match[1], 10, 32) + if fromByte >= meta.Size { + writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable) + return + } + if match[2] != "" { _toByte, _ := strconv.ParseInt(match[2], 10, 32) if _toByte >= fromByte && _toByte < toByte { @@ -188,18 +193,24 @@ func getContentHandler(ctx *context.Context) { } contentStore := &ContentStore{ObjectStorage: storage.LFS} - content, err := contentStore.Get(meta, fromByte) + content, err := contentStore.Get(meta) if err != nil { - if IsErrRangeNotSatisfiable(err) { - writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable) - } else { - // Errors are logged in contentStore.Get - writeStatus(ctx, 404) - } + // Errors are logged in contentStore.Get + writeStatus(ctx, http.StatusNotFound) return } defer content.Close() + if fromByte > 0 { + _, err = content.Seek(fromByte, io.SeekStart) + if err != nil { + log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err) + + writeStatus(ctx, http.StatusInternalServerError) + return + } + } + contentLength := toByte + 1 - fromByte ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10)) ctx.Resp.Header().Set("Content-Type", "application/octet-stream") diff --git a/modules/log/file.go b/modules/log/file.go index d5b38d4e01..79cbe740fd 100644 --- a/modules/log/file.go +++ b/modules/log/file.go @@ -177,7 +177,7 @@ func (log *FileLogger) DoRotate() error { // close fd before rename // Rename the file to its newfound home - if err = os.Rename(log.Filename, fname); err != nil { + if err = util.Rename(log.Filename, fname); err != nil { return fmt.Errorf("Rotate: %v", err) } diff --git a/modules/markup/html.go b/modules/markup/html.go index a7d66cc249..7b9844bf75 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -87,6 +87,7 @@ func isLinkStr(link string) bool { return validLinksPattern.MatchString(link) } +// FIXME: This function is not concurrent safe func getIssueFullPattern() *regexp.Regexp { if issueFullPattern == nil { issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) + @@ -313,7 +314,7 @@ func RenderEmoji( return ctx.postProcess(rawHTML) } -var tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL][ />])|(/?[hH][eE][aA][dD][ />]))`) +var tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`) var nulCleaner = strings.NewReplacer("\000", "") func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) { @@ -327,46 +328,43 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) { _, _ = res.WriteString("") // Strip out nuls - they're always invalid - _, _ = nulCleaner.WriteString(res, string(tagCleaner.ReplaceAll(rawHTML, []byte("<$1")))) + _, _ = res.Write(tagCleaner.ReplaceAll([]byte(nulCleaner.Replace(string(rawHTML))), []byte("<$1"))) // close the tags _, _ = res.WriteString("") // parse the HTML - nodes, err := html.ParseFragment(res, nil) + node, err := html.Parse(res) if err != nil { return nil, &postProcessError{"invalid HTML", err} } - for _, node := range nodes { - ctx.visitNode(node, true) + if node.Type == html.DocumentNode { + node = node.FirstChild } - newNodes := make([]*html.Node, 0, len(nodes)) + ctx.visitNode(node, true) - for _, node := range nodes { - if node.Data == "html" { - node = node.FirstChild - for node != nil && node.Data != "body" { - node = node.NextSibling - } - } - if node == nil { - continue + nodes := make([]*html.Node, 0, 5) + + if node.Data == "html" { + node = node.FirstChild + for node != nil && node.Data != "body" { + node = node.NextSibling } + } + if node != nil { if node.Data == "body" { child := node.FirstChild for child != nil { - newNodes = append(newNodes, child) + nodes = append(nodes, child) child = child.NextSibling } } else { - newNodes = append(newNodes, node) + nodes = append(nodes, node) } } - nodes = newNodes - // Create buffer in which the data will be placed again. We know that the // length will be at least that of res; to spare a few alloc+copy, we // reuse res, resetting its length to 0. @@ -403,24 +401,20 @@ func (ctx *postProcessCtx) visitNode(node *html.Node, visitText bool) { } case html.ElementNode: if node.Data == "img" { - attrs := node.Attr - for idx, attr := range attrs { + for i, attr := range node.Attr { if attr.Key != "src" { continue } - link := []byte(attr.Val) - if len(link) > 0 && !IsLink(link) { + if len(attr.Val) > 0 && !isLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { prefix := ctx.urlPrefix if ctx.isWikiMarkdown { prefix = util.URLJoin(prefix, "wiki", "raw") } prefix = strings.Replace(prefix, "/src/", "/media/", 1) - lnk := string(link) - lnk = util.URLJoin(prefix, lnk) - link = []byte(lnk) + attr.Val = util.URLJoin(prefix, attr.Val) } - node.Attr[idx].Val = string(link) + node.Attr[i] = attr } } else if node.Data == "a" { visitText = false @@ -610,26 +604,38 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) { } func mentionProcessor(ctx *postProcessCtx, node *html.Node) { - // We replace only the first mention; other mentions will be addressed later - found, loc := references.FindFirstMentionBytes([]byte(node.Data)) - if !found { - return - } - mention := node.Data[loc.Start:loc.End] - var teams string - teams, ok := ctx.metas["teams"] - // FIXME: util.URLJoin may not be necessary here: - // - setting.AppURL is defined to have a terminal '/' so unless mention[1:] - // is an AppSubURL link we can probably fallback to concatenation. - // team mention should follow @orgName/teamName style - if ok && strings.Contains(mention, "/") { - mentionOrgAndTeam := strings.Split(mention, "/") - if mentionOrgAndTeam[0][1:] == ctx.metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { - replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, "org", ctx.metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention")) + start := 0 + next := node.NextSibling + for node != nil && node != next && start < len(node.Data) { + // We replace only the first mention; other mentions will be addressed later + found, loc := references.FindFirstMentionBytes([]byte(node.Data[start:])) + if !found { + return } - return + loc.Start += start + loc.End += start + mention := node.Data[loc.Start:loc.End] + var teams string + teams, ok := ctx.metas["teams"] + // FIXME: util.URLJoin may not be necessary here: + // - setting.AppURL is defined to have a terminal '/' so unless mention[1:] + // is an AppSubURL link we can probably fallback to concatenation. + // team mention should follow @orgName/teamName style + if ok && strings.Contains(mention, "/") { + mentionOrgAndTeam := strings.Split(mention, "/") + if mentionOrgAndTeam[0][1:] == ctx.metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { + replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, "org", ctx.metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention")) + node = node.NextSibling.NextSibling + start = 0 + continue + } + start = loc.End + continue + } + replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention")) + node = node.NextSibling.NextSibling + start = 0 } - replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention")) } func shortLinkProcessor(ctx *postProcessCtx, node *html.Node) { @@ -637,188 +643,195 @@ func shortLinkProcessor(ctx *postProcessCtx, node *html.Node) { } func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) { - m := shortLinkPattern.FindStringSubmatchIndex(node.Data) - if m == nil { - return - } + next := node.NextSibling + for node != nil && node != next { + m := shortLinkPattern.FindStringSubmatchIndex(node.Data) + if m == nil { + return + } - content := node.Data[m[2]:m[3]] - tail := node.Data[m[4]:m[5]] - props := make(map[string]string) + content := node.Data[m[2]:m[3]] + tail := node.Data[m[4]:m[5]] + props := make(map[string]string) - // MediaWiki uses [[link|text]], while GitHub uses [[text|link]] - // It makes page handling terrible, but we prefer GitHub syntax - // And fall back to MediaWiki only when it is obvious from the look - // Of text and link contents - sl := strings.Split(content, "|") - for _, v := range sl { - if equalPos := strings.IndexByte(v, '='); equalPos == -1 { - // There is no equal in this argument; this is a mandatory arg - if props["name"] == "" { - if isLinkStr(v) { - // If we clearly see it is a link, we save it so + // MediaWiki uses [[link|text]], while GitHub uses [[text|link]] + // It makes page handling terrible, but we prefer GitHub syntax + // And fall back to MediaWiki only when it is obvious from the look + // Of text and link contents + sl := strings.Split(content, "|") + for _, v := range sl { + if equalPos := strings.IndexByte(v, '='); equalPos == -1 { + // There is no equal in this argument; this is a mandatory arg + if props["name"] == "" { + if isLinkStr(v) { + // If we clearly see it is a link, we save it so - // But first we need to ensure, that if both mandatory args provided - // look like links, we stick to GitHub syntax - if props["link"] != "" { - props["name"] = props["link"] + // But first we need to ensure, that if both mandatory args provided + // look like links, we stick to GitHub syntax + if props["link"] != "" { + props["name"] = props["link"] + } + + props["link"] = strings.TrimSpace(v) + } else { + props["name"] = v } - - props["link"] = strings.TrimSpace(v) } else { - props["name"] = v + props["link"] = strings.TrimSpace(v) } } else { - props["link"] = strings.TrimSpace(v) - } - } else { - // There is an equal; optional argument. + // There is an equal; optional argument. - sep := strings.IndexByte(v, '=') - key, val := v[:sep], html.UnescapeString(v[sep+1:]) + sep := strings.IndexByte(v, '=') + key, val := v[:sep], html.UnescapeString(v[sep+1:]) - // When parsing HTML, x/net/html will change all quotes which are - // not used for syntax into UTF-8 quotes. So checking val[0] won't - // be enough, since that only checks a single byte. - if len(val) > 1 { - if (strings.HasPrefix(val, "“") && strings.HasSuffix(val, "”")) || - (strings.HasPrefix(val, "‘") && strings.HasSuffix(val, "’")) { - const lenQuote = len("‘") - val = val[lenQuote : len(val)-lenQuote] - } else if (strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"")) || - (strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'")) { - val = val[1 : len(val)-1] - } else if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "’") { - const lenQuote = len("‘") - val = val[1 : len(val)-lenQuote] + // When parsing HTML, x/net/html will change all quotes which are + // not used for syntax into UTF-8 quotes. So checking val[0] won't + // be enough, since that only checks a single byte. + if len(val) > 1 { + if (strings.HasPrefix(val, "“") && strings.HasSuffix(val, "”")) || + (strings.HasPrefix(val, "‘") && strings.HasSuffix(val, "’")) { + const lenQuote = len("‘") + val = val[lenQuote : len(val)-lenQuote] + } else if (strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"")) || + (strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'")) { + val = val[1 : len(val)-1] + } else if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "’") { + const lenQuote = len("‘") + val = val[1 : len(val)-lenQuote] + } } + props[key] = val } - props[key] = val } - } - var name, link string - if props["link"] != "" { - link = props["link"] - } else if props["name"] != "" { - link = props["name"] - } - if props["title"] != "" { - name = props["title"] - } else if props["name"] != "" { - name = props["name"] - } else { - name = link - } - - name += tail - image := false - switch ext := filepath.Ext(link); ext { - // fast path: empty string, ignore - case "": - break - case ".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp", ".gif", ".bmp", ".ico", ".svg": - image = true - } - - childNode := &html.Node{} - linkNode := &html.Node{ - FirstChild: childNode, - LastChild: childNode, - Type: html.ElementNode, - Data: "a", - DataAtom: atom.A, - } - childNode.Parent = linkNode - absoluteLink := isLinkStr(link) - if !absoluteLink { - if image { - link = strings.ReplaceAll(link, " ", "+") + var name, link string + if props["link"] != "" { + link = props["link"] + } else if props["name"] != "" { + link = props["name"] + } + if props["title"] != "" { + name = props["title"] + } else if props["name"] != "" { + name = props["name"] } else { - link = strings.ReplaceAll(link, " ", "-") - } - if !strings.Contains(link, "/") { - link = url.PathEscape(link) - } - } - urlPrefix := ctx.urlPrefix - if image { - if !absoluteLink { - if IsSameDomain(urlPrefix) { - urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1) - } - if ctx.isWikiMarkdown { - link = util.URLJoin("wiki", "raw", link) - } - link = util.URLJoin(urlPrefix, link) - } - title := props["title"] - if title == "" { - title = props["alt"] - } - if title == "" { - title = path.Base(name) - } - alt := props["alt"] - if alt == "" { - alt = name + name = link } - // make the childNode an image - if we can, we also place the alt - childNode.Type = html.ElementNode - childNode.Data = "img" - childNode.DataAtom = atom.Img - childNode.Attr = []html.Attribute{ - {Key: "src", Val: link}, - {Key: "title", Val: title}, - {Key: "alt", Val: alt}, + name += tail + image := false + switch ext := filepath.Ext(link); ext { + // fast path: empty string, ignore + case "": + // leave image as false + case ".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp", ".gif", ".bmp", ".ico", ".svg": + image = true } - if alt == "" { - childNode.Attr = childNode.Attr[:2] + + childNode := &html.Node{} + linkNode := &html.Node{ + FirstChild: childNode, + LastChild: childNode, + Type: html.ElementNode, + Data: "a", + DataAtom: atom.A, } - } else { + childNode.Parent = linkNode + absoluteLink := isLinkStr(link) if !absoluteLink { - if ctx.isWikiMarkdown { - link = util.URLJoin("wiki", link) + if image { + link = strings.ReplaceAll(link, " ", "+") + } else { + link = strings.ReplaceAll(link, " ", "-") + } + if !strings.Contains(link, "/") { + link = url.PathEscape(link) } - link = util.URLJoin(urlPrefix, link) } - childNode.Type = html.TextNode - childNode.Data = name + urlPrefix := ctx.urlPrefix + if image { + if !absoluteLink { + if IsSameDomain(urlPrefix) { + urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1) + } + if ctx.isWikiMarkdown { + link = util.URLJoin("wiki", "raw", link) + } + link = util.URLJoin(urlPrefix, link) + } + title := props["title"] + if title == "" { + title = props["alt"] + } + if title == "" { + title = path.Base(name) + } + alt := props["alt"] + if alt == "" { + alt = name + } + + // make the childNode an image - if we can, we also place the alt + childNode.Type = html.ElementNode + childNode.Data = "img" + childNode.DataAtom = atom.Img + childNode.Attr = []html.Attribute{ + {Key: "src", Val: link}, + {Key: "title", Val: title}, + {Key: "alt", Val: alt}, + } + if alt == "" { + childNode.Attr = childNode.Attr[:2] + } + } else { + if !absoluteLink { + if ctx.isWikiMarkdown { + link = util.URLJoin("wiki", link) + } + link = util.URLJoin(urlPrefix, link) + } + childNode.Type = html.TextNode + childNode.Data = name + } + if noLink { + linkNode = childNode + } else { + linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} + } + replaceContent(node, m[0], m[1], linkNode) + node = node.NextSibling.NextSibling } - if noLink { - linkNode = childNode - } else { - linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} - } - replaceContent(node, m[0], m[1], linkNode) } func fullIssuePatternProcessor(ctx *postProcessCtx, node *html.Node) { if ctx.metas == nil { return } - m := getIssueFullPattern().FindStringSubmatchIndex(node.Data) - if m == nil { - return - } - link := node.Data[m[0]:m[1]] - id := "#" + node.Data[m[2]:m[3]] + next := node.NextSibling + for node != nil && node != next { + m := getIssueFullPattern().FindStringSubmatchIndex(node.Data) + if m == nil { + return + } + link := node.Data[m[0]:m[1]] + id := "#" + node.Data[m[2]:m[3]] - // extract repo and org name from matched link like - // http://localhost:3000/gituser/myrepo/issues/1 - linkParts := strings.Split(path.Clean(link), "/") - matchOrg := linkParts[len(linkParts)-4] - matchRepo := linkParts[len(linkParts)-3] + // extract repo and org name from matched link like + // http://localhost:3000/gituser/myrepo/issues/1 + linkParts := strings.Split(path.Clean(link), "/") + matchOrg := linkParts[len(linkParts)-4] + matchRepo := linkParts[len(linkParts)-3] - if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] { - // TODO if m[4]:m[5] is not nil, then link is to a comment, - // and we should indicate that in the text somehow - replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue")) - - } else { - orgRepoID := matchOrg + "/" + matchRepo + id - replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue")) + if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] { + // TODO if m[4]:m[5] is not nil, then link is to a comment, + // and we should indicate that in the text somehow + replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue")) + } else { + orgRepoID := matchOrg + "/" + matchRepo + id + replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue")) + } + node = node.NextSibling.NextSibling } } @@ -826,70 +839,74 @@ func issueIndexPatternProcessor(ctx *postProcessCtx, node *html.Node) { if ctx.metas == nil { return } - var ( found bool ref *references.RenderizableReference ) - _, exttrack := ctx.metas["format"] - alphanum := ctx.metas["style"] == IssueNameStyleAlphanumeric + next := node.NextSibling + for node != nil && node != next { + _, exttrack := ctx.metas["format"] + alphanum := ctx.metas["style"] == IssueNameStyleAlphanumeric - // Repos with external issue trackers might still need to reference local PRs - // We need to concern with the first one that shows up in the text, whichever it is - found, ref = references.FindRenderizableReferenceNumeric(node.Data, exttrack && alphanum) - if exttrack && alphanum { - if found2, ref2 := references.FindRenderizableReferenceAlphanumeric(node.Data); found2 { - if !found || ref2.RefLocation.Start < ref.RefLocation.Start { - found = true - ref = ref2 + // Repos with external issue trackers might still need to reference local PRs + // We need to concern with the first one that shows up in the text, whichever it is + found, ref = references.FindRenderizableReferenceNumeric(node.Data, exttrack && alphanum) + if exttrack && alphanum { + if found2, ref2 := references.FindRenderizableReferenceAlphanumeric(node.Data); found2 { + if !found || ref2.RefLocation.Start < ref.RefLocation.Start { + found = true + ref = ref2 + } } } - } - if !found { - return - } - - var link *html.Node - reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End] - if exttrack && !ref.IsPull { - ctx.metas["index"] = ref.Issue - link = createLink(com.Expand(ctx.metas["format"], ctx.metas), reftext, "ref-issue") - } else { - // Path determines the type of link that will be rendered. It's unknown at this point whether - // the linked item is actually a PR or an issue. Luckily it's of no real consequence because - // Gitea will redirect on click as appropriate. - path := "issues" - if ref.IsPull { - path = "pulls" + if !found { + return } - if ref.Owner == "" { - link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], path, ref.Issue), reftext, "ref-issue") + + var link *html.Node + reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End] + if exttrack && !ref.IsPull { + ctx.metas["index"] = ref.Issue + link = createLink(com.Expand(ctx.metas["format"], ctx.metas), reftext, "ref-issue") } else { - link = createLink(util.URLJoin(setting.AppURL, ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue") + // Path determines the type of link that will be rendered. It's unknown at this point whether + // the linked item is actually a PR or an issue. Luckily it's of no real consequence because + // Gitea will redirect on click as appropriate. + path := "issues" + if ref.IsPull { + path = "pulls" + } + if ref.Owner == "" { + link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], path, ref.Issue), reftext, "ref-issue") + } else { + link = createLink(util.URLJoin(setting.AppURL, ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue") + } } - } - if ref.Action == references.XRefActionNone { - replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) - return - } + if ref.Action == references.XRefActionNone { + replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) + node = node.NextSibling.NextSibling + continue + } - // Decorate action keywords if actionable - var keyword *html.Node - if references.IsXrefActionable(ref, exttrack, alphanum) { - keyword = createKeyword(node.Data[ref.ActionLocation.Start:ref.ActionLocation.End]) - } else { - keyword = &html.Node{ + // Decorate action keywords if actionable + var keyword *html.Node + if references.IsXrefActionable(ref, exttrack, alphanum) { + keyword = createKeyword(node.Data[ref.ActionLocation.Start:ref.ActionLocation.End]) + } else { + keyword = &html.Node{ + Type: html.TextNode, + Data: node.Data[ref.ActionLocation.Start:ref.ActionLocation.End], + } + } + spaces := &html.Node{ Type: html.TextNode, - Data: node.Data[ref.ActionLocation.Start:ref.ActionLocation.End], + Data: node.Data[ref.ActionLocation.End:ref.RefLocation.Start], } + replaceContentList(node, ref.ActionLocation.Start, ref.RefLocation.End, []*html.Node{keyword, spaces, link}) + node = node.NextSibling.NextSibling.NextSibling.NextSibling } - spaces := &html.Node{ - Type: html.TextNode, - Data: node.Data[ref.ActionLocation.End:ref.RefLocation.Start], - } - replaceContentList(node, ref.ActionLocation.Start, ref.RefLocation.End, []*html.Node{keyword, spaces, link}) } // fullSha1PatternProcessor renders SHA containing URLs @@ -897,87 +914,112 @@ func fullSha1PatternProcessor(ctx *postProcessCtx, node *html.Node) { if ctx.metas == nil { return } - m := anySHA1Pattern.FindStringSubmatchIndex(node.Data) - if m == nil { - return - } - urlFull := node.Data[m[0]:m[1]] - text := base.ShortSha(node.Data[m[2]:m[3]]) - - // 3rd capture group matches a optional path - subpath := "" - if m[5] > 0 { - subpath = node.Data[m[4]:m[5]] - } - - // 4th capture group matches a optional url hash - hash := "" - if m[7] > 0 { - hash = node.Data[m[6]:m[7]][1:] - } - - start := m[0] - end := m[1] - - // If url ends in '.', it's very likely that it is not part of the - // actual url but used to finish a sentence. - if strings.HasSuffix(urlFull, ".") { - end-- - urlFull = urlFull[:len(urlFull)-1] - if hash != "" { - hash = hash[:len(hash)-1] - } else if subpath != "" { - subpath = subpath[:len(subpath)-1] + next := node.NextSibling + for node != nil && node != next { + m := anySHA1Pattern.FindStringSubmatchIndex(node.Data) + if m == nil { + return } - } - if subpath != "" { - text += subpath - } + urlFull := node.Data[m[0]:m[1]] + text := base.ShortSha(node.Data[m[2]:m[3]]) - if hash != "" { - text += " (" + hash + ")" - } + // 3rd capture group matches a optional path + subpath := "" + if m[5] > 0 { + subpath = node.Data[m[4]:m[5]] + } - replaceContent(node, start, end, createCodeLink(urlFull, text, "commit")) + // 4th capture group matches a optional url hash + hash := "" + if m[7] > 0 { + hash = node.Data[m[6]:m[7]][1:] + } + + start := m[0] + end := m[1] + + // If url ends in '.', it's very likely that it is not part of the + // actual url but used to finish a sentence. + if strings.HasSuffix(urlFull, ".") { + end-- + urlFull = urlFull[:len(urlFull)-1] + if hash != "" { + hash = hash[:len(hash)-1] + } else if subpath != "" { + subpath = subpath[:len(subpath)-1] + } + } + + if subpath != "" { + text += subpath + } + + if hash != "" { + text += " (" + hash + ")" + } + + replaceContent(node, start, end, createCodeLink(urlFull, text, "commit")) + node = node.NextSibling.NextSibling + } } // emojiShortCodeProcessor for rendering text like :smile: into emoji func emojiShortCodeProcessor(ctx *postProcessCtx, node *html.Node) { - - m := EmojiShortCodeRegex.FindStringSubmatchIndex(node.Data) - if m == nil { - return - } - - alias := node.Data[m[0]:m[1]] - alias = strings.ReplaceAll(alias, ":", "") - converted := emoji.FromAlias(alias) - if converted == nil { - // check if this is a custom reaction - s := strings.Join(setting.UI.Reactions, " ") + "gitea" - if strings.Contains(s, alias) { - replaceContent(node, m[0], m[1], createCustomEmoji(alias, "emoji")) + start := 0 + next := node.NextSibling + for node != nil && node != next && start < len(node.Data) { + m := EmojiShortCodeRegex.FindStringSubmatchIndex(node.Data[start:]) + if m == nil { return } - return - } + m[0] += start + m[1] += start - replaceContent(node, m[0], m[1], createEmoji(converted.Emoji, "emoji", converted.Description)) + start = m[1] + + alias := node.Data[m[0]:m[1]] + alias = strings.ReplaceAll(alias, ":", "") + converted := emoji.FromAlias(alias) + if converted == nil { + // check if this is a custom reaction + s := strings.Join(setting.UI.Reactions, " ") + "gitea" + if strings.Contains(s, alias) { + replaceContent(node, m[0], m[1], createCustomEmoji(alias, "emoji")) + node = node.NextSibling.NextSibling + start = 0 + continue + } + continue + } + + replaceContent(node, m[0], m[1], createEmoji(converted.Emoji, "emoji", converted.Description)) + node = node.NextSibling.NextSibling + start = 0 + } } // emoji processor to match emoji and add emoji class func emojiProcessor(ctx *postProcessCtx, node *html.Node) { - m := emoji.FindEmojiSubmatchIndex(node.Data) - if m == nil { - return - } + start := 0 + next := node.NextSibling + for node != nil && node != next && start < len(node.Data) { + m := emoji.FindEmojiSubmatchIndex(node.Data[start:]) + if m == nil { + return + } + m[0] += start + m[1] += start - codepoint := node.Data[m[0]:m[1]] - val := emoji.FromCode(codepoint) - if val != nil { - replaceContent(node, m[0], m[1], createEmoji(codepoint, "emoji", val.Description)) + codepoint := node.Data[m[0]:m[1]] + start = m[1] + val := emoji.FromCode(codepoint) + if val != nil { + replaceContent(node, m[0], m[1], createEmoji(codepoint, "emoji", val.Description)) + node = node.NextSibling.NextSibling + start = 0 + } } } @@ -987,49 +1029,69 @@ func sha1CurrentPatternProcessor(ctx *postProcessCtx, node *html.Node) { if ctx.metas == nil || ctx.metas["user"] == "" || ctx.metas["repo"] == "" || ctx.metas["repoPath"] == "" { return } - m := sha1CurrentPattern.FindStringSubmatchIndex(node.Data) - if m == nil { - return - } - hash := node.Data[m[2]:m[3]] - // The regex does not lie, it matches the hash pattern. - // However, a regex cannot know if a hash actually exists or not. - // We could assume that a SHA1 hash should probably contain alphas AND numerics - // but that is not always the case. - // Although unlikely, deadbeef and 1234567 are valid short forms of SHA1 hash - // as used by git and github for linking and thus we have to do similar. - // Because of this, we check to make sure that a matched hash is actually - // a commit in the repository before making it a link. - if _, err := git.NewCommand("rev-parse", "--verify", hash).RunInDirBytes(ctx.metas["repoPath"]); err != nil { - if !strings.Contains(err.Error(), "fatal: Needed a single revision") { - log.Debug("sha1CurrentPatternProcessor git rev-parse: %v", err) - } - return - } - replaceContent(node, m[2], m[3], - createCodeLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "commit", hash), base.ShortSha(hash), "commit")) + start := 0 + next := node.NextSibling + for node != nil && node != next && start < len(node.Data) { + m := sha1CurrentPattern.FindStringSubmatchIndex(node.Data[start:]) + if m == nil { + return + } + m[2] += start + m[3] += start + + hash := node.Data[m[2]:m[3]] + // The regex does not lie, it matches the hash pattern. + // However, a regex cannot know if a hash actually exists or not. + // We could assume that a SHA1 hash should probably contain alphas AND numerics + // but that is not always the case. + // Although unlikely, deadbeef and 1234567 are valid short forms of SHA1 hash + // as used by git and github for linking and thus we have to do similar. + // Because of this, we check to make sure that a matched hash is actually + // a commit in the repository before making it a link. + if _, err := git.NewCommand("rev-parse", "--verify", hash).RunInDirBytes(ctx.metas["repoPath"]); err != nil { + if !strings.Contains(err.Error(), "fatal: Needed a single revision") { + log.Debug("sha1CurrentPatternProcessor git rev-parse: %v", err) + } + start = m[3] + continue + } + + replaceContent(node, m[2], m[3], + createCodeLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "commit", hash), base.ShortSha(hash), "commit")) + start = 0 + node = node.NextSibling.NextSibling + } } // emailAddressProcessor replaces raw email addresses with a mailto: link. func emailAddressProcessor(ctx *postProcessCtx, node *html.Node) { - m := emailRegex.FindStringSubmatchIndex(node.Data) - if m == nil { - return + next := node.NextSibling + for node != nil && node != next { + m := emailRegex.FindStringSubmatchIndex(node.Data) + if m == nil { + return + } + + mail := node.Data[m[2]:m[3]] + replaceContent(node, m[2], m[3], createLink("mailto:"+mail, mail, "mailto")) + node = node.NextSibling.NextSibling } - mail := node.Data[m[2]:m[3]] - replaceContent(node, m[2], m[3], createLink("mailto:"+mail, mail, "mailto")) } // linkProcessor creates links for any HTTP or HTTPS URL not captured by // markdown. func linkProcessor(ctx *postProcessCtx, node *html.Node) { - m := common.LinkRegex.FindStringIndex(node.Data) - if m == nil { - return + next := node.NextSibling + for node != nil && node != next { + m := common.LinkRegex.FindStringIndex(node.Data) + if m == nil { + return + } + uri := node.Data[m[0]:m[1]] + replaceContent(node, m[0], m[1], createLink(uri, uri, "link")) + node = node.NextSibling.NextSibling } - uri := node.Data[m[0]:m[1]] - replaceContent(node, m[0], m[1], createLink(uri, uri, "link")) } func genDefaultLinkProcessor(defaultLink string) processor { @@ -1053,12 +1115,17 @@ func genDefaultLinkProcessor(defaultLink string) processor { // descriptionLinkProcessor creates links for DescriptionHTML func descriptionLinkProcessor(ctx *postProcessCtx, node *html.Node) { - m := common.LinkRegex.FindStringIndex(node.Data) - if m == nil { - return + next := node.NextSibling + for node != nil && node != next { + m := common.LinkRegex.FindStringIndex(node.Data) + if m == nil { + return + } + + uri := node.Data[m[0]:m[1]] + replaceContent(node, m[0], m[1], createDescriptionLink(uri, uri)) + node = node.NextSibling.NextSibling } - uri := node.Data[m[0]:m[1]] - replaceContent(node, m[0], m[1], createDescriptionLink(uri, uri)) } func createDescriptionLink(href, content string) *html.Node { diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index a78b936f87..f471f8d081 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -124,13 +124,13 @@ func TestRender_links(t *testing.T) { `

http://www.example.com/wpstyle/?p=364

`) test( "https://www.example.com/foo/?bar=baz&inga=42&quux", - `

https://www.example.com/foo/?bar=baz&inga=42&quux

`) + `

https://www.example.com/foo/?bar=baz&inga=42&quux

`) test( "http://142.42.1.1/", `

http://142.42.1.1/

`) test( "https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd", - `

https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd

`) + `

https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd

`) test( "https://en.wikipedia.org/wiki/URL_(disambiguation)", `

https://en.wikipedia.org/wiki/URL_(disambiguation)

`) @@ -148,7 +148,7 @@ func TestRender_links(t *testing.T) { `

ftp://gitea.com/file.txt

`) test( "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download", - `

magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download

`) + `

magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download

`) // Test that should *not* be turned into URL test( @@ -384,6 +384,32 @@ func TestRender_ShortLinks(t *testing.T) { `

[[foobar]]

`) } +func TestRender_RelativeImages(t *testing.T) { + setting.AppURL = AppURL + setting.AppSubURL = AppSubURL + tree := util.URLJoin(AppSubURL, "src", "master") + + test := func(input, expected, expectedWiki string) { + buffer := markdown.RenderString(input, tree, localMetas) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) + buffer = markdown.RenderWiki([]byte(input), setting.AppSubURL, localMetas) + assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) + } + + rawwiki := util.URLJoin(AppSubURL, "wiki", "raw") + mediatree := util.URLJoin(AppSubURL, "media", "master") + + test( + ``, + ``, + ``) + + test( + ``, + ``, + ``) +} + func Test_ParseClusterFuzz(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL @@ -408,3 +434,36 @@ func Test_ParseClusterFuzz(t *testing.T) { assert.NotContains(t, string(val), "` + + // func PostProcess(rawHTML []byte, urlPrefix string, metas map[string]string, isWikiMarkdown bool) ([]byte, error) + res, err := PostProcess([]byte(data), "https://example.com", localMetas, false) + assert.NoError(t, err) + assert.Equal(t, data, string(res)) +} + +func BenchmarkEmojiPostprocess(b *testing.B) { + data := "🥰 " + for len(data) < 1<<16 { + data += data + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := PostProcess( + []byte(data), + "https://example.com", + localMetas, + false) + assert.NoError(b, err) + } +} diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index 9214a75fb3..5cae1529cc 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -46,10 +46,9 @@ func ReplaceSanitizer() { sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input") // Custom URL-Schemes - sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...) - - // Allow keyword markup - sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span") + if len(setting.Markdown.CustomURLSchemes) > 0 { + sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...) + } // Allow classes for anchors sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue`)).OnElements("a") @@ -66,8 +65,8 @@ func ReplaceSanitizer() { // Allow classes for emojis sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`emoji`)).OnElements("img") - // Allow icons, emojis, and chroma syntax on span - sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji))$|^([a-z][a-z0-9]{0,2})$`)).OnElements("span") + // Allow icons, emojis, chroma syntax and keyword markup on span + sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span") // Allow generally safe attributes generalSafeAttrs := []string{"abbr", "accept", "accept-charset", diff --git a/modules/markup/sanitizer_test.go b/modules/markup/sanitizer_test.go index 63b70166d8..9e173015d6 100644 --- a/modules/markup/sanitizer_test.go +++ b/modules/markup/sanitizer_test.go @@ -6,6 +6,8 @@ package markup import ( + "html/template" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -50,3 +52,13 @@ func Test_Sanitizer(t *testing.T) { assert.Equal(t, testCases[i+1], string(SanitizeBytes([]byte(testCases[i])))) } } + +func TestSanitizeNonEscape(t *testing.T) { + descStr := "<script>alert(document.domain)</script>" + + output := template.HTML(Sanitize(string(descStr))) + if strings.Contains(string(output), "