From 65f17bfc31f0f2659978d4d1d5ff825146c53a4d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 1 Apr 2022 16:47:50 +0800 Subject: [PATCH] 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). --- .golangci.yml | 14 ++++- integrations/goget_test.go | 3 +- models/migrations/migrations_test.go | 3 +- modules/cache/cache_redis.go | 8 +-- modules/context/context.go | 6 +- modules/context/csrf.go | 14 +++-- modules/context/repo.go | 10 ++- modules/json/json.go | 2 +- modules/markup/html.go | 11 +++- modules/repository/init.go | 10 ++- modules/setting/setting.go | 3 +- modules/sync/unique_queue.go | 10 ++- modules/templates/vars/vars.go | 93 ++++++++++++++++++++++++++++ modules/templates/vars/vars_test.go | 72 +++++++++++++++++++++ modules/util/copy.go | 20 ------ modules/util/io.go | 2 +- modules/util/legacy.go | 84 +++++++++++++++++++++++++ modules/util/legacy_test.go | 37 +++++++++++ modules/util/path.go | 27 +++++++- modules/web/middleware/binding.go | 6 +- routers/web/goget.go | 32 +++++----- routers/web/repo/issue.go | 11 +++- 22 files changed, 397 insertions(+), 81 deletions(-) create mode 100644 modules/templates/vars/vars.go create mode 100644 modules/templates/vars/vars_test.go delete mode 100644 modules/util/copy.go create mode 100644 modules/util/legacy.go create mode 100644 modules/util/legacy_test.go diff --git a/.golangci.yml b/.golangci.yml index 1794b4594..8e31d0cbc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,6 +18,7 @@ linters: - ineffassign - revive - gofumpt + - depguard enable-all: false disable-all: true fast: false @@ -65,7 +66,15 @@ linters-settings: - name: modifies-value-receiver gofumpt: 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: exclude-rules: @@ -153,5 +162,6 @@ issues: - path: models/user/openid.go linters: - 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." diff --git a/integrations/goget_test.go b/integrations/goget_test.go index 5dc9c5e0a..504d86999 100644 --- a/integrations/goget_test.go +++ b/integrations/goget_test.go @@ -29,8 +29,7 @@ func TestGoGet(t *testing.T) { go get --insecure %[1]s:%[2]s/blah/glah - -`, setting.Domain, setting.HTTPPort, setting.AppURL) +`, setting.Domain, setting.HTTPPort, setting.AppURL) assert.Equal(t, expected, resp.Body.String()) } diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go index f798d5011..a17eba54e 100644 --- a/models/migrations/migrations_test.go +++ b/models/migrations/migrations_test.go @@ -24,7 +24,6 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" - "github.com/unknwon/com" "xorm.io/xorm" "xorm.io/xorm/names" ) @@ -204,7 +203,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En deferFn := PrintCurrentTest(t, ourSkip) 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)) ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { diff --git a/modules/cache/cache_redis.go b/modules/cache/cache_redis.go index 148725ae6..e4b9a70f6 100644 --- a/modules/cache/cache_redis.go +++ b/modules/cache/cache_redis.go @@ -10,10 +10,10 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/nosql" + "code.gitea.io/gitea/modules/util" "gitea.com/go-chi/cache" "github.com/go-redis/redis/v8" - "github.com/unknwon/com" ) // 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 { key = c.prefix + key 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 } } else { - dur, err := time.ParseDuration(com.ToStr(expire) + "s") + dur, err := time.ParseDuration(util.ToStr(expire) + "s") if err != nil { 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 } } diff --git a/modules/context/context.go b/modules/context/context.go index 4905e1cb8..f73b5f19c 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -31,13 +31,13 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/auth" "gitea.com/go-chi/cache" "gitea.com/go-chi/session" chi "github.com/go-chi/chi/v5" - "github.com/unknwon/com" "github.com/unrolled/render" "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) - text, err = com.AESGCMDecrypt(key, text) + text, err = util.AESGCMDecrypt(key, text) 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 func (ctx *Context) CookieEncrypt(secret, value string) string { 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 { panic("error encrypting cookie: " + err.Error()) } diff --git a/modules/context/csrf.go b/modules/context/csrf.go index 99c223c88..1fb992e2a 100644 --- a/modules/context/csrf.go +++ b/modules/context/csrf.go @@ -19,13 +19,14 @@ package context import ( + "encoding/base32" + "fmt" "net/http" "time" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "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. @@ -162,7 +163,12 @@ func prepareOptions(options []CsrfOptions) CsrfOptions { // Defaults. 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 { opt.Header = "X-CSRFToken" @@ -211,7 +217,7 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF { x.ID = "0" uid := ctx.Session.Get(opt.SessionKey) if uid != nil { - x.ID = com.ToStr(uid) + x.ID = util.ToStr(uid) } needsNew := false diff --git a/modules/context/repo.go b/modules/context/repo.go index 5a9e38a1d..a7c9a982c 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -8,6 +8,7 @@ package context import ( "context" "fmt" + "html" "io" "net/http" "net/url" @@ -29,7 +30,6 @@ import ( asymkey_service "code.gitea.io/gitea/services/asymkey" "github.com/editorconfig/editorconfig-core-go/v2" - "github.com/unknwon/com" ) // IssueTemplateDirCandidates issue templates directory @@ -308,11 +308,9 @@ func EarlyResponseForGoGetMeta(ctx *Context) { ctx.PlainText(http.StatusBadRequest, "invalid repository path") return } - ctx.PlainText(http.StatusOK, com.Expand(``, - map[string]string{ - "GoGetImport": ComposeGoGetImport(username, reponame), - "CloneLink": repo_model.ComposeHTTPSCloneURL(username, reponame), - })) + goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), repo_model.ComposeHTTPSCloneURL(username, reponame)) + htmlMeta := fmt.Sprintf(``, html.EscapeString(goImportContent)) + ctx.PlainText(http.StatusOK, htmlMeta) } // RedirectToRepo redirect to a differently-named repository diff --git a/modules/json/json.go b/modules/json/json.go index 3afa86023..4361262a2 100644 --- a/modules/json/json.go +++ b/modules/json/json.go @@ -8,7 +8,7 @@ package json import ( "bytes" "encoding/binary" - "encoding/json" + "encoding/json" //nolint:depguard "io" jsoniter "github.com/json-iterator/go" diff --git a/modules/markup/html.go b/modules/markup/html.go index 6673f52bf..c5d36e701 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -21,9 +21,9 @@ import ( "code.gitea.io/gitea/modules/markup/common" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates/vars" "code.gitea.io/gitea/modules/util" - "github.com/unknwon/com" "golang.org/x/net/html" "golang.org/x/net/html/atom" "mvdan.cc/xurls/v2" @@ -838,7 +838,14 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *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 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 { // 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 diff --git a/modules/repository/init.go b/modules/repository/init.go index d5a67df5d..515992f97 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -22,10 +22,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates/vars" "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" - - "github.com/unknwon/com" ) var ( @@ -250,8 +249,13 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, "CloneURL.HTTPS": cloneLink.HTTPS, "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"), - []byte(com.Expand(string(data), match)), 0o644); err != nil { + []byte(res), 0o644); err != nil { return fmt.Errorf("write README.md: %v", err) } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 17a02bf5a..ed91382de 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -27,7 +27,6 @@ import ( "code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/util" - "github.com/unknwon/com" gossh "golang.org/x/crypto/ssh" ini "gopkg.in/ini.v1" ) @@ -612,7 +611,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { Cfg.NameMapper = ini.SnackCase - homeDir, err := com.HomeDir() + homeDir, err := util.HomeDir() if err != nil { log.Fatal("Failed to get home directory: %v", err) } diff --git a/modules/sync/unique_queue.go b/modules/sync/unique_queue.go index d41726b5a..414cc50f3 100644 --- a/modules/sync/unique_queue.go +++ b/modules/sync/unique_queue.go @@ -5,9 +5,7 @@ package sync -import ( - "github.com/unknwon/com" -) +import "code.gitea.io/gitea/modules/util" // UniqueQueue is a queue which guarantees only one instance of same // 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 // exists in the queue. 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, // the queue is blocked until the function exits. func (q *UniqueQueue) AddFunc(id interface{}, fn func()) { - idStr := com.ToStr(id) + idStr := util.ToStr(id) q.table.lock.Lock() if _, ok := q.table.pool[idStr]; ok { q.table.lock.Unlock() @@ -105,5 +103,5 @@ func (q *UniqueQueue) Add(id interface{}) { // Remove removes instance from the queue. func (q *UniqueQueue) Remove(id interface{}) { - q.table.Stop(com.ToStr(id)) + q.table.Stop(util.ToStr(id)) } diff --git a/modules/templates/vars/vars.go b/modules/templates/vars/vars.go new file mode 100644 index 000000000..a22ea4d77 --- /dev/null +++ b/modules/templates/vars/vars.go @@ -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 +} diff --git a/modules/templates/vars/vars_test.go b/modules/templates/vars/vars_test.go new file mode 100644 index 000000000..1cd7669c0 --- /dev/null +++ b/modules/templates/vars/vars_test.go @@ -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) + } + }) + } +} diff --git a/modules/util/copy.go b/modules/util/copy.go deleted file mode 100644 index 46765849d..000000000 --- a/modules/util/copy.go +++ /dev/null @@ -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) -} diff --git a/modules/util/io.go b/modules/util/io.go index b467c0ac8..0c677c359 100644 --- a/modules/util/io.go +++ b/modules/util/io.go @@ -9,7 +9,7 @@ import ( ) // 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. func ReadAtMost(r io.Reader, buf []byte) (n int, err error) { n, err = io.ReadFull(r, buf) diff --git a/modules/util/legacy.go b/modules/util/legacy.go new file mode 100644 index 000000000..c7da54153 --- /dev/null +++ b/modules/util/legacy.go @@ -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 +} diff --git a/modules/util/legacy_test.go b/modules/util/legacy_test.go new file mode 100644 index 000000000..cfda93d3a --- /dev/null +++ b/modules/util/legacy_test.go @@ -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) +} diff --git a/modules/util/path.go b/modules/util/path.go index f4acf92ba..ed7cc6269 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -154,6 +154,10 @@ func StatDir(rootPath string, includeDir ...bool) ([]string, error) { return statDir(rootPath, "", isIncludeDir, false, false) } +func isOSWindows() bool { + return runtime.GOOS == "windows" +} + // FileURLToPath extracts the path information from a file://... url. func FileURLToPath(u *url.URL) (string, error) { if u.Scheme != "file" { @@ -162,7 +166,7 @@ func FileURLToPath(u *url.URL) (string, error) { path := u.Path - if runtime.GOOS != "windows" { + if !isOSWindows() { return path, nil } @@ -173,3 +177,24 @@ func FileURLToPath(u *url.URL) (string, error) { } 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 +} diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 9b0b1d778..c9dc4a8f5 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -10,10 +10,10 @@ import ( "strings" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/validation" "gitea.com/go-chi/binding" - "github.com/unknwon/com" ) // Form form binding interface @@ -22,7 +22,7 @@ type Form interface { } func init() { - binding.SetNameMapper(com.ToSnakeCase) + binding.SetNameMapper(util.ToSnakeCase) } // AssignForm assign form values back to the template data. @@ -43,7 +43,7 @@ func AssignForm(form interface{}, data map[string]interface{}) { if fieldName == "-" { continue } else if len(fieldName) == 0 { - fieldName = com.ToSnakeCase(field.Name) + fieldName = util.ToSnakeCase(field.Name) } data[fieldName] = val.Field(i).Interface() diff --git a/routers/web/goget.go b/routers/web/goget.go index 4a31fcc2c..a58739fe4 100644 --- a/routers/web/goget.go +++ b/routers/web/goget.go @@ -5,6 +5,8 @@ package web import ( + "fmt" + "html" "net/http" "net/url" "path" @@ -14,8 +16,6 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - - "github.com/unknwon/com" ) func goGet(ctx *context.Context) { @@ -65,23 +65,23 @@ func goGet(ctx *context.Context) { if appURL.Scheme == string(setting.HTTP) { insecure = "--insecure " } - ctx.RespHeader().Set("Content-Type", "text/html") - ctx.Status(http.StatusOK) - _, _ = ctx.Write([]byte(com.Expand(` + + goGetImport := context.ComposeGoGetImport(ownerName, trimmedRepoName) + 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(` - - + + - go get {Insecure}{GoGetImport} + %s - -`, map[string]string{ - "GoGetImport": context.ComposeGoGetImport(ownerName, trimmedRepoName), - "CloneLink": repo_model.ComposeHTTPSCloneURL(ownerName, repoName), - "GoDocDirectory": prefix + "{/dir}", - "GoDocFile": prefix + "{/dir}/{file}#L{line}", - "Insecure": insecure, - }))) +`, html.EscapeString(goImportContent), html.EscapeString(goSourceContent), html.EscapeString(goGetCli)) + + ctx.RespHeader().Set("Content-Type", "text/html") + _, _ = ctx.Write([]byte(res)) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 486a63a9e..a1a7200ba 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -35,6 +35,7 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" 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/util" "code.gitea.io/gitea/modules/web" @@ -43,8 +44,6 @@ import ( "code.gitea.io/gitea/services/forms" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" - - "github.com/unknwon/com" ) const ( @@ -1113,7 +1112,13 @@ func ViewIssue(ctx *context.Context) { if extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == markup.IssueNameStyleNumeric || extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == "" { metas := ctx.Repo.Repository.ComposeMetas() 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 } } else if err != nil && !repo_model.IsErrUnitTypeNotExist(err) {