diff --git a/.travis.yml b/.travis.yml
index f1b1a5f6..6708afde 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,9 @@
dist: trusty
sudo: false
language: python
+notifications:
+ email: false
+
python:
- '2.7'
- '3.4'
diff --git a/doc/source/modules/constants.rst b/doc/source/modules/constants.rst
index c69ef359..460fdd22 100644
--- a/doc/source/modules/constants.rst
+++ b/doc/source/modules/constants.rst
@@ -58,6 +58,8 @@ Namespaces
- *XPointerNs* - http://www.w3.org/2001/04/xmldsig-more/xptr
- *Soap11Ns* - http://schemas.xmlsoap.org/soap/envelope/
- *Soap12Ns* - http://www.w3.org/2002/06/soap-envelope
+- *NsExcC14N* - http://www.w3.org/2001/10/xml-exc-c14n#
+- *NsExcC14NWithComments* - http://www.w3.org/2001/10/xml-exc-c14n#WithComments
Nodes
*****
diff --git a/src/constants.c b/src/constants.c
index 4c890729..ad85a210 100644
--- a/src/constants.c
+++ b/src/constants.c
@@ -292,6 +292,8 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_NS_CONSTANT(XPointerNs, "XPOINTER");
PYXMLSEC_ADD_NS_CONSTANT(Soap11Ns, "SOAP11");
PYXMLSEC_ADD_NS_CONSTANT(Soap12Ns, "SOAP12");
+ PYXMLSEC_ADD_NS_CONSTANT(NsExcC14N, "EXC_C14N");
+ PYXMLSEC_ADD_NS_CONSTANT(NsExcC14NWithComments, "EXC_C14N_WITH_COMMENT");
PYXMLSEC_CLOSE_NAMESPACE(nsCls);
diff --git a/src/platform.h b/src/platform.h
index eae902b9..795062f2 100644
--- a/src/platform.h
+++ b/src/platform.h
@@ -37,6 +37,7 @@ typedef int Py_ssize_t;
#if PY_MAJOR_VERSION >= 3
#define PY3K 1
+#define PyString_Check PyUnicode_Check
#define PyString_FromStringAndSize PyUnicode_FromStringAndSize
#define PyString_FromString PyUnicode_FromString
diff --git a/src/template.c b/src/template.c
index f2c4bffe..56aa4bf4 100644
--- a/src/template.c
+++ b/src/template.c
@@ -651,6 +651,57 @@ static PyObject* PyXmlSec_TemplateEncryptedDataEnsureCipherValue(PyObject* self,
return NULL;
}
+static char PyXmlSec_TemplateTransformAddC14NInclNamespaces__doc__[] = \
+ "Adds 'inclusive' namespaces to the ExcC14N transform node *node*.\n\n"
+ ":param node: the pointer to node.\n"
+ ":param prefixList: the list of namespace prefixes, where 'default' indicates the default namespace (optional).";
+static PyObject* PyXmlSec_TemplateTransformAddC14NInclNamespaces(PyObject* self, PyObject *args, PyObject *kwargs) {
+ static char *kwlist[] = { "node", "prefixes", NULL};
+
+ PyXmlSec_LxmlElementPtr node = NULL;
+ PyObject* prefixes = NULL;
+ // transform_add_c14n_inclusive_namespaces
+ PYXMLSEC_DEBUG("template encrypted_data_ensure_cipher_value - start");
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O:transform_add_c14n_inclusive_namespaces", kwlist,
+ PyXmlSec_LxmlElementConverter, &node, &prefixes))
+ {
+ prefixes = NULL;
+ goto ON_FAIL;
+ }
+ if (PyList_Check(prefixes) || PyTuple_Check(prefixes)) {
+ PyObject* sep = PyString_FromString(" ");
+ prefixes = PyObject_CallMethod(sep, "join", "O", prefixes);
+ Py_DECREF(sep);
+ } else if (PyString_Check(prefixes)) {
+ Py_INCREF(prefixes);
+ } else {
+ PyErr_SetString(PyExc_TypeError, "expected instance of str or list of str");
+ prefixes = NULL;
+ }
+
+ if (prefixes == NULL) {
+ goto ON_FAIL;
+ }
+
+ int res;
+ const char* c_prefixes = PyString_AsString(prefixes);
+ Py_BEGIN_ALLOW_THREADS;
+ res = xmlSecTmplTransformAddC14NInclNamespaces(node->_c_node, XSTR(c_prefixes));
+ Py_END_ALLOW_THREADS;
+ if (res != 0) {
+ PyXmlSec_SetLastError("cannot add 'inclusive' namespaces to the ExcC14N transform node");
+ goto ON_FAIL;
+ }
+
+ Py_DECREF(prefixes);
+ PYXMLSEC_DEBUG("transform_add_c14n_inclusive_namespaces - ok");
+ Py_RETURN_NONE;
+
+ON_FAIL:
+ PYXMLSEC_DEBUG("transform_add_c14n_inclusive_namespaces - fail");
+ Py_XDECREF(prefixes);
+ return NULL;
+}
static PyMethodDef PyXmlSec_TemplateMethods[] = {
{
@@ -761,6 +812,12 @@ static PyMethodDef PyXmlSec_TemplateMethods[] = {
METH_VARARGS|METH_KEYWORDS,
PyXmlSec_TemplateEncryptedDataEnsureCipherValue__doc__
},
+ {
+ "transform_add_c14n_inclusive_namespaces",
+ (PyCFunction)PyXmlSec_TemplateTransformAddC14NInclNamespaces,
+ METH_VARARGS|METH_KEYWORDS,
+ PyXmlSec_TemplateTransformAddC14NInclNamespaces__doc__,
+ },
{NULL, NULL} /* sentinel */
};
diff --git a/tests/examples/test_templates.py b/tests/examples/test_templates.py
new file mode 100644
index 00000000..a6fd1b78
--- /dev/null
+++ b/tests/examples/test_templates.py
@@ -0,0 +1,35 @@
+import xmlsec
+from .base import parse_xml
+from lxml import etree
+
+
+def _check_transform_add_custom_c14n_inclusive_namespaces(prefixes, expected):
+ template = parse_xml('sign2-doc.xml')
+ assert template is not None
+
+ # Create a signature template for RSA-SHA1 enveloped signature.
+ signature_node = xmlsec.template.create(template, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA1)
+ assert signature_node is not None
+
+ # Add the node to the document.
+ template.append(signature_node)
+
+ # Add the node to the signature template.
+ ref = xmlsec.template.add_reference(signature_node, xmlsec.Transform.SHA1)
+
+ # Add the enveloped transform descriptor.
+ transform = xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
+ assert transform is not None
+
+ xmlsec.template.transform_add_c14n_inclusive_namespaces(transform, prefixes)
+ ins = xmlsec.tree.find_child(transform, "InclusiveNamespaces", xmlsec.constants.NsExcC14N)
+ assert ins is not None
+ assert expected == ins.get("PrefixList")
+
+
+def test_transform_add_custom_c14n_inclusive_namespaces():
+ _check_transform_add_custom_c14n_inclusive_namespaces(["ns1", "ns2"], "ns1 ns2")
+
+
+def test_transform_add_default_c14n_inclusive_namespaces():
+ _check_transform_add_custom_c14n_inclusive_namespaces("default", "default")