#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;
}