gerrit: Add support for Digest Authorization

The existing implementation doesn't support the HTTP Digest Authorization
that widely used in docker-based Gerrit configuration

Proposed code is based on http://play.golang.org/p/ABoHSHoTmu

Change-Id: Ia01d03cc849a4fcd538b05a60b83ac7e18809d5a
Reviewed-on: https://go-review.googlesource.com/29295
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/gerrit/auth.go b/gerrit/auth.go
index 527249e..5718c6e 100644
--- a/gerrit/auth.go
+++ b/gerrit/auth.go
@@ -5,6 +5,9 @@
 package gerrit
 
 import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
 	"fmt"
 	"io/ioutil"
 	"log"
@@ -171,3 +174,90 @@
 type noAuth struct{}
 
 func (noAuth) setAuth(c *Client, r *http.Request) {}
+
+type digestAuth struct {
+	Username, Password, Realm, NONCE, QOP, Opaque, Algorithm string
+}
+
+func getDigestAuth(username, password string, resp *http.Response) *digestAuth {
+	header := resp.Header.Get("www-authenticate")
+	parts := strings.SplitN(header, " ", 2)
+	parts = strings.Split(parts[1], ", ")
+	opts := make(map[string]string)
+
+	for _, part := range parts {
+		vals := strings.SplitN(part, "=", 2)
+		key := vals[0]
+		val := strings.Trim(vals[1], "\",")
+		opts[key] = val
+	}
+
+	auth := digestAuth{
+		username, password,
+		opts["realm"], opts["nonce"], opts["qop"], opts["opaque"], opts["algorithm"],
+	}
+	return &auth
+}
+
+func setDigestAuth(r *http.Request, username, password string, resp *http.Response, nc int) {
+	auth := getDigestAuth(username, password, resp)
+	authStr := getDigestAuthString(auth, r.URL, r.Method, nc)
+	r.Header.Add("Authorization", authStr)
+}
+
+func getDigestAuthString(auth *digestAuth, url *url.URL, method string, nc int) string {
+	var buf bytes.Buffer
+	h := md5.New()
+	fmt.Fprintf(&buf, "%s:%s:%s", auth.Username, auth.Realm, auth.Password)
+	buf.WriteTo(h)
+	ha1 := hex.EncodeToString(h.Sum(nil))
+
+	h = md5.New()
+	fmt.Fprintf(&buf, "%s:%s", method, url.Path)
+	buf.WriteTo(h)
+	ha2 := hex.EncodeToString(h.Sum(nil))
+
+	ncStr := fmt.Sprintf("%08x", nc)
+	hnc := "MTM3MDgw"
+
+	h = md5.New()
+	fmt.Fprintf(&buf, "%s:%s:%s:%s:%s:%s", ha1, auth.NONCE, ncStr, hnc, auth.QOP, ha2)
+	buf.WriteTo(h)
+	respdig := hex.EncodeToString(h.Sum(nil))
+
+	buf.Write([]byte("Digest "))
+	fmt.Fprintf(&buf,
+		`username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`,
+		auth.Username, auth.Realm, auth.NONCE, url.Path, respdig,
+	)
+
+	if auth.Opaque != "" {
+		fmt.Fprintf(&buf, `, opaque="%s"`, auth.Opaque)
+	}
+	if auth.QOP != "" {
+		fmt.Fprintf(&buf, `, qop="%s", nc=%s, cnonce="%s"`, auth.QOP, ncStr, hnc)
+	}
+	if auth.Algorithm != "" {
+		fmt.Fprintf(&buf, `, algorithm="%s"`, auth.Algorithm)
+	}
+
+	return buf.String()
+}
+
+func (a digestAuth) setAuth(c *Client, r *http.Request) {
+	resp, err := http.Get(r.URL.String())
+	if err != nil {
+		return
+	}
+	setDigestAuth(r, a.Username, a.Password, resp, 1)
+}
+
+// DigestAuth returns an Auth implementation which sends
+// the provided username and password using HTTP Digest Authentication
+// (RFC 2617)
+func DigestAuth(username, password string) Auth {
+	return digestAuth{
+		Username: username,
+		Password: password,
+	}
+}