Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make avatar lookup occur at image request #10540

Merged
merged 17 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move to use a (potentially cached) db entry for emails
Signed-off-by: Andrew Thornton <[email protected]>
  • Loading branch information
zeripath committed Mar 7, 2020
commit 57339c0f306d16f13194423a70996ae73d0cb9b5
51 changes: 51 additions & 0 deletions models/avatar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 (
"crypto/md5"
"fmt"
"net/url"
"strings"

"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/setting"
)

var ()

// EmailHash represents a pre-generated hash map
type EmailHash struct {
ID int64 `xorm:"pk autoincr"`
zeripath marked this conversation as resolved.
Show resolved Hide resolved
Email string `xorm:"UNIQUE NOT NULL"`
lafriks marked this conversation as resolved.
Show resolved Hide resolved
MD5Sum string `xorm:"md5_sum UNIQUE NOT NULL"`
lafriks marked this conversation as resolved.
Show resolved Hide resolved
}

// GetEmailForHash converts a provided md5sum to the email
func GetEmailForHash(md5Sum string) (string, error) {
return cache.GetString("Avatar:"+md5Sum, func() (string, error) {
emailHash := EmailHash{
MD5Sum: strings.ToLower(strings.TrimSpace(md5Sum)),
}

_, err := x.Get(&emailHash)
return emailHash.Email, err
})
}

// AvatarLink returns an avatar link for a provided email
func AvatarLink(email string) 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,
MD5Sum: sum,
}
_, _ = x.Insert(emailHash)
return lowerEmail, nil
})
return setting.AppSubURL + "/avatar/" + url.PathEscape(sum)
}
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func init() {
new(OAuth2Grant),
new(Task),
new(LanguageStat),
new(EmailHash),
)

gonicNames := []string{"SSL", "UID"}
Expand Down
27 changes: 3 additions & 24 deletions modules/base/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,19 @@ func SizedAvatarLink(email string, size int) string {

// SizedAvatarLinkWithDomain returns a sized link to the avatar for the given email
// address.
func SizedAvatarLinkWithDomain(emailHash, domain string, size int) string {
func SizedAvatarLinkWithDomain(email string, size int) string {
var avatarURL *url.URL
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
var err error
avatarURL, err = libravatarURL("ignoreme@" + domain)
avatarURL, err = libravatarURL(email)
if err != nil {
return DefaultAvatarLink()
}
// now we replace the hash with the correct one...
avatarPath := avatarURL.EscapedPath()
lastSlash := strings.LastIndexByte(avatarPath, '/')
avatarURL.Path = avatarPath[:lastSlash+1] + emailHash
} else if !setting.DisableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
copyOfGravatarSourceURL := *setting.GravatarSourceURL
avatarURL = &copyOfGravatarSourceURL
avatarURL.Path = path.Join(avatarURL.Path, emailHash)
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
} else {
return DefaultAvatarLink()
}
Expand All @@ -225,23 +221,6 @@ func SizedAvatarLinkWithDomain(emailHash, domain string, size int) string {
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 {
lowerEmail := strings.ToLower(strings.TrimSpace(email))
sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail)))
index := strings.IndexByte(email, '@')
domain := ""
if index >= 0 {
domain = email[index+1:]
}
if len(domain) == 0 {
domain = "cdn.libravatar.org"
}
return setting.AppSubURL + "/avatar/" + url.PathEscape(domain) + "/" + url.PathEscape(sum)
}

// FileSize calculates the file size and generate user-friendly string.
func FileSize(s int64) string {
return humanize.IBytes(uint64(s))
Expand Down
13 changes: 0 additions & 13 deletions modules/base/tool_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package base

import (
"crypto/md5"
"fmt"
"net/url"
"testing"

Expand Down Expand Up @@ -92,17 +90,6 @@ func TestSizedAvatarLink(t *testing.T) {
)
}

func TestAvatarLink(t *testing.T) {
disableGravatar()
assert.Equal(t, "/avatar/example.com/"+fmt.Sprintf("%x", md5.Sum([]byte("[email protected]"))), AvatarLink("[email protected]"))

enableGravatar(t)
assert.Equal(t,
"/avatar/example.com/"+fmt.Sprintf("%x", md5.Sum([]byte("[email protected]"))),
AvatarLink("[email protected]"),
)
}

func TestFileSize(t *testing.T) {
var size int64 = 512
assert.Equal(t, "512 B", FileSize(size))
Expand Down
28 changes: 28 additions & 0 deletions modules/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ func NewContext() error {
return err
}

// GetString returns the key value from cache with callback when no key exists in cache
func GetString(key string, getFunc func() (string, error)) (string, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
if !conn.IsExist(key) {
var (
value string
err error
)
if value, err = getFunc(); err != nil {
return value, err
}
err = conn.Put(key, value, int64(setting.CacheService.TTL.Seconds()))
if err != nil {
return "", err
}
}
value := conn.Get(key)
if v, ok := value.(string); ok {
return v, nil
}
if v, ok := value.(fmt.Stringer); ok {
return v.String(), nil
}
return fmt.Sprintf("%s", conn.Get(key)), nil
}

// GetInt returns key value from cache with callback when no key exists in cache
func GetInt(key string, getFunc func() (int, error)) (int, error) {
if conn == nil || setting.CacheService.TTL == 0 {
Expand Down
3 changes: 1 addition & 2 deletions modules/repository/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"time"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -124,7 +123,7 @@ func (pc *PushCommits) AvatarLink(email string) string {
var err error
u, err = models.GetUserByEmail(email)
if err != nil {
pc.avatars[email] = base.AvatarLink(email)
pc.avatars[email] = models.AvatarLink(email)
if !models.IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return ""
Expand Down
2 changes: 1 addition & 1 deletion modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func NewFuncMap() []template.FuncMap {
"AllowedReactions": func() []string {
return setting.UI.Reactions
},
"AvatarLink": base.AvatarLink,
"AvatarLink": models.AvatarLink,
"Safe": Safe,
"SafeJS": SafeJS,
"Str2html": Str2html,
Expand Down
2 changes: 1 addition & 1 deletion routers/repo/blame.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
}
avatar = fmt.Sprintf(`<a href="%s/%s"><img class="ui avatar image" src="%s" title="%s" alt=""/></a>`, setting.AppSubURL, url.PathEscape(commit.User.Name), commit.User.RelAvatarLink(), html.EscapeString(authorName))
} else {
avatar = fmt.Sprintf(`<img class="ui avatar image" src="%s" title="%s"/>`, html.EscapeString(base.AvatarLink(commit.Author.Email)), html.EscapeString(commit.Author.Name))
avatar = fmt.Sprintf(`<img class="ui avatar image" src="%s" title="%s"/>`, html.EscapeString(models.AvatarLink(commit.Author.Email)), html.EscapeString(commit.Author.Name))
}
commitInfo.WriteString(fmt.Sprintf(`<div class="blame-info%s"><div class="blame-data"><div class="blame-avatar">%s</div><div class="blame-message"><a href="%s/commit/%s" title="%[5]s">%[5]s</a></div><div class="blame-time">%s</div></div></div>`, attr, avatar, repoLink, part.Sha, html.EscapeString(commit.CommitMessage), commitSince))
} else {
Expand Down
2 changes: 1 addition & 1 deletion routers/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func RegisterRoutes(m *macaron.Macaron) {
})
// ***** END: User *****

m.Get("/avatar/:domain/:hash", user.AvatarByEmail)
m.Get("/avatar/:hash", user.AvatarByEmail)

adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})

Expand Down
16 changes: 12 additions & 4 deletions routers/user/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,23 @@ func Avatar(ctx *context.Context) {

// AvatarByEmail redirects the browser to the appropriate Avatar link
func AvatarByEmail(ctx *context.Context) {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
domain := ctx.Params(":domain")
hash := ctx.Params(":hash")
if len(domain) == 0 || len(hash) == 0 {
ctx.ServerError("invalid email address", errors.New("email cannot be empty"))
if len(hash) == 0 {
ctx.ServerError("invalid avatar hash", errors.New("hash cannot be empty"))
return
}
email, err := models.GetEmailForHash(hash)
if err != nil {
ctx.ServerError("invalid avatar hash", err)
return
}
if len(email) == 0 {
ctx.Redirect(base.DefaultAvatarLink())
return
}
size := ctx.QueryInt("size")
if size == 0 {
size = base.DefaultAvatarSize
}
ctx.Redirect(base.SizedAvatarLinkWithDomain(hash, domain, size))
ctx.Redirect(base.SizedAvatarLinkWithDomain(email, size))
}