Index: Objects/bytesobject.c =================================================================== --- Objects/bytesobject.c (Revision 53942) +++ Objects/bytesobject.c (Arbeitskopie) @@ -970,17 +970,80 @@ return NULL; } +PyDoc_STRVAR(fromhex_doc, +"bytes.fromhex(string) -> bytes\n\ +\n\ +Create a bytes object from a string of hexadecimal numbers.\n\ +Spaces between two numbers are accepted. Example:\n\ +bytes.fromhex('10 2030') -> bytes([0x10, 0x20, 0x30])."); + +static int +hex_digit_to_int(int c) +{ + if (isdigit(c)) + return c - '0'; + else { + if (isupper(c)) + c = tolower(c); + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + } + return -1; +} + +static PyObject * +bytes_fromhex(PyObject *cls, PyObject *args) +{ + PyObject *new; + char *hex, *buf; + Py_ssize_t len, byteslen, i, j; + int top, bot; + + if (!PyArg_ParseTuple(args, "s#:fromhex", &hex, &len)) + return NULL; + + byteslen = (len / 2) + 1; /* max length if there are no spaces */ + + new = PyBytes_FromStringAndSize(NULL, byteslen); + if (!new) + return NULL; + buf = ((PyBytesObject *)new)->ob_bytes; + + for (i = j = 0; i < len; i += 2) { + while (isspace(Py_CHARMASK(hex[i]))) + i++; + if (hex[i] == 0) + break; + top = hex_digit_to_int(Py_CHARMASK(hex[i])); + bot = hex_digit_to_int(Py_CHARMASK(hex[i+1])); + if (top == -1 || bot == -1) { + PyErr_Format(PyExc_ValueError, + "non-hexadecimal number string '%c%c' found in " + "fromhex() arg", hex[i], hex[i+1]); + goto error; + } + buf[j++] = (top << 4) + bot; + } + if (PyBytes_Resize(new, j) < 0) + goto error; + return new; + + error: + Py_DECREF(new); + return NULL; +} + static PySequenceMethods bytes_as_sequence = { - (lenfunc)bytes_length, /*sq_length*/ - (binaryfunc)bytes_concat, /*sq_concat*/ - (ssizeargfunc)bytes_repeat, /*sq_repeat*/ - (ssizeargfunc)bytes_getitem, /*sq_item*/ - 0, /*sq_slice*/ - (ssizeobjargproc)bytes_setitem, /*sq_ass_item*/ - 0, /* sq_ass_slice */ + (lenfunc)bytes_length, /* sq_length */ + (binaryfunc)bytes_concat, /* sq_concat */ + (ssizeargfunc)bytes_repeat, /* sq_repeat */ + (ssizeargfunc)bytes_getitem, /* sq_item */ + 0, /* sq_slice */ + (ssizeobjargproc)bytes_setitem, /* sq_ass_item */ + 0, /* sq_ass_slice */ (objobjproc)bytes_contains, /* sq_contains */ - (binaryfunc)bytes_iconcat, /* sq_inplace_concat */ - (ssizeargfunc)bytes_irepeat, /* sq_inplace_repeat */ + (binaryfunc)bytes_iconcat, /* sq_inplace_concat */ + (ssizeargfunc)bytes_irepeat, /* sq_inplace_repeat */ }; static PyMappingMethods bytes_as_mapping = { @@ -1002,6 +1065,7 @@ bytes_methods[] = { {"decode", (PyCFunction)bytes_decode, METH_VARARGS, decode_doc}, {"__alloc__", (PyCFunction)bytes_alloc, METH_NOARGS, alloc_doc}, + {"fromhex", (PyCFunction)bytes_fromhex, METH_VARARGS|METH_CLASS, fromhex_doc}, {"join", (PyCFunction)bytes_join, METH_O|METH_CLASS, join_doc}, {NULL} }; @@ -1032,7 +1096,7 @@ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ &bytes_as_buffer, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ /* bytes is 'final' or 'sealed' */ bytes_doc, /* tp_doc */ 0, /* tp_traverse */ Index: Lib/test/test_bytes.py =================================================================== --- Lib/test/test_bytes.py (Revision 53942) +++ Lib/test/test_bytes.py (Arbeitskopie) @@ -396,6 +396,18 @@ seq.append(alloc) #print seq + def test_fromhex(self): + self.assertRaises(TypeError, bytes.fromhex) + self.assertRaises(TypeError, bytes.fromhex, 1) + self.assertEquals(bytes.fromhex(''), bytes()) + b = bytes([0x1a, 0x2b, 0x30]) + self.assertEquals(bytes.fromhex('1a2B30'), b) + self.assertEquals(bytes.fromhex('1A 2B 30'), b) + self.assertEquals(bytes.fromhex('\n\n1a\r2b 30'), b) + self.assertRaises(ValueError, bytes.fromhex, 'a') + self.assertRaises(ValueError, bytes.fromhex, 'rt') + self.assertRaises(ValueError, bytes.fromhex, '1a b cd') + def test_join(self): self.assertEqual(bytes.join([]), bytes()) self.assertEqual(bytes.join([bytes()]), bytes())