diff --git a/models/user.go b/models/user.go index f3a74f5e08..31af3747c0 100644 --- a/models/user.go +++ b/models/user.go @@ -315,10 +315,9 @@ func (u *User) generateRandomAvatar(e Engine) error { return nil } -// RelAvatarLink returns relative avatar link to the site domain, -// which includes app sub-url as prefix. However, it is possible -// to return full URL if user enables Gravatar-like service. -func (u *User) RelAvatarLink() string { +// SizedRelAvatarLink returns a relative link to the user's avatar. When +// applicable, the link is for an avatar of the indicated size (in pixels). +func (u *User) SizedRelAvatarLink(size int) string { if u.ID == -1 { return base.DefaultAvatarLink() } @@ -338,7 +337,14 @@ func (u *User) RelAvatarLink() string { return setting.AppSubURL + "/avatars/" + u.Avatar } - return base.AvatarLink(u.AvatarEmail) + return base.SizedAvatarLink(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(base.DefaultAvatarSize) } // AvatarLink returns user avatar absolute link. diff --git a/modules/base/tool.go b/modules/base/tool.go index 194db772cf..1316b8fad3 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -16,6 +16,8 @@ import ( "math" "math/big" "net/http" + "net/url" + "path" "strconv" "strings" "time" @@ -197,24 +199,59 @@ func DefaultAvatarLink() string { return setting.AppSubURL + "/img/avatar_default.png" } +// DefaultAvatarSize is a sentinel value for the default avatar size, as +// determined by the avatar-hosting service. +const DefaultAvatarSize = -1 + +// 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(4, "LibravatarService.FromEmail(email=%s): error %v", email, err) + return nil, err + } + u, err := url.Parse(urlStr) + if err != nil { + log.Error(4, "Failed to parse libravatar url(%s): error %v", urlStr, err) + return nil, err + } + return u, nil +} + +// 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 { + var err error + avatarURL, err = libravatarURL(email) + if err != nil { + return DefaultAvatarLink() + } + } else if !setting.DisableGravatar { + // copy GravatarSourceURL, because we will modify its Path. + copyOfGravatarSourceURL := *setting.GravatarSourceURL + avatarURL = ©OfGravatarSourceURL + avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email)) + } else { + return DefaultAvatarLink() + } + + vals := avatarURL.Query() + vals.Set("d", "identicon") + if size != DefaultAvatarSize { + vals.Set("s", strconv.Itoa(size)) + } + avatarURL.RawQuery = vals.Encode() + return avatarURL.String() +} + // AvatarLink returns relative avatar link to the site domain by given email, // which includes app sub-url as prefix. However, it is possible // to return full URL if user enables Gravatar-like service. func AvatarLink(email string) string { - if setting.EnableFederatedAvatar && setting.LibravatarService != nil { - url, err := setting.LibravatarService.FromEmail(email) - if err != nil { - log.Error(4, "LibravatarService.FromEmail(email=%s): error %v", email, err) - return DefaultAvatarLink() - } - return url - } - - if !setting.DisableGravatar { - return setting.GravatarSource + HashEmail(email) + "?d=identicon" - } - - return DefaultAvatarLink() + return SizedAvatarLink(email, DefaultAvatarSize) } // Seconds-based time units diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 44ea309f7c..ffa17fae00 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -1,11 +1,13 @@ package base import ( + "net/url" "os" "testing" "time" "code.gitea.io/gitea/modules/setting" + "github.com/Unknwon/i18n" macaroni18n "github.com/go-macaron/i18n" "github.com/stretchr/testify/assert" @@ -126,16 +128,40 @@ func TestHashEmail(t *testing.T) { ) } -func TestAvatarLink(t *testing.T) { +const gravatarSource = "https://secure.gravatar.com/avatar/" + +func disableGravatar() { setting.EnableFederatedAvatar = false setting.LibravatarService = nil setting.DisableGravatar = true +} - assert.Equal(t, "/img/avatar_default.png", AvatarLink("")) - +func enableGravatar(t *testing.T) { setting.DisableGravatar = false + var err error + setting.GravatarSourceURL, err = url.Parse(gravatarSource) + assert.NoError(t, err) +} + +func TestSizedAvatarLink(t *testing.T) { + disableGravatar() + assert.Equal(t, "/img/avatar_default.png", + SizedAvatarLink("gitea@example.com", 100)) + + enableGravatar(t) assert.Equal(t, - "353cbad9b58e69c96154ad99f92bedc7?d=identicon", + "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100", + SizedAvatarLink("gitea@example.com", 100), + ) +} + +func TestAvatarLink(t *testing.T) { + disableGravatar() + assert.Equal(t, "/img/avatar_default.png", AvatarLink("gitea@example.com")) + + enableGravatar(t) + assert.Equal(t, + "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon", AvatarLink("gitea@example.com"), ) } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index db6f749c06..f8da952413 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -326,6 +326,7 @@ var ( // Picture settings AvatarUploadPath string GravatarSource string + GravatarSourceURL *url.URL DisableGravatar bool EnableFederatedAvatar bool LibravatarService *libravatar.Libravatar @@ -1027,18 +1028,22 @@ func NewContext() { if DisableGravatar { EnableFederatedAvatar = false } + if EnableFederatedAvatar || !DisableGravatar { + GravatarSourceURL, err = url.Parse(GravatarSource) + if err != nil { + log.Fatal(4, "Failed to parse Gravatar URL(%s): %v", + GravatarSource, err) + } + } if EnableFederatedAvatar { LibravatarService = libravatar.New() - parts := strings.Split(GravatarSource, "/") - if len(parts) >= 3 { - if parts[0] == "https:" { - LibravatarService.SetUseHTTPS(true) - LibravatarService.SetSecureFallbackHost(parts[2]) - } else { - LibravatarService.SetUseHTTPS(false) - LibravatarService.SetFallbackHost(parts[2]) - } + if GravatarSourceURL.Scheme == "https" { + LibravatarService.SetUseHTTPS(true) + LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host) + } else { + LibravatarService.SetUseHTTPS(false) + LibravatarService.SetFallbackHost(GravatarSourceURL.Host) } } diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl index 0192ad7d82..806682aca9 100644 --- a/templates/org/header.tmpl +++ b/templates/org/header.tmpl @@ -3,7 +3,7 @@