Add possibility to specify one or more cookies for http based disks. This patch adds the config parser, storage and validation of the cookies. Signed-off-by: Peter Krempa <pkrempa@xxxxxxxxxx> --- docs/formatdomain.html.in | 10 ++ docs/schemas/domaincommon.rng | 24 ++++ src/conf/domain_conf.c | 82 +++++++++++++ src/libvirt_private.syms | 1 + src/util/virstoragefile.c | 115 ++++++++++++++++++ src/util/virstoragefile.h | 15 +++ .../disk-network-http.xml | 8 ++ 7 files changed, 255 insertions(+) diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index 8f503f6967..dfea614907 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -2849,6 +2849,9 @@ <driver name='qemu' type='raw'/> <source protocol="http" name="url_path"> <host name="hostname" port="80"/> + <cookies> + <cookie name="test">somevalue</cookie> + </cookies> </source> <target dev='hde' bus='ide' tray='open'/> <readonly/> @@ -3392,6 +3395,13 @@ certificate validation. Supported values are <code>yes</code> and <code>no</code>. <span class="since">Since 6.1.0</span> </dd> + <dt><code>cookies</code></dt> + <dd> + For <code>http</code> and <code>https</code> accessed storage it's + possible to pass one or more cookies. The cookie name and value + must conform to the HTTP specification. + <span class="since">Since 6.2.0</span> + </dd> </dl> <p> diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index d179a25ee6..85d6484dbd 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -1817,6 +1817,24 @@ </element> </define> + <define name="diskSourceNetworkProtocolHTTPCookies"> + <element name="cookies"> + <oneOrMore> + <element name="cookie"> + <attribute name="name"> + <data type="string"> + <param name="pattern">[!#$%&'*+\-.0-9A-Z\^_`a-z|~]+</param> + </data> + </attribute> + <data type="string"> + <param name="pattern">[!#$%&'()*+\-./0-9:>=<?@A-Z\^_`\[\]a-z|~]+</param> + </data> + </element> + </oneOrMore> + <empty/> + </element> + </define> + <define name="diskSourceNetworkProtocolHTTPS"> <element name="source"> <attribute name="protocol"> @@ -1833,6 +1851,9 @@ <optional> <ref name="diskSourceNetworkProtocolSSLVerify"/> </optional> + <optional> + <ref name="diskSourceNetworkProtocolHTTPCookies"/> + </optional> </element> </define> @@ -1849,6 +1870,9 @@ <optional> <ref name="encryption"/> </optional> + <optional> + <ref name="diskSourceNetworkProtocolHTTPCookies"/> + </optional> </element> </define> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index dd3a3a1439..dc7a47dd21 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -9340,6 +9340,62 @@ virDomainDiskSourcePoolDefParse(xmlNodePtr node, } +static virStorageNetCookieDefPtr +virDomainStorageCookieParse(xmlNodePtr node, + xmlXPathContextPtr ctxt) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt); + g_autoptr(virStorageNetCookieDef) cookie = NULL; + + ctxt->node = node; + + cookie = g_new0(virStorageNetCookieDef, 1); + + if (!(cookie->name = virXPathString("string(./@name)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("missing cookie name")); + return NULL; + } + + if (!(cookie->value = virXPathString("string(.)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, _("missing value for cookie '%s'"), + cookie->name); + return NULL; + } + + return g_steal_pointer(&cookie); +} + + +static int +virDomainStorageCookiesParse(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virStorageSourcePtr src) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt); + g_autofree xmlNodePtr *nodes = NULL; + ssize_t nnodes; + size_t i; + + ctxt->node = node; + + if ((nnodes = virXPathNodeSet("./cookie", ctxt, &nodes)) < 0) + return -1; + + src->cookies = g_new0(virStorageNetCookieDefPtr, nnodes); + src->ncookies = nnodes; + + for (i = 0; i < nnodes; i++) { + if (!(src->cookies[i] = virDomainStorageCookieParse(nodes[i], ctxt))) + return -1; + } + + if (virStorageSourceNetCookiesValidate(src) < 0) + return -1; + + return 0; +} + + static int virDomainDiskSourceNetworkParse(xmlNodePtr node, xmlXPathContextPtr ctxt, @@ -9351,6 +9407,7 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node, g_autofree char *haveTLS = NULL; g_autofree char *tlsCfg = NULL; g_autofree char *sslverifystr = NULL; + xmlNodePtr tmpnode; if (!(protocol = virXMLPropString(node, "protocol"))) { virReportError(VIR_ERR_XML_ERROR, "%s", @@ -9436,6 +9493,13 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node, src->sslverify = verify; } + if ((src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP || + src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) && + (tmpnode = virXPathNode("./cookies", ctxt))) { + if (virDomainStorageCookiesParse(tmpnode, ctxt, src) < 0) + return -1; + } + return 0; } @@ -24500,6 +24564,22 @@ virDomainSourceDefFormatSeclabel(virBufferPtr buf, } +static void +virDomainDiskSourceFormatNetworkCookies(virBufferPtr buf, + virStorageSourcePtr src) +{ + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + size_t i; + + for (i = 0; i < src->ncookies; i++) { + virBufferEscapeString(&childBuf, "<cookie name='%s'>", src->cookies[i]->name); + virBufferEscapeString(&childBuf, "%s</cookie>\n", src->cookies[i]->value); + } + + virXMLFormatElement(buf, "cookies", NULL, &childBuf); +} + + static int virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf, virBufferPtr childBuf, @@ -24549,6 +24629,8 @@ virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf, virBufferAsprintf(childBuf, "<ssl verify='%s'/>\n", virTristateBoolTypeToString(src->sslverify)); + virDomainDiskSourceFormatNetworkCookies(childBuf, src); + return 0; } diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 511fb88872..73db753652 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3143,6 +3143,7 @@ virStorageSourceIsEmpty; virStorageSourceIsLocalStorage; virStorageSourceIsRelative; virStorageSourceIsSameLocation; +virStorageSourceNetCookiesValidate; virStorageSourceNetworkAssignDefaultPorts; virStorageSourceNew; virStorageSourceNewFromBacking; diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index ca91fc65ba..fb5fff5c5f 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2157,6 +2157,118 @@ virStorageSourceSeclabelsCopy(virStorageSourcePtr to, } +void +virStorageNetCookieDefFree(virStorageNetCookieDefPtr def) +{ + if (!def) + return; + + g_free(def->name); + g_free(def->value); + + g_free(def); +} + + +static void +virStorageSourceCookiesClear(virStorageSourcePtr src) +{ + size_t i; + + if (!src || !src->cookies) + return; + + for (i = 0; i < src->ncookies; i++) + virStorageNetCookieDefFree(src->cookies[i]); + + g_clear_pointer(&src->cookies, g_free); + src->ncookies = 0; +} + + +static void +virStorageSourceNetCookiesCopy(virStorageSourcePtr to, + const virStorageSource *from) +{ + size_t i; + + if (from->ncookies == 0) + return; + + to->cookies = g_new0(virStorageNetCookieDefPtr, from->ncookies); + to->ncookies = from->ncookies; + + for (i = 0; i < from->ncookies; i++) { + to->cookies[i]->name = g_strdup(from->cookies[i]->name); + to->cookies[i]->value = g_strdup(from->cookies[i]->value); + } +} + + +/* see https://tools.ietf.org/html/rfc6265#section-4.1.1 */ +static const char virStorageSourceCookieValueInvalidChars[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" + " \",;\\"; + +/* in addition cookie name can't contain these */ +static const char virStorageSourceCookieNameInvalidChars[] = + "()<>@:/[]?={}"; + +static int +virStorageSourceNetCookieValidate(virStorageNetCookieDefPtr def) +{ + /* name must have at least 1 character */ + if (*(def->name) == '\0') { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cookie name must not be empty")); + return -1; + } + + /* check invalid characters in name */ + if (virStringHasChars(def->name, virStorageSourceCookieValueInvalidChars) || + virStringHasChars(def->name, virStorageSourceCookieNameInvalidChars)) { + virReportError(VIR_ERR_XML_ERROR, + _("cookie name '%s' contains invalid characters"), + def->name); + return -1; + } + + /* check invalid characters in value */ + if (virStringHasChars(def->value, virStorageSourceCookieValueInvalidChars)) { + virReportError(VIR_ERR_XML_ERROR, + _("value of cookie '%s' contains invalid characters"), + def->name); + return -1; + } + + return 0; +} + + +int +virStorageSourceNetCookiesValidate(virStorageSourcePtr src) +{ + size_t i; + size_t j; + + for (i = 0; i < src->ncookies; i++) { + if (virStorageSourceNetCookieValidate(src->cookies[i]) < 0) + return -1; + + for (j = i + 1; j < src->ncookies; j++) { + if (STREQ(src->cookies[i]->name, src->cookies[j]->name)) { + virReportError(VIR_ERR_XML_ERROR, _("duplicate cookie '%s'"), + src->cookies[i]->name); + return -1; + } + } + } + + return 0; +} + + static virStorageTimestampsPtr virStorageTimestampsCopy(const virStorageTimestamps *src) { @@ -2299,6 +2411,8 @@ virStorageSourceCopy(const virStorageSource *src, def->nhosts = src->nhosts; } + virStorageSourceNetCookiesCopy(def, src); + if (src->srcpool && !(def->srcpool = virStorageSourcePoolDefCopy(src->srcpool))) return NULL; @@ -2560,6 +2674,7 @@ virStorageSourceClear(virStorageSourcePtr def) VIR_FREE(def->volume); VIR_FREE(def->snapshot); VIR_FREE(def->configFile); + virStorageSourceCookiesClear(def); virStorageSourcePoolDefFree(def->srcpool); virBitmapFree(def->features); VIR_FREE(def->compat); diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index 49718b51d8..95d9501dd8 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -161,6 +161,17 @@ struct _virStorageNetHostDef { char *socket; /* path to unix socket */ }; +typedef struct _virStorageNetCookieDef virStorageNetCookieDef; +typedef virStorageNetCookieDef *virStorageNetCookieDefPtr; +struct _virStorageNetCookieDef { + char *name; + char *value; +}; + +void virStorageNetCookieDefFree(virStorageNetCookieDefPtr def); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virStorageNetCookieDef, virStorageNetCookieDefFree); + /* Information for a storage volume from a virStoragePool */ /* @@ -275,6 +286,8 @@ struct _virStorageSource { the source definition */ size_t nhosts; virStorageNetHostDefPtr hosts; + size_t ncookies; + virStorageNetCookieDefPtr *cookies; virStorageSourcePoolDefPtr srcpool; virStorageAuthDefPtr auth; bool authInherited; @@ -476,6 +489,8 @@ int virStorageSourceUpdateCapacity(virStorageSourcePtr src, int virStorageSourceNewFromBacking(virStorageSourcePtr parent, virStorageSourcePtr *backing); +int virStorageSourceNetCookiesValidate(virStorageSourcePtr src); + virStorageSourcePtr virStorageSourceCopy(const virStorageSource *src, bool backingChain) ATTRIBUTE_NONNULL(1); diff --git a/tests/genericxml2xmlindata/disk-network-http.xml b/tests/genericxml2xmlindata/disk-network-http.xml index bdcc1977f2..bafb77c8ec 100644 --- a/tests/genericxml2xmlindata/disk-network-http.xml +++ b/tests/genericxml2xmlindata/disk-network-http.xml @@ -33,6 +33,10 @@ <driver name='qemu' type='raw'/> <source protocol='http' name='test3.img'> <host name='example.org' port='1234'/> + <cookies> + <cookie name='test'>testcookievalue</cookie> + <cookie name='test2'>blurb</cookie> + </cookies> </source> <target dev='vdc' bus='virtio'/> </disk> @@ -41,6 +45,10 @@ <source protocol='https' name='test4.img'> <host name='example.org' port='1234'/> <ssl verify='yes'/> + <cookies> + <cookie name='test'>testcookievalue</cookie> + <cookie name='test2'>blurb</cookie> + </cookies> </source> <target dev='vdd' bus='virtio'/> </disk> -- 2.24.1