aboutsummaryrefslogtreecommitdiffstats
path: root/src/tag/Format.cxx
blob: de4db57ef425f292bc9df2c06df5d2904ef35afb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 * Copyright (C) 2003-2015 The Music Player Daemon Project
 * http://www.musicpd.org
 *
 * 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.
 *
 * 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.
 */

#include "config.h"
#include "Format.hxx"
#include "Tag.hxx"
#include "util/format.h"
#include "util/StringUtil.hxx"

#include <algorithm>

#include <string.h>
#include <time.h>

struct FormatTagContext {
	const Tag &tag;

	char buffer[256];

	explicit FormatTagContext(const Tag &_tag):tag(_tag) {}
};

/**
 * Is this a character unsafe to use in a path name segment?
 */
static constexpr bool
IsUnsafeChar(char ch)
{
	return
		/* disallow characters illegal in file names on
		   Windows (Linux allows almost anything) */
		ch == '\\' || ch == '/' || ch == ':' || ch == '*' ||
		ch == '?' || ch == '<' || ch == '>' || ch == '|' ||
		/* allow space, but disallow all other whitespace */
		(unsigned char)ch < 0x20;
}

gcc_pure
static bool
HasUnsafeChar(const char *s)
{
	for (; *s; ++s)
		if (IsUnsafeChar(*s))
			return true;

	return false;
}

static const char *
SanitizeString(const char *s, char *buffer, size_t buffer_size)
{
	/* skip leading dots to avoid generating "../" sequences */
	while (*s == '.')
		++s;

	if (!HasUnsafeChar(s))
		return s;

	char *end = CopyString(buffer, s, buffer_size);
	std::replace_if(buffer, end, IsUnsafeChar, ' ');
	return buffer;
}

gcc_pure gcc_nonnull_all
static const char *
TagGetter(const void *object, const char *name)
{
	const auto &_ctx = *(const FormatTagContext *)object;
	auto &ctx = const_cast<FormatTagContext &>(_ctx);

	if (strcmp(name, "iso8601") == 0) {
		time_t t = time(nullptr);
#ifdef WIN32
		const struct tm *tm2 = gmtime(&t);
#else
		struct tm tm;
		const struct tm *tm2 = gmtime_r(&t, &tm);
#endif
		if (tm2 == nullptr)
			return "";

		strftime(ctx.buffer, sizeof(ctx.buffer),
#ifdef WIN32
			 /* kludge: use underscore instead of colon on
			    Windows because colons are not allowed in
			    file names, and this library is mostly
			    used to generate file names */
			 "%Y-%m-%dT%H_%M_%SZ",
#else
			 "%FT%TZ",
#endif
			 tm2);
		return ctx.buffer;
	}

	const Tag &tag = ctx.tag;

	TagType tag_type = tag_name_parse_i(name);
	if (tag_type == TAG_NUM_OF_ITEM_TYPES)
		/* unknown tag name */
		return nullptr;

	const char *value = tag.GetValue(tag_type);
	if (value == nullptr)
		/* known tag name, but not present in this object */
		value = "";

	// TODO: handle multiple tag values
	return SanitizeString(value, ctx.buffer, sizeof(ctx.buffer));
}

char *
FormatTag(const Tag &tag, const char *format)
{
	FormatTagContext ctx(tag);
	return format_object(format, &ctx, TagGetter);
}