1/* MiniDLNA media server
2 * Copyright (C) 2008-2009  Justin Maggard
3 *
4 * This file is part of MiniDLNA.
5 *
6 * MiniDLNA is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * MiniDLNA is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
17 */
18#include "config.h"
19
20#include <stdio.h>
21#include <ctype.h>
22#include <string.h>
23#include <stdlib.h>
24#include <sys/param.h>
25#include <sys/stat.h>
26#include <unistd.h>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <sys/sysinfo.h>
30#include <limits.h>
31#include <fcntl.h>
32#include <errno.h>
33
34#include "minidlnatypes.h"
35#include "upnpglobalvars.h"
36#include "utils.h"
37#include "log.h"
38
39int
40xasprintf(char **strp, char *fmt, ...)
41{
42	va_list args;
43	int ret;
44
45	va_start(args, fmt);
46	ret = vasprintf(strp, fmt, args);
47	va_end(args);
48	if( ret < 0 )
49	{
50		DPRINTF(E_WARN, L_GENERAL, "xasprintf: allocation failed\n");
51		*strp = NULL;
52	}
53	return ret;
54}
55
56int
57ends_with(const char * haystack, const char * needle)
58{
59	const char * end;
60	int nlen = strlen(needle);
61	int hlen = strlen(haystack);
62
63	if( nlen > hlen )
64		return 0;
65 	end = haystack + hlen - nlen;
66
67	return (strcasecmp(end, needle) ? 0 : 1);
68}
69
70char *
71trim(char *str)
72{
73	int i;
74	int len;
75
76	if (!str)
77		return(NULL);
78
79	len = strlen(str);
80	for (i=len-1; i >= 0 && isspace(str[i]); i--)
81	{
82		str[i] = '\0';
83		len--;
84	}
85	while (isspace(*str))
86	{
87		str++;
88		len--;
89	}
90
91	if (str[0] == '"' && str[len-1] == '"')
92	{
93		str[0] = '\0';
94		str[len-1] = '\0';
95		str++;
96	}
97
98	return str;
99}
100
101/* Find the first occurrence of p in s, where s is terminated by t */
102char *
103strstrc(const char *s, const char *p, const char t)
104{
105	char *endptr;
106	size_t slen, plen;
107
108	endptr = strchr(s, t);
109	if (!endptr)
110		return strstr(s, p);
111
112	plen = strlen(p);
113	slen = endptr - s;
114	while (slen >= plen)
115	{
116		if (*s == *p && strncmp(s+1, p+1, plen-1) == 0)
117			return (char*)s;
118		s++;
119		slen--;
120	}
121
122	return NULL;
123}
124
125char *
126strcasestrc(const char *s, const char *p, const char t)
127{
128	char *endptr;
129	size_t slen, plen;
130
131	endptr = strchr(s, t);
132	if (!endptr)
133		return strcasestr(s, p);
134
135	plen = strlen(p);
136	slen = endptr - s;
137	while (slen >= plen)
138	{
139		if (*s == *p && strncasecmp(s+1, p+1, plen-1) == 0)
140			return (char*)s;
141		s++;
142		slen--;
143	}
144
145	return NULL;
146}
147
148char *
149modifyString(char *string, const char *before, const char *after, int noalloc)
150{
151	int oldlen, newlen, chgcnt = 0;
152	char *s, *p;
153
154	/* If there is no match, just return */
155	s = strstr(string, before);
156	if (!s)
157		return string;
158
159	oldlen = strlen(before);
160	newlen = strlen(after);
161	if (newlen > oldlen)
162	{
163		if (noalloc)
164			return string;
165
166		while ((p = strstr(s, before)))
167		{
168			chgcnt++;
169			s = p + oldlen;
170		}
171		s = realloc(string, strlen(string)+((newlen-oldlen)*chgcnt)+1);
172		/* If we failed to realloc, return the original alloc'd string */
173		if( s )
174			string = s;
175		else
176			return string;
177	}
178
179	s = string;
180	while (s)
181	{
182		p = strstr(s, before);
183		if (!p)
184			return string;
185		memmove(p + newlen, p + oldlen, strlen(p + oldlen) + 1);
186		memcpy(p, after, newlen);
187		s = p + newlen;
188	}
189
190	return string;
191}
192
193char *
194unescape_tag(const char *tag, int force_alloc)
195{
196	char *esc_tag = NULL;
197
198	if (strchr(tag, '&') &&
199	    (strstr(tag, "&amp;") || strstr(tag, "&lt;") || strstr(tag, "&gt;") ||
200	     strstr(tag, "&quot;") || strstr(tag, "&apos;")))
201	{
202		esc_tag = strdup(tag);
203		esc_tag = modifyString(esc_tag, "&amp;", "&", 1);
204		esc_tag = modifyString(esc_tag, "&lt;", "<", 1);
205		esc_tag = modifyString(esc_tag, "&gt;", ">", 1);
206		esc_tag = modifyString(esc_tag, "&quot;", "\"", 1);
207		esc_tag = modifyString(esc_tag, "&apos;", "'", 1);
208	}
209	else if( force_alloc )
210		esc_tag = strdup(tag);
211
212	return esc_tag;
213}
214
215char *
216escape_tag(const char *tag, int force_alloc)
217{
218	char *esc_tag = NULL;
219
220	if( strchr(tag, '&') || strchr(tag, '<') || strchr(tag, '>') || strchr(tag, '"') )
221	{
222		esc_tag = strdup(tag);
223		esc_tag = modifyString(esc_tag, "&", "&amp;amp;", 0);
224		esc_tag = modifyString(esc_tag, "<", "&amp;lt;", 0);
225		esc_tag = modifyString(esc_tag, ">", "&amp;gt;", 0);
226		esc_tag = modifyString(esc_tag, "\"", "&amp;quot;", 0);
227	}
228	else if( force_alloc )
229		esc_tag = strdup(tag);
230
231	return esc_tag;
232}
233
234char *
235strip_ext(char *name)
236{
237	char *period;
238
239	period = strrchr(name, '.');
240	if (period)
241		*period = '\0';
242
243	return period;
244}
245
246/* Code basically stolen from busybox */
247int
248make_dir(char * path, mode_t mode)
249{
250	char * s = path;
251	char c;
252	struct stat st;
253
254	do {
255		c = '\0';
256
257		/* Before we do anything, skip leading /'s, so we don't bother
258		 * trying to create /. */
259		while (*s == '/')
260			++s;
261
262		/* Bypass leading non-'/'s and then subsequent '/'s. */
263		while (*s) {
264			if (*s == '/') {
265				do {
266					++s;
267				} while (*s == '/');
268				c = *s;     /* Save the current char */
269				*s = '\0';     /* and replace it with nul. */
270				break;
271			}
272			++s;
273		}
274
275		if (mkdir(path, mode) < 0) {
276			/* If we failed for any other reason than the directory
277			 * already exists, output a diagnostic and return -1.*/
278			if ((errno != EEXIST && errno != EISDIR)
279			    || (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))) {
280				DPRINTF(E_WARN, L_GENERAL, "make_dir: cannot create directory '%s'\n", path);
281				if (c)
282					*s = c;
283				return -1;
284			}
285		}
286	        if (!c)
287			return 0;
288
289		/* Remove any inserted nul from the path. */
290		*s = c;
291
292	} while (1);
293}
294
295/* Simple, efficient hash function from Daniel J. Bernstein */
296unsigned int
297DJBHash(uint8_t *data, int len)
298{
299	unsigned int hash = 5381;
300	unsigned int i = 0;
301
302	for(i = 0; i < len; data++, i++)
303	{
304		hash = ((hash << 5) + hash) + (*data);
305	}
306
307	return hash;
308}
309
310const char *
311mime_to_ext(const char * mime)
312{
313	switch( *mime )
314	{
315		/* Audio extensions */
316		case 'a':
317			if( strcmp(mime+6, "mpeg") == 0 )
318				return "mp3";
319			else if( strcmp(mime+6, "mp4") == 0 )
320				return "m4a";
321			else if( strcmp(mime+6, "x-ms-wma") == 0 )
322				return "wma";
323			else if( strcmp(mime+6, "x-flac") == 0 )
324				return "flac";
325			else if( strcmp(mime+6, "flac") == 0 )
326				return "flac";
327			else if( strcmp(mime+6, "x-wav") == 0 )
328				return "wav";
329			else if( strncmp(mime+6, "L16", 3) == 0 )
330				return "pcm";
331			else if( strcmp(mime+6, "3gpp") == 0 )
332				return "3gp";
333			else if( strcmp(mime, "application/ogg") == 0 )
334				return "ogg";
335			break;
336		case 'v':
337			if( strcmp(mime+6, "avi") == 0 )
338				return "avi";
339			else if( strcmp(mime+6, "divx") == 0 )
340				return "avi";
341			else if( strcmp(mime+6, "x-msvideo") == 0 )
342				return "avi";
343			else if( strcmp(mime+6, "mpeg") == 0 )
344				return "mpg";
345			else if( strcmp(mime+6, "mp4") == 0 )
346				return "mp4";
347			else if( strcmp(mime+6, "x-ms-wmv") == 0 )
348				return "wmv";
349			else if( strcmp(mime+6, "x-matroska") == 0 )
350				return "mkv";
351			else if( strcmp(mime+6, "x-mkv") == 0 )
352				return "mkv";
353			else if( strcmp(mime+6, "x-flv") == 0 )
354				return "flv";
355			else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
356				return "mpg";
357			else if( strcmp(mime+6, "quicktime") == 0 )
358				return "mov";
359			else if( strcmp(mime+6, "3gpp") == 0 )
360				return "3gp";
361			else if( strncmp(mime+6, "x-tivo-mpeg", 11) == 0 )
362				return "TiVo";
363			break;
364		case 'i':
365			if( strcmp(mime+6, "jpeg") == 0 )
366				return "jpg";
367			else if( strcmp(mime+6, "png") == 0 )
368				return "png";
369			break;
370		default:
371			break;
372	}
373	return "dat";
374}
375
376int
377is_video(const char * file)
378{
379	return (ends_with(file, ".mpg") || ends_with(file, ".mpeg")  ||
380		ends_with(file, ".avi") || ends_with(file, ".divx")  ||
381		ends_with(file, ".asf") || ends_with(file, ".wmv")   ||
382		ends_with(file, ".mp4") || ends_with(file, ".m4v")   ||
383		ends_with(file, ".mts") || ends_with(file, ".m2ts")  ||
384		ends_with(file, ".m2t") || ends_with(file, ".mkv")   ||
385		ends_with(file, ".vob") || ends_with(file, ".ts")    ||
386		ends_with(file, ".tp")  || ends_with(file, ".rmvb")  ||
387		ends_with(file, ".flv") || ends_with(file, ".xvid")  ||
388#ifdef TIVO_SUPPORT
389		ends_with(file, ".TiVo") ||
390#endif
391		ends_with(file, ".mov") || ends_with(file, ".3gp"));
392}
393
394int
395is_audio(const char * file)
396{
397	return (ends_with(file, ".mp3") || ends_with(file, ".flac") ||
398		ends_with(file, ".wma") || ends_with(file, ".asf")  ||
399		ends_with(file, ".fla") || ends_with(file, ".flc")  ||
400		ends_with(file, ".m4a") || ends_with(file, ".aac")  ||
401		ends_with(file, ".mp4") || ends_with(file, ".m4p")  ||
402		ends_with(file, ".wav") || ends_with(file, ".ogg")  ||
403		ends_with(file, ".pcm") || ends_with(file, ".3gp"));
404}
405
406int
407is_image(const char * file)
408{
409	return (ends_with(file, ".jpg") || ends_with(file, ".jpeg"));
410}
411
412int
413is_playlist(const char * file)
414{
415	return (ends_with(file, ".m3u") || ends_with(file, ".pls"));
416}
417
418int
419is_caption(const char * file)
420{
421	return (ends_with(file, ".srt") || ends_with(file, ".smi"));
422}
423
424int
425is_album_art(const char * name)
426{
427	struct album_art_name_s * album_art_name;
428
429	/* Check if this file name matches one of the default album art names */
430	for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next )
431	{
432		if( album_art_name->wildcard )
433		{
434			if( strncmp(album_art_name->name, name, strlen(album_art_name->name)) == 0 )
435				break;
436		}
437		else
438		{
439			if( strcmp(album_art_name->name, name) == 0 )
440				break;
441		}
442	}
443
444	return (album_art_name ? 1 : 0);
445}
446
447int
448resolve_unknown_type(const char * path, media_types dir_type)
449{
450	struct stat entry;
451	unsigned char type = TYPE_UNKNOWN;
452	char str_buf[PATH_MAX];
453	ssize_t len;
454
455	if( lstat(path, &entry) == 0 )
456	{
457		if( S_ISLNK(entry.st_mode) )
458		{
459			if( (len = readlink(path, str_buf, PATH_MAX-1)) > 0 )
460			{
461				str_buf[len] = '\0';
462				//DEBUG DPRINTF(E_DEBUG, L_GENERAL, "Checking for recursive symbolic link: %s (%s)\n", path, str_buf);
463				if( strncmp(path, str_buf, strlen(str_buf)) == 0 )
464				{
465					DPRINTF(E_DEBUG, L_GENERAL, "Ignoring recursive symbolic link: %s (%s)\n", path, str_buf);
466					return type;
467				}
468			}
469			stat(path, &entry);
470		}
471
472		if( S_ISDIR(entry.st_mode) )
473		{
474			type = TYPE_DIR;
475		}
476		else if( S_ISREG(entry.st_mode) )
477		{
478			switch( dir_type )
479			{
480				case ALL_MEDIA:
481					if( is_image(path) ||
482					    is_audio(path) ||
483					    is_video(path) ||
484					    is_playlist(path) )
485						type = TYPE_FILE;
486					break;
487				case TYPE_AUDIO:
488					if( is_audio(path) ||
489					    is_playlist(path) )
490						type = TYPE_FILE;
491					break;
492				case TYPE_VIDEO:
493					if( is_video(path) )
494						type = TYPE_FILE;
495					break;
496				case TYPE_IMAGES:
497					if( is_image(path) )
498						type = TYPE_FILE;
499					break;
500				default:
501					break;
502			}
503		}
504	}
505	return type;
506}
507
508long uptime(void)
509{
510	struct sysinfo info;
511	sysinfo(&info);
512
513	return info.uptime;
514}
515