Repository: plone/plone.restapi (language: python)

Issue:
Blocks linkintegrity throws an error if there is a link with "url": null
Trying to save blocks-enabled content raises an exception if the blocks contain a link with `"url": null`:

```
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 167, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 376, in publish_module
  Module ZPublisher.WSGIPublisher, line 271, in publish
  Module ZPublisher.mapply, line 85, in mapply
  Module ZPublisher.WSGIPublisher, line 68, in call_object
  Module plone.rest.service, line 22, in __call__
  Module plone.restapi.services, line 19, in render
  Module plone.restapi.services.content.update, line 32, in reply
  Module plone.restapi.deserializer.dxcontent, line 75, in __call__
  Module zope.event, line 32, in notify
  Module zope.component.event, line 27, in dispatch
  Module zope.component._api, line 134, in subscribers
  Module zope.interface.registry, line 448, in subscribers
  Module zope.interface.adapter, line 899, in subscribers
  Module zope.component.event, line 36, in objectEventNotify
  Module zope.component._api, line 134, in subscribers
  Module zope.interface.registry, line 448, in subscribers
  Module zope.interface.adapter, line 899, in subscribers
  Module plone.app.linkintegrity.handlers, line 117, in modifiedContent
  Module plone.app.linkintegrity.handlers, line 97, in getObjectsFromLinks
  Module plone.app.linkintegrity.handlers, line 34, in findObject
TypeError: startswith first arg must be bytes or a tuple of bytes, not str
```

The traceback does not even mention plone.restapi, but the problem is in the retrievers in https://github.com/plone/plone.restapi/blob/master/src/plone/restapi/blocks_linkintegrity.py which are called by the code in the traceback. They need to filter out None from the list of links passed to the linkintegrity code, or linkintegrity needs to ignore None. Currently it is passing the value to `six.moves.urllib.parse.urlsplit` which returns a bytes-like object if the input is None. Very confusing.


Discussion / hints:

Fix patch:
diff --git a/news/1553.bugfix b/news/1553.bugfix
new file mode 100644
index 0000000000..e6d23e0906
--- /dev/null
+++ b/news/1553.bugfix
@@ -0,0 +1,1 @@
+Fix an error when saving content with a slate block that includes an empty link. [davisagli]
diff --git a/src/plone/restapi/blocks_linkintegrity.py b/src/plone/restapi/blocks_linkintegrity.py
index b65aefdca7..cafe026906 100644
--- a/src/plone/restapi/blocks_linkintegrity.py
+++ b/src/plone/restapi/blocks_linkintegrity.py
@@ -88,7 +88,9 @@ def __call__(self, block):
             if node_type:
                 handler = getattr(self, f"handle_{node_type}", None)
                 if handler:
-                    self.links.append(handler(child))
+                    value = handler(child)
+                    if value:
+                        self.links.append(value)
 
         return self.links
 


Test patch:
diff --git a/src/plone/restapi/tests/test_blocks_linkintegrity.py b/src/plone/restapi/tests/test_blocks_linkintegrity.py
index 8d7eae633e..f36cd4e1b3 100644
--- a/src/plone/restapi/tests/test_blocks_linkintegrity.py
+++ b/src/plone/restapi/tests/test_blocks_linkintegrity.py
@@ -202,6 +202,35 @@ def test_links_retriever_return_internal_links_in_text_block_once(self):
         self.assertEqual(len(value), 1)
         self.assertIn("../resolveuid/{}".format(uid), value)
 
+    def test_links_retriever_skip_empty_links(self):
+        blocks = {
+            "abc": {
+                "@type": "slate",
+                "plaintext": "Frontpage content here",
+                "value": [
+                    {
+                        "children": [
+                            {"text": "Frontpage "},
+                            {
+                                "children": [{"text": "content "}],
+                                "data": {
+                                    "url": None,
+                                },
+                                "type": "link",
+                            },
+                            {"text": "here"},
+                        ],
+                        "type": "h2",
+                    }
+                ],
+            }
+        }
+
+        self.portal.doc1.blocks = blocks
+        value = self.retrieve_links(blocks)
+
+        self.assertEqual(len(value), 0)
+
 
 class TestLinkintegrityForBlocks(TestCase):