diff options
Diffstat (limited to 'internal/util')
-rw-r--r-- | internal/util/parse.go | 96 | ||||
-rw-r--r-- | internal/util/regexes.go | 79 | ||||
-rw-r--r-- | internal/util/statustools.go (renamed from internal/util/status.go) | 20 | ||||
-rw-r--r-- | internal/util/statustools_test.go (renamed from internal/util/status_test.go) | 11 | ||||
-rw-r--r-- | internal/util/uri.go | 218 | ||||
-rw-r--r-- | internal/util/validation.go | 49 | ||||
-rw-r--r-- | internal/util/validation_test.go | 81 |
7 files changed, 357 insertions, 197 deletions
diff --git a/internal/util/parse.go b/internal/util/parse.go deleted file mode 100644 index f0bcff5dc..000000000 --- a/internal/util/parse.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package util - -import ( - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" - mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel" -) - -// URIs contains a bunch of URIs and URLs for a user, host, account, etc. -type URIs struct { - HostURL string - UserURL string - StatusesURL string - - UserURI string - StatusesURI string - InboxURI string - OutboxURI string - FollowersURI string - CollectionURI string -} - -// GenerateURIs throws together a bunch of URIs for the given username, with the given protocol and host. -func GenerateURIs(username string, protocol string, host string) *URIs { - hostURL := fmt.Sprintf("%s://%s", protocol, host) - userURL := fmt.Sprintf("%s/@%s", hostURL, username) - statusesURL := fmt.Sprintf("%s/statuses", userURL) - - userURI := fmt.Sprintf("%s/users/%s", hostURL, username) - statusesURI := fmt.Sprintf("%s/statuses", userURI) - inboxURI := fmt.Sprintf("%s/inbox", userURI) - outboxURI := fmt.Sprintf("%s/outbox", userURI) - followersURI := fmt.Sprintf("%s/followers", userURI) - collectionURI := fmt.Sprintf("%s/collections/featured", userURI) - return &URIs{ - HostURL: hostURL, - UserURL: userURL, - StatusesURL: statusesURL, - - UserURI: userURI, - StatusesURI: statusesURI, - InboxURI: inboxURI, - OutboxURI: outboxURI, - FollowersURI: followersURI, - CollectionURI: collectionURI, - } -} - -// ParseGTSVisFromMastoVis converts a mastodon visibility into its gts equivalent. -func ParseGTSVisFromMastoVis(m mastotypes.Visibility) gtsmodel.Visibility { - switch m { - case mastotypes.VisibilityPublic: - return gtsmodel.VisibilityPublic - case mastotypes.VisibilityUnlisted: - return gtsmodel.VisibilityUnlocked - case mastotypes.VisibilityPrivate: - return gtsmodel.VisibilityFollowersOnly - case mastotypes.VisibilityDirect: - return gtsmodel.VisibilityDirect - } - return "" -} - -// ParseMastoVisFromGTSVis converts a gts visibility into its mastodon equivalent -func ParseMastoVisFromGTSVis(m gtsmodel.Visibility) mastotypes.Visibility { - switch m { - case gtsmodel.VisibilityPublic: - return mastotypes.VisibilityPublic - case gtsmodel.VisibilityUnlocked: - return mastotypes.VisibilityUnlisted - case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly: - return mastotypes.VisibilityPrivate - case gtsmodel.VisibilityDirect: - return mastotypes.VisibilityDirect - } - return "" -} diff --git a/internal/util/regexes.go b/internal/util/regexes.go index 60b397d86..a59bd678a 100644 --- a/internal/util/regexes.go +++ b/internal/util/regexes.go @@ -18,19 +18,78 @@ package util -import "regexp" +import ( + "fmt" + "regexp" +) + +const ( + minimumPasswordEntropy = 60 // dictates password strength. See https://github.com/wagslane/go-password-validator + minimumReasonLength = 40 + maximumReasonLength = 500 + maximumEmailLength = 256 + maximumUsernameLength = 64 + maximumPasswordLength = 64 + maximumEmojiShortcodeLength = 30 + maximumHashtagLength = 30 +) var ( // mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 - mentionRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)` - mentionRegex = regexp.MustCompile(mentionRegexString) + mentionFinderRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)` + mentionFinderRegex = regexp.MustCompile(mentionFinderRegexString) + // hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1 - hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)` - hashtagRegex = regexp.MustCompile(hashtagRegexString) - // emoji regex can be played with here: https://regex101.com/r/478XGM/1 - emojiRegexString = `(?: |^|\W)?:([a-zA-Z0-9_]{2,30}):(?:\b|\r)?` - emojiRegex = regexp.MustCompile(emojiRegexString) + hashtagFinderRegexString = fmt.Sprintf(`(?: |^|\W)?#([a-zA-Z0-9]{1,%d})(?:\b|\r)`, maximumHashtagLength) + hashtagFinderRegex = regexp.MustCompile(hashtagFinderRegexString) + // emoji shortcode regex can be played with here: https://regex101.com/r/zMDRaG/1 - emojiShortcodeString = `^[a-z0-9_]{2,30}$` - emojiShortcodeRegex = regexp.MustCompile(emojiShortcodeString) + emojiShortcodeRegexString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumEmojiShortcodeLength) + emojiShortcodeValidationRegex = regexp.MustCompile(fmt.Sprintf("^%s$", emojiShortcodeRegexString)) + + // emoji regex can be played with here: https://regex101.com/r/478XGM/1 + emojiFinderRegexString = fmt.Sprintf(`(?: |^|\W)?:(%s):(?:\b|\r)?`, emojiShortcodeRegexString) + emojiFinderRegex = regexp.MustCompile(emojiFinderRegexString) + + // usernameRegexString defines an acceptable username on this instance + usernameRegexString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength) + // usernameValidationRegex can be used to validate usernames of new signups + usernameValidationRegex = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameRegexString)) + + userPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, UsersPath, usernameRegexString) + // userPathRegex parses a path that validates and captures the username part from eg /users/example_username + userPathRegex = regexp.MustCompile(userPathRegexString) + + inboxPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, InboxPath) + // inboxPathRegex parses a path that validates and captures the username part from eg /users/example_username/inbox + inboxPathRegex = regexp.MustCompile(inboxPathRegexString) + + outboxPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, OutboxPath) + // outboxPathRegex parses a path that validates and captures the username part from eg /users/example_username/outbox + outboxPathRegex = regexp.MustCompile(outboxPathRegexString) + + actorPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, ActorsPath, usernameRegexString) + // actorPathRegex parses a path that validates and captures the username part from eg /actors/example_username + actorPathRegex = regexp.MustCompile(actorPathRegexString) + + followersPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowersPath) + // followersPathRegex parses a path that validates and captures the username part from eg /users/example_username/followers + followersPathRegex = regexp.MustCompile(followersPathRegexString) + + followingPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowingPath) + // followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following + followingPathRegex = regexp.MustCompile(followingPathRegexString) + + likedPathRegexString = fmt.Sprintf(`^/?%s/%s/%s$`, UsersPath, usernameRegexString, LikedPath) + // followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked + likedPathRegex = regexp.MustCompile(likedPathRegexString) + + // see https://ihateregex.io/expr/uuid/ + uuidRegexString = `[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}` + + statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, uuidRegexString) + // statusesPathRegex parses a path that validates and captures the username part and the uuid part + // from eg /users/example_username/statuses/123e4567-e89b-12d3-a456-426655440000. + // The regex can be played with here: https://regex101.com/r/G9zuxQ/1 + statusesPathRegex = regexp.MustCompile(statusesPathRegexString) ) diff --git a/internal/util/status.go b/internal/util/statustools.go index e4b3ec6a5..5591f185a 100644 --- a/internal/util/status.go +++ b/internal/util/statustools.go @@ -31,10 +31,10 @@ import ( // The case of the returned mentions will be lowered, for consistency. func DeriveMentions(status string) []string { mentionedAccounts := []string{} - for _, m := range mentionRegex.FindAllStringSubmatch(status, -1) { + for _, m := range mentionFinderRegex.FindAllStringSubmatch(status, -1) { mentionedAccounts = append(mentionedAccounts, m[1]) } - return Lower(Unique(mentionedAccounts)) + return lower(unique(mentionedAccounts)) } // DeriveHashtags takes a plaintext (ie., not html-formatted) status, @@ -43,10 +43,10 @@ func DeriveMentions(status string) []string { // tags will be lowered, for consistency. func DeriveHashtags(status string) []string { tags := []string{} - for _, m := range hashtagRegex.FindAllStringSubmatch(status, -1) { + for _, m := range hashtagFinderRegex.FindAllStringSubmatch(status, -1) { tags = append(tags, m[1]) } - return Lower(Unique(tags)) + return lower(unique(tags)) } // DeriveEmojis takes a plaintext (ie., not html-formatted) status, @@ -55,14 +55,14 @@ func DeriveHashtags(status string) []string { // emojis will be lowered, for consistency. func DeriveEmojis(status string) []string { emojis := []string{} - for _, m := range emojiRegex.FindAllStringSubmatch(status, -1) { + for _, m := range emojiFinderRegex.FindAllStringSubmatch(status, -1) { emojis = append(emojis, m[1]) } - return Lower(Unique(emojis)) + return lower(unique(emojis)) } -// Unique returns a deduplicated version of a given string slice. -func Unique(s []string) []string { +// unique returns a deduplicated version of a given string slice. +func unique(s []string) []string { keys := make(map[string]bool) list := []string{} for _, entry := range s { @@ -74,8 +74,8 @@ func Unique(s []string) []string { return list } -// Lower lowercases all strings in a given string slice -func Lower(s []string) []string { +// lower lowercases all strings in a given string slice +func lower(s []string) []string { new := []string{} for _, i := range s { new = append(new, strings.ToLower(i)) diff --git a/internal/util/status_test.go b/internal/util/statustools_test.go index 72bd3e885..7c9af2cbd 100644 --- a/internal/util/status_test.go +++ b/internal/util/statustools_test.go @@ -16,13 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package util +package util_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/util" ) type StatusTestSuite struct { @@ -41,7 +42,7 @@ func (suite *StatusTestSuite) TestDeriveMentionsOK() { here is a duplicate mention: @hello@test.lgbt ` - menchies := DeriveMentions(statusText) + menchies := util.DeriveMentions(statusText) assert.Len(suite.T(), menchies, 4) assert.Equal(suite.T(), "@dumpsterqueer@example.org", menchies[0]) assert.Equal(suite.T(), "@someone_else@testing.best-horse.com", menchies[1]) @@ -51,7 +52,7 @@ func (suite *StatusTestSuite) TestDeriveMentionsOK() { func (suite *StatusTestSuite) TestDeriveMentionsEmpty() { statusText := `` - menchies := DeriveMentions(statusText) + menchies := util.DeriveMentions(statusText) assert.Len(suite.T(), menchies, 0) } @@ -66,7 +67,7 @@ func (suite *StatusTestSuite) TestDeriveHashtagsOK() { #111111 thisalsoshouldn'twork#### ##` - tags := DeriveHashtags(statusText) + tags := util.DeriveHashtags(statusText) assert.Len(suite.T(), tags, 5) assert.Equal(suite.T(), "testing123", tags[0]) assert.Equal(suite.T(), "also", tags[1]) @@ -89,7 +90,7 @@ Here's some normal text with an :emoji: at the end :underscores_ok_too: ` - tags := DeriveEmojis(statusText) + tags := util.DeriveEmojis(statusText) assert.Len(suite.T(), tags, 7) assert.Equal(suite.T(), "test", tags[0]) assert.Equal(suite.T(), "another", tags[1]) diff --git a/internal/util/uri.go b/internal/util/uri.go new file mode 100644 index 000000000..9b96edc61 --- /dev/null +++ b/internal/util/uri.go @@ -0,0 +1,218 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package util + +import ( + "fmt" + "net/url" + "strings" +) + +const ( + // UsersPath is for serving users info + UsersPath = "users" + // ActorsPath is for serving actors info + ActorsPath = "actors" + // StatusesPath is for serving statuses + StatusesPath = "statuses" + // InboxPath represents the webfinger inbox location + InboxPath = "inbox" + // OutboxPath represents the webfinger outbox location + OutboxPath = "outbox" + // FollowersPath represents the webfinger followers location + FollowersPath = "followers" + // FollowingPath represents the webfinger following location + FollowingPath = "following" + // LikedPath represents the webfinger liked location + LikedPath = "liked" + // CollectionsPath represents the webfinger collections location + CollectionsPath = "collections" + // FeaturedPath represents the webfinger featured location + FeaturedPath = "featured" + // PublicKeyPath is for serving an account's public key + PublicKeyPath = "publickey" +) + +// APContextKey is a type used specifically for settings values on contexts within go-fed AP request chains +type APContextKey string + +const ( + // APActivity can be used to set and retrieve the actual go-fed pub.Activity within a context. + APActivity APContextKey = "activity" + // APAccount can be used the set and retrieve the account being interacted with + APAccount APContextKey = "account" + // APRequestingAccount can be used to set and retrieve the account of an incoming federation request. + APRequestingAccount APContextKey = "requestingAccount" + // APRequestingPublicKeyID can be used to set and retrieve the public key ID of an incoming federation request. + APRequestingPublicKeyID APContextKey = "requestingPublicKeyID" +) + +type ginContextKey struct{} + +// GinContextKey is used solely for setting and retrieving the gin context from a context.Context +var GinContextKey = &ginContextKey{} + +// UserURIs contains a bunch of UserURIs and URLs for a user, host, account, etc. +type UserURIs struct { + // The web URL of the instance host, eg https://example.org + HostURL string + // The web URL of the user, eg., https://example.org/@example_user + UserURL string + // The web URL for statuses of this user, eg., https://example.org/@example_user/statuses + StatusesURL string + + // The webfinger URI of this user, eg., https://example.org/users/example_user + UserURI string + // The webfinger URI for this user's statuses, eg., https://example.org/users/example_user/statuses + StatusesURI string + // The webfinger URI for this user's activitypub inbox, eg., https://example.org/users/example_user/inbox + InboxURI string + // The webfinger URI for this user's activitypub outbox, eg., https://example.org/users/example_user/outbox + OutboxURI string + // The webfinger URI for this user's followers, eg., https://example.org/users/example_user/followers + FollowersURI string + // The webfinger URI for this user's following, eg., https://example.org/users/example_user/following + FollowingURI string + // The webfinger URI for this user's liked posts eg., https://example.org/users/example_user/liked + LikedURI string + // The webfinger URI for this user's featured collections, eg., https://example.org/users/example_user/collections/featured + CollectionURI string + // The URI for this user's public key, eg., https://example.org/users/example_user/publickey + PublicKeyURI string +} + +// GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host. +func GenerateURIsForAccount(username string, protocol string, host string) *UserURIs { + // The below URLs are used for serving web requests + hostURL := fmt.Sprintf("%s://%s", protocol, host) + userURL := fmt.Sprintf("%s/@%s", hostURL, username) + statusesURL := fmt.Sprintf("%s/%s", userURL, StatusesPath) + + // the below URIs are used in ActivityPub and Webfinger + userURI := fmt.Sprintf("%s/%s/%s", hostURL, UsersPath, username) + statusesURI := fmt.Sprintf("%s/%s", userURI, StatusesPath) + inboxURI := fmt.Sprintf("%s/%s", userURI, InboxPath) + outboxURI := fmt.Sprintf("%s/%s", userURI, OutboxPath) + followersURI := fmt.Sprintf("%s/%s", userURI, FollowersPath) + followingURI := fmt.Sprintf("%s/%s", userURI, FollowingPath) + likedURI := fmt.Sprintf("%s/%s", userURI, LikedPath) + collectionURI := fmt.Sprintf("%s/%s/%s", userURI, CollectionsPath, FeaturedPath) + publicKeyURI := fmt.Sprintf("%s/%s", userURI, PublicKeyPath) + + return &UserURIs{ + HostURL: hostURL, + UserURL: userURL, + StatusesURL: statusesURL, + + UserURI: userURI, + StatusesURI: statusesURI, + InboxURI: inboxURI, + OutboxURI: outboxURI, + FollowersURI: followersURI, + FollowingURI: followingURI, + LikedURI: likedURI, + CollectionURI: collectionURI, + PublicKeyURI: publicKeyURI, + } +} + +// IsUserPath returns true if the given URL path corresponds to eg /users/example_username +func IsUserPath(id *url.URL) bool { + return userPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsInboxPath returns true if the given URL path corresponds to eg /users/example_username/inbox +func IsInboxPath(id *url.URL) bool { + return inboxPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsOutboxPath returns true if the given URL path corresponds to eg /users/example_username/outbox +func IsOutboxPath(id *url.URL) bool { + return outboxPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsInstanceActorPath returns true if the given URL path corresponds to eg /actors/example_username +func IsInstanceActorPath(id *url.URL) bool { + return actorPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsFollowersPath returns true if the given URL path corresponds to eg /users/example_username/followers +func IsFollowersPath(id *url.URL) bool { + return followersPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsFollowingPath returns true if the given URL path corresponds to eg /users/example_username/following +func IsFollowingPath(id *url.URL) bool { + return followingPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked +func IsLikedPath(id *url.URL) bool { + return likedPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS +func IsStatusesPath(id *url.URL) bool { + return statusesPathRegex.MatchString(strings.ToLower(id.Path)) +} + +// ParseStatusesPath returns the username and uuid from a path such as /users/example_username/statuses/SOME_UUID_OF_A_STATUS +func ParseStatusesPath(id *url.URL) (username string, uuid string, err error) { + matches := statusesPathRegex.FindStringSubmatch(id.Path) + if len(matches) != 3 { + err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) + return + } + username = matches[1] + uuid = matches[2] + return +} + +// ParseUserPath returns the username from a path such as /users/example_username +func ParseUserPath(id *url.URL) (username string, err error) { + matches := userPathRegex.FindStringSubmatch(id.Path) + if len(matches) != 2 { + err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) + return + } + username = matches[1] + return +} + +// ParseInboxPath returns the username from a path such as /users/example_username/inbox +func ParseInboxPath(id *url.URL) (username string, err error) { + matches := inboxPathRegex.FindStringSubmatch(id.Path) + if len(matches) != 2 { + err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) + return + } + username = matches[1] + return +} + +// ParseOutboxPath returns the username from a path such as /users/example_username/outbox +func ParseOutboxPath(id *url.URL) (username string, err error) { + matches := outboxPathRegex.FindStringSubmatch(id.Path) + if len(matches) != 2 { + err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) + return + } + username = matches[1] + return +} diff --git a/internal/util/validation.go b/internal/util/validation.go index acf0e68cd..d392231bb 100644 --- a/internal/util/validation.go +++ b/internal/util/validation.go @@ -22,45 +22,22 @@ import ( "errors" "fmt" "net/mail" - "regexp" pwv "github.com/wagslane/go-password-validator" "golang.org/x/text/language" ) -const ( - // MinimumPasswordEntropy dictates password strength. See https://github.com/wagslane/go-password-validator - MinimumPasswordEntropy = 60 - // MinimumReasonLength is the length of chars we expect as a bare minimum effort - MinimumReasonLength = 40 - // MaximumReasonLength is the maximum amount of chars we're happy to accept - MaximumReasonLength = 500 - // MaximumEmailLength is the maximum length of an email address we're happy to accept - MaximumEmailLength = 256 - // MaximumUsernameLength is the maximum length of a username we're happy to accept - MaximumUsernameLength = 64 - // MaximumPasswordLength is the maximum length of a password we're happy to accept - MaximumPasswordLength = 64 - // NewUsernameRegexString is string representation of the regular expression for validating usernames - NewUsernameRegexString = `^[a-z0-9_]+$` -) - -var ( - // NewUsernameRegex is the compiled regex for validating new usernames - NewUsernameRegex = regexp.MustCompile(NewUsernameRegexString) -) - // ValidateNewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok. func ValidateNewPassword(password string) error { if password == "" { return errors.New("no password provided") } - if len(password) > MaximumPasswordLength { - return fmt.Errorf("password should be no more than %d chars", MaximumPasswordLength) + if len(password) > maximumPasswordLength { + return fmt.Errorf("password should be no more than %d chars", maximumPasswordLength) } - return pwv.Validate(password, MinimumPasswordEntropy) + return pwv.Validate(password, minimumPasswordEntropy) } // ValidateUsername makes sure that a given username is valid (ie., letters, numbers, underscores, check length). @@ -70,11 +47,11 @@ func ValidateUsername(username string) error { return errors.New("no username provided") } - if len(username) > MaximumUsernameLength { - return fmt.Errorf("username should be no more than %d chars but '%s' was %d", MaximumUsernameLength, username, len(username)) + if len(username) > maximumUsernameLength { + return fmt.Errorf("username should be no more than %d chars but '%s' was %d", maximumUsernameLength, username, len(username)) } - if !NewUsernameRegex.MatchString(username) { + if !usernameValidationRegex.MatchString(username) { return fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", username) } @@ -88,8 +65,8 @@ func ValidateEmail(email string) error { return errors.New("no email provided") } - if len(email) > MaximumEmailLength { - return fmt.Errorf("email address should be no more than %d chars but '%s' was %d", MaximumEmailLength, email, len(email)) + if len(email) > maximumEmailLength { + return fmt.Errorf("email address should be no more than %d chars but '%s' was %d", maximumEmailLength, email, len(email)) } _, err := mail.ParseAddress(email) @@ -118,12 +95,12 @@ func ValidateSignUpReason(reason string, reasonRequired bool) error { return errors.New("no reason provided") } - if len(reason) < MinimumReasonLength { - return fmt.Errorf("reason should be at least %d chars but '%s' was %d", MinimumReasonLength, reason, len(reason)) + if len(reason) < minimumReasonLength { + return fmt.Errorf("reason should be at least %d chars but '%s' was %d", minimumReasonLength, reason, len(reason)) } - if len(reason) > MaximumReasonLength { - return fmt.Errorf("reason should be no more than %d chars but given reason was %d", MaximumReasonLength, len(reason)) + if len(reason) > maximumReasonLength { + return fmt.Errorf("reason should be no more than %d chars but given reason was %d", maximumReasonLength, len(reason)) } return nil } @@ -150,7 +127,7 @@ func ValidatePrivacy(privacy string) error { // for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters, // lowercase a-z, numbers, and underscores. func ValidateEmojiShortcode(shortcode string) error { - if !emojiShortcodeRegex.MatchString(shortcode) { + if !emojiShortcodeValidationRegex.MatchString(shortcode) { return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, lowercase letters, numbers, and underscores only", shortcode) } return nil diff --git a/internal/util/validation_test.go b/internal/util/validation_test.go index dbac5e248..73f5cb977 100644 --- a/internal/util/validation_test.go +++ b/internal/util/validation_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package util +package util_test import ( "errors" @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/util" ) type ValidationTestSuite struct { @@ -42,42 +43,42 @@ func (suite *ValidationTestSuite) TestCheckPasswordStrength() { strongPassword := "3dX5@Zc%mV*W2MBNEy$@" var err error - err = ValidateNewPassword(empty) + err = util.ValidateNewPassword(empty) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("no password provided"), err) } - err = ValidateNewPassword(terriblePassword) + err = util.ValidateNewPassword(terriblePassword) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("insecure password, try including more special characters, using uppercase letters, using numbers or using a longer password"), err) } - err = ValidateNewPassword(weakPassword) + err = util.ValidateNewPassword(weakPassword) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("insecure password, try including more special characters, using numbers or using a longer password"), err) } - err = ValidateNewPassword(shortPassword) + err = util.ValidateNewPassword(shortPassword) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("insecure password, try including more special characters or using a longer password"), err) } - err = ValidateNewPassword(specialPassword) + err = util.ValidateNewPassword(specialPassword) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("insecure password, try including more special characters or using a longer password"), err) } - err = ValidateNewPassword(longPassword) + err = util.ValidateNewPassword(longPassword) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateNewPassword(tooLong) + err = util.ValidateNewPassword(tooLong) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("password should be no more than 64 chars"), err) } - err = ValidateNewPassword(strongPassword) + err = util.ValidateNewPassword(strongPassword) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } @@ -94,42 +95,42 @@ func (suite *ValidationTestSuite) TestValidateUsername() { goodUsername := "this_is_a_good_username" var err error - err = ValidateUsername(empty) + err = util.ValidateUsername(empty) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("no username provided"), err) } - err = ValidateUsername(tooLong) + err = util.ValidateUsername(tooLong) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("username should be no more than 64 chars but '%s' was 66", tooLong), err) } - err = ValidateUsername(withSpaces) + err = util.ValidateUsername(withSpaces) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", withSpaces), err) } - err = ValidateUsername(weirdChars) + err = util.ValidateUsername(weirdChars) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", weirdChars), err) } - err = ValidateUsername(leadingSpace) + err = util.ValidateUsername(leadingSpace) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", leadingSpace), err) } - err = ValidateUsername(trailingSpace) + err = util.ValidateUsername(trailingSpace) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", trailingSpace), err) } - err = ValidateUsername(newlines) + err = util.ValidateUsername(newlines) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", newlines), err) } - err = ValidateUsername(goodUsername) + err = util.ValidateUsername(goodUsername) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } @@ -144,32 +145,32 @@ func (suite *ValidationTestSuite) TestValidateEmail() { emailAddress := "thisis.actually@anemail.address" var err error - err = ValidateEmail(empty) + err = util.ValidateEmail(empty) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("no email provided"), err) } - err = ValidateEmail(notAnEmailAddress) + err = util.ValidateEmail(notAnEmailAddress) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) } - err = ValidateEmail(almostAnEmailAddress) + err = util.ValidateEmail(almostAnEmailAddress) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("mail: no angle-addr"), err) } - err = ValidateEmail(aWebsite) + err = util.ValidateEmail(aWebsite) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) } - err = ValidateEmail(tooLong) + err = util.ValidateEmail(tooLong) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), fmt.Errorf("email address should be no more than 256 chars but '%s' was 286", tooLong), err) } - err = ValidateEmail(emailAddress) + err = util.ValidateEmail(emailAddress) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } @@ -187,47 +188,47 @@ func (suite *ValidationTestSuite) TestValidateLanguage() { german := "de" var err error - err = ValidateLanguage(empty) + err = util.ValidateLanguage(empty) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("no language provided"), err) } - err = ValidateLanguage(notALanguage) + err = util.ValidateLanguage(notALanguage) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) } - err = ValidateLanguage(english) + err = util.ValidateLanguage(english) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateLanguage(capitalEnglish) + err = util.ValidateLanguage(capitalEnglish) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateLanguage(arabic3Letters) + err = util.ValidateLanguage(arabic3Letters) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateLanguage(mixedCapsEnglish) + err = util.ValidateLanguage(mixedCapsEnglish) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateLanguage(englishUS) + err = util.ValidateLanguage(englishUS) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) } - err = ValidateLanguage(dutch) + err = util.ValidateLanguage(dutch) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateLanguage(german) + err = util.ValidateLanguage(german) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } @@ -241,43 +242,43 @@ func (suite *ValidationTestSuite) TestValidateReason() { var err error // check with no reason required - err = ValidateSignUpReason(empty, false) + err = util.ValidateSignUpReason(empty, false) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateSignUpReason(badReason, false) + err = util.ValidateSignUpReason(badReason, false) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateSignUpReason(tooLong, false) + err = util.ValidateSignUpReason(tooLong, false) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } - err = ValidateSignUpReason(goodReason, false) + err = util.ValidateSignUpReason(goodReason, false) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } // check with reason required - err = ValidateSignUpReason(empty, true) + err = util.ValidateSignUpReason(empty, true) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("no reason provided"), err) } - err = ValidateSignUpReason(badReason, true) + err = util.ValidateSignUpReason(badReason, true) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("reason should be at least 40 chars but 'because' was 7"), err) } - err = ValidateSignUpReason(tooLong, true) + err = util.ValidateSignUpReason(tooLong, true) if assert.Error(suite.T(), err) { assert.Equal(suite.T(), errors.New("reason should be no more than 500 chars but given reason was 600"), err) } - err = ValidateSignUpReason(goodReason, true) + err = util.ValidateSignUpReason(goodReason, true) if assert.NoError(suite.T(), err) { assert.Equal(suite.T(), nil, err) } |