Index: pjproject/trunk/pjlib-util/include/pjlib-util/http_client.h
===================================================================
--- pjproject/trunk/pjlib-util/include/pjlib-util/http_client.h	(revision 3320)
+++ pjproject/trunk/pjlib-util/include/pjlib-util/http_client.h	(revision 3321)
@@ -50,4 +50,13 @@
 
 /**
+ * HTTP header representation.
+ */
+typedef struct pj_http_header_elmt
+{
+    pj_str_t name;	/**< Header name */
+    pj_str_t value;	/**< Header value */
+} pj_http_header_elmt;
+
+/**
  * This structure describes http request/response headers.
  * Application should call #pj_http_headers_add_elmt() to
@@ -56,11 +65,59 @@
 typedef struct pj_http_headers
 {
-    unsigned     count;                   /**< Number of header fields */
-    struct pj_http_header_elmt
-    {
-        pj_str_t name;
-        pj_str_t value;
-    } header[PJ_HTTP_HEADER_SIZE];        /**< Header elements/fields */
+    /**< Number of header fields */
+    unsigned     count;
+
+    /** Header elements/fields */
+    pj_http_header_elmt header[PJ_HTTP_HEADER_SIZE];
 } pj_http_headers;
+
+/**
+ * Structure to save HTTP authentication credential.
+ */
+typedef struct pj_http_auth_cred
+{
+    /**
+     * Specify specific authentication schemes to be responded. Valid values
+     * are "basic" and "digest". If this field is not set, any authentication
+     * schemes will be responded.
+     *
+     * Default is empty.
+     */
+    pj_str_t	scheme;
+
+    /**
+     * Specify specific authentication realm to be responded. If this field
+     * is set, only 401/407 response with matching realm will be responded.
+     * If this field is not set, any realms will be responded.
+     *
+     * Default is empty.
+     */
+    pj_str_t	realm;
+
+    /**
+     * Specify authentication username.
+     *
+     * Default is empty.
+     */
+    pj_str_t	username;
+
+    /**
+     * The type of password in \a data field. Currently only 0 is
+     * supported, meaning the \a data contains plain-text password.
+     *
+     * Default is 0.
+     */
+    unsigned	data_type;
+
+    /**
+     * Specify authentication password. The encoding of the password depends
+     * on the value of \a data_type field above.
+     *
+     * Default is empty.
+     */
+    pj_str_t	data;
+
+} pj_http_auth_cred;
+
 
 /**
@@ -126,5 +183,26 @@
                                    /**< will be provided later  */
     } reqdata;
+
+    /**
+     * Authentication credential needed to respond to 401/407 response.
+     */
+    pj_http_auth_cred	auth_cred;
+
 } pj_http_req_param;
+
+/**
+ * HTTP authentication challenge, parsed from WWW-Authenticate header.
+ */
+typedef struct pj_http_auth_chal
+{
+    pj_str_t	scheme;		/**< Auth scheme.		*/
+    pj_str_t	realm;		/**< Realm for the challenge.	*/
+    pj_str_t	domain;		/**< Domain.			*/
+    pj_str_t	nonce;		/**< Nonce challenge.		*/
+    pj_str_t	opaque;		/**< Opaque value.		*/
+    int		stale;		/**< Stale parameter.		*/
+    pj_str_t	algorithm;	/**< Algorithm parameter.	*/
+    pj_str_t	qop;		/**< Quality of protection.	*/
+} pj_http_auth_chal;
 
 /**
@@ -137,9 +215,8 @@
     pj_str_t        reason;         /**< Reason phrase */
     pj_http_headers headers;        /**< Response headers */
-    /**
-     * The value of content-length header field. -1 if not
-     * specified.
-     */
-    pj_int32_t      content_length; 
+    pj_http_auth_chal auth_chal;    /**< Parsed WWW-Authenticate header, if
+				         any. */
+    pj_int32_t      content_length; /**< The value of content-length header
+					 field. -1 if not specified. */
     void            *data;          /**< Data received */
     pj_size_t       size;           /**< Data size */
@@ -151,4 +228,6 @@
 typedef struct pj_http_url
 {
+    pj_str_t	username;	    /**< Username part */
+    pj_str_t	passwd;		    /**< Password part */
     pj_str_t    protocol;           /**< Protocol used */
     pj_str_t    host;               /**< Host name */
Index: pjproject/trunk/pjlib-util/src/pjlib-util-test/http_client.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjlib-util-test/http_client.c	(revision 3320)
+++ pjproject/trunk/pjlib-util/src/pjlib-util-test/http_client.c	(revision 3321)
@@ -238,17 +238,16 @@
 
 
-pj_status_t parse_url(const char *url)
+pj_status_t parse_url(const char *url, pj_http_url *hurl)
 {
     pj_str_t surl;
-    pj_http_url hurl;
     pj_status_t status;
 
     pj_cstr(&surl, url);
-    status = pj_http_req_parse_url(&surl, &hurl);
+    status = pj_http_req_parse_url(&surl, hurl);
 #ifdef VERBOSE
     if (!status) {
         printf("URL: %s\nProtocol: %.*s\nHost: %.*s\nPort: %d\nPath: %.*s\n\n",
-               url, STR_PREC(hurl.protocol), STR_PREC(hurl.host), 
-               hurl.port, STR_PREC(hurl.path));
+               url, STR_PREC(hurl->protocol), STR_PREC(hurl->host),
+               hurl->port, STR_PREC(hurl->path));
     } else {
     }
@@ -257,28 +256,105 @@
 }
 
-int parse_url_test()
-{
-    /* Simple URL without '/' in the end */
-    if (parse_url("http://www.google.com.sg") != PJ_SUCCESS)
-        return -11;
-    /* Simple URL with port number but without '/' in the end */
-    if (parse_url("http://www.example.com:8080") != PJ_SUCCESS)
-        return -13;
-    /* URL with path */
-    if (parse_url("http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6")
-        != PJ_SUCCESS)
-        return -15;
-    /* URL with port and path */
-    if (parse_url("http://teluu.com:81/about-us/") != PJ_SUCCESS)
-        return -17;
-    /* unsupported protocol */
-    if (parse_url("ftp://www.teluu.com") != PJ_ENOTSUP)
-        return -19;
-    /* invalid format */
-    if (parse_url("http:/teluu.com/about-us/") != PJLIB_UTIL_EHTTPINURL)
-        return -21;
-    /* invalid port number */
-    if (parse_url("http://teluu.com:xyz/") != PJLIB_UTIL_EHTTPINPORT)
-        return -23;
+static int parse_url_test()
+{
+    struct test_data
+    {
+	char *url;
+	pj_status_t result;
+	const char *username;
+	const char *passwd;
+	const char *host;
+	int port;
+	const char *path;
+    } test_data[] =
+    {
+	/* Simple URL without '/' in the end */
+        {"http://www.pjsip.org", PJ_SUCCESS, "", "", "www.pjsip.org", 80, "/"},
+
+        /* Simple URL with port number but without '/' in the end */
+        {"http://pjsip.org:8080", PJ_SUCCESS, "", "", "pjsip.org", 8080, "/"},
+
+        /* URL with path */
+        {"http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6",
+        	PJ_SUCCESS, "", "", "127.0.0.1", 280,
+        	"/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6"},
+
+	/* URL with port and path */
+	{"http://pjsip.org:81/about-us/", PJ_SUCCESS, "", "", "pjsip.org", 81, "/about-us/"},
+
+	/* unsupported protocol */
+	{"ftp://www.pjsip.org", PJ_ENOTSUP, "", "", "", 80, ""},
+
+	/* invalid format */
+	{"http:/pjsip.org/about-us/", PJLIB_UTIL_EHTTPINURL, "", "", "", 80, ""},
+
+	/* invalid port number */
+	{"http://pjsip.org:xyz/", PJLIB_UTIL_EHTTPINPORT, "", "", "", 80, ""},
+
+	/* with username and password */
+	{"http://user:pass@pjsip.org", PJ_SUCCESS, "user", "pass", "pjsip.org", 80, "/"},
+
+	/* password only*/
+	{"http://:pass@pjsip.org", PJ_SUCCESS, "", "pass", "pjsip.org", 80, "/"},
+
+	/* user only*/
+	{"http://user:@pjsip.org", PJ_SUCCESS, "user", "", "pjsip.org", 80, "/"},
+
+	/* empty username and passwd*/
+	{"http://:@pjsip.org", PJ_SUCCESS, "", "", "pjsip.org", 80, "/"},
+
+	/* Invalid URL */
+	{"http://:", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http://@", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http:/", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http://", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http:///", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http://@/", PJ_EINVAL, "", "", "", 0, ""},
+
+	/* Invalid URL */
+	{"http://:::", PJ_EINVAL, "", "", "", 0, ""},
+    };
+    unsigned i;
+
+    for (i=0; i<PJ_ARRAY_SIZE(test_data); ++i) {
+	struct test_data *ptd;
+	pj_http_url hurl;
+	pj_status_t status;
+
+	ptd = &test_data[i];
+
+	PJ_LOG(3, (THIS_FILE, ".. %s", ptd->url));
+	status = parse_url(ptd->url, &hurl);
+
+	if (status != ptd->result) {
+	    PJ_LOG(3,(THIS_FILE, "%d", status));
+	    return -11;
+	}
+	if (status != PJ_SUCCESS)
+	    continue;
+	if (pj_strcmp2(&hurl.username, ptd->username))
+	    return -12;
+	if (pj_strcmp2(&hurl.passwd, ptd->passwd))
+	    return -13;
+	if (pj_strcmp2(&hurl.host, ptd->host))
+	    return -14;
+	if (hurl.port != ptd->port)
+	    return -15;
+	if (pj_strcmp2(&hurl.path, ptd->path))
+	    return -16;
+    }
 
     return 0;
Index: pjproject/trunk/pjlib-util/src/pjlib-util/http_client.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjlib-util/http_client.c	(revision 3320)
+++ pjproject/trunk/pjlib-util/src/pjlib-util/http_client.c	(revision 3321)
@@ -21,4 +21,5 @@
 #include <pj/activesock.h>
 #include <pj/assert.h>
+#include <pj/ctype.h>
 #include <pj/errno.h>
 #include <pj/except.h>
@@ -26,6 +27,9 @@
 #include <pj/string.h>
 #include <pj/timer.h>
+#include <pjlib-util/base64.h>
 #include <pjlib-util/errno.h>
+#include <pjlib-util/md5.h>
 #include <pjlib-util/scanner.h>
+#include <pjlib-util/string.h>
 
 #if 0
@@ -40,5 +44,4 @@
 #define HTTP_1_0                "1.0"
 #define HTTP_1_1                "1.1"
-#define HTTP_SEPARATOR          "://"
 #define CONTENT_LENGTH          "Content-Length"
 /* Buffer size for sending/receiving messages. */
@@ -94,4 +97,11 @@
     READING_COMPLETE,
     ABORTING,
+};
+
+enum auth_state
+{
+    AUTH_NONE,		/* Not authenticating */
+    AUTH_RETRYING,	/* New request with auth has been submitted */
+    AUTH_DONE		/* Done retrying the request with auth. */
 };
 
@@ -110,4 +120,5 @@
     pj_str_t                buffer;     /* Buffer to send/receive msgs */
     enum http_state         state;      /* State of the HTTP request */
+    enum auth_state	    auth_state; /* Authentication state */
     pj_timer_entry          timer_entry;/* Timer entry */
     pj_bool_t               resolved;   /* Whether URL's host is resolved */
@@ -143,4 +154,9 @@
                                        void *data, pj_size_t size,
                                        pj_size_t *remainder);
+/* Restart the request with authentication */
+static void restart_req_with_auth(pj_http_req *hreq);
+/* Parse authentication challenge */
+static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input,
+				   pj_http_auth_chal *chal);
 
 static pj_uint16_t get_http_default_port(const pj_str_t *protocol)
@@ -319,6 +335,57 @@
                 hreq->response.size = size - rem;
             }
+
+            /* If code is 401 or 407, find and parse WWW-Authenticate or
+             * Proxy-Authenticate header
+             */
+            if (hreq->response.status_code == 401 ||
+        	hreq->response.status_code == 407)
+            {
+        	const pj_str_t STR_WWW_AUTH = { "WWW-Authenticate", 16 };
+        	const pj_str_t STR_PROXY_AUTH = { "Proxy-Authenticate", 18 };
+        	pj_http_resp *response = &hreq->response;
+        	pj_http_headers *hdrs = &response->headers;
+        	unsigned i;
+
+        	status = PJ_ENOTFOUND;
+        	for (i = 0; i < hdrs->count; i++) {
+        	    if (!pj_stricmp(&hdrs->header[i].name, &STR_WWW_AUTH) ||
+        		!pj_stricmp(&hdrs->header[i].name, &STR_PROXY_AUTH))
+        	    {
+        		status = parse_auth_chal(hreq->pool,
+        					 &hdrs->header[i].value,
+        					 &response->auth_chal);
+        		break;
+        	    }
+        	}
+
+                /* Check if we should perform authentication */
+                if (status == PJ_SUCCESS &&
+		    hreq->auth_state == AUTH_NONE &&
+		    hreq->response.auth_chal.scheme.slen &&
+		    hreq->param.auth_cred.username.slen &&
+		    (hreq->param.auth_cred.scheme.slen == 0 ||
+		     !pj_stricmp(&hreq->response.auth_chal.scheme,
+				 &hreq->param.auth_cred.scheme)) &&
+		    (hreq->param.auth_cred.realm.slen == 0 ||
+		     !pj_stricmp(&hreq->response.auth_chal.realm,
+				 &hreq->param.auth_cred.realm))
+		    )
+		    {
+		    /* Yes, authentication is required and we have been
+		     * configured with credential.
+		     */
+		    restart_req_with_auth(hreq);
+		    if (hreq->auth_state == AUTH_RETRYING) {
+			/* We'll be resending the request with auth. This
+			 * connection has been closed.
+			 */
+			return PJ_FALSE;
+		    }
+                }
+            }
+
             /* We already received the response header, call the 
-             * appropriate callback. 
+             * appropriate callback.
              */
             if (hreq->cb.on_response)
@@ -385,5 +452,5 @@
         (status == PJ_EEOF && hreq->response.content_length == -1)) 
     {
-        /* Finish reading */
+	/* Finish reading */
         http_req_end_request(hreq);
         hreq->response.size = hreq->tcp_state.current_read_size;
@@ -434,4 +501,96 @@
 }
 
+/* Parse authentication challenge */
+static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input,
+				   pj_http_auth_chal *chal)
+{
+    pj_scanner scanner;
+    const pj_str_t REALM_STR 	=    { "realm", 5},
+    		NONCE_STR 	=    { "nonce", 5},
+    		ALGORITHM_STR 	=    { "algorithm", 9 },
+    		STALE_STR 	=    { "stale", 5},
+    		QOP_STR 	=    { "qop", 3},
+    		OPAQUE_STR 	=    { "opaque", 6};
+    pj_status_t status = PJ_SUCCESS;
+    PJ_USE_EXCEPTION ;
+
+    pj_scan_init(&scanner, input->ptr, input->slen, PJ_SCAN_AUTOSKIP_WS,
+		 &on_syntax_error);
+    PJ_TRY {
+	/* Get auth scheme */
+	if (*scanner.curptr == '"') {
+	    pj_scan_get_quote(&scanner, '"', '"', &chal->scheme);
+	    chal->scheme.ptr++;
+	    chal->scheme.slen -= 2;
+	} else {
+	    pj_scan_get_until_chr(&scanner, " \t\r\n", &chal->scheme);
+	}
+
+	/* Loop parsing all parameters */
+	for (;;) {
+	    const char *end_param = ", \t\r\n;";
+	    pj_str_t name, value;
+
+	    /* Get pair of parameter name and value */
+	    value.ptr = NULL;
+	    value.slen = 0;
+	    pj_scan_get_until_chr(&scanner, "=, \t\r\n", &name);
+	    if (*scanner.curptr == '=') {
+		pj_scan_get_char(&scanner);
+		if (!pj_scan_is_eof(&scanner)) {
+		    if (*scanner.curptr == '"' || *scanner.curptr == '\'') {
+			int quote_char = *scanner.curptr;
+			pj_scan_get_quote(&scanner, quote_char, quote_char,
+					  &value);
+			value.ptr++;
+			value.slen -= 2;
+		    } else if (!strchr(end_param, *scanner.curptr)) {
+			pj_scan_get_until_chr(&scanner, end_param, &value);
+		    }
+		}
+		value = pj_str_unescape(pool, &value);
+	    }
+
+	    if (!pj_stricmp(&name, &REALM_STR)) {
+		chal->realm = value;
+
+	    } else if (!pj_stricmp(&name, &NONCE_STR)) {
+		chal->nonce = value;
+
+	    } else if (!pj_stricmp(&name, &ALGORITHM_STR)) {
+		chal->algorithm = value;
+
+	    } else if (!pj_stricmp(&name, &OPAQUE_STR)) {
+		chal->opaque = value;
+
+	    } else if (!pj_stricmp(&name, &QOP_STR)) {
+		chal->qop = value;
+
+	    } else if (!pj_stricmp(&name, &STALE_STR)) {
+		chal->stale = value.slen &&
+			      (*value.ptr != '0') &&
+			      (*value.ptr != 'f') &&
+			      (*value.ptr != 'F');
+
+	    }
+
+	    /* Eat comma */
+	    if (!pj_scan_is_eof(&scanner) && *scanner.curptr == ',')
+		pj_scan_get_char(&scanner);
+	    else
+		break;
+	}
+
+    }
+    PJ_CATCH_ANY {
+	status = PJ_GET_EXCEPTION();
+	pj_bzero(chal, sizeof(*chal));
+	TRACE_((THIS_FILE, "Error: parsing of auth header failed"));
+    }
+    PJ_END;
+    pj_scan_fini(&scanner);
+    return status;
+}
+
 /* The same as #pj_http_headers_add_elmt() with char * as
  * its parameters.
@@ -465,7 +624,8 @@
     pj_size_t i;
     char *cptr;
-    char *newdata;
+    char *end_status, *newdata;
     pj_scanner scanner;
     pj_str_t s;
+    const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 };
     pj_status_t status;
 
@@ -518,8 +678,11 @@
     PJ_END;
 
+    end_status = scanner.curptr;
+    pj_scan_fini(&scanner);
+
     /* Parse the response headers. */
-    size = i - 2 - (scanner.curptr - newdata);
+    size = i - 2 - (end_status - newdata);
     if (size > 0) {
-        status = http_headers_parse(scanner.curptr + 1, size, 
+        status = http_headers_parse(end_status + 1, size,
                                     &response->headers);
     } else {
@@ -529,6 +692,6 @@
     /* Find content-length header field. */
     for (i = 0; i < response->headers.count; i++) {
-        if (!pj_stricmp2(&response->headers.header[i].name, 
-                         CONTENT_LENGTH)) 
+        if (!pj_stricmp(&response->headers.header[i].name,
+                        &STR_CONTENT_LENGTH))
         {
             response->content_length = 
@@ -545,6 +708,4 @@
         }
     }
-
-    pj_scan_fini(&scanner);
 
     return status;
@@ -615,4 +776,5 @@
     if (!len) return -1;
     
+    pj_bzero(hurl, sizeof(*hurl));
     pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error);
 
@@ -635,14 +797,26 @@
         }
 
-        if (pj_scan_strcmp(&scanner, HTTP_SEPARATOR,
-                           pj_ansi_strlen(HTTP_SEPARATOR))) 
-        {
+        if (pj_scan_strcmp(&scanner, "://", 3)) {
             PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name
         }
-        pj_scan_advance_n(&scanner, pj_ansi_strlen(HTTP_SEPARATOR), PJ_FALSE);
+        pj_scan_advance_n(&scanner, 3, PJ_FALSE);
+
+        if (pj_memchr(url->ptr, '@', url->slen)) {
+            /* Parse username and password */
+            pj_scan_get_until_chr(&scanner, ":@", &hurl->username);
+            if (*scanner.curptr == ':') {
+        	pj_scan_get_char(&scanner);
+        	pj_scan_get_until_chr(&scanner, "@", &hurl->passwd);
+            } else {
+        	hurl->passwd.slen = 0;
+            }
+            pj_scan_get_char(&scanner);
+        }
 
         /* Parse the host and port number (if any) */
         pj_scan_get_until_chr(&scanner, ":/", &s);
         pj_strassign(&hurl->host, &s);
+        if (hurl->host.slen==0)
+            PJ_THROW(PJ_EINVAL);
         if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') {
             /* No port number specified */
@@ -693,4 +867,5 @@
     pj_pool_t *own_pool;
     pj_http_req *hreq;
+    char *at_pos;
     pj_status_t status;
 
@@ -737,9 +912,52 @@
 
     /* Parse the URL */
-    if (!pj_strdup(hreq->pool, &hreq->url, url))
+    if (!pj_strdup_with_null(hreq->pool, &hreq->url, url)) {
+	pj_pool_release(hreq->pool);
         return PJ_ENOMEM;
+    }
     status = pj_http_req_parse_url(&hreq->url, &hreq->hurl);
-    if (status != PJ_SUCCESS)
+    if (status != PJ_SUCCESS) {
+	pj_pool_release(hreq->pool);
         return status; // Invalid URL supplied
+    }
+
+    /* If URL contains username/password, move them to credential and
+     * remove them from the URL.
+     */
+    if ((at_pos=pj_strchr(&hreq->url, '@')) != NULL) {
+	pj_str_t tmp;
+	char *user_pos = pj_strchr(&hreq->url, '/');
+	int removed_len;
+
+	/* Save credential first, unescape the string */
+	tmp = pj_str_unescape(hreq->pool, &hreq->hurl.username);;
+	pj_strdup(hreq->pool, &hreq->param.auth_cred.username, &tmp);
+
+	tmp = pj_str_unescape(hreq->pool, &hreq->hurl.passwd);
+	pj_strdup(hreq->pool, &hreq->param.auth_cred.data, &tmp);
+
+	hreq->hurl.username.ptr = hreq->hurl.passwd.ptr = NULL;
+	hreq->hurl.username.slen = hreq->hurl.passwd.slen = 0;
+
+	/* Remove "username:password@" from the URL */
+	pj_assert(user_pos != 0 && user_pos < at_pos);
+	user_pos += 2;
+	removed_len = at_pos + 1 - user_pos;
+	pj_memmove(user_pos, at_pos+1, hreq->url.ptr+hreq->url.slen-at_pos-1);
+	hreq->url.slen -= removed_len;
+
+	/* Need to adjust hostname and path pointers due to memmove*/
+	if (hreq->hurl.host.ptr > user_pos &&
+	    hreq->hurl.host.ptr < user_pos + hreq->url.slen)
+	{
+	    hreq->hurl.host.ptr -= removed_len;
+	}
+	/* path may come from a string constant, don't shift it if so */
+	if (hreq->hurl.path.ptr > user_pos &&
+	    hreq->hurl.path.ptr < user_pos + hreq->url.slen)
+	{
+	    hreq->hurl.path.ptr -= removed_len;
+	}
+    }
 
     *http_req = hreq;
@@ -771,5 +989,8 @@
     PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY);
 
+    /* Reset few things to make sure restarting works */
     http_req->error = 0;
+    http_req->response.headers.count = 0;
+    pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state));
 
     if (!http_req->resolved) {
@@ -838,5 +1059,355 @@
 }
 
-#define STR_PREC(s) s.slen, s.ptr
+/* Respond to basic authentication challenge */
+static pj_status_t auth_respond_basic(pj_http_req *hreq)
+{
+    /* Basic authentication:
+     *      credentials       = "Basic" basic-credentials
+     *      basic-credentials = base64-user-pass
+     *      base64-user-pass  = <base64 [4] encoding of user-pass>
+     *      user-pass         = userid ":" password
+     *
+     * Sample:
+     *       Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+     */
+    pj_str_t user_pass;
+    pj_http_header_elmt *phdr;
+    int len;
+
+    /* Use send buffer to store userid ":" password */
+    user_pass.ptr = hreq->buffer.ptr;
+    pj_strcpy(&user_pass, &hreq->param.auth_cred.username);
+    pj_strcat2(&user_pass, ":");
+    pj_strcat(&user_pass, &hreq->param.auth_cred.data);
+
+    /* Create Authorization header */
+    phdr = &hreq->param.headers.header[hreq->param.headers.count++];
+    pj_bzero(phdr, sizeof(*phdr));
+    if (hreq->response.status_code == 401)
+	phdr->name = pj_str("Authorization");
+    else
+	phdr->name = pj_str("Proxy-Authorization");
+
+    len = PJ_BASE256_TO_BASE64_LEN(user_pass.slen) + 10;
+    phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len);
+    phdr->value.slen = 0;
+
+    pj_strcpy2(&phdr->value, "Basic ");
+    len -= phdr->value.slen;
+    pj_base64_encode((pj_uint8_t*)user_pass.ptr, (int)user_pass.slen,
+		     phdr->value.ptr + phdr->value.slen, &len);
+    phdr->value.slen += len;
+
+    return PJ_SUCCESS;
+}
+
+/** Length of digest string. */
+#define MD5_STRLEN 32
+/* A macro just to get rid of type mismatch between char and unsigned char */
+#define MD5_APPEND(pms,buf,len)	pj_md5_update(pms, (const pj_uint8_t*)buf, len)
+
+/* Transform digest to string.
+ * output must be at least PJSIP_MD5STRLEN+1 bytes.
+ *
+ * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
+ */
+static void digest2str(const unsigned char digest[], char *output)
+{
+    int i;
+    for (i = 0; i<16; ++i) {
+	pj_val_to_hex_digit(digest[i], output);
+	output += 2;
+    }
+}
+
+static void auth_create_digest_response(pj_str_t *result,
+					const pj_http_auth_cred *cred,
+				        const pj_str_t *nonce,
+				        const pj_str_t *nc,
+				        const pj_str_t *cnonce,
+				        const pj_str_t *qop,
+				        const pj_str_t *uri,
+				        const pj_str_t *realm,
+				        const pj_str_t *method)
+{
+    char ha1[MD5_STRLEN];
+    char ha2[MD5_STRLEN];
+    unsigned char digest[16];
+    pj_md5_context pms;
+
+    pj_assert(result->slen >= MD5_STRLEN);
+
+    TRACE_((THIS_FILE, "Begin creating digest"));
+
+    if (cred->data_type == 0) {
+	/***
+	 *** ha1 = MD5(username ":" realm ":" password)
+	 ***/
+	pj_md5_init(&pms);
+	MD5_APPEND( &pms, cred->username.ptr, cred->username.slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, realm->ptr, realm->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cred->data.ptr, cred->data.slen);
+	pj_md5_final(&pms, digest);
+
+	digest2str(digest, ha1);
+
+    } else if (cred->data_type == 1) {
+	pj_assert(cred->data.slen == 32);
+	pj_memcpy( ha1, cred->data.ptr, cred->data.slen );
+    } else {
+	pj_assert(!"Invalid data_type");
+    }
+
+    TRACE_((THIS_FILE, "  ha1=%.32s", ha1));
+
+    /***
+     *** ha2 = MD5(method ":" req_uri)
+     ***/
+    pj_md5_init(&pms);
+    MD5_APPEND( &pms, method->ptr, method->slen);
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, uri->ptr, uri->slen);
+    pj_md5_final(&pms, digest);
+    digest2str(digest, ha2);
+
+    TRACE_((THIS_FILE, "  ha2=%.32s", ha2));
+
+    /***
+     *** When qop is not used:
+     ***    response = MD5(ha1 ":" nonce ":" ha2)
+     ***
+     *** When qop=auth is used:
+     ***    response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
+     ***/
+    pj_md5_init(&pms);
+    MD5_APPEND( &pms, ha1, MD5_STRLEN);
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, nonce->ptr, nonce->slen);
+    if (qop && qop->slen != 0) {
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, nc->ptr, nc->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, qop->ptr, qop->slen);
+    }
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, ha2, MD5_STRLEN);
+
+    /* This is the final response digest. */
+    pj_md5_final(&pms, digest);
+
+    /* Convert digest to string and store in chal->response. */
+    result->slen = MD5_STRLEN;
+    digest2str(digest, result->ptr);
+
+    TRACE_((THIS_FILE, "  digest=%.32s", result->ptr));
+    TRACE_((THIS_FILE, "Digest created"));
+}
+
+/* Find out if qop offer contains "auth" token */
+static pj_bool_t auth_has_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
+{
+    pj_str_t qop;
+    char *p;
+
+    pj_strdup_with_null( pool, &qop, qop_offer);
+    p = qop.ptr;
+    while (*p) {
+	*p = (char)pj_tolower(*p);
+	++p;
+    }
+
+    p = qop.ptr;
+    while (*p) {
+	if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
+	    int e = *(p+4);
+	    if (e=='"' || e==',' || e==0)
+		return PJ_TRUE;
+	    else
+		p += 4;
+	} else {
+	    ++p;
+	}
+    }
+
+    return PJ_FALSE;
+}
+
+#define STR_PREC(s) (int)(s).slen, (s).ptr
+
+/* Respond to digest authentication */
+static pj_status_t auth_respond_digest(pj_http_req *hreq)
+{
+    const pj_http_auth_chal *chal = &hreq->response.auth_chal;
+    const pj_http_auth_cred *cred = &hreq->param.auth_cred;
+    pj_http_header_elmt *phdr;
+    char digest_response_buf[MD5_STRLEN];
+    int len;
+    pj_str_t digest_response;
+
+    /* Check algorithm is supported. We only support MD5 */
+    if (chal->algorithm.slen!=0 &&
+	pj_stricmp2(&chal->algorithm, "MD5"))
+    {
+	TRACE_((THIS_FILE, "Error: Unsupported digest algorithm \"%.*s\"",
+		  chal->algorithm.slen, chal->algorithm.ptr));
+	return PJ_ENOTSUP;
+    }
+
+    /* Add Authorization header */
+    phdr = &hreq->param.headers.header[hreq->param.headers.count++];
+    pj_bzero(phdr, sizeof(*phdr));
+    if (hreq->response.status_code == 401)
+	phdr->name = pj_str("Authorization");
+    else
+	phdr->name = pj_str("Proxy-Authorization");
+
+    /* Allocate space for the header */
+    len = 8 + /* Digest */
+	  16 + hreq->param.auth_cred.username.slen + /* username= */
+	  12 + chal->realm.slen + /* realm= */
+	  12 + chal->nonce.slen + /* nonce= */
+	  8 + hreq->hurl.path.slen + /* uri= */
+	  16 + /* algorithm=MD5 */
+	  16 + MD5_STRLEN + /* response= */
+	  12 + /* qop=auth */
+	  8 + /* nc=.. */
+	  30 + /* cnonce= */
+	  0;
+    phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len);
+
+    /* Configure buffer to temporarily store the digest */
+    digest_response.ptr = digest_response_buf;
+    digest_response.slen = MD5_STRLEN;
+
+    if (chal->qop.slen == 0) {
+	const pj_str_t STR_MD5 = { "MD5", 3 };
+
+	/* Server doesn't require quality of protection. */
+	auth_create_digest_response(&digest_response, cred,
+				    &chal->nonce, NULL, NULL,  NULL,
+				    &hreq->hurl.path, &chal->realm,
+				    &hreq->param.method);
+
+	len = pj_ansi_snprintf(
+		phdr->value.ptr, len,
+		"Digest username=\"%.*s\", "
+		"realm=\"%.*s\", "
+		"nonce=\"%.*s\", "
+		"uri=\"%.*s\", "
+		"algorithm=%.*s, "
+		"response=\"%.*s\"",
+		STR_PREC(cred->username),
+		STR_PREC(chal->realm),
+		STR_PREC(chal->nonce),
+		STR_PREC(hreq->hurl.path),
+		STR_PREC(STR_MD5),
+		STR_PREC(digest_response));
+	if (len < 0)
+	    return PJ_ETOOSMALL;
+	phdr->value.slen = len;
+
+    } else if (auth_has_qop(hreq->pool, &chal->qop)) {
+	/* Server requires quality of protection.
+	 * We respond with selecting "qop=auth" protection.
+	 */
+	const pj_str_t STR_MD5 = { "MD5", 3 };
+	const pj_str_t qop = pj_str("auth");
+	const pj_str_t nc = pj_str("1");
+	const pj_str_t cnonce = pj_str("b39971");
+
+	auth_create_digest_response(&digest_response, cred,
+				    &chal->nonce, &nc, &cnonce, &qop,
+				    &hreq->hurl.path, &chal->realm,
+				    &hreq->param.method);
+	len = pj_ansi_snprintf(
+		phdr->value.ptr, len,
+		"Digest username=\"%.*s\", "
+		"realm=\"%.*s\", "
+		"nonce=\"%.*s\", "
+		"uri=\"%.*s\", "
+		"algorithm=%.*s, "
+		"response=\"%.*s\", "
+		"qop=%.*s, "
+		"nc=%.*s, "
+		"cnonce=\"%.*s\"",
+		STR_PREC(cred->username),
+		STR_PREC(chal->realm),
+		STR_PREC(chal->nonce),
+		STR_PREC(hreq->hurl.path),
+		STR_PREC(STR_MD5),
+		STR_PREC(digest_response),
+		STR_PREC(qop),
+		STR_PREC(nc),
+		STR_PREC(cnonce));
+	if (len < 0)
+	    return PJ_ETOOSMALL;
+	phdr->value.slen = len;
+
+    } else {
+	/* Server requires quality protection that we don't support. */
+	TRACE_((THIS_FILE, "Error: Unsupported qop offer %.*s",
+		chal->qop.slen, chal->qop.ptr));
+	return PJ_ENOTSUP;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+static void restart_req_with_auth(pj_http_req *hreq)
+{
+    pj_http_auth_chal *chal = &hreq->response.auth_chal;
+    pj_http_auth_cred *cred = &hreq->param.auth_cred;
+    pj_status_t status;
+
+    if (hreq->param.headers.count >= PJ_HTTP_HEADER_SIZE) {
+	TRACE_((THIS_FILE, "Error: no place to put Authorization header"));
+	hreq->auth_state = AUTH_DONE;
+	return;
+    }
+
+    /* If credential specifies specific scheme, make sure they match */
+    if (cred->scheme.slen && pj_stricmp(&chal->scheme, &cred->scheme)) {
+	status = PJ_ENOTSUP;
+	TRACE_((THIS_FILE, "Error: auth schemes mismatch"));
+	goto on_error;
+    }
+
+    /* If credential specifies specific realm, make sure they match */
+    if (cred->realm.slen && pj_stricmp(&chal->realm, &cred->realm)) {
+	status = PJ_ENOTSUP;
+	TRACE_((THIS_FILE, "Error: auth realms mismatch"));
+	goto on_error;
+    }
+
+    if (!pj_stricmp2(&chal->scheme, "basic")) {
+	status = auth_respond_basic(hreq);
+    } else if (!pj_stricmp2(&chal->scheme, "digest")) {
+	status = auth_respond_digest(hreq);
+    } else {
+	TRACE_((THIS_FILE, "Error: unsupported HTTP auth scheme"));
+	status = PJ_ENOTSUP;
+    }
+
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    http_req_end_request(hreq);
+
+    status = pj_http_req_start(hreq);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    hreq->auth_state = AUTH_RETRYING;
+    return;
+
+on_error:
+    hreq->auth_state = AUTH_DONE;
+}
+
 
 /* snprintf() to a pj_str_t struct with an option to append the 
Index: pjproject/trunk/pjsip-apps/src/samples/httpdemo.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/httpdemo.c	(revision 3320)
+++ pjproject/trunk/pjsip-apps/src/samples/httpdemo.c	(revision 3321)
@@ -46,8 +46,22 @@
 static void on_response(pj_http_req *http_req, const pj_http_resp *resp)
 {
-	PJ_UNUSED_ARG(http_req);
+    unsigned i;
+
+    PJ_UNUSED_ARG(http_req);
     PJ_LOG(3,(THIS_FILE, "%.*s %d %.*s", (int)resp->version.slen, resp->version.ptr,
 				           resp->status_code,
 				           (int)resp->reason.slen, resp->reason.ptr));
+
+    for (i=0; i<resp->headers.count; ++i) {
+	const pj_http_header_elmt *h = &resp->headers.header[i];
+
+	if (!pj_stricmp2(&h->name, "Content-Length") ||
+	    !pj_stricmp2(&h->name, "Content-Type"))
+	{
+	    PJ_LOG(3,(THIS_FILE, "%.*s: %.*s",
+		      (int)h->name.slen, h->name.ptr,
+		      (int)h->value.slen, h->value.ptr));
+	}
+    }
 }
 
