summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/avatar.go148
-rw-r--r--models/avatars/avatar.go180
-rw-r--r--models/avatars/avatar_test.go (renamed from models/avatar_test.go)6
-rw-r--r--models/user_avatar.go65
4 files changed, 205 insertions, 194 deletions
diff --git a/models/avatar.go b/models/avatar.go
deleted file mode 100644
index 71b14ad915..0000000000
--- a/models/avatar.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
- "context"
- "crypto/md5"
- "fmt"
- "net/url"
- "path"
- "strconv"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/cache"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-)
-
-// EmailHash represents a pre-generated hash map
-type EmailHash struct {
- Hash string `xorm:"pk varchar(32)"`
- Email string `xorm:"UNIQUE NOT NULL"`
-}
-
-func init() {
- db.RegisterModel(new(EmailHash))
-}
-
-// DefaultAvatarLink the default avatar link
-func DefaultAvatarLink() string {
- u, err := url.Parse(setting.AppSubURL)
- if err != nil {
- log.Error("GetUserByEmail: %v", err)
- return ""
- }
-
- u.Path = path.Join(u.Path, "/assets/img/avatar_default.png")
- return u.String()
-}
-
-// DefaultAvatarSize is a sentinel value for the default avatar size, as
-// determined by the avatar-hosting service.
-const DefaultAvatarSize = -1
-
-// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
-const DefaultAvatarPixelSize = 28
-
-// AvatarRenderedSizeFactor is the factor by which the default size is increased for finer rendering
-const AvatarRenderedSizeFactor = 4
-
-// HashEmail hashes email address to MD5 string.
-// https://en.gravatar.com/site/implement/hash/
-func HashEmail(email string) string {
- return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
-}
-
-// GetEmailForHash converts a provided md5sum to the email
-func GetEmailForHash(md5Sum string) (string, error) {
- return cache.GetString("Avatar:"+md5Sum, func() (string, error) {
- emailHash := EmailHash{
- Hash: strings.ToLower(strings.TrimSpace(md5Sum)),
- }
-
- _, err := db.GetEngine(db.DefaultContext).Get(&emailHash)
- return emailHash.Email, err
- })
-}
-
-// LibravatarURL returns the URL for the given email. This function should only
-// be called if a federated avatar service is enabled.
-func LibravatarURL(email string) (*url.URL, error) {
- urlStr, err := setting.LibravatarService.FromEmail(email)
- if err != nil {
- log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
- return nil, err
- }
- u, err := url.Parse(urlStr)
- if err != nil {
- log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err)
- return nil, err
- }
- return u, nil
-}
-
-// HashedAvatarLink returns an avatar link for a provided email
-func HashedAvatarLink(email string, size int) string {
- lowerEmail := strings.ToLower(strings.TrimSpace(email))
- sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail)))
- _, _ = cache.GetString("Avatar:"+sum, func() (string, error) {
- emailHash := &EmailHash{
- Email: lowerEmail,
- Hash: sum,
- }
- // OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors
- if err := db.WithTx(func(ctx context.Context) error {
- has, err := db.GetEngine(ctx).Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash))
- if has || err != nil {
- // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
- return nil
- }
- _, _ = db.GetEngine(ctx).Insert(emailHash)
- return nil
- }); err != nil {
- // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
- return lowerEmail, nil
- }
- return lowerEmail, nil
- })
- if size > 0 {
- return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) + "?size=" + strconv.Itoa(size)
- }
- return setting.AppSubURL + "/avatar/" + url.PathEscape(sum)
-}
-
-// MakeFinalAvatarURL constructs the final avatar URL string
-func MakeFinalAvatarURL(u *url.URL, size int) string {
- vals := u.Query()
- vals.Set("d", "identicon")
- if size != DefaultAvatarSize {
- vals.Set("s", strconv.Itoa(size))
- }
- u.RawQuery = vals.Encode()
- return u.String()
-}
-
-// SizedAvatarLink returns a sized link to the avatar for the given email address.
-func SizedAvatarLink(email string, size int) string {
- var avatarURL *url.URL
- if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
- // This is the slow path that would need to call LibravatarURL() which
- // does DNS lookups. Avoid it by issuing a redirect so we don't block
- // the template render with network requests.
- return HashedAvatarLink(email, size)
- } else if !setting.DisableGravatar {
- // copy GravatarSourceURL, because we will modify its Path.
- copyOfGravatarSourceURL := *setting.GravatarSourceURL
- avatarURL = &copyOfGravatarSourceURL
- avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
- } else {
- return DefaultAvatarLink()
- }
-
- return MakeFinalAvatarURL(avatarURL, size)
-}
diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go
new file mode 100644
index 0000000000..0a1445d2f2
--- /dev/null
+++ b/models/avatars/avatar.go
@@ -0,0 +1,180 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package avatars
+
+import (
+ "context"
+ "net/url"
+ "path"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/cache"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
+const DefaultAvatarPixelSize = 28
+
+// AvatarRenderedSizeFactor is the factor by which the default size is increased for finer rendering
+const AvatarRenderedSizeFactor = 4
+
+// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
+type EmailHash struct {
+ Hash string `xorm:"pk varchar(32)"`
+ Email string `xorm:"UNIQUE NOT NULL"`
+}
+
+func init() {
+ db.RegisterModel(new(EmailHash))
+}
+
+// DefaultAvatarLink the default avatar link
+func DefaultAvatarLink() string {
+ u, err := url.Parse(setting.AppSubURL)
+ if err != nil {
+ log.Error("GetUserByEmail: %v", err)
+ return ""
+ }
+
+ u.Path = path.Join(u.Path, "/assets/img/avatar_default.png")
+ return u.String()
+}
+
+// HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/
+func HashEmail(email string) string {
+ return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
+}
+
+// GetEmailForHash converts a provided md5sum to the email
+func GetEmailForHash(md5Sum string) (string, error) {
+ return cache.GetString("Avatar:"+md5Sum, func() (string, error) {
+ emailHash := EmailHash{
+ Hash: strings.ToLower(strings.TrimSpace(md5Sum)),
+ }
+
+ _, err := db.GetEngine(db.DefaultContext).Get(&emailHash)
+ return emailHash.Email, err
+ })
+}
+
+// LibravatarURL returns the URL for the given email. Slow due to the DNS lookup.
+// This function should only be called if a federated avatar service is enabled.
+func LibravatarURL(email string) (*url.URL, error) {
+ urlStr, err := setting.LibravatarService.FromEmail(email)
+ if err != nil {
+ log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
+ return nil, err
+ }
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err)
+ return nil, err
+ }
+ return u, nil
+}
+
+// saveEmailHash returns an avatar link for a provided email,
+// the email and hash are saved into database, which will be used by GetEmailForHash later
+func saveEmailHash(email string) string {
+ lowerEmail := strings.ToLower(strings.TrimSpace(email))
+ emailHash := HashEmail(lowerEmail)
+ _, _ = cache.GetString("Avatar:"+emailHash, func() (string, error) {
+ emailHash := &EmailHash{
+ Email: lowerEmail,
+ Hash: emailHash,
+ }
+ // OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors
+ if err := db.WithTx(func(ctx context.Context) error {
+ has, err := db.GetEngine(ctx).Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash))
+ if has || err != nil {
+ // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
+ return nil
+ }
+ _, _ = db.GetEngine(ctx).Insert(emailHash)
+ return nil
+ }); err != nil {
+ // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
+ return lowerEmail, nil
+ }
+ return lowerEmail, nil
+ })
+ return emailHash
+}
+
+// GenerateUserAvatarFastLink returns a fast link (302) to the user's avatar: "/user/avatar/${User.Name}/${size}"
+func GenerateUserAvatarFastLink(userName string, size int) string {
+ if size < 0 {
+ size = 0
+ }
+ return setting.AppSubURL + "/user/avatar/" + userName + "/" + strconv.Itoa(size)
+}
+
+// GenerateUserAvatarImageLink returns a link for `User.Avatar` image file: "/avatars/${User.Avatar}"
+func GenerateUserAvatarImageLink(userAvatar string, size int) string {
+ if size > 0 {
+ return setting.AppSubURL + "/avatars/" + userAvatar + "?size=" + strconv.Itoa(size)
+ }
+ return setting.AppSubURL + "/avatars/" + userAvatar
+}
+
+// generateRecognizedAvatarURL generate a recognized avatar (Gravatar/Libravatar) URL, it modifies the URL so the parameter is passed by a copy
+func generateRecognizedAvatarURL(u url.URL, size int) string {
+ urlQuery := u.Query()
+ urlQuery.Set("d", "identicon")
+ if size > 0 {
+ urlQuery.Set("s", strconv.Itoa(size))
+ }
+ u.RawQuery = urlQuery.Encode()
+ return u.String()
+}
+
+// generateEmailAvatarLink returns a email avatar link.
+// if final is true, it may use a slow path (eg: query DNS).
+// if final is false, it always uses a fast path.
+func generateEmailAvatarLink(email string, size int, final bool) string {
+ email = strings.TrimSpace(email)
+ if email == "" {
+ return DefaultAvatarLink()
+ }
+
+ var err error
+ if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
+ emailHash := saveEmailHash(email)
+ if final {
+ // for final link, we can spend more time on slow external query
+ var avatarURL *url.URL
+ if avatarURL, err = LibravatarURL(email); err != nil {
+ return DefaultAvatarLink()
+ }
+ return generateRecognizedAvatarURL(*avatarURL, size)
+ }
+ // for non-final link, we should return fast (use a 302 redirection link)
+ urlStr := setting.AppSubURL + "/avatar/" + emailHash
+ if size > 0 {
+ urlStr += "?size=" + strconv.Itoa(size)
+ }
+ return urlStr
+ } else if !setting.DisableGravatar {
+ // copy GravatarSourceURL, because we will modify its Path.
+ avatarURLCopy := *setting.GravatarSourceURL
+ avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
+ return generateRecognizedAvatarURL(avatarURLCopy, size)
+ }
+ return DefaultAvatarLink()
+}
+
+//GenerateEmailAvatarFastLink returns a avatar link (fast, the link may be a delegated one: "/avatar/${hash}")
+func GenerateEmailAvatarFastLink(email string, size int) string {
+ return generateEmailAvatarLink(email, size, false)
+}
+
+//GenerateEmailAvatarFinalLink returns a avatar final link (maybe slow)
+func GenerateEmailAvatarFinalLink(email string, size int) string {
+ return generateEmailAvatarLink(email, size, true)
+}
diff --git a/models/avatar_test.go b/models/avatars/avatar_test.go
index 09f1a8066d..4d6255ca5f 100644
--- a/models/avatar_test.go
+++ b/models/avatars/avatar_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package models
+package avatars
import (
"net/url"
@@ -44,11 +44,11 @@ func TestSizedAvatarLink(t *testing.T) {
disableGravatar()
assert.Equal(t, "/testsuburl/assets/img/avatar_default.png",
- SizedAvatarLink("gitea@example.com", 100))
+ GenerateEmailAvatarFastLink("gitea@example.com", 100))
enableGravatar(t)
assert.Equal(t,
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
- SizedAvatarLink("gitea@example.com", 100),
+ GenerateEmailAvatarFastLink("gitea@example.com", 100),
)
}
diff --git a/models/user_avatar.go b/models/user_avatar.go
index a7ca1c9151..b8296568c2 100644
--- a/models/user_avatar.go
+++ b/models/user_avatar.go
@@ -9,9 +9,8 @@ import (
"fmt"
"image/png"
"io"
- "strconv"
- "strings"
+ "code.gitea.io/gitea/models/avatars"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/log"
@@ -40,7 +39,7 @@ func (u *User) generateRandomAvatar(e db.Engine) error {
return fmt.Errorf("RandomImage: %v", err)
}
- u.Avatar = HashEmail(seed)
+ u.Avatar = avatars.HashEmail(seed)
// Don't share the images so that we can delete them easily
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
@@ -60,61 +59,41 @@ func (u *User) generateRandomAvatar(e db.Engine) error {
return nil
}
-// SizedRelAvatarLink returns a link to the user's avatar via
-// the local explore page. Function returns immediately.
-// When applicable, the link is for an avatar of the indicated size (in pixels).
-func (u *User) SizedRelAvatarLink(size int) string {
- return setting.AppSubURL + "/user/avatar/" + u.Name + "/" + strconv.Itoa(size)
-}
-
-// RealSizedAvatarLink returns a link to the user's avatar. When
-// applicable, the link is for an avatar of the indicated size (in pixels).
-//
-// This function make take time to return when federated avatars
-// are in use, due to a DNS lookup need
-//
-func (u *User) RealSizedAvatarLink(size int) string {
+// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
+func (u *User) AvatarLinkWithSize(size int) string {
if u.ID == -1 {
- return DefaultAvatarLink()
+ // ghost user
+ return avatars.DefaultAvatarLink()
}
+ useLocalAvatar := false
+ autoGenerateAvatar := false
+
switch {
case u.UseCustomAvatar:
- if u.Avatar == "" {
- return DefaultAvatarLink()
- }
- if size > 0 {
- return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size)
- }
- return setting.AppSubURL + "/avatars/" + u.Avatar
+ useLocalAvatar = true
case setting.DisableGravatar, setting.OfflineMode:
- if u.Avatar == "" {
+ useLocalAvatar = true
+ autoGenerateAvatar = true
+ }
+
+ if useLocalAvatar {
+ if u.Avatar == "" && autoGenerateAvatar {
if err := u.GenerateRandomAvatar(); err != nil {
log.Error("GenerateRandomAvatar: %v", err)
}
}
- if size > 0 {
- return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size)
+ if u.Avatar == "" {
+ return avatars.DefaultAvatarLink()
}
- return setting.AppSubURL + "/avatars/" + u.Avatar
+ return avatars.GenerateUserAvatarImageLink(u.Avatar, size)
}
- return SizedAvatarLink(u.AvatarEmail, size)
+ return avatars.GenerateEmailAvatarFastLink(u.AvatarEmail, size)
}
-// RelAvatarLink returns a relative link to the user's avatar. The link
-// may either be a sub-URL to this site, or a full URL to an external avatar
-// service.
-func (u *User) RelAvatarLink() string {
- return u.SizedRelAvatarLink(DefaultAvatarSize)
-}
-
-// AvatarLink returns user avatar absolute link.
+// AvatarLink returns a avatar link with default size
func (u *User) AvatarLink() string {
- link := u.RelAvatarLink()
- if link[0] == '/' && link[1] != '/' {
- return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
- }
- return link
+ return u.AvatarLinkWithSize(0)
}
// UploadAvatar saves custom avatar for user.