Add name resolution APIs that return names as bytes objects. Adds getnameinfob(), getaddrinfob(), gethostbyaddrb(), gethostbyname_exb() and gethostnameb(). diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -258,6 +258,15 @@ The module :mod:`socket` exports the fol Raises :exc:`UnicodeDecodeError` if any of the returned names contain non-ASCII byte values. + +.. function:: getaddrinfob(host, port, family=0, type=0, proto=0, flags=0) + + Like :func:`getaddrinfo`, but returns host names as bytes objects + without decoding them. + + .. versionadded:: 3.2 + + .. function:: getfqdn([name]) Return a fully qualified domain name for *name*. If *name* is omitted or empty, @@ -293,6 +302,14 @@ The module :mod:`socket` exports the fol contain non-ASCII byte values. +.. function:: gethostbyname_exb(hostname) + + Like :func:`gethostbyname_ex`, but returns host names as bytes + objects without decoding them. + + .. versionadded:: 3.2 + + .. function:: gethostname() Return a string containing the hostname of the machine where the Python @@ -310,6 +327,15 @@ The module :mod:`socket` exports the fol Raises :exc:`UnicodeDecodeError` if the returned name contains non-ASCII byte values. + +.. function:: gethostnameb() + + Like :func:`gethostname`, but returns the hostname as a bytes + object without decoding it. + + .. versionadded:: 3.2 + + .. function:: gethostbyaddr(ip_address) Return a triple ``(hostname, aliaslist, ipaddrlist)`` where *hostname* is the @@ -325,6 +351,14 @@ The module :mod:`socket` exports the fol contain non-ASCII byte values. +.. function:: gethostbyaddrb(ip_address) + + Like :func:`gethostbyaddr`, but returns host names as bytes objects + without decoding them. + + .. versionadded:: 3.2 + + .. function:: getnameinfo(sockaddr, flags) Translate a socket address *sockaddr* into a 2-tuple ``(host, port)``. Depending @@ -337,6 +371,14 @@ The module :mod:`socket` exports the fol non-ASCII byte values. +.. function:: getnameinfob(sockaddr, flags) + + Like :func:`getnameinfo`, but returns the host name as a bytes + object without decoding it. + + .. versionadded:: 3.2 + + .. function:: getprotobyname(protocolname) Translate an Internet protocol name (for example, ``'icmp'``) to a constant diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -303,6 +303,64 @@ class GeneralModuleTests(unittest.TestCa if not fqhn in all_host_names: self.fail("Error testing host resolution mechanisms. (fqdn: %s, all: %s)" % (fqhn, repr(all_host_names))) + def testGethostnameTypes(self): + # Check that gethostname() and gethostnameb() return the + # correct types. + self.assertIsInstance(socket.gethostname(), str) + self.assertIsInstance(socket.gethostnameb(), bytes) + + def testGethostbyaddrTypes(self): + # Check that gethostbyaddr() and gethostbyaddrb() return the + # correct type for names. + for func, t in ((socket.gethostbyaddr, str), + (socket.gethostbyaddrb, bytes)): + try: + name, aliases, addrs = func("127.0.0.1") + except socket.error: + self.skipTest("Can't get name for 127.0.0.1") + else: + self.assertIsInstance(name, t) + for alias in aliases: + self.assertIsInstance(alias, t) + + def testGethostbyname_exTypes(self): + # Check that gethostbyname_ex() and gethostbyname_exb() return + # the correct type for names. + for func, t in ((socket.gethostbyname_ex, str), + (socket.gethostbyname_exb, bytes)): + try: + name, aliases, addrs = func("localhost") + except socket.error: + self.skipTest("Can't get address for 'localhost'") + else: + self.assertIsInstance(name, t) + for alias in aliases: + self.assertIsInstance(alias, t) + + def testGetnameinfoTypes(self): + # Check that getnameinfo() and getnameinfob() return the + # correct type for names. + for func, t in ((socket.getnameinfo, str), + (socket.getnameinfob, bytes)): + try: + name, service = func(("127.0.0.1", 0), 0) + except socket.gaierror: + self.skipTest("Can't get name for 127.0.0.1") + else: + self.assertIsInstance(name, t) + + def testGetaddrinfoTypes(self): + # Check that getnameinfo() and getnameinfob() return the + # correct type for names. + for func, t in ((socket.getaddrinfo, str), + (socket.getaddrinfob, bytes)): + try: + info = func("localhost", 80, flags=socket.AI_CANONNAME) + except socket.gaierror: + self.skipTest("Can't get address for 'localhost'") + else: + self.assertIsInstance(info[0][3], t) + def testRefCountGetNameInfo(self): # Testing reference count for getnameinfo if hasattr(sys, "getrefcount"): @@ -640,12 +698,15 @@ class GeneralModuleTests(unittest.TestCa def test_getnameinfo(self): # only IP addresses are allowed self.assertRaises(socket.error, socket.getnameinfo, ('mail.python.org',0), 0) + self.assertRaises(socket.error, socket.getnameinfob, ('mail.python.org',0), 0) def test_idna(self): # these should all be successful socket.gethostbyname('испытание.python.org') socket.gethostbyname_ex('испытание.python.org') socket.getaddrinfo('испытание.python.org',0,socket.AF_UNSPEC,socket.SOCK_STREAM) + socket.gethostbyname_exb('испытание.python.org') + socket.getaddrinfob('испытание.python.org',0,socket.AF_UNSPEC,socket.SOCK_STREAM) # this may not work if the forward lookup choses the IPv6 address, as that doesn't # have a reverse entry yet # socket.gethostbyaddr('испытание.python.org') diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -2980,9 +2980,8 @@ static PyTypeObject sock_type = { /* Python interface to gethostname(). */ -/*ARGSUSED*/ static PyObject * -socket_gethostname(PyObject *self, PyObject *unused) +gethostname_common(PyObject *(*name_constructor)(const char *)) { char buf[1024]; int res; @@ -2992,7 +2991,14 @@ socket_gethostname(PyObject *self, PyObj if (res < 0) return set_error(); buf[sizeof buf - 1] = '\0'; - return decode_hostname(buf); + return (*name_constructor)(buf); +} + +/*ARGSUSED*/ +static PyObject * +socket_gethostname(PyObject *self, PyObject *unused) +{ + return gethostname_common(decode_hostname); } PyDoc_STRVAR(gethostname_doc, @@ -3000,6 +3006,18 @@ PyDoc_STRVAR(gethostname_doc, \n\ Return the current host name."); +/*ARGSUSED*/ +static PyObject * +socket_gethostnameb(PyObject *self, PyObject *unused) +{ + return gethostname_common(PyBytes_FromString); +} + +PyDoc_STRVAR(gethostnameb_doc, +"gethostnameb() -> bytes\n\ +\n\ +Return the current host name as a bytes object."); + /* Python interface to gethostbyname(name). */ @@ -3030,7 +3048,8 @@ Return the IP address (a string of the f /* Convenience function common to gethostbyname_ex and gethostbyaddr */ static PyObject * -gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af) +gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af, + PyObject *(*name_constructor)(const char *)) { char **pch; PyObject *rtn_tuple = (PyObject *)NULL; @@ -3078,7 +3097,7 @@ gethost_common(struct hostent *h, struct if (h->h_aliases) { for (pch = h->h_aliases; *pch != NULL; pch++) { int status; - tmp = decode_hostname(*pch); + tmp = (*name_constructor)(*pch); if (tmp == NULL) goto err; @@ -3146,7 +3165,7 @@ gethost_common(struct hostent *h, struct goto err; } - rtn_tuple = Py_BuildValue("NOO", decode_hostname(h->h_name), + rtn_tuple = Py_BuildValue("NOO", (*name_constructor)(h->h_name), name_list, addr_list); err: @@ -3158,9 +3177,9 @@ gethost_common(struct hostent *h, struct /* Python interface to gethostbyname_ex(name). */ -/*ARGSUSED*/ static PyObject * -socket_gethostbyname_ex(PyObject *self, PyObject *args) +gethostbyname_ex_common(PyObject *args, + PyObject *(*name_constructor)(const char *)) { char *name; struct hostent *h; @@ -3214,7 +3233,7 @@ socket_gethostbyname_ex(PyObject *self, access sa_family. */ sa = (struct sockaddr*)&addr; ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr), - sa->sa_family); + sa->sa_family, name_constructor); #ifdef USE_GETHOSTBYNAME_LOCK PyThread_release_lock(netdb_lock); #endif @@ -3223,18 +3242,39 @@ finally: return ret; } +/*ARGSUSED*/ +static PyObject * +socket_gethostbyname_ex(PyObject *self, PyObject *args) +{ + return gethostbyname_ex_common(args, decode_hostname); +} + PyDoc_STRVAR(ghbn_ex_doc, "gethostbyname_ex(host) -> (name, aliaslist, addresslist)\n\ \n\ Return the true host name, a list of aliases, and a list of IP addresses,\n\ for a host. The host argument is a string giving a host name or IP number."); - -/* Python interface to gethostbyaddr(IP). */ - /*ARGSUSED*/ static PyObject * -socket_gethostbyaddr(PyObject *self, PyObject *args) +socket_gethostbyname_exb(PyObject *self, PyObject *args) +{ + return gethostbyname_ex_common(args, PyBytes_FromString); +} + +PyDoc_STRVAR(ghbn_exb_doc, +"gethostbyname_exb(host) -> (name, aliaslist, addresslist)\n\ +\n\ +Return the true host name, a list of aliases, and a list of IP addresses,\n\ +for a host, returning the host names as bytes objects. The host argument\n\ +is a string giving a host name or IP number."); + + +/* Python interface to gethostbyaddr(IP). */ + +static PyObject * +gethostbyaddr_common(PyObject *args, + PyObject *(*name_constructor)(const char *)) { #ifdef ENABLE_IPV6 struct sockaddr_storage addr; @@ -3310,7 +3350,8 @@ socket_gethostbyaddr(PyObject *self, PyO h = gethostbyaddr(ap, al, af); #endif /* HAVE_GETHOSTBYNAME_R */ Py_END_ALLOW_THREADS - ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr), af); + ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr), af, + name_constructor); #ifdef USE_GETHOSTBYNAME_LOCK PyThread_release_lock(netdb_lock); #endif @@ -3319,12 +3360,33 @@ finally: return ret; } +/*ARGSUSED*/ +static PyObject * +socket_gethostbyaddr(PyObject *self, PyObject *args) +{ + return gethostbyaddr_common(args, decode_hostname); +} + PyDoc_STRVAR(gethostbyaddr_doc, "gethostbyaddr(host) -> (name, aliaslist, addresslist)\n\ \n\ Return the true host name, a list of aliases, and a list of IP addresses,\n\ for a host. The host argument is a string giving a host name or IP number."); +/*ARGSUSED*/ +static PyObject * +socket_gethostbyaddrb(PyObject *self, PyObject *args) +{ + return gethostbyaddr_common(args, PyBytes_FromString); +} + +PyDoc_STRVAR(gethostbyaddrb_doc, +"gethostbyaddrb(host) -> (name, aliaslist, addresslist)\n\ +\n\ +Return the true host name, a list of aliases, and a list of IP addresses,\n\ +for a host, returning the host names as bytes objects. The host argument\n\ +is a string giving a host name or IP number."); + /* Python interface to getservbyname(name). This only returns the port number, since the other info is already @@ -3841,9 +3903,9 @@ socket_inet_ntop(PyObject *self, PyObjec /* Python interface to getaddrinfo(host, port). */ -/*ARGSUSED*/ static PyObject * -socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs) +getaddrinfo_common(PyObject *args, PyObject* kwargs, + PyObject *(*name_constructor)(const char *)) { static char* kwnames[] = {"host", "port", "family", "type", "proto", "flags", 0}; @@ -3921,7 +3983,7 @@ socket_getaddrinfo(PyObject *self, PyObj goto err; single = Py_BuildValue("iiiNO", res->ai_family, res->ai_socktype, res->ai_protocol, - decode_hostname(res->ai_canonname ? res->ai_canonname : ""), + (*name_constructor)(res->ai_canonname ? res->ai_canonname : ""), addr); Py_DECREF(addr); if (single == NULL) @@ -3943,17 +4005,37 @@ socket_getaddrinfo(PyObject *self, PyObj return (PyObject *)NULL; } +/*ARGSUSED*/ +static PyObject * +socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs) +{ + return getaddrinfo_common(args, kwargs, decode_hostname); +} + PyDoc_STRVAR(getaddrinfo_doc, "getaddrinfo(host, port [, family, socktype, proto, flags])\n\ -> list of (family, socktype, proto, canonname, sockaddr)\n\ \n\ Resolve host and port into addrinfo struct."); -/* Python interface to getnameinfo(sa, flags). */ - /*ARGSUSED*/ static PyObject * -socket_getnameinfo(PyObject *self, PyObject *args) +socket_getaddrinfob(PyObject *self, PyObject *args, PyObject* kwargs) +{ + return getaddrinfo_common(args, kwargs, PyBytes_FromString); +} + +PyDoc_STRVAR(getaddrinfob_doc, +"getaddrinfob(host, port [, family, socktype, proto, flags])\n\ + -> list of (family, socktype, proto, canonname, sockaddr)\n\ +\n\ +Resolve host and port into addrinfo struct, returning host\n\ +names as bytes objects."); + +/* Python interface to getnameinfo(sa, flags). */ + +static PyObject * +getnameinfo_common(PyObject *args, PyObject *(*name_constructor)(const char *)) { PyObject *sa = (PyObject *)NULL; int flags; @@ -4021,7 +4103,7 @@ socket_getnameinfo(PyObject *self, PyObj set_gaierror(error); goto fail; } - ret = Py_BuildValue("Ns", decode_hostname(hbuf), pbuf); + ret = Py_BuildValue("Ns", (*name_constructor)(hbuf), pbuf); fail: if (res) @@ -4029,11 +4111,31 @@ fail: return ret; } +/*ARGSUSED*/ +static PyObject * +socket_getnameinfo(PyObject *self, PyObject *args) +{ + return getnameinfo_common(args, decode_hostname); +} + PyDoc_STRVAR(getnameinfo_doc, "getnameinfo(sockaddr, flags) --> (host, port)\n\ \n\ Get host and port for a sockaddr."); +/*ARGSUSED*/ +static PyObject * +socket_getnameinfob(PyObject *self, PyObject *args) +{ + return getnameinfo_common(args, PyBytes_FromString); +} + +PyDoc_STRVAR(getnameinfob_doc, +"getnameinfob(sockaddr, flags) --> (host, port)\n\ +\n\ +Get host and port for a sockaddr, returning the host name as\n\ +a bytes object."); + /* Python API to getting and setting the default timeout value. */ @@ -4093,10 +4195,16 @@ static PyMethodDef socket_methods[] = { METH_VARARGS, gethostbyname_doc}, {"gethostbyname_ex", socket_gethostbyname_ex, METH_VARARGS, ghbn_ex_doc}, + {"gethostbyname_exb", socket_gethostbyname_exb, + METH_VARARGS, ghbn_exb_doc}, {"gethostbyaddr", socket_gethostbyaddr, METH_VARARGS, gethostbyaddr_doc}, + {"gethostbyaddrb", socket_gethostbyaddrb, + METH_VARARGS, gethostbyaddrb_doc}, {"gethostname", socket_gethostname, METH_NOARGS, gethostname_doc}, + {"gethostnameb", socket_gethostnameb, + METH_NOARGS, gethostnameb_doc}, {"getservbyname", socket_getservbyname, METH_VARARGS, getservbyname_doc}, {"getservbyport", socket_getservbyport, @@ -4131,8 +4239,12 @@ static PyMethodDef socket_methods[] = { #endif {"getaddrinfo", (PyCFunction)socket_getaddrinfo, METH_VARARGS | METH_KEYWORDS, getaddrinfo_doc}, + {"getaddrinfob", (PyCFunction)socket_getaddrinfob, + METH_VARARGS | METH_KEYWORDS, getaddrinfob_doc}, {"getnameinfo", socket_getnameinfo, METH_VARARGS, getnameinfo_doc}, + {"getnameinfob", socket_getnameinfob, + METH_VARARGS, getnameinfob_doc}, {"getdefaulttimeout", (PyCFunction)socket_getdefaulttimeout, METH_NOARGS, getdefaulttimeout_doc}, {"setdefaulttimeout", socket_setdefaulttimeout,