mirror of https://github.com/go-gitea/gitea
commit
62386dc79c
@ -0,0 +1,47 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
// GetSubModules get all the submodules of current revision git tree
|
||||
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) { |
||||
if c.submoduleCache != nil { |
||||
return c.submoduleCache, nil |
||||
} |
||||
|
||||
entry, err := c.GetTreeEntryByPath(".gitmodules") |
||||
if err != nil { |
||||
if _, ok := err.(ErrNotExist); ok { |
||||
return nil, nil |
||||
} |
||||
return nil, err |
||||
} |
||||
|
||||
rd, err := entry.Blob().DataAsync() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer rd.Close() |
||||
|
||||
// at the moment we do not strictly limit the size of the .gitmodules file because some users would have huge .gitmodules files (>1MB)
|
||||
c.submoduleCache, err = configParseSubModules(rd) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return c.submoduleCache, nil |
||||
} |
||||
|
||||
// GetSubModule get the submodule according entry name
|
||||
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) { |
||||
modules, err := c.GetSubModules() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if modules != nil { |
||||
if module, has := modules.Get(entryName); has { |
||||
return module, nil |
||||
} |
||||
} |
||||
return nil, nil |
||||
} |
@ -0,0 +1,187 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"regexp" |
||||
"runtime" |
||||
"strings" |
||||
|
||||
"code.gitea.io/gitea/modules/setting" |
||||
) |
||||
|
||||
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
||||
func syncGitConfig() (err error) { |
||||
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil { |
||||
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err) |
||||
} |
||||
|
||||
// first, write user's git config options to git config file
|
||||
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
|
||||
for k, v := range setting.GitConfig.Options { |
||||
if err = configSet(strings.ToLower(k), v); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
||||
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
||||
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
||||
for configKey, defaultValue := range map[string]string{ |
||||
"user.name": "Gitea", |
||||
"user.email": "gitea@fake.local", |
||||
} { |
||||
if err := configSetNonExist(configKey, defaultValue); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// Set git some configurations - these must be set to these values for gitea to work correctly
|
||||
if err := configSet("core.quotePath", "false"); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.10") { |
||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.18") { |
||||
if err := configSet("core.commitGraph", "true"); err != nil { |
||||
return err |
||||
} |
||||
if err := configSet("gc.writeCommitGraph", "true"); err != nil { |
||||
return err |
||||
} |
||||
if err := configSet("fetch.writeCommitGraph", "true"); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if DefaultFeatures().SupportProcReceive { |
||||
// set support for AGit flow
|
||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
||||
// However, some docker users and samba users find it difficult to configure their systems correctly,
|
||||
// so that Gitea's git repositories are owned by the Gitea user.
|
||||
// (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
|
||||
// See issue: https://github.com/go-gitea/gitea/issues/19455
|
||||
// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
|
||||
// it is now safe to set "safe.directory=*" for internal usage only.
|
||||
// Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
|
||||
if err := configAddNonExist("safe.directory", "*"); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if runtime.GOOS == "windows" { |
||||
if err := configSet("core.longpaths", "true"); err != nil { |
||||
return err |
||||
} |
||||
if setting.Git.DisableCoreProtectNTFS { |
||||
err = configSet("core.protectNTFS", "false") |
||||
} else { |
||||
err = configUnsetAll("core.protectNTFS", "false") |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") { |
||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil { |
||||
return err |
||||
} |
||||
err = configSet("uploadpack.allowAnySHA1InWant", "true") |
||||
} else { |
||||
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil { |
||||
return err |
||||
} |
||||
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true") |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func configSet(key, value string) error { |
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) |
||||
if err != nil && !IsErrorExitCode(err, 1) { |
||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err) |
||||
} |
||||
|
||||
currValue := strings.TrimSpace(stdout) |
||||
if currValue == value { |
||||
return nil |
||||
} |
||||
|
||||
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func configSetNonExist(key, value string) error { |
||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) |
||||
if err == nil { |
||||
// already exist
|
||||
return nil |
||||
} |
||||
if IsErrorExitCode(err, 1) { |
||||
// not exist, set new config
|
||||
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err) |
||||
} |
||||
|
||||
func configAddNonExist(key, value string) error { |
||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) |
||||
if err == nil { |
||||
// already exist
|
||||
return nil |
||||
} |
||||
if IsErrorExitCode(err, 1) { |
||||
// not exist, add new config
|
||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to add git global config %s, err: %w", key, err) |
||||
} |
||||
return nil |
||||
} |
||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err) |
||||
} |
||||
|
||||
func configUnsetAll(key, value string) error { |
||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) |
||||
if err == nil { |
||||
// exist, need to remove
|
||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) |
||||
} |
||||
return nil |
||||
} |
||||
if IsErrorExitCode(err, 1) { |
||||
// not exist
|
||||
return nil |
||||
} |
||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err) |
||||
} |
@ -0,0 +1,75 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io" |
||||
"strings" |
||||
) |
||||
|
||||
// SubModule is a reference on git repository
|
||||
type SubModule struct { |
||||
Path string |
||||
URL string |
||||
Branch string // this field is newly added but not really used
|
||||
} |
||||
|
||||
// configParseSubModules this is not a complete parse for gitmodules file, it only
|
||||
// parses the url and path of submodules. At the moment it only parses well-formed gitmodules files.
|
||||
// In the future, there should be a complete implementation of https://git-scm.com/docs/git-config#_syntax
|
||||
func configParseSubModules(r io.Reader) (*ObjectCache[*SubModule], error) { |
||||
var subModule *SubModule |
||||
subModules := newObjectCache[*SubModule]() |
||||
scanner := bufio.NewScanner(r) |
||||
for scanner.Scan() { |
||||
line := strings.TrimSpace(scanner.Text()) |
||||
|
||||
// Skip empty lines and comments
|
||||
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { |
||||
continue |
||||
} |
||||
|
||||
// Section header [section]
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { |
||||
if subModule != nil { |
||||
subModules.Set(subModule.Path, subModule) |
||||
} |
||||
if strings.HasPrefix(line, "[submodule") { |
||||
subModule = &SubModule{} |
||||
} else { |
||||
subModule = nil |
||||
} |
||||
continue |
||||
} |
||||
|
||||
if subModule == nil { |
||||
continue |
||||
} |
||||
|
||||
parts := strings.SplitN(line, "=", 2) |
||||
if len(parts) != 2 { |
||||
continue |
||||
} |
||||
key := strings.TrimSpace(parts[0]) |
||||
value := strings.TrimSpace(parts[1]) |
||||
switch key { |
||||
case "path": |
||||
subModule.Path = value |
||||
case "url": |
||||
subModule.URL = value |
||||
case "branch": |
||||
subModule.Branch = value |
||||
} |
||||
} |
||||
|
||||
if err := scanner.Err(); err != nil { |
||||
return nil, fmt.Errorf("error reading file: %w", err) |
||||
} |
||||
if subModule != nil { |
||||
subModules.Set(subModule.Path, subModule) |
||||
} |
||||
return subModules, nil |
||||
} |
@ -0,0 +1,49 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestConfigSubmodule(t *testing.T) { |
||||
input := ` |
||||
[core] |
||||
path = test |
||||
|
||||
[submodule "submodule1"] |
||||
path = path1 |
||||
url = https://gitea.io/foo/foo
|
||||
#branch = b1 |
||||
|
||||
[other1] |
||||
branch = master |
||||
|
||||
[submodule "submodule2"] |
||||
path = path2 |
||||
url = https://gitea.io/bar/bar
|
||||
branch = b2 |
||||
|
||||
[other2] |
||||
branch = main |
||||
|
||||
[submodule "submodule3"] |
||||
path = path3 |
||||
url = https://gitea.io/xxx/xxx
|
||||
` |
||||
|
||||
subModules, err := configParseSubModules(strings.NewReader(input)) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, subModules.cache, 3) |
||||
|
||||
sm1, _ := subModules.Get("path1") |
||||
assert.Equal(t, &SubModule{Path: "path1", URL: "https://gitea.io/foo/foo", Branch: ""}, sm1) |
||||
sm2, _ := subModules.Get("path2") |
||||
assert.Equal(t, &SubModule{Path: "path2", URL: "https://gitea.io/bar/bar", Branch: "b2"}, sm2) |
||||
sm3, _ := subModules.Get("path3") |
||||
assert.Equal(t, &SubModule{Path: "path3", URL: "https://gitea.io/xxx/xxx", Branch: ""}, sm3) |
||||
} |
@ -0,0 +1,66 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"os" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/modules/setting" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func gitConfigContains(sub string) bool { |
||||
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { |
||||
return strings.Contains(string(b), sub) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func TestGitConfig(t *testing.T) { |
||||
assert.False(t, gitConfigContains("key-a")) |
||||
|
||||
assert.NoError(t, configSetNonExist("test.key-a", "val-a")) |
||||
assert.True(t, gitConfigContains("key-a = val-a")) |
||||
|
||||
assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed")) |
||||
assert.False(t, gitConfigContains("key-a = val-a-changed")) |
||||
|
||||
assert.NoError(t, configSet("test.key-a", "val-a-changed")) |
||||
assert.True(t, gitConfigContains("key-a = val-a-changed")) |
||||
|
||||
assert.NoError(t, configAddNonExist("test.key-b", "val-b")) |
||||
assert.True(t, gitConfigContains("key-b = val-b")) |
||||
|
||||
assert.NoError(t, configAddNonExist("test.key-b", "val-2b")) |
||||
assert.True(t, gitConfigContains("key-b = val-b")) |
||||
assert.True(t, gitConfigContains("key-b = val-2b")) |
||||
|
||||
assert.NoError(t, configUnsetAll("test.key-b", "val-b")) |
||||
assert.False(t, gitConfigContains("key-b = val-b")) |
||||
assert.True(t, gitConfigContains("key-b = val-2b")) |
||||
|
||||
assert.NoError(t, configUnsetAll("test.key-b", "val-2b")) |
||||
assert.False(t, gitConfigContains("key-b = val-2b")) |
||||
|
||||
assert.NoError(t, configSet("test.key-x", "*")) |
||||
assert.True(t, gitConfigContains("key-x = *")) |
||||
assert.NoError(t, configSetNonExist("test.key-x", "*")) |
||||
assert.NoError(t, configUnsetAll("test.key-x", "*")) |
||||
assert.False(t, gitConfigContains("key-x = *")) |
||||
} |
||||
|
||||
func TestSyncConfig(t *testing.T) { |
||||
oldGitConfig := setting.GitConfig |
||||
defer func() { |
||||
setting.GitConfig = oldGitConfig |
||||
}() |
||||
|
||||
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA" |
||||
assert.NoError(t, syncGitConfig()) |
||||
assert.True(t, gitConfigContains("[sync-test]")) |
||||
assert.True(t, gitConfigContains("cfg-key-a = CfgValA")) |
||||
} |
@ -0,0 +1,14 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git |
||||
|
||||
import ( |
||||
"context" |
||||
"time" |
||||
) |
||||
|
||||
// Fsck verifies the connectivity and validity of the objects in the database
|
||||
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error { |
||||
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) |
||||
} |
@ -1,25 +0,0 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package html |
||||
|
||||
// ParseSizeAndClass get size and class from string with default values
|
||||
// If present, "others" expects the new size first and then the classes to use
|
||||
func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) { |
||||
size := defaultSize |
||||
if len(others) >= 1 { |
||||
if v, ok := others[0].(int); ok && v != 0 { |
||||
size = v |
||||
} |
||||
} |
||||
class := defaultClass |
||||
if len(others) >= 2 { |
||||
if v, ok := others[1].(string); ok && v != "" { |
||||
if class != "" { |
||||
class += " " |
||||
} |
||||
class += v |
||||
} |
||||
} |
||||
return size, class |
||||
} |
@ -0,0 +1,48 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package htmlutil |
||||
|
||||
import ( |
||||
"fmt" |
||||
"html/template" |
||||
"slices" |
||||
) |
||||
|
||||
// ParseSizeAndClass get size and class from string with default values
|
||||
// If present, "others" expects the new size first and then the classes to use
|
||||
func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) { |
||||
size := defaultSize |
||||
if len(others) >= 1 { |
||||
if v, ok := others[0].(int); ok && v != 0 { |
||||
size = v |
||||
} |
||||
} |
||||
class := defaultClass |
||||
if len(others) >= 2 { |
||||
if v, ok := others[1].(string); ok && v != "" { |
||||
if class != "" { |
||||
class += " " |
||||
} |
||||
class += v |
||||
} |
||||
} |
||||
return size, class |
||||
} |
||||
|
||||
func HTMLFormat(s string, rawArgs ...any) template.HTML { |
||||
args := slices.Clone(rawArgs) |
||||
for i, v := range args { |
||||
switch v := v.(type) { |
||||
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML: |
||||
// for most basic types (including template.HTML which is safe), just do nothing and use it
|
||||
case string: |
||||
args[i] = template.HTMLEscapeString(v) |
||||
case fmt.Stringer: |
||||
args[i] = template.HTMLEscapeString(v.String()) |
||||
default: |
||||
args[i] = template.HTMLEscapeString(fmt.Sprint(v)) |
||||
} |
||||
} |
||||
return template.HTML(fmt.Sprintf(s, args...)) |
||||
} |
@ -0,0 +1,15 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package htmlutil |
||||
|
||||
import ( |
||||
"html/template" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestHTMLFormat(t *testing.T) { |
||||
assert.Equal(t, template.HTML("<a>< < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1)) |
||||
} |
@ -1,16 +0,0 @@ |
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package common |
||||
|
||||
import ( |
||||
"mvdan.cc/xurls/v2" |
||||
) |
||||
|
||||
// NOTE: All below regex matching do not perform any extra validation.
|
||||
// Thus a link is produced even if the linked entity does not exist.
|
||||
// While fast, this is also incorrect and lead to false positives.
|
||||
// TODO: fix invalid linking issue
|
||||
|
||||
// LinkRegex is a regexp matching a valid link
|
||||
var LinkRegex, _ = xurls.StrictMatchingScheme("https?://") |
@ -0,0 +1,30 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"bytes" |
||||
"io" |
||||
) |
||||
|
||||
type finalProcessor struct { |
||||
renderInternal *RenderInternal |
||||
|
||||
output io.Writer |
||||
buf bytes.Buffer |
||||
} |
||||
|
||||
func (p *finalProcessor) Write(data []byte) (int, error) { |
||||
p.buf.Write(data) |
||||
return len(data), nil |
||||
} |
||||
|
||||
func (p *finalProcessor) Close() error { |
||||
// TODO: reading the whole markdown isn't a problem at the moment,
|
||||
// because "postProcess" already does so. In the future we could optimize the code to process data on the fly.
|
||||
buf := p.buf.Bytes() |
||||
buf = bytes.ReplaceAll(buf, []byte(` data-attr-class="`+p.renderInternal.secureIDPrefix), []byte(` class="`)) |
||||
_, err := p.output.Write(buf) |
||||
return err |
||||
} |
@ -0,0 +1,61 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"bytes" |
||||
"html/template" |
||||
"io" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestRenderInternal(t *testing.T) { |
||||
cases := []struct { |
||||
input, protected, recovered string |
||||
}{ |
||||
{ |
||||
input: `<div class="test">class="content"</div>`, |
||||
protected: `<div data-attr-class="sec:test">class="content"</div>`, |
||||
recovered: `<div class="test">class="content"</div>`, |
||||
}, |
||||
{ |
||||
input: "<div\nclass=\"test\" data-xxx></div>", |
||||
protected: `<div data-attr-class="sec:test" data-xxx></div>`, |
||||
recovered: `<div class="test" data-xxx></div>`, |
||||
}, |
||||
} |
||||
for _, c := range cases { |
||||
var r RenderInternal |
||||
out := &bytes.Buffer{} |
||||
in := r.init("sec", out) |
||||
protected := r.ProtectSafeAttrs(template.HTML(c.input)) |
||||
assert.EqualValues(t, c.protected, protected) |
||||
_, _ = io.WriteString(in, string(protected)) |
||||
_ = in.Close() |
||||
assert.EqualValues(t, c.recovered, out.String()) |
||||
} |
||||
|
||||
var r1, r2 RenderInternal |
||||
protected := r1.ProtectSafeAttrs(`<div class="test"></div>`) |
||||
assert.EqualValues(t, `<div class="test"></div>`, protected, "non-initialized RenderInternal should not protect any attributes") |
||||
_ = r1.init("sec", nil) |
||||
protected = r1.ProtectSafeAttrs(`<div class="test"></div>`) |
||||
assert.EqualValues(t, `<div data-attr-class="sec:test"></div>`, protected) |
||||
assert.EqualValues(t, "data-attr-class", r1.SafeAttr("class")) |
||||
assert.EqualValues(t, "sec:val", r1.SafeValue("val")) |
||||
recovered, ok := r1.RecoverProtectedValue("sec:val") |
||||
assert.True(t, ok) |
||||
assert.EqualValues(t, "val", recovered) |
||||
recovered, ok = r1.RecoverProtectedValue("other:val") |
||||
assert.False(t, ok) |
||||
assert.Empty(t, recovered) |
||||
|
||||
out2 := &bytes.Buffer{} |
||||
in2 := r2.init("sec-other", out2) |
||||
_, _ = io.WriteString(in2, string(protected)) |
||||
_ = in2.Close() |
||||
assert.EqualValues(t, `<div data-attr-class="sec:test"></div>`, out2.String(), "different secureID should not recover the value") |
||||
} |
@ -0,0 +1,82 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"encoding/base64" |
||||
"html/template" |
||||
"io" |
||||
"regexp" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"code.gitea.io/gitea/modules/htmlutil" |
||||
|
||||
"golang.org/x/net/html" |
||||
) |
||||
|
||||
var reAttrClass = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp { |
||||
// TODO: it isn't a problem at the moment because our HTML contents are always well constructed
|
||||
return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`) |
||||
}) |
||||
|
||||
// RenderInternal also works without initialization
|
||||
// If no initialization (no secureID), it will not protect any attributes and return the original name&value
|
||||
type RenderInternal struct { |
||||
secureID string |
||||
secureIDPrefix string |
||||
} |
||||
|
||||
func (r *RenderInternal) Init(output io.Writer) io.WriteCloser { |
||||
buf := make([]byte, 12) |
||||
_, err := rand.Read(buf) |
||||
if err != nil { |
||||
panic("unable to generate secure id") |
||||
} |
||||
return r.init(base64.URLEncoding.EncodeToString(buf), output) |
||||
} |
||||
|
||||
func (r *RenderInternal) init(secID string, output io.Writer) io.WriteCloser { |
||||
r.secureID = secID |
||||
r.secureIDPrefix = r.secureID + ":" |
||||
return &finalProcessor{renderInternal: r, output: output} |
||||
} |
||||
|
||||
func (r *RenderInternal) RecoverProtectedValue(v string) (string, bool) { |
||||
if !strings.HasPrefix(v, r.secureIDPrefix) { |
||||
return "", false |
||||
} |
||||
return v[len(r.secureIDPrefix):], true |
||||
} |
||||
|
||||
func (r *RenderInternal) SafeAttr(name string) string { |
||||
if r.secureID == "" { |
||||
return name |
||||
} |
||||
return "data-attr-" + name |
||||
} |
||||
|
||||
func (r *RenderInternal) SafeValue(val string) string { |
||||
if r.secureID == "" { |
||||
return val |
||||
} |
||||
return r.secureID + ":" + val |
||||
} |
||||
|
||||
func (r *RenderInternal) NodeSafeAttr(attr, val string) html.Attribute { |
||||
return html.Attribute{Key: r.SafeAttr(attr), Val: r.SafeValue(val)} |
||||
} |
||||
|
||||
func (r *RenderInternal) ProtectSafeAttrs(content template.HTML) template.HTML { |
||||
if r.secureID == "" { |
||||
return content |
||||
} |
||||
return template.HTML(reAttrClass().ReplaceAllString(string(content), `$1 data-attr-class="`+r.secureIDPrefix+`$2"$3`)) |
||||
} |
||||
|
||||
func (r *RenderInternal) FormatWithSafeAttrs(w io.Writer, fmt string, a ...any) error { |
||||
_, err := w.Write([]byte(r.ProtectSafeAttrs(htmlutil.HTMLFormat(fmt, a...)))) |
||||
return err |
||||
} |
@ -0,0 +1,55 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package auth |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models/unittest" |
||||
user_model "code.gitea.io/gitea/models/user" |
||||
"code.gitea.io/gitea/modules/web/middleware" |
||||
"code.gitea.io/gitea/services/actions" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestUserIDFromToken(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
|
||||
t.Run("Actions JWT", func(t *testing.T) { |
||||
const RunningTaskID = 47 |
||||
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2) |
||||
assert.NoError(t, err) |
||||
|
||||
ds := make(middleware.ContextData) |
||||
|
||||
o := OAuth2{} |
||||
uid := o.userIDFromToken(context.Background(), token, ds) |
||||
assert.Equal(t, int64(user_model.ActionsUserID), uid) |
||||
assert.Equal(t, ds["IsActionsToken"], true) |
||||
assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID)) |
||||
}) |
||||
} |
||||
|
||||
func TestCheckTaskIsRunning(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
|
||||
cases := map[string]struct { |
||||
TaskID int64 |
||||
Expected bool |
||||
}{ |
||||
"Running": {TaskID: 47, Expected: true}, |
||||
"Missing": {TaskID: 1, Expected: false}, |
||||
"Cancelled": {TaskID: 46, Expected: false}, |
||||
} |
||||
|
||||
for name := range cases { |
||||
c := cases[name] |
||||
t.Run(name, func(t *testing.T) { |
||||
actual := CheckTaskIsRunning(context.Background(), c.TaskID) |
||||
assert.Equal(t, c.Expected, actual) |
||||
}) |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue