From f43a0a4cf8d7ba4460eba789c2a27d265537c765 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Sun, 8 Jul 2018 15:25:00 +0300 Subject: [PATCH 1/3] quick bugfix for issue #20 (#21) issue title: "Can't upload file with content" https://github.com/studio-b12/gowebdav/issues/20 --- requests.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/requests.go b/requests.go index 5e047e8..73db2c8 100644 --- a/requests.go +++ b/requests.go @@ -10,10 +10,16 @@ import ( func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) { // Tee the body, because if authorization fails we will need to read from it again. + var r *http.Request var ba bytes.Buffer bb := io.TeeReader(body, &ba) - r, err := http.NewRequest(method, PathEscape(Join(c.root, path)), &ba) + if body == nil { + r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), nil) + } else { + r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), bb) + } + if err != nil { return nil, err } @@ -40,7 +46,13 @@ func (c *Client) req(method, path string, body io.Reader, intercept func(*http.R } else { return rs, newPathError("Authorize", c.root, rs.StatusCode) } - return c.req(method, path, bb, intercept) + + if body == nil { + return c.req(method, path, nil, intercept) + } else { + return c.req(method, path, &ba, intercept) + } + } else if rs.StatusCode == 401 { return rs, newPathError("Authorize", c.root, rs.StatusCode) } From 95706c074714b966e543d7dee404ba83ca84c630 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Sun, 8 Jul 2018 15:27:10 +0300 Subject: [PATCH 2/3] .gitignore was expanded (#18) --- .gitignore | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.gitignore b/.gitignore index 5fc5eda..3ae0f0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,19 @@ +# Folders to ignore /src /bin /pkg /gowebdav +/.idea + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out From ec1263db2f56e2c3155e5540a8a641d406f01a45 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Tue, 10 Jul 2018 19:51:11 +0300 Subject: [PATCH 3/3] all cases of Digest authorization was implemented (#19) Digest authentication was improved --- digestAuth.go | 76 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/digestAuth.go b/digestAuth.go index dbf8660..a75fdf2 100644 --- a/digestAuth.go +++ b/digestAuth.go @@ -44,12 +44,15 @@ func (d *DigestAuth) Authorize(c *Client, method string, path string) { func digestParts(resp *http.Response) map[string]string { result := map[string]string{} if len(resp.Header["Www-Authenticate"]) > 0 { - wantedHeaders := []string{"nonce", "realm", "qop", "opaque"} + wantedHeaders := []string{"nonce", "realm", "qop", "opaque", "algorithm", "entityBody"} responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",") for _, r := range responseHeaders { for _, w := range wantedHeaders { if strings.Contains(r, w) { - result[w] = strings.Split(r, `"`)[1] + result[w] = strings.Trim( + strings.SplitN(r, `=`, 2)[1], + `"`, + ) } } } @@ -72,13 +75,68 @@ func getCnonce() string { func getDigestAuthorization(digestParts map[string]string) string { d := digestParts // These are the correct ha1 and ha2 for qop=auth. We should probably check for other types of qop. - ha1 := getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"]) - ha2 := getMD5(d["method"] + ":" + d["uri"]) - nonceCount := 00000001 - cnonce := getCnonce() - response := getMD5(fmt.Sprintf("%s:%s:%v:%s:%s:%s", ha1, d["nonce"], nonceCount, cnonce, d["qop"], ha2)) - authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%v, cnonce="%s", response="%s"`, - d["username"], d["realm"], d["nonce"], d["uri"], d["qop"], nonceCount, cnonce, response) + + var ( + ha1 string + ha2 string + nonceCount = 00000001 + cnonce = getCnonce() + response string + ) + + // 'ha1' value depends on value of "algorithm" field + switch d["algorithm"] { + case "MD5", "": + ha1 = getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"]) + case "MD5-sess": + ha1 = getMD5( + fmt.Sprintf("%s:%v:%s", + getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"]), + nonceCount, + cnonce, + ), + ) + } + + // 'ha2' value depends on value of "qop" field + switch d["qop"] { + case "auth", "": + ha2 = getMD5(d["method"] + ":" + d["uri"]) + case "auth-int": + if d["entityBody"] != "" { + ha2 = getMD5(d["method"] + ":" + d["uri"] + ":" + getMD5(d["entityBody"])) + } + } + + // 'response' value depends on value of "qop" field + switch d["qop"] { + case "": + response = getMD5( + fmt.Sprintf("%s:%s:%s", + ha1, + d["nonce"], + ha2, + ), + ) + case "auth", "auth-int": + response = getMD5( + fmt.Sprintf("%s:%s:%v:%s:%s:%s", + ha1, + d["nonce"], + nonceCount, + cnonce, + d["qop"], + ha2, + ), + ) + } + + authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", nc=%v, cnonce="%s", response="%s"`, + d["username"], d["realm"], d["nonce"], d["uri"], nonceCount, cnonce, response) + + if d["qop"] != "" { + authorization += fmt.Sprintf(`, qop=%s`, d["qop"]) + } if d["opaque"] != "" { authorization += fmt.Sprintf(`, opaque="%s"`, d["opaque"])