Products: chat command filter, request signing, saved state
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2190230f81
commit
0c4f6caec1
4 files changed
+313
No files matched your search
@@ -0,0 +1,68 @@
|
||||
package products
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// IMVU signs the client's requests: X-imvu-auth is md5(user ID + session security key + body), with the query
|
||||
// string added for some calls. The proxy re-signs requests it changes (taking community products out), with the key
|
||||
// from the login response. The key never leaves this machine.
|
||||
|
||||
func sign(user, key string, body []byte, query string, withQuery bool) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(user + key))
|
||||
h.Write(body)
|
||||
if withQuery {
|
||||
h.Write([]byte(query))
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// keys holds each signed-in user's security key.
|
||||
type keys struct {
|
||||
mu sync.Mutex
|
||||
byID map[string]string
|
||||
}
|
||||
|
||||
var securityKeyPattern = regexp.MustCompile(`<name>securityKey</name>\s*<value>(?:<string>)?([0-9A-Za-z]+)`)
|
||||
var userIDPattern = regexp.MustCompile(`<name>userId</name>\s*<value>(?:<int>|<i4>|<string>)?([0-9]+)`)
|
||||
|
||||
// learn takes the user's key from a login response (test.avatarInfoForLogin2).
|
||||
func (k *keys) learn(body []byte) {
|
||||
key, user := securityKeyPattern.FindSubmatch(body), userIDPattern.FindSubmatch(body)
|
||||
if key == nil || user == nil {
|
||||
return
|
||||
}
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
if k.byID == nil {
|
||||
k.byID = map[string]string{}
|
||||
}
|
||||
k.byID[string(user[1])] = string(key[1])
|
||||
}
|
||||
|
||||
// resign recomputes X-imvu-auth for a request whose body or query changed, in the scheme the original used. It
|
||||
// reports false if it couldn't (no key, or the original signature didn't match either scheme).
|
||||
func (k *keys) resign(h http.Header, oldBody []byte, oldQuery string, newBody []byte, newQuery string) bool {
|
||||
auth, user := h.Get("X-imvu-auth"), h.Get("X-imvu-userid")
|
||||
if auth == "" {
|
||||
return true // not signed
|
||||
}
|
||||
k.mu.Lock()
|
||||
key, ok := k.byID[user]
|
||||
k.mu.Unlock()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, withQuery := range []bool{false, true} {
|
||||
if sign(user, key, oldBody, oldQuery, withQuery) == auth {
|
||||
h.Set("X-imvu-auth", sign(user, key, newBody, newQuery, withQuery))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user