From 19d2864c34ebb04d5790578bd92854c7baa78f90 Mon Sep 17 00:00:00 2001
From: Max Kellermann <mk@cm4all.com>
Date: Fri, 4 Feb 2022 11:11:38 +0100
Subject: [PATCH] lib/curl/Headers: central type definition for the header map

---
 src/input/plugins/CurlInputPlugin.cxx   | 16 +++++-----
 src/input/plugins/CurlInputPlugin.hxx   |  7 ++---
 src/input/plugins/QobuzClient.cxx       |  4 +--
 src/input/plugins/QobuzClient.hxx       |  6 ++--
 src/input/plugins/QobuzErrorParser.cxx  |  2 +-
 src/input/plugins/QobuzErrorParser.hxx  |  7 ++---
 src/input/plugins/QobuzLoginRequest.cxx |  7 ++---
 src/input/plugins/QobuzLoginRequest.hxx |  2 +-
 src/input/plugins/QobuzTagScanner.cxx   |  3 +-
 src/input/plugins/QobuzTagScanner.hxx   |  2 +-
 src/input/plugins/QobuzTrackRequest.cxx |  2 +-
 src/input/plugins/QobuzTrackRequest.hxx |  2 +-
 src/lib/curl/Adapter.hxx                | 13 +++-----
 src/lib/curl/Delegate.cxx               |  5 ++-
 src/lib/curl/Delegate.hxx               | 12 +++----
 src/lib/curl/Form.cxx                   |  3 +-
 src/lib/curl/Form.hxx                   |  6 ++--
 src/lib/curl/Handler.hxx                | 11 ++-----
 src/lib/curl/Headers.hxx                | 42 +++++++++++++++++++++++++
 src/lib/upnp/Discovery.cxx              |  2 +-
 src/lib/upnp/Discovery.hxx              |  3 +-
 src/storage/plugins/CurlStorage.cxx     |  5 ++-
 test/RunCurl.cxx                        |  3 +-
 23 files changed, 90 insertions(+), 75 deletions(-)
 create mode 100644 src/lib/curl/Headers.hxx

diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index be5efed91..cc2606714 100644
--- a/src/input/plugins/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -82,7 +82,7 @@ class CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
 public:
 	template<typename I>
 	CurlInputStream(EventLoop &event_loop, const char *_url,
-			const std::multimap<std::string, std::string> &headers,
+			const Curl::Headers &headers,
 			I &&_icy,
 			Mutex &_mutex);
 
@@ -92,7 +92,7 @@ public:
 	CurlInputStream &operator=(const CurlInputStream &) = delete;
 
 	static InputStreamPtr Open(const char *url,
-				   const std::multimap<std::string, std::string> &headers,
+				   const Curl::Headers &headers,
 				   Mutex &mutex);
 
 private:
@@ -131,8 +131,7 @@ private:
 	void SeekInternal(offset_type new_offset);
 
 	/* virtual methods from CurlResponseHandler */
-	void OnHeaders(unsigned status,
-		       std::multimap<std::string, std::string> &&headers) override;
+	void OnHeaders(unsigned status, Curl::Headers &&headers) override;
 	void OnData(ConstBuffer<void> data) override;
 	void OnEnd() override;
 	void OnError(std::exception_ptr e) noexcept override;
@@ -227,7 +226,7 @@ WithConvertedTagValue(const char *uri, const char *value, F &&f) noexcept
 
 void
 CurlInputStream::OnHeaders(unsigned status,
-			   std::multimap<std::string, std::string> &&headers)
+			   Curl::Headers &&headers)
 {
 	assert(GetEventLoop().IsInside());
 	assert(!postponed_exception);
@@ -391,7 +390,7 @@ input_curl_finish() noexcept
 template<typename I>
 inline
 CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
-				 const std::multimap<std::string, std::string> &headers,
+				 const Curl::Headers &headers,
 				 I &&_icy,
 				 Mutex &_mutex)
 	:AsyncInputStream(event_loop, _url, _mutex,
@@ -491,7 +490,7 @@ CurlInputStream::DoSeek(offset_type new_offset)
 
 inline InputStreamPtr
 CurlInputStream::Open(const char *url,
-		      const std::multimap<std::string, std::string> &headers,
+		      const Curl::Headers &headers,
 		      Mutex &mutex)
 {
 	auto icy = std::make_shared<IcyMetaDataParser>();
@@ -510,8 +509,7 @@ CurlInputStream::Open(const char *url,
 }
 
 InputStreamPtr
-OpenCurlInputStream(const char *uri,
-		    const std::multimap<std::string, std::string> &headers,
+OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
 		    Mutex &mutex)
 {
 	return CurlInputStream::Open(uri, headers, mutex);
diff --git a/src/input/plugins/CurlInputPlugin.hxx b/src/input/plugins/CurlInputPlugin.hxx
index c3decf675..3595a84ea 100644
--- a/src/input/plugins/CurlInputPlugin.hxx
+++ b/src/input/plugins/CurlInputPlugin.hxx
@@ -20,12 +20,10 @@
 #ifndef MPD_INPUT_CURL_HXX
 #define MPD_INPUT_CURL_HXX
 
+#include "lib/curl/Headers.hxx"
 #include "input/Ptr.hxx"
 #include "thread/Mutex.hxx"
 
-#include <string>
-#include <map>
-
 extern const struct InputPlugin input_plugin_curl;
 
 /**
@@ -36,8 +34,7 @@ extern const struct InputPlugin input_plugin_curl;
  * Throws on error.
  */
 InputStreamPtr
-OpenCurlInputStream(const char *uri,
-		    const std::multimap<std::string, std::string> &headers,
+OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
 		    Mutex &mutex);
 
 #endif
diff --git a/src/input/plugins/QobuzClient.cxx b/src/input/plugins/QobuzClient.cxx
index 294456a5a..48f8d8ecd 100644
--- a/src/input/plugins/QobuzClient.cxx
+++ b/src/input/plugins/QobuzClient.cxx
@@ -164,7 +164,7 @@ QobuzClient::InvokeHandlers() noexcept
 
 std::string
 QobuzClient::MakeUrl(const char *object, const char *method,
-		     const std::multimap<std::string, std::string> &query) const noexcept
+		     const Curl::Headers &query) const noexcept
 {
 	assert(!query.empty());
 
@@ -183,7 +183,7 @@ QobuzClient::MakeUrl(const char *object, const char *method,
 
 std::string
 QobuzClient::MakeSignedUrl(const char *object, const char *method,
-			   const std::multimap<std::string, std::string> &query) const noexcept
+			   const Curl::Headers &query) const noexcept
 {
 	assert(!query.empty());
 
diff --git a/src/input/plugins/QobuzClient.hxx b/src/input/plugins/QobuzClient.hxx
index 0fa00dce0..65c9ec49a 100644
--- a/src/input/plugins/QobuzClient.hxx
+++ b/src/input/plugins/QobuzClient.hxx
@@ -23,12 +23,12 @@
 #include "QobuzSession.hxx"
 #include "QobuzLoginRequest.hxx"
 #include "lib/curl/Init.hxx"
+#include "lib/curl/Headers.hxx"
 #include "thread/Mutex.hxx"
 #include "event/DeferEvent.hxx"
 #include "util/IntrusiveList.hxx"
 
 #include <memory>
-#include <map>
 #include <string>
 
 class QobuzSessionHandler
@@ -94,10 +94,10 @@ public:
 	QobuzSession GetSession() const;
 
 	std::string MakeUrl(const char *object, const char *method,
-			    const std::multimap<std::string, std::string> &query) const noexcept;
+			    const Curl::Headers &query) const noexcept;
 
 	std::string MakeSignedUrl(const char *object, const char *method,
-				  const std::multimap<std::string, std::string> &query) const noexcept;
+				  const Curl::Headers &query) const noexcept;
 
 private:
 	void StartLogin();
diff --git a/src/input/plugins/QobuzErrorParser.cxx b/src/input/plugins/QobuzErrorParser.cxx
index 96ac1f743..ae19be5dc 100644
--- a/src/input/plugins/QobuzErrorParser.cxx
+++ b/src/input/plugins/QobuzErrorParser.cxx
@@ -38,7 +38,7 @@ static constexpr yajl_callbacks qobuz_error_parser_callbacks = {
 };
 
 QobuzErrorParser::QobuzErrorParser(unsigned _status,
-				   const std::multimap<std::string, std::string> &headers)
+				   const Curl::Headers &headers)
 	:YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this),
 	 status(_status)
 {
diff --git a/src/input/plugins/QobuzErrorParser.hxx b/src/input/plugins/QobuzErrorParser.hxx
index 4f50f8265..23523af46 100644
--- a/src/input/plugins/QobuzErrorParser.hxx
+++ b/src/input/plugins/QobuzErrorParser.hxx
@@ -20,11 +20,9 @@
 #ifndef QOBUZ_ERROR_PARSER_HXX
 #define QOBUZ_ERROR_PARSER_HXX
 
+#include "lib/curl/Headers.hxx"
 #include "lib/yajl/ResponseParser.hxx"
 
-#include <string>
-#include <map>
-
 template<typename T> struct ConstBuffer;
 struct StringView;
 
@@ -46,8 +44,7 @@ public:
 	 * May throw if there is a formal error in the response
 	 * headers.
 	 */
-	QobuzErrorParser(unsigned status,
-			 const std::multimap<std::string, std::string> &headers);
+	QobuzErrorParser(unsigned status, const Curl::Headers &headers);
 
 protected:
 	/* virtual methods from CurlResponseParser */
diff --git a/src/input/plugins/QobuzLoginRequest.cxx b/src/input/plugins/QobuzLoginRequest.cxx
index 2a3e091d0..143c3f931 100644
--- a/src/input/plugins/QobuzLoginRequest.cxx
+++ b/src/input/plugins/QobuzLoginRequest.cxx
@@ -77,7 +77,7 @@ QobuzLoginRequest::ResponseParser::GetSession()
 	return std::move(session);
 }
 
-static std::multimap<std::string, std::string>
+static Curl::Headers
 MakeLoginForm(const char *app_id,
 	      const char *username, const char *email,
 	      const char *password,
@@ -85,7 +85,7 @@ MakeLoginForm(const char *app_id,
 {
 	assert(username != nullptr || email != nullptr);
 
-	std::multimap<std::string, std::string> form{
+	Curl::Headers form{
 		{"app_id", app_id},
 		{"password", password},
 		{"device_manufacturer_id", device_manufacturer_id},
@@ -134,8 +134,7 @@ QobuzLoginRequest::~QobuzLoginRequest() noexcept
 }
 
 std::unique_ptr<CurlResponseParser>
-QobuzLoginRequest::MakeParser(unsigned status,
-			      std::multimap<std::string, std::string> &&headers)
+QobuzLoginRequest::MakeParser(unsigned status, Curl::Headers &&headers)
 {
 	if (status != 200)
 		return std::make_unique<QobuzErrorParser>(status, headers);
diff --git a/src/input/plugins/QobuzLoginRequest.hxx b/src/input/plugins/QobuzLoginRequest.hxx
index 189cb6896..1bf657ec6 100644
--- a/src/input/plugins/QobuzLoginRequest.hxx
+++ b/src/input/plugins/QobuzLoginRequest.hxx
@@ -56,7 +56,7 @@ public:
 private:
 	/* virtual methods from DelegateCurlResponseHandler */
 	std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
-						       std::multimap<std::string, std::string> &&headers) override;
+						       Curl::Headers &&headers) override;
 	void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
 
 	/* virtual methods from CurlResponseHandler */
diff --git a/src/input/plugins/QobuzTagScanner.cxx b/src/input/plugins/QobuzTagScanner.cxx
index 3629d9865..50a3f022b 100644
--- a/src/input/plugins/QobuzTagScanner.cxx
+++ b/src/input/plugins/QobuzTagScanner.cxx
@@ -99,8 +99,7 @@ QobuzTagScanner::~QobuzTagScanner() noexcept
 }
 
 std::unique_ptr<CurlResponseParser>
-QobuzTagScanner::MakeParser(unsigned status,
-			    std::multimap<std::string, std::string> &&headers)
+QobuzTagScanner::MakeParser(unsigned status, Curl::Headers &&headers)
 {
 	if (status != 200)
 		return std::make_unique<QobuzErrorParser>(status, headers);
diff --git a/src/input/plugins/QobuzTagScanner.hxx b/src/input/plugins/QobuzTagScanner.hxx
index 3a8a3276e..ce596bc80 100644
--- a/src/input/plugins/QobuzTagScanner.hxx
+++ b/src/input/plugins/QobuzTagScanner.hxx
@@ -49,7 +49,7 @@ public:
 private:
 	/* virtual methods from DelegateCurlResponseHandler */
 	std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
-						       std::multimap<std::string, std::string> &&headers) override;
+						       Curl::Headers &&headers) override;
 	void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
 
 	/* virtual methods from CurlResponseHandler */
diff --git a/src/input/plugins/QobuzTrackRequest.cxx b/src/input/plugins/QobuzTrackRequest.cxx
index e48a3848e..b697d3ec3 100644
--- a/src/input/plugins/QobuzTrackRequest.cxx
+++ b/src/input/plugins/QobuzTrackRequest.cxx
@@ -93,7 +93,7 @@ QobuzTrackRequest::~QobuzTrackRequest() noexcept
 
 std::unique_ptr<CurlResponseParser>
 QobuzTrackRequest::MakeParser(unsigned status,
-			      std::multimap<std::string, std::string> &&headers)
+			      Curl::Headers &&headers)
 {
 	if (status != 200)
 		return std::make_unique<QobuzErrorParser>(status, headers);
diff --git a/src/input/plugins/QobuzTrackRequest.hxx b/src/input/plugins/QobuzTrackRequest.hxx
index 9de1890b6..a16ce760b 100644
--- a/src/input/plugins/QobuzTrackRequest.hxx
+++ b/src/input/plugins/QobuzTrackRequest.hxx
@@ -56,7 +56,7 @@ public:
 private:
 	/* virtual methods from DelegateCurlResponseHandler */
 	std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
-						       std::multimap<std::string, std::string> &&headers) override;
+						       Curl::Headers &&headers) override;
 	void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
 
 	/* virtual methods from CurlResponseHandler */
diff --git a/src/lib/curl/Adapter.hxx b/src/lib/curl/Adapter.hxx
index 1d26f46e6..fc4c946c3 100644
--- a/src/lib/curl/Adapter.hxx
+++ b/src/lib/curl/Adapter.hxx
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
+ * Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,14 +27,13 @@
  * OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef CURL_ADAPTER_HXX
-#define CURL_ADAPTER_HXX
+#pragma once
+
+#include "Headers.hxx"
 
 #include <curl/curl.h>
 
 #include <cstddef>
-#include <map>
-#include <string>
 
 struct StringView;
 class CurlEasy;
@@ -45,7 +44,7 @@ class CurlResponseHandlerAdapter {
 
 	CurlResponseHandler &handler;
 
-	std::multimap<std::string, std::string> headers;
+	Curl::Headers headers;
 
 	/** error message provided by libcurl */
 	char error_buffer[CURL_ERROR_SIZE];
@@ -83,5 +82,3 @@ private:
 					 std::size_t size, std::size_t nmemb,
 					 void *stream) noexcept;
 };
-
-#endif
diff --git a/src/lib/curl/Delegate.cxx b/src/lib/curl/Delegate.cxx
index a16793ed2..b6b6dc507 100644
--- a/src/lib/curl/Delegate.cxx
+++ b/src/lib/curl/Delegate.cxx
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2018 Max Kellermann <max.kellermann@gmail.com>
+ * Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -34,8 +34,7 @@
 #include <utility>
 
 void
-DelegateCurlResponseHandler::OnHeaders(unsigned status,
-				       std::multimap<std::string, std::string> &&headers)
+DelegateCurlResponseHandler::OnHeaders(unsigned status, Curl::Headers &&headers)
 {
 	parser = MakeParser(status, std::move(headers));
 	assert(parser);
diff --git a/src/lib/curl/Delegate.hxx b/src/lib/curl/Delegate.hxx
index fe6a65f35..95c8576ce 100644
--- a/src/lib/curl/Delegate.hxx
+++ b/src/lib/curl/Delegate.hxx
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2018 Max Kellermann <max.kellermann@gmail.com>
+ * Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,8 +27,7 @@
  * OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef CURL_DELEGATE_HXX
-#define CURL_DELEGATE_HXX
+#pragma once
 
 #include "Handler.hxx"
 
@@ -53,7 +52,7 @@ protected:
 	 * CurlResponseParser::OnError()).
 	 */
 	virtual std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
-							       std::multimap<std::string, std::string> &&headers) = 0;
+							       Curl::Headers &&headers) = 0;
 
 	/**
 	 * The parser has finished parsing the response body.  This
@@ -64,10 +63,7 @@ protected:
 	virtual void FinishParser(std::unique_ptr<CurlResponseParser> p) = 0;
 
 public:
-	void OnHeaders(unsigned status,
-		       std::multimap<std::string, std::string> &&headers) final;
+	void OnHeaders(unsigned status, Curl::Headers &&headers) final;
 	void OnData(ConstBuffer<void> data) final;
 	void OnEnd() final;
 };
-
-#endif
diff --git a/src/lib/curl/Form.cxx b/src/lib/curl/Form.cxx
index 5b668ec46..5f45fdb56 100644
--- a/src/lib/curl/Form.cxx
+++ b/src/lib/curl/Form.cxx
@@ -31,8 +31,7 @@
 #include "String.hxx"
 
 std::string
-EncodeForm(CURL *curl,
-	   const std::multimap<std::string, std::string> &fields) noexcept
+EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept
 {
 	std::string result;
 
diff --git a/src/lib/curl/Form.hxx b/src/lib/curl/Form.hxx
index 8c045ac22..1545d3e7d 100644
--- a/src/lib/curl/Form.hxx
+++ b/src/lib/curl/Form.hxx
@@ -30,17 +30,17 @@
 #ifndef CURL_FORM_HXX
 #define CURL_FORM_HXX
 
+#include "Headers.hxx"
+
 #include <curl/curl.h>
 
 #include <string>
-#include <map>
 
 /**
  * Encode the given map of form fields to a
  * "application/x-www-form-urlencoded" string.
  */
 std::string
-EncodeForm(CURL *curl,
-	   const std::multimap<std::string, std::string> &fields) noexcept;
+EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept;
 
 #endif
diff --git a/src/lib/curl/Handler.hxx b/src/lib/curl/Handler.hxx
index 14a928a80..c0bdeced1 100644
--- a/src/lib/curl/Handler.hxx
+++ b/src/lib/curl/Handler.hxx
@@ -27,14 +27,12 @@
  * OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef CURL_HANDLER_HXX
-#define CURL_HANDLER_HXX
+#pragma once
 
+#include "Headers.hxx"
 #include "util/ConstBuffer.hxx"
 
 #include <exception>
-#include <string>
-#include <map>
 
 /**
  * Asynchronous response handler for a #CurlRequest.
@@ -53,8 +51,7 @@ public:
 	/**
 	 * Status line and headers have been received.
 	 */
-	virtual void OnHeaders(unsigned status,
-			       std::multimap<std::string, std::string> &&headers) = 0;
+	virtual void OnHeaders(unsigned status, Curl::Headers &&headers) = 0;
 
 	/**
 	 * Response body data has been received.
@@ -75,5 +72,3 @@ public:
 	 */
 	virtual void OnError(std::exception_ptr e) noexcept = 0;
 };
-
-#endif
diff --git a/src/lib/curl/Headers.hxx b/src/lib/curl/Headers.hxx
new file mode 100644
index 000000000..7ff04a6e4
--- /dev/null
+++ b/src/lib/curl/Headers.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020-2021 CM4all GmbH
+ * All rights reserved.
+ *
+ * author: Max Kellermann <mk@cm4all.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+namespace Curl {
+
+using Headers = std::multimap<std::string, std::string>;
+
+} // namespace Curl
diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx
index 7bd8b8d90..e224b28bd 100644
--- a/src/lib/upnp/Discovery.cxx
+++ b/src/lib/upnp/Discovery.cxx
@@ -55,7 +55,7 @@ UPnPDeviceDirectory::Downloader::Destroy() noexcept
 
 void
 UPnPDeviceDirectory::Downloader::OnHeaders(unsigned status,
-					   std::multimap<std::string, std::string> &&)
+					   Curl::Headers &&)
 {
 	if (status != 200) {
 		Destroy();
diff --git a/src/lib/upnp/Discovery.hxx b/src/lib/upnp/Discovery.hxx
index 3391148dd..8052b1a31 100644
--- a/src/lib/upnp/Discovery.hxx
+++ b/src/lib/upnp/Discovery.hxx
@@ -113,8 +113,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
 		}
 
 		/* virtual methods from CurlResponseHandler */
-		void OnHeaders(unsigned status,
-			       std::multimap<std::string, std::string> &&headers) override;
+		void OnHeaders(unsigned status, Curl::Headers &&headers) override;
 		void OnData(ConstBuffer<void> data) override;
 		void OnEnd() override;
 		void OnError(std::exception_ptr e) noexcept override;
diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx
index d6f1a932d..4b5b84553 100644
--- a/src/storage/plugins/CurlStorage.cxx
+++ b/src/storage/plugins/CurlStorage.cxx
@@ -227,7 +227,7 @@ IsXmlContentType(const char *content_type) noexcept
 
 gcc_pure
 static bool
-IsXmlContentType(const std::multimap<std::string, std::string> &headers) noexcept
+IsXmlContentType(const Curl::Headers &headers) noexcept
 {
 	auto i = headers.find("content-type");
 	return i != headers.end() && IsXmlContentType(i->second.c_str());
@@ -297,8 +297,7 @@ private:
 	}
 
 	/* virtual methods from CurlResponseHandler */
-	void OnHeaders(unsigned status,
-		       std::multimap<std::string, std::string> &&headers) final {
+	void OnHeaders(unsigned status, Curl::Headers &&headers) final {
 		if (status != 207)
 			throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
 						 status);
diff --git a/test/RunCurl.cxx b/test/RunCurl.cxx
index 9d4874eb0..7ae233054 100644
--- a/test/RunCurl.cxx
+++ b/test/RunCurl.cxx
@@ -41,8 +41,7 @@ public:
 	}
 
 	/* virtual methods from CurlResponseHandler */
-	void OnHeaders(unsigned status,
-		       std::multimap<std::string, std::string> &&headers) override {
+	void OnHeaders(unsigned status, Curl::Headers &&headers) override {
 		fprintf(stderr, "status: %u\n", status);
 		for (const auto &i : headers)
 			fprintf(stderr, "%s: %s\n",