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, "&") || strstr(tag, "<") || strstr(tag, ">") || 200 strstr(tag, """) || strstr(tag, "'"))) 201 { 202 esc_tag = strdup(tag); 203 esc_tag = modifyString(esc_tag, "&", "&", 1); 204 esc_tag = modifyString(esc_tag, "<", "<", 1); 205 esc_tag = modifyString(esc_tag, ">", ">", 1); 206 esc_tag = modifyString(esc_tag, """, "\"", 1); 207 esc_tag = modifyString(esc_tag, "'", "'", 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;", 0); 224 esc_tag = modifyString(esc_tag, "<", "&lt;", 0); 225 esc_tag = modifyString(esc_tag, ">", "&gt;", 0); 226 esc_tag = modifyString(esc_tag, "\"", "&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