Skip to content

Commit ab62da2

Browse files
ethantkoeniglunny
authored andcommitted
Fix avatar URLs (#3069)
* Fix avatar URLs * import order
1 parent 7bab3d2 commit ab62da2

File tree

8 files changed

+107
-33
lines changed

8 files changed

+107
-33
lines changed

models/user.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,9 @@ func (u *User) generateRandomAvatar(e Engine) error {
315315
return nil
316316
}
317317

318-
// RelAvatarLink returns relative avatar link to the site domain,
319-
// which includes app sub-url as prefix. However, it is possible
320-
// to return full URL if user enables Gravatar-like service.
321-
func (u *User) RelAvatarLink() string {
318+
// SizedRelAvatarLink returns a relative link to the user's avatar. When
319+
// applicable, the link is for an avatar of the indicated size (in pixels).
320+
func (u *User) SizedRelAvatarLink(size int) string {
322321
if u.ID == -1 {
323322
return base.DefaultAvatarLink()
324323
}
@@ -338,7 +337,14 @@ func (u *User) RelAvatarLink() string {
338337

339338
return setting.AppSubURL + "/avatars/" + u.Avatar
340339
}
341-
return base.AvatarLink(u.AvatarEmail)
340+
return base.SizedAvatarLink(u.AvatarEmail, size)
341+
}
342+
343+
// RelAvatarLink returns a relative link to the user's avatar. The link
344+
// may either be a sub-URL to this site, or a full URL to an external avatar
345+
// service.
346+
func (u *User) RelAvatarLink() string {
347+
return u.SizedRelAvatarLink(base.DefaultAvatarSize)
342348
}
343349

344350
// AvatarLink returns user avatar absolute link.

modules/base/tool.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"math"
1717
"math/big"
1818
"net/http"
19+
"net/url"
20+
"path"
1921
"strconv"
2022
"strings"
2123
"time"
@@ -197,24 +199,59 @@ func DefaultAvatarLink() string {
197199
return setting.AppSubURL + "/img/avatar_default.png"
198200
}
199201

200-
// AvatarLink returns relative avatar link to the site domain by given email,
201-
// which includes app sub-url as prefix. However, it is possible
202-
// to return full URL if user enables Gravatar-like service.
203-
func AvatarLink(email string) string {
202+
// DefaultAvatarSize is a sentinel value for the default avatar size, as
203+
// determined by the avatar-hosting service.
204+
const DefaultAvatarSize = -1
205+
206+
// libravatarURL returns the URL for the given email. This function should only
207+
// be called if a federated avatar service is enabled.
208+
func libravatarURL(email string) (*url.URL, error) {
209+
urlStr, err := setting.LibravatarService.FromEmail(email)
210+
if err != nil {
211+
log.Error(4, "LibravatarService.FromEmail(email=%s): error %v", email, err)
212+
return nil, err
213+
}
214+
u, err := url.Parse(urlStr)
215+
if err != nil {
216+
log.Error(4, "Failed to parse libravatar url(%s): error %v", urlStr, err)
217+
return nil, err
218+
}
219+
return u, nil
220+
}
221+
222+
// SizedAvatarLink returns a sized link to the avatar for the given email
223+
// address.
224+
func SizedAvatarLink(email string, size int) string {
225+
var avatarURL *url.URL
204226
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
205-
url, err := setting.LibravatarService.FromEmail(email)
227+
var err error
228+
avatarURL, err = libravatarURL(email)
206229
if err != nil {
207-
log.Error(4, "LibravatarService.FromEmail(email=%s): error %v", email, err)
208230
return DefaultAvatarLink()
209231
}
210-
return url
232+
} else if !setting.DisableGravatar {
233+
// copy GravatarSourceURL, because we will modify its Path.
234+
copyOfGravatarSourceURL := *setting.GravatarSourceURL
235+
avatarURL = &copyOfGravatarSourceURL
236+
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
237+
} else {
238+
return DefaultAvatarLink()
211239
}
212240

213-
if !setting.DisableGravatar {
214-
return setting.GravatarSource + HashEmail(email) + "?d=identicon"
241+
vals := avatarURL.Query()
242+
vals.Set("d", "identicon")
243+
if size != DefaultAvatarSize {
244+
vals.Set("s", strconv.Itoa(size))
215245
}
246+
avatarURL.RawQuery = vals.Encode()
247+
return avatarURL.String()
248+
}
216249

217-
return DefaultAvatarLink()
250+
// AvatarLink returns relative avatar link to the site domain by given email,
251+
// which includes app sub-url as prefix. However, it is possible
252+
// to return full URL if user enables Gravatar-like service.
253+
func AvatarLink(email string) string {
254+
return SizedAvatarLink(email, DefaultAvatarSize)
218255
}
219256

220257
// Seconds-based time units

modules/base/tool_test.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package base
22

33
import (
4+
"net/url"
45
"os"
56
"testing"
67
"time"
78

89
"code.gitea.io/gitea/modules/setting"
10+
911
"github.com/Unknwon/i18n"
1012
macaroni18n "github.com/go-macaron/i18n"
1113
"github.com/stretchr/testify/assert"
@@ -126,16 +128,40 @@ func TestHashEmail(t *testing.T) {
126128
)
127129
}
128130

129-
func TestAvatarLink(t *testing.T) {
131+
const gravatarSource = "https://secure.gravatar.com/avatar/"
132+
133+
func disableGravatar() {
130134
setting.EnableFederatedAvatar = false
131135
setting.LibravatarService = nil
132136
setting.DisableGravatar = true
137+
}
133138

134-
assert.Equal(t, "/img/avatar_default.png", AvatarLink(""))
135-
139+
func enableGravatar(t *testing.T) {
136140
setting.DisableGravatar = false
141+
var err error
142+
setting.GravatarSourceURL, err = url.Parse(gravatarSource)
143+
assert.NoError(t, err)
144+
}
145+
146+
func TestSizedAvatarLink(t *testing.T) {
147+
disableGravatar()
148+
assert.Equal(t, "/img/avatar_default.png",
149+
SizedAvatarLink("gitea@example.com", 100))
150+
151+
enableGravatar(t)
152+
assert.Equal(t,
153+
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
154+
SizedAvatarLink("gitea@example.com", 100),
155+
)
156+
}
157+
158+
func TestAvatarLink(t *testing.T) {
159+
disableGravatar()
160+
assert.Equal(t, "/img/avatar_default.png", AvatarLink("gitea@example.com"))
161+
162+
enableGravatar(t)
137163
assert.Equal(t,
138-
"353cbad9b58e69c96154ad99f92bedc7?d=identicon",
164+
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon",
139165
AvatarLink("gitea@example.com"),
140166
)
141167
}

modules/setting/setting.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ var (
326326
// Picture settings
327327
AvatarUploadPath string
328328
GravatarSource string
329+
GravatarSourceURL *url.URL
329330
DisableGravatar bool
330331
EnableFederatedAvatar bool
331332
LibravatarService *libravatar.Libravatar
@@ -1027,18 +1028,22 @@ func NewContext() {
10271028
if DisableGravatar {
10281029
EnableFederatedAvatar = false
10291030
}
1031+
if EnableFederatedAvatar || !DisableGravatar {
1032+
GravatarSourceURL, err = url.Parse(GravatarSource)
1033+
if err != nil {
1034+
log.Fatal(4, "Failed to parse Gravatar URL(%s): %v",
1035+
GravatarSource, err)
1036+
}
1037+
}
10301038

10311039
if EnableFederatedAvatar {
10321040
LibravatarService = libravatar.New()
1033-
parts := strings.Split(GravatarSource, "/")
1034-
if len(parts) >= 3 {
1035-
if parts[0] == "https:" {
1036-
LibravatarService.SetUseHTTPS(true)
1037-
LibravatarService.SetSecureFallbackHost(parts[2])
1038-
} else {
1039-
LibravatarService.SetUseHTTPS(false)
1040-
LibravatarService.SetFallbackHost(parts[2])
1041-
}
1041+
if GravatarSourceURL.Scheme == "https" {
1042+
LibravatarService.SetUseHTTPS(true)
1043+
LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
1044+
} else {
1045+
LibravatarService.SetUseHTTPS(false)
1046+
LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
10421047
}
10431048
}
10441049

templates/org/header.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="ui vertically grid head">
44
<div class="column">
55
<div class="ui header">
6-
<img class="ui image" src="{{.RelAvatarLink}}?s=100">
6+
<img class="ui image" src="{{.SizedRelAvatarLink 100}}">
77
<span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span>
88

99
<div class="ui right">

templates/org/home.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="ui container">
44
<div class="ui grid">
55
<div class="ui sixteen wide column">
6-
<img class="ui left" id="org-avatar" src="{{.Org.RelAvatarLink}}?s=140"/>
6+
<img class="ui left" id="org-avatar" src="{{.Org.SizedRelAvatarLink 140}}"/>
77
<div id="org-info">
88
<div class="ui header">
99
{{.Org.DisplayName}}

templates/org/member/members.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{{range .Members}}
99
<div class="item ui grid">
1010
<div class="ui one wide column">
11-
<img class="ui avatar" src="{{.RelAvatarLink}}?s=48">
11+
<img class="ui avatar" src="{{.SizedRelAvatarLink 48}}">
1212
</div>
1313
<div class="ui three wide column">
1414
<div class="meta"><a href="{{.HomeLink}}">{{.Name}}</a></div>

templates/user/profile.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
<div class="ui card">
77
{{if eq .SignedUserName .Owner.Name}}
88
<a class="image poping up" href="{{AppSubUrl}}/user/settings/avatar" id="profile-avatar" data-content="{{.i18n.Tr "user.change_avatar"}}" data-variation="inverted tiny" data-position="bottom center">
9-
<img src="{{.Owner.RelAvatarLink}}?s=290" title="{{.Owner.Name}}"/>
9+
<img src="{{.Owner.SizedRelAvatarLink 290}}" title="{{.Owner.Name}}"/>
1010
</a>
1111
{{else}}
1212
<span class="image">
13-
<img src="{{.Owner.RelAvatarLink}}?s=290" title="{{.Owner.Name}}"/>
13+
<img src="{{.Owner.SizedRelAvatarLink 290}}" title="{{.Owner.Name}}"/>
1414
</span>
1515
{{end}}
1616
<div class="content">

0 commit comments

Comments
 (0)