#include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <ctype.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/file.h> #include <sys/time.h> #include <sys/param.h> #include <sys/stat.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <time.h> #include <signal.h> #include <errno.h> #include <pthread.h> #include <pwd.h> #include "config.h" #ifdef ENABLE_NLS #include <locale.h> #include <libintl.h> #endif #include "upnpglobalvars.h" #include "sql.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "minidlnapath.h" #include "getifaddr.h" #include "upnpsoap.h" #include "options.h" #include "utils.h" #include "minissdp.h" #include "minidlnatypes.h" #include "daemonize.h" #include "upnpevents.h" #include "scanner.h" #include "inotify.h" #include "log.h" #ifdef TIVO_SUPPORT #include "tivo_beacon.h" #include "tivo_utils.h" #endif #if SQLITE_VERSION_NUMBER < 3005001 # warning "Your SQLite3 library appears to be too old! Please use 3.5.1 or newer." # define sqlite3_threadsafe() 0 #endif static int OpenAndConfHTTPSocket(unsigned short port) { int s; int i = 1; struct sockaddr_in listenname; memset(&clients, 0, sizeof(struct client_cache_s)); if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { DPRINTF(E_ERROR, L_GENERAL, "socket(http): %s\n", strerror(errno)); return -1; } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { DPRINTF(E_WARN, L_GENERAL, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno)); } memset(&listenname, 0, sizeof(struct sockaddr_in)); listenname.sin_family = AF_INET; listenname.sin_port = htons(port); listenname.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(s, (struct sockaddr *)&listenname, sizeof(struct sockaddr_in)) < 0) { DPRINTF(E_ERROR, L_GENERAL, "bind(http): %s\n", strerror(errno)); close(s); return -1; } if(listen(s, 6) < 0) { DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno)); close(s); return -1; } return s; } static void sigterm(int sig) { signal(sig, SIG_IGN); DPRINTF(E_WARN, L_GENERAL, "received signal %d, good-bye\n", sig); quitting = 1; } static void set_startup_time(void) { startup_time = time(NULL); } static int parselanaddr(struct lan_addr_s * lan_addr, const char * str) { const char * p; int nbits = 24; int n; p = str; while(*p && *p != '/' && !isspace(*p)) p++; n = p - str; if(*p == '/') { nbits = atoi(++p); while(*p && !isspace(*p)) p++; } if(n>15) { DPRINTF(E_OFF, L_GENERAL, "Error parsing address/mask: %s\n", str); return -1; } memcpy(lan_addr->str, str, n); lan_addr->str[n] = '\0'; if(!inet_aton(lan_addr->str, &lan_addr->addr)) { DPRINTF(E_OFF, L_GENERAL, "Error parsing address: %s\n", str); return -1; } lan_addr->mask.s_addr = htonl(nbits ? (0xffffffff << (32 - nbits)) : 0); return 0; } static void getfriendlyname(char * buf, int len) { char * dot = NULL; char * hn = calloc(1, 256); int off; if( gethostname(hn, 256) == 0 ) { strncpyt(buf, hn, len); dot = strchr(buf, '.'); if( dot ) *dot = '\0'; } else { strcpy(buf, "Unknown"); } free(hn); off = strlen(buf); off += snprintf(buf+off, len-off, ": "); #ifdef READYNAS FILE * info; char ibuf[64], *key, *val; snprintf(buf+off, len-off, "ReadyNAS"); info = fopen("/proc/sys/dev/boot/info", "r"); if( !info ) return; while( (val = fgets(ibuf, 64, info)) != NULL ) { key = strsep(&val, ": \t"); val = trim(val); if( strcmp(key, "model") == 0 ) { snprintf(buf+off, len-off, "%s", val); key = strchr(val, ' '); if( key ) { strncpyt(modelnumber, key+1, MODELNUMBER_MAX_LEN); *key = '\0'; } snprintf(modelname, MODELNAME_MAX_LEN, "Windows Media Connect compatible (%s)", val); } else if( strcmp(key, "serial") == 0 ) { strncpyt(serialnumber, val, SERIALNUMBER_MAX_LEN); if( serialnumber[0] == '\0' ) { char mac_str[13]; if( getsyshwaddr(mac_str, sizeof(mac_str)) == 0 ) strcpy(serialnumber, mac_str); else strcpy(serialnumber, "0"); } break; } } fclose(info); memcpy(pnpx_hwid+4, "01F2", 4); if( strcmp(modelnumber, "NVX") == 0 ) memcpy(pnpx_hwid+17, "0101", 4); else if( strcmp(modelnumber, "Pro") == 0 || strcmp(modelnumber, "Pro 6") == 0 || strncmp(modelnumber, "Ultra 6", 7) == 0 ) memcpy(pnpx_hwid+17, "0102", 4); else if( strcmp(modelnumber, "Pro 2") == 0 || strncmp(modelnumber, "Ultra 2", 7) == 0 ) memcpy(pnpx_hwid+17, "0103", 4); else if( strcmp(modelnumber, "Pro 4") == 0 || strncmp(modelnumber, "Ultra 4", 7) == 0 ) memcpy(pnpx_hwid+17, "0104", 4); else if( strcmp(modelnumber+1, "100") == 0 ) memcpy(pnpx_hwid+17, "0105", 4); else if( strcmp(modelnumber+1, "200") == 0 ) memcpy(pnpx_hwid+17, "0106", 4); else if( strcmp(modelnumber, "Duo v2") == 0 ) memcpy(pnpx_hwid+17, "0108", 4); else if( strcmp(modelnumber, "NV+ v2") == 0 ) memcpy(pnpx_hwid+17, "0109", 4); #else char * logname; logname = getenv("LOGNAME"); #ifndef STATIC if( !logname ) { struct passwd * pwent; pwent = getpwuid(getuid()); if( pwent ) logname = pwent->pw_name; } #endif snprintf(buf+off, len-off, "%s", logname?logname:"Unknown"); #endif } static int open_db(void) { char path[PATH_MAX]; int new_db = 0; snprintf(path, sizeof(path), "%s/files.db", db_path); if( access(path, F_OK) != 0 ) { new_db = 1; make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); } if( sqlite3_open(path, &db) != SQLITE_OK ) { DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to open sqlite database! Exiting...\n"); } sqlite3_busy_timeout(db, 5000); sql_exec(db, "pragma page_size = 4096"); sql_exec(db, "pragma journal_mode = OFF"); sql_exec(db, "pragma synchronous = OFF;"); sql_exec(db, "pragma default_cache_size = 8192;"); return new_db; } static int init(int argc, char * * argv) { int i; int pid; int debug_flag = 0; int verbose_flag = 0; int options_flag = 0; struct sigaction sa; const char * presurl = NULL; const char * optionsfile = "/etc/minidlna.conf"; char mac_str[13]; char * string, * word; enum media_types type; char * path; char buf[PATH_MAX]; char ip_addr[INET_ADDRSTRLEN + 3] = {'\0'}; char log_str[72] = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"; char *log_level = NULL; for(i=2; i<argc; i++) { if(0 == strcmp(argv[i-1], "-f")) { optionsfile = argv[i]; options_flag = 1; break; } } if( getsyshwaddr(mac_str, sizeof(mac_str)) < 0 ) { DPRINTF(E_OFF, L_GENERAL, "No MAC address found. Falling back to generic UUID.\n"); strcpy(mac_str, "554e4b4e4f57"); } strcpy(uuidvalue+5, "4d696e69-444c-164e-9d41-"); strncat(uuidvalue, mac_str, 12); getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN); runtime_vars.port = -1; runtime_vars.notify_interval = 895; runtime_vars.root_container = NULL; if(readoptionsfile(optionsfile) < 0) { if(access(optionsfile, F_OK) == 0 || options_flag) DPRINTF(E_FATAL, L_GENERAL, "Error reading configuration file %s\n", optionsfile); } else { for(i=0; i<num_options; i++) { switch(ary_options[i].id) { case UPNPIFNAME: for( string = ary_options[i].value; (word = strtok(string, ",")); string = NULL ) { if(n_lan_addr < MAX_LAN_ADDR) { if(getifaddr(word, ip_addr, sizeof(ip_addr)) >= 0) { if( *ip_addr && parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0 ) if(n_lan_addr < MAX_LAN_ADDR) n_lan_addr++; } } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, word); } } break; case UPNPLISTENING_IP: if(n_lan_addr < MAX_LAN_ADDR) { if(parselanaddr(&lan_addr[n_lan_addr], ary_options[i].value) == 0) n_lan_addr++; } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, ary_options[i].value); } break; case UPNPPORT: runtime_vars.port = atoi(ary_options[i].value); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: runtime_vars.notify_interval = atoi(ary_options[i].value); break; case UPNPSERIAL: strncpyt(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); break; case UPNPMODEL_NAME: strncpyt(modelname, ary_options[i].value, MODELNAME_MAX_LEN); break; case UPNPMODEL_NUMBER: strncpyt(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); break; case UPNPFRIENDLYNAME: strncpyt(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); break; case UPNPMEDIADIR: type = ALL_MEDIA; path = ary_options[i].value; if( *path && (path[1] == ',') && (access(path, F_OK) != 0) ) { switch( *path ) { case 'A': case 'a': type = AUDIO_ONLY; break; case 'V': case 'v': type = VIDEO_ONLY; break; case 'P': case 'p': type = IMAGES_ONLY; break; default: DPRINTF(E_FATAL, L_GENERAL, "Media directory entry not understood [%s]\n", ary_options[i].value); break; } path += 2; } path = realpath(path, buf); if( !path || access(path, F_OK) != 0 ) { DPRINTF(E_ERROR, L_GENERAL, "Media directory \"%s\" not accessible [%s]\n", ary_options[i].value, strerror(errno)); break; } struct media_dir_s * this_dir = calloc(1, sizeof(struct media_dir_s)); this_dir->path = strdup(path); this_dir->type = type; if( !media_dirs ) { media_dirs = this_dir; } else { struct media_dir_s * all_dirs = media_dirs; while( all_dirs->next ) all_dirs = all_dirs->next; all_dirs->next = this_dir; } break; case UPNPALBUMART_NAMES: for( string = ary_options[i].value; (word = strtok(string, "/")); string = NULL ) { struct album_art_name_s * this_name = calloc(1, sizeof(struct album_art_name_s)); int len = strlen(word); if( word[len-1] == '*' ) { word[len-1] = '\0'; this_name->wildcard = 1; } this_name->name = strdup(word); if( !album_art_names ) { album_art_names = this_name; } else { struct album_art_name_s * all_names = album_art_names; while( all_names->next ) all_names = all_names->next; all_names->next = this_name; } } break; case UPNPDBDIR: path = realpath(ary_options[i].value, buf); if( !path ) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if( access(path, F_OK) != 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Database path not accessible! [%s]\n", path); break; } strncpyt(db_path, path, PATH_MAX); break; case UPNPLOGDIR: path = realpath(ary_options[i].value, buf); if( !path ) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if( access(path, F_OK) != 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Log path not accessible! [%s]\n", path); break; } strncpyt(log_path, path, PATH_MAX); break; case UPNPLOGLEVEL: log_level = ary_options[i].value; break; case UPNPINOTIFY: if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) ) CLEARFLAG(INOTIFY_MASK); break; case ENABLE_TIVO: if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) SETFLAG(TIVO_MASK); break; case ENABLE_DLNA_STRICT: if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) SETFLAG(DLNA_STRICT_MASK); break; case ROOT_CONTAINER: switch( ary_options[i].value[0] ) { case '.': runtime_vars.root_container = NULL; break; case 'B': case 'b': runtime_vars.root_container = BROWSEDIR_ID; break; case 'M': case 'm': runtime_vars.root_container = MUSIC_ID; break; case 'V': case 'v': runtime_vars.root_container = VIDEO_ID; break; case 'P': case 'p': runtime_vars.root_container = IMAGE_ID; break; default: DPRINTF(E_ERROR, L_GENERAL, "Invalid root container! [%s]\n", ary_options[i].value); break; } break; case UPNPMINISSDPDSOCKET: minissdpdsocketpath = ary_options[i].value; break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); } } } if( log_path[0] == '\0' ) { if( db_path[0] == '\0' ) strncpyt(log_path, DEFAULT_LOG_PATH, PATH_MAX); else strncpyt(log_path, db_path, PATH_MAX); } if( db_path[0] == '\0' ) strncpyt(db_path, DEFAULT_DB_PATH, PATH_MAX); for(i=1; i<argc; i++) { if(argv[i][0]!='-') { DPRINTF(E_FATAL, L_GENERAL, "Unknown option: %s\n", argv[i]); } else if(strcmp(argv[i], "--help")==0) { runtime_vars.port = 0; break; } else switch(argv[i][1]) { case 't': if(i+1 < argc) runtime_vars.notify_interval = atoi(argv[++i]); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 's': if(i+1 < argc) strncpyt(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'm': if(i+1 < argc) strncpyt(modelnumber, argv[++i], MODELNUMBER_MAX_LEN); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'p': if(i+1 < argc) runtime_vars.port = atoi(argv[++i]); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'P': if(i+1 < argc) { if (argv[++i][0] != '/') DPRINTF(E_FATAL, L_GENERAL, "Option -%c requires an absolute filename.\n", argv[i-1][1]); else pidfilename = argv[i]; } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'd': debug_flag = 1; case 'v': verbose_flag = 1; break; case 'L': SETFLAG(NO_PLAYLIST_MASK); break; case 'w': if(i+1 < argc) presurl = argv[++i]; else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'a': if(i+1 < argc) { int address_already_there = 0; int j; i++; for(j=0; j<n_lan_addr; j++) { struct lan_addr_s tmpaddr; parselanaddr(&tmpaddr, argv[i]); if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if(address_already_there) break; if(n_lan_addr < MAX_LAN_ADDR) { if(parselanaddr(&lan_addr[n_lan_addr], argv[i]) == 0) n_lan_addr++; } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); } } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'i': if(i+1 < argc) { int address_already_there = 0; int j; i++; if( getifaddr(argv[i], ip_addr, sizeof(ip_addr)) < 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Required network interface '%s' not found.\n", argv[i]); } for(j=0; j<n_lan_addr; j++) { struct lan_addr_s tmpaddr; parselanaddr(&tmpaddr, ip_addr); if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if(address_already_there) break; if(n_lan_addr < MAX_LAN_ADDR) { if(parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0) n_lan_addr++; } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); } } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'f': i++; break; case 'h': runtime_vars.port = 0; break; case 'R': snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path, db_path); if( system(buf) != 0 ) DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache. EXITING\n"); break; case 'V': printf("Version " MINIDLNA_VERSION "\n"); exit(0); break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option: %s\n", argv[i]); } } if( n_lan_addr < 1 ) { if( (getsysaddr(ip_addr, sizeof(ip_addr)) < 0) && (getifaddr("eth0", ip_addr, sizeof(ip_addr)) < 0) && (getifaddr("eth1", ip_addr, sizeof(ip_addr)) < 0) ) { DPRINTF(E_OFF, L_GENERAL, "No IP address automatically detected!\n"); } if( *ip_addr && parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0 ) { n_lan_addr++; } } if( (n_lan_addr==0) || (runtime_vars.port<=0) ) { DPRINTF(E_ERROR, L_GENERAL, "Usage:\n\t" "%s [-d] [-v] [-f config_file]\n" "\t\t[-a listening_ip] [-p port]\n" "\t\t[-s serial] [-m model_number] \n" "\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-w url] [-R] [-V] [-h]\n" "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" "\tDefault pid file is %s.\n" "\tWith -d minidlna will run in debug mode (not daemonize).\n" "\t-w sets the presentation url. Default is http address on port 80\n" "\t-h displays this text\n" "\t-R forces a full rescan\n" "\t-L do note create playlists\n" "\t-V print the version number\n", argv[0], pidfilename); return 1; } if( verbose_flag ) { strcpy(log_str+65, "debug"); log_level = log_str; } else if( !log_level ) { log_level = log_str; } if(debug_flag) { pid = getpid(); log_init(NULL, log_level); } else { pid = daemonize(); #ifdef READYNAS log_init("/var/log/upnp-av.log", log_level); #else if( access(db_path, F_OK) != 0 ) make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); sprintf(buf, "%s/minidlna.log", log_path); log_init(buf, log_level); #endif } if (checkforrunning(pidfilename) < 0) { DPRINTF(E_ERROR, L_GENERAL, "MiniDLNA is already running. EXITING.\n"); return 1; } set_startup_time(); if (presurl) strncpyt(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); else strcpy(presentationurl, "/"); memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; if (sigaction(SIGTERM, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", SIGTERM); if (sigaction(SIGINT, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", SIGINT); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", SIGPIPE); if (writepidfile(pidfilename, pid) != 0) pidfilename = NULL; return 0; } int main(int argc, char * * argv) { int i; int sudp = -1, shttpl = -1; int snotify[MAX_LAN_ADDR]; LIST_HEAD(httplisthead, upnphttp) upnphttphead; struct upnphttp * e = 0; struct upnphttp * next; fd_set readset; fd_set writeset; struct timeval timeout, timeofday, lastnotifytime = {0, 0}; time_t lastupdatetime = 0; int max_fd = -1; int last_changecnt = 0; pid_t scanner_pid = 0; pthread_t inotify_thread = 0; struct media_dir_s *media_path, *last_path; struct album_art_name_s *art_names, *last_name; #ifdef TIVO_SUPPORT unsigned short int beacon_interval = 5; int sbeacon = -1; struct sockaddr_in tivo_bcast; struct timeval lastbeacontime = {0, 0}; #endif for (i = 0; i < L_MAX; i++) log_level[i] = E_WARN; #ifdef ENABLE_NLS setlocale(LC_MESSAGES, ""); setlocale(LC_CTYPE, "en_US.utf8"); DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"))); textdomain("minidlna"); #endif if (init(argc, argv) != 0) return 1; #ifdef READYNAS DPRINTF(E_WARN, L_GENERAL, "Starting " SERVER_NAME " version " MINIDLNA_VERSION ".\n"); unlink("/ramfs/.upnp-av_scan"); #else DPRINTF(E_WARN, L_GENERAL, "Starting " SERVER_NAME " version " MINIDLNA_VERSION " [SQLite %s].\n", sqlite3_libversion()); if( !sqlite3_threadsafe() ) { DPRINTF(E_ERROR, L_GENERAL, "SQLite library is not threadsafe! " "Scanning must be finished before file serving can begin, " "and inotify will be disabled.\n"); } if( sqlite3_libversion_number() < 3005001 ) { DPRINTF(E_WARN, L_GENERAL, "SQLite library is old. Please use version 3.5.1 or newer.\n"); } #endif LIST_INIT(&upnphttphead); if( open_db() == 0 ) { updateID = sql_get_int_field(db, "SELECT UPDATE_ID from SETTINGS"); } i = db_upgrade(db); if( i != 0 ) { if( i < 0 ) { DPRINTF(E_WARN, L_GENERAL, "Creating new database at %s/files.db\n", db_path); } else { DPRINTF(E_WARN, L_GENERAL, "Database version mismatch; need to recreate...\n"); } sqlite3_close(db); char *cmd; i = asprintf(&cmd, "rm -rf %s/files.db %s/art_cache", db_path, db_path); if( i > 0 ) i = system(cmd); else cmd = NULL; if( i != 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache! Exiting...\n"); } free(cmd); open_db(); if( CreateDatabase() != 0 ) { DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database! Exiting...\n"); } #if USE_FORK scanning = 1; sqlite3_close(db); scanner_pid = fork(); open_db(); if( !scanner_pid ) { start_scanner(); sqlite3_close(db); media_path = media_dirs; art_names = album_art_names; while( media_path ) { free(media_path->path); last_path = media_path; media_path = media_path->next; free(last_path); } while( art_names ) { free(art_names->name); last_name = art_names; art_names = art_names->next; free(last_name); } freeoptions(); exit(EXIT_SUCCESS); } #else start_scanner(); #endif } signal(SIGCHLD, SIG_IGN); #ifdef HAVE_INOTIFY if( sqlite3_threadsafe() && sqlite3_libversion_number() >= 3005001 && GETFLAG(INOTIFY_MASK) && pthread_create(&inotify_thread, NULL, start_inotify, NULL) ) { DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify. EXITING\n"); } #endif sudp = OpenAndConfSSDPReceiveSocket(n_lan_addr, lan_addr); if(sudp < 0) { DPRINTF(E_INFO, L_GENERAL, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd\n"); if(SubmitServicesToMiniSSDPD(lan_addr[0].str, runtime_vars.port) < 0) { DPRINTF(E_FATAL, L_GENERAL, "Failed to connect to MiniSSDPd. EXITING"); return 1; } } shttpl = OpenAndConfHTTPSocket(runtime_vars.port); if(shttpl < 0) { DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n"); } DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port); if(OpenAndConfSSDPNotifySockets(snotify) < 0) { DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending SSDP notify " "messages. EXITING\n"); } #ifdef TIVO_SUPPORT if( GETFLAG(TIVO_MASK) ) { DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n"); if( sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL) != SQLITE_OK ) { DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n"); } sbeacon = OpenAndConfTivoBeaconSocket(); if(sbeacon < 0) { DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify " "messages. EXITING\n"); } tivo_bcast.sin_family = AF_INET; tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); tivo_bcast.sin_port = htons(2190); } else { sbeacon = -1; } #endif SendSSDPGoodbye(snotify, n_lan_addr); while(!quitting) { if(gettimeofday(&timeofday, 0) < 0) { DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { if(timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) { SendSSDPNotifies2(snotify, (unsigned short)runtime_vars.port, (runtime_vars.notify_interval << 1)+10); memcpy(&lastnotifytime, &timeofday, sizeof(struct timeval)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval - timeofday.tv_sec; if(timeofday.tv_usec > lastnotifytime.tv_usec) { timeout.tv_usec = 1000000 + lastnotifytime.tv_usec - timeofday.tv_usec; timeout.tv_sec--; } else { timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec; } } #ifdef TIVO_SUPPORT if( GETFLAG(TIVO_MASK) ) { if(timeofday.tv_sec >= (lastbeacontime.tv_sec + beacon_interval)) { sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); memcpy(&lastbeacontime, &timeofday, sizeof(struct timeval)); if( timeout.tv_sec > beacon_interval ) { timeout.tv_sec = beacon_interval; timeout.tv_usec = 0; } if( beacon_interval == 5 && (timeofday.tv_sec - startup_time) > 60 ) { beacon_interval = 60; } } else if( timeout.tv_sec > (lastbeacontime.tv_sec + beacon_interval + 1 - timeofday.tv_sec) ) { timeout.tv_sec = lastbeacontime.tv_sec + beacon_interval - timeofday.tv_sec; } } #endif } if( scanning ) { if( !scanner_pid || kill(scanner_pid, 0) ) { scanning = 0; updateID++; } } FD_ZERO(&readset); if (sudp >= 0) { FD_SET(sudp, &readset); max_fd = MAX(max_fd, sudp); } if (shttpl >= 0) { FD_SET(shttpl, &readset); max_fd = MAX(max_fd, shttpl); } #ifdef TIVO_SUPPORT if (sbeacon >= 0) { FD_SET(sbeacon, &readset); max_fd = MAX(max_fd, sbeacon); } #endif i = 0; for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if((e->socket >= 0) && (e->state <= 2)) { FD_SET(e->socket, &readset); max_fd = MAX(max_fd, e->socket); i++; } } #ifdef DEBUG if(i > 1) { DPRINTF(E_DEBUG, L_GENERAL, "%d active incoming HTTP connections\n", i); } #endif FD_ZERO(&writeset); upnpevents_selectfds(&readset, &writeset, &max_fd); if(select(max_fd+1, &readset, &writeset, 0, &timeout) < 0) { if(quitting) goto shutdown; DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno)); DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n"); } upnpevents_processfds(&readset, &writeset); if(sudp >= 0 && FD_ISSET(sudp, &readset)) { ProcessSSDPRequest(sudp, (unsigned short)runtime_vars.port); } #ifdef TIVO_SUPPORT if(sbeacon >= 0 && FD_ISSET(sbeacon, &readset)) { ProcessTiVoBeacon(sbeacon); } #endif if( i && (timeofday.tv_sec >= (lastupdatetime + 2)) ) { if( scanning || sqlite3_total_changes(db) != last_changecnt ) { updateID++; last_changecnt = sqlite3_total_changes(db); upnp_event_var_change_notify(EContentDirectory); lastupdatetime = timeofday.tv_sec; } } for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if( (e->socket >= 0) && (e->state <= 2) &&(FD_ISSET(e->socket, &readset)) ) { Process_upnphttp(e); } } if(shttpl >= 0 && FD_ISSET(shttpl, &readset)) { int shttp; socklen_t clientnamelen; struct sockaddr_in clientname; clientnamelen = sizeof(struct sockaddr_in); shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); if(shttp<0) { DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno)); } else { struct upnphttp * tmp = 0; DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port) ); tmp = New_upnphttp(shttp); if(tmp) { tmp->clientaddr = clientname.sin_addr; LIST_INSERT_HEAD(&upnphttphead, tmp, entries); } else { DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n"); close(shttp); } } } for(e = upnphttphead.lh_first; e != NULL; ) { next = e->entries.le_next; if(e->state >= 100) { LIST_REMOVE(e, entries); Delete_upnphttp(e); } e = next; } } shutdown: if( scanning && scanner_pid ) kill(scanner_pid, 9); while(upnphttphead.lh_first != NULL) { e = upnphttphead.lh_first; LIST_REMOVE(e, entries); Delete_upnphttp(e); } if (sudp >= 0) close(sudp); if (shttpl >= 0) close(shttpl); #ifdef TIVO_SUPPORT if (sbeacon >= 0) close(sbeacon); #endif if(SendSSDPGoodbye(snotify, n_lan_addr) < 0) DPRINTF(E_ERROR, L_GENERAL, "Failed to broadcast good-bye notifications\n"); for(i=0; i<n_lan_addr; i++) close(snotify[i]); if( inotify_thread ) pthread_join(inotify_thread, NULL); sql_exec(db, "UPDATE SETTINGS set UPDATE_ID = %u", updateID); sqlite3_close(db); upnpevents_removeSubscribers(); media_path = media_dirs; art_names = album_art_names; while( media_path ) { free(media_path->path); last_path = media_path; media_path = media_path->next; free(last_path); } while( art_names ) { free(art_names->name); last_name = art_names; art_names = art_names->next; free(last_name); } if(pidfilename && unlink(pidfilename) < 0) DPRINTF(E_ERROR, L_GENERAL, "Failed to remove pidfile %s: %s\n", pidfilename, strerror(errno)); freeoptions(); exit(EXIT_SUCCESS); } #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/param.h> #include <ctype.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <arpa/inet.h> #include <sys/time.h> #include <sys/resource.h> #include "config.h" #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "minidlnapath.h" #include "upnpsoap.h" #include "upnpevents.h" #include "utils.h" #include "getifaddr.h" #include "image_utils.h" #include "log.h" #include "sql.h" #include <libexif/exif-loader.h> #ifdef TIVO_SUPPORT #include "tivo_utils.h" #include "tivo_commands.h" #endif #include "sendfile.h" #define MAX_BUFFER_SIZE 2147483647 #define MIN_BUFFER_SIZE 65536 #include "icons.c" struct upnphttp * New_upnphttp(int s) { struct upnphttp * ret; if(s<0) return NULL; ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); if(ret == NULL) return NULL; memset(ret, 0, sizeof(struct upnphttp)); ret->socket = s; return ret; } void CloseSocket_upnphttp(struct upnphttp * h) { if(close(h->socket) < 0) { DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->socket, strerror(errno)); } h->socket = -1; h->state = 100; } void Delete_upnphttp(struct upnphttp * h) { if(h) { if(h->socket >= 0) CloseSocket_upnphttp(h); free(h->req_buf); free(h->res_buf); free(h); } } int SearchClientCache(struct in_addr addr, int quiet) { int i; for( i=0; i<CLIENT_CACHE_SLOTS; i++ ) { if( clients[i].addr.s_addr == addr.s_addr ) { if( (time(NULL) - clients[i].age) > 3600 ) { unsigned char mac[6]; if( get_remote_mac(addr, mac) == 0 && memcmp(mac, clients[i].mac, 6) == 0 ) { clients[i].age = time(NULL); } else { memset(&clients[i], 0, sizeof(struct client_cache_s)); return -1; } } if( !quiet ) DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n", clients[i].type, i); return i; } } return -1; } static void ParseHttpHeaders(struct upnphttp * h) { char * line; char * colon; char * p; int n; line = h->req_buf; while(line < (h->req_buf + h->req_contentoff)) { colon = strchr(line, ':'); if(colon) { if(strncasecmp(line, "Content-Length", 14)==0) { p = colon; while(*p < '0' || *p > '9') p++; h->req_contentlen = atoi(p); } else if(strncasecmp(line, "SOAPAction", 10)==0) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>=' ') { n++; } if((p[0] == '"' && p[n-1] == '"') || (p[0] == '\'' && p[n-1] == '\'')) { p++; n -= 2; } h->req_soapAction = p; h->req_soapActionLen = n; } else if(strncasecmp(line, "Callback", 8)==0) { p = colon; while(*p != '<' && *p != '\r' ) p++; n = 0; while(p[n] != '>' && p[n] != '\r' ) n++; h->req_Callback = p + 1; h->req_CallbackLen = MAX(0, n - 1); if(strncmp(h->req_Callback, "http: h->req_Callback = NULL; } else if(strncasecmp(line, "SID", 3)==0) { for(p=line+3;p<colon;p++) { if(!isspace(*p)) { p = NULL; break; } } if(p) { p = colon + 1; while(isspace(*p)) p++; n = 0; while(!isspace(p[n])) n++; h->req_SID = p; h->req_SIDLen = n; } } else if(strncasecmp(line, "NT", 2)==0) { p = colon + 1; while(isspace(*p)) p++; n = 0; while(!isspace(p[n])) n++; h->req_NT = p; h->req_NTLen = n; } else if(strncasecmp(line, "Timeout", 7)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Second-", 7)==0) { h->req_Timeout = atoi(p+7); } } else if(strncasecmp(line, "Range", 5)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "bytes=", 6)==0) { h->reqflags |= FLAG_RANGE; h->req_RangeStart = strtoll(p+6, &colon, 10); h->req_RangeEnd = colon ? atoll(colon+1) : 0; DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n", h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); } } else if(strncasecmp(line, "Host", 4)==0) { int i; h->reqflags |= FLAG_HOST; p = colon + 1; while(isspace(*p)) p++; for(n = 0; n<n_lan_addr; n++) { for(i=0; lan_addr[n].str[i]; i++) { if(lan_addr[n].str[i] != p[i]) break; } if(!lan_addr[n].str[i]) { h->iface = n; break; } } } else if(strncasecmp(line, "User-Agent", 10)==0) { char *s; if( h->req_client ) goto next_header; p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Xbox/", 5)==0) { h->req_client = EXbox; h->reqflags |= FLAG_MIME_AVI_AVI; h->reqflags |= FLAG_MS_PFS; } else if(strncmp(p, "PLAYSTATION", 11)==0) { h->req_client = EPS3; h->reqflags |= FLAG_DLNA; h->reqflags |= FLAG_MIME_AVI_DIVX; } else if((s=strstrc(p, "SEC_HHP_", '\r'))) { h->req_client = ESamsungSeriesC; h->reqflags |= FLAG_SAMSUNG; h->reqflags |= FLAG_DLNA; h->reqflags |= FLAG_NO_RESIZE; if(strstrc(s+8, "TV", '\r')) h->reqflags |= FLAG_SAMSUNG_TV; } else if(strncmp(p, "SamsungWiselinkPro", 18)==0) { h->req_client = ESamsungSeriesA; h->reqflags |= FLAG_SAMSUNG; h->reqflags |= FLAG_DLNA; h->reqflags |= FLAG_NO_RESIZE; } else if(strstrc(p, "bridgeCo-DMP/3", '\r')) { h->req_client = EDenonReceiver; h->reqflags |= FLAG_DLNA; } else if(strstrc(p, "fbxupnpav/", '\r')) { h->req_client = EFreeBox; } else if(strncmp(p, "SMP8634", 7)==0) { h->req_client = EPopcornHour; h->reqflags |= FLAG_MIME_FLAC_FLAC; } else if(strstrc(p, "Microsoft-IPTV-Client", '\r')) { h->req_client = EMediaRoom; h->reqflags |= FLAG_MS_PFS; } else if(strstrc(p, "LGE_DLNA_SDK", '\r')) { h->req_client = ELGDevice; h->reqflags |= FLAG_DLNA; } else if(strncmp(p, "Verismo,", 8)==0) { h->req_client = ENetgearEVA2000; h->reqflags |= FLAG_MS_PFS; } else if(strstrc(p, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r')) { h->req_client = EToshibaTV; h->reqflags |= FLAG_DLNA; } else if(strstrc(p, "DLNADOC/1.50", '\r')) { h->req_client = EStandardDLNA150; h->reqflags |= FLAG_DLNA; h->reqflags |= FLAG_MIME_AVI_AVI; } } else if(strncasecmp(line, "X-AV-Client-Info", 16)==0) { if( h->req_client && h->req_client < EStandardDLNA150 ) goto next_header; p = colon + 1; while(isspace(*p)) p++; if(strstrc(p, "PLAYSTATION 3", '\r')) { h->req_client = EPS3; h->reqflags |= FLAG_DLNA; h->reqflags |= FLAG_MIME_AVI_DIVX; } else if(strstrc(p, "Blu-ray Disc Player", '\r') || strstrc(p, "BLU-RAY HOME THEATRE SYSTEM", '\r') || strstrc(p, "Media Player", '\r')) { h->req_client = ESonyBDP; h->reqflags |= FLAG_DLNA; } else if(strstrc(p, "BRAVIA", '\r') || strstrc(p, "INTERNET TV", '\r')) { h->req_client = ESonyBravia; h->reqflags |= FLAG_DLNA; } } else if(strncasecmp(line, "Transfer-Encoding", 17)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "chunked", 7)==0) { h->reqflags |= FLAG_CHUNKED; } } else if(strncasecmp(line, "Accept-Language", 15)==0) { h->reqflags |= FLAG_LANGUAGE; } else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0) { p = colon + 1; while(isspace(*p)) p++; if( (*p != '1') || !isspace(p[1]) ) h->reqflags |= FLAG_INVALID_REQ; } else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0) { h->reqflags |= FLAG_TIMESEEK; } else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0) { h->reqflags |= FLAG_PLAYSPEED; } else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0) { h->reqflags |= FLAG_REALTIMEINFO; } else if(strncasecmp(line, "getAvailableSeekRange.dlna.org", 21)==0) { p = colon + 1; while(isspace(*p)) p++; if( (*p != '1') || !isspace(p[1]) ) h->reqflags |= FLAG_INVALID_REQ; } else if(strncasecmp(line, "transferMode.dlna.org", 21)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Streaming", 9)==0) { h->reqflags |= FLAG_XFERSTREAMING; } if(strncasecmp(p, "Interactive", 11)==0) { h->reqflags |= FLAG_XFERINTERACTIVE; } if(strncasecmp(p, "Background", 10)==0) { h->reqflags |= FLAG_XFERBACKGROUND; } } else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0) { h->reqflags |= FLAG_CAPTION; } else if(strncasecmp(line, "FriendlyName", 12)==0) { p = colon + 1; while(isspace(*p)) p++; if(strstrc(p, "LIFETAB", '\r')) { h->req_client = ELifeTab; h->reqflags |= FLAG_MS_PFS; } } } next_header: while(!(line[0] == '\r' && line[1] == '\n')) line++; line += 2; } if( h->reqflags & FLAG_CHUNKED ) { char *endptr; h->req_chunklen = -1; if( h->req_buflen <= h->req_contentoff ) return; while( (line < (h->req_buf + h->req_buflen)) && (h->req_chunklen = strtol(line, &endptr, 16)) && (endptr != line) ) { while(!(endptr[0] == '\r' && endptr[1] == '\n')) { endptr++; } line = endptr+h->req_chunklen+2; } if( endptr == line ) { h->req_chunklen = -1; return; } } n = SearchClientCache(h->clientaddr, 0); if( h->req_client ) { if( n < 0 ) { for( n=0; n<CLIENT_CACHE_SLOTS; n++ ) { if( clients[n].addr.s_addr ) continue; get_remote_mac(h->clientaddr, clients[n].mac); clients[n].addr = h->clientaddr; DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", h->req_client, inet_ntoa(clients[n].addr), clients[n].mac[0], clients[n].mac[1], clients[n].mac[2], clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n); break; } } else if( (clients[n].type < EStandardDLNA150 && h->req_client == EStandardDLNA150) || (clients[n].type == ESamsungSeriesB && h->req_client == ESamsungSeriesA) ) { h->reqflags |= clients[n].flags; h->req_client = clients[n].type; return; } clients[n].type = h->req_client; clients[n].flags = h->reqflags & 0xFFF00000; clients[n].age = time(NULL); } else if( n >= 0 ) { h->reqflags |= clients[n].flags; h->req_client = clients[n].type; } } static void Send400(struct upnphttp * h) { static const char body400[] = "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>" "<BODY><H1>Bad Request</H1>The request is invalid" " for this HTTP version.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", body400, sizeof(body400) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void Send404(struct upnphttp * h) { static const char body404[] = "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" "<BODY><H1>Not Found</H1>The requested URL was not found" " on this server.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 404, "Not Found", body404, sizeof(body404) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void Send406(struct upnphttp * h) { static const char body406[] = "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>" "<BODY><H1>Not Acceptable</H1>An unsupported operation" " was requested.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 406, "Not Acceptable", body406, sizeof(body406) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void Send416(struct upnphttp * h) { static const char body416[] = "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>" "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range" " was outside the file's size.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable", body416, sizeof(body416) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } void Send500(struct upnphttp * h) { static const char body500[] = "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>" "<BODY><H1>Internal Server Error</H1>Server encountered " "and Internal Error.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 500, "Internal Server Errror", body500, sizeof(body500) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } void Send501(struct upnphttp * h) { static const char body501[] = "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" "<BODY><H1>Not Implemented</H1>The HTTP Method " "is not implemented by this server.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 501, "Not Implemented", body501, sizeof(body501) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static const char * findendheaders(const char * s, int len) { while(len-- > 0) { if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') return s; s++; } return NULL; } static void sendXMLdesc(struct upnphttp * h, char * (f)(int *)) { char * desc; int len; desc = f(&len); if(!desc) { DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n"); Send500(h); return; } BuildResp_upnphttp(h, desc, len); SendResp_upnphttp(h); CloseSocket_upnphttp(h); free(desc); } static void SendResp_presentation(struct upnphttp * h) { char body[1024]; int l; h->respflags = FLAG_HTML; #ifdef READYNAS l = snprintf(body, sizeof(body), "<meta http-equiv=\"refresh\" content=\"0; url=https: lan_addr[h->iface].str); #else int a, v, p; a = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'a*'"); v = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'v*'"); p = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'i*'"); l = snprintf(body, sizeof(body), "<HTML><HEAD><TITLE>" SERVER_NAME " " MINIDLNA_VERSION "</TITLE></HEAD>" "<BODY><div style=\"text-align: center\">" "<h3>" SERVER_NAME " status</h3>" "Audio files: %d<br>" "Video files: %d<br>" "Image files: %d</div>" "</BODY></HTML>\r\n", a, v, p); #endif BuildResp_upnphttp(h, body, l); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void ProcessHTTPPOST_upnphttp(struct upnphttp * h) { if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { if(h->req_soapAction) { DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction); ExecuteSoapAction(h, h->req_soapAction, h->req_soapActionLen); } else { static const char err400str[] = "<html><body>Bad request</body></html>"; DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n"); h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", err400str, sizeof(err400str) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } } else { h->state = 1; } } static void ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) { const char * sid; DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path); DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n", h->req_CallbackLen, h->req_Callback, h->req_Timeout); DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); if((!h->req_Callback && !h->req_SID) || strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } else { if(h->req_Callback) { if(!h->req_NT || strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0) { BuildResp2_upnphttp(h, 400, "Bad Request", "<html><body>Bad request</body></html>", 37); } else { sid = upnpevents_addSubscriber(path, h->req_Callback, h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if(sid) { DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid); h->respflags |= FLAG_SID; h->req_SID = sid; h->req_SIDLen = strlen(sid); } BuildResp_upnphttp(h, 0, 0); } } else { if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { h->respflags = FLAG_TIMEOUT; h->req_Timeout = 300; h->respflags |= FLAG_SID; BuildResp_upnphttp(h, 0, 0); } } SendResp_upnphttp(h); CloseSocket_upnphttp(h); } } static void ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) { DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path); DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { BuildResp_upnphttp(h, 0, 0); } SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { char HttpCommand[16]; char HttpUrl[512]; char * HttpVer; char * p; int i; p = h->req_buf; if(!p) return; for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) HttpCommand[i] = *(p++); HttpCommand[i] = '\0'; while(*p==' ') p++; if(strncmp(p, "http: { p = p+7; while(*p!='/') p++; } for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++) HttpUrl[i] = *(p++); HttpUrl[i] = '\0'; while(*p==' ') p++; HttpVer = h->HttpVer; for(i = 0; i<15 && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; for(i = 0; i<n_lan_addr; i++) { if( (h->clientaddr.s_addr & lan_addr[i].mask.s_addr) == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { h->iface = i; break; } } ParseHttpHeaders(h); if( (h->reqflags & FLAG_CHUNKED) ) { if( h->req_chunklen ) { h->state = 2; return; } char *chunkstart, *chunk, *endptr, *endbuf; chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff; while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) ) { while(!(endptr[0] == '\r' && endptr[1] == '\n')) { endptr++; } endptr += 2; memmove(endbuf, endptr, h->req_chunklen); endbuf += h->req_chunklen; chunk = endptr + h->req_chunklen; } h->req_contentlen = endbuf - chunkstart; h->req_buflen = endbuf - h->req_buf; h->state = 100; } DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp(h); } else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0)) { if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) ) { DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n"); Send400(h); return; } else if( (h->reqflags & (FLAG_TIMESEEK|FLAG_PLAYSPEED)) && !(h->reqflags & FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n", h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed"); Send406(h); return; } else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; } else { h->req_command = EHead; } if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) { if( (h->req_client == EXbox) && !strchr(friendly_name, ':') ) { i = strlen(friendly_name); snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1"); sendXMLdesc(h, genRootDesc); friendly_name[i] = '\0'; } else if( h->reqflags & FLAG_SAMSUNG_TV ) { sendXMLdesc(h, genRootDescSamsung); } else { sendXMLdesc(h, genRootDesc); } } else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) { sendXMLdesc(h, genContentDirectory); } else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0) { sendXMLdesc(h, genConnectionManager); } else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0) { sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); } else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) { SendResp_dlnafile(h, HttpUrl+12); } else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) { SendResp_thumbnail(h, HttpUrl+12); } else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) { SendResp_albumArt(h, HttpUrl+10); } #ifdef TIVO_SUPPORT else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0) { if( GETFLAG(TIVO_MASK) ) { if( *(HttpUrl+12) == '?' ) { ProcessTiVoCommand(h, HttpUrl+13); } else { DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12); Send404(h); } } else { DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n", HttpUrl+12); Send404(h); } } #endif else if(strncmp(HttpUrl, "/Resized/", 9) == 0) { SendResp_resizedimg(h, HttpUrl+9); } else if(strncmp(HttpUrl, "/icons/", 7) == 0) { SendResp_icon(h, HttpUrl+7); } else if(strncmp(HttpUrl, "/Captions/", 10) == 0) { SendResp_caption(h, HttpUrl+10); } else if(strcmp(HttpUrl, "/") == 0) { SendResp_presentation(h); } else { DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl); Send404(h); } } else if(strcmp("SUBSCRIBE", HttpCommand) == 0) { h->req_command = ESubscribe; ProcessHTTPSubscribe_upnphttp(h, HttpUrl); } else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) { h->req_command = EUnSubscribe; ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); } else { DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand); Send501(h); } } void Process_upnphttp(struct upnphttp * h) { char buf[2048]; int n; if(!h) return; switch(h->state) { case 0: n = recv(h->socket, buf, 2048, 0); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno)); h->state = 100; } else if(n==0) { DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); h->state = 100; } else { const char * endheaders; h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; h->req_buf[h->req_buflen] = '\0'; endheaders = findendheaders(h->req_buf, h->req_buflen); if(endheaders) { h->req_contentoff = endheaders - h->req_buf + 4; h->req_contentlen = h->req_buflen - h->req_contentoff; ProcessHttpQuery_upnphttp(h); } } break; case 1: case 2: n = recv(h->socket, buf, 2048, 0); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno)); h->state = 100; } else if(n==0) { DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); h->state = 100; } else { h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { if( h->state == 1 ) { ParseHttpHeaders(h); ProcessHTTPPOST_upnphttp(h); } else if( h->state == 2 ) { ProcessHttpQuery_upnphttp(h); } } } break; default: DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state); } } void BuildHeader_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, int bodylen) { static const char httpresphead[] = "%s %d %s\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "Content-Length: %d\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n"; time_t curtime = time(NULL); char date[30]; int templen; if(!h->res_buf) { templen = sizeof(httpresphead) + 256 + bodylen; h->res_buf = (char *)malloc(templen); h->res_buf_alloclen = templen; } h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, httpresphead, "HTTP/1.1", respcode, respmsg, (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", bodylen); if(h->respflags & FLAG_TIMEOUT) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Timeout: Second-"); if(h->req_Timeout) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "%d\r\n", h->req_Timeout); } else { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "300\r\n"); } } if(h->respflags & FLAG_SID) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "SID: %.*s\r\n", h->req_SIDLen, h->req_SID); } if(h->reqflags & FLAG_LANGUAGE) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Content-Language: en\r\n"); } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Date: %s\r\n", date); h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "EXT:\r\n"); #if 0 h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "contentFeatures.dlna.org: \r\n"); #endif h->res_buf[h->res_buflen++] = '\r'; h->res_buf[h->res_buflen++] = '\n'; if(h->res_buf_alloclen < (h->res_buflen + bodylen)) { h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); h->res_buf_alloclen = h->res_buflen + bodylen; } } void BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen) { BuildHeader_upnphttp(h, respcode, respmsg, bodylen); if( h->req_command == EHead ) return; if(body) memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; } void BuildResp_upnphttp(struct upnphttp * h, const char * body, int bodylen) { BuildResp2_upnphttp(h, 200, "OK", body, bodylen); } void SendResp_upnphttp(struct upnphttp * h) { int n; DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf); n = send(h->socket, h->res_buf, h->res_buflen, 0); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); } else if(n < h->res_buflen) { DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", n, h->res_buflen); } } int send_data(struct upnphttp * h, char * header, size_t size, int flags) { int n; n = send(h->socket, header, size, flags); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); } else if(n < h->res_buflen) { DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", n, h->res_buflen); } else { return 0; } return 1; } void send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) { off_t send_size; off_t ret; char *buf = NULL; #if HAVE_SENDFILE int try_sendfile = 1; #endif while( offset < end_offset ) { #if HAVE_SENDFILE if( try_sendfile ) { send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); ret = sys_sendfile(h->socket, sendfd, &offset, send_size); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); if( errno == EOVERFLOW || errno == EINVAL ) try_sendfile = 0; else if( errno != EAGAIN ) break; } else { continue; } } #endif if( !buf ) buf = malloc(MIN_BUFFER_SIZE); send_size = ( ((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE); lseek(sendfd, offset, SEEK_SET); ret = read(sendfd, buf, send_size); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno)); if( errno != EAGAIN ) break; } ret = write(h->socket, buf, ret); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno)); if( errno != EAGAIN ) break; } offset+=ret; } free(buf); } void SendResp_icon(struct upnphttp * h, char * icon) { char header[512]; char mime[12] = "image/"; char date[30]; char *data; int size, ret; time_t curtime = time(NULL); if( strcmp(icon, "sm.png") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n"); data = (char *)png_sm; size = sizeof(png_sm)-1; strcpy(mime+6, "png"); } else if( strcmp(icon, "lrg.png") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n"); data = (char *)png_lrg; size = sizeof(png_lrg)-1; strcpy(mime+6, "png"); } else if( strcmp(icon, "sm.jpg") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n"); data = (char *)jpeg_sm; size = sizeof(jpeg_sm)-1; strcpy(mime+6, "jpeg"); } else if( strcmp(icon, "lrg.jpg") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n"); data = (char *)jpeg_lrg; size = sizeof(jpeg_lrg)-1; strcpy(mime+6, "jpeg"); } else { DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon); Send404(h); return; } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "Date: %s\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", mime, size, date); if( send_data(h, header, ret, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_data(h, data, size, 0); } CloseSocket_upnphttp(h); } void SendResp_albumArt(struct upnphttp * h, char * object) { char header[512]; char *path; char *dash; char date[30]; time_t curtime = time(NULL); off_t size; int fd; int ret; if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); return; } dash = strchr(object, '-'); if( dash ) *dash = '\0'; path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%s'", object); if( !path ) { DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object); Send404(h); return; } DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path); fd = open(path, O_RDONLY); if( fd < 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); sqlite3_free(path); Send404(h); return; } sqlite3_free(path); size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %jd\r\n" "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n" "transferMode.dlna.org: Interactive\r\n\r\n", (intmax_t)size, date); if( send_data(h, header, ret, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_file(h, fd, 0, size-1); } close(fd); CloseSocket_upnphttp(h); } void SendResp_caption(struct upnphttp * h, char * object) { char header[512]; char *path; char date[30]; time_t curtime = time(NULL); off_t size; int fd, ret; strip_ext(object); path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %s", object); if( !path ) { DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object); Send404(h); return; } DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path); fd = open(path, O_RDONLY); if( fd < 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); sqlite3_free(path); Send404(h); return; } sqlite3_free(path); size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: smi/caption\r\n" "Content-Length: %jd\r\n" "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", (intmax_t)size, date); if( send_data(h, header, ret, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_file(h, fd, 0, size-1); } close(fd); CloseSocket_upnphttp(h); } void SendResp_thumbnail(struct upnphttp * h, char * object) { char header[512]; char *path; char date[30]; time_t curtime = time(NULL); int ret; ExifData *ed; ExifLoader *l; if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); return; } strip_ext(object); path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%s'", object); if( !path ) { DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object); Send404(h); return; } DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path); if( access(path, F_OK) != 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path); sqlite3_free(path); return; } l = exif_loader_new(); exif_loader_write_file(l, path); ed = exif_loader_get_data(l); exif_loader_unref(l); sqlite3_free(path); if( !ed || !ed->size ) { Send404(h); if( ed ) exif_data_unref(ed); return; } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %jd\r\n" "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n" "transferMode.dlna.org: Interactive\r\n\r\n", (intmax_t)ed->size, date); if( send_data(h, header, ret, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_data(h, (char *)ed->data, ed->size, 0); } exif_data_unref(ed); CloseSocket_upnphttp(h); } void SendResp_resizedimg(struct upnphttp * h, char * object) { char header[512]; char buf[128]; struct string_s str; char **result; char date[30]; char dlna_pn[22]; uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I; time_t curtime = time(NULL); int width=640, height=480, dstw, dsth, size; int srcw, srch; unsigned char * data = NULL; char *path, *file_path; char *resolution; char *key, *val; char *saveptr, *item=NULL; int rotate; sqlite_int64 id; int rows=0, chunked, ret; image_s *imsrc = NULL, *imdst = NULL; int scale = 1; id = strtoll(object, &saveptr, 10); snprintf(buf, sizeof(buf), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", id); ret = sql_get_table(db, buf, &result, &rows, NULL); if( (ret != SQLITE_OK) ) { DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id); Send500(h); return; } file_path = result[3]; resolution = result[4]; rotate = result[5] ? atoi(result[5]) : 0; if( !rows || !file_path || !resolution || (access(file_path, F_OK) != 0) ) { DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); sqlite3_free_table(result); Send404(h); return; } if( saveptr ) saveptr = strchr(saveptr, '?'); path = saveptr ? saveptr + 1 : object; for( item = strtok_r(path, "&,", &saveptr); item != NULL; item = strtok_r(NULL, "&,", &saveptr) ) { #ifdef TIVO_SUPPORT decodeString(item, 1); #endif val = item; key = strsep(&val, "="); if( !val ) continue; DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); if( strcasecmp(key, "width") == 0 ) { width = atoi(val); } else if( strcasecmp(key, "height") == 0 ) { height = atoi(val); } else if( strcasecmp(key, "rotation") == 0 ) { rotate = (rotate + atoi(val)) % 360; sql_exec(db, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate, id); } } #if USE_FORK pid_t newpid = 0; newpid = fork(); if( newpid ) { CloseSocket_upnphttp(h); goto resized_error; } #endif if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); goto resized_error; } DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path); switch( rotate ) { case 90: ret = sscanf(resolution, "%dx%d", &srch, &srcw); rotate = ROTATE_90; break; case 270: ret = sscanf(resolution, "%dx%d", &srch, &srcw); rotate = ROTATE_270; break; case 180: ret = sscanf(resolution, "%dx%d", &srcw, &srch); rotate = ROTATE_180; break; default: ret = sscanf(resolution, "%dx%d", &srcw, &srch); rotate = ROTATE_NONE; break; } if( ret != 2 ) { Send500(h); return; } dstw = width; dsth = ((((width<<10)/srcw)*srch)>>10); if( dsth > height ) { dsth = height; dstw = (((height<<10)/srch) * srcw>>10); } if( dstw <= 640 && dsth <= 480 ) strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_SM;"); else if( dstw <= 1024 && dsth <= 768 ) strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_MED;"); else strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_LRG;"); if( srcw>>4 >= dstw && srch>>4 >= dsth) scale = 8; else if( srcw>>3 >= dstw && srch>>3 >= dsth ) scale = 4; else if( srcw>>2 >= dstw && srch>>2 >= dsth ) scale = 2; str.data = header; str.size = sizeof(header); str.off = 0; strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); strcatf(&str, "HTTP/1.1 200 OK\r\n" "Content-Type: image/jpeg\r\n" "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" "contentFeatures.dlna.org: %sDLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n", date, dlna_pn, 1, dlna_flags, 0); #if USE_FORK if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) ) strcatf(&str, "transferMode.dlna.org: Background\r\n"); else #endif strcatf(&str, "transferMode.dlna.org: Interactive\r\n"); if( strcmp(h->HttpVer, "HTTP/1.0") == 0 ) { chunked = 0; imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate); } else { chunked = 1; strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n"); } if( !chunked ) { if( !imsrc ) { DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); Send500(h); goto resized_error; } imdst = image_resize(imsrc, dstw, dsth); data = image_save_to_jpeg_buf(imdst, &size); strcatf(&str, "Content-Length: %d\r\n\r\n", size); } if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) ) { if( chunked ) { imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate); if( !imsrc ) { DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); Send500(h); goto resized_error; } imdst = image_resize(imsrc, dstw, dsth); data = image_save_to_jpeg_buf(imdst, &size); ret = sprintf(buf, "%x\r\n", size); send_data(h, buf, ret, MSG_MORE); send_data(h, (char *)data, size, MSG_MORE); send_data(h, "\r\n0\r\n\r\n", 7, 0); } else { send_data(h, (char *)data, size, 0); } } DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); if( imsrc ) image_free(imsrc); if( imdst ) image_free(imdst); CloseSocket_upnphttp(h); resized_error: sqlite3_free_table(result); #if USE_FORK if( !newpid ) _exit(0); #endif } void SendResp_dlnafile(struct upnphttp * h, char * object) { char header[1024]; struct string_s str; char buf[128]; char **result; int rows, ret; char date[30]; time_t curtime = time(NULL); off_t total, offset, size; sqlite_int64 id; int sendfh; uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B; static struct { sqlite_int64 id; enum client_types client; char path[PATH_MAX]; char mime[32]; char dlna[96]; } last_file = { 0, 0 }; #if USE_FORK pid_t newpid = 0; #endif id = strtoll(object, NULL, 10); if( h->reqflags & FLAG_MS_PFS ) { if( strstr(object, "?albumArt=true") ) { char *art; art = sql_get_text_field(db, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id); SendResp_albumArt(h, art); sqlite3_free(art); return; } } if( id != last_file.id || h->req_client != last_file.client ) { snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id); ret = sql_get_table(db, buf, &result, &rows, NULL); if( (ret != SQLITE_OK) ) { DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id); Send500(h); return; } if( !rows || !result[3] || !result[4] ) { DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); sqlite3_free_table(result); Send404(h); return; } last_file.id = id; last_file.client = h->req_client; strncpy(last_file.path, result[3], sizeof(last_file.path)-1); if( result[4] ) { strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1); if( h->reqflags & FLAG_SAMSUNG ) { if( strcmp(last_file.mime+6, "x-matroska") == 0 ) strcpy(last_file.mime+8, "mkv"); else if( h->req_client == ESamsungSeriesA && strcmp(last_file.mime+6, "x-msvideo") == 0 ) strcpy(last_file.mime+6, "mpeg"); } else if( h->req_client == ESonyBDP ) { if( strcmp(last_file.mime+6, "x-matroska") == 0 || strcmp(last_file.mime+6, "mpeg") == 0 ) strcpy(last_file.mime+6, "divx"); } } if( result[5] ) snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s;", result[5]); else last_file.dlna[0] = '\0'; sqlite3_free_table(result); } #if USE_FORK newpid = fork(); if( newpid ) { CloseSocket_upnphttp(h); goto error; } #endif DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path); if( h->reqflags & FLAG_XFERSTREAMING ) { if( strncmp(last_file.mime, "image", 5) == 0 ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); goto error; } } else if( h->reqflags & FLAG_XFERINTERACTIVE ) { if( h->reqflags & FLAG_REALTIMEINFO ) { DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n"); Send400(h); goto error; } if( strncmp(last_file.mime, "image", 5) != 0 ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n"); if( !(h->reqflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) ) { Send406(h); goto error; } } } offset = h->req_RangeStart; sendfh = open(last_file.path, O_RDONLY); if( sendfh < 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path); Send404(h); goto error; } size = lseek(sendfh, 0, SEEK_END); lseek(sendfh, 0, SEEK_SET); str.data = header; str.size = sizeof(header); str.off = 0; strcatf(&str, "HTTP/1.1 20%c OK\r\n" "Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), last_file.mime); if( h->reqflags & FLAG_RANGE ) { if( !h->req_RangeEnd || h->req_RangeEnd == size ) { h->req_RangeEnd = size - 1; } if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) ) { DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n"); Send400(h); close(sendfh); goto error; } if( h->req_RangeEnd >= size ) { DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n"); Send416(h); close(sendfh); goto error; } total = h->req_RangeEnd - h->req_RangeStart + 1; strcatf(&str, "Content-Length: %jd\r\n" "Content-Range: bytes %jd-%jd/%jd\r\n", (intmax_t)total, (intmax_t)h->req_RangeStart, (intmax_t)h->req_RangeEnd, (intmax_t)size); } else { h->req_RangeEnd = size - 1; total = size; strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total); } #if USE_FORK if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) ) strcatf(&str, "transferMode.dlna.org: Background\r\n"); else #endif if( strncmp(last_file.mime, "image", 5) == 0 ) strcatf(&str, "transferMode.dlna.org: Interactive\r\n"); else strcatf(&str, "transferMode.dlna.org: Streaming\r\n"); switch( *last_file.mime ) { case 'i': dlna_flags |= DLNA_FLAG_TM_I; break; case 'a': case 'v': default: dlna_flags |= DLNA_FLAG_TM_S; break; } if( h->reqflags & FLAG_CAPTION ) { if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 ) strcatf(&str, "CaptionInfo.sec: http: lan_addr[h->iface].str, runtime_vars.port, id); } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); strcatf(&str, "Accept-Ranges: bytes\r\n" "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" "contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", date, last_file.dlna, 1, 0, dlna_flags, 0); if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_file(h, sendfh, offset, h->req_RangeEnd); } close(sendfh); CloseSocket_upnphttp(h); error: #if USE_FORK if( !newpid ) _exit(0); #endif return; }