1 /* libmpdclient
2    (c) 2003-2019 The Music Player Daemon Project
3    This project's homepage is: http://www.musicpd.org
4 
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions
7    are met:
8 
9    - Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11 
12    - Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15 
16    - Neither the name of the Music Player Daemon nor the names of its
17    contributors may be used to endorse or promote products derived from
18    this software without specific prior written permission.
19 
20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23    A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
24    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31    */
32 
33 #include <mpd/search.h>
34 #include <mpd/send.h>
35 #include <mpd/pair.h>
36 #include <mpd/recv.h>
37 #include "internal.h"
38 #include "iso8601.h"
39 
40 #include <assert.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <string.h>
44 
45 static bool
mpd_search_init(struct mpd_connection * connection,const char * cmd)46 mpd_search_init(struct mpd_connection *connection, const char *cmd)
47 {
48 	assert(connection != NULL);
49 	assert(cmd != NULL);
50 
51 	if (mpd_error_is_defined(&connection->error))
52 		return false;
53 
54 	if (connection->request) {
55 		mpd_error_code(&connection->error, MPD_ERROR_STATE);
56 		mpd_error_message(&connection->error,
57 				  "search already in progress");
58 		return false;
59 	}
60 
61 	connection->request = strdup(cmd);
62 	if (connection->request == NULL) {
63 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
64 		return false;
65 	}
66 
67 	return true;
68 }
69 
70 bool
mpd_search_db_songs(struct mpd_connection * connection,bool exact)71 mpd_search_db_songs(struct mpd_connection *connection, bool exact)
72 {
73 	return mpd_search_init(connection,
74 			       exact ? "find" : "search");
75 }
76 
77 bool
mpd_search_add_db_songs(struct mpd_connection * connection,bool exact)78 mpd_search_add_db_songs(struct mpd_connection *connection, bool exact)
79 {
80 	return mpd_search_init(connection,
81 			       exact ? "findadd" : "searchadd");
82 }
83 
84 bool
mpd_search_queue_songs(struct mpd_connection * connection,bool exact)85 mpd_search_queue_songs(struct mpd_connection *connection, bool exact)
86 {
87 	return mpd_search_init(connection,
88 			       exact ? "playlistfind" : "playlistsearch");
89 }
90 
91 bool
mpd_search_db_tags(struct mpd_connection * connection,enum mpd_tag_type type)92 mpd_search_db_tags(struct mpd_connection *connection, enum mpd_tag_type type)
93 {
94 	assert(connection != NULL);
95 
96 	if (mpd_error_is_defined(&connection->error))
97 		return false;
98 
99 	if (connection->request) {
100 		mpd_error_code(&connection->error, MPD_ERROR_STATE);
101 		mpd_error_message(&connection->error,
102 				  "search already in progress");
103 		return false;
104 	}
105 
106 	const char *strtype = mpd_tag_name(type);
107 	if (strtype == NULL) {
108 		mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT);
109 		mpd_error_message(&connection->error,
110 				  "invalid type specified");
111 		return false;
112 	}
113 
114 	const size_t len = 5 + strlen(strtype) + 1;
115 	connection->request = malloc(len);
116 	if (connection->request == NULL) {
117 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
118 		return false;
119 	}
120 
121 	snprintf(connection->request, len, "list %s", strtype);
122 
123 	return true;
124 }
125 
126 bool
mpd_count_db_songs(struct mpd_connection * connection)127 mpd_count_db_songs(struct mpd_connection *connection)
128 {
129 	assert(connection != NULL);
130 
131 	return mpd_search_init(connection, "count");
132 }
133 
134 static char *
mpd_search_prepare_append(struct mpd_connection * connection,size_t add_length)135 mpd_search_prepare_append(struct mpd_connection *connection,
136 			  size_t add_length)
137 {
138 	assert(connection != NULL);
139 
140 	if (mpd_error_is_defined(&connection->error))
141 		return NULL;
142 
143 	if (connection->request == NULL) {
144 		mpd_error_code(&connection->error, MPD_ERROR_STATE);
145 		mpd_error_message(&connection->error,
146 				  "no search in progress");
147 		return NULL;
148 	}
149 
150 	const size_t old_length = strlen(connection->request);
151 	char *new_request = realloc(connection->request,
152 				    old_length + add_length + 1);
153 	if (new_request == NULL) {
154 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
155 		return NULL;
156 	}
157 
158 	connection->request = new_request;
159 	return new_request + old_length;
160 }
161 
162 static char *
mpd_sanitize_arg(const char * src)163 mpd_sanitize_arg(const char *src)
164 {
165 	assert(src != NULL);
166 
167 	/* instead of counting in that loop above, just
168 	 * use a bit more memory and half running time
169 	 */
170 	char *result = malloc(strlen(src) * 2 + 1);
171 	if (result == NULL)
172 		return NULL;
173 
174 	char *dest = result;
175 	char ch;
176 	do {
177 		ch = *src++;
178 		if (ch == '"' || ch == '\\')
179 			*dest++ = '\\';
180 		*dest++ = ch;
181 	} while (ch != 0);
182 
183 	return result;
184 }
185 
186 static bool
mpd_search_add_constraint(struct mpd_connection * connection,mpd_unused enum mpd_operator oper,const char * name,const char * value)187 mpd_search_add_constraint(struct mpd_connection *connection,
188 			  mpd_unused enum mpd_operator oper,
189 			  const char *name,
190 			  const char *value)
191 {
192 	assert(connection != NULL);
193 	assert(name != NULL);
194 	assert(value != NULL);
195 
196 	char *arg = mpd_sanitize_arg(value);
197 	if (arg == NULL) {
198 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
199 		return false;
200 	}
201 
202 	const size_t add_length = 1 + strlen(name) + 2 + strlen(arg) + 1;
203 
204 	char *dest = mpd_search_prepare_append(connection, add_length);
205 	if (dest == NULL) {
206 		free(arg);
207 		return false;
208 	}
209 
210 	sprintf(dest, " %s \"%s\"", name, arg);
211 
212 	free(arg);
213 	return true;
214 }
215 bool
mpd_search_add_base_constraint(struct mpd_connection * connection,enum mpd_operator oper,const char * value)216 mpd_search_add_base_constraint(struct mpd_connection *connection,
217 			       enum mpd_operator oper,
218 			       const char *value)
219 {
220 	return mpd_search_add_constraint(connection, oper, "base", value);
221 }
222 
223 bool
mpd_search_add_uri_constraint(struct mpd_connection * connection,enum mpd_operator oper,const char * value)224 mpd_search_add_uri_constraint(struct mpd_connection *connection,
225 			      enum mpd_operator oper,
226 			      const char *value)
227 {
228 	return mpd_search_add_constraint(connection, oper, "file", value);
229 }
230 
231 bool
mpd_search_add_tag_constraint(struct mpd_connection * connection,enum mpd_operator oper,enum mpd_tag_type type,const char * value)232 mpd_search_add_tag_constraint(struct mpd_connection *connection,
233 			      enum mpd_operator oper,
234 			      enum mpd_tag_type type, const char *value)
235 {
236 	assert(connection != NULL);
237 	assert(value != NULL);
238 
239 	const char *strtype = mpd_tag_name(type);
240 	if (strtype == NULL) {
241 		mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT);
242 		mpd_error_message(&connection->error,
243 				  "invalid type specified");
244 		return false;
245 	}
246 
247 	return mpd_search_add_constraint(connection, oper, strtype, value);
248 }
249 
250 bool
mpd_search_add_any_tag_constraint(struct mpd_connection * connection,enum mpd_operator oper,const char * value)251 mpd_search_add_any_tag_constraint(struct mpd_connection *connection,
252 				  enum mpd_operator oper,
253 				  const char *value)
254 {
255 	return mpd_search_add_constraint(connection, oper, "any", value);
256 }
257 
258 bool
mpd_search_add_modified_since_constraint(struct mpd_connection * connection,enum mpd_operator oper,time_t value)259 mpd_search_add_modified_since_constraint(struct mpd_connection *connection,
260 					 enum mpd_operator oper,
261 					 time_t value)
262 {
263 	char buffer[64];
264 	if (!iso8601_datetime_format(buffer, sizeof(buffer), value)) {
265 		mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT);
266 		mpd_error_message(&connection->error,
267 				  "failed to format time stamp");
268 		return false;
269 	}
270 
271 	return mpd_search_add_constraint(connection, oper,
272 					 "modified-since", buffer);
273 }
274 
275 bool
mpd_search_add_expression(struct mpd_connection * connection,const char * expression)276 mpd_search_add_expression(struct mpd_connection *connection,
277 			  const char *expression)
278 {
279 	assert(connection != NULL);
280 	assert(expression != NULL);
281 
282 	char *arg = mpd_sanitize_arg(expression);
283 	if (arg == NULL) {
284 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
285 		return false;
286 	}
287 
288 	const size_t add_length = 2 + strlen(arg) + 1;
289 
290 	char *dest = mpd_search_prepare_append(connection, add_length);
291 	if (dest == NULL) {
292 		free(arg);
293 		return false;
294 	}
295 
296 	sprintf(dest, " \"%s\"", arg);
297 
298 	free(arg);
299 	return true;
300 }
301 
302 bool
mpd_search_add_group_tag(struct mpd_connection * connection,enum mpd_tag_type type)303 mpd_search_add_group_tag(struct mpd_connection *connection,
304 			 enum mpd_tag_type type)
305 {
306 	assert(connection != NULL);
307 
308 	const size_t size = 64;
309 	char *dest = mpd_search_prepare_append(connection, size);
310 	if (dest == NULL)
311 		return false;
312 
313 	snprintf(dest, size, " group %s", mpd_tag_name(type));
314 	return true;
315 }
316 
317 bool
mpd_search_add_sort_name(struct mpd_connection * connection,const char * name,bool descending)318 mpd_search_add_sort_name(struct mpd_connection *connection,
319 			 const char *name, bool descending)
320 {
321 	assert(connection != NULL);
322 
323 	const size_t size = 64;
324 	char *dest = mpd_search_prepare_append(connection, size);
325 	if (dest == NULL)
326 		return false;
327 
328 	snprintf(dest, size, " sort %s%s",
329 		 descending ? "-" : "",
330 		 name);
331 	return true;
332 }
333 
334 bool
mpd_search_add_sort_tag(struct mpd_connection * connection,enum mpd_tag_type type,bool descending)335 mpd_search_add_sort_tag(struct mpd_connection *connection,
336 			enum mpd_tag_type type, bool descending)
337 {
338 	return mpd_search_add_sort_name(connection,
339 					mpd_tag_name(type),
340 					descending);
341 }
342 
343 bool
mpd_search_add_window(struct mpd_connection * connection,unsigned start,unsigned end)344 mpd_search_add_window(struct mpd_connection *connection,
345 		      unsigned start, unsigned end)
346 {
347 	assert(connection != NULL);
348 	assert(start <= end);
349 
350 	const size_t size = 64;
351 	char *dest = mpd_search_prepare_append(connection, size);
352 	if (dest == NULL)
353 		return false;
354 
355 	snprintf(dest, size, " window %u:%u", start, end);
356 	return true;
357 }
358 
359 bool
mpd_search_add_position(struct mpd_connection * connection,unsigned position,enum mpd_position_whence whence)360 mpd_search_add_position(struct mpd_connection *connection,
361 			unsigned position, enum mpd_position_whence whence)
362 {
363 	assert(connection != NULL);
364 
365 	const size_t size = 64;
366 	char *dest = mpd_search_prepare_append(connection, size);
367 	if (dest == NULL)
368 		return false;
369 
370 	const char *whence_s = mpd_position_whence_char(whence);
371 
372 	snprintf(dest, size, " position %s%u", whence_s, position);
373 	return true;
374 }
375 
376 bool
mpd_search_commit(struct mpd_connection * connection)377 mpd_search_commit(struct mpd_connection *connection)
378 {
379 	assert(connection != NULL);
380 
381 	if (mpd_error_is_defined(&connection->error)) {
382 		mpd_search_cancel(connection);
383 		return false;
384 	}
385 
386 	if (connection->request == NULL) {
387 		mpd_error_code(&connection->error, MPD_ERROR_STATE);
388 		mpd_error_message(&connection->error,
389 				  "no search in progress");
390 		return false;
391 	}
392 
393 	bool success = mpd_send_command(connection, connection->request, NULL);
394 	free(connection->request);
395 	connection->request = NULL;
396 
397 	return success;
398 }
399 
400 void
mpd_search_cancel(struct mpd_connection * connection)401 mpd_search_cancel(struct mpd_connection *connection)
402 {
403 	assert(connection != NULL);
404 
405 	free(connection->request);
406 	connection->request = NULL;
407 }
408 
409 struct mpd_pair *
mpd_recv_pair_tag(struct mpd_connection * connection,enum mpd_tag_type type)410 mpd_recv_pair_tag(struct mpd_connection *connection, enum mpd_tag_type type)
411 {
412 	assert(connection != NULL);
413 
414 	const char *name = mpd_tag_name(type);
415 	if (name == NULL)
416 		return NULL;
417 
418 	return mpd_recv_pair_named(connection, name);
419 }
420 
421 bool
mpd_search_add_db_songs_to_playlist(struct mpd_connection * connection,const char * playlist_name)422 mpd_search_add_db_songs_to_playlist(struct mpd_connection *connection,
423 				    const char *playlist_name)
424 {
425 	assert(connection != NULL);
426 	assert(playlist_name != NULL);
427 
428 	if (mpd_error_is_defined(&connection->error))
429 		return false;
430 
431 	if (connection->request) {
432 		mpd_error_code(&connection->error, MPD_ERROR_STATE);
433 		mpd_error_message(&connection->error,
434 				  "search already in progress");
435 		return false;
436 	}
437 
438 	char *arg = mpd_sanitize_arg(playlist_name);
439 	if (arg == NULL) {
440 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
441 		return false;
442 	}
443 
444 	const size_t len = 13 + strlen(arg) + 2;
445 	connection->request = malloc(len);
446 	if (connection->request == NULL) {
447 		free(arg);
448 		mpd_error_code(&connection->error, MPD_ERROR_OOM);
449 		return false;
450 	}
451 
452 	snprintf(connection->request, len, "searchaddpl \"%s\" ", arg);
453 
454 	free(arg);
455 	return true;
456 }
457