mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-19 06:25:38 +02:00
Merge branch 'main' into fix-24635
Signed-off-by: Excellencedev <ademiluyisuccessandexcellence@gmail.com>
This commit is contained in:
110 files changed
+2702
-1720
No files matched your search
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -91,7 +92,13 @@ loop:
|
||||
}
|
||||
|
||||
for _, userStopwatches := range usersStopwatches {
|
||||
apiSWs, err := convert.ToStopWatches(ctx, userStopwatches.StopWatches)
|
||||
u, err := user_model.GetUserByID(ctx, userStopwatches.UserID)
|
||||
if err != nil {
|
||||
log.Error("Unable to get user %d: %v", userStopwatches.UserID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
apiSWs, err := convert.ToStopWatches(ctx, u, userStopwatches.StopWatches)
|
||||
if err != nil {
|
||||
if !issues_model.IsErrIssueNotExist(err) {
|
||||
log.Error("Unable to APIFormat stopwatches: %v", err)
|
||||
|
||||
@@ -426,9 +426,9 @@ type RunStdError interface {
|
||||
}
|
||||
|
||||
type runStdError struct {
|
||||
err error
|
||||
stderr string
|
||||
errMsg string
|
||||
err error // usually the low-level error like `*exec.ExitError`
|
||||
stderr string // git command's stderr output
|
||||
errMsg string // the cached error message for Error() method
|
||||
}
|
||||
|
||||
func (r *runStdError) Error() string {
|
||||
@@ -448,6 +448,22 @@ func (r *runStdError) Stderr() string {
|
||||
return r.stderr
|
||||
}
|
||||
|
||||
func ErrorAsStderr(err error) (string, bool) {
|
||||
var runErr RunStdError
|
||||
if errors.As(err, &runErr) {
|
||||
return runErr.Stderr(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func StderrHasPrefix(err error, prefix string) bool {
|
||||
stderr, ok := ErrorAsStderr(err)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(stderr, prefix)
|
||||
}
|
||||
|
||||
func IsErrorExitCode(err error, code int) bool {
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/tempdir"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -99,3 +100,14 @@ func TestCommandString(t *testing.T) {
|
||||
cmd = NewCommand("url: https://a:b@c/", "/root/dir-a/dir-b")
|
||||
assert.Equal(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString())
|
||||
}
|
||||
|
||||
func TestRunStdError(t *testing.T) {
|
||||
e := &runStdError{stderr: "some error"}
|
||||
var err RunStdError = e
|
||||
|
||||
var asErr RunStdError
|
||||
require.ErrorAs(t, err, &asErr)
|
||||
require.Equal(t, "some error", asErr.Stderr())
|
||||
|
||||
require.ErrorAs(t, fmt.Errorf("wrapped %w", err), &asErr)
|
||||
}
|
||||
@@ -3,12 +3,18 @@
|
||||
|
||||
package gitcmd
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
// ConcatenateError concatenats an error with stderr string
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ConcatenateError concatenates an error with stderr string
|
||||
// FIXME: use RunStdError instead
|
||||
func ConcatenateError(err error, stderr string) error {
|
||||
if len(stderr) == 0 {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%w - %s", err, stderr)
|
||||
errMsg := fmt.Sprintf("%s - %s", err.Error(), stderr)
|
||||
return util.ErrorWrap(&runStdError{err: err, stderr: stderr, errMsg: errMsg}, "%s", errMsg)
|
||||
}
|
||||
@@ -46,8 +46,8 @@ func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
|
||||
entry.Size = optional.Some(size)
|
||||
}
|
||||
|
||||
entry.EntryMode, err = ParseEntryMode(string(entryMode))
|
||||
if err != nil || entry.EntryMode == EntryModeNoEntry {
|
||||
entry.EntryMode = ParseEntryMode(string(entryMode))
|
||||
if entry.EntryMode == EntryModeNoEntry {
|
||||
return nil, fmt.Errorf("invalid ls-tree output (invalid mode): %q, err: %w", line, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -55,21 +54,38 @@ func (e EntryMode) IsExecutable() bool {
|
||||
return e == EntryModeExec
|
||||
}
|
||||
|
||||
func ParseEntryMode(mode string) (EntryMode, error) {
|
||||
func ParseEntryMode(mode string) EntryMode {
|
||||
switch mode {
|
||||
case "000000":
|
||||
return EntryModeNoEntry, nil
|
||||
return EntryModeNoEntry
|
||||
case "100644":
|
||||
return EntryModeBlob, nil
|
||||
return EntryModeBlob
|
||||
case "100755":
|
||||
return EntryModeExec, nil
|
||||
return EntryModeExec
|
||||
case "120000":
|
||||
return EntryModeSymlink, nil
|
||||
return EntryModeSymlink
|
||||
case "160000":
|
||||
return EntryModeCommit, nil
|
||||
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
|
||||
return EntryModeTree, nil
|
||||
return EntryModeCommit
|
||||
case "040000":
|
||||
return EntryModeTree
|
||||
default:
|
||||
return 0, fmt.Errorf("unparsable entry mode: %s", mode)
|
||||
// git uses 040000 for tree object, but some users may get 040755 from non-standard git implementations
|
||||
m, _ := strconv.ParseInt(mode, 8, 32)
|
||||
modeInt := EntryMode(m)
|
||||
switch modeInt & 0o770000 {
|
||||
case 0o040000:
|
||||
return EntryModeTree
|
||||
case 0o160000:
|
||||
return EntryModeCommit
|
||||
case 0o120000:
|
||||
return EntryModeSymlink
|
||||
case 0o100000:
|
||||
if modeInt&0o777 == 0o755 {
|
||||
return EntryModeExec
|
||||
}
|
||||
return EntryModeBlob
|
||||
default:
|
||||
return EntryModeNoEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,3 +27,30 @@ func TestEntriesCustomSort(t *testing.T) {
|
||||
entries.CustomSort(strings.Compare)
|
||||
assert.Equal(t, expected, entries)
|
||||
}
|
||||
|
||||
func TestParseEntryMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
modeStr string
|
||||
expectMod EntryMode
|
||||
}{
|
||||
{"000000", EntryModeNoEntry},
|
||||
{"000755", EntryModeNoEntry},
|
||||
|
||||
{"100644", EntryModeBlob},
|
||||
{"100755", EntryModeExec},
|
||||
|
||||
{"120000", EntryModeSymlink},
|
||||
{"120755", EntryModeSymlink},
|
||||
{"160000", EntryModeCommit},
|
||||
{"160755", EntryModeCommit},
|
||||
|
||||
{"040000", EntryModeTree},
|
||||
{"040755", EntryModeTree},
|
||||
|
||||
{"777777", EntryModeNoEntry}, // invalid mode
|
||||
}
|
||||
for _, test := range tests {
|
||||
mod := ParseEntryMode(test.modeStr)
|
||||
assert.Equal(t, test.expectMod, mod, "modeStr: %s", test.modeStr)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
@@ -16,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// CreateArchive create archive content to the target path
|
||||
func CreateArchive(ctx context.Context, repo Repository, format string, target io.Writer, usePrefix bool, commitID string) error {
|
||||
func CreateArchive(ctx context.Context, repo Repository, format string, target io.Writer, usePrefix bool, commitID string, paths []string) error {
|
||||
if format == "unknown" {
|
||||
return fmt.Errorf("unknown format: %v", format)
|
||||
}
|
||||
@@ -28,6 +30,13 @@ func CreateArchive(ctx context.Context, repo Repository, format string, target i
|
||||
cmd.AddOptionFormat("--format=%s", format)
|
||||
cmd.AddDynamicArguments(commitID)
|
||||
|
||||
paths = slices.Clone(paths)
|
||||
for i := range paths {
|
||||
// although "git archive" already ensures the paths won't go outside the repo, we still clean them here for safety
|
||||
paths[i] = path.Clean(paths[i])
|
||||
}
|
||||
cmd.AddDynamicArguments(paths...)
|
||||
|
||||
var stderr strings.Builder
|
||||
if err := RunCmd(ctx, repo, cmd.WithStdout(target).WithStderr(&stderr)); err != nil {
|
||||
return gitcmd.ConcatenateError(err, stderr.String())
|
||||
|
||||
@@ -66,6 +66,21 @@ type Link struct {
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
func NewLink(href string) *Link {
|
||||
return &Link{Href: href}
|
||||
}
|
||||
|
||||
func (l *Link) WithHeader(k, v string) *Link {
|
||||
if v == "" {
|
||||
return l
|
||||
}
|
||||
if l.Header == nil {
|
||||
l.Header = make(map[string]string)
|
||||
}
|
||||
l.Header[k] = v
|
||||
return l
|
||||
}
|
||||
|
||||
// ObjectError defines the JSON structure returned to the client in case of an error.
|
||||
type ObjectError struct {
|
||||
Code int `json:"code"`
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -71,3 +74,31 @@ func SetupGiteaRoot() string {
|
||||
_ = os.Setenv("GITEA_ROOT", giteaRoot)
|
||||
return giteaRoot
|
||||
}
|
||||
|
||||
func ReadAllTarGzContent(r io.Reader) (map[string]string, error) {
|
||||
gzr, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := make(map[string]string)
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
for {
|
||||
hd, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content[hd.Name] = string(buf)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
@@ -107,6 +107,17 @@ func detectFileTypeBox(data []byte) (brands []string, found bool) {
|
||||
return brands, true
|
||||
}
|
||||
|
||||
func isEmbeddedOpenType(data []byte) bool {
|
||||
// https://www.w3.org/submissions/EOT
|
||||
if len(data) < 80 {
|
||||
return false
|
||||
}
|
||||
version := binary.LittleEndian.Uint32(data[8:]) // Actually this standard is abandoned (for IE6-IE11 only), there are only 3 versions defined
|
||||
magic := binary.LittleEndian.Uint16(data[34:36]) // MagicNumber: 0x504C ("LP")
|
||||
reserved := data[64:80] // Reserved 1-4 (each: unsigned long)
|
||||
return (version == 0x00010000 || version == 0x00020001 || version == 0x00020002) && magic == 0x504C && bytes.Count(reserved, []byte{0}) == len(reserved)
|
||||
}
|
||||
|
||||
// DetectContentType extends http.DetectContentType with more content types. Defaults to text/plain if input is empty.
|
||||
func DetectContentType(data []byte) SniffedType {
|
||||
if len(data) == 0 {
|
||||
@@ -119,6 +130,18 @@ func DetectContentType(data []byte) SniffedType {
|
||||
data = data[:SniffContentSize]
|
||||
}
|
||||
|
||||
const typeMsFontObject = "application/vnd.ms-fontobject"
|
||||
if ct == typeMsFontObject {
|
||||
// Stupid Golang blindly detects any content with 34th-35th bytes being "LP" as font.
|
||||
// If it is not really for ".eot" content, we try to detect it again by hiding the "LP", see the test for more details.
|
||||
if isEmbeddedOpenType(data) {
|
||||
return SniffedType{typeMsFontObject}
|
||||
}
|
||||
data = slices.Clone(data)
|
||||
data[34] = 'l'
|
||||
ct = http.DetectContentType(data)
|
||||
}
|
||||
|
||||
vars := globalVars()
|
||||
// SVG is unsupported by http.DetectContentType, https://github.com/golang/go/issues/15888
|
||||
detectByHTML := strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html")
|
||||
|
||||
@@ -6,6 +6,7 @@ package typesniffer
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -154,3 +155,25 @@ func TestDetectContentTypeAvif(t *testing.T) {
|
||||
st := DetectContentType(buf)
|
||||
assert.Equal(t, MimeTypeImageAvif, st.contentType)
|
||||
}
|
||||
|
||||
func TestDetectContentTypeIncorrectFont(t *testing.T) {
|
||||
s := "Stupid Golang keep detecting 34th LP as font"
|
||||
// They don't want to have any improvement to it: https://github.com/golang/go/issues/77172
|
||||
golangDetected := http.DetectContentType([]byte(s))
|
||||
assert.Equal(t, "application/vnd.ms-fontobject", golangDetected)
|
||||
// We have to make our patch to make it work correctly
|
||||
ourDetected := DetectContentType([]byte(s))
|
||||
assert.Equal(t, "text/plain; charset=utf-8", ourDetected.contentType)
|
||||
|
||||
// For binary content, ensure it still detects as font. The content is from "opensans-regular.eot"
|
||||
b := []byte{
|
||||
0x3d, 0x30, 0x00, 0x00, 0x6b, 0x2f, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x02, 0x0b, 0x06, 0x06, 0x03, 0x05, 0x04, 0x02, 0x02, 0x04, 0x01, 0x00, 0x90, 0x01, 0x00, 0x00,
|
||||
0x04, 0x00, 0x4c, 0x50, 0xef, 0x02, 0x00, 0xe0, 0x5b, 0x20, 0x00, 0x40, 0x28, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x9f, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x63, 0xf4, 0x17, 0x14,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x12, 0x00, 0x4f, 0x00, 0x70, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x53, 0x00,
|
||||
}
|
||||
assert.Equal(t, "application/vnd.ms-fontobject", http.DetectContentType(b))
|
||||
assert.Equal(t, "application/vnd.ms-fontobject", DetectContentType(b).contentType)
|
||||
}
|
||||
Reference in new issue
Block a user