Blob Blame History Raw
From 4abdfae3b67295a0143f650768630e009d1b2798 Mon Sep 17 00:00:00 2001
Message-Id: <4abdfae3b67295a0143f650768630e009d1b2798@dist-git>
From: Peter Krempa <pkrempa@redhat.com>
Date: Mon, 16 Mar 2020 22:11:57 +0100
Subject: [PATCH] conf: Add support for cookies for HTTP based disks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
(cherry picked from commit 3b076391befc3fe72deb0c244ac6c2b4c100b410)

https://bugzilla.redhat.com/show_bug.cgi?id=1804750
Message-Id: <3135a30f0d0a1a4bb8da02c49f10a1bcf3a394f4.1584391727.git.pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
---
 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 2cce247958..5a10d64e83 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -2839,6 +2839,9 @@
     &lt;driver name='qemu' type='raw'/&gt;
     &lt;source protocol="http" name="url_path"&gt;
       &lt;host name="hostname" port="80"/&gt;
+      &lt;cookies&gt;
+        &lt;cookie name="test"&gt;somevalue&lt;/cookie&gt;
+      &lt;/cookies&gt;
     &lt;/source&gt;
     &lt;target dev='hde' bus='ide' tray='open'/&gt;
     &lt;readonly/&gt;
@@ -3382,6 +3385,13 @@
             certificate validation. Supported values are <code>yes</code> and
             <code>no</code>. <span class="since">Since 6.2.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 548601b61c..bdf35e64f6 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">[!#$%&amp;'*+\-.0-9A-Z\^_`a-z|~]+</param>
+            </data>
+          </attribute>
+          <data type="string">
+            <param name="pattern">[!#$%&amp;'()*+\-./0-9:&gt;=&lt;?@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 70bbc35bb3..d066d3aac1 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -9249,6 +9249,62 @@ virDomainDiskSourcePoolDefParse(xmlNodePtr node,
 }
 
 
+static virStorageNetCookieDefPtr
+virDomainStorageNetCookieParse(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
+virDomainStorageNetCookiesParse(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] = virDomainStorageNetCookieParse(nodes[i], ctxt)))
+            return -1;
+    }
+
+    if (virStorageSourceNetCookiesValidate(src) < 0)
+        return -1;
+
+    return 0;
+}
+
+
 static int
 virDomainDiskSourceNetworkParse(xmlNodePtr node,
                                 xmlXPathContextPtr ctxt,
@@ -9260,6 +9316,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",
@@ -9345,6 +9402,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 (virDomainStorageNetCookiesParse(tmpnode, ctxt, src) < 0)
+            return -1;
+    }
+
     return 0;
 }
 
@@ -24281,6 +24345,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,
@@ -24331,6 +24411,8 @@ virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf,
                           virTristateBoolTypeToString(src->sslverify));
     }
 
+    virDomainDiskSourceFormatNetworkCookies(childBuf, src);
+
     return 0;
 }
 
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index dbbec0d567..ac5527ef01 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -3123,6 +3123,7 @@ virStorageSourceIsEmpty;
 virStorageSourceIsLocalStorage;
 virStorageSourceIsRelative;
 virStorageSourceIsSameLocation;
+virStorageSourceNetCookiesValidate;
 virStorageSourceNetworkAssignDefaultPorts;
 virStorageSourceNew;
 virStorageSourceNewFromBacking;
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
index cfa77fccf8..6350168d73 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
+virStorageSourceNetCookiesClear(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);
+    virStorageSourceNetCookiesClear(def);
     virStorageSourcePoolDefFree(def->srcpool);
     virBitmapFree(def->features);
     VIR_FREE(def->compat);
diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h
index fab4248c3d..1c7c046ad6 100644
--- a/src/util/virstoragefile.h
+++ b/src/util/virstoragefile.h
@@ -162,6 +162,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 */
 
 /*
@@ -276,6 +287,8 @@ struct _virStorageSource {
                          the source definition */
     size_t nhosts;
     virStorageNetHostDefPtr hosts;
+    size_t ncookies;
+    virStorageNetCookieDefPtr *cookies;
     virStorageSourcePoolDefPtr srcpool;
     virStorageAuthDefPtr auth;
     bool authInherited;
@@ -477,6 +490,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.25.1