Add possibility to specify one or more cookies for http based disks. This patch adds the config parser, storage and validation of the cookies. --- docs/formatdomain.html.in | 9 ++ docs/schemas/domaincommon.rng | 39 +++++-- src/conf/domain_conf.c | 108 +++++++++++++++++- src/util/virstoragefile.c | 124 +++++++++++++++++++++ src/util/virstoragefile.h | 14 +++ .../generic-disk-network-http.xml | 4 + 6 files changed, 289 insertions(+), 9 deletions(-) diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index e31a271a5..ab70edff3 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -2245,6 +2245,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/> @@ -2593,6 +2596,12 @@ protocol. Supported for 'rbd' <span class="since">since 1.2.11 (QEMU only).</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. + </dd> </dl> <p> diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 0c51f5151..b2fa72381 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -1556,16 +1556,39 @@ </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="diskSourceNetworkProtocolHTTP"> <element name="source"> - <attribute name="protocol"> - <choice> - <value>http</value> - <value>https</value> - </choice> - </attribute> - <attribute name="name"/> - <ref name="diskSourceNetworkHost"/> + <interleave> + <attribute name="protocol"> + <choice> + <value>http</value> + <value>https</value> + </choice> + </attribute> + <attribute name="name"/> + <ref name="diskSourceNetworkHost"/> + <optional> + <ref name="diskSourceNetworkProtocolHTTPCookies"/> + </optional> + </interleave> </element> </define> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 5a736c853..b1a357174 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -7520,6 +7520,78 @@ virDomainDiskSourcePoolDefParse(xmlNodePtr node, } +static virStorageNetCookieDefPtr +virDomainStorageCookieParse(xmlNodePtr node, + xmlXPathContextPtr ctxt) +{ + virStorageNetCookieDefPtr cookie = NULL; + virStorageNetCookieDefPtr ret = NULL; + xmlNodePtr oldnode = ctxt->node; + + ctxt->node = node; + + if (VIR_ALLOC(cookie) < 0) + goto cleanup; + + if (!(cookie->name = virXPathString("string(./@name)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("missing cookie name")); + goto cleanup; + } + + if (!(cookie->value = virXPathString("string(.)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, _("missing value for cookie '%s'"), + cookie->name); + goto cleanup; + } + + VIR_STEAL_PTR(ret, cookie); + + cleanup: + ctxt->node = oldnode; + virStorageNetCookieDefFree(cookie); + return ret; +} + + +static int +virDomainStorageCookiesParse(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virStorageSourcePtr src) +{ + xmlNodePtr oldnode = ctxt->node; + xmlNodePtr *nodes = NULL; + ssize_t nnodes; + size_t i; + int ret = -1; + + ctxt->node = node; + + if ((nnodes = virXPathNodeSet("./cookie", ctxt, &nodes)) < 0) + goto cleanup; + + if (VIR_ALLOC_N(src->cookies, nnodes) < 0) + goto cleanup; + + src->ncookies = nnodes; + + for (i = 0; i < nnodes; i++) { + if (!(src->cookies[i] = virDomainStorageCookieParse(nodes[i], ctxt))) + goto cleanup; + } + + if (virStorageSourceNetCookiesValidate(src) < 0) + goto cleanup; + + ret = 0; + + cleanup: + VIR_FREE(nodes); + ctxt->node = oldnode; + + return ret; +} + + int virDomainDiskSourceParse(xmlNodePtr node, xmlXPathContextPtr ctxt, @@ -7528,6 +7600,7 @@ virDomainDiskSourceParse(xmlNodePtr node, int ret = -1; char *protocol = NULL; xmlNodePtr saveNode = ctxt->node; + xmlNodePtr tmpnode; ctxt->node = node; @@ -7590,6 +7663,12 @@ virDomainDiskSourceParse(xmlNodePtr node, if (virDomainStorageHostParse(node, &src->hosts, &src->nhosts) < 0) goto cleanup; + + if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP && + (tmpnode = virXPathNode("./cookies", ctxt))) { + if (virDomainStorageCookiesParse(tmpnode, ctxt, src) < 0) + goto cleanup; + } break; case VIR_STORAGE_TYPE_VOLUME: if (virDomainDiskSourcePoolDefParse(node, &src->srcpool) < 0) @@ -20769,6 +20848,30 @@ virDomainSourceDefFormatSeclabel(virBufferPtr buf, static int +virDomainDiskSourceFormatNetworkCookies(virBufferPtr buf, + virStorageSourcePtr src) +{ + size_t i; + + if (src->ncookies == 0) + return 0; + + virBufferAddLit(buf, "<cookies>\n"); + virBufferAdjustIndent(buf, 2); + + for (i = 0; i < src->ncookies; i++) { + virBufferEscapeString(buf, "<cookie name='%s'>", src->cookies[i]->name); + virBufferEscapeString(buf, "%s</cookie>\n", src->cookies[i]->value); + } + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</cookies>\n"); + + return 0; +} + + +static int virDomainDiskSourceFormatNetwork(virBufferPtr buf, virStorageSourcePtr src) { @@ -20787,7 +20890,7 @@ virDomainDiskSourceFormatNetwork(virBufferPtr buf, VIR_FREE(path); - if (src->nhosts == 0 && !src->snapshot && !src->configFile) { + if (src->nhosts == 0 && !src->snapshot && !src->configFile && src->ncookies == 0) { virBufferAddLit(buf, "/>\n"); } else { virBufferAddLit(buf, ">\n"); @@ -20809,6 +20912,9 @@ virDomainDiskSourceFormatNetwork(virBufferPtr buf, virBufferEscapeString(buf, "<snapshot name='%s'/>\n", src->snapshot); virBufferEscapeString(buf, "<config file='%s'/>\n", src->configFile); + if (virDomainDiskSourceFormatNetworkCookies(buf, src) < 0) + return -1; + virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "</source>\n"); } diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 94a77ce86..fe03a3009 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -1948,6 +1948,126 @@ virStorageSourceSeclabelsCopy(virStorageSourcePtr to, } +void +virStorageNetCookieDefFree(virStorageNetCookieDefPtr def) +{ + if (!def) + return; + + VIR_FREE(def->name); + VIR_FREE(def->value); + + VIR_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]); + + VIR_FREE(src->cookies); + src->ncookies = 0; +} + + +static int +virStorageSourceNetCookiesCopy(virStorageSourcePtr to, + const virStorageSource *from) +{ + size_t i; + + if (from->ncookies == 0) + return 0; + + if (VIR_ALLOC_N(to->cookies, from->ncookies) < 0) + return -1; + to->ncookies = from->ncookies; + + for (i = 0; i < from->ncookies; i++) { + if (VIR_STRDUP(to->cookies[i]->name, from->cookies[i]->name) < 0 || + VIR_STRDUP(to->cookies[i]->value, from->cookies[i]->value) < 0) + goto error; + } + + return 0; + + error: + virStorageSourceCookiesClear(to); + return -1; +} + + +/* 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) { @@ -2060,6 +2180,9 @@ virStorageSourceCopy(const virStorageSource *src, ret->nhosts = src->nhosts; } + if (virStorageSourceNetCookiesCopy(ret, src) < 0) + goto error; + if (src->srcpool && !(ret->srcpool = virStorageSourcePoolDefCopy(src->srcpool))) goto error; @@ -2258,6 +2381,7 @@ virStorageSourceClear(virStorageSourcePtr def) VIR_FREE(def->volume); VIR_FREE(def->snapshot); VIR_FREE(def->configFile); + virStorageSourceCookiesClear(def); virStorageSourcePoolDefFree(def->srcpool); VIR_FREE(def->driverName); virBitmapFree(def->features); diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index 9ebfc1108..42d9eac61 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -160,6 +160,16 @@ 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); + + /* Information for a storage volume from a virStoragePool */ /* @@ -235,6 +245,8 @@ struct _virStorageSource { the source definition */ size_t nhosts; virStorageNetHostDefPtr hosts; + size_t ncookies; + virStorageNetCookieDefPtr *cookies; virStorageSourcePoolDefPtr srcpool; virStorageAuthDefPtr auth; virStorageEncryptionPtr encryption; @@ -371,6 +383,8 @@ int virStorageSourceUpdateCapacity(virStorageSourcePtr src, char *buf, ssize_t len, bool probe); +int virStorageSourceNetCookiesValidate(virStorageSourcePtr src); + virStorageSourcePtr virStorageSourceNewFromBacking(virStorageSourcePtr parent); virStorageSourcePtr virStorageSourceCopy(const virStorageSource *src, bool backingChain) diff --git a/tests/genericxml2xmlindata/generic-disk-network-http.xml b/tests/genericxml2xmlindata/generic-disk-network-http.xml index 51c779502..fc5ac6e5e 100644 --- a/tests/genericxml2xmlindata/generic-disk-network-http.xml +++ b/tests/genericxml2xmlindata/generic-disk-network-http.xml @@ -32,6 +32,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> -- 2.12.2 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list