Filter.cxx 11.5 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "config.h"
21
#include "Filter.hxx"
22
#include "NotSongFilter.hxx"
23 24 25 26
#include "UriSongFilter.hxx"
#include "BaseSongFilter.hxx"
#include "TagSongFilter.hxx"
#include "ModifiedSinceSongFilter.hxx"
27
#include "AudioFormatSongFilter.hxx"
28
#include "LightSong.hxx"
29
#include "AudioParser.hxx"
30
#include "tag/ParseName.hxx"
31
#include "tag/Tag.hxx"
32 33
#include "time/ChronoUtil.hxx"
#include "time/ISO8601.hxx"
34
#include "util/CharUtil.hxx"
35
#include "util/ConstBuffer.hxx"
36
#include "util/RuntimeError.hxx"
37
#include "util/StringAPI.hxx"
38
#include "util/StringCompare.hxx"
39
#include "util/StringStrip.hxx"
40
#include "util/StringView.hxx"
41
#include "util/ASCII.hxx"
42
#include "util/UriUtil.hxx"
43
#include "lib/icu/CaseFold.hxx"
44

45
#include <exception>
46

47
#include <assert.h>
48 49
#include <stdlib.h>

50 51
#define LOCATE_TAG_FILE_KEY     "file"
#define LOCATE_TAG_FILE_KEY_OLD "filename"
52 53
#define LOCATE_TAG_ANY_KEY      "any"

54 55 56 57 58 59 60
enum {
	/**
	 * Limit the search to files within the given directory.
	 */
	LOCATE_TAG_BASE_TYPE = TAG_NUM_OF_ITEM_TYPES + 1,

	LOCATE_TAG_MODIFIED_SINCE,
61
	LOCATE_TAG_AUDIO_FORMAT,
62 63 64
	LOCATE_TAG_FILE_TYPE,
	LOCATE_TAG_ANY_TYPE,
};
65

66 67 68 69 70
/**
 * @return #TAG_NUM_OF_ITEM_TYPES on error
 */
gcc_pure
static unsigned
71
locate_parse_type(const char *str) noexcept
72
{
73 74
	if (StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY) ||
	    StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY_OLD))
75 76
		return LOCATE_TAG_FILE_TYPE;

77
	if (StringEqualsCaseASCII(str, LOCATE_TAG_ANY_KEY))
78 79
		return LOCATE_TAG_ANY_TYPE;

80 81 82
	if (strcmp(str, "base") == 0)
		return LOCATE_TAG_BASE_TYPE;

83 84 85
	if (strcmp(str, "modified-since") == 0)
		return LOCATE_TAG_MODIFIED_SINCE;

86 87 88
	if (StringEqualsCaseASCII(str, "AudioFormat"))
		return LOCATE_TAG_AUDIO_FORMAT;

89
	return tag_name_parse_i(str);
90 91
}

92 93
SongFilter::SongFilter(TagType tag, const char *value, bool fold_case)
{
94 95
	/* for compatibility with MPD 0.20 and older, "fold_case" also
	   switches on "substring" */
96 97
	and_filter.AddItem(std::make_unique<TagSongFilter>(tag,
							   StringFilter(value, fold_case, fold_case, false)));
98 99 100 101 102 103 104 105 106 107 108 109 110
}

SongFilter::~SongFilter()
{
	/* this destructor exists here just so it won't get inlined */
}

std::string
SongFilter::ToExpression() const noexcept
{
	return and_filter.ToExpression();
}

111 112
static std::chrono::system_clock::time_point
ParseTimeStamp(const char *s)
113 114 115
{
	assert(s != nullptr);

116 117 118 119 120 121 122 123 124 125 126 127 128
	try {
		/* try ISO 8601 */
		return ParseISO8601(s).first;
	} catch (...) {
		char *endptr;
		unsigned long long value = strtoull(s, &endptr, 10);
		if (*endptr == 0 && endptr > s)
			/* it's an integral UNIX time stamp */
			return std::chrono::system_clock::from_time_t((time_t)value);

		/* rethrow the ParseISO8601() error */
		throw;
	}
129 130
}

131 132 133
static constexpr bool
IsTagNameChar(char ch) noexcept
{
134
	return IsAlphaASCII(ch) || ch == '_' || ch == '-';
135 136 137 138 139 140 141 142 143 144 145
}

static const char *
FirstNonTagNameChar(const char *s) noexcept
{
	while (IsTagNameChar(*s))
		++s;
	return s;
}

static auto
146
ExpectWord(const char *&s)
147
{
148
	const char *begin = s;
149 150
	const char *end = FirstNonTagNameChar(s);
	if (end == s)
151
		throw std::runtime_error("Word expected");
152 153

	s = StripLeft(end);
154 155 156 157 158 159 160
	return std::string(begin, end);
}

static auto
ExpectFilterType(const char *&s)
{
	const auto name = ExpectWord(s);
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

	const auto type = locate_parse_type(name.c_str());
	if (type == TAG_NUM_OF_ITEM_TYPES)
		throw FormatRuntimeError("Unknown filter type: %s",
					 name.c_str());

	return type;
}

static constexpr bool
IsQuote(char ch) noexcept
{
	return ch == '"' || ch == '\'';
}

static std::string
ExpectQuoted(const char *&s)
{
	const char quote = *s++;
	if (!IsQuote(quote))
		throw std::runtime_error("Quoted string expected");

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	char buffer[4096];
	size_t length = 0;

	while (*s != quote) {
		if (*s == '\\')
			/* backslash escapes the following character */
			++s;

		if (*s == 0)
			throw std::runtime_error("Closing quote not found");

		buffer[length++] = *s++;

		if (length >= sizeof(buffer))
			throw std::runtime_error("Quoted value is too long");
	}

	s = StripLeft(s + 1);
201

202
	return {buffer, length};
203 204
}

205 206 207 208 209 210 211 212 213
/**
 * Parse a string operator and its second operand and convert it to a
 * #StringFilter.
 *
 * Throws on error.
 */
static StringFilter
ParseStringFilter(const char *&s, bool fold_case)
{
214 215 216 217 218 219 220 221 222 223 224 225 226 227
	if (auto after_contains = StringAfterPrefixIgnoreCase(s, "contains ")) {
		s = StripLeft(after_contains);
		auto value = ExpectQuoted(s);
		return StringFilter(std::move(value),
				    fold_case, true, false);
	}

	if (auto after_not_contains = StringAfterPrefixIgnoreCase(s, "!contains ")) {
		s = StripLeft(after_not_contains);
		auto value = ExpectQuoted(s);
		return StringFilter(std::move(value),
				    fold_case, true, true);
	}

228
	bool negated = false;
229 230 231 232 233 234 235 236 237 238 239 240 241 242

#ifdef HAVE_PCRE
	if ((s[0] == '!' || s[0] == '=') && s[1] == '~') {
		negated = s[0] == '!';
		s = StripLeft(s + 2);
		auto value = ExpectQuoted(s);
		StringFilter f(std::move(value), fold_case, false, negated);
		f.SetRegex(std::make_shared<UniqueRegex>(f.GetValue().c_str(),
							 false, false,
							 fold_case));
		return f;
	}
#endif

243 244 245 246 247 248 249 250 251 252 253 254
	if (s[0] == '!' && s[1] == '=')
		negated = true;
	else if (s[0] != '=' || s[1] != '=')
		throw std::runtime_error("'==' or '!=' expected");

	s = StripLeft(s + 2);
	auto value = ExpectQuoted(s);

	return StringFilter(std::move(value),
			    fold_case, false, negated);
}

255 256
ISongFilterPtr
SongFilter::ParseExpression(const char *&s, bool fold_case)
257 258 259 260 261
{
	assert(*s == '(');

	s = StripLeft(s + 1);

262 263 264 265 266 267 268
	if (*s == '(') {
		auto first = ParseExpression(s, fold_case);
		if (*s == ')') {
			++s;
			return first;
		}

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
		if (ExpectWord(s) != "AND")
			throw std::runtime_error("'AND' expected");

		auto and_filter = std::make_unique<AndSongFilter>();
		and_filter->AddItem(std::move(first));

		while (true) {
			and_filter->AddItem(ParseExpression(s, fold_case));

			if (*s == ')') {
				++s;
				return and_filter;
			}

			if (ExpectWord(s) != "AND")
				throw std::runtime_error("'AND' expected");
		}
286
	}
287

288 289 290 291 292 293 294 295 296 297 298 299 300 301
	if (*s == '!') {
		s = StripLeft(s + 1);

		if (*s != '(')
			throw std::runtime_error("'(' expected");

		auto inner = ParseExpression(s, fold_case);
		if (*s != ')')
			throw std::runtime_error("')' expected");
		s = StripLeft(s + 1);

		return std::make_unique<NotSongFilter>(std::move(inner));
	}

302
	auto type = ExpectFilterType(s);
303 304 305 306 307

	if (type == LOCATE_TAG_MODIFIED_SINCE) {
		const auto value_s = ExpectQuoted(s);
		if (*s != ')')
			throw std::runtime_error("')' expected");
308 309
		s = StripLeft(s + 1);
		return std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value_s.c_str()));
310 311 312 313
	} else if (type == LOCATE_TAG_BASE_TYPE) {
		auto value = ExpectQuoted(s);
		if (*s != ')')
			throw std::runtime_error("')' expected");
314
		s = StripLeft(s + 1);
315

316
		return std::make_unique<BaseSongFilter>(std::move(value));
317
	} else if (type == LOCATE_TAG_AUDIO_FORMAT) {
318 319 320 321 322 323 324
		bool mask;
		if (s[0] == '=' && s[1] == '=')
			mask = false;
		else if (s[0] == '=' && s[1] == '~')
			mask = true;
		else
			throw std::runtime_error("'==' or '=~' expected");
325 326 327 328

		s = StripLeft(s + 2);

		const auto value = ParseAudioFormat(ExpectQuoted(s).c_str(),
329
						    mask);
330 331 332 333 334 335

		if (*s != ')')
			throw std::runtime_error("')' expected");
		s = StripLeft(s + 1);

		return std::make_unique<AudioFormatSongFilter>(value);
336
	} else {
337
		auto string_filter = ParseStringFilter(s, fold_case);
338 339 340
		if (*s != ')')
			throw std::runtime_error("')' expected");

341 342 343 344 345 346
		s = StripLeft(s + 1);

		if (type == LOCATE_TAG_ANY_TYPE)
			type = TAG_NUM_OF_ITEM_TYPES;

		if (type == LOCATE_TAG_FILE_TYPE)
347
			return std::make_unique<UriSongFilter>(std::move(string_filter));
348 349

		return std::make_unique<TagSongFilter>(TagType(type),
350
						       std::move(string_filter));
351 352 353
	}
}

354
void
355 356 357
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{
	unsigned tag = locate_parse_type(tag_string);
358 359 360

	switch (tag) {
	case TAG_NUM_OF_ITEM_TYPES:
361
		throw std::runtime_error("Unknown filter type");
362

363
	case LOCATE_TAG_BASE_TYPE:
364
		if (!uri_safe_local(value))
365
			throw std::runtime_error("Bad URI");
366

367
		and_filter.AddItem(std::make_unique<BaseSongFilter>(value));
368 369 370
		break;

	case LOCATE_TAG_MODIFIED_SINCE:
371
		and_filter.AddItem(std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value)));
372
		break;
373

374
	case LOCATE_TAG_FILE_TYPE:
375 376
		/* for compatibility with MPD 0.20 and older,
		   "fold_case" also switches on "substring" */
377 378 379 380
		and_filter.AddItem(std::make_unique<UriSongFilter>(StringFilter(value,
										fold_case,
										fold_case,
										false)));
381 382 383 384 385 386
		break;

	default:
		if (tag == LOCATE_TAG_ANY_TYPE)
			tag = TAG_NUM_OF_ITEM_TYPES;

387 388
		/* for compatibility with MPD 0.20 and older,
		   "fold_case" also switches on "substring" */
389
		and_filter.AddItem(std::make_unique<TagSongFilter>(TagType(tag),
390 391 392 393
								   StringFilter(value,
										fold_case,
										fold_case,
										false)));
394 395
		break;
	}
396 397
}

398
void
399
SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case)
400
{
401
	if (args.empty())
402
		throw std::runtime_error("Incorrect number of filter arguments");
403

404
	do {
405 406
		if (*args.front() == '(') {
			const char *s = args.shift();
407 408
			const char *end = s;
			auto f = ParseExpression(end, fold_case);
409 410 411
			if (*end != 0)
				throw std::runtime_error("Unparsed garbage after expression");

412
			and_filter.AddItem(std::move(f));
413 414 415
			continue;
		}

416 417 418 419 420 421 422
		if (args.size < 2)
			throw std::runtime_error("Incorrect number of filter arguments");

		const char *tag = args.shift();
		const char *value = args.shift();
		Parse(tag, value, fold_case);
	} while (!args.empty());
423 424
}

425 426 427 428 429 430
void
SongFilter::Optimize() noexcept
{
	OptimizeSongFilter(and_filter);
}

431
bool
432
SongFilter::Match(const LightSong &song) const noexcept
433
{
434
	return and_filter.Match(song);
435 436
}

437 438 439
bool
SongFilter::HasFoldCase() const noexcept
{
440
	for (const auto &i : and_filter.GetItems()) {
441 442 443 444 445 446 447 448 449 450 451 452
		if (auto t = dynamic_cast<const TagSongFilter *>(i.get())) {
			if (t->GetFoldCase())
				return true;
		} else if (auto u = dynamic_cast<const UriSongFilter *>(i.get())) {
			if (u->GetFoldCase())
				return true;
		}
	}

	return false;
}

453
bool
454
SongFilter::HasOtherThanBase() const noexcept
455
{
456
	for (const auto &i : and_filter.GetItems()) {
457 458
		const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
		if (f == nullptr)
459
			return true;
460
	}
461 462 463 464

	return false;
}

465
const char *
466
SongFilter::GetBase() const noexcept
467
{
468
	for (const auto &i : and_filter.GetItems()) {
469 470 471 472
		const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
		if (f != nullptr)
			return f->GetValue();
	}
473

474
	return nullptr;
475
}
476 477 478 479 480 481 482

SongFilter
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
{
	const StringView prefix(_prefix);
	SongFilter result;

483
	for (const auto &i : and_filter.GetItems()) {
484 485 486
		const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
		if (f != nullptr) {
			const char *s = StringAfterPrefix(f->GetValue(), prefix);
487 488 489 490 491 492 493 494
			if (s != nullptr) {
				if (*s == 0)
					continue;

				if (*s == '/') {
					++s;

					if (*s != 0)
495
						result.and_filter.AddItem(std::make_unique<BaseSongFilter>(s));
496 497 498 499 500 501

					continue;
				}
			}
		}

502
		result.and_filter.AddItem(i->Clone());
503 504 505 506
	}

	return result;
}