Index: /pjproject/trunk/pjlib/include/pj/sock.h
===================================================================
--- /pjproject/trunk/pjlib/include/pj/sock.h	(revision 2742)
+++ /pjproject/trunk/pjlib/include/pj/sock.h	(revision 2743)
@@ -941,4 +941,55 @@
 				      pj_uint16_t hostport);
 
+/**
+ * Parse string containing IP address and optional port into socket address,
+ * possibly also with address family detection. This function supports both
+ * IPv4 and IPv6 parsing, however IPv6 parsing may only be done if IPv6 is
+ * enabled during compilation.
+ *
+ * This function supports parsing several formats. Sample IPv4 inputs and
+ * their default results::
+ *  - "10.0.0.1:80": address 10.0.0.1 and port 80.
+ *  - "10.0.0.1": address 10.0.0.1 and port zero.
+ *  - "10.0.0.1:": address 10.0.0.1 and port zero.
+ *  - "10.0.0.1:0": address 10.0.0.1 and port zero.
+ *  - ":80": address 0.0.0.0 and port 80.
+ *  - ":": address 0.0.0.0 and port 0.
+ *  - "localhost": address 127.0.0.1 and port 0.
+ *  - "localhost:": address 127.0.0.1 and port 0.
+ *  - "localhost:80": address 127.0.0.1 and port 80.
+ *
+ * Sample IPv6 inputs and their default results:
+ *  - "[fec0::01]:80": address fec0::01 and port 80
+ *  - "[fec0::01]": address fec0::01 and port 0
+ *  - "[fec0::01]:": address fec0::01 and port 0
+ *  - "[fec0::01]:0": address fec0::01 and port 0
+ *  - "fec0::01": address fec0::01 and port 0
+ *  - "fec0::01:80": address fec0::01:80 and port 0
+ *  - "::": address zero (::) and port 0
+ *  - "[::]": address zero (::) and port 0
+ *  - "[::]:": address zero (::) and port 0
+ *  - ":::": address zero (::) and port 0
+ *  - "[::]:80": address zero (::) and port 0
+ *  - ":::80": address zero (::) and port 80
+ *
+ * Note: when the IPv6 socket address contains port number, the IP 
+ * part of the socket address should be enclosed with square brackets, 
+ * otherwise the port number will be included as part of the IP address
+ * (see "fec0::01:80" example above).
+ *
+ * @param af	    Optionally specify the address family to be used. If the
+ *		    address family is to be deducted from the input, specify
+ *		    pj_AF_UNSPEC() here.
+ * @param options   Additional options to assist the parsing, must be zero
+ *		    for now.
+ * @param str	    The input string to be parsed.
+ * @param addr	    Pointer to store the result.
+ *
+ * @return	    PJ_SUCCESS if the parsing is successful.
+ */
+PJ_DECL(pj_status_t) pj_sockaddr_parse(int af, unsigned options,
+				       const pj_str_t *str,
+				       pj_sockaddr *addr);
+
 /*****************************************************************************
  *
Index: /pjproject/trunk/pjlib/src/pj/sock_common.c
===================================================================
--- /pjproject/trunk/pjlib/src/pj/sock_common.c	(revision 2742)
+++ /pjproject/trunk/pjlib/src/pj/sock_common.c	(revision 2743)
@@ -20,4 +20,5 @@
 #include <pj/sock.h>
 #include <pj/assert.h>
+#include <pj/ctype.h>
 #include <pj/errno.h>
 #include <pj/ip_helper.h>
@@ -455,4 +456,189 @@
 }
 
+/*
+ * Parse address
+ */
+PJ_DEF(pj_status_t) pj_sockaddr_parse( int af, unsigned options,
+				       const pj_str_t *str,
+				       pj_sockaddr *addr)
+{
+    const char *end = str->ptr + str->slen;
+    const char *last_colon_pos = NULL;
+
+    PJ_ASSERT_RETURN(addr, PJ_EINVAL);
+    PJ_ASSERT_RETURN(af==PJ_AF_UNSPEC ||
+		     af==PJ_AF_INET ||
+		     af==PJ_AF_INET6, PJ_EINVAL);
+    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+
+    /* Deduce address family if it's not given */
+    if (af == PJ_AF_UNSPEC) {
+	unsigned colon_cnt = 0;
+	const char *p;
+
+	/* Can't accept NULL or empty input if address family is unknown */
+	PJ_ASSERT_RETURN(str && str->slen, PJ_EINVAL);
+
+	for (p=str->ptr; p!=end; ++p) {
+	    if (*p == ':') {
+		++colon_cnt;
+		last_colon_pos = p;
+	    }
+	}
+
+	if (colon_cnt > 1)
+	    af = PJ_AF_INET6;
+	else
+	    af = PJ_AF_INET;
+    } else {
+	/* Input may be NULL or empty as long as address family is given */
+	if (str == NULL || str->slen == 0)
+	    return pj_sockaddr_init(af, addr, NULL, 0);
+    }
+
+    if (af == PJ_AF_INET) {
+	/* Parse as IPv4. Supported formats:
+	 *  - "10.0.0.1:80"
+	 *  - "10.0.0.1"
+	 *  - "10.0.0.1:"
+	 *  - ":80"
+	 *  - ":"
+	 */
+	pj_str_t ip_part;
+	unsigned long port;
+
+	if (last_colon_pos == NULL)
+	    last_colon_pos = pj_strchr(str, ':');
+	
+	ip_part.ptr = (char*)str->ptr;
+
+	if (last_colon_pos) {
+	    pj_str_t port_part;
+	    int i;
+
+	    ip_part.slen = last_colon_pos - str->ptr;
+
+	    port_part.ptr = (char*)last_colon_pos + 1;
+	    port_part.slen = end - port_part.ptr;
+
+	    /* Make sure port number is valid */
+	    for (i=0; i<port_part.slen; ++i) {
+		if (!pj_isdigit(port_part.ptr[i]))
+		    return PJ_EINVAL;
+	    }
+	    port = pj_strtoul(&port_part);
+	    if (port > 65535)
+		return PJ_EINVAL;
+	} else {
+	    ip_part.slen = str->slen;
+	    port = 0;
+	}
+
+	return pj_sockaddr_in_init(&addr->ipv4, &ip_part, (pj_uint16_t)port);
+    }
+#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
+    else if (af == PJ_AF_INET6) {
+	/* Parse as IPv4. Supported formats:
+	 *  - "fe::01:80"  ==> note: port number is zero in this case, not 80!
+	 *  - "[fe::01]:80"
+	 *  - "fe::01"
+	 *  - "fe::01:"
+	 *  - "[fe::01]"
+	 *  - "[fe::01]:"
+	 *  - "[::]:80"
+	 *  - ":::80"
+	 *  - "[::]"
+	 *  - "[::]:"
+	 *  - ":::"
+	 *  - "::"
+	 */
+	pj_str_t ip_part, port_part;
+
+	if (*str->ptr == '[') {
+	    char *end_bracket = pj_strchr(str, ']');
+	    int i;
+	    unsigned long port;
+
+	    if (end_bracket == NULL)
+		return PJ_EINVAL;
+
+	    ip_part.ptr = (char*)str->ptr + 1;
+	    ip_part.slen = end_bracket - ip_part.ptr;
+
+	    if (last_colon_pos == NULL) {
+		const char *p;
+		for (p=str->ptr; p!=end; ++p) {
+		    if (*p == ':')
+			last_colon_pos = p;
+		}
+	    }
+
+	    if (last_colon_pos == NULL)
+		return PJ_EINVAL;
+
+	    if (last_colon_pos < end_bracket) {
+		port_part.ptr = NULL;
+		port_part.slen = 0;
+	    } else {
+		port_part.ptr = (char*)last_colon_pos + 1;
+		port_part.slen = end - port_part.ptr;
+	    }
+
+	    /* Make sure port number is valid */
+	    for (i=0; i<port_part.slen; ++i) {
+		if (!pj_isdigit(port_part.ptr[i]))
+		    return PJ_EINVAL;
+	    }
+	    port = pj_strtoul(&port_part);
+	    if (port > 65535)
+		return PJ_EINVAL;
+
+	    return pj_sockaddr_init(PJ_AF_INET6, addr, &ip_part, 
+				    (pj_uint16_t)port);
+	} else {
+	    int i;
+	    unsigned long port;
+
+	    /* First lets try to parse everything as IPv6 address */
+	    if (pj_sockaddr_init(PJ_AF_INET6, addr, str, 0)==PJ_SUCCESS)
+		return PJ_SUCCESS;
+
+	    /* Parse as IPv6:port */
+	    if (last_colon_pos == NULL) {
+		const char *p;
+		for (p=str->ptr; p!=end; ++p) {
+		    if (*p == ':')
+			last_colon_pos = p;
+		}
+	    }
+
+	    if (last_colon_pos == NULL)
+		return PJ_EINVAL;
+
+	    ip_part.ptr = (char*)str->ptr;
+	    ip_part.slen = last_colon_pos - str->ptr;
+
+	    port_part.ptr = (char*)last_colon_pos + 1;
+	    port_part.slen = end - port_part.ptr;
+
+	    /* Make sure port number is valid */
+	    for (i=0; i<port_part.slen; ++i) {
+		if (!pj_isdigit(port_part.ptr[i]))
+		    return PJ_EINVAL;
+	    }
+	    port = pj_strtoul(&port_part);
+	    if (port > 65535)
+		return PJ_EINVAL;
+
+	    return pj_sockaddr_init(PJ_AF_INET6, addr, &ip_part, 
+				    (pj_uint16_t)port);
+	}
+    }
+#endif
+    else {
+	return PJ_EIPV6NOTSUP;
+    }
+}
+
 static pj_bool_t is_usable_ip(const pj_sockaddr *addr)
 {
Index: /pjproject/trunk/pjlib/src/pjlib-test/sock.c
===================================================================
--- /pjproject/trunk/pjlib/src/pjlib-test/sock.c	(revision 2742)
+++ /pjproject/trunk/pjlib/src/pjlib-test/sock.c	(revision 2743)
@@ -168,4 +168,159 @@
     /* pj_gethostaddr() */
 
+
+    return 0;
+}
+
+static int parse_test(void)
+{
+#define IPv4	1
+#define IPv6	2
+
+    struct test_t {
+	const char  *input;
+	int	     result_af;
+	const char  *result_ip;
+	pj_uint16_t  result_port;
+    };
+    struct test_t valid_tests[] = 
+    {
+	/* IPv4 */
+	{ "10.0.0.1:80", IPv4, "10.0.0.1", 80},
+	{ "10.0.0.1", IPv4, "10.0.0.1", 0},
+	{ "10.0.0.1:", IPv4, "10.0.0.1", 0},
+	{ "10.0.0.1:0", IPv4, "10.0.0.1", 0},
+	{ ":80", IPv4, "0.0.0.0", 80},
+	{ ":", IPv4, "0.0.0.0", 0},
+	{ "localhost", IPv4, "127.0.0.1", 0},
+	{ "localhost:", IPv4, "127.0.0.1", 0},
+	{ "localhost:80", IPv4, "127.0.0.1", 80},
+
+#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
+	{ "fe::01:80", IPv6, "fe::01:80", 0},
+	{ "[fe::01]:80", IPv6, "fe::01", 80},
+	{ "fe::01", IPv6, "fe::01", 0},
+	{ "[fe::01]", IPv6, "fe::01", 0},
+	{ "fe::01:", IPv6, "fe::01", 0},
+	{ "[fe::01]:", IPv6, "fe::01", 0},
+	{ "::", IPv6, "::0", 0},
+	{ "[::]", IPv6, "::", 0},
+	{ ":::", IPv6, "::", 0},
+	{ "[::]:", IPv6, "::", 0},
+	{ ":::80", IPv6, "::", 80},
+	{ "[::]:80", IPv6, "::", 80},
+#endif
+    };
+    struct test_t invalid_tests[] = 
+    {
+	/* IPv4 */
+	{ "10.0.0.1:abcd", IPv4},   /* port not numeric */
+	{ "10.0.0.1:-1", IPv4},	    /* port contains illegal character */
+	{ "10.0.0.1:123456", IPv4}, /* port too big	*/
+	{ "1.2.3.4.5:80", IPv4},    /* invalid IP */
+
+#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
+	{ "[fe::01]:abcd", IPv6},   /* port not numeric */
+	{ "[fe::01]:-1", IPv6},	    /* port contains illegal character */
+	{ "[fe::01]:123456", IPv6}, /* port too big	*/
+	{ "fe::01:02::03:04:80", IPv6},	    /* invalid IP */
+	{ "[fe::01:02::03:04]:80", IPv6},   /* invalid IP */
+	{ "[fe:01", IPv6},	    /* Unterminated bracket */
+#endif
+    };
+
+    unsigned i;
+
+    PJ_LOG(3,("test", "...IP address parsing"));
+
+    for (i=0; i<PJ_ARRAY_SIZE(valid_tests); ++i) {
+	pj_status_t status;
+	pj_str_t input;
+	pj_sockaddr addr, result;
+
+	switch (valid_tests[i].result_af) {
+	case IPv4:
+	    valid_tests[i].result_af = PJ_AF_INET;
+	    break;
+	case IPv6:
+	    valid_tests[i].result_af = PJ_AF_INET6;
+	    break;
+	default:
+	    pj_assert(!"Invalid AF!");
+	    continue;
+	}
+
+	/* Try parsing with PJ_AF_UNSPEC */
+	status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, 
+				   pj_cstr(&input, valid_tests[i].input), 
+				   &addr);
+	if (status != PJ_SUCCESS) {
+	    PJ_LOG(1,("test", ".... failed when parsing %s", 
+		      valid_tests[i].input));
+	    return -10;
+	}
+
+	/* Build the correct result */
+	status = pj_sockaddr_init(valid_tests[i].result_af,
+				  &result,
+				  pj_cstr(&input, valid_tests[i].result_ip), 
+				  valid_tests[i].result_port);
+	if (status != PJ_SUCCESS) {
+	    PJ_LOG(1,("test", ".... error building IP address %s", 
+		      valid_tests[i].input));
+	    return -30;
+	}
+
+	/* Compare the result */
+	if (pj_sockaddr_cmp(&addr, &result) != 0) {
+	    PJ_LOG(1,("test", ".... parsed result mismatched for %s", 
+		      valid_tests[i].input));
+	    return -40;
+	}
+
+	/* Parse again with the specified af */
+	status = pj_sockaddr_parse(valid_tests[i].result_af, 0, 
+				   pj_cstr(&input, valid_tests[i].input), 
+				   &addr);
+	if (status != PJ_SUCCESS) {
+	    PJ_LOG(1,("test", ".... failed when parsing %s", 
+		      valid_tests[i].input));
+	    return -50;
+	}
+
+	/* Compare the result again */
+	if (pj_sockaddr_cmp(&addr, &result) != 0) {
+	    PJ_LOG(1,("test", ".... parsed result mismatched for %s", 
+		      valid_tests[i].input));
+	    return -60;
+	}
+    }
+
+    for (i=0; i<PJ_ARRAY_SIZE(invalid_tests); ++i) {
+	pj_status_t status;
+	pj_str_t input;
+	pj_sockaddr addr;
+
+	switch (invalid_tests[i].result_af) {
+	case IPv4:
+	    invalid_tests[i].result_af = PJ_AF_INET;
+	    break;
+	case IPv6:
+	    invalid_tests[i].result_af = PJ_AF_INET6;
+	    break;
+	default:
+	    pj_assert(!"Invalid AF!");
+	    continue;
+	}
+
+	/* Try parsing with PJ_AF_UNSPEC */
+	status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, 
+				   pj_cstr(&input, invalid_tests[i].input), 
+				   &addr);
+	if (status == PJ_SUCCESS) {
+	    PJ_LOG(1,("test", ".... expecting failure when parsing %s", 
+		      invalid_tests[i].input));
+	    return -100;
+	}
+    }
 
     return 0;
@@ -577,4 +732,8 @@
 	return rc;
 
+    rc = parse_test();
+    if (rc != 0)
+	return rc;
+
     rc = gethostbyname_test();
     if (rc != 0)
