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(`securityKey\s*(?:)?([0-9A-Za-z]+)`) var userIDPattern = regexp.MustCompile(`userId\s*(?:||)?([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 }