Repository: RDFLib/rdflib

Issue:
SPARQLConnector.update should set ContentType charset
I use rdflib to connect to a Blazegraph triplestore using the SPARQLUpdateStore. 

When I add data containing non-ASCII characters using `addN()` it results in garbled data in the triplestore (UTF-8 interpreted as ISO-8859).

The problem seems to be that SPARQLConnector.update() does not set "charset=UTF-8" in the Content-Type header:

https://github.com/RDFLib/rdflib/blob/a70a9c82649f9345c3691d69df6f1108e9e05871/rdflib/plugins/stores/sparqlconnector.py#L170

but it does use UTF-8 in `data=query.encode()` in line 183.

The problem goes away if I patch the Content-Type to be "application/sparql-update; charset=UTF-8".

I can provide a PR with this one-line change if you like.

Comments:

Patch:
diff --git a/rdflib/plugins/stores/sparqlconnector.py b/rdflib/plugins/stores/sparqlconnector.py
index 1d03c6fe..55fdfd15 100644
--- a/rdflib/plugins/stores/sparqlconnector.py
+++ b/rdflib/plugins/stores/sparqlconnector.py
@@ -167,7 +167,7 @@ class SPARQLConnector(object):
 
         headers = {
             "Accept": _response_mime_types[self.returnFormat],
-            "Content-Type": "application/sparql-update",
+            "Content-Type": "application/sparql-update; charset=UTF-8",
         }
 
         args = dict(self.kwargs)  # other QSAs


Test patch:
diff --git a/test/test_store/test_store_sparqlstore.py b/test/test_store/test_store_sparqlstore.py
index 39a7d3cd..fa98b956 100644
--- a/test/test_store/test_store_sparqlstore.py
+++ b/test/test_store/test_store_sparqlstore.py
@@ -1,3 +1,4 @@
+import logging
 import re
 import socket
 from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -457,23 +458,22 @@ class SPARQL11ProtocolStoreMock(BaseHTTPRequestHandler):
         print(body)
         ```
         """
-        contenttype = self.headers.get("Content-Type")
+        contenttype = [
+            part.strip() for part in f"{self.headers.get('Content-Type')}".split(";")
+        ]
+        logging.debug("contenttype = %s", contenttype)
         if self.path == "/query" or self.path == "/query?":
-            if self.headers.get("Content-Type") == "application/sparql-query":
+            if "application/sparql-query" in contenttype:
                 pass
-            elif (
-                self.headers.get("Content-Type") == "application/x-www-form-urlencoded"
-            ):
+            elif "application/x-www-form-urlencoded" in contenttype:
                 pass
             else:
                 self.send_response(406, "Not Acceptable")
                 self.end_headers()
         elif self.path == "/update" or self.path == "/update?":
-            if self.headers.get("Content-Type") == "application/sparql-update":
+            if "application/sparql-update" in contenttype:
                 pass
-            elif (
-                self.headers.get("Content-Type") == "application/x-www-form-urlencoded"
-            ):
+            elif "application/x-www-form-urlencoded" in contenttype:
                 pass
             else:
                 self.send_response(406, "Not Acceptable")
diff --git a/test/test_store/test_store_sparqlupdatestore_mock.py b/test/test_store/test_store_sparqlupdatestore_mock.py
index 11a4983f..be8f0fae 100644
--- a/test/test_store/test_store_sparqlupdatestore_mock.py
+++ b/test/test_store/test_store_sparqlupdatestore_mock.py
@@ -58,3 +58,26 @@ class TestSPARQLConnector:
         req = self.httpmock.requests[MethodName.POST].pop(0)
         assert req.parsed_path.path == self.update_path
         assert "application/sparql-update" in req.headers.get("content-type")
+
+    def test_update_encoding(self):
+        graph = ConjunctiveGraph("SPARQLUpdateStore")
+        graph.open((self.query_endpoint, self.update_endpoint))
+        update_statement = f"INSERT DATA {{ {EG['subj']} {EG['pred']} {EG['obj']}. }}"
+
+        self.httpmock.responses[MethodName.POST].append(
+            MockHTTPResponse(
+                200,
+                "OK",
+                b"Update succeeded",
+                {"Content-Type": ["text/plain; charset=UTF-8"]},
+            )
+        )
+
+        # This test assumes that updates are performed using POST
+        # at the moment this is the only supported way for SPARQLUpdateStore
+        # to do updates.
+        graph.update(update_statement)
+        assert self.httpmock.call_count == 1
+        req = self.httpmock.requests[MethodName.POST].pop(0)
+        assert req.parsed_path.path == self.update_path
+        assert "charset=UTF-8" in req.headers.get("content-type")