From 356c829b767863512da1792c048f6ddbeb8457a3 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Sep 2015 22:03:01 +0200
Subject: util/StringView: new utility class

---
 Makefile.am                                 |   1 +
 src/IcyMetaDataParser.cxx                   |   3 +-
 src/db/plugins/upnp/Directory.cxx           |  19 ++---
 src/decoder/plugins/Mpg123DecoderPlugin.cxx |   3 +-
 src/playlist/plugins/AsxPlaylistPlugin.cxx  |   4 +-
 src/playlist/plugins/RssPlaylistPlugin.cxx  |   4 +-
 src/playlist/plugins/XspfPlaylistPlugin.cxx |   4 +-
 src/tag/ApeLoader.cxx                       |   3 +-
 src/tag/ApeLoader.hxx                       |   4 +-
 src/tag/ApeReplayGain.cxx                   |  16 ++---
 src/tag/ApeTag.cxx                          |  16 ++---
 src/tag/TagBuilder.cxx                      |  34 ++++-----
 src/tag/TagBuilder.hxx                      |   5 +-
 src/tag/TagPool.cxx                         |  42 +++++------
 src/tag/TagPool.hxx                         |   3 +-
 src/tag/TagString.cxx                       |  47 ++++++------
 src/tag/TagString.hxx                       |   3 +-
 src/util/StringView.hxx                     | 108 ++++++++++++++++++++++++++++
 18 files changed, 208 insertions(+), 111 deletions(-)
 create mode 100644 src/util/StringView.hxx

diff --git a/Makefile.am b/Makefile.am
index 155a6a743..e6ac686c0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -401,6 +401,7 @@ libutil_a_SOURCES = \
 	src/util/CharUtil.hxx \
 	src/util/NumberParser.hxx \
 	src/util/StringPointer.hxx \
+	src/util/StringView.hxx \
 	src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
 	src/util/StringUtil.cxx src/util/StringUtil.hxx \
 	src/util/WStringUtil.cxx src/util/WStringUtil.hxx \
diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx
index 4fdb81aa1..9fb97d4d4 100644
--- a/src/IcyMetaDataParser.cxx
+++ b/src/IcyMetaDataParser.cxx
@@ -22,6 +22,7 @@
 #include "tag/Tag.hxx"
 #include "tag/TagBuilder.hxx"
 #include "util/Domain.hxx"
+#include "util/StringView.hxx"
 #include "Log.hxx"
 
 #include <assert.h>
@@ -76,7 +77,7 @@ icy_add_item(TagBuilder &tag, TagType type, const char *value)
 	}
 
 	if (length > 0)
-		tag.AddItem(type, value, length);
+		tag.AddItem(type, {value, length});
 }
 
 static void
diff --git a/src/db/plugins/upnp/Directory.cxx b/src/db/plugins/upnp/Directory.cxx
index 894a53c6a..e7f92432a 100644
--- a/src/db/plugins/upnp/Directory.cxx
+++ b/src/db/plugins/upnp/Directory.cxx
@@ -25,6 +25,7 @@
 #include "tag/TagBuilder.hxx"
 #include "tag/TagTable.hxx"
 #include "util/NumberParser.hxx"
+#include "util/StringView.hxx"
 
 #include <algorithm>
 #include <string>
@@ -36,23 +37,13 @@ UPnPDirContent::~UPnPDirContent()
 	/* this destructor exists here just so it won't get inlined */
 }
 
-gcc_pure gcc_nonnull_all
-static bool
-CompareStringLiteral(const char *literal, const char *value, size_t length)
-{
-	return length == strlen(literal) &&
-		memcmp(literal, value, length) == 0;
-}
-
 gcc_pure
 static UPnPDirObject::ItemClass
-ParseItemClass(const char *name, size_t length)
+ParseItemClass(StringView name)
 {
-	if (CompareStringLiteral("object.item.audioItem.musicTrack",
-				 name, length))
+	if (name.EqualsLiteral("object.item.audioItem.musicTrack"))
 		return UPnPDirObject::ItemClass::MUSIC;
-	else if (CompareStringLiteral("object.item.playlistItem",
-				      name, length))
+	else if (name.EqualsLiteral("object.item.playlistItem"))
 		return UPnPDirObject::ItemClass::PLAYLIST;
 	else
 		return UPnPDirObject::ItemClass::UNKNOWN;
@@ -239,7 +230,7 @@ protected:
 			break;
 
 		case CLASS:
-			object.item_class = ParseItemClass(s, len);
+			object.item_class = ParseItemClass(StringView(s, len));
 			break;
 		}
 	}
diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
index 760b8b9c2..43c0c868f 100644
--- a/src/decoder/plugins/Mpg123DecoderPlugin.cxx
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
@@ -28,6 +28,7 @@
 #include "fs/Path.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "util/StringView.hxx"
 #include "Log.hxx"
 
 #include <mpg123.h>
@@ -111,7 +112,7 @@ AddTagItem(TagBuilder &tag, TagType type, const mpg123_string &s)
 	assert(s.size >= s.fill);
 	assert(s.fill > 0);
 
-	tag.AddItem(type, s.p, s.fill - 1);
+	tag.AddItem(type, {s.p, s.fill - 1});
 }
 
 static void
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx
index c6226346b..1a0334237 100644
--- a/src/playlist/plugins/AsxPlaylistPlugin.cxx
+++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx
@@ -24,6 +24,7 @@
 #include "tag/TagBuilder.hxx"
 #include "util/ASCII.hxx"
 #include "util/Error.hxx"
+#include "util/StringView.hxx"
 #include "lib/expat/ExpatParser.hxx"
 #include "Log.hxx"
 
@@ -130,7 +131,8 @@ asx_char_data(void *user_data, const XML_Char *s, int len)
 
 	case AsxParser::ENTRY:
 		if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
-			parser->tag_builder.AddItem(parser->tag_type, s, len);
+			parser->tag_builder.AddItem(parser->tag_type,
+						    StringView(s, len));
 
 		break;
 	}
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
index 27588cddf..a2d8e7a42 100644
--- a/src/playlist/plugins/RssPlaylistPlugin.cxx
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -24,6 +24,7 @@
 #include "tag/TagBuilder.hxx"
 #include "util/ASCII.hxx"
 #include "util/Error.hxx"
+#include "util/StringView.hxx"
 #include "lib/expat/ExpatParser.hxx"
 #include "Log.hxx"
 
@@ -128,7 +129,8 @@ rss_char_data(void *user_data, const XML_Char *s, int len)
 
 	case RssParser::ITEM:
 		if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
-			parser->tag_builder.AddItem(parser->tag_type, s, len);
+			parser->tag_builder.AddItem(parser->tag_type,
+						    StringView(s, len));
 
 		break;
 	}
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
index 6d09b6db2..0b7358e15 100644
--- a/src/playlist/plugins/XspfPlaylistPlugin.cxx
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -25,6 +25,7 @@
 #include "input/InputStream.hxx"
 #include "tag/TagBuilder.hxx"
 #include "util/Error.hxx"
+#include "util/StringView.hxx"
 #include "lib/expat/ExpatParser.hxx"
 #include "Log.hxx"
 
@@ -170,7 +171,8 @@ xspf_char_data(void *user_data, const XML_Char *s, int len)
 	case XspfParser::TRACK:
 		if (!parser->location.empty() &&
 		    parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
-			parser->tag_builder.AddItem(parser->tag_type, s, len);
+			parser->tag_builder.AddItem(parser->tag_type,
+						    StringView(s, len));
 
 		break;
 
diff --git a/src/tag/ApeLoader.cxx b/src/tag/ApeLoader.cxx
index 8c89f34c9..c42d088a8 100644
--- a/src/tag/ApeLoader.cxx
+++ b/src/tag/ApeLoader.cxx
@@ -21,6 +21,7 @@
 #include "ApeLoader.hxx"
 #include "system/ByteOrder.hxx"
 #include "fs/FileSystem.hxx"
+#include "util/StringView.hxx"
 
 #include <stdint.h>
 #include <assert.h>
@@ -89,7 +90,7 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
 		if (remaining < size)
 			break;
 
-		if (!callback(flags, key, p, size))
+		if (!callback(flags, key, {p, size}))
 			break;
 
 		p += size;
diff --git a/src/tag/ApeLoader.hxx b/src/tag/ApeLoader.hxx
index 1bdfe692d..4587ff063 100644
--- a/src/tag/ApeLoader.hxx
+++ b/src/tag/ApeLoader.hxx
@@ -26,11 +26,11 @@
 
 #include <stddef.h>
 
+struct StringView;
 class Path;
 
 typedef std::function<bool(unsigned long flags, const char *key,
-			   const char *value,
-			   size_t value_length)> ApeTagCallback;
+			   StringView value)> ApeTagCallback;
 
 /**
  * Scans the APE tag values from a file.
diff --git a/src/tag/ApeReplayGain.cxx b/src/tag/ApeReplayGain.cxx
index 139d44d1f..883885369 100644
--- a/src/tag/ApeReplayGain.cxx
+++ b/src/tag/ApeReplayGain.cxx
@@ -21,15 +21,16 @@
 #include "ApeReplayGain.hxx"
 #include "ApeLoader.hxx"
 #include "ReplayGain.hxx"
-#include "util/ASCII.hxx"
 #include "fs/Path.hxx"
+#include "util/ASCII.hxx"
+#include "util/StringView.hxx"
 
 #include <string.h>
 #include <stdlib.h>
 
 static bool
 replay_gain_ape_callback(unsigned long flags, const char *key,
-			 const char *_value, size_t value_length,
+			 StringView _value,
 			 ReplayGainInfo &info)
 {
 	/* we only care about utf-8 text tags */
@@ -37,11 +38,11 @@ replay_gain_ape_callback(unsigned long flags, const char *key,
 		return false;
 
 	char value[16];
-	if (value_length >= sizeof(value))
+	if (_value.size >= sizeof(value))
 		return false;
 
-	memcpy(value, _value, value_length);
-	value[value_length] = 0;
+	memcpy(value, _value.data, _value.size);
+	value[_value.size] = 0;
 
 	return ParseReplayGainTag(info, key, value);
 }
@@ -53,10 +54,9 @@ replay_gain_ape_read(Path path_fs, ReplayGainInfo &info)
 
 	auto callback = [&info, &found]
 		(unsigned long flags, const char *key,
-		 const char *value,
-		 size_t value_length) {
+		 StringView value) {
 		found |= replay_gain_ape_callback(flags, key,
-						  value, value_length,
+						  value,
 						  info);
 		return true;
 	};
diff --git a/src/tag/ApeTag.cxx b/src/tag/ApeTag.cxx
index 49ae7a036..81318a771 100644
--- a/src/tag/ApeTag.cxx
+++ b/src/tag/ApeTag.cxx
@@ -24,6 +24,7 @@
 #include "TagTable.hxx"
 #include "TagHandler.hxx"
 #include "fs/Path.hxx"
+#include "util/StringView.hxx"
 
 #include <string>
 
@@ -75,17 +76,18 @@ ForEachValue(const char *value, const char *end, C &&callback)
  */
 static bool
 tag_ape_import_item(unsigned long flags,
-		    const char *key, const char *value, size_t value_length,
+		    const char *key, StringView value,
 		    const struct tag_handler *handler, void *handler_ctx)
 {
 	/* we only care about utf-8 text tags */
 	if ((flags & (0x3 << 1)) != 0)
 		return false;
 
-	const char *const end = value + value_length;
+	const auto begin = value.begin();
+	const auto end = value.end();
 
 	if (handler->pair != nullptr)
-		ForEachValue(value, end, [handler, handler_ctx,
+		ForEachValue(begin, end, [handler, handler_ctx,
 					  key](const char *_value) {
 				handler->pair(key, _value, handler_ctx);
 			});
@@ -94,8 +96,8 @@ tag_ape_import_item(unsigned long flags,
 	if (type == TAG_NUM_OF_ITEM_TYPES)
 		return false;
 
-	ForEachValue(value, end, [handler, handler_ctx,
-				    type](const char *_value) {
+	ForEachValue(begin, end, [handler, handler_ctx,
+				  type](const char *_value) {
 			tag_handler_invoke_tag(handler, handler_ctx,
 					       type, _value);
 		});
@@ -111,10 +113,8 @@ tag_ape_scan2(Path path_fs,
 
 	auto callback = [handler, handler_ctx, &recognized]
 		(unsigned long flags, const char *key,
-		 const char *value,
-		 size_t value_length) {
+		 StringView value) {
 		recognized |= tag_ape_import_item(flags, key, value,
-						  value_length,
 						  handler, handler_ctx);
 		return true;
 	};
diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx
index 4ce50cc4c..82d99006a 100644
--- a/src/tag/TagBuilder.cxx
+++ b/src/tag/TagBuilder.cxx
@@ -24,6 +24,7 @@
 #include "TagString.hxx"
 #include "Tag.hxx"
 #include "util/WritableBuffer.hxx"
+#include "util/StringView.hxx"
 
 #include <array>
 
@@ -189,22 +190,16 @@ TagBuilder::Complement(const Tag &other)
 }
 
 inline void
-TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
+TagBuilder::AddItemInternal(TagType type, StringView value)
 {
-#if !CLANG_CHECK_VERSION(3,6)
-	/* disabled on clang due to -Wtautological-pointer-compare */
-	assert(value != nullptr);
-#endif
-	assert(length > 0);
+	assert(!value.IsEmpty());
 
-	auto f = FixTagString(value, length);
-	if (!f.IsNull()) {
-		value = f.data;
-		length = f.size;
-	}
+	auto f = FixTagString(value);
+	if (!f.IsNull())
+		value = { f.data, f.size };
 
 	tag_pool_lock.lock();
-	auto i = tag_pool_get_item(type, value, length);
+	auto i = tag_pool_get_item(type, value);
 	tag_pool_lock.unlock();
 
 	free(f.data);
@@ -213,17 +208,12 @@ TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
 }
 
 void
-TagBuilder::AddItem(TagType type, const char *value, size_t length)
+TagBuilder::AddItem(TagType type, StringView value)
 {
-#if !CLANG_CHECK_VERSION(3,6)
-	/* disabled on clang due to -Wtautological-pointer-compare */
-	assert(value != nullptr);
-#endif
-
-	if (length == 0 || !IsTagEnabled(type))
+	if (value.IsEmpty() || !IsTagEnabled(type))
 		return;
 
-	AddItemInternal(type, value, length);
+	AddItemInternal(type, value);
 }
 
 void
@@ -234,14 +224,14 @@ TagBuilder::AddItem(TagType type, const char *value)
 	assert(value != nullptr);
 #endif
 
-	AddItem(type, value, strlen(value));
+	AddItem(type, StringView(value));
 }
 
 void
 TagBuilder::AddEmptyItem(TagType type)
 {
 	tag_pool_lock.lock();
-	auto i = tag_pool_get_item(type, "", 0);
+	auto i = tag_pool_get_item(type, StringView::Empty());
 	tag_pool_lock.unlock();
 
 	items.push_back(i);
diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx
index 738caa6cd..08c66d0b3 100644
--- a/src/tag/TagBuilder.hxx
+++ b/src/tag/TagBuilder.hxx
@@ -28,6 +28,7 @@
 
 #include <stddef.h>
 
+struct StringView;
 struct TagItem;
 struct Tag;
 
@@ -141,7 +142,7 @@ public:
 	 * @param length the length of #value
 	 */
 	gcc_nonnull_all
-	void AddItem(TagType type, const char *value, size_t length);
+	void AddItem(TagType type, StringView value);
 
 	/**
 	 * Appends a new tag item.
@@ -171,7 +172,7 @@ public:
 
 private:
 	gcc_nonnull_all
-	void AddItemInternal(TagType type, const char *value, size_t length);
+	void AddItemInternal(TagType type, StringView value);
 };
 
 #endif
diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx
index 0280948fd..88b2ba333 100644
--- a/src/tag/TagPool.cxx
+++ b/src/tag/TagPool.cxx
@@ -22,6 +22,7 @@
 #include "TagItem.hxx"
 #include "util/Cast.hxx"
 #include "util/VarSize.hxx"
+#include "util/StringView.hxx"
 
 #include <assert.h>
 #include <string.h>
@@ -37,39 +38,37 @@ struct TagPoolSlot {
 	TagItem item;
 
 	TagPoolSlot(TagPoolSlot *_next, TagType type,
-		    const char *value, size_t length)
+		    StringView value)
 		:next(_next), ref(1) {
 		item.type = type;
-		memcpy(item.value, value, length);
-		item.value[length] = 0;
+		memcpy(item.value, value.data, value.size);
+		item.value[value.size] = 0;
 	}
 
 	static TagPoolSlot *Create(TagPoolSlot *_next, TagType type,
-				   const char *value, size_t length);
+				   StringView value);
 } gcc_packed;
 
 TagPoolSlot *
 TagPoolSlot::Create(TagPoolSlot *_next, TagType type,
-		    const char *value, size_t length)
+		    StringView value)
 {
 	TagPoolSlot *dummy;
 	return NewVarSize<TagPoolSlot>(sizeof(dummy->item.value),
-				       length + 1,
+				       value.size + 1,
 				       _next, type,
-				       value, length);
+				       value);
 }
 
 static TagPoolSlot *slots[NUM_SLOTS];
 
 static inline unsigned
-calc_hash_n(TagType type, const char *p, size_t length)
+calc_hash(TagType type, StringView p)
 {
 	unsigned hash = 5381;
 
-	assert(p != nullptr);
-
-	while (length-- > 0)
-		hash = (hash << 5) + hash + *p++;
+	for (auto ch : p)
+		hash = (hash << 5) + hash + ch;
 
 	return hash ^ type;
 }
@@ -97,9 +96,9 @@ tag_item_to_slot(TagItem *item)
 }
 
 static inline TagPoolSlot **
-tag_value_slot_p(TagType type, const char *value, size_t length)
+tag_value_slot_p(TagType type, StringView value)
 {
-	return &slots[calc_hash_n(type, value, length) % NUM_SLOTS];
+	return &slots[calc_hash(type, value) % NUM_SLOTS];
 }
 
 static inline TagPoolSlot **
@@ -109,13 +108,12 @@ tag_value_slot_p(TagType type, const char *value)
 }
 
 TagItem *
-tag_pool_get_item(TagType type, const char *value, size_t length)
+tag_pool_get_item(TagType type, StringView value)
 {
-	auto slot_p = tag_value_slot_p(type, value, length);
+	auto slot_p = tag_value_slot_p(type, value);
 	for (auto slot = *slot_p; slot != nullptr; slot = slot->next) {
 		if (slot->item.type == type &&
-		    length == strlen(slot->item.value) &&
-		    memcmp(value, slot->item.value, length) == 0 &&
+		    value.Equals(slot->item.value) &&
 		    slot->ref < 0xff) {
 			assert(slot->ref > 0);
 			++slot->ref;
@@ -123,7 +121,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
 		}
 	}
 
-	auto slot = TagPoolSlot::Create(*slot_p, type, value, length);
+	auto slot = TagPoolSlot::Create(*slot_p, type, value);
 	*slot_p = slot;
 	return &slot->item;
 }
@@ -141,11 +139,9 @@ tag_pool_dup_item(TagItem *item)
 	} else {
 		/* the reference counter overflows above 0xff;
 		   duplicate the item, and start with 1 */
-		size_t length = strlen(item->value);
-		auto slot_p = tag_value_slot_p(item->type,
-					       item->value, length);
+		auto slot_p = tag_value_slot_p(item->type, item->value);
 		slot = TagPoolSlot::Create(*slot_p, item->type,
-					   item->value, strlen(item->value));
+					   item->value);
 		*slot_p = slot;
 		return &slot->item;
 	}
diff --git a/src/tag/TagPool.hxx b/src/tag/TagPool.hxx
index 7fba85b54..7b4b5dadf 100644
--- a/src/tag/TagPool.hxx
+++ b/src/tag/TagPool.hxx
@@ -26,9 +26,10 @@
 extern Mutex tag_pool_lock;
 
 struct TagItem;
+struct StringView;
 
 TagItem *
-tag_pool_get_item(TagType type, const char *value, size_t length);
+tag_pool_get_item(TagType type, StringView value);
 
 TagItem *
 tag_pool_dup_item(TagItem *item);
diff --git a/src/tag/TagString.cxx b/src/tag/TagString.cxx
index 85227de24..d30a07a27 100644
--- a/src/tag/TagString.cxx
+++ b/src/tag/TagString.cxx
@@ -21,6 +21,7 @@
 #include "TagString.hxx"
 #include "util/Alloc.hxx"
 #include "util/WritableBuffer.hxx"
+#include "util/StringView.hxx"
 #include "util/UTF8.hxx"
 
 #include <assert.h>
@@ -54,14 +55,14 @@ FindInvalidUTF8(const char *p, const char *const end)
  * Replace invalid sequences with the question mark.
  */
 static WritableBuffer<char>
-patch_utf8(const char *src, size_t length, const char *_invalid)
+patch_utf8(StringView src, const char *_invalid)
 {
 	/* duplicate the string, and replace invalid bytes in that
 	   buffer */
-	char *dest = (char *)xmemdup(src, length);
-	char *const end = dest + length;
+	char *dest = (char *)xmemdup(src.data, src.size);
+	char *const end = dest + src.size;
 
-	char *invalid = dest + (_invalid - src);
+	char *invalid = dest + (_invalid - src.data);
 	do {
 		*invalid = '?';
 
@@ -69,19 +70,19 @@ patch_utf8(const char *src, size_t length, const char *_invalid)
 		invalid = const_cast<char *>(__invalid);
 	} while (invalid != nullptr);
 
-	return { dest, length };
+	return { dest, src.size };
 }
 
 static WritableBuffer<char>
-fix_utf8(const char *str, size_t length)
+fix_utf8(StringView p)
 {
 	/* check if the string is already valid UTF-8 */
-	const char *invalid = FindInvalidUTF8(str, str + length);
+	const char *invalid = FindInvalidUTF8(p.begin(), p.end());
 	if (invalid == nullptr)
 		return nullptr;
 
 	/* no, broken - patch invalid sequences */
-	return patch_utf8(str, length, invalid);
+	return patch_utf8(p, invalid);
 }
 
 static bool
@@ -91,11 +92,11 @@ char_is_non_printable(unsigned char ch)
 }
 
 static const char *
-find_non_printable(const char *p, size_t length)
+find_non_printable(StringView p)
 {
-	for (size_t i = 0; i < length; ++i)
-		if (char_is_non_printable(p[i]))
-			return p + i;
+	for (const char &ch : p)
+		if (char_is_non_printable(ch))
+			return &ch;
 
 	return nullptr;
 }
@@ -105,31 +106,29 @@ find_non_printable(const char *p, size_t length)
  * Returns nullptr if nothing needs to be cleared.
  */
 static WritableBuffer<char>
-clear_non_printable(const char *p, size_t length)
+clear_non_printable(StringView src)
 {
-	const char *first = find_non_printable(p, length);
+	const char *first = find_non_printable(src);
 	if (first == nullptr)
 		return nullptr;
 
-	char *dest = (char *)xmemdup(p, length);
+	char *dest = (char *)xmemdup(src.data, src.size);
 
-	for (size_t i = first - p; i < length; ++i)
+	for (size_t i = first - src.data; i < src.size; ++i)
 		if (char_is_non_printable(dest[i]))
 			dest[i] = ' ';
 
-	return { dest, length };
+	return { dest, src.size };
 }
 
 WritableBuffer<char>
-FixTagString(const char *p, size_t length)
+FixTagString(StringView p)
 {
-	auto utf8 = fix_utf8(p, length);
-	if (!utf8.IsNull()) {
-		p = utf8.data;
-		length = utf8.size;
-	}
+	auto utf8 = fix_utf8(p);
+	if (!utf8.IsNull())
+		p = {utf8.data, utf8.size};
 
-	WritableBuffer<char> cleared = clear_non_printable(p, length);
+	WritableBuffer<char> cleared = clear_non_printable(p);
 	if (cleared.IsNull())
 		cleared = utf8;
 	else
diff --git a/src/tag/TagString.hxx b/src/tag/TagString.hxx
index f4cba9a40..53fbc7abd 100644
--- a/src/tag/TagString.hxx
+++ b/src/tag/TagString.hxx
@@ -25,10 +25,11 @@
 
 #include <stddef.h>
 
+struct StringView;
 template<typename T> struct WritableBuffer;
 
 gcc_nonnull_all
 WritableBuffer<char>
-FixTagString(const char *p, size_t length);
+FixTagString(StringView p);
 
 #endif
diff --git a/src/util/StringView.hxx b/src/util/StringView.hxx
new file mode 100644
index 000000000..fbae9a7cc
--- /dev/null
+++ b/src/util/StringView.hxx
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013-2015 Max Kellermann <max@duempel.org>
+ *
+ * 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.
+ */
+
+#ifndef STRING_VIEW_HXX
+#define STRING_VIEW_HXX
+
+#include "ConstBuffer.hxx"
+
+#include <string.h>
+
+struct StringView : ConstBuffer<char> {
+	StringView() = default;
+
+	constexpr StringView(pointer_type _data, size_type _size)
+		:ConstBuffer<char>(_data, _size) {}
+
+	constexpr StringView(pointer_type _begin, pointer_type _end)
+		:ConstBuffer<char>(_begin, _end - _begin) {}
+
+	StringView(pointer_type _data)
+		:ConstBuffer<char>(_data,
+				   _data != nullptr ? strlen(_data) : 0) {}
+
+	StringView(std::nullptr_t n)
+		:ConstBuffer<char>(n) {}
+
+	static constexpr StringView Empty() {
+		return StringView("", size_t(0));
+	}
+
+	void SetEmpty() {
+		data = "";
+		size = 0;
+	}
+
+	gcc_pure
+	pointer_type Find(char ch) const {
+		return (pointer_type)memchr(data, ch, size);
+	}
+
+	StringView &operator=(std::nullptr_t) {
+		data = nullptr;
+		size = 0;
+		return *this;
+	}
+
+	StringView &operator=(pointer_type _data) {
+		data = _data;
+		size = _data != nullptr ? strlen(_data) : 0;
+		return *this;
+	}
+
+	gcc_pure
+	bool StartsWith(StringView needle) const {
+		return size >= needle.size &&
+			memcmp(data, needle.data, needle.size) == 0;
+	}
+
+	gcc_pure
+	bool Equals(StringView other) const {
+		return size == other.size &&
+			memcmp(data, other.data, size) == 0;
+	}
+
+	template<size_t n>
+	bool EqualsLiteral(const char (&other)[n]) const {
+		return Equals({other, n - 1});
+	}
+
+	gcc_pure
+	bool EqualsIgnoreCase(StringView other) const {
+		return size == other.size &&
+			strncasecmp(data, other.data, size) == 0;
+	}
+
+	template<size_t n>
+	bool EqualsLiteralIgnoreCase(const char (&other)[n]) const {
+		return EqualsIgnoreCase({other, n - 1});
+	}
+};
+
+#endif
-- 
cgit v1.2.3