diff --git a/uppsrc/Web/httpcli.cpp b/uppsrc/Web/httpcli.cpp index 543b1a3ef..4944e01f6 100644 --- a/uppsrc/Web/httpcli.cpp +++ b/uppsrc/Web/httpcli.cpp @@ -19,6 +19,7 @@ void HttpClient::Init() max_header_size = DEFAULT_MAX_HEADER_SIZE; max_content_size = DEFAULT_MAX_CONTENT_SIZE; keepalive = false; + force_digest = false; std_headers = true; hasurlvar = false; method = METHOD_GET; @@ -78,6 +79,14 @@ String HttpClient::ExecuteRedirect(int max_redirect, int retries, Gate2 max_redirect) { + error = NFormat("Maximum number of digest authentication attempts exceeded: %d", max_redirect); + return String::GetVoid(); + } + Digest(CalculateDigest(authenticate)); + continue; + } if(status_code >= 400 && status_code < 500) { error = status_line; return String::GetVoid(); @@ -146,6 +155,81 @@ HttpClient& HttpClient::UrlVar(const char *id, const String& data) return *this; } +String HttpClient::CalculateDigest(String authenticate) const +{ + const char *p = authenticate; + String realm, qop, nonce, opaque; + while(*p) { + if(!IsAlNum(*p)) { + p++; + continue; + } + else { + const char *b = p; + while(IsAlNum(*p)) + p++; + String var = ToLower(String(b, p)); + String value; + while(*p && (byte)*p <= ' ') + p++; + if(*p == '=') { + p++; + while(*p && (byte)*p <= ' ') + p++; + if(*p == '\"') { + p++; + while(*p && *p != '\"') + if(*p != '\\' || *++p) + value.Cat(*p++); + if(*p == '\"') + p++; + } + else { + b = p; + while(*p && *p != ',' && (byte)*p > ' ') + p++; + value = String(b, p); + } + } + if(var == "realm") + realm = value; + else if(var == "qop") + qop = value; + else if(var == "nonce") + nonce = var; + else if(var == "opaque") + opaque = var; + } + } + String hv1, hv2; + hv1 << username << ':' << realm << ':' << password; + String ha1 = MD5String(hv1); + hv2 << (method == METHOD_GET ? "GET" : method == METHOD_POST ? "POST" : "READ") + << ':' << path; + String ha2 = MD5String(hv2); + int nc = 1; + String cnonce = FormatIntHex(Random(), 8); + String hv; + hv << BinhexEncode(ha1) + << ':' << nonce + << ':' << FormatIntHex(nc, 8) + << ':' << cnonce + << ':' << qop << ':' << BinhexEncode(ha2); + String ha = MD5String(hv); + String auth; + auth << "Authorization: Digest " + "username=" << AsCString(username) + << ", realm=" << AsCString(realm) + << ", nonce=" << AsCString(nonce) + << ", uri=" << AsCString(path) + << ", qop=" << AsCString(qop) + << ", nc=" << AsCString(FormatIntHex(nc, 8)) + << ", cnonce=" << cnonce + << ", response=" << AsCString(BinhexEncode(ha)) + << ", opaque=" << AsCString(opaque); + return auth; +} + String HttpClient::Execute(Gate2 progress) { LLOGBLOCK("HttpClient::Execute"); @@ -222,7 +306,9 @@ String HttpClient::Execute(Gate2 progress) } if(use_proxy && !IsNull(proxy_username)) request << "Proxy-Authorization: basic " << Base64Encode(proxy_username + ':' + proxy_password) << "\r\n"; - if(!IsNull(username) || !IsNull(password)) + if(!IsNull(digest)) + request << "Authorization: Digest " << digest << "\r\n"; + else if(!force_digest && (!IsNull(username) || !IsNull(password))) request << "Authorization: basic " << Base64Encode(username + ":" + password) << "\r\n"; request << client_headers << "\r\n" << postdata; LLOG("host = " << host << ", port = " << port); @@ -285,10 +371,12 @@ String HttpClient::Execute(Gate2 progress) static const char ce[] = "content-encoding:"; static const char te[] = "transfer-encoding:"; static const char lo[] = "location:"; + static const char au[] = "www-authenticate:"; static const int CL_LENGTH = sizeof(cl) - 1; static const int CE_LENGTH = sizeof(ce) - 1; static const int TE_LENGTH = sizeof(te) - 1; static const int LO_LENGTH = sizeof(lo) - 1; + static const int AU_LENGTH = sizeof(au) - 1; if(!MemICmp(p, cl, CL_LENGTH)) { for(p += CL_LENGTH; *p == ' '; p++) ; @@ -323,6 +411,11 @@ String HttpClient::Execute(Gate2 progress) if(p >= 0 && q < 0) redirect_url.Cat(path.GetIter(p)); } + else if(!MemICmp(p, au, AU_LENGTH)) { + for(p += LO_LENGTH; *p == ' '; p++) + ; + authenticate = String(p, e); + } if(server_headers.GetLength() + (e - b) + 2 > max_header_size) { error = NFormat(t_("%s:%d: maximum header length exceeded (%d B)"), host, port, max_header_size); return String::GetVoid(); diff --git a/uppsrc/Web/httpcli.h b/uppsrc/Web/httpcli.h index 7ccb343c6..760ba7b46 100644 --- a/uppsrc/Web/httpcli.h +++ b/uppsrc/Web/httpcli.h @@ -15,6 +15,8 @@ public: HttpClient& Port(int p) { port = p; return *this; } HttpClient& Path(String p) { path = p; return *this; } HttpClient& User(String u, String p) { username = u; password = p; return *this; } + HttpClient& Digest() { force_digest = true; return *this; } + HttpClient& Digest(String d) { digest = d; return *this; } HttpClient& URL(const char *url); HttpClient& Url(const char *id, const String& data); HttpClient& KeepAlive(bool k) { keepalive = k; return *this; } @@ -60,6 +62,8 @@ public: bool IsRedirect() const { return is_redirect; } String GetRedirectURL() const { return redirect_url; } + String CalculateDigest(String authenticate) const; + void Close() { socket.Close(); } static void Trace(bool b = true); @@ -68,6 +72,7 @@ public: Socket socket; bool keepalive; bool aborted; + bool force_digest; String error; int timeout_msecs; @@ -84,6 +89,7 @@ public: String path; String username; String password; + String digest; String client_headers; String accept; String agent; @@ -100,6 +106,7 @@ public: String server_headers; String redirect_url; + String authenticate; enum { DEFAULT_PORT = 80, diff --git a/uppsrc/Web/util.cpp b/uppsrc/Web/util.cpp index 9155bff0d..f512102df 100644 --- a/uppsrc/Web/util.cpp +++ b/uppsrc/Web/util.cpp @@ -217,6 +217,19 @@ String BinHexEncode(const char *s, const char *e) return out; } +String BinhexEncode(const char *s, const char *e) +{ + static const char bh[] = "0123456789abcdef"; + int l = int(e - s); + StringBuffer out(2 * l); + char *p = out; + for(; s < e; s++) { + *p++ = bh[(*s >> 4) & 0xF]; + *p++ = bh[*s & 0xF]; + } + return out; +} + String BinHexDecode(const char *p, const char *e) { StringBuffer out; diff --git a/uppsrc/Web/util.h b/uppsrc/Web/util.h index ecc1fbe36..c5c2d9e33 100644 --- a/uppsrc/Web/util.h +++ b/uppsrc/Web/util.h @@ -12,6 +12,8 @@ String OtpEncode(String password, String otp_key); String EncryptString(String password, String otp_key); String BinHexEncode(const char *b, const char *e); inline String BinHexEncode(String data) { return BinHexEncode(data.Begin(), data.End()); } +String BinhexEncode(const char *b, const char *e); +inline String BinhexEncode(String data) { return BinhexEncode(data.Begin(), data.End()); } String BinHexDecode(const char *b, const char *e); inline String BinHexDecode(String data) { return BinHexDecode(data.Begin(), data.End()); } String Base64Encode(const char *b, const char *e);