aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorAnthony Wang2023-02-11 23:52:36 +0000
committerAnthony Wang2023-02-11 23:52:36 +0000
commite61e9fba59d6e6f0eb86869e07d9d52867384132 (patch)
treec6072319ab1b1429eb4132c9797ab4c6d6ffd760 /modules
parent1a54d5e8970f2ff6ffe3aeaa19b3917f5e7dc9fd (diff)
parent1cb8d14bf71e0b8637c9eaa10808b4fd05139f45 (diff)
Merge remote-tracking branch 'origin/main' into forgejo-federation
Diffstat (limited to 'modules')
-rw-r--r--modules/auth/common.go22
-rw-r--r--modules/charset/escape_stream.go24
-rw-r--r--modules/context/api.go30
-rw-r--r--modules/context/captcha.go11
-rw-r--r--modules/context/context.go32
-rw-r--r--modules/context/org.go2
-rw-r--r--modules/git/blame.go31
-rw-r--r--modules/git/blame_test.go2
-rw-r--r--modules/git/repo.go6
-rw-r--r--modules/git/repo_attribute.go3
-rwxr-xr-xmodules/metrics/collector.go24
-rw-r--r--modules/migration/comment.go3
-rw-r--r--modules/migration/issue.go13
-rw-r--r--modules/migration/review.go12
-rw-r--r--modules/packages/cargo/parser.go169
-rw-r--r--modules/packages/cargo/parser_test.go86
-rw-r--r--modules/packages/chef/metadata.go134
-rw-r--r--modules/packages/chef/metadata_test.go92
-rw-r--r--modules/packages/container/metadata.go7
-rw-r--r--modules/packages/container/metadata_test.go6
-rw-r--r--modules/packages/container/oci/digest.go26
-rw-r--r--modules/packages/container/oci/mediatype.go35
-rw-r--r--modules/packages/container/oci/oci.go190
-rw-r--r--modules/packages/container/oci/reference.go16
-rw-r--r--modules/proxy/proxy.go14
-rw-r--r--modules/repository/create.go1
-rw-r--r--modules/repository/create_test.go4
-rw-r--r--modules/repository/repo.go2
-rw-r--r--modules/setting/packages.go4
-rw-r--r--modules/setting/service.go4
-rw-r--r--modules/setting/setting.go1
-rw-r--r--modules/structs/repo.go1
-rw-r--r--modules/structs/visible_type.go (renamed from modules/structs/org_type.go)6
-rw-r--r--modules/templates/helper.go4
-rw-r--r--modules/turnstile/turnstile.go92
-rw-r--r--modules/util/keypair.go45
-rw-r--r--modules/util/keypair_test.go61
-rw-r--r--modules/validation/binding.go24
-rw-r--r--modules/web/middleware/binding.go2
39 files changed, 850 insertions, 391 deletions
diff --git a/modules/auth/common.go b/modules/auth/common.go
new file mode 100644
index 000000000..77361f656
--- /dev/null
+++ b/modules/auth/common.go
@@ -0,0 +1,22 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package auth
+
+import (
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+)
+
+func UnmarshalGroupTeamMapping(raw string) (map[string]map[string][]string, error) {
+ groupTeamMapping := make(map[string]map[string][]string)
+ if raw == "" {
+ return groupTeamMapping, nil
+ }
+ err := json.Unmarshal([]byte(raw), &groupTeamMapping)
+ if err != nil {
+ log.Error("Failed to unmarshal group team mapping: %v", err)
+ return nil, err
+ }
+ return groupTeamMapping, nil
+}
diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go
index 823b63513..1b956bf4a 100644
--- a/modules/charset/escape_stream.go
+++ b/modules/charset/escape_stream.go
@@ -6,7 +6,6 @@ package charset
import (
"fmt"
"regexp"
- "sort"
"strings"
"unicode"
"unicode/utf8"
@@ -20,12 +19,16 @@ import (
var defaultWordRegexp = regexp.MustCompile(`(-?\d*\.\d\w*)|([^\` + "`" + `\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s\x00-\x1f]+)`)
func NewEscapeStreamer(locale translation.Locale, next HTMLStreamer, allowed ...rune) HTMLStreamer {
+ allowedM := make(map[rune]bool, len(allowed))
+ for _, v := range allowed {
+ allowedM[v] = true
+ }
return &escapeStreamer{
escaped: &EscapeStatus{},
PassthroughHTMLStreamer: *NewPassthroughStreamer(next),
locale: locale,
ambiguousTables: AmbiguousTablesForLocale(locale),
- allowed: allowed,
+ allowed: allowedM,
}
}
@@ -34,7 +37,7 @@ type escapeStreamer struct {
escaped *EscapeStatus
locale translation.Locale
ambiguousTables []*AmbiguousTable
- allowed []rune
+ allowed map[rune]bool
}
func (e *escapeStreamer) EscapeStatus() *EscapeStatus {
@@ -256,7 +259,7 @@ func (e *escapeStreamer) runeTypes(runes ...rune) (types []runeType, confusables
runeCounts.numBrokenRunes++
case r == ' ' || r == '\t' || r == '\n':
runeCounts.numBasicRunes++
- case e.isAllowed(r):
+ case e.allowed[r]:
if r > 0x7e || r < 0x20 {
types[i] = nonBasicASCIIRuneType
runeCounts.numNonConfusingNonBasicRunes++
@@ -282,16 +285,3 @@ func (e *escapeStreamer) runeTypes(runes ...rune) (types []runeType, confusables
}
return types, confusables, runeCounts
}
-
-func (e *escapeStreamer) isAllowed(r rune) bool {
- if len(e.allowed) == 0 {
- return false
- }
- if len(e.allowed) == 1 {
- return e.allowed[0] == r
- }
-
- return sort.Search(len(e.allowed), func(i int) bool {
- return e.allowed[i] >= r
- }) >= 0
-}
diff --git a/modules/context/api.go b/modules/context/api.go
index 3f52c54d4..3f938948a 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -19,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
- auth_service "code.gitea.io/gitea/services/auth"
)
// APIContext is a specific context for API service
@@ -215,35 +214,6 @@ func (ctx *APIContext) CheckForOTP() {
}
}
-// APIAuth converts auth_service.Auth as a middleware
-func APIAuth(authMethod auth_service.Method) func(*APIContext) {
- return func(ctx *APIContext) {
- // Get user from session if logged in.
- var err error
- ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
- if err != nil {
- ctx.Error(http.StatusUnauthorized, "APIAuth", err)
- return
- }
-
- if ctx.Doer != nil {
- if ctx.Locale.Language() != ctx.Doer.Language {
- ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
- }
- ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth_service.BasicMethodName
- ctx.IsSigned = true
- ctx.Data["IsSigned"] = ctx.IsSigned
- ctx.Data["SignedUser"] = ctx.Doer
- ctx.Data["SignedUserID"] = ctx.Doer.ID
- ctx.Data["SignedUserName"] = ctx.Doer.Name
- ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
- } else {
- ctx.Data["SignedUserID"] = int64(0)
- ctx.Data["SignedUserName"] = ""
- }
- }
-}
-
// APIContexter returns apicontext as middleware
func APIContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
diff --git a/modules/context/captcha.go b/modules/context/captcha.go
index 735613504..07232e939 100644
--- a/modules/context/captcha.go
+++ b/modules/context/captcha.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/turnstile"
"gitea.com/go-chi/captcha"
)
@@ -47,12 +48,14 @@ func SetCaptchaData(ctx *Context) {
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
+ ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
}
const (
- gRecaptchaResponseField = "g-recaptcha-response"
- hCaptchaResponseField = "h-captcha-response"
- mCaptchaResponseField = "m-captcha-response"
+ gRecaptchaResponseField = "g-recaptcha-response"
+ hCaptchaResponseField = "h-captcha-response"
+ mCaptchaResponseField = "m-captcha-response"
+ cfTurnstileResponseField = "cf-turnstile-response"
)
// VerifyCaptcha verifies Captcha data
@@ -73,6 +76,8 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) {
valid, err = hcaptcha.Verify(ctx, ctx.Req.Form.Get(hCaptchaResponseField))
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, ctx.Req.Form.Get(mCaptchaResponseField))
+ case setting.CfTurnstile:
+ valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField))
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
diff --git a/modules/context/context.go b/modules/context/context.go
index 84f40ce06..a2088217f 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -36,7 +36,6 @@ import (
"code.gitea.io/gitea/modules/typesniffer"
"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"
@@ -659,37 +658,6 @@ func getCsrfOpts() CsrfOptions {
}
}
-// Auth converts auth.Auth as a middleware
-func Auth(authMethod auth.Method) func(*Context) {
- return func(ctx *Context) {
- var err error
- ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
- if err != nil {
- log.Error("Failed to verify user %v: %v", ctx.Req.RemoteAddr, err)
- ctx.Error(http.StatusUnauthorized, "Verify")
- return
- }
- if ctx.Doer != nil {
- if ctx.Locale.Language() != ctx.Doer.Language {
- ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
- }
- ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth.BasicMethodName
- ctx.IsSigned = true
- ctx.Data["IsSigned"] = ctx.IsSigned
- ctx.Data["SignedUser"] = ctx.Doer
- ctx.Data["SignedUserID"] = ctx.Doer.ID
- ctx.Data["SignedUserName"] = ctx.Doer.Name
- ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
- } else {
- ctx.Data["SignedUserID"] = int64(0)
- ctx.Data["SignedUserName"] = ""
-
- // ensure the session uid is deleted
- _ = ctx.Session.Delete("uid")
- }
- }
-}
-
// Contexter initializes a classic context for a request.
func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
_, rnd := templates.HTMLRenderer(ctx)
diff --git a/modules/context/org.go b/modules/context/org.go
index ff3a5ae7e..0add7f2c0 100644
--- a/modules/context/org.go
+++ b/modules/context/org.go
@@ -80,7 +80,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
orgName := ctx.Params(":org")
var err error
- ctx.Org.Organization, err = organization.GetOrgByName(orgName)
+ ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil {
if organization.IsErrOrgNotExist(err) {
redirectUserID, err := user_model.LookupUserRedirect(orgName)
diff --git a/modules/git/blame.go b/modules/git/blame.go
index 3b6e4c95d..ec88accb1 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -20,11 +20,12 @@ type BlamePart struct {
// BlameReader returns part of file blame one by one
type BlameReader struct {
- cmd *Command
- output io.WriteCloser
- reader io.ReadCloser
- done chan error
- lastSha *string
+ cmd *Command
+ output io.WriteCloser
+ reader io.ReadCloser
+ bufferedReader *bufio.Reader
+ done chan error
+ lastSha *string
}
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
@@ -33,8 +34,6 @@ var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart
- reader := bufio.NewReader(r.reader)
-
if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0)}
}
@@ -44,7 +43,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
var err error
for err != io.EOF {
- line, isPrefix, err = reader.ReadLine()
+ line, isPrefix, err = r.bufferedReader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
@@ -66,7 +65,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
r.lastSha = &sha1
// need to munch to end of line...
for isPrefix {
- _, isPrefix, err = reader.ReadLine()
+ _, isPrefix, err = r.bufferedReader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
@@ -81,7 +80,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// need to munch to end of line...
for isPrefix {
- _, isPrefix, err = reader.ReadLine()
+ _, isPrefix, err = r.bufferedReader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
@@ -96,6 +95,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
err := <-r.done
+ r.bufferedReader = nil
_ = r.reader.Close()
_ = r.output.Close()
return err
@@ -126,10 +126,13 @@ func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*B
done <- err
}(cmd, repoPath, stdout, done)
+ bufferedReader := bufio.NewReader(reader)
+
return &BlameReader{
- cmd: cmd,
- output: stdout,
- reader: reader,
- done: done,
+ cmd: cmd,
+ output: stdout,
+ reader: reader,
+ bufferedReader: bufferedReader,
+ done: done,
}, nil
}
diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go
index a2c8fe8e7..1c0cd5c4a 100644
--- a/modules/git/blame_test.go
+++ b/modules/git/blame_test.go
@@ -28,7 +28,7 @@ func TestReadingBlameOutput(t *testing.T) {
},
{
"f32b0a9dfd09a60f616f29158f772cedd89942d2",
- []string{},
+ []string{"", "Do not make any changes to this repo it is used for unit testing"},
},
}
diff --git a/modules/git/repo.go b/modules/git/repo.go
index e77a3a6ad..233f7f20c 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -163,10 +163,8 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op
envs := os.Environ()
u, err := url.Parse(from)
- if err == nil && (strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https")) {
- if proxy.Match(u.Host) {
- envs = append(envs, fmt.Sprintf("https_proxy=%s", proxy.GetProxyURL()))
- }
+ if err == nil {
+ envs = proxy.EnvWithProxy(u)
}
stderr := new(bytes.Buffer)
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index e7d5fb680..2b34f117f 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -135,8 +135,7 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
c.env = append(c.env, "GIT_FLUSH=1")
- // The empty "--" comes from #16773 , and it seems unnecessary because nothing else would be added later.
- c.cmd.AddDynamicArguments(c.Attributes...).AddArguments("--")
+ c.cmd.AddDynamicArguments(c.Attributes...)
var err error
diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go
index 17f8dd133..94699c161 100755
--- a/modules/metrics/collector.go
+++ b/modules/metrics/collector.go
@@ -4,7 +4,10 @@
package metrics
import (
+ "runtime"
+
activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/modules/setting"
"github.com/prometheus/client_golang/prometheus"
)
@@ -17,6 +20,7 @@ type Collector struct {
Accesses *prometheus.Desc
Actions *prometheus.Desc
Attachments *prometheus.Desc
+ BuildInfo *prometheus.Desc
Comments *prometheus.Desc
Follows *prometheus.Desc
HookTasks *prometheus.Desc
@@ -62,6 +66,16 @@ func NewCollector() Collector {
"Number of Attachments",
nil, nil,
),
+ BuildInfo: prometheus.NewDesc(
+ namespace+"build_info",
+ "Build information",
+ []string{
+ "goarch",
+ "goos",
+ "goversion",
+ "version",
+ }, nil,
+ ),
Comments: prometheus.NewDesc(
namespace+"comments",
"Number of Comments",
@@ -195,6 +209,7 @@ func (c Collector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.Accesses
ch <- c.Actions
ch <- c.Attachments
+ ch <- c.BuildInfo
ch <- c.Comments
ch <- c.Follows
ch <- c.HookTasks
@@ -242,6 +257,15 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
float64(stats.Counter.Attachment),
)
ch <- prometheus.MustNewConstMetric(
+ c.BuildInfo,
+ prometheus.GaugeValue,
+ 1,
+ runtime.GOARCH,
+ runtime.GOOS,
+ runtime.Version(),
+ setting.AppVer,
+ )
+ ch <- prometheus.MustNewConstMetric(
c.Comments,
prometheus.GaugeValue,
float64(stats.Counter.Comment),
diff --git a/modules/migration/comment.go b/modules/migration/comment.go
index f994e972e..92ce30e30 100644
--- a/modules/migration/comment.go
+++ b/modules/migration/comment.go
@@ -8,8 +8,7 @@ import "time"
// Commentable can be commented upon
type Commentable interface {
- GetLocalIndex() int64
- GetForeignIndex() int64
+ Reviewable
GetContext() DownloaderContext
}
diff --git a/modules/migration/issue.go b/modules/migration/issue.go
index 7cb9f84b0..3d1d1b4e0 100644
--- a/modules/migration/issue.go
+++ b/modules/migration/issue.go
@@ -34,6 +34,15 @@ func (issue *Issue) GetExternalName() string { return issue.PosterName }
// GetExternalID ExternalUserMigrated interface
func (issue *Issue) GetExternalID() int64 { return issue.PosterID }
-func (issue *Issue) GetLocalIndex() int64 { return issue.Number }
-func (issue *Issue) GetForeignIndex() int64 { return issue.ForeignIndex }
+func (issue *Issue) GetLocalIndex() int64 { return issue.Number }
+
+func (issue *Issue) GetForeignIndex() int64 {
+ // see the comment of Reviewable.GetForeignIndex
+ // if there is no ForeignIndex, then use LocalIndex
+ if issue.ForeignIndex == 0 {
+ return issue.Number
+ }
+ return issue.ForeignIndex
+}
+
func (issue *Issue) GetContext() DownloaderContext { return issue.Context }
diff --git a/modules/migration/review.go b/modules/migration/review.go
index a420c130c..79e821b2e 100644
--- a/modules/migration/review.go
+++ b/modules/migration/review.go
@@ -8,6 +8,16 @@ import "time"
// Reviewable can be reviewed
type Reviewable interface {
GetLocalIndex() int64
+
+ // GetForeignIndex presents the foreign index, which could be misused:
+ // For example, if there are 2 Gitea sites: site-A exports a dataset, then site-B imports it:
+ // * if site-A exports files by using its LocalIndex
+ // * from site-A's view, LocalIndex is site-A's IssueIndex while ForeignIndex is site-B's IssueIndex
+ // * but from site-B's view, LocalIndex is site-B's IssueIndex while ForeignIndex is site-A's IssueIndex
+ //
+ // So the exporting/importing must be paired, but the meaning of them looks confusing then:
+ // * either site-A and site-B both use LocalIndex during dumping/restoring
+ // * or site-A and site-B both use ForeignIndex
GetForeignIndex() int64
}
@@ -37,7 +47,7 @@ type Review struct {
// GetExternalName ExternalUserMigrated interface
func (r *Review) GetExternalName() string { return r.ReviewerName }
-// ExternalID ExternalUserMigrated interface
+// GetExternalID ExternalUserMigrated interface
func (r *Review) GetExternalID() int64 { return r.ReviewerID }
// ReviewComment represents a review comment
diff --git a/modules/packages/cargo/parser.go b/modules/packages/cargo/parser.go
new file mode 100644
index 000000000..36cd44df8
--- /dev/null
+++ b/modules/packages/cargo/parser.go
@@ -0,0 +1,169 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cargo
+
+import (
+ "encoding/binary"
+ "errors"
+ "io"
+ "regexp"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/validation"
+
+ "github.com/hashicorp/go-version"
+)
+
+const PropertyYanked = "cargo.yanked"
+
+var (
+ ErrInvalidName = errors.New("package name is invalid")
+ ErrInvalidVersion = errors.New("package version is invalid")
+)
+
+// Package represents a Cargo package
+type Package struct {
+ Name string
+ Version string
+ Metadata *Metadata
+ Content io.Reader
+ ContentSize int64
+}
+
+// Metadata represents the metadata of a Cargo package
+type Metadata struct {
+ Dependencies []*Dependency `json:"dependencies,omitempty"`
+ Features map[string][]string `json:"features,omitempty"`
+ Authors []string `json:"authors,omitempty"`
+ Description string `json:"description,omitempty"`
+ DocumentationURL string `json:"documentation_url,omitempty"`
+ ProjectURL string `json:"project_url,omitempty"`
+ Readme string `json:"readme,omitempty"`
+ Keywords []string `json:"keywords,omitempty"`
+ Categories []string `json:"categories,omitempty"`
+ License string `json:"license,omitempty"`
+ RepositoryURL string `json:"repository_url,omitempty"`
+ Links string `json:"links,omitempty"`
+}
+
+type Dependency struct {
+ Name string `json:"name"`
+ Req string `json:"req"`
+ Features []string `json:"features"`
+ Optional bool `json:"optional"`
+ DefaultFeatures bool `json:"default_features"`
+ Target *string `json:"target"`
+ Kind string `json:"kind"`
+ Registry *string `json:"registry"`
+ Package *string `json:"package"`
+}
+
+var nameMatch = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9-_]{0,63}\z`)
+
+// ParsePackage reads the metadata and content of a package
+func ParsePackage(r io.Reader) (*Package, error) {
+ var size uint32
+ if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
+ return nil, err
+ }
+
+ p, err := parsePackage(io.LimitReader(r, int64(size)))
+ if err != nil {
+ return nil, err
+ }
+
+ if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
+ return nil, err
+ }
+
+ p.Content = io.LimitReader(r, int64(size))
+ p.ContentSize = int64(size)
+
+ return p, nil
+}
+
+func parsePackage(r io.Reader) (*Package, error) {
+ var meta struct {
+ Name string `json:"name"`
+ Vers string `json:"vers"`
+ Deps []struct {
+ Name string `json:"name"`
+ VersionReq string `json:"version_req"`
+ Features []string `json:"features"`
+ Optional bool `json:"optional"`
+ DefaultFeatures bool `json:"default_features"`
+ Target *string `json:"target"`
+ Kind string `json:"kind"`
+ Registry *string `json:"registry"`
+ ExplicitNameInToml string `json:"explicit_name_in_toml"`
+ } `json:"deps"`
+ Features map[string][]string `json:"features"`
+ Authors []string `json:"authors"`
+ Description string `json:"description"`
+ Documentation string `json:"documentation"`
+ Homepage string `json:"homepage"`
+ Readme string `json:"readme"`
+ ReadmeFile string `json:"readme_file"`
+ Keywords []string `json:"keywords"`
+ Categories []string `json:"categories"`
+ License string `json:"license"`
+ LicenseFile string `json:"license_file"`
+ Repository string `json:"repository"`
+ Links string `json:"links"`
+ }
+ if err := json.NewDecoder(r).Decode(&meta); err != nil {
+ return nil, err
+ }
+
+ if !nameMatch.MatchString(meta.Name) {
+ return nil, ErrInvalidName
+ }
+
+ if _, err := version.NewSemver(meta.Vers); err != nil {
+ return nil, ErrInvalidVersion
+ }
+
+ if !validation.IsValidURL(meta.Homepage) {
+ meta.Homepage = ""
+ }
+ if !validation.IsValidURL(meta.Documentation) {
+ meta.Documentation = ""
+ }
+ if !validation.IsValidURL(meta.Repository) {
+ meta.Repository = ""
+ }
+
+ dependencies := make([]*Dependency, 0, len(meta.Deps))
+ for _, dep := range meta.Deps {
+ dependencies = append(dependencies, &Dependency{
+ Name: dep.Name,
+ Req: dep.VersionReq,
+ Features: dep.Features,
+ Optional: dep.Optional,
+ DefaultFeatures: dep.DefaultFeatures,
+ Target: dep.Target,
+ Kind: dep.Kind,
+ Registry: dep.Registry,
+ })
+ }
+
+ return &Package{
+ Name: meta.Name,
+ Version: meta.Vers,
+ Metadata: &Metadata{
+ Dependencies: dependencies,
+ Features: meta.Features,
+ Authors: meta.Authors,
+ Description: meta.Description,
+ DocumentationURL: meta.Documentation,
+ ProjectURL: meta.Homepage,
+ Readme: meta.Readme,
+ Keywords: meta.Keywords,
+ Categories: meta.Categories,
+ License: meta.License,
+ RepositoryURL: meta.Repository,
+ Links: meta.Links,
+ },
+ }, nil
+}
diff --git a/modules/packages/cargo/parser_test.go b/modules/packages/cargo/parser_test.go
new file mode 100644
index 000000000..2230a5b49
--- /dev/null
+++ b/modules/packages/cargo/parser_test.go
@@ -0,0 +1,86 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cargo
+
+import (
+ "bytes"
+ "encoding/binary"
+ "io"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ description = "Package Description"
+ author = "KN4CK3R"
+ homepage = "https://gitea.io/"
+ license = "MIT"
+)
+
+func TestParsePackage(t *testing.T) {
+ createPackage := func(name, version string) io.Reader {
+ metadata := `{
+ "name":"` + name + `",
+ "vers":"` + version + `",
+ "description":"` + description + `",
+ "authors": ["` + author + `"],
+ "deps":[
+ {
+ "name":"dep",
+ "version_req":"1.0"
+ }
+ ],
+ "homepage":"` + homepage + `",
+ "license":"` + license + `"
+}`
+
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.LittleEndian, uint32(len(metadata)))
+ buf.WriteString(metadata)
+ binary.Write(&buf, binary.LittleEndian, uint32(4))
+ buf.WriteString("test")
+ return &buf
+ }
+
+ t.Run("InvalidName", func(t *testing.T) {
+ for _, name := range []string{"", "0test", "-test", "_test", strings.Repeat("a", 65)} {
+ data := createPackage(name, "1.0.0")
+
+ cp, err := ParsePackage(data)
+ assert.Nil(t, cp)
+ assert.ErrorIs(t, err, ErrInvalidName)
+ }
+ })
+
+ t.Run("InvalidVersion", func(t *testing.T) {
+ for _, version := range []string{"", "1.", "-1.0", "1.0.0/1"} {
+ data := createPackage("test", version)
+
+ cp, err := ParsePackage(data)
+ assert.Nil(t, cp)
+ assert.ErrorIs(t, err, ErrInvalidVersion)
+ }
+ })
+
+ t.Run("Valid", func(t *testing.T) {
+ data := createPackage("test", "1.0.0")
+
+ cp, err := ParsePackage(data)
+ assert.NotNil(t, cp)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", cp.Name)
+ assert.Equal(t, "1.0.0", cp.Version)
+ assert.Equal(t, description, cp.Metadata.Description)
+ assert.Equal(t, []string{author}, cp.Metadata.Authors)
+ assert.Len(t, cp.Metadata.Dependencies, 1)
+ assert.Equal(t, "dep", cp.Metadata.Dependencies[0].Name)
+ assert.Equal(t, homepage, cp.Metadata.ProjectURL)
+ assert.Equal(t, license, cp.Metadata.License)
+ content, _ := io.ReadAll(cp.Content)
+ assert.Equal(t, "test", string(content))
+ })
+}
diff --git a/modules/packages/chef/metadata.go b/modules/packages/chef/metadata.go
new file mode 100644
index 000000000..a1c91870c
--- /dev/null
+++ b/modules/packages/chef/metadata.go
@@ -0,0 +1,134 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package chef
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "io"
+ "regexp"
+ "strings"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/validation"
+)
+
+const (
+ KeyBits = 4096
+ SettingPublicPem = "chef.public_pem"
+)
+
+var (
+ ErrMissingMetadataFile = util.NewInvalidArgumentErrorf("metadata.json file is missing")
+ ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
+ ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
+
+ namePattern = regexp.MustCompile(`\A\S+\z`)
+ versionPattern = regexp.MustCompile(`\A\d+\.\d+(?:\.\d+)?\z`)
+)
+
+// Package represents a Chef package
+type Package struct {
+ Name string
+ Version string
+ Metadata *Metadata
+}
+
+// Metadata represents the metadata of a Chef package
+type Metadata struct {
+ Description string `json:"description,omitempty"`
+ LongDescription string `json:"long_description,omitempty"`
+ Author string `json:"author,omitempty"`
+ License string `json:"license,omitempty"`
+ RepositoryURL string `json:"repository_url,omitempty"`
+ Dependencies map[string]string `json:"dependencies,omitempty"`
+}
+
+type chefMetadata struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ LongDescription string `json:"long_description"`
+ Maintainer string `json:"maintainer"`
+ MaintainerEmail string `json:"maintainer_email"`
+ License string `json:"license"`
+ Platforms map[string]string `json:"platforms"`
+ Dependencies map[string]string `json:"dependencies"`
+ Providing map[string]string `json:"providing"`
+ Recipes map[string]string `json:"recipes"`
+ Version string `json:"version"`
+ SourceURL string `json:"source_url"`
+ IssuesURL string `json:"issues_url"`
+ Privacy bool `json:"privacy"`
+ ChefVersions [][]string `json:"chef_versions"`
+ Gems [][]string `json:"gems"`
+ EagerLoadLibraries bool `json:"eager_load_libraries"`
+}
+
+// ParsePackage parses the Chef package file
+func ParsePackage(r io.Reader) (*Package, error) {
+ gzr, err := gzip.NewReader(r)
+ if err != nil {
+ return nil, err
+ }
+ defer gzr.Close()
+
+ tr := tar.NewReader(gzr)
+ for {
+ hd, err := tr.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if hd.Typeflag != tar.TypeReg {
+ continue
+ }
+
+ if strings.Count(hd.Name, "/") != 1 {
+ continue
+ }
+
+ if hd.FileInfo().Name() == "metadata.json" {
+ return ParseChefMetadata(tr)
+ }
+ }
+
+ return nil, ErrMissingMetadataFile
+}
+
+// ParseChefMetadata parses a metadata.json file to retrieve the metadata of a Chef package
+func ParseChefMetadata(r io.Reader) (*Package, error) {
+ var cm chefMetadata
+ if err := json.NewDecoder(r).Decode(&cm); err != nil {
+ return nil, err
+ }
+
+ if !namePattern.MatchString(cm.Name) {
+ return nil, ErrInvalidName
+ }
+
+ if !versionPattern.MatchString(cm.Version) {
+ return nil, ErrInvalidVersion
+ }
+
+ if !validation.IsValidURL(cm.SourceURL) {
+ cm.SourceURL = ""
+ }
+
+ return &Package{
+ Name: cm.Name,
+ Version: cm.Version,
+ Metadata: &Metadata{
+ Description: cm.Description,
+ LongDescription: cm.LongDescription,
+ Author: cm.Maintainer,
+ License: cm.License,
+ RepositoryURL: cm.SourceURL,
+ Dependencies: cm.Dependencies,
+ },
+ }, nil
+}
diff --git a/modules/packages/chef/metadata_test.go b/modules/packages/chef/metadata_test.go
new file mode 100644
index 000000000..6def4162a
--- /dev/null
+++ b/modules/packages/chef/metadata_test.go
@@ -0,0 +1,92 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package chef
+
+import (
+ "archive/tar"
+ "bytes"
+ "compress/gzip"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ packageName = "gitea"
+ packageVersion = "1.0.1"
+ packageAuthor = "KN4CK3R"
+ packageDescription = "Package Description"
+ packageRepositoryURL = "https://gitea.io/gitea/gitea"
+)
+
+func TestParsePackage(t *testing.T) {
+ t.Run("MissingMetadataFile", func(t *testing.T) {
+ var buf bytes.Buffer
+ zw := gzip.NewWriter(&buf)
+ tw := tar.NewWriter(zw)
+ tw.Close()
+ zw.Close()
+
+ p, err := ParsePackage(&buf)
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrMissingMetadataFile)
+ })
+
+ t.Run("Valid", func(t *testing.T) {
+ var buf bytes.Buffer
+ zw := gzip.NewWriter(&buf)
+ tw := tar.NewWriter(zw)
+
+ content := `{"name":"` + packageName + `","version":"` + packageVersion + `"}`
+
+ hdr := &tar.Header{
+ Name: packageName + "/metadata.json",
+ Mode: 0o600,
+ Size: int64(len(content)),
+ }
+ tw.WriteHeader(hdr)
+ tw.Write([]byte(content))
+
+ tw.Close()
+ zw.Close()
+
+ p, err := ParsePackage(&buf)
+ assert.NoError(t, err)
+ assert.NotNil(t, p)
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageVersion, p.Version)
+ assert.NotNil(t, p.Metadata)
+ })
+}
+
+func TestParseChefMetadata(t *testing.T) {
+ t.Run("InvalidName", func(t *testing.T) {
+ for _, name := range []string{" test", "test "} {
+ p, err := ParseChefMetadata(strings.NewReader(`{"name":"` + name + `","version":"1.0.0"}`))
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrInvalidName)
+ }
+ })
+
+ t.Run("InvalidVersion", func(t *testing.T) {
+ for _, version := range []string{"1", "1.2.3.4", "1.0.0 "} {
+ p, err := ParseChefMetadata(strings.NewReader(`{"name":"test","version":"` + version + `"}`))
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrInvalidVersion)
+ }
+ })
+
+ t.Run("Valid", func(t *testing.T) {
+ p, err := ParseChefMetadata(strings.NewReader(`{"name":"` + packageName + `","version":"` + packageVersion + `","description":"` + packageDescription + `","maintainer":"` + packageAuthor + `","source_url":"` + packageRepositoryURL + `"}`))
+ assert.NotNil(t, p)
+ assert.NoError(t, err)
+
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageVersion, p.Version)
+ assert.Equal(t, packageDescription, p.Metadata.Description)
+ assert.Equal(t, packageAuthor, p.Metadata.Author)
+ assert.Equal(t, packageRepositoryURL, p.Metadata.RepositoryURL)
+ })
+}
diff --git a/modules/packages/container/metadata.go b/modules/packages/container/metadata.go
index c3946f38f..6f62ab6a5 100644
--- a/modules/packages/container/metadata.go
+++ b/modules/packages/container/metadata.go
@@ -10,8 +10,9 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/packages/container/helm"
- "code.gitea.io/gitea/modules/packages/container/oci"
"code.gitea.io/gitea/modules/validation"
+
+ oci "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
@@ -65,8 +66,8 @@ type Metadata struct {
}
// ParseImageConfig parses the metadata of an image config
-func ParseImageConfig(mediaType oci.MediaType, r io.Reader) (*Metadata, error) {
- if strings.EqualFold(string(mediaType), helm.ConfigMediaType) {
+func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
+ if strings.EqualFold(mt, helm.ConfigMediaType) {
return parseHelmConfig(r)
}
diff --git a/modules/packages/container/metadata_test.go b/modules/packages/container/metadata_test.go
index f9ee478d6..5d8d3abfa 100644
--- a/modules/packages/container/metadata_test.go
+++ b/modules/packages/container/metadata_test.go
@@ -8,8 +8,8 @@ import (
"testing"
"code.gitea.io/gitea/modules/packages/container/helm"
- "code.gitea.io/gitea/modules/packages/container/oci"
+ oci "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)
@@ -23,7 +23,7 @@ func TestParseImageConfig(t *testing.T) {
configOCI := `{"config": {"labels": {"` + labelAuthors + `": "` + author + `", "` + labelLicenses + `": "` + license + `", "` + labelURL + `": "` + projectURL + `", "` + labelSource + `": "` + repositoryURL + `", "` + labelDocumentation + `": "` + documentationURL + `", "` + labelDescription + `": "` + description + `"}}, "history": [{"created_by": "do it 1"}, {"created_by": "dummy #(nop) do it 2"}]}`
- metadata, err := ParseImageConfig(oci.MediaType(oci.MediaTypeImageManifest), strings.NewReader(configOCI))
+ metadata, err := ParseImageConfig(oci.MediaTypeImageManifest, strings.NewReader(configOCI))
assert.NoError(t, err)
assert.Equal(t, TypeOCI, metadata.Type)
@@ -50,7 +50,7 @@ func TestParseImageConfig(t *testing.T) {
configHelm := `{"description":"` + description + `", "home": "` + projectURL + `", "sources": ["` + repositoryURL + `"], "maintainers":[{"name":"` + author + `"}]}`
- metadata, err = ParseImageConfig(oci.MediaType(helm.ConfigMediaType), strings.NewReader(configHelm))
+ metadata, err = ParseImageConfig(helm.ConfigMediaType, strings.NewReader(configHelm))
assert.NoError(t, err)
assert.Equal(t, TypeHelm, metadata.Type)
diff --git a/modules/packages/container/oci/digest.go b/modules/packages/container/oci/digest.go
deleted file mode 100644
index dd9cc0095..000000000
--- a/modules/packages/container/oci/digest.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package oci
-
-import (
- "regexp"
- "strings"
-)
-
-var digestPattern = regexp.MustCompile(`\Asha256:[a-f0-9]{64}\z`)
-
-type Digest string
-
-// Validate checks if the digest has a valid SHA256 signature
-func (d Digest) Validate() bool {
- return digestPattern.MatchString(string(d))
-}
-
-func (d Digest) Hash() string {
- p := strings.SplitN(string(d), ":", 2)
- if len(p) != 2 {
- return ""
- }
- return p[1]
-}
diff --git a/modules/packages/container/oci/mediatype.go b/modules/packages/container/oci/mediatype.go
deleted file mode 100644
index f9c3907e1..000000000
--- a/modules/packages/container/oci/mediatype.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package oci
-
-import (
- "strings"
-)
-
-const (
- MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json"
- MediaTypeImageIndex = "application/vnd.oci.image.index.v1+json"
- MediaTypeDockerManifest = "application/vnd.docker.distribution.manifest.v2+json"
- MediaTypeDockerManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
-)
-
-type MediaType string
-
-// IsValid tests if the media type is in the OCI or Docker namespace
-func (m MediaType) IsValid() bool {
- s := string(m)
- return strings.HasPrefix(s, "application/vnd.docker.") || strings.HasPrefix(s, "application/vnd.oci.")
-}
-
-// IsImageManifest tests if the media type is an image manifest
-func (m MediaType) IsImageManifest() bool {
- s := string(m)
- return strings.EqualFold(s, MediaTypeDockerManifest) || strings.EqualFold(s, MediaTypeImageManifest)
-}
-
-// IsImageIndex tests if the media type is an image index
-func (m MediaType) IsImageIndex() bool {
- s := string(m)
- return strings.EqualFold(s, MediaTypeDockerManifestList) || strings.EqualFold(s, MediaTypeImageIndex)
-}
diff --git a/modules/packages/container/oci/oci.go b/modules/packages/container/oci/oci.go
deleted file mode 100644
index 570d2e92c..000000000
--- a/modules/packages/container/oci/oci.go
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package oci
-
-import (
- "time"
-)
-
-// https://github.com/opencontainers/image-spec/tree/main/specs-go/v1
-
-// ImageConfig defines the execution parameters which should be used as a base when running a container using an image.
-type ImageConfig struct {
- // User defines the username or UID which the process in the container should run as.
- User string `json:"User,omitempty"`
-
- // ExposedPorts a set of ports to expose from a container running this image.
- ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"`
-
- // Env is a list of environment variables to be used in a container.
- Env []string `json:"Env,omitempty"`
-
- // Entrypoint defines a list of arguments to use as the command to execute when the container starts.
- Entrypoint []string `json:"Entrypoint,omitempty"`
-
- // Cmd defines the default arguments to the entrypoint of the container.
- Cmd []string `json:"Cmd,omitempty"`
-
- // Volumes is a set of directories describing where the process is likely write data specific to a container instance.
- Volumes map[string]struct{} `json:"Volumes,omitempty"`
-
- // WorkingDir sets the current working directory of the entrypoint process in the container.
- WorkingDir string `json:"WorkingDir,omitempty"`
-
- // Labels contains arbitrary metadata for the container.
- Labels map[string]string `json:"Labels,omitempty"`
-
- // StopSignal contains the system call signal that will be sent to the container to exit.
- StopSignal string `json:"StopSignal,omitempty"`
-}
-
-// RootFS describes a layer content addresses
-type RootFS struct {
- // Type is the type of the rootfs.
- Type string `json:"type"`
-
- // DiffIDs is an array of layer content hashes, in order from bottom-most to top-most.
- DiffIDs []string `json:"diff_ids"`
-}
-
-// History describes the history of a layer.
-type History struct {
- // Created is the combined date and time at which the layer was created, formatted as defined by RFC 3339, section 5.6.
- Created *time.Time `json:"created,omitempty"`
-
- // CreatedBy is the command which created the layer.
- CreatedBy string `json:"created_by,omitempty"`
-
- // Author is the author of the build point.
- Author string `json:"author,omitempty"`
-
- // Comment is a custom message set when creating the layer.
- Comment string `json:"comment,omitempty"`
-
- // EmptyLayer is used to mark if the history item created a filesystem diff.
- EmptyLayer bool `json:"empty_layer,omitempty"`
-}
-
-// Image is the JSON structure which describes some basic information about the image.
-// This provides the `application/vnd.oci.image.config.v1+json` mediatype when marshalled to JSON.
-type Image struct {
- // Created is the combined date and time at which the image was created, formatted as defined by RFC 3339, section 5.6.
- Created *time.Time `json:"created,omitempty"`
-
- // Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image.
- Author string `json:"author,omitempty"`
-
- // Architecture is the CPU architecture which the binaries in this image are built to run on.
- Architecture string `json:"architecture"`
-
- // Variant is the variant of the specified CPU architecture which image binaries are intended to run on.
- Variant string `json:"variant,omitempty"`
-
- // OS is the name of the operating system which the image is built to run on.
- OS string `json:"os"`
-
- // OSVersion is an optional field specifying the operating system
- // version, for example on Windows `10.0.14393.1066`.
- OSVersion string `json:"os.version,omitempty"`
-
- // OSFeatures is an optional field specifying an array of strings,
- // each listing a required OS feature (for example on Windows `win32k`).
- OSFeatures []string `json:"os.features,omitempty"`
-
- // Config defines the execution parameters which should be used as a base when running a container using the image.
- Config ImageConfig `json:"config,omitempty"`
-
- // RootFS references the layer content addresses used by the image.
- RootFS RootFS `json:"rootfs"`
-
- // History describes the history of each layer.
- History []History `json:"history,omitempty"`
-}
-
-// Descriptor describes the disposition of targeted content.
-// This structure provides `application/vnd.oci.descriptor.v1+json` mediatype
-// when marshalled to JSON.
-type Descriptor struct {
- // MediaType is the media type of the object this schema refers to.
- MediaType MediaType `json:"mediaType,omitempty"`
-
- // Digest is the digest of the targeted content.
- Digest Digest `json:"digest"`
-
- // Size specifies the size in bytes of the blob.
- Size int64 `json:"size"`
-
- // URLs specifies a list of URLs from which this object MAY be downloaded
- URLs []string `json:"urls,omitempty"`
-
- // Annotations contains arbitrary metadata relating to the targeted content.
- Annotations map[string]string `json:"annotations,omitempty"`
-
- // Data is an embedding of the targeted content. This is encoded as a base64
- // string when marshalled to JSON (automatically, by encoding/json). If
- // present, Data can be used directly to avoid fetching the targeted content.
- Data []byte `json:"data,omitempty"`
-
- // Platform describes the platform which the image in the manifest runs on.
- //
- // This should only be used when referring to a manifest.
- Platform *Platform `json:"platform,omitempty"`
-}
-
-// Platform describes the platform which the image in the manifest runs on.
-type Platform struct {
- // Architecture field specifies the CPU architecture, for example
- // `amd64` or `ppc64`.
- Architecture string `json:"architecture"`
-
- // OS specifies the operating system, for example `linux` or `windows`.
- OS string `json:"os"`
-
- // OSVersion is an optional field specifying the operating system
- // version, for example on Windows `10.0.14393.1066`.
- OSVersion string `json:"os.version,omitempty"`
-
- // OSFeatures is an optional field specifying an array of strings,
- // each listing a required OS feature (for example on Windows `win32k`).
- OSFeatures []string `json:"os.features,omitempty"`
-
- // Variant is an optional field specifying a variant of the CPU, for
- // example `v7` to specify ARMv7 when architecture is `arm`.
- Variant string `json:"variant,omitempty"`
-}
-
-type SchemaMediaBase struct {
- // SchemaVersion is the image manifest schema that this image follows
- SchemaVersion int `json:"schemaVersion"`
-
- // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json`
- MediaType MediaType `json:"mediaType,omitempty"`
-}
-
-// Manifest provides `application/vnd.oci.image.manifest.v1+json` mediatype structure when marshalled to JSON.
-type Manifest struct {
- SchemaMediaBase
-
- // Config references a configuration object for a container, by digest.
- // The referenced configuration object is a JSON blob that the runtime uses to set up the container.
- Config Descriptor `json:"config"`
-
- // Layers is an indexed list of layers referenced by the manifest.
- Layers []Descriptor `json:"layers"`
-
- // Annotations contains arbitrary metadata for the image manifest.
- Annotations map[string]string `json:"annotations,omitempty"`
-}
-
-// Index references manifests for various platforms.
-// This structure provides `application/vnd.oci.image.index.v1+json` mediatype when marshalled to JSON.
-type Index struct {
- SchemaMediaBase
-
- // Manifests references platform specific manifests.
- Manifests []Descriptor `json:"manifests"`
-
- // Annotations contains arbitrary metadata for the image index.
- Annotations map[string]string `json:"annotations,omitempty"`
-}
diff --git a/modules/packages/container/oci/reference.go b/modules/packages/container/oci/reference.go
deleted file mode 100644
index 7ec399255..000000000
--- a/modules/packages/container/oci/reference.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package oci
-
-import (
- "regexp"
-)
-
-var referencePattern = regexp.MustCompile(`\A[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}\z`)
-
-type Reference string
-
-func (r Reference) Validate() bool {
- return referencePattern.MatchString(string(r))
-}
diff --git a/modules/proxy/proxy.go b/modules/proxy/proxy.go
index f0cd366c1..1a6bdad7f 100644
--- a/modules/proxy/proxy.go
+++ b/modules/proxy/proxy.go
@@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"os"
+ "strings"
"sync"
"code.gitea.io/gitea/modules/log"
@@ -82,3 +83,16 @@ func Proxy() func(req *http.Request) (*url.URL, error) {
return http.ProxyFromEnvironment(req)
}
}
+
+// EnvWithProxy returns os.Environ(), with a https_proxy env, if the given url
+// needs to be proxied.
+func EnvWithProxy(u *url.URL) []string {
+ envs := os.Environ()
+ if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") {
+ if Match(u.Host) {
+ envs = append(envs, "https_proxy="+GetProxyURL())
+ }
+ }
+
+ return envs
+}
diff --git a/modules/repository/create.go b/modules/repository/create.go
index 7bcda0fe4..b9a72ad57 100644
--- a/modules/repository/create.go
+++ b/modules/repository/create.go
@@ -211,6 +211,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
IsEmpty: !opts.AutoInit,
TrustModel: opts.TrustModel,
IsMirror: opts.IsMirror,
+ DefaultBranch: opts.DefaultBranch,
}
var rollbackRepo *repo_model.Repository
diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go
index 293071bdc..e620422bc 100644
--- a/modules/repository/create_test.go
+++ b/modules/repository/create_test.go
@@ -49,7 +49,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization")
// Check Owner team.
- ownerTeam, err := org.GetOwnerTeam()
+ ownerTeam, err := org.GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err, "GetOwnerTeam")
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
@@ -63,7 +63,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
}
}
// Get fresh copy of Owner team after creating repos.
- ownerTeam, err = org.GetOwnerTeam()
+ ownerTeam, err = org.GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err, "GetOwnerTeam")
// Create teams and check repositories.
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index d1a70e7c1..c03e46999 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -57,7 +57,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
if u.IsOrganization() {
- t, err := organization.OrgFromUser(u).GetOwnerTeam()
+ t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
if err != nil {
return nil, err
}
diff --git a/modules/setting/packages.go b/modules/setting/packages.go
index d0cd80aa0..84da4eb53 100644
--- a/modules/setting/packages.go
+++ b/modules/setting/packages.go
@@ -25,6 +25,8 @@ var (
LimitTotalOwnerCount int64
LimitTotalOwnerSize int64
+ LimitSizeCargo int64
+ LimitSizeChef int64
LimitSizeComposer int64
LimitSizeConan int64
LimitSizeConda int64
@@ -65,6 +67,8 @@ func newPackages() {
}
Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
+ Packages.LimitSizeCargo = mustBytes(sec, "LIMIT_SIZE_CARGO")
+ Packages.LimitSizeChef = mustBytes(sec, "LIMIT_SIZE_CHEF")
Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER")
Packages.LimitSizeConan = mustBytes(sec, "LIMIT_SIZE_CONAN")
Packages.LimitSizeConda = mustBytes(sec, "LIMIT_SIZE_CONDA")
diff --git a/modules/setting/service.go b/modules/setting/service.go
index 7b4bfc5c7..1d33ac6bc 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -46,6 +46,8 @@ var Service = struct {
RecaptchaSecret string
RecaptchaSitekey string
RecaptchaURL string
+ CfTurnstileSecret string
+ CfTurnstileSitekey string
HcaptchaSecret string
HcaptchaSitekey string
McaptchaSecret string
@@ -137,6 +139,8 @@ func newService() {
Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")
Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")
Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/")
+ Service.CfTurnstileSecret = sec.Key("CF_TURNSTILE_SECRET").MustString("")
+ Service.CfTurnstileSitekey = sec.Key("CF_TURNSTILE_SITEKEY").MustString("")
Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("")
Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("")
Service.McaptchaURL = sec.Key("MCAPTCHA_URL").MustString("https://demo.mcaptcha.org/")
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 23cd90553..a68a46f7a 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -61,6 +61,7 @@ const (
ReCaptcha = "recaptcha"
HCaptcha = "hcaptcha"
MCaptcha = "mcaptcha"
+ CfTurnstile = "cfturnstile"
)
// settings
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index 16f3d9dd2..ee4bec4df 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -63,6 +63,7 @@ type Repository struct {
Language string `json:"language"`
LanguagesURL string `json:"languages_url"`
HTMLURL string `json:"html_url"`
+ Link string `json:"link"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
OriginalURL string `json:"original_url"`
diff --git a/modules/structs/org_type.go b/modules/structs/visible_type.go
index 69f323fa9..b5ff353b8 100644
--- a/modules/structs/org_type.go
+++ b/modules/structs/visible_type.go
@@ -3,7 +3,7 @@
package structs
-// VisibleType defines the visibility (Organization only)
+// VisibleType defines the visibility of user and org
type VisibleType int
const (
@@ -13,11 +13,11 @@ const (
// VisibleTypeLimited Visible for every connected user
VisibleTypeLimited
- // VisibleTypePrivate Visible only for organization's members
+ // VisibleTypePrivate Visible only for self or admin user
VisibleTypePrivate
)
-// VisibilityModes is a map of org Visibility types
+// VisibilityModes is a map of Visibility types
var VisibilityModes = map[string]VisibleType{
"public": VisibleTypePublic,
"limited": VisibleTypeLimited,
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index a390d9459..7afc3aa59 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -72,6 +72,10 @@ func NewFuncMap() []template.FuncMap {
return setting.StaticURLPrefix + "/assets"
},
"AppUrl": func() string {
+ // The usage of AppUrl should be avoided as much as possible,
+ // because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
+ // And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
+ // because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
return setting.AppURL
},
"AppVer": func() string {
diff --git a/modules/turnstile/turnstile.go b/modules/turnstile/turnstile.go
new file mode 100644
index 000000000..38d023344
--- /dev/null
+++ b/modules/turnstile/turnstile.go
@@ -0,0 +1,92 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package turnstile
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// Response is the structure of JSON returned from API
+type Response struct {
+ Success bool `json:"success"`
+ ChallengeTS string `json:"challenge_ts"`
+ Hostname string `json:"hostname"`
+ ErrorCodes []ErrorCode `json:"error-codes"`
+ Action string `json:"login"`
+ Cdata string `json:"cdata"`
+}
+
+// Verify calls Cloudflare Turnstile API to verify token
+func Verify(ctx context.Context, response string) (bool, error) {
+ // Cloudflare turnstile official access instruction address: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
+ post := url.Values{
+ "secret": {setting.Service.CfTurnstileSecret},
+ "response": {response},
+ }
+ // Basically a copy of http.PostForm, but with a context
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost,
+ "https://challenges.cloudflare.com/turnstile/v0/siteverify", strings.NewReader(post.Encode()))
+ if err != nil {
+ return false, fmt.Errorf("Failed to create CAPTCHA request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return false, fmt.Errorf("Failed to send CAPTCHA response: %w", err)
+ }
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return false, fmt.Errorf("Failed to read CAPTCHA response: %w", err)
+ }
+
+ var jsonResponse Response
+ if err := json.Unmarshal(body, &jsonResponse); err != nil {
+ return false, fmt.Errorf("Failed to parse CAPTCHA response: %w", err)
+ }
+
+ var respErr error
+ if len(jsonResponse.ErrorCodes) > 0 {
+ respErr = jsonResponse.ErrorCodes[0]
+ }
+ return jsonResponse.Success, respErr
+}
+
+// ErrorCode is a reCaptcha error
+type ErrorCode string
+
+// String fulfills the Stringer interface
+func (e ErrorCode) String() string {
+ switch e {
+ case "missing-input-secret":
+ return "The secret parameter was not passed."
+ case "invalid-input-secret":
+ return "The secret parameter was invalid or did not exist."
+ case "missing-input-response":
+ return "The response parameter was not passed."
+ case "invalid-input-response":
+ return "The response parameter is invalid or has expired."
+ case "bad-request":
+ return "The request was rejected because it was malformed."
+ case "timeout-or-duplicate":
+ return "The response parameter has already been validated before."
+ case "internal-error":
+ return "An internal error happened while validating the response. The request can be retried."
+ }
+ return string(e)
+}
+
+// Error fulfills the error interface
+func (e ErrorCode) Error() string {
+ return e.String()
+}
diff --git a/modules/util/keypair.go b/modules/util/keypair.go
new file mode 100644
index 000000000..5a3ce715a
--- /dev/null
+++ b/modules/util/keypair.go
@@ -0,0 +1,45 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+)
+
+// GenerateKeyPair generates a public and private keypair
+func GenerateKeyPair(bits int) (string, string, error) {
+ priv, _ := rsa.GenerateKey(rand.Reader, bits)
+ privPem, err := pemBlockForPriv(priv)
+ if err != nil {
+ return "", "", err
+ }
+ pubPem, err := pemBlockForPub(&priv.PublicKey)
+ if err != nil {
+ return "", "", err
+ }
+ return privPem, pubPem, nil
+}
+
+func pemBlockForPriv(priv *rsa.PrivateKey) (string, error) {
+ privBytes := pem.EncodeToMemory(&pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: x509.MarshalPKCS1PrivateKey(priv),
+ })
+ return string(privBytes), nil
+}
+
+func pemBlockForPub(pub *rsa.PublicKey) (string, error) {
+ pubASN1, err := x509.MarshalPKIXPublicKey(pub)
+ if err != nil {
+ return "", err
+ }
+ pubBytes := pem.EncodeToMemory(&pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: pubASN1,
+ })
+ return string(pubBytes), nil
+}
diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go
new file mode 100644
index 000000000..c6f68c845
--- /dev/null
+++ b/modules/util/keypair_test.go
@@ -0,0 +1,61 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/pem"
+ "regexp"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestKeygen(t *testing.T) {
+ priv, pub, err := GenerateKeyPair(2048)
+ assert.NoError(t, err)
+
+ assert.NotEmpty(t, priv)
+ assert.NotEmpty(t, pub)
+
+ assert.Regexp(t, regexp.MustCompile("^-----BEGIN RSA PRIVATE KEY-----.*"), priv)
+ assert.Regexp(t, regexp.MustCompile("^-----BEGIN PUBLIC KEY-----.*"), pub)
+}
+
+func TestSignUsingKeys(t *testing.T) {
+ priv, pub, err := GenerateKeyPair(2048)
+ assert.NoError(t, err)
+
+ privPem, _ := pem.Decode([]byte(priv))
+ if privPem == nil || privPem.Type != "RSA PRIVATE KEY" {
+ t.Fatal("key is wrong type")
+ }
+
+ privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
+ assert.NoError(t, err)
+
+ pubPem, _ := pem.Decode([]byte(pub))
+ if pubPem == nil || pubPem.Type != "PUBLIC KEY" {
+ t.Fatal("key failed to decode")
+ }
+
+ pubParsed, err := x509.ParsePKIXPublicKey(pubPem.Bytes)
+ assert.NoError(t, err)
+
+ // Sign
+ msg := "activity pub is great!"
+ h := sha256.New()
+ h.Write([]byte(msg))
+ d := h.Sum(nil)
+ sig, err := rsa.SignPKCS1v15(rand.Reader, privParsed, crypto.SHA256, d)
+ assert.NoError(t, err)
+
+ // Verify
+ err = rsa.VerifyPKCS1v15(pubParsed.(*rsa.PublicKey), crypto.SHA256, d, sig)
+ assert.NoError(t, err)
+}
diff --git a/modules/validation/binding.go b/modules/validation/binding.go
index ef0d01e80..1f904979f 100644
--- a/modules/validation/binding.go
+++ b/modules/validation/binding.go
@@ -8,6 +8,7 @@ import (
"regexp"
"strings"
+ "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/git"
"gitea.com/go-chi/binding"
@@ -17,15 +18,14 @@ import (
const (
// ErrGitRefName is git reference name error
ErrGitRefName = "GitRefNameError"
-
// ErrGlobPattern is returned when glob pattern is invalid
ErrGlobPattern = "GlobPattern"
-
// ErrRegexPattern is returned when a regex pattern is invalid
ErrRegexPattern = "RegexPattern"
-
// ErrUsername is username error
ErrUsername = "UsernameError"
+ // ErrInvalidGroupTeamMap is returned when a group team mapping is invalid
+ ErrInvalidGroupTeamMap = "InvalidGroupTeamMap"
)
// AddBindingRules adds additional binding rules
@@ -37,6 +37,7 @@ func AddBindingRules() {
addRegexPatternRule()
addGlobOrRegexPatternRule()
addUsernamePatternRule()
+ addValidGroupTeamMapRule()
}
func addGitRefNameBindingRule() {
@@ -167,6 +168,23 @@ func addUsernamePatternRule() {
})
}
+func addValidGroupTeamMapRule() {
+ binding.AddRule(&binding.Rule{
+ IsMatch: func(rule string) bool {
+ return strings.HasPrefix(rule, "ValidGroupTeamMap")
+ },
+ IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) {
+ _, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val))
+ if err != nil {
+ errs.Add([]string{name}, ErrInvalidGroupTeamMap, err.Error())
+ return false, errs
+ }
+
+ return true, errs
+ },
+ })
+}
+
func portOnly(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go
index 733f00a1d..8b74a864d 100644
--- a/modules/web/middleware/binding.go
+++ b/modules/web/middleware/binding.go
@@ -136,6 +136,8 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl
data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
case validation.ErrUsername:
data["ErrorMsg"] = trName + l.Tr("form.username_error")
+ case validation.ErrInvalidGroupTeamMap:
+ data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
default:
msg := errs[0].Classification
if msg != "" && errs[0].Message != "" {