mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-09 17:37:20 -06:00
refactor: lazy init global regexp variables (#40269)
Gitea binary is an all-in-one program. It doesn't make sense to compile dozens or even hundreds of regexps for git hook sub commands.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
)
|
)
|
||||||
@@ -31,7 +32,9 @@ type emoji struct {
|
|||||||
description string
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
var slugRe = regexp.MustCompile(`[^a-z0-9]+`)
|
var slugRe = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`[^a-z0-9]+`)
|
||||||
|
})
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := generate(); err != nil {
|
if err := generate(); err != nil {
|
||||||
@@ -108,7 +111,7 @@ func fetchEmojis(existingAliases map[string][]string) ([]*emoji, error) {
|
|||||||
for _, e := range emojis {
|
for _, e := range emojis {
|
||||||
e.Aliases = existingAliases[e.Emoji]
|
e.Aliases = existingAliases[e.Emoji]
|
||||||
if e.Aliases == nil {
|
if e.Aliases == nil {
|
||||||
e.Aliases = []string{strings.Trim(slugRe.ReplaceAllString(strings.ReplaceAll(strings.ToLower(e.description), "’", ""), "_"), "_")}
|
e.Aliases = []string{strings.Trim(slugRe().ReplaceAllString(strings.ReplaceAll(strings.ToLower(e.description), "’", ""), "_"), "_")}
|
||||||
}
|
}
|
||||||
delete(existingAliases, e.Emoji)
|
delete(existingAliases, e.Emoji)
|
||||||
for _, alias := range e.Aliases {
|
for _, alias := range e.Aliases {
|
||||||
|
|||||||
@@ -15,17 +15,24 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// regexp is based on go-license, excluding README and NOTICE
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
// https://github.com/google/go-licenses/blob/master/licenses/find.go
|
licenseRe *regexp.Regexp
|
||||||
// also defined in vite.config.ts
|
primaryLicenseRe *regexp.Regexp
|
||||||
var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`)
|
},
|
||||||
|
) {
|
||||||
// primaryLicenseRe matches exact primary license filenames without suffixes.
|
// regexp is based on go-license, excluding README and NOTICE
|
||||||
// When a directory has both primary and variant files (e.g. LICENSE and
|
// https://github.com/google/go-licenses/blob/master/licenses/find.go
|
||||||
// LICENSE.docs), only the primary files are kept.
|
// also defined in vite.config.ts
|
||||||
var primaryLicenseRe = regexp.MustCompile(`^(?i)(LICEN[SC]E|COPYING)$`)
|
ret.licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`)
|
||||||
|
// primaryLicenseRe matches exact primary license filenames without suffixes.
|
||||||
|
// When a directory has both primary and variant files (e.g. LICENSE and
|
||||||
|
// LICENSE.docs), only the primary files are kept.
|
||||||
|
ret.primaryLicenseRe = regexp.MustCompile(`^(?i)(LICEN[SC]E|COPYING)$`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// ignoredNames are LicenseEntry.Name values to exclude from the output.
|
// ignoredNames are LicenseEntry.Name values to exclude from the output.
|
||||||
var ignoredNames = map[string]bool{
|
var ignoredNames = map[string]bool{
|
||||||
@@ -144,7 +151,7 @@ func scanDirForLicenses(dir, modulePath, moduleRoot string) []LicenseEntry {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
if !licenseRe.MatchString(name) {
|
if !globalVars().licenseRe.MatchString(name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if excludedExt[strings.ToLower(filepath.Ext(name))] {
|
if excludedExt[strings.ToLower(filepath.Ext(name))] {
|
||||||
@@ -181,7 +188,7 @@ func scanDirForLicenses(dir, modulePath, moduleRoot string) []LicenseEntry {
|
|||||||
var primary []LicenseEntry
|
var primary []LicenseEntry
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
fileName := e.Path[strings.LastIndex(e.Path, "/")+1:]
|
fileName := e.Path[strings.LastIndex(e.Path, "/")+1:]
|
||||||
if primaryLicenseRe.MatchString(fileName) {
|
if globalVars().primaryLicenseRe.MatchString(fileName) {
|
||||||
primary = append(primary, e)
|
primary = append(primary, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
|
|
||||||
@@ -16,11 +17,19 @@ import (
|
|||||||
"github.com/getkin/kin-openapi/openapi3"
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rxDeprecated matches "deprecated" as a word at the start of a description
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
// or preceded by whitespace/punctuation that indicates a leading marker (e.g.
|
rxDeprecated *regexp.Regexp
|
||||||
// "Deprecated: true", "deprecated (use X instead)"). Rejects negated phrases
|
rxSwaggerEnum *regexp.Regexp
|
||||||
// like "not deprecated" or "previously deprecated, now supported".
|
},
|
||||||
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
) {
|
||||||
|
// rxDeprecated matches "deprecated" as a word at the start of a description
|
||||||
|
// or preceded by whitespace/punctuation that indicates a leading marker (e.g.
|
||||||
|
// "Deprecated: true", "deprecated (use X instead)"). Rejects negated phrases
|
||||||
|
// like "not deprecated" or "previously deprecated, now supported".
|
||||||
|
ret.rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
||||||
|
ret.rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
||||||
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
||||||
@@ -158,7 +167,7 @@ func addDeprecatedFlags(doc *openapi3.T) {
|
|||||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if rxDeprecated.MatchString(propRef.Value.Description) {
|
if globalVars().rxDeprecated.MatchString(propRef.Value.Description) {
|
||||||
propRef.Value.Deprecated = true
|
propRef.Value.Deprecated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"go/token"
|
"go/token"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -31,8 +30,6 @@ func EnumKey(values []any) string {
|
|||||||
return strings.Join(strs, "|")
|
return strings.Join(strs, "|")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`)
|
|
||||||
|
|
||||||
// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from
|
// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from
|
||||||
// a canonical value-set key (see EnumKey) to the Go type names declared with
|
// a canonical value-set key (see EnumKey) to the Go type names declared with
|
||||||
// // swagger:enum TypeName. Multiple type names per key are allowed (e.g.
|
// // swagger:enum TypeName. Multiple type names per key are allowed (e.g.
|
||||||
@@ -146,7 +143,7 @@ func registerEnumAnnotation(doc *ast.CommentGroup, specs []ast.Spec, enumTypes m
|
|||||||
if doc == nil {
|
if doc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
matches := rxSwaggerEnum.FindStringSubmatch(doc.Text())
|
matches := globalVars().rxSwaggerEnum.FindStringSubmatch(doc.Text())
|
||||||
if len(matches) < 2 {
|
if len(matches) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
project_model "gitea.dev/models/project"
|
project_model "gitea.dev/models/project"
|
||||||
@@ -112,10 +113,15 @@ type Issue struct {
|
|||||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
issueTasksPat = regexp.MustCompile(`(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)`)
|
issueTasksPat *regexp.Regexp
|
||||||
issueTasksDonePat = regexp.MustCompile(`(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)`)
|
issueTasksDonePat *regexp.Regexp
|
||||||
)
|
},
|
||||||
|
) {
|
||||||
|
ret.issueTasksPat = regexp.MustCompile(`(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)`)
|
||||||
|
ret.issueTasksDonePat = regexp.MustCompile(`(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// IssueIndex represents the issue index table
|
// IssueIndex represents the issue index table
|
||||||
type IssueIndex db.ResourceIndex
|
type IssueIndex db.ResourceIndex
|
||||||
@@ -463,12 +469,12 @@ func (issue *Issue) IsPoster(uid int64) bool {
|
|||||||
|
|
||||||
// GetTasks returns the amount of tasks in the issues content
|
// GetTasks returns the amount of tasks in the issues content
|
||||||
func (issue *Issue) GetTasks() int {
|
func (issue *Issue) GetTasks() int {
|
||||||
return len(issueTasksPat.FindAllStringIndex(issue.Content, -1))
|
return len(globalVars().issueTasksPat.FindAllStringIndex(issue.Content, -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTasksDone returns the amount of completed tasks in the issues content
|
// GetTasksDone returns the amount of completed tasks in the issues content
|
||||||
func (issue *Issue) GetTasksDone() int {
|
func (issue *Issue) GetTasksDone() int {
|
||||||
return len(issueTasksDonePat.FindAllStringIndex(issue.Content, -1))
|
return len(globalVars().issueTasksDonePat.FindAllStringIndex(issue.Content, -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastEventTimestamp returns the last user visible event timestamp, either the creation of this issue or the close.
|
// GetLastEventTimestamp returns the last user visible event timestamp, either the creation of this issue or the close.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
@@ -36,10 +37,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ColumnColorPattern is a regexp witch can validate ColumnColor
|
// ColumnColorPattern is a regexp witch can validate ColumnColor
|
||||||
var ColumnColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
var ColumnColorPattern = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
||||||
|
})
|
||||||
|
|
||||||
func validateColumnColor(color string) error {
|
func validateColumnColor(color string) error {
|
||||||
if len(color) != 0 && !ColumnColorPattern.MatchString(color) {
|
if len(color) != 0 && !ColumnColorPattern().MatchString(color) {
|
||||||
return util.ErrorWrap(util.ErrUnprocessableContent, "invalid column color %q, expected a 6-digit hex string like #FF0000", color)
|
return util.ErrorWrap(util.ErrUnprocessableContent, "invalid column color %q, expected a 6-digit hex string like #FF0000", color)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/container"
|
"gitea.dev/modules/container"
|
||||||
@@ -22,7 +23,9 @@ func init() {
|
|||||||
db.RegisterModel(new(RepoTopic))
|
db.RegisterModel(new(RepoTopic))
|
||||||
}
|
}
|
||||||
|
|
||||||
var topicPattern = regexp.MustCompile(`^[a-z0-9][-.a-z0-9]*$`)
|
var topicPattern = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`^[a-z0-9][-.a-z0-9]*$`)
|
||||||
|
})
|
||||||
|
|
||||||
// Topic represents a topic of repositories
|
// Topic represents a topic of repositories
|
||||||
type Topic struct {
|
type Topic struct {
|
||||||
@@ -55,7 +58,7 @@ func (err ErrTopicNotExist) Unwrap() error {
|
|||||||
|
|
||||||
// ValidateTopic checks a topic by length and match pattern rules
|
// ValidateTopic checks a topic by length and match pattern rules
|
||||||
func ValidateTopic(topic string) bool {
|
func ValidateTopic(topic string) bool {
|
||||||
return len(topic) <= 35 && topicPattern.MatchString(topic)
|
return len(topic) <= 35 && topicPattern().MatchString(topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeAndValidateTopics sanitizes and checks an array or topics
|
// SanitizeAndValidateTopics sanitizes and checks an array or topics
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/actionslib/pkg/model"
|
"gitea.dev/actionslib/pkg/model"
|
||||||
|
|
||||||
@@ -75,7 +76,9 @@ func ParseWorkflowCallConfig(content []byte) (*model.WorkflowCall, error) {
|
|||||||
const SecretsInherit = "inherit"
|
const SecretsInherit = "inherit"
|
||||||
|
|
||||||
// callerSecretValueRegexp matches the `${{ secrets.NAME }}` form expected for each value in a caller's `secrets:` mapping.
|
// callerSecretValueRegexp matches the `${{ secrets.NAME }}` form expected for each value in a caller's `secrets:` mapping.
|
||||||
var callerSecretValueRegexp = regexp.MustCompile(`^\s*\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}\s*$`)
|
var callerSecretValueRegexp = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`^\s*\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}\s*$`)
|
||||||
|
})
|
||||||
|
|
||||||
// ParseCallerSecrets decodes a caller's "secrets:" YAML node into one of two forms:
|
// ParseCallerSecrets decodes a caller's "secrets:" YAML node into one of two forms:
|
||||||
// - inherit == true: the caller wrote `secrets: inherit`; mapping is nil
|
// - inherit == true: the caller wrote `secrets: inherit`; mapping is nil
|
||||||
@@ -101,7 +104,7 @@ func ParseCallerSecrets(node yaml.Node) (inherit bool, mapping map[string]string
|
|||||||
if err := v.Decode(&sv); err != nil {
|
if err := v.Decode(&sv); err != nil {
|
||||||
return false, nil, fmt.Errorf("decode secret %q: %w", k.Value, err)
|
return false, nil, fmt.Errorf("decode secret %q: %w", k.Value, err)
|
||||||
}
|
}
|
||||||
matches := callerSecretValueRegexp.FindStringSubmatch(sv)
|
matches := callerSecretValueRegexp().FindStringSubmatch(sv)
|
||||||
if len(matches) != 2 {
|
if len(matches) != 2 {
|
||||||
return false, nil, fmt.Errorf("caller secret %q value must be of the form ${{ secrets.NAME }}", k.Value)
|
return false, nil, fmt.Errorf("caller secret %q value must be of the form ${{ secrets.NAME }}", k.Value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/markup"
|
"gitea.dev/modules/markup"
|
||||||
"gitea.dev/modules/translation"
|
"gitea.dev/modules/translation"
|
||||||
@@ -71,15 +72,24 @@ func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
|
|||||||
return delimiter
|
return delimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
// quoteRegexp follows the RFC-4180 CSV standard for when double-quotes are used to enclose fields, then a double-quote appearing inside a
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
// field must be escaped by preceding it with another double quote. https://www.ietf.org/rfc/rfc4180.txt
|
quoteRegexp *regexp.Regexp
|
||||||
// This finds all quoted strings that have escaped quotes.
|
beforeAfterQuotes *regexp.Regexp
|
||||||
var quoteRegexp = regexp.MustCompile(`"[^"]*"`)
|
},
|
||||||
|
) {
|
||||||
|
// quoteRegexp follows the RFC-4180 CSV standard for when double-quotes are used to enclose fields, then a double-quote appearing inside a
|
||||||
|
// field must be escaped by preceding it with another double quote. https://www.ietf.org/rfc/rfc4180.txt
|
||||||
|
// This finds all quoted strings that have escaped quotes.
|
||||||
|
ret.quoteRegexp = regexp.MustCompile(`"[^"]*"`)
|
||||||
|
// Looks for possible delimiters right before or after (with spaces after the former) double quotes with closing quotes
|
||||||
|
ret.beforeAfterQuotes = regexp.MustCompile(`([,@\t;|]{0,1}) *(?:"[^"]*")+([,@\t;|]{0,1})`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// removeQuotedStrings uses the quoteRegexp to remove all quoted strings so that we can reliably have each row on one line
|
// removeQuotedStrings uses the quoteRegexp to remove all quoted strings so that we can reliably have each row on one line
|
||||||
// (quoted strings often have new lines within the string)
|
// (quoted strings often have new lines within the string)
|
||||||
func removeQuotedString(text string) string {
|
func removeQuotedString(text string) string {
|
||||||
return quoteRegexp.ReplaceAllLiteralString(text, "")
|
return globalVars().quoteRegexp.ReplaceAllLiteralString(text, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// guessDelimiter takes up to maxLines of the CSV text, iterates through the possible delimiters, and sees if the CSV Reader reads it without throwing any errors.
|
// guessDelimiter takes up to maxLines of the CSV text, iterates through the possible delimiters, and sees if the CSV Reader reads it without throwing any errors.
|
||||||
@@ -133,13 +143,10 @@ func FormatError(err error, locale translation.Locale) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks for possible delimiters right before or after (with spaces after the former) double quotes with closing quotes
|
|
||||||
var beforeAfterQuotes = regexp.MustCompile(`([,@\t;|]{0,1}) *(?:"[^"]*")+([,@\t;|]{0,1})`)
|
|
||||||
|
|
||||||
// guessFromBeforeAfterQuotes guesses the limiter by finding a double quote that has a valid delimiter before it and a closing quote,
|
// guessFromBeforeAfterQuotes guesses the limiter by finding a double quote that has a valid delimiter before it and a closing quote,
|
||||||
// or a double quote with a closing quote and a valid delimiter after it
|
// or a double quote with a closing quote and a valid delimiter after it
|
||||||
func guessFromBeforeAfterQuotes(data []byte) rune {
|
func guessFromBeforeAfterQuotes(data []byte) rune {
|
||||||
rs := beforeAfterQuotes.FindStringSubmatch(string(data)) // returns first match, or nil if none
|
rs := globalVars().beforeAfterQuotes.FindStringSubmatch(string(data)) // returns first match, or nil if none
|
||||||
if rs != nil {
|
if rs != nil {
|
||||||
if rs[1] != "" {
|
if rs[1] != "" {
|
||||||
return rune(rs[1][0]) // delimiter found left of quoted string
|
return rune(rs[1][0]) // delimiter found left of quoted string
|
||||||
|
|||||||
@@ -74,20 +74,43 @@ func (c *CommitMessage) MessageTrailer() CommitMessageTrailerValues {
|
|||||||
return c.trailerValues
|
return c.trailerValues
|
||||||
}
|
}
|
||||||
|
|
||||||
var commitMessageTrailerSplit = sync.OnceValue(func() *regexp.Regexp {
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
commitMessageTrailerSplit *regexp.Regexp
|
||||||
|
hunkRegex *regexp.Regexp
|
||||||
|
refNamePatternInvalid *regexp.Regexp
|
||||||
|
patchCommits *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
// ref: https://git-scm.com/docs/git-interpret-trailers
|
// ref: https://git-scm.com/docs/git-interpret-trailers
|
||||||
// TODO: the regexp is not able to perfectly parse the all kinds of trailers
|
// TODO: the regexp is not able to perfectly parse the all kinds of trailers
|
||||||
// It was just copied from legacy code, it is not exactly the same as how Git parses the trailer and not quite right in some cases.
|
// It was just copied from legacy code, it is not exactly the same as how Git parses the trailer and not quite right in some cases.
|
||||||
// For the key characters: it follows RFC 822 field name syntax (or RFC 2822/RFC 5322): printable ASCII characters between 33 and 126 except the colon (:),
|
// For the key characters: it follows RFC 822 field name syntax (or RFC 2822/RFC 5322): printable ASCII characters between 33 and 126 except the colon (:),
|
||||||
// but maybe we don't want to make it that complicated, so here we only support some common "symbol-like" characters.
|
// but maybe we don't want to make it that complicated, so here we only support some common "symbol-like" characters.
|
||||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n+|\n+-{3,}\n+|\n{2,})(?P<trailer>(?:[A-Za-z0-9][-\w]*:[^\n]*(\n\s+[^\n]*)*\n?)*\n*)$`)
|
ret.commitMessageTrailerSplit = regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n+|\n+-{3,}\n+|\n{2,})(?P<trailer>(?:[A-Za-z0-9][-\w]*:[^\n]*(\n\s+[^\n]*)*\n?)*\n*)$`)
|
||||||
|
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
|
||||||
|
ret.hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+))? \+(?P<beginNew>[0-9]+)(,(?P<endNew>[0-9]+))? @@`)
|
||||||
|
// refNamePatternInvalid is regular expression with unallowed characters in git reference name
|
||||||
|
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
|
||||||
|
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
|
||||||
|
ret.refNamePatternInvalid = regexp.MustCompile(
|
||||||
|
`[\000-\037\177 \\~^:?*[]|` + // No absolutely invalid characters
|
||||||
|
`(?:^[/.])|` + // Not HasPrefix("/") or "."
|
||||||
|
`(?:/\.)|` + // no "/."
|
||||||
|
`(?:\.lock$)|(?:\.lock/)|` + // No ".lock/"" or ".lock" at the end
|
||||||
|
`(?:\.\.)|` + // no ".." anywhere
|
||||||
|
`(?://)|` + // no "//" anywhere
|
||||||
|
`(?:@{)|` + // no "@{"
|
||||||
|
`(?:[/.]$)|` + // no terminal '/' or '.'
|
||||||
|
`(?:^@$)`) // Not "@"
|
||||||
|
ret.patchCommits = regexp.MustCompile(`^From\s(\w+)\s`)
|
||||||
|
return ret
|
||||||
})
|
})
|
||||||
|
|
||||||
// CommitMessageSplitTrailer tries to split the message by the trailer separator
|
// CommitMessageSplitTrailer tries to split the message by the trailer separator
|
||||||
// content + sep + trailer will reconstruct the original message
|
// content + sep + trailer will reconstruct the original message
|
||||||
func CommitMessageSplitTrailer(s string) (content, sep, trailer string) {
|
func CommitMessageSplitTrailer(s string) (content, sep, trailer string) {
|
||||||
s = util.NormalizeStringEOL(s)
|
s = util.NormalizeStringEOL(s)
|
||||||
re := commitMessageTrailerSplit()
|
re := globalVars().commitMessageTrailerSplit
|
||||||
v := re.FindStringSubmatch(s)
|
v := re.FindStringSubmatch(s)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return s, "", ""
|
return s, "", ""
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -132,9 +131,6 @@ func ParseDiffHunkString(diffHunk string) (leftLine, leftHunk, rightLine, rightH
|
|||||||
return leftLine, leftHunk, rightLine, rightHunk
|
return leftLine, leftHunk, rightLine, rightHunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
|
|
||||||
var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+))? \+(?P<beginNew>[0-9]+)(,(?P<endNew>[0-9]+))? @@`)
|
|
||||||
|
|
||||||
const cmdDiffHead = "diff --git "
|
const cmdDiffHead = "diff --git "
|
||||||
|
|
||||||
func isHeader(lof string, inHunk bool) bool {
|
func isHeader(lof string, inHunk bool) bool {
|
||||||
@@ -192,9 +188,9 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
// A map with named groups of our regex to recognize them later more easily
|
// A map with named groups of our regex to recognize them later more easily
|
||||||
submatches := hunkRegex.FindStringSubmatch(lof)
|
submatches := globalVars().hunkRegex.FindStringSubmatch(lof)
|
||||||
groups := make(map[string]string)
|
groups := make(map[string]string)
|
||||||
for i, name := range hunkRegex.SubexpNames() {
|
for i, name := range globalVars().hunkRegex.SubexpNames() {
|
||||||
if i != 0 && name != "" {
|
if i != 0 && name != "" {
|
||||||
groups[name] = submatches[i]
|
groups[name] = submatches[i]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -22,27 +21,13 @@ const (
|
|||||||
pullSuffix = "/head"
|
pullSuffix = "/head"
|
||||||
)
|
)
|
||||||
|
|
||||||
// refNamePatternInvalid is regular expression with unallowed characters in git reference name
|
|
||||||
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
|
|
||||||
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
|
|
||||||
var refNamePatternInvalid = regexp.MustCompile(
|
|
||||||
`[\000-\037\177 \\~^:?*[]|` + // No absolutely invalid characters
|
|
||||||
`(?:^[/.])|` + // Not HasPrefix("/") or "."
|
|
||||||
`(?:/\.)|` + // no "/."
|
|
||||||
`(?:\.lock$)|(?:\.lock/)|` + // No ".lock/"" or ".lock" at the end
|
|
||||||
`(?:\.\.)|` + // no ".." anywhere
|
|
||||||
`(?://)|` + // no "//" anywhere
|
|
||||||
`(?:@{)|` + // no "@{"
|
|
||||||
`(?:[/.]$)|` + // no terminal '/' or '.'
|
|
||||||
`(?:^@$)`) // Not "@"
|
|
||||||
|
|
||||||
// IsValidRefPattern ensures that the provided string could be a valid reference
|
// IsValidRefPattern ensures that the provided string could be a valid reference
|
||||||
func IsValidRefPattern(name string) bool {
|
func IsValidRefPattern(name string) bool {
|
||||||
return !refNamePatternInvalid.MatchString(name)
|
return !globalVars().refNamePatternInvalid.MatchString(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SanitizeRefPattern(name string) string {
|
func SanitizeRefPattern(name string) string {
|
||||||
return refNamePatternInvalid.ReplaceAllString(name, "_")
|
return globalVars().refNamePatternInvalid.ReplaceAllString(name, "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference represents a Git ref.
|
// Reference represents a Git ref.
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
@@ -51,8 +50,6 @@ func (repo *Repository) GetDiffNumChangedFiles(ctx context.Context, base, head s
|
|||||||
return w.numLines, nil
|
return w.numLines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var patchCommits = regexp.MustCompile(`^From\s(\w+)\s`)
|
|
||||||
|
|
||||||
// GetDiff generates and returns patch data between given revisions, optimized for human readability
|
// GetDiff generates and returns patch data between given revisions, optimized for human readability
|
||||||
func (repo *Repository) GetDiff(ctx context.Context, compareArg string, w io.Writer) error {
|
func (repo *Repository) GetDiff(ctx context.Context, compareArg string, w io.Writer) error {
|
||||||
return gitcmd.NewCommand("diff", "-p").AddDynamicArguments(compareArg).
|
return gitcmd.NewCommand("diff", "-p").AddDynamicArguments(compareArg).
|
||||||
@@ -119,7 +116,7 @@ func (repo *Repository) ReadPatchCommit(prID int64) (commitSHA string, err error
|
|||||||
scanner := bufio.NewScanner(loadPatch)
|
scanner := bufio.NewScanner(loadPatch)
|
||||||
scanner.Scan()
|
scanner.Scan()
|
||||||
// Parse the Patch stats, sometimes Migration returns a 404 for the patch file
|
// Parse the Patch stats, sometimes Migration returns a 404 for the patch file
|
||||||
commitSHAGroups := patchCommits.FindStringSubmatch(scanner.Text())
|
commitSHAGroups := globalVars().patchCommits.FindStringSubmatch(scanner.Text())
|
||||||
if len(commitSHAGroups) != 0 {
|
if len(commitSHAGroups) != 0 {
|
||||||
commitSHA = commitSHAGroups[1]
|
commitSHA = commitSHAGroups[1]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/packages"
|
"gitea.dev/modules/packages"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -38,6 +39,18 @@ const (
|
|||||||
maxPKGINFOSize = 1024 * 1024
|
maxPKGINFOSize = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
namePattern *regexp.Regexp
|
||||||
|
versionPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
// https://man.archlinux.org/man/PKGBUILD.5
|
||||||
|
ret.namePattern = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
|
||||||
|
// (epoch:pkgver-pkgrel)
|
||||||
|
ret.versionPattern = regexp.MustCompile(`\A(?:\d:)?[\w.+~]+(?:-[-\w.+~]+)?\z`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf(".PKGINFO file is missing")
|
ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf(".PKGINFO file is missing")
|
||||||
ErrUnsupportedFormat = util.NewInvalidArgumentErrorf("unsupported package container format")
|
ErrUnsupportedFormat = util.NewInvalidArgumentErrorf("unsupported package container format")
|
||||||
@@ -45,11 +58,6 @@ var (
|
|||||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||||
ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
|
ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
|
||||||
|
|
||||||
// https://man.archlinux.org/man/PKGBUILD.5
|
|
||||||
namePattern = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
|
|
||||||
// (epoch:pkgver-pkgrel)
|
|
||||||
versionPattern = regexp.MustCompile(`\A(?:\d:)?[\w.+~]+(?:-[-\w.+~]+)?\z`)
|
|
||||||
|
|
||||||
// caps on the accumulated package file list (vars so tests can lower them); far above
|
// caps on the accumulated package file list (vars so tests can lower them); far above
|
||||||
// any legitimate package, but low enough to stop metadata amplification
|
// any legitimate package, but low enough to stop metadata amplification
|
||||||
maxFileEntries = 100000
|
maxFileEntries = 100000
|
||||||
@@ -251,10 +259,10 @@ func ParsePackageInfo(r io.Reader) (*Package, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !namePattern.MatchString(p.Name) {
|
if !globalVars().namePattern.MatchString(p.Name) {
|
||||||
return nil, ErrInvalidName
|
return nil, ErrInvalidName
|
||||||
}
|
}
|
||||||
if !versionPattern.MatchString(p.Version) {
|
if !globalVars().versionPattern.MatchString(p.Version) {
|
||||||
return nil, ErrInvalidVersion
|
return nil, ErrInvalidVersion
|
||||||
}
|
}
|
||||||
if p.FileMetadata.Architecture == "" {
|
if p.FileMetadata.Architecture == "" {
|
||||||
|
|||||||
@@ -127,11 +127,11 @@ func TestParsePackageInfo(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Regexp", func(t *testing.T) {
|
t.Run("Regexp", func(t *testing.T) {
|
||||||
assert.Regexp(t, versionPattern, "1.2_3~4+5")
|
assert.Regexp(t, globalVars().versionPattern, "1.2_3~4+5")
|
||||||
assert.Regexp(t, versionPattern, "1:2_3~4+5")
|
assert.Regexp(t, globalVars().versionPattern, "1:2_3~4+5")
|
||||||
assert.NotRegexp(t, versionPattern, "a:1.0.0-1")
|
assert.NotRegexp(t, globalVars().versionPattern, "a:1.0.0-1")
|
||||||
assert.NotRegexp(t, versionPattern, "0.0.1/1-1")
|
assert.NotRegexp(t, globalVars().versionPattern, "0.0.1/1-1")
|
||||||
assert.NotRegexp(t, versionPattern, "1.0.0 -1")
|
assert.NotRegexp(t, globalVars().versionPattern, "1.0.0 -1")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidVersion", func(t *testing.T) {
|
t.Run("InvalidVersion", func(t *testing.T) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/packages"
|
"gitea.dev/modules/packages"
|
||||||
@@ -60,7 +61,9 @@ type Dependency struct {
|
|||||||
Package *string `json:"package"`
|
Package *string `json:"package"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameMatch = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9-_]{0,63}\z`)
|
var nameMatch = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9-_]{0,63}\z`)
|
||||||
|
})
|
||||||
|
|
||||||
// ParsePackage reads the metadata and content of a package
|
// ParsePackage reads the metadata and content of a package
|
||||||
func ParsePackage(r io.Reader) (*Package, error) {
|
func ParsePackage(r io.Reader) (*Package, error) {
|
||||||
@@ -120,7 +123,7 @@ func parsePackage(r io.Reader) (*Package, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !nameMatch.MatchString(meta.Name) {
|
if !nameMatch().MatchString(meta.Name) {
|
||||||
return nil, ErrInvalidName
|
return nil, ErrInvalidName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/packages"
|
"gitea.dev/modules/packages"
|
||||||
@@ -21,13 +22,20 @@ const (
|
|||||||
SettingPublicPem = "chef.public_pem"
|
SettingPublicPem = "chef.public_pem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
namePattern *regexp.Regexp
|
||||||
|
versionPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.namePattern = regexp.MustCompile(`\A\S+\z`)
|
||||||
|
ret.versionPattern = regexp.MustCompile(`\A\d+\.\d+(?:\.\d+)?\z`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingMetadataFile = util.NewInvalidArgumentErrorf("metadata.json file is missing")
|
ErrMissingMetadataFile = util.NewInvalidArgumentErrorf("metadata.json file is missing")
|
||||||
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
|
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
|
||||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version 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
|
// Package represents a Chef package
|
||||||
@@ -108,11 +116,11 @@ func ParseChefMetadata(r io.Reader) (*Package, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !namePattern.MatchString(cm.Name) {
|
if !globalVars().namePattern.MatchString(cm.Name) {
|
||||||
return nil, ErrInvalidName
|
return nil, ErrInvalidName
|
||||||
}
|
}
|
||||||
|
|
||||||
if !versionPattern.MatchString(cm.Version) {
|
if !globalVars().versionPattern.MatchString(cm.Version) {
|
||||||
return nil, ErrInvalidVersion
|
return nil, ErrInvalidVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/charset"
|
"gitea.dev/modules/charset"
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
@@ -117,7 +118,9 @@ type Author struct {
|
|||||||
Homepage string `json:"homepage,omitempty"`
|
Homepage string `json:"homepage,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`)
|
var nameMatch = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`)
|
||||||
|
})
|
||||||
|
|
||||||
type ReadSeekAt interface {
|
type ReadSeekAt interface {
|
||||||
io.Reader
|
io.Reader
|
||||||
@@ -237,7 +240,7 @@ func ParsePackage(r ReadSeekAt, optVersion ...string) (*PackageInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !nameMatch.MatchString(cj.Name) {
|
if !nameMatch().MatchString(cj.Name) {
|
||||||
return nil, ErrInvalidName
|
return nil, ErrInvalidName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,36 @@
|
|||||||
package conan
|
package conan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
patternAuthor = compilePattern("author")
|
patternAuthor *regexp.Regexp
|
||||||
patternHomepage = compilePattern("homepage")
|
patternHomepage *regexp.Regexp
|
||||||
patternURL = compilePattern("url")
|
patternURL *regexp.Regexp
|
||||||
patternLicense = compilePattern("license")
|
patternLicense *regexp.Regexp
|
||||||
patternDescription = compilePattern("description")
|
patternDescription *regexp.Regexp
|
||||||
patternTopics = regexp.MustCompile(`(?im)^\s*topics\s*=\s*\((.+)\)`)
|
patternTopics *regexp.Regexp
|
||||||
patternTopicList = regexp.MustCompile(`\s*['"](.+?)['"]\s*,?`)
|
patternTopicList *regexp.Regexp
|
||||||
)
|
namePattern *regexp.Regexp
|
||||||
|
revisionPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.patternAuthor = compilePattern("author")
|
||||||
|
ret.patternHomepage = compilePattern("homepage")
|
||||||
|
ret.patternURL = compilePattern("url")
|
||||||
|
ret.patternLicense = compilePattern("license")
|
||||||
|
ret.patternDescription = compilePattern("description")
|
||||||
|
ret.patternTopics = regexp.MustCompile(`(?im)^\s*topics\s*=\s*\((.+)\)`)
|
||||||
|
ret.patternTopicList = regexp.MustCompile(`\s*['"](.+?)['"]\s*,?`)
|
||||||
|
ret.namePattern = regexp.MustCompile(fmt.Sprintf(`^[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{%d,%d}$`, minChars-1, maxChars-1))
|
||||||
|
ret.revisionPattern = regexp.MustCompile(fmt.Sprintf(`^[a-zA-Z0-9]{1,%d}$`, maxChars))
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
func compilePattern(name string) *regexp.Regexp {
|
func compilePattern(name string) *regexp.Regexp {
|
||||||
return regexp.MustCompile(`(?im)^\s*` + name + `\s*=\s*['"\(](.+)['"\)]`)
|
return regexp.MustCompile(`(?im)^\s*` + name + `\s*=\s*['"\(](.+)['"\)]`)
|
||||||
@@ -31,29 +47,29 @@ func ParseConanfile(r io.Reader) (*Metadata, error) {
|
|||||||
|
|
||||||
metadata := &Metadata{}
|
metadata := &Metadata{}
|
||||||
|
|
||||||
m := patternAuthor.FindSubmatch(buf)
|
m := globalVars().patternAuthor.FindSubmatch(buf)
|
||||||
if len(m) > 1 && len(m[1]) > 0 {
|
if len(m) > 1 && len(m[1]) > 0 {
|
||||||
metadata.Author = string(m[1])
|
metadata.Author = string(m[1])
|
||||||
}
|
}
|
||||||
m = patternHomepage.FindSubmatch(buf)
|
m = globalVars().patternHomepage.FindSubmatch(buf)
|
||||||
if len(m) > 1 && len(m[1]) > 0 {
|
if len(m) > 1 && len(m[1]) > 0 {
|
||||||
metadata.ProjectURL = string(m[1])
|
metadata.ProjectURL = string(m[1])
|
||||||
}
|
}
|
||||||
m = patternURL.FindSubmatch(buf)
|
m = globalVars().patternURL.FindSubmatch(buf)
|
||||||
if len(m) > 1 && len(m[1]) > 0 {
|
if len(m) > 1 && len(m[1]) > 0 {
|
||||||
metadata.RepositoryURL = string(m[1])
|
metadata.RepositoryURL = string(m[1])
|
||||||
}
|
}
|
||||||
m = patternLicense.FindSubmatch(buf)
|
m = globalVars().patternLicense.FindSubmatch(buf)
|
||||||
if len(m) > 1 && len(m[1]) > 0 {
|
if len(m) > 1 && len(m[1]) > 0 {
|
||||||
metadata.License = strings.ReplaceAll(strings.ReplaceAll(string(m[1]), "'", ""), "\"", "")
|
metadata.License = strings.ReplaceAll(strings.ReplaceAll(string(m[1]), "'", ""), "\"", "")
|
||||||
}
|
}
|
||||||
m = patternDescription.FindSubmatch(buf)
|
m = globalVars().patternDescription.FindSubmatch(buf)
|
||||||
if len(m) > 1 && len(m[1]) > 0 {
|
if len(m) > 1 && len(m[1]) > 0 {
|
||||||
metadata.Description = string(m[1])
|
metadata.Description = string(m[1])
|
||||||
}
|
}
|
||||||
m = patternTopics.FindSubmatch(buf)
|
m = globalVars().patternTopics.FindSubmatch(buf)
|
||||||
if len(m) > 1 && len(m[1]) > 0 {
|
if len(m) > 1 && len(m[1]) > 0 {
|
||||||
m2 := patternTopicList.FindAllSubmatch(m[1], -1)
|
m2 := globalVars().patternTopicList.FindAllSubmatch(m[1], -1)
|
||||||
if len(m2) > 0 {
|
if len(m2) > 0 {
|
||||||
metadata.Keywords = make([]string, 0, len(m2))
|
metadata.Keywords = make([]string, 0, len(m2))
|
||||||
for _, g := range m2 {
|
for _, g := range m2 {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package conan
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
@@ -21,12 +20,7 @@ const (
|
|||||||
DefaultRevision = "0"
|
DefaultRevision = "0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ErrValidation = util.NewInvalidArgumentErrorf("could not validate one or more reference fields")
|
||||||
namePattern = regexp.MustCompile(fmt.Sprintf(`^[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{%d,%d}$`, minChars-1, maxChars-1))
|
|
||||||
revisionPattern = regexp.MustCompile(fmt.Sprintf(`^[a-zA-Z0-9]{1,%d}$`, maxChars))
|
|
||||||
|
|
||||||
ErrValidation = util.NewInvalidArgumentErrorf("could not validate one or more reference fields")
|
|
||||||
)
|
|
||||||
|
|
||||||
// RecipeReference represents a recipe <Name>/<Version>@<User>/<Channel>#<Revision>
|
// RecipeReference represents a recipe <Name>/<Version>@<User>/<Channel>#<Revision>
|
||||||
type RecipeReference struct {
|
type RecipeReference struct {
|
||||||
@@ -51,7 +45,7 @@ func NewRecipeReference(name, version, user, channel, revision string) (*RecipeR
|
|||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
|
|
||||||
if !namePattern.MatchString(name) {
|
if !globalVars().namePattern.MatchString(name) {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,13 +53,13 @@ func NewRecipeReference(name, version, user, channel, revision string) (*RecipeR
|
|||||||
if v == "" {
|
if v == "" {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
if user != "" && !namePattern.MatchString(user) {
|
if user != "" && !globalVars().namePattern.MatchString(user) {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
if channel != "" && !namePattern.MatchString(channel) {
|
if channel != "" && !globalVars().namePattern.MatchString(channel) {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
if revision != "" && !revisionPattern.MatchString(revision) {
|
if revision != "" && !globalVars().revisionPattern.MatchString(revision) {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,10 +118,10 @@ func NewPackageReference(recipe *RecipeReference, reference, revision string) (*
|
|||||||
if recipe == nil {
|
if recipe == nil {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
if reference == "" || !revisionPattern.MatchString(reference) {
|
if reference == "" || !globalVars().revisionPattern.MatchString(reference) {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
if revision != "" && !revisionPattern.MatchString(revision) {
|
if revision != "" && !globalVars().revisionPattern.MatchString(revision) {
|
||||||
return nil, ErrValidation
|
return nil, ErrValidation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/base"
|
"gitea.dev/modules/base"
|
||||||
"gitea.dev/modules/packages"
|
"gitea.dev/modules/packages"
|
||||||
@@ -34,12 +35,19 @@ var (
|
|||||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
fieldPattern = regexp.MustCompile(`\A\S+:`)
|
fieldPattern *regexp.Regexp
|
||||||
namePattern = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9\.]*[a-zA-Z0-9]\z`)
|
namePattern *regexp.Regexp
|
||||||
versionPattern = regexp.MustCompile(`\A[0-9]+(?:[.\-][0-9]+)+\z`)
|
versionPattern *regexp.Regexp
|
||||||
authorReplacePattern = regexp.MustCompile(`[\[\(].+?[\]\)]`)
|
authorReplacePattern *regexp.Regexp
|
||||||
)
|
},
|
||||||
|
) {
|
||||||
|
ret.fieldPattern = regexp.MustCompile(`\A\S+:`)
|
||||||
|
ret.namePattern = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9\.]*[a-zA-Z0-9]\z`)
|
||||||
|
ret.versionPattern = regexp.MustCompile(`\A[0-9]+(?:[.\-][0-9]+)+\z`)
|
||||||
|
ret.authorReplacePattern = regexp.MustCompile(`[\[\(].+?[\]\)]`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// Package represents a CRAN package
|
// Package represents a CRAN package
|
||||||
type Package struct {
|
type Package struct {
|
||||||
@@ -162,7 +170,7 @@ func ParseDescription(r io.Reader) (*Package, error) {
|
|||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !fieldPattern.MatchString(line) {
|
if !globalVars().fieldPattern.MatchString(line) {
|
||||||
b.WriteRune(' ')
|
b.WriteRune(' ')
|
||||||
b.WriteString(line)
|
b.WriteString(line)
|
||||||
continue
|
continue
|
||||||
@@ -202,12 +210,12 @@ func setField(p *Package, data string) error {
|
|||||||
|
|
||||||
switch name {
|
switch name {
|
||||||
case "Package":
|
case "Package":
|
||||||
if !namePattern.MatchString(value) {
|
if !globalVars().namePattern.MatchString(value) {
|
||||||
return ErrInvalidName
|
return ErrInvalidName
|
||||||
}
|
}
|
||||||
p.Name = value
|
p.Name = value
|
||||||
case "Version":
|
case "Version":
|
||||||
if !versionPattern.MatchString(value) {
|
if !globalVars().versionPattern.MatchString(value) {
|
||||||
return ErrInvalidVersion
|
return ErrInvalidVersion
|
||||||
}
|
}
|
||||||
p.Version = value
|
p.Version = value
|
||||||
@@ -220,7 +228,7 @@ func setField(p *Package, data string) error {
|
|||||||
case "License":
|
case "License":
|
||||||
p.Metadata.License = value
|
p.Metadata.License = value
|
||||||
case "Author":
|
case "Author":
|
||||||
p.Metadata.Authors = splitAndTrim(authorReplacePattern.ReplaceAllString(value, ""))
|
p.Metadata.Authors = splitAndTrim(globalVars().authorReplacePattern.ReplaceAllString(value, ""))
|
||||||
case "Depends":
|
case "Depends":
|
||||||
p.Metadata.Depends = splitAndTrim(value)
|
p.Metadata.Depends = splitAndTrim(value)
|
||||||
case "Imports":
|
case "Imports":
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
"gitea.dev/modules/validation"
|
"gitea.dev/modules/validation"
|
||||||
@@ -42,7 +43,9 @@ const (
|
|||||||
PropertySymbolID = "nuget.symbol.id"
|
PropertySymbolID = "nuget.symbol.id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var idmatch = regexp.MustCompile(`\A\w+(?:[.-]\w+)*\z`)
|
var idmatch = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`\A\w+(?:[.-]\w+)*\z`)
|
||||||
|
})
|
||||||
|
|
||||||
const maxNuspecFileSize = 3 * 1024 * 1024
|
const maxNuspecFileSize = 3 * 1024 * 1024
|
||||||
|
|
||||||
@@ -171,7 +174,7 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !idmatch.MatchString(p.Metadata.ID) {
|
if !idmatch().MatchString(p.Metadata.ID) {
|
||||||
return nil, ErrNuspecInvalidID
|
return nil, ErrNuspecInvalidID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
"gitea.dev/modules/validation"
|
"gitea.dev/modules/validation"
|
||||||
@@ -24,7 +25,9 @@ var (
|
|||||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
var namePattern = regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`)
|
var namePattern = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`)
|
||||||
|
})
|
||||||
|
|
||||||
// https://github.com/dart-lang/pub-dev/blob/4d582302a8d10152a5cd6129f65bf4f4dbca239d/pkg/pub_package_reader/lib/pub_package_reader.dart#L143
|
// https://github.com/dart-lang/pub-dev/blob/4d582302a8d10152a5cd6129f65bf4f4dbca239d/pkg/pub_package_reader/lib/pub_package_reader.dart#L143
|
||||||
const maxPubspecFileSize = 128 * 1024
|
const maxPubspecFileSize = 128 * 1024
|
||||||
@@ -118,7 +121,7 @@ func ParsePubspecMetadata(r io.Reader) (*Package, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !namePattern.MatchString(p.Name) {
|
if !namePattern().MatchString(p.Name) {
|
||||||
return nil, ErrInvalidName
|
return nil, ErrInvalidName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/packages"
|
"gitea.dev/modules/packages"
|
||||||
@@ -19,14 +20,21 @@ import (
|
|||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
manifestPattern *regexp.Regexp
|
||||||
|
toolsVersionPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.manifestPattern = regexp.MustCompile(`\APackage(?:@swift-(\d+(?:\.\d+)?(?:\.\d+)?))?\.swift\z`)
|
||||||
|
ret.toolsVersionPattern = regexp.MustCompile(`\A// swift-tools-version:(\d+(?:\.\d+)?(?:\.\d+)?)`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingManifestFile = util.NewInvalidArgumentErrorf("Package.swift file is missing")
|
ErrMissingManifestFile = util.NewInvalidArgumentErrorf("Package.swift file is missing")
|
||||||
ErrManifestFileTooLarge = util.NewInvalidArgumentErrorf("Package.swift file is too large")
|
ErrManifestFileTooLarge = util.NewInvalidArgumentErrorf("Package.swift file is too large")
|
||||||
ErrManifestFilesTooLarge = util.NewInvalidArgumentErrorf("Package.swift files are too large")
|
ErrManifestFilesTooLarge = util.NewInvalidArgumentErrorf("Package.swift files are too large")
|
||||||
ErrInvalidManifestVersion = util.NewInvalidArgumentErrorf("manifest version is invalid")
|
ErrInvalidManifestVersion = util.NewInvalidArgumentErrorf("manifest version is invalid")
|
||||||
|
|
||||||
manifestPattern = regexp.MustCompile(`\APackage(?:@swift-(\d+(?:\.\d+)?(?:\.\d+)?))?\.swift\z`)
|
|
||||||
toolsVersionPattern = regexp.MustCompile(`\A// swift-tools-version:(\d+(?:\.\d+)?(?:\.\d+)?)`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -133,7 +141,7 @@ func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
|
|||||||
var manifestFiles []*zip.File
|
var manifestFiles []*zip.File
|
||||||
manifestDir, manifestDepth := "", 0
|
manifestDir, manifestDepth := "", 0
|
||||||
for _, file := range zr.File {
|
for _, file := range zr.File {
|
||||||
if strings.HasSuffix(file.Name, "/") || !manifestPattern.MatchString(path.Base(file.Name)) {
|
if strings.HasSuffix(file.Name, "/") || !globalVars().manifestPattern.MatchString(path.Base(file.Name)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dir, depth := path.Dir(file.Name), strings.Count(file.Name, "/")
|
dir, depth := path.Dir(file.Name), strings.Count(file.Name, "/")
|
||||||
@@ -156,7 +164,7 @@ func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range manifestFiles {
|
for _, file := range manifestFiles {
|
||||||
manifestMatch := manifestPattern.FindStringSubmatch(path.Base(file.Name))
|
manifestMatch := globalVars().manifestPattern.FindStringSubmatch(path.Base(file.Name))
|
||||||
|
|
||||||
if file.UncompressedSize64 > maxManifestFileSize {
|
if file.UncompressedSize64 > maxManifestFileSize {
|
||||||
return nil, ErrManifestFileTooLarge
|
return nil, ErrManifestFileTooLarge
|
||||||
@@ -190,7 +198,7 @@ func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
|
|||||||
Content: string(content),
|
Content: string(content),
|
||||||
}
|
}
|
||||||
|
|
||||||
toolsMatch := toolsVersionPattern.FindStringSubmatch(manifest.Content)
|
toolsMatch := globalVars().toolsVersionPattern.FindStringSubmatch(manifest.Content)
|
||||||
if len(toolsMatch) == 2 {
|
if len(toolsMatch) == 2 {
|
||||||
v, err := version.NewSemver(toolsMatch[1])
|
v, err := version.NewSemver(toolsMatch[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
packages_module "gitea.dev/modules/packages"
|
packages_module "gitea.dev/modules/packages"
|
||||||
|
|
||||||
@@ -31,20 +32,25 @@ var (
|
|||||||
|
|
||||||
const maxReadmeSize = 1024 * 1024 // larger READMEs are not stored, they would be rendered on every page view
|
const maxReadmeSize = 1024 * 1024 // larger READMEs are not stored, they would be rendered on every page view
|
||||||
|
|
||||||
// same rules as Terraform's module source address parser (github.com/hashicorp/terraform-registry-address)
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
var (
|
nameRe *regexp.Regexp
|
||||||
nameRe = regexp.MustCompile(`\A[0-9A-Za-z](?:[0-9A-Za-z_-]{0,62}[0-9A-Za-z])?\z`)
|
systemRe *regexp.Regexp
|
||||||
systemRe = regexp.MustCompile(`\A[0-9a-z]{1,64}\z`)
|
},
|
||||||
)
|
) {
|
||||||
|
// same rules as Terraform's module source address parser (github.com/hashicorp/terraform-registry-address)
|
||||||
|
ret.nameRe = regexp.MustCompile(`\A[0-9A-Za-z](?:[0-9A-Za-z_-]{0,62}[0-9A-Za-z])?\z`)
|
||||||
|
ret.systemRe = regexp.MustCompile(`\A[0-9a-z]{1,64}\z`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
func ValidateAddress(namespace, name, provider string) error {
|
func ValidateAddress(namespace, name, provider string) error {
|
||||||
if !nameRe.MatchString(namespace) {
|
if !globalVars().nameRe.MatchString(namespace) {
|
||||||
return ErrInvalidNamespace
|
return ErrInvalidNamespace
|
||||||
}
|
}
|
||||||
if !nameRe.MatchString(name) {
|
if !globalVars().nameRe.MatchString(name) {
|
||||||
return ErrInvalidName
|
return ErrInvalidName
|
||||||
}
|
}
|
||||||
if !systemRe.MatchString(provider) {
|
if !globalVars().systemRe.MatchString(provider) {
|
||||||
return ErrInvalidProvider
|
return ErrInvalidProvider
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -20,10 +20,19 @@ import (
|
|||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
validNamePattern *regexp.Regexp
|
||||||
|
mentionPattern *regexp.Regexp
|
||||||
|
issueNumericPattern *regexp.Regexp
|
||||||
|
issueAlphanumericPattern *regexp.Regexp
|
||||||
|
crossReferenceIssueNumericPattern *regexp.Regexp
|
||||||
|
crossReferenceCommitPattern *regexp.Regexp
|
||||||
|
timeLogPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
// validNamePattern performs only the most basic validation for user or repository names
|
// validNamePattern performs only the most basic validation for user or repository names
|
||||||
// Repository name should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.
|
// Repository name should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.
|
||||||
validNamePattern = regexp.MustCompile(`^[a-z0-9_.-]+$`)
|
ret.validNamePattern = regexp.MustCompile(`^[a-z0-9_.-]+$`)
|
||||||
|
|
||||||
// NOTE: All below regex matching do not perform any extra validation.
|
// NOTE: All below regex matching do not perform any extra validation.
|
||||||
// Thus a link is produced even if the linked entity does not exist.
|
// Thus a link is produced even if the linked entity does not exist.
|
||||||
@@ -31,22 +40,25 @@ var (
|
|||||||
// TODO: fix invalid linking issue
|
// TODO: fix invalid linking issue
|
||||||
|
|
||||||
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
|
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
|
||||||
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[-\w][-.\w]*?|@[-\w][-.\w]*?/[-\w][-.\w]*?)(?:\s|$|[:,;.?!](\s|$)|'|\)|\])`)
|
ret.mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[-\w][-.\w]*?|@[-\w][-.\w]*?/[-\w][-.\w]*?)(?:\s|$|[:,;.?!](\s|$)|'|\)|\])`)
|
||||||
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
|
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
|
||||||
// TODO: REFERENCE-REGEXP-PATTERN: findValidReferenceIndex checks a non-ASCII trailing character
|
// TODO: REFERENCE-REGEXP-PATTERN: findValidReferenceIndex checks a non-ASCII trailing character
|
||||||
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\"|[^\x00-\x7f])([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$|[^\x00-\x7f])`)
|
ret.issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\"|[^\x00-\x7f])([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$|[^\x00-\x7f])`)
|
||||||
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
|
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
|
||||||
issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\'|,)`)
|
ret.issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\'|,)`)
|
||||||
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
|
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
|
||||||
// e.g. org/repo#12345
|
// e.g. org/repo#12345
|
||||||
// TODO: REFERENCE-REGEXP-PATTERN: findValidReferenceIndex checks a non-ASCII trailing character
|
// TODO: REFERENCE-REGEXP-PATTERN: findValidReferenceIndex checks a non-ASCII trailing character
|
||||||
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|[^\x00-\x7f])([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$|[^\x00-\x7f])`)
|
ret.crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|[^\x00-\x7f])([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$|[^\x00-\x7f])`)
|
||||||
// crossReferenceCommitPattern matches a string that references a commit in a different repository
|
// crossReferenceCommitPattern matches a string that references a commit in a different repository
|
||||||
// e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters)
|
// e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters)
|
||||||
crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,64})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
ret.crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,64})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
||||||
// timeLogPattern matches string for time tracking
|
// timeLogPattern matches string for time tracking
|
||||||
timeLogPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@([0-9]+([\.,][0-9]+)?(w|d|m|h))+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
ret.timeLogPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@([0-9]+([\.,][0-9]+)?(w|d|m|h))+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
|
var (
|
||||||
issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp
|
issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp
|
||||||
issueKeywordWindow int
|
issueKeywordWindow int
|
||||||
issueKeywordsOnce sync.Once
|
issueKeywordsOnce sync.Once
|
||||||
@@ -225,7 +237,7 @@ func FindAllMentionsBytes(content []byte) []RefSpan {
|
|||||||
ret := make([]RefSpan, 0, 5)
|
ret := make([]RefSpan, 0, 5)
|
||||||
pos := 0
|
pos := 0
|
||||||
for {
|
for {
|
||||||
match := mentionPattern.FindSubmatchIndex(content[pos:])
|
match := globalVars().mentionPattern.FindSubmatchIndex(content[pos:])
|
||||||
if match == nil {
|
if match == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -238,7 +250,7 @@ func FindAllMentionsBytes(content []byte) []RefSpan {
|
|||||||
// FindFirstMentionBytes matches the first mention in then given content
|
// FindFirstMentionBytes matches the first mention in then given content
|
||||||
// and returns the location of the unvalidated username, including the @ prefix.
|
// and returns the location of the unvalidated username, including the @ prefix.
|
||||||
func FindFirstMentionBytes(content []byte) (bool, RefSpan) {
|
func FindFirstMentionBytes(content []byte) (bool, RefSpan) {
|
||||||
mention := mentionPattern.FindSubmatchIndex(content)
|
mention := globalVars().mentionPattern.FindSubmatchIndex(content)
|
||||||
if mention == nil {
|
if mention == nil {
|
||||||
return false, RefSpan{}
|
return false, RefSpan{}
|
||||||
}
|
}
|
||||||
@@ -365,10 +377,10 @@ func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool
|
|||||||
contentBytes := util.UnsafeStringToBytes(content)
|
contentBytes := util.UnsafeStringToBytes(content)
|
||||||
var match []int
|
var match []int
|
||||||
if !crossLinkOnly {
|
if !crossLinkOnly {
|
||||||
match = findValidReferenceIndex(issueNumericPattern, contentBytes, 0)
|
match = findValidReferenceIndex(globalVars().issueNumericPattern, contentBytes, 0)
|
||||||
}
|
}
|
||||||
if match == nil {
|
if match == nil {
|
||||||
if match = findValidReferenceIndex(crossReferenceIssueNumericPattern, contentBytes, 0); match == nil {
|
if match = findValidReferenceIndex(globalVars().crossReferenceIssueNumericPattern, contentBytes, 0); match == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,7 +402,7 @@ func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool
|
|||||||
|
|
||||||
// FindRenderizableCommitCrossReference returns the first unvalidated commit cross reference found in a string.
|
// FindRenderizableCommitCrossReference returns the first unvalidated commit cross reference found in a string.
|
||||||
func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableReference) {
|
func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableReference) {
|
||||||
m := crossReferenceCommitPattern.FindStringSubmatchIndex(content)
|
m := globalVars().crossReferenceCommitPattern.FindStringSubmatchIndex(content)
|
||||||
if len(m) < 8 {
|
if len(m) < 8 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -436,7 +448,7 @@ func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) *Re
|
|||||||
|
|
||||||
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
|
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
|
||||||
func FindRenderizableReferenceAlphanumeric(content string) *RenderizableReference {
|
func FindRenderizableReferenceAlphanumeric(content string) *RenderizableReference {
|
||||||
match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
|
match := globalVars().issueAlphanumericPattern.FindStringSubmatchIndex(content)
|
||||||
if match == nil {
|
if match == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -460,7 +472,7 @@ func findAllIssueReferencesBytes(content []byte, links []string, originalContent
|
|||||||
// trailing spaces (\s#ref,\s), so if we get two consecutive references, the space
|
// trailing spaces (\s#ref,\s), so if we get two consecutive references, the space
|
||||||
// from the second reference will be "eaten" by the first one:
|
// from the second reference will be "eaten" by the first one:
|
||||||
// ...\s#ref1\s#ref2\s... --> ...`\s#ref1\s`, (not) `#ref2,\s...`
|
// ...\s#ref1\s#ref2\s... --> ...`\s#ref1\s`, (not) `#ref2,\s...`
|
||||||
for _, pattern := range []*regexp.Regexp{issueNumericPattern, crossReferenceIssueNumericPattern} {
|
for _, pattern := range []*regexp.Regexp{globalVars().issueNumericPattern, globalVars().crossReferenceIssueNumericPattern} {
|
||||||
for match := findValidReferenceIndex(pattern, content, 0); match != nil; match = findValidReferenceIndex(pattern, content, match[3]) {
|
for match := findValidReferenceIndex(pattern, content, 0); match != nil; match = findValidReferenceIndex(pattern, content, match[3]) {
|
||||||
if ref := getCrossReference(content, match[2], match[3], false, false); ref != nil {
|
if ref := getCrossReference(content, match[2], match[3], false, false); ref != nil {
|
||||||
ret = append(ret, ref)
|
ret = append(ret, ref)
|
||||||
@@ -523,7 +535,7 @@ func findAllIssueReferencesBytes(content []byte, links []string, originalContent
|
|||||||
pos := 0
|
pos := 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
match := timeLogPattern.FindSubmatchIndex(content[pos:])
|
match := globalVars().timeLogPattern.FindSubmatchIndex(content[pos:])
|
||||||
if match == nil {
|
if match == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -586,7 +598,7 @@ func getCrossReference(content []byte, start, end int, fromLink, prOnly bool) *r
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
owner, name := parts[0], parts[1]
|
owner, name := parts[0], parts[1]
|
||||||
if !validNamePattern.MatchString(owner) || !validNamePattern.MatchString(name) {
|
if !globalVars().validNamePattern.MatchString(owner) || !globalVars().validNamePattern.MatchString(name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
action, location := findActionKeywords(content, start)
|
action, location := findActionKeywords(content, start)
|
||||||
|
|||||||
@@ -483,11 +483,11 @@ func TestRegExp_mentionPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range trueTestCases {
|
for _, testCase := range trueTestCases {
|
||||||
found := mentionPattern.FindStringSubmatch(testCase.pat)
|
found := globalVars().mentionPattern.FindStringSubmatch(testCase.pat)
|
||||||
assert.Equal(t, testCase.exp, found[1])
|
assert.Equal(t, testCase.exp, found[1])
|
||||||
}
|
}
|
||||||
for _, testCase := range falseTestCases {
|
for _, testCase := range falseTestCases {
|
||||||
res := mentionPattern.MatchString(testCase)
|
res := globalVars().mentionPattern.MatchString(testCase)
|
||||||
assert.False(t, res, "[%s] should be false", testCase)
|
assert.False(t, res, "[%s] should be false", testCase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,10 +515,10 @@ func TestRegExp_issueNumericPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range trueTestCases {
|
for _, testCase := range trueTestCases {
|
||||||
assert.True(t, issueNumericPattern.MatchString(testCase))
|
assert.True(t, globalVars().issueNumericPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
for _, testCase := range falseTestCases {
|
for _, testCase := range falseTestCases {
|
||||||
assert.False(t, issueNumericPattern.MatchString(testCase))
|
assert.False(t, globalVars().issueNumericPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,10 +552,10 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range trueTestCases {
|
for _, testCase := range trueTestCases {
|
||||||
assert.True(t, issueAlphanumericPattern.MatchString(testCase))
|
assert.True(t, globalVars().issueAlphanumericPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
for _, testCase := range falseTestCases {
|
for _, testCase := range falseTestCases {
|
||||||
assert.False(t, issueAlphanumericPattern.MatchString(testCase))
|
assert.False(t, globalVars().issueAlphanumericPattern.MatchString(testCase))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
)
|
)
|
||||||
@@ -20,7 +21,15 @@ const (
|
|||||||
|
|
||||||
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
|
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
|
||||||
|
|
||||||
var escapeRegex = regexp.MustCompile(escapeRegexpString)
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
escapeRegex *regexp.Regexp
|
||||||
|
extensionReg *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.escapeRegex = regexp.MustCompile(escapeRegexpString)
|
||||||
|
ret.extensionReg = regexp.MustCompile(`^(\.[-\w]+)+$`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
func CollectEnvConfigKeys() (keys []string) {
|
func CollectEnvConfigKeys() (keys []string) {
|
||||||
for _, env := range os.Environ() {
|
for _, env := range os.Environ() {
|
||||||
@@ -47,7 +56,7 @@ func ClearEnvConfigKeys() {
|
|||||||
func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
|
func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
|
||||||
inKey := false
|
inKey := false
|
||||||
last := 0
|
last := 0
|
||||||
escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1)
|
escapeStringIndices := globalVars().escapeRegex.FindAllStringIndex(encoded, -1)
|
||||||
for _, unescapeIdx := range escapeStringIndices {
|
for _, unescapeIdx := range escapeStringIndices {
|
||||||
preceding := encoded[last:unescapeIdx[0]]
|
preceding := encoded[last:unescapeIdx[0]]
|
||||||
if !inKey {
|
if !inKey {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package setting
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -222,14 +221,10 @@ func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerR
|
|||||||
return rule, true
|
return rule, true
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensionReg = sync.OnceValue(func() *regexp.Regexp {
|
|
||||||
return regexp.MustCompile(`^(\.[-\w]+)+$`)
|
|
||||||
})
|
|
||||||
|
|
||||||
func fileExtensionsToPatterns(sectionName string, extensions []string) []string {
|
func fileExtensionsToPatterns(sectionName string, extensions []string) []string {
|
||||||
patterns := make([]string, 0, len(extensions))
|
patterns := make([]string, 0, len(extensions))
|
||||||
for _, extension := range extensions {
|
for _, extension := range extensions {
|
||||||
if !extensionReg().MatchString(extension) {
|
if !globalVars().extensionReg.MatchString(extension) {
|
||||||
log.Warn("Config section %s file extension %s is invalid. Extension ignored", sectionName, extension)
|
log.Warn("Config section %s file extension %s is invalid. Extension ignored", sectionName, extension)
|
||||||
} else {
|
} else {
|
||||||
patterns = append(patterns, "*"+extension)
|
patterns = append(patterns, "*"+extension)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestSubjectBodySeparator(t *testing.T) {
|
|||||||
{"Multiple\n---\n-------\n---\nSeparators", "Multiple\n", "\n-------\n---\nSeparators"},
|
{"Multiple\n---\n-------\n---\nSeparators", "Multiple\n", "\n-------\n---\nSeparators"},
|
||||||
{"Insufficient\n--\nSeparators", "", "Insufficient\n--\nSeparators"},
|
{"Insufficient\n--\nSeparators", "", "Insufficient\n--\nSeparators"},
|
||||||
} {
|
} {
|
||||||
loc := mailSubjectSplit.FindStringIndex(testCase.input)
|
loc := globalVars().mailSubjectSplit.FindStringIndex(testCase.input)
|
||||||
if loc == nil {
|
if loc == nil {
|
||||||
assert.Empty(t, testCase.subject, "no subject found, but one expected")
|
assert.Empty(t, testCase.subject, "no subject found, but one expected")
|
||||||
assert.Equal(t, testCase.body, testCase.input)
|
assert.Equal(t, testCase.body, testCase.input)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
texttemplate "text/template"
|
texttemplate "text/template"
|
||||||
|
|
||||||
@@ -81,10 +82,31 @@ type templateErrorPrettier struct {
|
|||||||
assets *assetfs.LayeredFS
|
assets *assetfs.LayeredFS
|
||||||
}
|
}
|
||||||
|
|
||||||
var reGenericTemplateError = regexp.MustCompile(`^template: (.*):([0-9]+): (.*)`)
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
reGenericTemplateError *regexp.Regexp
|
||||||
|
reFuncNotDefinedError *regexp.Regexp
|
||||||
|
reUnexpectedOperandError *regexp.Regexp
|
||||||
|
reExpectedEndError *regexp.Regexp
|
||||||
|
reTemplateExecutingError *regexp.Regexp
|
||||||
|
reTemplateExecutingErrorMsg *regexp.Regexp
|
||||||
|
mailSubjectSplit *regexp.Regexp
|
||||||
|
codeMatcher *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.reGenericTemplateError = regexp.MustCompile(`^template: (.*):([0-9]+): (.*)`)
|
||||||
|
ret.reFuncNotDefinedError = regexp.MustCompile(`^template: (.*):([0-9]+): (function "(.*)" not defined)`)
|
||||||
|
ret.reUnexpectedOperandError = regexp.MustCompile(`^template: (.*):([0-9]+): (unexpected "(.*)" in operand)`)
|
||||||
|
ret.reExpectedEndError = regexp.MustCompile(`^template: (.*):([0-9]+): (expected end; found (.*))`)
|
||||||
|
ret.reTemplateExecutingError = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): (executing .*)`)
|
||||||
|
ret.reTemplateExecutingErrorMsg = regexp.MustCompile(`^executing "(.*)" at <(.*)>: `)
|
||||||
|
ret.mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
|
||||||
|
// Match text that is between back ticks.
|
||||||
|
ret.codeMatcher = regexp.MustCompile("`([^`]+)`")
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
func (p *templateErrorPrettier) handleGenericTemplateError(err error) string {
|
func (p *templateErrorPrettier) handleGenericTemplateError(err error) string {
|
||||||
groups := reGenericTemplateError.FindStringSubmatch(err.Error())
|
groups := globalVars().reGenericTemplateError.FindStringSubmatch(err.Error())
|
||||||
if len(groups) != 4 {
|
if len(groups) != 4 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -92,10 +114,8 @@ func (p *templateErrorPrettier) handleGenericTemplateError(err error) string {
|
|||||||
return p.makeDetailedError(message, tmplName, lineStr, "", "")
|
return p.makeDetailedError(message, tmplName, lineStr, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
var reFuncNotDefinedError = regexp.MustCompile(`^template: (.*):([0-9]+): (function "(.*)" not defined)`)
|
|
||||||
|
|
||||||
func (p *templateErrorPrettier) handleFuncNotDefinedError(err error) string {
|
func (p *templateErrorPrettier) handleFuncNotDefinedError(err error) string {
|
||||||
groups := reFuncNotDefinedError.FindStringSubmatch(err.Error())
|
groups := globalVars().reFuncNotDefinedError.FindStringSubmatch(err.Error())
|
||||||
if len(groups) != 5 {
|
if len(groups) != 5 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -104,10 +124,8 @@ func (p *templateErrorPrettier) handleFuncNotDefinedError(err error) string {
|
|||||||
return p.makeDetailedError(message, tmplName, lineStr, "", funcName)
|
return p.makeDetailedError(message, tmplName, lineStr, "", funcName)
|
||||||
}
|
}
|
||||||
|
|
||||||
var reUnexpectedOperandError = regexp.MustCompile(`^template: (.*):([0-9]+): (unexpected "(.*)" in operand)`)
|
|
||||||
|
|
||||||
func (p *templateErrorPrettier) handleUnexpectedOperandError(err error) string {
|
func (p *templateErrorPrettier) handleUnexpectedOperandError(err error) string {
|
||||||
groups := reUnexpectedOperandError.FindStringSubmatch(err.Error())
|
groups := globalVars().reUnexpectedOperandError.FindStringSubmatch(err.Error())
|
||||||
if len(groups) != 5 {
|
if len(groups) != 5 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -116,10 +134,8 @@ func (p *templateErrorPrettier) handleUnexpectedOperandError(err error) string {
|
|||||||
return p.makeDetailedError(message, tmplName, lineStr, "", unexpected)
|
return p.makeDetailedError(message, tmplName, lineStr, "", unexpected)
|
||||||
}
|
}
|
||||||
|
|
||||||
var reExpectedEndError = regexp.MustCompile(`^template: (.*):([0-9]+): (expected end; found (.*))`)
|
|
||||||
|
|
||||||
func (p *templateErrorPrettier) handleExpectedEndError(err error) string {
|
func (p *templateErrorPrettier) handleExpectedEndError(err error) string {
|
||||||
groups := reExpectedEndError.FindStringSubmatch(err.Error())
|
groups := globalVars().reExpectedEndError.FindStringSubmatch(err.Error())
|
||||||
if len(groups) != 5 {
|
if len(groups) != 5 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -127,16 +143,11 @@ func (p *templateErrorPrettier) handleExpectedEndError(err error) string {
|
|||||||
return p.makeDetailedError(message, tmplName, lineStr, "", unexpected)
|
return p.makeDetailedError(message, tmplName, lineStr, "", unexpected)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
reTemplateExecutingError = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): (executing .*)`)
|
|
||||||
reTemplateExecutingErrorMsg = regexp.MustCompile(`^executing "(.*)" at <(.*)>: `)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *templateErrorPrettier) handleTemplateRenderingError(err error) string {
|
func (p *templateErrorPrettier) handleTemplateRenderingError(err error) string {
|
||||||
if groups := reTemplateExecutingError.FindStringSubmatch(err.Error()); len(groups) > 0 {
|
if groups := globalVars().reTemplateExecutingError.FindStringSubmatch(err.Error()); len(groups) > 0 {
|
||||||
tmplName, lineStr, posStr, msgPart := groups[1], groups[2], groups[3], groups[4]
|
tmplName, lineStr, posStr, msgPart := groups[1], groups[2], groups[3], groups[4]
|
||||||
target := ""
|
target := ""
|
||||||
if groups = reTemplateExecutingErrorMsg.FindStringSubmatch(msgPart); len(groups) > 0 {
|
if groups = globalVars().reTemplateExecutingErrorMsg.FindStringSubmatch(msgPart); len(groups) > 0 {
|
||||||
target = groups[2]
|
target = groups[2]
|
||||||
}
|
}
|
||||||
return p.makeDetailedError(msgPart, tmplName, lineStr, posStr, target)
|
return p.makeDetailedError(msgPart, tmplName, lineStr, posStr, target)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -104,8 +103,6 @@ func mailBodyFuncMap() template.FuncMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
|
|
||||||
|
|
||||||
func newMailRenderer() (*MailRender, error) {
|
func newMailRenderer() (*MailRender, error) {
|
||||||
subjectTemplates := texttmpl.New("")
|
subjectTemplates := texttmpl.New("")
|
||||||
subjectTemplates.Funcs(mailSubjectTextFuncMap())
|
subjectTemplates.Funcs(mailSubjectTextFuncMap())
|
||||||
@@ -155,7 +152,7 @@ func newMailRenderer() (*MailRender, error) {
|
|||||||
}
|
}
|
||||||
var subjectContent []byte
|
var subjectContent []byte
|
||||||
bodyContent := content
|
bodyContent := content
|
||||||
loc := mailSubjectSplit.FindIndex(content)
|
loc := globalVars().mailSubjectSplit.FindIndex(content)
|
||||||
if loc != nil {
|
if loc != nil {
|
||||||
subjectContent, bodyContent = content[0:loc[0]], content[loc[1]:]
|
subjectContent, bodyContent = content[0:loc[0]], content[loc[1]:]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -78,12 +77,9 @@ func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) templ
|
|||||||
return renderedMessage
|
return renderedMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match text that is between back ticks.
|
|
||||||
var codeMatcher = regexp.MustCompile("`([^`]+)`")
|
|
||||||
|
|
||||||
// renderCodeBlock renders "`…`" as highlighted "<code>" block, intended for issue and PR titles
|
// renderCodeBlock renders "`…`" as highlighted "<code>" block, intended for issue and PR titles
|
||||||
func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
||||||
htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), `<code class="inline-code-block">$1</code>`) // replace with HTML <code> tags
|
htmlWithCodeTags := globalVars().codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), `<code class="inline-code-block">$1</code>`) // replace with HTML <code> tags
|
||||||
return template.HTML(htmlWithCodeTags)
|
return template.HTML(htmlWithCodeTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
@@ -34,13 +35,19 @@ const (
|
|||||||
maxTimeDifference = 10 * time.Minute
|
maxTimeDifference = 10 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
algorithmPattern = regexp.MustCompile(`algorithm=(\w+)`)
|
algorithmPattern *regexp.Regexp
|
||||||
versionPattern = regexp.MustCompile(`version=(\d+\.\d+)`)
|
versionPattern *regexp.Regexp
|
||||||
authorizationPattern = regexp.MustCompile(`\AX-Ops-Authorization-(\d+)`)
|
authorizationPattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.algorithmPattern = regexp.MustCompile(`algorithm=(\w+)`)
|
||||||
|
ret.versionPattern = regexp.MustCompile(`version=(\d+\.\d+)`)
|
||||||
|
ret.authorizationPattern = regexp.MustCompile(`\AX-Ops-Authorization-(\d+)`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
_ auth.Method = &Auth{}
|
var _ auth.Method = &Auth{}
|
||||||
)
|
|
||||||
|
|
||||||
// Documentation:
|
// Documentation:
|
||||||
// https://docs.chef.io/server/api_chef_server/#required-headers
|
// https://docs.chef.io/server/api_chef_server/#required-headers
|
||||||
@@ -139,7 +146,7 @@ func getSignVersion(req *http.Request) (string, error) {
|
|||||||
return "", util.NewInvalidArgumentErrorf("X-Ops-Sign header missing")
|
return "", util.NewInvalidArgumentErrorf("X-Ops-Sign header missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := versionPattern.FindStringSubmatch(hdr)
|
m := globalVars().versionPattern.FindStringSubmatch(hdr)
|
||||||
if len(m) != 2 {
|
if len(m) != 2 {
|
||||||
return "", util.NewInvalidArgumentErrorf("invalid X-Ops-Sign header")
|
return "", util.NewInvalidArgumentErrorf("invalid X-Ops-Sign header")
|
||||||
}
|
}
|
||||||
@@ -152,7 +159,7 @@ func getSignVersion(req *http.Request) (string, error) {
|
|||||||
|
|
||||||
version := m[1]
|
version := m[1]
|
||||||
|
|
||||||
m = algorithmPattern.FindStringSubmatch(hdr)
|
m = globalVars().algorithmPattern.FindStringSubmatch(hdr)
|
||||||
if len(m) == 2 && m[1] != "sha1" && !(m[1] == "sha256" && version == "1.3") {
|
if len(m) == 2 && m[1] != "sha1" && !(m[1] == "sha256" && version == "1.3") {
|
||||||
return "", util.NewInvalidArgumentErrorf("unsupported algorithm")
|
return "", util.NewInvalidArgumentErrorf("unsupported algorithm")
|
||||||
}
|
}
|
||||||
@@ -181,7 +188,7 @@ func verifySignedHeaders(req *http.Request, version string, pub *rsa.PublicKey)
|
|||||||
func getAuthorizationData(req *http.Request) ([]byte, error) {
|
func getAuthorizationData(req *http.Request) ([]byte, error) {
|
||||||
valueList := make(map[int]string)
|
valueList := make(map[int]string)
|
||||||
for k, vs := range req.Header {
|
for k, vs := range req.Header {
|
||||||
if m := authorizationPattern.FindStringSubmatch(k); m != nil {
|
if m := globalVars().authorizationPattern.FindStringSubmatch(k); m != nil {
|
||||||
index, _ := strconv.Atoi(m[1])
|
index, _ := strconv.Atoi(m[1])
|
||||||
var v string
|
var v string
|
||||||
if len(vs) == 0 {
|
if len(vs) == 0 {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
packages_model "gitea.dev/models/packages"
|
packages_model "gitea.dev/models/packages"
|
||||||
@@ -17,10 +18,15 @@ import (
|
|||||||
packages_service "gitea.dev/services/packages"
|
packages_service "gitea.dev/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`)
|
packageNameRegex *regexp.Regexp
|
||||||
filenameRegex = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`)
|
filenameRegex *regexp.Regexp
|
||||||
)
|
},
|
||||||
|
) {
|
||||||
|
ret.packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`)
|
||||||
|
ret.filenameRegex = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
func apiError(ctx *context.Context, status int, obj any) {
|
func apiError(ctx *context.Context, status int, obj any) {
|
||||||
message := helper.ProcessErrorForUser(ctx, status, obj)
|
message := helper.ProcessErrorForUser(ctx, status, obj)
|
||||||
@@ -54,11 +60,11 @@ func isValidPackageName(packageName string) bool {
|
|||||||
if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
|
if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return packageNameRegex.MatchString(packageName) && packageName != ".."
|
return globalVars().packageNameRegex.MatchString(packageName) && packageName != ".."
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidFileName(filename string) bool {
|
func isValidFileName(filename string) bool {
|
||||||
return filenameRegex.MatchString(filename) &&
|
return globalVars().filenameRegex.MatchString(filename) &&
|
||||||
strings.TrimSpace(filename) == filename &&
|
strings.TrimSpace(filename) == filename &&
|
||||||
filename != "." && filename != ".."
|
filename != "." && filename != ".."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
packages_model "gitea.dev/models/packages"
|
packages_model "gitea.dev/models/packages"
|
||||||
"gitea.dev/modules/globallock"
|
"gitea.dev/modules/globallock"
|
||||||
@@ -46,7 +47,9 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidParameters = errors.New("request parameters are invalid")
|
errInvalidParameters = errors.New("request parameters are invalid")
|
||||||
illegalCharacters = regexp.MustCompile(`[\\/:"<>|?*]`)
|
illegalCharacters = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`[\\/:"<>|?*]`)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
func apiError(ctx *context.Context, status int, obj any) {
|
func apiError(ctx *context.Context, status int, obj any) {
|
||||||
@@ -471,7 +474,7 @@ func extractPathParameters(ctx *context.Context) (parameters, error) {
|
|||||||
parts = parts[:len(parts)-1]
|
parts = parts[:len(parts)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if illegalCharacters.MatchString(p.Version) {
|
if illegalCharacters().MatchString(p.Version) {
|
||||||
return p, errInvalidParameters
|
return p, errInvalidParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +485,7 @@ func extractPathParameters(ctx *context.Context) (parameters, error) {
|
|||||||
p.ArtifactID = parts[len(parts)-1]
|
p.ArtifactID = parts[len(parts)-1]
|
||||||
p.GroupID = strings.Join(parts[:len(parts)-1], ".")
|
p.GroupID = strings.Join(parts[:len(parts)-1], ".")
|
||||||
|
|
||||||
if illegalCharacters.MatchString(p.GroupID) || illegalCharacters.MatchString(p.ArtifactID) {
|
if illegalCharacters().MatchString(p.GroupID) || illegalCharacters().MatchString(p.ArtifactID) {
|
||||||
return p, errInvalidParameters
|
return p, errInvalidParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
packages_model "gitea.dev/models/packages"
|
packages_model "gitea.dev/models/packages"
|
||||||
@@ -90,10 +91,15 @@ func FeedCapabilityResource(ctx *context.Context) {
|
|||||||
xmlResponse(ctx, http.StatusOK, Metadata)
|
xmlResponse(ctx, http.StatusOK, Metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
searchTermExtract = regexp.MustCompile(`'([^']+)'`)
|
searchTermExtract *regexp.Regexp
|
||||||
searchTermExact = regexp.MustCompile(`\s+eq\s+'`)
|
searchTermExact *regexp.Regexp
|
||||||
)
|
},
|
||||||
|
) {
|
||||||
|
ret.searchTermExtract = regexp.MustCompile(`'([^']+)'`)
|
||||||
|
ret.searchTermExact = regexp.MustCompile(`\s+eq\s+'`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
func getSearchTerm(ctx *context.Context) packages_model.SearchValue {
|
func getSearchTerm(ctx *context.Context) packages_model.SearchValue {
|
||||||
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
|
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
|
||||||
@@ -109,11 +115,11 @@ func getSearchTerm(ctx *context.Context) packages_model.SearchValue {
|
|||||||
// https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5
|
// https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5
|
||||||
// We don't support these queries, just extract the search term.
|
// We don't support these queries, just extract the search term.
|
||||||
filter := ctx.FormTrim("$filter")
|
filter := ctx.FormTrim("$filter")
|
||||||
match := searchTermExtract.FindStringSubmatch(filter)
|
match := globalVars().searchTermExtract.FindStringSubmatch(filter)
|
||||||
if len(match) == 2 {
|
if len(match) == 2 {
|
||||||
return packages_model.SearchValue{
|
return packages_model.SearchValue{
|
||||||
Value: strings.TrimSpace(match[1]),
|
Value: strings.TrimSpace(match[1]),
|
||||||
ExactMatch: searchTermExact.MatchString(filter),
|
ExactMatch: globalVars().searchTermExact.MatchString(filter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
packages_model "gitea.dev/models/packages"
|
packages_model "gitea.dev/models/packages"
|
||||||
@@ -23,20 +24,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// https://peps.python.org/pep-0426/#name
|
// https://peps.python.org/pep-0426/#name
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
normalizer = strings.NewReplacer(".", "-", "_", "-")
|
nameMatcher *regexp.Regexp
|
||||||
nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\z`)
|
versionMatcher *regexp.Regexp
|
||||||
)
|
},
|
||||||
|
) {
|
||||||
|
ret.nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\z`)
|
||||||
|
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||||
|
ret.versionMatcher = regexp.MustCompile(`\Av?` +
|
||||||
|
`(?:[0-9]+!)?` + // epoch
|
||||||
|
`[0-9]+(?:\.[0-9]+)*` + // release segment
|
||||||
|
`(?:[-_\.]?(?:a|b|c|rc|alpha|beta|pre|preview)[-_\.]?[0-9]*)?` + // pre-release
|
||||||
|
`(?:-[0-9]+|[-_\.]?(?:post|rev|r)[-_\.]?[0-9]*)?` + // post release
|
||||||
|
`(?:[-_\.]?dev[-_\.]?[0-9]*)?` + // dev release
|
||||||
|
`(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version
|
||||||
|
`\z`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
var normalizer = strings.NewReplacer(".", "-", "_", "-")
|
||||||
var versionMatcher = regexp.MustCompile(`\Av?` +
|
|
||||||
`(?:[0-9]+!)?` + // epoch
|
|
||||||
`[0-9]+(?:\.[0-9]+)*` + // release segment
|
|
||||||
`(?:[-_\.]?(?:a|b|c|rc|alpha|beta|pre|preview)[-_\.]?[0-9]*)?` + // pre-release
|
|
||||||
`(?:-[0-9]+|[-_\.]?(?:post|rev|r)[-_\.]?[0-9]*)?` + // post release
|
|
||||||
`(?:[-_\.]?dev[-_\.]?[0-9]*)?` + // dev release
|
|
||||||
`(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version
|
|
||||||
`\z`)
|
|
||||||
|
|
||||||
func apiError(ctx *context.Context, status int, obj any) {
|
func apiError(ctx *context.Context, status int, obj any) {
|
||||||
message := helper.ProcessErrorForUser(ctx, status, obj)
|
message := helper.ProcessErrorForUser(ctx, status, obj)
|
||||||
@@ -225,5 +231,5 @@ func normalizeLabel(label string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isValidNameAndVersion(packageName, packageVersion string) bool {
|
func isValidNameAndVersion(packageName, packageVersion string) bool {
|
||||||
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
|
return globalVars().nameMatcher.MatchString(packageName) && globalVars().versionMatcher.MatchString(packageVersion)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
packages_model "gitea.dev/models/packages"
|
packages_model "gitea.dev/models/packages"
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
@@ -35,12 +36,17 @@ const (
|
|||||||
AcceptZip = "application/vnd.swift.registry.v1+zip"
|
AcceptZip = "application/vnd.swift.registry.v1+zip"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
|
scopePattern *regexp.Regexp
|
||||||
|
namePattern *regexp.Regexp
|
||||||
|
},
|
||||||
|
) {
|
||||||
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#361-package-scope
|
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#361-package-scope
|
||||||
scopePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\z`)
|
ret.scopePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\z`)
|
||||||
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#362-package-name
|
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#362-package-name
|
||||||
namePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-_]{0,99}\z`)
|
ret.namePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-_]{0,99}\z`)
|
||||||
)
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
type headers struct {
|
type headers struct {
|
||||||
Status int
|
Status int
|
||||||
@@ -327,7 +333,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
|
|
||||||
_, err := version.NewSemver(packageVersion)
|
_, err := version.NewSemver(packageVersion)
|
||||||
|
|
||||||
if err != nil || !scopePattern.MatchString(packageScope) || !namePattern.MatchString(packageName) {
|
if err != nil || !globalVars().scopePattern.MatchString(packageScope) || !globalVars().namePattern.MatchString(packageName) {
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
apiError(ctx, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
packages_model "gitea.dev/models/packages"
|
packages_model "gitea.dev/models/packages"
|
||||||
@@ -24,7 +25,9 @@ import (
|
|||||||
packages_service "gitea.dev/services/packages"
|
packages_service "gitea.dev/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
var packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`)
|
var packageNameRegex = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`\A[-_+.\w]+\z`)
|
||||||
|
})
|
||||||
|
|
||||||
const (
|
const (
|
||||||
stateFilename = "tfstate"
|
stateFilename = "tfstate"
|
||||||
@@ -81,7 +84,7 @@ func isValidPackageName(packageName string) bool {
|
|||||||
if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
|
if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return packageNameRegex.MatchString(packageName) && packageName != ".."
|
return packageNameRegex().MatchString(packageName) && packageName != ".."
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadState uploads the specific terraform package.
|
// UploadState uploads the specific terraform package.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/auth"
|
"gitea.dev/models/auth"
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
@@ -35,10 +36,15 @@ const (
|
|||||||
tplAuthEdit templates.TplName = "admin/auth/edit"
|
tplAuthEdit templates.TplName = "admin/auth/edit"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalVars = sync.OnceValue(func() (ret struct {
|
||||||
separatorAntiPattern = regexp.MustCompile(`[^\w-\.]`)
|
separatorAntiPattern *regexp.Regexp
|
||||||
langCodePattern = regexp.MustCompile(`^[a-z]{2}-[A-Z]{2}$`)
|
langCodePattern *regexp.Regexp
|
||||||
)
|
},
|
||||||
|
) {
|
||||||
|
ret.separatorAntiPattern = regexp.MustCompile(`[^\w-\.]`)
|
||||||
|
ret.langCodePattern = regexp.MustCompile(`^[a-z]{2}-[A-Z]{2}$`)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
// Authentications show authentication config page
|
// Authentications show authentication config page
|
||||||
func Authentications(ctx *context.Context) {
|
func Authentications(ctx *context.Context) {
|
||||||
@@ -204,12 +210,12 @@ func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi
|
|||||||
ctx.Data["Err_SSPISeparatorReplacement"] = true
|
ctx.Data["Err_SSPISeparatorReplacement"] = true
|
||||||
return nil, errors.New(ctx.Locale.TrString("form.require_error", ctx.Locale.TrString("form.SSPISeparatorReplacement")))
|
return nil, errors.New(ctx.Locale.TrString("form.require_error", ctx.Locale.TrString("form.SSPISeparatorReplacement")))
|
||||||
}
|
}
|
||||||
if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) {
|
if globalVars().separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) {
|
||||||
ctx.Data["Err_SSPISeparatorReplacement"] = true
|
ctx.Data["Err_SSPISeparatorReplacement"] = true
|
||||||
return nil, errors.New(ctx.Locale.TrString("form.alpha_dash_dot_error", ctx.Locale.TrString("form.SSPISeparatorReplacement")))
|
return nil, errors.New(ctx.Locale.TrString("form.alpha_dash_dot_error", ctx.Locale.TrString("form.SSPISeparatorReplacement")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) {
|
if form.SSPIDefaultLanguage != "" && !globalVars().langCodePattern.MatchString(form.SSPIDefaultLanguage) {
|
||||||
ctx.Data["Err_SSPIDefaultLanguage"] = true
|
ctx.Data["Err_SSPIDefaultLanguage"] = true
|
||||||
return nil, errors.New(ctx.Locale.TrString("form.lang_select_error"))
|
return nil, errors.New(ctx.Locale.TrString("form.lang_select_error"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/templates"
|
"gitea.dev/modules/templates"
|
||||||
@@ -19,7 +20,9 @@ import (
|
|||||||
"go.yaml.in/yaml/v4"
|
"go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mailDarkSchemeQuery = regexp.MustCompile(`@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)`)
|
var mailDarkSchemeQuery = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)`)
|
||||||
|
})
|
||||||
|
|
||||||
func mailPreviewMockData(tmplName string) (map[string]any, error) {
|
func mailPreviewMockData(tmplName string) (map[string]any, error) {
|
||||||
mockData := map[string]any{}
|
mockData := map[string]any{}
|
||||||
@@ -51,7 +54,7 @@ func MailPreviewRender(ctx *context.Context) {
|
|||||||
body = strings.ReplaceAll(body, `src="cid:`, `src="`+setting.AppSubURL+`/devtest/mail-preview-embed/`)
|
body = strings.ReplaceAll(body, `src="cid:`, `src="`+setting.AppSubURL+`/devtest/mail-preview-embed/`)
|
||||||
// a page can force "color-scheme" on an embedded document but never "prefers-color-scheme"
|
// a page can force "color-scheme" on an embedded document but never "prefers-color-scheme"
|
||||||
if scheme := ctx.FormString("scheme"); scheme == "light" || scheme == "dark" {
|
if scheme := ctx.FormString("scheme"); scheme == "light" || scheme == "dark" {
|
||||||
body = mailDarkSchemeQuery.ReplaceAllString(body, util.Iif(scheme == "dark", "@media all", "@media not all"))
|
body = mailDarkSchemeQuery().ReplaceAllString(body, util.Iif(scheme == "dark", "@media all", "@media not all"))
|
||||||
body = strings.Replace(body, "</head>", "<style>:root {color-scheme: "+scheme+"}</style></head>", 1)
|
body = strings.Replace(body, "</head>", "<style>:root {color-scheme: "+scheme+"}</style></head>", 1)
|
||||||
}
|
}
|
||||||
// fragment templates like "mail/base/head" would be sniffed as text/plain otherwise
|
// fragment templates like "mail/base/head" would be sniffed as text/plain otherwise
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
activities_model "gitea.dev/models/activities"
|
activities_model "gitea.dev/models/activities"
|
||||||
asymkey_model "gitea.dev/models/asymkey"
|
asymkey_model "gitea.dev/models/asymkey"
|
||||||
@@ -183,7 +184,7 @@ func Milestones(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(reposQuery) != 0 {
|
if len(reposQuery) != 0 {
|
||||||
if issueReposQueryPattern.MatchString(reposQuery) {
|
if issueReposQueryPattern().MatchString(reposQuery) {
|
||||||
// remove "[" and "]" from string
|
// remove "[" and "]" from string
|
||||||
reposQuery = reposQuery[1 : len(reposQuery)-1]
|
reposQuery = reposQuery[1 : len(reposQuery)-1]
|
||||||
// for each ID (delimiter ",") add to int to repoIDs
|
// for each ID (delimiter ",") add to int to repoIDs
|
||||||
@@ -353,7 +354,9 @@ func Issues(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Regexp for repos query
|
// Regexp for repos query
|
||||||
var issueReposQueryPattern = regexp.MustCompile(`^\[\d+(,\d+)*,?\]$`)
|
var issueReposQueryPattern = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`^\[\d+(,\d+)*,?\]$`)
|
||||||
|
})
|
||||||
|
|
||||||
func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
@@ -38,7 +39,9 @@ func (err ErrFileTypeForbidden) Unwrap() error {
|
|||||||
return util.ErrInvalidArgument
|
return util.ErrInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
var wildcardTypeRe = regexp.MustCompile(`^[a-z]+/\*$`)
|
var wildcardTypeRe = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`^[a-z]+/\*$`)
|
||||||
|
})
|
||||||
|
|
||||||
// Verify validates whether a file is allowed to be uploaded. If buf is empty, it will just check if the file
|
// Verify validates whether a file is allowed to be uploaded. If buf is empty, it will just check if the file
|
||||||
// has an allowed file extension.
|
// has an allowed file extension.
|
||||||
@@ -80,7 +83,7 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error {
|
|||||||
if mimeType == allowEntry {
|
if mimeType == allowEntry {
|
||||||
return nil // mime type is allowed
|
return nil // mime type is allowed
|
||||||
}
|
}
|
||||||
if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) {
|
if wildcardTypeRe().MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) {
|
||||||
return nil // wildcard match, e.g. image/*
|
return nil // wildcard match, e.g. image/*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
issues_model "gitea.dev/models/issues"
|
issues_model "gitea.dev/models/issues"
|
||||||
@@ -35,11 +36,13 @@ const (
|
|||||||
secondsByMonth = 4 * secondsByWeek // seconds in a month
|
secondsByMonth = 4 * secondsByWeek // seconds in a month
|
||||||
)
|
)
|
||||||
|
|
||||||
var reDuration = regexp.MustCompile(`(?i)^(?:(\d+([\.,]\d+)?)(?:mo))?(?:(\d+([\.,]\d+)?)(?:w))?(?:(\d+([\.,]\d+)?)(?:d))?(?:(\d+([\.,]\d+)?)(?:h))?(?:(\d+([\.,]\d+)?)(?:m))?$`)
|
var reDuration = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?i)^(?:(\d+([\.,]\d+)?)(?:mo))?(?:(\d+([\.,]\d+)?)(?:w))?(?:(\d+([\.,]\d+)?)(?:d))?(?:(\d+([\.,]\d+)?)(?:h))?(?:(\d+([\.,]\d+)?)(?:m))?$`)
|
||||||
|
})
|
||||||
|
|
||||||
// timeLogToAmount parses time log string and returns amount in seconds
|
// timeLogToAmount parses time log string and returns amount in seconds
|
||||||
func timeLogToAmount(str string) int64 {
|
func timeLogToAmount(str string) int64 {
|
||||||
matches := reDuration.FindAllStringSubmatch(str, -1)
|
matches := reDuration().FindAllStringSubmatch(str, -1)
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
auth_model "gitea.dev/models/auth"
|
auth_model "gitea.dev/models/auth"
|
||||||
@@ -111,7 +112,9 @@ func CheckAcceptMediaType(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rangeHeaderRegexp = regexp.MustCompile(`bytes=(\d+)-(\d*).*`)
|
var rangeHeaderRegexp = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`bytes=(\d+)-(\d*).*`)
|
||||||
|
})
|
||||||
|
|
||||||
// DownloadHandler gets the content from the content store
|
// DownloadHandler gets the content from the content store
|
||||||
func DownloadHandler(ctx *context.Context) {
|
func DownloadHandler(ctx *context.Context) {
|
||||||
@@ -128,7 +131,7 @@ func DownloadHandler(ctx *context.Context) {
|
|||||||
toByte = meta.Size - 1
|
toByte = meta.Size - 1
|
||||||
statusCode := http.StatusOK
|
statusCode := http.StatusOK
|
||||||
if rangeHdr := ctx.Req.Header.Get("Range"); rangeHdr != "" {
|
if rangeHdr := ctx.Req.Header.Get("Range"); rangeHdr != "" {
|
||||||
match := rangeHeaderRegexp.FindStringSubmatch(rangeHdr)
|
match := rangeHeaderRegexp().FindStringSubmatch(rangeHdr)
|
||||||
if len(match) > 1 {
|
if len(match) > 1 {
|
||||||
statusCode = http.StatusPartialContent
|
statusCode = http.StatusPartialContent
|
||||||
fromByte, _ = strconv.ParseInt(match[1], 10, 32)
|
fromByte, _ = strconv.ParseInt(match[1], 10, 32)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
@@ -31,7 +32,9 @@ import (
|
|||||||
|
|
||||||
const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322
|
const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322
|
||||||
|
|
||||||
var subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
|
var subjectRemoveSpaces = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`[\s]+`)
|
||||||
|
})
|
||||||
|
|
||||||
func LoadedTemplates() *templates.MailRender {
|
func LoadedTemplates() *templates.MailRender {
|
||||||
return templates.MailRenderer()
|
return templates.MailRenderer()
|
||||||
@@ -47,7 +50,7 @@ func SendTestMail(email string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeSubject(subject string) string {
|
func sanitizeSubject(subject string) string {
|
||||||
runes := []rune(strings.TrimSpace(subjectRemoveSpaces.ReplaceAllLiteralString(subject, " ")))
|
runes := []rune(strings.TrimSpace(subjectRemoveSpaces().ReplaceAllLiteralString(subject, " ")))
|
||||||
if len(runes) > mailMaxSubjectRunes {
|
if len(runes) > mailMaxSubjectRunes {
|
||||||
runes = runes[:mailMaxSubjectRunes]
|
runes = runes[:mailMaxSubjectRunes]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
issues_model "gitea.dev/models/issues"
|
issues_model "gitea.dev/models/issues"
|
||||||
@@ -564,7 +565,9 @@ func (g *GitlabDownloader) GetComments(ctx context.Context, commentable base.Com
|
|||||||
return allComments, true, nil
|
return allComments, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetBranchChangeRegexp = regexp.MustCompile("^changed target branch from `(.*?)` to `(.*?)`$")
|
var targetBranchChangeRegexp = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile("^changed target branch from `(.*?)` to `(.*?)`$")
|
||||||
|
})
|
||||||
|
|
||||||
func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.Note) *base.Comment {
|
func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.Note) *base.Comment {
|
||||||
comment := &base.Comment{
|
comment := &base.Comment{
|
||||||
@@ -580,7 +583,7 @@ func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.N
|
|||||||
|
|
||||||
// Try to find the underlying event of system notes.
|
// Try to find the underlying event of system notes.
|
||||||
if note.System {
|
if note.System {
|
||||||
if match := targetBranchChangeRegexp.FindStringSubmatch(note.Body); match != nil {
|
if match := targetBranchChangeRegexp().FindStringSubmatch(note.Body); match != nil {
|
||||||
comment.CommentType = issues_model.CommentTypeChangeTargetBranch.String()
|
comment.CommentType = issues_model.CommentTypeChangeTargetBranch.String()
|
||||||
comment.Meta["OldRef"] = match[1]
|
comment.Meta["OldRef"] = match[1]
|
||||||
comment.Meta["NewRef"] = match[2]
|
comment.Meta["NewRef"] = match[2]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
audit_model "gitea.dev/models/audit"
|
audit_model "gitea.dev/models/audit"
|
||||||
@@ -28,7 +29,9 @@ import (
|
|||||||
repo_service "gitea.dev/services/repository"
|
repo_service "gitea.dev/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stripExitStatus = regexp.MustCompile(`exit status \d+ - `)
|
var stripExitStatus = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`exit status \d+ - `)
|
||||||
|
})
|
||||||
|
|
||||||
// AddPushMirrorRemote registers the push mirror remote.
|
// AddPushMirrorRemote registers the push mirror remote.
|
||||||
func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr string) (retErr error) {
|
func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr string) (retErr error) {
|
||||||
@@ -132,7 +135,7 @@ func SyncPushMirror(ctx context.Context, mirrorID int64) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = util.SanitizeErrorCredentialURLs(err)
|
err = util.SanitizeErrorCredentialURLs(err)
|
||||||
log.Error("SyncPushMirror [mirror: %d][repo: %-v]: %v", m.ID, m.Repo, err)
|
log.Error("SyncPushMirror [mirror: %d][repo: %-v]: %v", m.ID, m.Repo, err)
|
||||||
m.LastError = stripExitStatus.ReplaceAllLiteralString(err.Error(), "")
|
m.LastError = stripExitStatus().ReplaceAllLiteralString(err.Error(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
m.LastUpdateUnix = timeutil.TimeStampNow()
|
m.LastUpdateUnix = timeutil.TimeStampNow()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unicode"
|
"unicode"
|
||||||
"uuid"
|
"uuid"
|
||||||
|
|
||||||
@@ -666,7 +667,9 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var escapedSymbols = regexp.MustCompile(`([*[?! \\])`)
|
var escapedSymbols = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`([*[?! \\])`)
|
||||||
|
})
|
||||||
|
|
||||||
// IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
|
// IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
|
||||||
func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p access_model.Permission, user *user_model.User) (bool, error) {
|
func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p access_model.Permission, user *user_model.User) (bool, error) {
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ func getDiffTree(ctx context.Context, repo git.RepositoryFacade, baseBranch, hea
|
|||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
treePath := scanner.Text()
|
treePath := scanner.Text()
|
||||||
// escape '*', '?', '[', spaces and '!' prefix
|
// escape '*', '?', '[', spaces and '!' prefix
|
||||||
treePath = escapedSymbols.ReplaceAllString(treePath, `\$1`)
|
treePath = escapedSymbols().ReplaceAllString(treePath, `\$1`)
|
||||||
// no necessary to escape the first '#' symbol because the first symbol is '/'
|
// no necessary to escape the first '#' symbol because the first symbol is '/'
|
||||||
if _, err := fmt.Fprintf(out, "/%s\n", treePath); err != nil {
|
if _, err := fmt.Fprintf(out, "/%s\n", treePath); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
webhook_model "gitea.dev/models/webhook"
|
webhook_model "gitea.dev/models/webhook"
|
||||||
"gitea.dev/modules/base"
|
"gitea.dev/modules/base"
|
||||||
@@ -267,10 +268,12 @@ func (m matrixConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MatrixPayload,
|
|||||||
return m.newPayload(text)
|
return m.newPayload(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`)
|
var urlRegex = sync.OnceValue(func() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`)
|
||||||
|
})
|
||||||
|
|
||||||
func getMessageBody(htmlText string) string {
|
func getMessageBody(htmlText string) string {
|
||||||
htmlText = urlRegex.ReplaceAllString(htmlText, "[$2]($1)")
|
htmlText = urlRegex().ReplaceAllString(htmlText, "[$2]($1)")
|
||||||
htmlText = strings.ReplaceAll(htmlText, "<br>", "\n")
|
htmlText = strings.ReplaceAll(htmlText, "<br>", "\n")
|
||||||
return htmlText
|
return htmlText
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user