forked from NYANDEV/forgejo
Refactor legacy unknwon/com
package, improve golangci lint (#19284)
The main purpose is to refactor the legacy `unknwon/com` package. 1. Remove most imports of `unknwon/com`, only `util/legacy.go` imports the legacy `unknwon/com` 2. Use golangci's depguard to process denied packages 3. Fix some incorrect values in golangci.yml, eg, the version should be quoted string `"1.18"` 4. Use correctly escaped content for `go-import` and `go-source` meta tags 5. Refactor `com.Expand` to our stable (and the same fast) `vars.Expand`, our `vars.Expand` can still return partially rendered content even if the template is not good (eg: key mistach).
This commit is contained in:
parent
5b7466053d
commit
65f17bfc31
22 changed files with 397 additions and 81 deletions
|
@ -18,6 +18,7 @@ linters:
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- revive
|
- revive
|
||||||
- gofumpt
|
- gofumpt
|
||||||
|
- depguard
|
||||||
enable-all: false
|
enable-all: false
|
||||||
disable-all: true
|
disable-all: true
|
||||||
fast: false
|
fast: false
|
||||||
|
@ -65,7 +66,15 @@ linters-settings:
|
||||||
- name: modifies-value-receiver
|
- name: modifies-value-receiver
|
||||||
gofumpt:
|
gofumpt:
|
||||||
extra-rules: true
|
extra-rules: true
|
||||||
lang-version: 1.18
|
lang-version: "1.18"
|
||||||
|
depguard:
|
||||||
|
# TODO: use depguard to replace import checks in gitea-vet
|
||||||
|
list-type: denylist
|
||||||
|
# Check the list against standard lib.
|
||||||
|
include-go-root: true
|
||||||
|
packages-with-error-message:
|
||||||
|
- encoding/json: "use gitea's modules/json instead of encoding/json"
|
||||||
|
- github.com/unknwon/com: "use gitea's util and replacements"
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
@ -153,5 +162,6 @@ issues:
|
||||||
- path: models/user/openid.go
|
- path: models/user/openid.go
|
||||||
linters:
|
linters:
|
||||||
- golint
|
- golint
|
||||||
- linters: staticcheck
|
- linters:
|
||||||
|
- staticcheck
|
||||||
text: "strings.Title is deprecated: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead."
|
text: "strings.Title is deprecated: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead."
|
||||||
|
|
|
@ -29,8 +29,7 @@ func TestGoGet(t *testing.T) {
|
||||||
<body>
|
<body>
|
||||||
go get --insecure %[1]s:%[2]s/blah/glah
|
go get --insecure %[1]s:%[2]s/blah/glah
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>`, setting.Domain, setting.HTTPPort, setting.AppURL)
|
||||||
`, setting.Domain, setting.HTTPPort, setting.AppURL)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, resp.Body.String())
|
assert.Equal(t, expected, resp.Body.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/unknwon/com"
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
"xorm.io/xorm/names"
|
"xorm.io/xorm/names"
|
||||||
)
|
)
|
||||||
|
@ -204,7 +203,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
|
||||||
deferFn := PrintCurrentTest(t, ourSkip)
|
deferFn := PrintCurrentTest(t, ourSkip)
|
||||||
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
||||||
|
|
||||||
assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
|
assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
|
||||||
setting.RepoRootPath))
|
setting.RepoRootPath))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
8
modules/cache/cache_redis.go
vendored
8
modules/cache/cache_redis.go
vendored
|
@ -10,10 +10,10 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/nosql"
|
"code.gitea.io/gitea/modules/nosql"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache"
|
"gitea.com/go-chi/cache"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RedisCacher represents a redis cache adapter implementation.
|
// RedisCacher represents a redis cache adapter implementation.
|
||||||
|
@ -29,15 +29,15 @@ type RedisCacher struct {
|
||||||
func (c *RedisCacher) Put(key string, val interface{}, expire int64) error {
|
func (c *RedisCacher) Put(key string, val interface{}, expire int64) error {
|
||||||
key = c.prefix + key
|
key = c.prefix + key
|
||||||
if expire == 0 {
|
if expire == 0 {
|
||||||
if err := c.c.Set(graceful.GetManager().HammerContext(), key, com.ToStr(val), 0).Err(); err != nil {
|
if err := c.c.Set(graceful.GetManager().HammerContext(), key, util.ToStr(val), 0).Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dur, err := time.ParseDuration(com.ToStr(expire) + "s")
|
dur, err := time.ParseDuration(util.ToStr(expire) + "s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = c.c.Set(graceful.GetManager().HammerContext(), key, com.ToStr(val), dur).Err(); err != nil {
|
if err = c.c.Set(graceful.GetManager().HammerContext(), key, util.ToStr(val), dur).Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,13 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache"
|
"gitea.com/go-chi/cache"
|
||||||
"gitea.com/go-chi/session"
|
"gitea.com/go-chi/session"
|
||||||
chi "github.com/go-chi/chi/v5"
|
chi "github.com/go-chi/chi/v5"
|
||||||
"github.com/unknwon/com"
|
|
||||||
"github.com/unrolled/render"
|
"github.com/unrolled/render"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
@ -475,7 +475,7 @@ func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
||||||
text, err = com.AESGCMDecrypt(key, text)
|
text, err = util.AESGCMDecrypt(key, text)
|
||||||
return string(text), err == nil
|
return string(text), err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,7 +489,7 @@ func (ctx *Context) SetSuperSecureCookie(secret, name, value string, expiry int)
|
||||||
// CookieEncrypt encrypts a given value using the provided secret
|
// CookieEncrypt encrypts a given value using the provided secret
|
||||||
func (ctx *Context) CookieEncrypt(secret, value string) string {
|
func (ctx *Context) CookieEncrypt(secret, value string) string {
|
||||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
||||||
text, err := com.AESGCMEncrypt(key, []byte(value))
|
text, err := util.AESGCMEncrypt(key, []byte(value))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("error encrypting cookie: " + err.Error())
|
panic("error encrypting cookie: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,14 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base32"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
|
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
|
||||||
|
@ -162,7 +163,12 @@ func prepareOptions(options []CsrfOptions) CsrfOptions {
|
||||||
|
|
||||||
// Defaults.
|
// Defaults.
|
||||||
if len(opt.Secret) == 0 {
|
if len(opt.Secret) == 0 {
|
||||||
opt.Secret = string(com.RandomCreateBytes(10))
|
randBytes, err := util.CryptoRandomBytes(8)
|
||||||
|
if err != nil {
|
||||||
|
// this panic can be handled by the recover() in http handlers
|
||||||
|
panic(fmt.Errorf("failed to generate random bytes: %w", err))
|
||||||
|
}
|
||||||
|
opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
|
||||||
}
|
}
|
||||||
if len(opt.Header) == 0 {
|
if len(opt.Header) == 0 {
|
||||||
opt.Header = "X-CSRFToken"
|
opt.Header = "X-CSRFToken"
|
||||||
|
@ -211,7 +217,7 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
|
||||||
x.ID = "0"
|
x.ID = "0"
|
||||||
uid := ctx.Session.Get(opt.SessionKey)
|
uid := ctx.Session.Get(opt.SessionKey)
|
||||||
if uid != nil {
|
if uid != nil {
|
||||||
x.ID = com.ToStr(uid)
|
x.ID = util.ToStr(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
needsNew := false
|
needsNew := false
|
||||||
|
|
|
@ -8,6 +8,7 @@ package context
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -29,7 +30,6 @@ import (
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
|
|
||||||
"github.com/editorconfig/editorconfig-core-go/v2"
|
"github.com/editorconfig/editorconfig-core-go/v2"
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IssueTemplateDirCandidates issue templates directory
|
// IssueTemplateDirCandidates issue templates directory
|
||||||
|
@ -308,11 +308,9 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
|
||||||
ctx.PlainText(http.StatusBadRequest, "invalid repository path")
|
ctx.PlainText(http.StatusBadRequest, "invalid repository path")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.PlainText(http.StatusOK, com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
|
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), repo_model.ComposeHTTPSCloneURL(username, reponame))
|
||||||
map[string]string{
|
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
|
||||||
"GoGetImport": ComposeGoGetImport(username, reponame),
|
ctx.PlainText(http.StatusOK, htmlMeta)
|
||||||
"CloneLink": repo_model.ComposeHTTPSCloneURL(username, reponame),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedirectToRepo redirect to a differently-named repository
|
// RedirectToRepo redirect to a differently-named repository
|
||||||
|
|
|
@ -8,7 +8,7 @@ package json
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json" //nolint:depguard
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
|
|
@ -21,9 +21,9 @@ import (
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/templates/vars"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"golang.org/x/net/html/atom"
|
"golang.org/x/net/html/atom"
|
||||||
"mvdan.cc/xurls/v2"
|
"mvdan.cc/xurls/v2"
|
||||||
|
@ -838,7 +838,14 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
||||||
if exttrack && !ref.IsPull {
|
if exttrack && !ref.IsPull {
|
||||||
ctx.Metas["index"] = ref.Issue
|
ctx.Metas["index"] = ref.Issue
|
||||||
link = createLink(com.Expand(ctx.Metas["format"], ctx.Metas), reftext, "ref-issue ref-external-issue")
|
|
||||||
|
res, err := vars.Expand(ctx.Metas["format"], ctx.Metas)
|
||||||
|
if err != nil {
|
||||||
|
// here we could just log the error and continue the rendering
|
||||||
|
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
link = createLink(res, reftext, "ref-issue ref-external-issue")
|
||||||
} else {
|
} else {
|
||||||
// Path determines the type of link that will be rendered. It's unknown at this point whether
|
// 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
|
// the linked item is actually a PR or an issue. Luckily it's of no real consequence because
|
||||||
|
|
|
@ -22,10 +22,9 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/options"
|
"code.gitea.io/gitea/modules/options"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/templates/vars"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -250,8 +249,13 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
|
||||||
"CloneURL.HTTPS": cloneLink.HTTPS,
|
"CloneURL.HTTPS": cloneLink.HTTPS,
|
||||||
"OwnerName": repo.OwnerName,
|
"OwnerName": repo.OwnerName,
|
||||||
}
|
}
|
||||||
|
res, err := vars.Expand(string(data), match)
|
||||||
|
if err != nil {
|
||||||
|
// here we could just log the error and continue the rendering
|
||||||
|
log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err)
|
||||||
|
}
|
||||||
if err = os.WriteFile(filepath.Join(tmpDir, "README.md"),
|
if err = os.WriteFile(filepath.Join(tmpDir, "README.md"),
|
||||||
[]byte(com.Expand(string(data), match)), 0o644); err != nil {
|
[]byte(res), 0o644); err != nil {
|
||||||
return fmt.Errorf("write README.md: %v", err)
|
return fmt.Errorf("write README.md: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/user"
|
"code.gitea.io/gitea/modules/user"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
ini "gopkg.in/ini.v1"
|
ini "gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
@ -612,7 +611,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
|
||||||
|
|
||||||
Cfg.NameMapper = ini.SnackCase
|
Cfg.NameMapper = ini.SnackCase
|
||||||
|
|
||||||
homeDir, err := com.HomeDir()
|
homeDir, err := util.HomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to get home directory: %v", err)
|
log.Fatal("Failed to get home directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
|
|
||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import "code.gitea.io/gitea/modules/util"
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UniqueQueue is a queue which guarantees only one instance of same
|
// UniqueQueue is a queue which guarantees only one instance of same
|
||||||
// identity is in the line. Instances with same identity will be
|
// identity is in the line. Instances with same identity will be
|
||||||
|
@ -73,13 +71,13 @@ func (q *UniqueQueue) Queue() <-chan string {
|
||||||
// Exist returns true if there is an instance with given identity
|
// Exist returns true if there is an instance with given identity
|
||||||
// exists in the queue.
|
// exists in the queue.
|
||||||
func (q *UniqueQueue) Exist(id interface{}) bool {
|
func (q *UniqueQueue) Exist(id interface{}) bool {
|
||||||
return q.table.IsRunning(com.ToStr(id))
|
return q.table.IsRunning(util.ToStr(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFunc adds new instance to the queue with a custom runnable function,
|
// AddFunc adds new instance to the queue with a custom runnable function,
|
||||||
// the queue is blocked until the function exits.
|
// the queue is blocked until the function exits.
|
||||||
func (q *UniqueQueue) AddFunc(id interface{}, fn func()) {
|
func (q *UniqueQueue) AddFunc(id interface{}, fn func()) {
|
||||||
idStr := com.ToStr(id)
|
idStr := util.ToStr(id)
|
||||||
q.table.lock.Lock()
|
q.table.lock.Lock()
|
||||||
if _, ok := q.table.pool[idStr]; ok {
|
if _, ok := q.table.pool[idStr]; ok {
|
||||||
q.table.lock.Unlock()
|
q.table.lock.Unlock()
|
||||||
|
@ -105,5 +103,5 @@ func (q *UniqueQueue) Add(id interface{}) {
|
||||||
|
|
||||||
// Remove removes instance from the queue.
|
// Remove removes instance from the queue.
|
||||||
func (q *UniqueQueue) Remove(id interface{}) {
|
func (q *UniqueQueue) Remove(id interface{}) {
|
||||||
q.table.Stop(com.ToStr(id))
|
q.table.Stop(util.ToStr(id))
|
||||||
}
|
}
|
||||||
|
|
93
modules/templates/vars/vars.go
Normal file
93
modules/templates/vars/vars.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2022 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 vars
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrWrongSyntax represents a wrong syntax with a template
|
||||||
|
type ErrWrongSyntax struct {
|
||||||
|
Template string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrWrongSyntax) Error() string {
|
||||||
|
return fmt.Sprintf("wrong syntax found in %s", err.Template)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrVarMissing represents an error that no matched variable
|
||||||
|
type ErrVarMissing struct {
|
||||||
|
Template string
|
||||||
|
Var string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrVarMissing) Error() string {
|
||||||
|
return fmt.Sprintf("the variable %s is missing for %s", err.Var, err.Template)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand replaces all variables like {var} by `vars` map, it always returns the expanded string regardless of errors
|
||||||
|
// if error occurs, the error part doesn't change and is returned as it is.
|
||||||
|
func Expand(template string, vars map[string]string) (string, error) {
|
||||||
|
// in the future, if necessary, we can introduce some escape-char,
|
||||||
|
// for example: it will use `#' as a reversed char, templates will use `{#{}` to do escape and output char '{'.
|
||||||
|
var buf strings.Builder
|
||||||
|
var err error
|
||||||
|
|
||||||
|
posBegin := 0
|
||||||
|
strLen := len(template)
|
||||||
|
for posBegin < strLen {
|
||||||
|
// find the next `{`
|
||||||
|
pos := strings.IndexByte(template[posBegin:], '{')
|
||||||
|
if pos == -1 {
|
||||||
|
buf.WriteString(template[posBegin:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy texts between vars
|
||||||
|
buf.WriteString(template[posBegin : posBegin+pos])
|
||||||
|
|
||||||
|
// find the var between `{` and `}`/end
|
||||||
|
posBegin += pos
|
||||||
|
posEnd := posBegin + 1
|
||||||
|
for posEnd < strLen {
|
||||||
|
if template[posEnd] == '}' {
|
||||||
|
posEnd++
|
||||||
|
break
|
||||||
|
} // in the future, if we need to support escape chars, we can do: if (isEscapeChar) { posEnd+=2 }
|
||||||
|
posEnd++
|
||||||
|
}
|
||||||
|
|
||||||
|
// the var part, it can be "{", "{}", "{..." or or "{...}"
|
||||||
|
part := template[posBegin:posEnd]
|
||||||
|
posBegin = posEnd
|
||||||
|
if part == "{}" || part[len(part)-1] != '}' {
|
||||||
|
// treat "{}" or "{..." as error
|
||||||
|
err = ErrWrongSyntax{Template: template}
|
||||||
|
buf.WriteString(part)
|
||||||
|
} else {
|
||||||
|
// now we get a valid key "{...}"
|
||||||
|
key := part[1 : len(part)-1]
|
||||||
|
keyFirst, _ := utf8.DecodeRuneInString(key)
|
||||||
|
if unicode.IsSpace(keyFirst) || unicode.IsPunct(keyFirst) || unicode.IsControl(keyFirst) {
|
||||||
|
// the if key doesn't start with a letter, then we do not treat it as a var now
|
||||||
|
buf.WriteString(part)
|
||||||
|
} else {
|
||||||
|
// look up in the map
|
||||||
|
if val, ok := vars[key]; ok {
|
||||||
|
buf.WriteString(val)
|
||||||
|
} else {
|
||||||
|
// write the non-existing var as it is
|
||||||
|
buf.WriteString(part)
|
||||||
|
err = ErrVarMissing{Template: template, Var: key}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), err
|
||||||
|
}
|
72
modules/templates/vars/vars_test.go
Normal file
72
modules/templates/vars/vars_test.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2022 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 vars
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpandVars(t *testing.T) {
|
||||||
|
kases := []struct {
|
||||||
|
tmpl string
|
||||||
|
data map[string]string
|
||||||
|
out string
|
||||||
|
error bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
tmpl: "{a}",
|
||||||
|
data: map[string]string{
|
||||||
|
"a": "1",
|
||||||
|
},
|
||||||
|
out: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tmpl: "expand {a}, {b} and {c}, with non-var { } {#}",
|
||||||
|
data: map[string]string{
|
||||||
|
"a": "1",
|
||||||
|
"b": "2",
|
||||||
|
"c": "3",
|
||||||
|
},
|
||||||
|
out: "expand 1, 2 and 3, with non-var { } {#}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tmpl: "中文内容 {一}, {二} 和 {三} 中文结尾",
|
||||||
|
data: map[string]string{
|
||||||
|
"一": "11",
|
||||||
|
"二": "22",
|
||||||
|
"三": "33",
|
||||||
|
},
|
||||||
|
out: "中文内容 11, 22 和 33 中文结尾",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tmpl: "expand {{a}, {b} and {c}",
|
||||||
|
data: map[string]string{
|
||||||
|
"a": "foo",
|
||||||
|
"b": "bar",
|
||||||
|
},
|
||||||
|
out: "expand {{a}, bar and {c}",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tmpl: "expand } {} and {",
|
||||||
|
out: "expand } {} and {",
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kase := range kases {
|
||||||
|
t.Run(kase.tmpl, func(t *testing.T) {
|
||||||
|
res, err := Expand(kase.tmpl, kase.data)
|
||||||
|
assert.EqualValues(t, kase.out, res)
|
||||||
|
if kase.error {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
// Copyright 2020 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 util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CopyFile copies file from source to target path.
|
|
||||||
func CopyFile(src, dest string) error {
|
|
||||||
return com.Copy(src, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyDir copy files recursively from source to target directory.
|
|
||||||
// It returns error when error occurs in underlying functions.
|
|
||||||
func CopyDir(srcPath, destPath string) error {
|
|
||||||
return com.CopyDir(srcPath, destPath)
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadAtMost reads at most len(buf) bytes from r into buf.
|
// ReadAtMost reads at most len(buf) bytes from r into buf.
|
||||||
// It returns the number of bytes copied. n is only less then len(buf) if r provides fewer bytes.
|
// It returns the number of bytes copied. n is only less than len(buf) if r provides fewer bytes.
|
||||||
// If EOF occurs while reading, err will be nil.
|
// If EOF occurs while reading, err will be nil.
|
||||||
func ReadAtMost(r io.Reader, buf []byte) (n int, err error) {
|
func ReadAtMost(r io.Reader, buf []byte) (n int, err error) {
|
||||||
n, err = io.ReadFull(r, buf)
|
n, err = io.ReadFull(r, buf)
|
||||||
|
|
84
modules/util/legacy.go
Normal file
84
modules/util/legacy.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2022 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/unknwon/com" //nolint:depguard
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyFile copies file from source to target path.
|
||||||
|
func CopyFile(src, dest string) error {
|
||||||
|
return com.Copy(src, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDir copy files recursively from source to target directory.
|
||||||
|
// It returns error when error occurs in underlying functions.
|
||||||
|
func CopyDir(srcPath, destPath string) error {
|
||||||
|
return com.CopyDir(srcPath, destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStr converts any interface to string. should be replaced.
|
||||||
|
func ToStr(value interface{}, args ...int) string {
|
||||||
|
return com.ToStr(value, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSnakeCase converts a string to snake_case. should be replaced.
|
||||||
|
func ToSnakeCase(str string) string {
|
||||||
|
return com.ToSnakeCase(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AESGCMEncrypt (from legacy package): encrypts plaintext with the given key using AES in GCM mode. should be replaced.
|
||||||
|
func AESGCMEncrypt(key, plaintext []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err := rand.Read(nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
|
||||||
|
return append(nonce, ciphertext...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AESGCMDecrypt (from legacy package): decrypts ciphertext with the given key using AES in GCM mode. should be replaced.
|
||||||
|
func AESGCMDecrypt(key, ciphertext []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := gcm.NonceSize()
|
||||||
|
if len(ciphertext)-size <= 0 {
|
||||||
|
return nil, errors.New("ciphertext is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := ciphertext[:size]
|
||||||
|
ciphertext = ciphertext[size:]
|
||||||
|
|
||||||
|
plainText, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plainText, nil
|
||||||
|
}
|
37
modules/util/legacy_test.go
Normal file
37
modules/util/legacy_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2022 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/unknwon/com" //nolint:depguard
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAESGCM(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
key := make([]byte, aes.BlockSize)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
plaintext := []byte("this will be encrypted")
|
||||||
|
|
||||||
|
ciphertext, err := AESGCMEncrypt(key, plaintext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
decrypted, err := AESGCMDecrypt(key, ciphertext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, plaintext, decrypted)
|
||||||
|
|
||||||
|
// at the moment, we make sure the result is the same as the legacy package, this assertion can be removed in next round refactoring
|
||||||
|
legacy, err := com.AESGCMDecrypt(key, ciphertext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, legacy, plaintext)
|
||||||
|
}
|
|
@ -154,6 +154,10 @@ func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
|
||||||
return statDir(rootPath, "", isIncludeDir, false, false)
|
return statDir(rootPath, "", isIncludeDir, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isOSWindows() bool {
|
||||||
|
return runtime.GOOS == "windows"
|
||||||
|
}
|
||||||
|
|
||||||
// FileURLToPath extracts the path information from a file://... url.
|
// FileURLToPath extracts the path information from a file://... url.
|
||||||
func FileURLToPath(u *url.URL) (string, error) {
|
func FileURLToPath(u *url.URL) (string, error) {
|
||||||
if u.Scheme != "file" {
|
if u.Scheme != "file" {
|
||||||
|
@ -162,7 +166,7 @@ func FileURLToPath(u *url.URL) (string, error) {
|
||||||
|
|
||||||
path := u.Path
|
path := u.Path
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if !isOSWindows() {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,3 +177,24 @@ func FileURLToPath(u *url.URL) (string, error) {
|
||||||
}
|
}
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HomeDir returns path of '~'(in Linux) on Windows,
|
||||||
|
// it returns error when the variable does not exist.
|
||||||
|
func HomeDir() (home string, err error) {
|
||||||
|
// TODO: some users run Gitea with mismatched uid and "HOME=xxx" (they set HOME=xxx by environment manually)
|
||||||
|
// so at the moment we can not use `user.Current().HomeDir`
|
||||||
|
if isOSWindows() {
|
||||||
|
home = os.Getenv("USERPROFILE")
|
||||||
|
if home == "" {
|
||||||
|
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
home = os.Getenv("HOME")
|
||||||
|
}
|
||||||
|
|
||||||
|
if home == "" {
|
||||||
|
return "", errors.New("cannot get home directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return home, nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
|
|
||||||
"gitea.com/go-chi/binding"
|
"gitea.com/go-chi/binding"
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Form form binding interface
|
// Form form binding interface
|
||||||
|
@ -22,7 +22,7 @@ type Form interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
binding.SetNameMapper(com.ToSnakeCase)
|
binding.SetNameMapper(util.ToSnakeCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssignForm assign form values back to the template data.
|
// AssignForm assign form values back to the template data.
|
||||||
|
@ -43,7 +43,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
|
||||||
if fieldName == "-" {
|
if fieldName == "-" {
|
||||||
continue
|
continue
|
||||||
} else if len(fieldName) == 0 {
|
} else if len(fieldName) == 0 {
|
||||||
fieldName = com.ToSnakeCase(field.Name)
|
fieldName = util.ToSnakeCase(field.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
data[fieldName] = val.Field(i).Interface()
|
data[fieldName] = val.Field(i).Interface()
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -14,8 +16,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func goGet(ctx *context.Context) {
|
func goGet(ctx *context.Context) {
|
||||||
|
@ -65,23 +65,23 @@ func goGet(ctx *context.Context) {
|
||||||
if appURL.Scheme == string(setting.HTTP) {
|
if appURL.Scheme == string(setting.HTTP) {
|
||||||
insecure = "--insecure "
|
insecure = "--insecure "
|
||||||
}
|
}
|
||||||
ctx.RespHeader().Set("Content-Type", "text/html")
|
|
||||||
ctx.Status(http.StatusOK)
|
goGetImport := context.ComposeGoGetImport(ownerName, trimmedRepoName)
|
||||||
_, _ = ctx.Write([]byte(com.Expand(`<!doctype html>
|
goImportContent := fmt.Sprintf("%s git %s", goGetImport, repo_model.ComposeHTTPSCloneURL(ownerName, repoName) /*CloneLink*/)
|
||||||
|
goSourceContent := fmt.Sprintf("%s _ %s %s", goGetImport, prefix+"{/dir}" /*GoDocDirectory*/, prefix+"{/dir}/{file}#L{line}" /*GoDocFile*/)
|
||||||
|
goGetCli := fmt.Sprintf("go get %s%s", insecure, goGetImport)
|
||||||
|
|
||||||
|
res := fmt.Sprintf(`<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="go-import" content="{GoGetImport} git {CloneLink}">
|
<meta name="go-import" content="%s">
|
||||||
<meta name="go-source" content="{GoGetImport} _ {GoDocDirectory} {GoDocFile}">
|
<meta name="go-source" content="%s">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
go get {Insecure}{GoGetImport}
|
%s
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>`, html.EscapeString(goImportContent), html.EscapeString(goSourceContent), html.EscapeString(goGetCli))
|
||||||
`, map[string]string{
|
|
||||||
"GoGetImport": context.ComposeGoGetImport(ownerName, trimmedRepoName),
|
ctx.RespHeader().Set("Content-Type", "text/html")
|
||||||
"CloneLink": repo_model.ComposeHTTPSCloneURL(ownerName, repoName),
|
_, _ = ctx.Write([]byte(res))
|
||||||
"GoDocDirectory": prefix + "{/dir}",
|
|
||||||
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
|
|
||||||
"Insecure": insecure,
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/templates/vars"
|
||||||
"code.gitea.io/gitea/modules/upload"
|
"code.gitea.io/gitea/modules/upload"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -43,8 +44,6 @@ import (
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1113,7 +1112,13 @@ func ViewIssue(ctx *context.Context) {
|
||||||
if extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == markup.IssueNameStyleNumeric || extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == "" {
|
if extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == markup.IssueNameStyleNumeric || extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == "" {
|
||||||
metas := ctx.Repo.Repository.ComposeMetas()
|
metas := ctx.Repo.Repository.ComposeMetas()
|
||||||
metas["index"] = ctx.Params(":index")
|
metas["index"] = ctx.Params(":index")
|
||||||
ctx.Redirect(com.Expand(extIssueUnit.ExternalTrackerConfig().ExternalTrackerFormat, metas))
|
res, err := vars.Expand(extIssueUnit.ExternalTrackerConfig().ExternalTrackerFormat, metas)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("unable to expand template vars for issue url. issue: %s, err: %v", metas["index"], err)
|
||||||
|
ctx.ServerError("Expand", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Redirect(res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if err != nil && !repo_model.IsErrUnitTypeNotExist(err) {
|
} else if err != nil && !repo_model.IsErrUnitTypeNotExist(err) {
|
||||||
|
|
Loading…
Reference in a new issue