-
Notifications
You must be signed in to change notification settings - Fork 3
/
libravatar-server.go
196 lines (175 loc) · 8.36 KB
/
libravatar-server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// This implements a minimalistic Libravatar-compatible, federated server, which will run as part of gOSWI.
// It returns Profile images from OpenSim to be used as Libravatars.
// For this to work, DNS needs to be properly setup with:
// (... mumble mumble needs further explanation... )
//
// Partially based on Surrogator, written in PHP by Christian Weiske ([email protected]) and licensed under the AGPL v3
/* Lots of things to do here */
package main
import (
"database/sql"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/gabriel-vasile/mimetype"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
// "gopkg.in/gographics/imagick.v3/imagick" // not needed since we call the conversion function (gwyneth 20200910)
)
// Database fields that we will retrieve later for checking email, names, profile image, etc.
type LibravatarProfile struct {
ProfileID, FirstName, LastName, Email, ProfileImage string
}
// LibravatarParams is required by the ShouldBindURI() call, it cannot be a simple string for some reason...
type LibravatarParams struct {
Hash string `uri:"hash"`
}
// Libravatar is the handle, being called with ../avatar/<hash>?s=<size>&d=<default> .
func Libravatar(c *gin.Context) {
// start by parsing what we can
var params LibravatarParams // see what parameters we have here; at least we ought to get the hash...
if err := c.ShouldBindUri(¶ms); err != nil {
// this is fatal!
c.String(http.StatusInternalServerError, "Libravatar: Cannot bind to hash parameters")
return
}
//var size uint
size, err := strconv.ParseUint(c.DefaultQuery("s", "80"), 10, 0)
if size == 80 {
size, err = strconv.ParseUint(c.DefaultQuery("size", "80"), 10, 0)
}
if err != nil {
log.Println("[WARN] Libravatar: size is not an integer, 80 assumed")
size = 80
}
var defaultParam = c.DefaultQuery("d", "")
if defaultParam == "" {
defaultParam = c.DefaultQuery("default", "")
}
// create filename (it's horrible, but that's how both Gravatar + Libravatar work) (gwyneth 20200908)
profileImageFilename := strings.TrimPrefix(c.Request.URL.RequestURI(), "/avatar/")
if *config["ginMode"] == "debug" {
log.Println("[DEBUG] PathToStaticFiles is", PathToStaticFiles, "and profileImageFilename is now", profileImageFilename)
}
// check if image exists on the diskv cache; code shares similarities with profile.go (gwyneth 20200908)
profileImage := filepath.Join( /* PathToStaticFiles, */ *config["cache"], profileImageFilename)
if imageCache.Has(profileImage) {
if *config["ginMode"] == "debug" {
log.Println("[DEBUG] Libravatar: returning file", profileImage)
}
// c.Header("Content-Transfer-Encoding", "binary")
// c.Header("Content-Type", "image/png")
// c.File(profileImage)
// assemble path to static file on disk, because, path complications (gwyneth 20200908).
pathToProfileImage := filepath.Clean(filepath.Join(PathToStaticFiles, profileImage))
if *config["ginMode"] == "debug" {
log.Printf("[DEBUG] Libravatar: pathToProfileImage is now %q\n", pathToProfileImage)
}
if fileContent, err := os.ReadFile(pathToProfileImage); err == nil {
mime := mimetype.Detect(fileContent)
if *config["ginMode"] == "debug" {
log.Printf("[DEBUG] Libravatar: file %q for profileImage %q is about to be returned, MIME type is %q, file size is %d\n", pathToProfileImage, profileImage, mime.String(), len(fileContent))
}
c.Data(http.StatusOK, mime.String(), fileContent) // note: mime.String() will return "application/octet-stream" if the image type was not detected
return
} else {
c.String(http.StatusNotFound, fmt.Sprintf("Libravatar: file not found for received hash: %q; desired size is: %d and default param is %q\n", params.Hash, size, defaultParam))
log.Printf("[ERROR] Libravatar: imageCache error; file %q is in hash table but %q is not on filesystem! Error was: %v\n",
profileImage, pathToProfileImage, err)
// this probably means that the imageCache is corrupted, e.g. it has keys for non-existing files
return
}
} else {
/*
Image not found in cache, let's get it from OpenSimulator!
Again, this is very similar to profile.go (gwyneth 20200908).
The difference is that we need to do the following:
- Check entries on the database
- See if we get a match on the hash for the email address stored on the database (try MD5 or SHA256 depending on key size)
- If not, check for a hash of (email) AvatarFirstName.AvatarLastName@<hostname> and/or (OpenID) <hostname>/AvatarFirstName.AvatarLastName
*/
var (
hashType = "MD5" // try this first
oneLibravatarProfile LibravatarProfile
// username string // not needed yet!
)
if len(params.Hash) > 32 {
hashType = "SHA256"
}
// open database connection
if *config["dsn"] == "" {
log.Fatal("Please configure the DSN for accessing your OpenSimulator database; this application won't work without that")
}
db, err := sql.Open("mysql", *config["dsn"]) // presumes mysql for now
checkErrFatal(err)
defer db.Close()
err = db.QueryRow("SELECT PrincipalID, FirstName, LastName, Email, profileImage FROM UserAccounts, userprofile WHERE "+hashType+" (LOWER(Email)) = ? AND useruuid = PrincipalID AND profileImage <> '00000000-0000-0000-0000-000000000000'", params.Hash).Scan(
&oneLibravatarProfile.ProfileID,
&oneLibravatarProfile.FirstName,
&oneLibravatarProfile.LastName,
&oneLibravatarProfile.Email,
&oneLibravatarProfile.ProfileImage,
)
if err != nil { // db.QueryRow() will return ErrNoRows, which will be passed to Scan()
if *config["ginMode"] == "debug" {
log.Printf("[ERROR] Libravatar: retrieving profile for hash %q failed; database error was %v\n", params.Hash, err)
}
// no rows found, so we can assume that either the email is NULL or possibly there isn't a profileImage
// First we will attempt to do some hashing on the 'fake' email:
} else {
// match found for email on database!
if *config["ginMode"] == "debug" {
log.Printf("[DEBUG] Libravatar: retrieving profile for hash %q: %+v\n", params.Hash, oneLibravatarProfile)
}
// get the image from OpenSimulator!
profileImageAssetURL := *config["assetServer"] + path.Join("/assets/", oneLibravatarProfile.ProfileImage, "/data")
resp, err := http.Get(profileImageAssetURL)
if err != nil {
// handle error
log.Println("[ERROR] Libravatar: Oops — OpenSimulator cannot find", profileImageAssetURL, "error was:", err)
}
defer resp.Body.Close()
newImage, err := io.ReadAll(resp.Body)
if err != nil {
log.Println("[ERROR] Libravatar: Oops — could not get contents of", profileImageAssetURL, "from OpenSimulator, error was:", err)
}
if len(newImage) == 0 {
log.Println("[ERROR] Libravatar: Image retrieved from OpenSimulator", profileImageAssetURL, "has zero bytes.")
// we might have to get out of here
} else {
if *config["ginMode"] == "debug" {
log.Println("[INFO] Libravatar: Image retrieved from OpenSimulator", profileImageAssetURL, "has", len(newImage), "bytes.")
}
}
// Now use ImageMagick to convert this image!
// Unlike what happened on GetProfile(), here we're ignoring the Retina version (gwyneth 20200910)
convertedImage, _, err := ImageConvert(newImage, uint(size), uint(size), 100)
if err != nil {
log.Println("[ERROR] Libravatar: Could not convert", profileImageAssetURL, " - error was:", err)
}
if /* convertedImage == nil || */ len(convertedImage) == 0 {
log.Println("[ERROR] Libravatar: Converted image is empty")
}
if *config["ginMode"] == "debug" {
log.Println("[INFO] Libravatar: Regular image from", profileImageAssetURL, "has", len(convertedImage), "bytes.")
}
// put it into KV cache:
if err := imageCache.Write(profileImage, convertedImage); err != nil {
log.Println("[ERROR] Libravatar: Could not store converted", profileImage, "in the cache, error was:", err)
}
mime := mimetype.Detect(convertedImage)
if *config["ginMode"] == "debug" {
log.Printf("[DEBUG] Libravatar: file for profileImage %q is about to be returned, MIME type is %q, file size is %d\n", profileImage, mime.String(), len(convertedImage))
}
c.Data(http.StatusOK, mime.String(), convertedImage)
return
}
}
c.String(http.StatusNotFound, fmt.Sprintf("Libravatar: File not in image cache but could not retrieve it anyway; received hash was %q; desired size was: %d and default param was %q\n", params.Hash, size, defaultParam))
}