Implemented and closed #1136: added HTTP authentication support
git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3321 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
parent
3ade00659b
commit
00f8827827
|
@ -48,6 +48,15 @@ typedef struct pj_http_req pj_http_req;
|
||||||
*/
|
*/
|
||||||
#define PJ_HTTP_HEADER_SIZE 32
|
#define PJ_HTTP_HEADER_SIZE 32
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* This structure describes http request/response headers.
|
||||||
* Application should call #pj_http_headers_add_elmt() to
|
* Application should call #pj_http_headers_add_elmt() to
|
||||||
|
@ -55,14 +64,62 @@ typedef struct pj_http_req pj_http_req;
|
||||||
*/
|
*/
|
||||||
typedef struct pj_http_headers
|
typedef struct pj_http_headers
|
||||||
{
|
{
|
||||||
unsigned count; /**< Number of header fields */
|
/**< Number of header fields */
|
||||||
struct pj_http_header_elmt
|
unsigned count;
|
||||||
{
|
|
||||||
pj_str_t name;
|
/** Header elements/fields */
|
||||||
pj_str_t value;
|
pj_http_header_elmt header[PJ_HTTP_HEADER_SIZE];
|
||||||
} header[PJ_HTTP_HEADER_SIZE]; /**< Header elements/fields */
|
|
||||||
} pj_http_headers;
|
} 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;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameters that can be given during http request creation. Application
|
* Parameters that can be given during http request creation. Application
|
||||||
* must initialize this structure with #pj_http_req_param_default().
|
* must initialize this structure with #pj_http_req_param_default().
|
||||||
|
@ -125,8 +182,29 @@ typedef struct pj_http_req_param
|
||||||
pj_size_t total_size; /**< If total_size > 0, data */
|
pj_size_t total_size; /**< If total_size > 0, data */
|
||||||
/**< will be provided later */
|
/**< will be provided later */
|
||||||
} reqdata;
|
} reqdata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication credential needed to respond to 401/407 response.
|
||||||
|
*/
|
||||||
|
pj_http_auth_cred auth_cred;
|
||||||
|
|
||||||
} pj_http_req_param;
|
} 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This structure describes HTTP response.
|
* This structure describes HTTP response.
|
||||||
*/
|
*/
|
||||||
|
@ -136,11 +214,10 @@ typedef struct pj_http_resp
|
||||||
pj_uint16_t status_code; /**< Status code of the request */
|
pj_uint16_t status_code; /**< Status code of the request */
|
||||||
pj_str_t reason; /**< Reason phrase */
|
pj_str_t reason; /**< Reason phrase */
|
||||||
pj_http_headers headers; /**< Response headers */
|
pj_http_headers headers; /**< Response headers */
|
||||||
/**
|
pj_http_auth_chal auth_chal; /**< Parsed WWW-Authenticate header, if
|
||||||
* The value of content-length header field. -1 if not
|
any. */
|
||||||
* specified.
|
pj_int32_t content_length; /**< The value of content-length header
|
||||||
*/
|
field. -1 if not specified. */
|
||||||
pj_int32_t content_length;
|
|
||||||
void *data; /**< Data received */
|
void *data; /**< Data received */
|
||||||
pj_size_t size; /**< Data size */
|
pj_size_t size; /**< Data size */
|
||||||
} pj_http_resp;
|
} pj_http_resp;
|
||||||
|
@ -150,6 +227,8 @@ typedef struct pj_http_resp
|
||||||
*/
|
*/
|
||||||
typedef struct pj_http_url
|
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 protocol; /**< Protocol used */
|
||||||
pj_str_t host; /**< Host name */
|
pj_str_t host; /**< Host name */
|
||||||
pj_uint16_t port; /**< Port number */
|
pj_uint16_t port; /**< Port number */
|
||||||
|
|
|
@ -237,49 +237,125 @@ static void on_response(pj_http_req *hreq, const pj_http_resp *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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_str_t surl;
|
||||||
pj_http_url hurl;
|
|
||||||
pj_status_t status;
|
pj_status_t status;
|
||||||
|
|
||||||
pj_cstr(&surl, url);
|
pj_cstr(&surl, url);
|
||||||
status = pj_http_req_parse_url(&surl, &hurl);
|
status = pj_http_req_parse_url(&surl, hurl);
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
if (!status) {
|
if (!status) {
|
||||||
printf("URL: %s\nProtocol: %.*s\nHost: %.*s\nPort: %d\nPath: %.*s\n\n",
|
printf("URL: %s\nProtocol: %.*s\nHost: %.*s\nPort: %d\nPath: %.*s\n\n",
|
||||||
url, STR_PREC(hurl.protocol), STR_PREC(hurl.host),
|
url, STR_PREC(hurl->protocol), STR_PREC(hurl->host),
|
||||||
hurl.port, STR_PREC(hurl.path));
|
hurl->port, STR_PREC(hurl->path));
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parse_url_test()
|
static int parse_url_test()
|
||||||
{
|
{
|
||||||
/* Simple URL without '/' in the end */
|
struct test_data
|
||||||
if (parse_url("http://www.google.com.sg") != PJ_SUCCESS)
|
{
|
||||||
return -11;
|
char *url;
|
||||||
/* Simple URL with port number but without '/' in the end */
|
pj_status_t result;
|
||||||
if (parse_url("http://www.example.com:8080") != PJ_SUCCESS)
|
const char *username;
|
||||||
return -13;
|
const char *passwd;
|
||||||
/* URL with path */
|
const char *host;
|
||||||
if (parse_url("http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6")
|
int port;
|
||||||
!= PJ_SUCCESS)
|
const char *path;
|
||||||
return -15;
|
} test_data[] =
|
||||||
/* URL with port and path */
|
{
|
||||||
if (parse_url("http://teluu.com:81/about-us/") != PJ_SUCCESS)
|
/* Simple URL without '/' in the end */
|
||||||
return -17;
|
{"http://www.pjsip.org", PJ_SUCCESS, "", "", "www.pjsip.org", 80, "/"},
|
||||||
/* unsupported protocol */
|
|
||||||
if (parse_url("ftp://www.teluu.com") != PJ_ENOTSUP)
|
/* Simple URL with port number but without '/' in the end */
|
||||||
return -19;
|
{"http://pjsip.org:8080", PJ_SUCCESS, "", "", "pjsip.org", 8080, "/"},
|
||||||
/* invalid format */
|
|
||||||
if (parse_url("http:/teluu.com/about-us/") != PJLIB_UTIL_EHTTPINURL)
|
/* URL with path */
|
||||||
return -21;
|
{"http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6",
|
||||||
/* invalid port number */
|
PJ_SUCCESS, "", "", "127.0.0.1", 280,
|
||||||
if (parse_url("http://teluu.com:xyz/") != PJLIB_UTIL_EHTTPINPORT)
|
"/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6"},
|
||||||
return -23;
|
|
||||||
|
/* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,17 @@
|
||||||
#include <pjlib-util/http_client.h>
|
#include <pjlib-util/http_client.h>
|
||||||
#include <pj/activesock.h>
|
#include <pj/activesock.h>
|
||||||
#include <pj/assert.h>
|
#include <pj/assert.h>
|
||||||
|
#include <pj/ctype.h>
|
||||||
#include <pj/errno.h>
|
#include <pj/errno.h>
|
||||||
#include <pj/except.h>
|
#include <pj/except.h>
|
||||||
#include <pj/pool.h>
|
#include <pj/pool.h>
|
||||||
#include <pj/string.h>
|
#include <pj/string.h>
|
||||||
#include <pj/timer.h>
|
#include <pj/timer.h>
|
||||||
|
#include <pjlib-util/base64.h>
|
||||||
#include <pjlib-util/errno.h>
|
#include <pjlib-util/errno.h>
|
||||||
|
#include <pjlib-util/md5.h>
|
||||||
#include <pjlib-util/scanner.h>
|
#include <pjlib-util/scanner.h>
|
||||||
|
#include <pjlib-util/string.h>
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
/* Enable some tracing */
|
/* Enable some tracing */
|
||||||
|
@ -39,7 +43,6 @@
|
||||||
#define NUM_PROTOCOL 2
|
#define NUM_PROTOCOL 2
|
||||||
#define HTTP_1_0 "1.0"
|
#define HTTP_1_0 "1.0"
|
||||||
#define HTTP_1_1 "1.1"
|
#define HTTP_1_1 "1.1"
|
||||||
#define HTTP_SEPARATOR "://"
|
|
||||||
#define CONTENT_LENGTH "Content-Length"
|
#define CONTENT_LENGTH "Content-Length"
|
||||||
/* Buffer size for sending/receiving messages. */
|
/* Buffer size for sending/receiving messages. */
|
||||||
#define BUF_SIZE 2048
|
#define BUF_SIZE 2048
|
||||||
|
@ -95,6 +98,13 @@ enum http_state
|
||||||
ABORTING,
|
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. */
|
||||||
|
};
|
||||||
|
|
||||||
struct pj_http_req
|
struct pj_http_req
|
||||||
{
|
{
|
||||||
pj_str_t url; /* Request URL */
|
pj_str_t url; /* Request URL */
|
||||||
|
@ -109,6 +119,7 @@ struct pj_http_req
|
||||||
pj_status_t error; /* Error status */
|
pj_status_t error; /* Error status */
|
||||||
pj_str_t buffer; /* Buffer to send/receive msgs */
|
pj_str_t buffer; /* Buffer to send/receive msgs */
|
||||||
enum http_state state; /* State of the HTTP request */
|
enum http_state state; /* State of the HTTP request */
|
||||||
|
enum auth_state auth_state; /* Authentication state */
|
||||||
pj_timer_entry timer_entry;/* Timer entry */
|
pj_timer_entry timer_entry;/* Timer entry */
|
||||||
pj_bool_t resolved; /* Whether URL's host is resolved */
|
pj_bool_t resolved; /* Whether URL's host is resolved */
|
||||||
pj_http_resp response; /* HTTP response */
|
pj_http_resp response; /* HTTP response */
|
||||||
|
@ -142,6 +153,11 @@ static pj_status_t http_response_parse(pj_pool_t *pool,
|
||||||
pj_http_resp *response,
|
pj_http_resp *response,
|
||||||
void *data, pj_size_t size,
|
void *data, pj_size_t size,
|
||||||
pj_size_t *remainder);
|
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)
|
static pj_uint16_t get_http_default_port(const pj_str_t *protocol)
|
||||||
{
|
{
|
||||||
|
@ -318,8 +334,59 @@ static pj_bool_t http_on_data_read(pj_activesock_t *asock,
|
||||||
hreq->response.data = data;
|
hreq->response.data = data;
|
||||||
hreq->response.size = size - rem;
|
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
|
/* We already received the response header, call the
|
||||||
* appropriate callback.
|
* appropriate callback.
|
||||||
*/
|
*/
|
||||||
if (hreq->cb.on_response)
|
if (hreq->cb.on_response)
|
||||||
(*hreq->cb.on_response)(hreq, &hreq->response);
|
(*hreq->cb.on_response)(hreq, &hreq->response);
|
||||||
|
@ -384,7 +451,7 @@ static pj_bool_t http_on_data_read(pj_activesock_t *asock,
|
||||||
hreq->response.content_length) ||
|
hreq->response.content_length) ||
|
||||||
(status == PJ_EEOF && hreq->response.content_length == -1))
|
(status == PJ_EEOF && hreq->response.content_length == -1))
|
||||||
{
|
{
|
||||||
/* Finish reading */
|
/* Finish reading */
|
||||||
http_req_end_request(hreq);
|
http_req_end_request(hreq);
|
||||||
hreq->response.size = hreq->tcp_state.current_read_size;
|
hreq->response.size = hreq->tcp_state.current_read_size;
|
||||||
|
|
||||||
|
@ -433,6 +500,98 @@ static void on_timeout( pj_timer_heap_t *timer_heap,
|
||||||
pj_http_req_cancel(hreq, PJ_TRUE);
|
pj_http_req_cancel(hreq, PJ_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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
|
/* The same as #pj_http_headers_add_elmt() with char * as
|
||||||
* its parameters.
|
* its parameters.
|
||||||
*/
|
*/
|
||||||
|
@ -464,9 +623,10 @@ static pj_status_t http_response_parse(pj_pool_t *pool,
|
||||||
{
|
{
|
||||||
pj_size_t i;
|
pj_size_t i;
|
||||||
char *cptr;
|
char *cptr;
|
||||||
char *newdata;
|
char *end_status, *newdata;
|
||||||
pj_scanner scanner;
|
pj_scanner scanner;
|
||||||
pj_str_t s;
|
pj_str_t s;
|
||||||
|
const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 };
|
||||||
pj_status_t status;
|
pj_status_t status;
|
||||||
|
|
||||||
PJ_USE_EXCEPTION;
|
PJ_USE_EXCEPTION;
|
||||||
|
@ -517,10 +677,13 @@ static pj_status_t http_response_parse(pj_pool_t *pool,
|
||||||
}
|
}
|
||||||
PJ_END;
|
PJ_END;
|
||||||
|
|
||||||
|
end_status = scanner.curptr;
|
||||||
|
pj_scan_fini(&scanner);
|
||||||
|
|
||||||
/* Parse the response headers. */
|
/* Parse the response headers. */
|
||||||
size = i - 2 - (scanner.curptr - newdata);
|
size = i - 2 - (end_status - newdata);
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
status = http_headers_parse(scanner.curptr + 1, size,
|
status = http_headers_parse(end_status + 1, size,
|
||||||
&response->headers);
|
&response->headers);
|
||||||
} else {
|
} else {
|
||||||
status = PJ_SUCCESS;
|
status = PJ_SUCCESS;
|
||||||
|
@ -528,8 +691,8 @@ static pj_status_t http_response_parse(pj_pool_t *pool,
|
||||||
|
|
||||||
/* Find content-length header field. */
|
/* Find content-length header field. */
|
||||||
for (i = 0; i < response->headers.count; i++) {
|
for (i = 0; i < response->headers.count; i++) {
|
||||||
if (!pj_stricmp2(&response->headers.header[i].name,
|
if (!pj_stricmp(&response->headers.header[i].name,
|
||||||
CONTENT_LENGTH))
|
&STR_CONTENT_LENGTH))
|
||||||
{
|
{
|
||||||
response->content_length =
|
response->content_length =
|
||||||
pj_strtoul(&response->headers.header[i].value);
|
pj_strtoul(&response->headers.header[i].value);
|
||||||
|
@ -545,8 +708,6 @@ static pj_status_t http_response_parse(pj_pool_t *pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pj_scan_fini(&scanner);
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,6 +775,7 @@ PJ_DEF(pj_status_t) pj_http_req_parse_url(const pj_str_t *url,
|
||||||
|
|
||||||
if (!len) return -1;
|
if (!len) return -1;
|
||||||
|
|
||||||
|
pj_bzero(hurl, sizeof(*hurl));
|
||||||
pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error);
|
pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error);
|
||||||
|
|
||||||
PJ_TRY {
|
PJ_TRY {
|
||||||
|
@ -634,16 +796,28 @@ PJ_DEF(pj_status_t) pj_http_req_parse_url(const pj_str_t *url,
|
||||||
PJ_THROW(PJ_ENOTSUP); // unsupported protocol
|
PJ_THROW(PJ_ENOTSUP); // unsupported protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pj_scan_strcmp(&scanner, HTTP_SEPARATOR,
|
if (pj_scan_strcmp(&scanner, "://", 3)) {
|
||||||
pj_ansi_strlen(HTTP_SEPARATOR)))
|
|
||||||
{
|
|
||||||
PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name
|
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) */
|
/* Parse the host and port number (if any) */
|
||||||
pj_scan_get_until_chr(&scanner, ":/", &s);
|
pj_scan_get_until_chr(&scanner, ":/", &s);
|
||||||
pj_strassign(&hurl->host, &s);
|
pj_strassign(&hurl->host, &s);
|
||||||
|
if (hurl->host.slen==0)
|
||||||
|
PJ_THROW(PJ_EINVAL);
|
||||||
if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') {
|
if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') {
|
||||||
/* No port number specified */
|
/* No port number specified */
|
||||||
/* Assume default http/https port number */
|
/* Assume default http/https port number */
|
||||||
|
@ -692,6 +866,7 @@ PJ_DEF(pj_status_t) pj_http_req_create(pj_pool_t *pool,
|
||||||
{
|
{
|
||||||
pj_pool_t *own_pool;
|
pj_pool_t *own_pool;
|
||||||
pj_http_req *hreq;
|
pj_http_req *hreq;
|
||||||
|
char *at_pos;
|
||||||
pj_status_t status;
|
pj_status_t status;
|
||||||
|
|
||||||
PJ_ASSERT_RETURN(pool && url && timer && ioqueue &&
|
PJ_ASSERT_RETURN(pool && url && timer && ioqueue &&
|
||||||
|
@ -736,11 +911,54 @@ PJ_DEF(pj_status_t) pj_http_req_create(pj_pool_t *pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse the URL */
|
/* 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;
|
return PJ_ENOMEM;
|
||||||
|
}
|
||||||
status = pj_http_req_parse_url(&hreq->url, &hreq->hurl);
|
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
|
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;
|
*http_req = hreq;
|
||||||
return PJ_SUCCESS;
|
return PJ_SUCCESS;
|
||||||
|
@ -770,7 +988,10 @@ PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
|
||||||
*/
|
*/
|
||||||
PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY);
|
PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY);
|
||||||
|
|
||||||
|
/* Reset few things to make sure restarting works */
|
||||||
http_req->error = 0;
|
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) {
|
if (!http_req->resolved) {
|
||||||
/* Resolve the Internet address of the host */
|
/* Resolve the Internet address of the host */
|
||||||
|
@ -837,7 +1058,357 @@ on_return:
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
#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
|
/* snprintf() to a pj_str_t struct with an option to append the
|
||||||
* result at the back of the string.
|
* result at the back of the string.
|
||||||
|
|
|
@ -45,10 +45,24 @@ static FILE *f = NULL;
|
||||||
|
|
||||||
static void on_response(pj_http_req *http_req, const pj_http_resp *resp)
|
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,
|
PJ_LOG(3,(THIS_FILE, "%.*s %d %.*s", (int)resp->version.slen, resp->version.ptr,
|
||||||
resp->status_code,
|
resp->status_code,
|
||||||
(int)resp->reason.slen, resp->reason.ptr));
|
(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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_send_data(pj_http_req *http_req, void **data, pj_size_t *size)
|
static void on_send_data(pj_http_req *http_req, void **data, pj_size_t *size)
|
||||||
|
|
Loading…
Reference in New Issue