diff options
author | jmaggard <jmaggard> | 2008-10-23 21:30:45 +0400 |
---|---|---|
committer | jmaggard <jmaggard> | 2008-10-23 21:30:45 +0400 |
commit | 5ce6a3290bb81e848b648dfaf815745de3bb4d45 (patch) | |
tree | 7337b08cfd3ea772643d3ff9596423396a0f8e9b | |
parent | 9c28554fd96076cded42e0921bde618fb6375a3a (diff) |
Initial checkinv1
-rw-r--r-- | Changelog.txt | 433 | ||||
-rw-r--r-- | INSTALL | 9 | ||||
-rw-r--r-- | LICENCE | 2 | ||||
-rw-r--r-- | Makefile | 126 | ||||
-rw-r--r-- | README | 6 | ||||
-rw-r--r-- | commonrdr.h | 35 | ||||
-rw-r--r-- | config.h | 55 | ||||
-rw-r--r-- | daemonize.c | 43 | ||||
-rw-r--r-- | daemonize.h | 30 | ||||
-rwxr-xr-x | genconfig.sh | 198 | ||||
-rw-r--r-- | getifaddr.c | 202 | ||||
-rw-r--r-- | getifaddr.h | 48 | ||||
-rw-r--r-- | linux/miniupnpd.init.d.script | 40 | ||||
-rw-r--r-- | metadata.c | 1590 | ||||
-rw-r--r-- | metadata.h | 90 | ||||
-rw-r--r-- | minidlna.c | 1024 | ||||
-rw-r--r-- | minissdp.c | 499 | ||||
-rw-r--r-- | minissdp.h | 31 | ||||
-rw-r--r-- | miniupnpd.1 | 73 | ||||
-rw-r--r-- | miniupnpd.conf | 73 | ||||
-rw-r--r-- | miniupnpdpath.h | 47 | ||||
-rw-r--r-- | miniupnpdtypes.h | 23 | ||||
-rw-r--r-- | minixml.h | 30 | ||||
-rw-r--r-- | options.c | 62 | ||||
-rw-r--r-- | options.h | 73 | ||||
-rw-r--r-- | scanner.c | 964 | ||||
-rw-r--r-- | scanner.h | 83 | ||||
-rw-r--r-- | sql.c | 193 | ||||
-rw-r--r-- | sql.h | 32 | ||||
-rw-r--r-- | testupnpdescgen.c | 55 | ||||
-rw-r--r-- | upnpdescgen.c | 336 | ||||
-rw-r--r-- | upnpdescgen.c.dlna | 944 | ||||
-rw-r--r-- | upnpdescgen.h | 51 | ||||
-rw-r--r-- | upnpdescstrings.h | 71 | ||||
-rw-r--r-- | upnpevents.c | 154 | ||||
-rw-r--r-- | upnpevents.h | 61 | ||||
-rw-r--r-- | upnpglobalvars.c | 123 | ||||
-rw-r--r-- | upnpglobalvars.h | 227 | ||||
-rw-r--r-- | upnphttp.c | 1395 | ||||
-rw-r--r-- | upnphttp.h | 99 | ||||
-rw-r--r-- | upnpreplyparse.c | 76 | ||||
-rw-r--r-- | upnpreplyparse.h | 30 | ||||
-rw-r--r-- | upnpsoap.c | 1462 | ||||
-rw-r--r-- | upnpsoap.h | 47 |
44 files changed, 4238 insertions, 7007 deletions
diff --git a/Changelog.txt b/Changelog.txt new file mode 100644 index 0000000..42af405 --- /dev/null +++ b/Changelog.txt @@ -0,0 +1,433 @@ +$Id$ + +2008/07/10: + Fixed compilation without ENABLE_L3F_SERVICE + +2008/04/27: + correct UNSUBSCRIBE processing + +2008/04/25(bis): + changed iptables_removeall.sh and iptables_init.sh in order + to remove IP from the rules + +VERSION 1.1 : +2008/04/25: + Eventing is allmost completly implemented + +2008/04/24: + Correct event handling ? + +2008/04/08: + enabling tag in PF rules. quick can be set off. + +2008/03/13: + implementing event notify + +2008/03/11: + fixing a command line parsing error + +2008/03/09: + optimisations in upnpsoap.c + +2008/03/08: + optimizing upnpsoap.c for size + +2008/03/06: + Worked on the Eventing : generating XML event notifications + Send initial notification after subscribe + Improved pretty print of testupnpdescgen + Reduced Memory usage of upnpdescgen + fixed a small bug in the description + +2008/03/03: + Fixed miniupnpd.c for compiling without natpmp support + fixed presentationURL not there with L3F + fixing lease file creation/modification + +2008/02/25: + Rewrite of Send501() and Send404() + More work on events + genconfig.sh autodetects pf/ipf + +2008/02/24: + Started to implement UPnP Events. do NOT use it at the moment ! + +2008/02/21: + Added support for the Layer3Forwarding Service + added init_redirect() and shutdown_redirect() functions + +2008/02/20: + Removed Ext: HTTP header when useless + enabled the dummy service by default to please windows XP ! + +2008/02/07: + upnp_enable patch by Nikos Mavrogiannopoulos. + lease_file patch by Nikos Mavrogiannopoulos. + +2008/01/29: + some changes to Makefile.openwrt + use daemon() - daemonize() is still available for systems lacking daemon() + +VERSION 1.0 : +2008/01/27: + moved lan_addr to upnpglobalvars.h/.c + Adding experimental multiple external IP support. + +2008/01/22: + removed dummy service from description to improve compatibility + with emule client + Add "secure mode". put runtime flags in the same variable + +2008/01/14: + Fixed a bug in options.c for the parsing of empty lines. + +2008/01/03: + Fixed CleanExpiredNATPMP() + +2008/01/02: + Adding a queue parameter for setting ALTQ in pf + +2007/12/27: + improving some stuff with the PF_ENABLE_FILTER_RULE. + +2007/12/22: + Adding a runtime option to enable/disable NAT-PMP + +2007/12/20: + Added a cache in linux getifstats(). Please enable by editing config.h + +2007/12/14: + Updating an existing NAT-PMP mapping now works + +2007/12/13: + NAT-PMP code now remove expired mappings + TCP/UDP where swapped in NAT-PMP code + +2007/12/04: + Adding details to the error message for sendto(udp_notify) + +2007/11/27: + pf code doesn't generate filter rules by default anymore. The + #ifdef PF_ENABLE_FILTER_RULES must be uncommented in config.h. + +2007/11/02: + moved some of the prototypes common to all firewalls to commonrdr.h + Added functionalities to NAT-PMP + +2007/11/01: + Debugged NAT-PMP code + +2007/10/28: + Cleaning and improving NAT-PMP code + +2007/10/25: + improved the NAT-PMP experimental support + updated README and INSTALL files + +2007/10/24: + Adding support for NAT-PMP (from apple !) + +2007/10/11: + Checking the commandline for errors. + +2007/10/08: + Improved the BSD/Solaris Makefile + Merging last code from Darren Reed. Solaris/IPF should work now ! + added a man page. + +2007/10/07: + Adding Darren Reed code for ipf. + +2007/10/06: + Adding SunOS support thanks to Darren Reed. + Reorganizing os/firewall dependent code thanks to Darren Reed. + +2007/09/27: + linux make install support PREFIX variable + +2007/09/25: + reorganizing LAN sockets/address to improve multi LAN support. + SSDP announces are sent to all configured networks. + SSDP responses are "customized" by subnetwork. + +2007/09/24: + prototype code to remove unused rules + miniupnpdctl now display current rules + synchronised add_filter_rule2() prototype between pf and netfilter code. + +2007/09/19: + Correctly filling the Cache-control header in SSDP packets + +2007/08/28: + update PFRULE_INOUT_COUNTS detection for FreeBSD + +2007/08/27: + update version in genconfig.sh + do not error when a duplicate redirection is requested. + +2007/07/16: + really fixed the compilation bug with linux>=2.6.22 + +2007/07/04: + fixed an error in options.c that prevented to use packet_log option + +2007/07/03: + improved genconfig.sh + fixed a compilation bug with linux>=2.6.22 + +2007/06/22: + added PFRULE_INOUT_COUNTS macro to enable separate in/out packet and + bytes counts in pf for OpenBSD >= 3.8 + +2007/06/15: + removed a possible racecondition in writepidfile() + +2007/06/12: + improved genconfig.sh : no more "echo -e", use lsb_release when available + +2007/06/11: + get_redirect_rule*() functions now return some statistics about + rule usage (bytes and packets) + +2007/06/07: + Fixed the get_redirect_desc() in the linux/netfilter code + +2007/06/05: + Clean up init code in miniupnpd.c + Added a syslog message in SoapError() + +2007/06/04: + Now store redirection descriptions in the linux/netfilter code + +2007/05/21: + Answers to SSDP M-SEARCH requests with ST: ssdp:all + added make install to Makefile.linux + +2007/05/10: + Fixed a bug int the DeletePortMapping linux/netfilter implementation + It was allways the 1st rule that was deleted. + +2007/04/26: + Fixed config.h.openwrt + +2007/04/16: + added something in the INSTALL file about the FreeBSD send(udp_notify) + problem fix (allowing 239.0.0.0/8 explicitely in pf.conf) + +2007/03/30: + added setsockopt(s, SOL_SOCKET, SO_BROADCAST ...) for broadcasting + socket + +2007/03/17: + Fixed filter rule under linux : it was using wrong port ! + thanks to Wesley W. Terpstra + +2007/03/01: + Moved some of the SSDP code from miniupnpd.c to minissdp.c + +2007/02/28: + creating miniupnpdctl + +2007/02/26: + use LOG_MINIUPNPD macro for openlog() + simplify miniupndShutdown() + +2007/02/09: + improved genconfig.h + Added stuff to change the pf rule "rdr" to "rdr pass" + +2007/02/07: + Corrected Bytes per seconds to bits per second. + Ryan cleaned up comments and typos. + Ryan cleaned up daemonize stuff. + Ryan added possibility to configure model number and serial number + +2007/01/30: + ryan improved the robustness of most UPnP Soap methods + I added a target in the Makefiles to properly generate an uuid using + command line tools. + Improved configuration file parsing. + +2007/01/29: + Adding uuid option in miniupnpd.conf + +2007/01/27: + Added upnppermissions stuff : adding some security to UPnP ! + fixed XML description thanks to Ryan Wagoner + improved QueryStateVariable thanks to Ryan Wagoner + +2007/01/22: + use getifaddr() for each GetExtenalIPAddress() Call. + We can change the ip during execution without pb + +2007/01/17: + Lots of code cleanup + +2007/01/12: + Fixed a nasty bug in the linux/netfilter version of get_filter_rule() + +2007/01/11: + Improved the handling of the miniupnpd.conf file. + added -f option to choose which config file to read. + +2007/01/10: + Fixed potential bugs with ClearNameValueList() + +2007/01/08: + All by Ryan Wagoner : + - coding style and comments cleanup + - using now option file miniupnpd.conf + +2007/01/03: + changed "xx active incoming HTTP connections" msg + +2007/01/02: + Patch from Ryan Wagoner : + - no need to open sockets if we can't set the error handlers + - format the usage so it fits nicely on a standard size terminal + - fix up log_err message so they have the same format and you know what + they are related to + - use same "white space" style throughout + - on shutdown no need to continue if opening socket or setsockopt fails + +2006/12/14: + reduce amount of log lines (keeping the same information) + +2006/12/07: + Fixed Makefiles + fixed typos in logs + version 1.0-RC1 released + +2006/12/02: + moved strings from upnpdescgen.c to upnpdescstrings.h for + easier modification + Server: HTTP header now comes from a #define + added a compilation-time generated config.h + +2006/11/30: + minixml updated. should have no impact + Added support for presentationURL with -w switch + implemented getifstats() for linux. Added testgetifstats program + improved error handling in getifstats() BSD + +2006/11/26: + no need to have miniupnpc sources to compile miniupnpd. + Makefile.openwrt updated + Closing sockets on exit thanks to Ryan Wagoner + +2006/11/23: + now handling signal SIGINT + setting HTTP socket with REUSEADDR thanks to Ryan Wagoner + daemon now tested on a Linksys WRT54G device running OpenWRT ! + +2006/11/21: + disabling rtableid in pf code. + +2006/11/22: + Also responds on M-SEARCH with the uuid + +2006/11/20: + gaining some space in upnpsoap.c + +2006/11/19: + Cleaning up code to comply with ANSI C89 + +2006/11/17: + Linux version now deleting both nat and accept rules + implemented -U option under Linux + +2006/11/16: + implemented delete_redirect_rule() for linux + returning error 714 in DeletePortMapping() when needed + +2006/11/12: + The linux/netfilter version should now WORK ! + fix in the writepidfile() function. open with a mode ! + +2006/11/10: + fixing the XML description generation for big endian machines + working on the linux/netfilter port + +2006/11/09: + improved a lot the handling of HTTP error cases + +2006/11/08: + Tried to make the Makefile compatible with both BSDmake + and GNUmake. It was hard because of $^ and $< + +2006/11/07: + Makefile compatible with BSD make + make install target. + getifstats.c compatible with both OpenBSD and FreeBSD. + +2006/11/06: + added getifstats.c for openBSD. May not work under FreeBSD ? + now reports bytes/packets sent/received + reporting bitrates + possibility to report system uptime + +2006/10/29: + added a -L option to enable loggin (is off by default now). + +2006/10/28: + Patch by Ryan Wagoner to correct the XML description (was NewUpTime + instead of NewUptime) and implement uptime. + Trying to fix the memory leak. Added some comments + added a -d option for debugging purpose + Tnaks to valgrind (under linux!) I removed a small memory access error. + +2006/10/27: + Thanks to a patch sent by Michael van Tellingen, miniupnpd is + now ignoring NOTIFY packets sent by other devices and is + writing is own pid to /var/run/miniupnpd.pid + +2006/10/23: + Allways set sendEvents="no" in XML description (was causing + pb with winXP as SUBSCRIBE is not implemented) + +2006/10/22: + added translation from hostname to IP in the AddPortMapping() method + Thanks to Ryan Wagoner. + +2006/10/18: + Added an INSTALL file + +2006/10/13: + Added the possibility to change the notify interval + +2006/09/29: + Improved compliance of the XML Descriptions + pretty print for testupnpdescgen + +2006/09/25: + improved the Error 404 response. + Better serviceType and serviceId for dummy service... + +2006/09/24: + updating the XML description generator + +2006/09/18: + Thanks to Rick Richard, support for SSDP "alive" and "byebye" notifications + was added. The -u options was also added. The SSDP response are now + improved. + The -o option is now working (to force a specific external IP address). + The Soap Methods errors are correctly responded (401 Invalid Action) + +2006/09/09: + Added code to handle filter rules. Thanks to Seth Mos (pfsense.com) + storing the descriptions in the label of the rule + +2006/09/02: + improved the generation of the XML descriptions. + I still need to add allowed values to variables. + +2006/07/29: + filtering SSDP requests and responding with same ST: field + +2006/07/25: + Added a dummy description for the WANDevice + +2006/07/20: + Command line arguments processing + Added possibility to listen internally on several interfaces + @@ -1,18 +1,13 @@ MiniDLNA project. -(c) 2009 Justin Maggard +(c) 2008 Justin Maggard Parts (c) 2006-2008 Thomas Bernard Homepage : http://sourceforge.net/projects/minidlna/ Prerequisites : +- taglib - libexif -- libjpeg -- libid3tag -- libFLAC -- libvorbis - sqlite3 -- libavformat (the ffmpeg libraries) -- libuuid To Build and install : @@ -1,6 +1,6 @@ MiniDLNA is distributed under version 2 of the General Public License (included in its entirety, below). Version 2 is the only version of this license which -this version of MiniDLNA (or modified versions derived from this one) may be +this version of BusyBox (or modified versions derived from this one) may be distributed under. ------------------------------------------------------------------------ @@ -1,50 +1,42 @@ -# $Id: Makefile,v 1.31 2011/04/21 08:50:44 jmaggard Exp $ -# MiniDLNA project -# http://sourceforge.net/projects/minidlna/ -# (c) 2008-2009 Justin Maggard +# $Id$ +# MiniUPnP project +# http://miniupnp.free.fr/ +# Author : Thomas Bernard # for use with GNU Make # To install use : -# $ DESTDIR=/dummyinstalldir make install +# $ PREFIX=/dummyinstalldir make -f Makefile.linux install # or : -# $ INSTALLPREFIX=/usr/local make install +# $ INSTALLPREFIX=/usr/local make -f Makefile.linux install # or : -# $ make install +# $ make -f Makefile.linux install # #CFLAGS = -Wall -O -D_GNU_SOURCE -g -DDEBUG #CFLAGS = -Wall -g -Os -D_GNU_SOURCE -CFLAGS = -Wall -g -O3 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I/usr/include/ffmpeg \ - -I/usr/include/libavutil -I/usr/include/libavcodec -I/usr/include/libavformat \ - -I/usr/include/ffmpeg/libavutil -I/usr/include/ffmpeg/libavcodec -I/usr/include/ffmpeg/libavformat -#STATIC_LINKING: CFLAGS += -DSTATIC -#STATIC_LINKING: LDFLAGS = -static +CFLAGS = -Wall -g -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 CC = gcc RM = rm -f INSTALL = install -INSTALLPREFIX ?= $(DESTDIR)/usr +INSTALLPREFIX ?= $(PREFIX)/usr SBININSTALLDIR = $(INSTALLPREFIX)/sbin -ETCINSTALLDIR = $(DESTDIR)/etc +ETCINSTALLDIR = $(PREFIX)/etc/miniupnpd BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpreplyparse.o minixml.o \ getifaddr.o daemonize.o upnpglobalvars.o \ - options.o minissdp.o uuid.o upnpevents.o \ - sql.o utils.o metadata.o scanner.o inotify.o \ - tivo_utils.o tivo_beacon.o tivo_commands.o \ - tagutils/textutils.o tagutils/misc.o tagutils/tagutils.o \ - playlist.o image_utils.o albumart.o log.o + options.o minissdp.o upnpevents.o \ + sql.o metadata.o scanner.o ALLOBJS = $(BASEOBJS) $(LNXOBJS) -LIBS = -lpthread -lexif -ljpeg -lsqlite3 -lavformat -lavutil -lavcodec -lid3tag -lFLAC -logg -lvorbis -#STATIC_LINKING: LIBS = -lvorbis -logg -lm -lsqlite3 -lpthread -lexif -ljpeg -lFLAC -lm -lid3tag -lz -lavformat -lavutil -lavcodec -lm +#LIBS = -liptc +LIBS = -lexif -ltag_c -lsqlite3 #-lgd TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o EXECUTABLES = minidlna testupnpdescgen -.PHONY: all clean distclean install depend +.PHONY: all clean install depend genuuid all: $(EXECUTABLES) @@ -53,23 +45,24 @@ clean: $(RM) $(EXECUTABLES) $(RM) testupnpdescgen.o -distclean: clean - $(RM) config.h - -install: minidlna +install: minidlna genuuid $(INSTALL) -d $(SBININSTALLDIR) $(INSTALL) minidlna $(SBININSTALLDIR) $(INSTALL) -d $(ETCINSTALLDIR) + $(INSTALL) netfilter/iptables_init.sh $(ETCINSTALLDIR) + $(INSTALL) netfilter/iptables_removeall.sh $(ETCINSTALLDIR) $(INSTALL) --mode=0644 minidlna.conf $(ETCINSTALLDIR) + $(INSTALL) -d $(PREFIX)/etc/init.d + $(INSTALL) linux/miniupnpd.init.d.script $(PREFIX)/etc/init.d/miniupnpd -minidlna: $(BASEOBJS) $(LNXOBJS) $(LIBS) - @echo Linking $@ - @$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(BASEOBJS) $(LNXOBJS) $(LIBS) +# genuuid is using the uuidgen CLI tool which is part of libuuid +# from the e2fsprogs +genuuid: + sed -i -e "s/^uuid=[-0-9a-f]*/uuid=`(genuuid||uuidgen) 2>/dev/null`/" minidlna.conf +minidlna: $(BASEOBJS) $(LNXOBJS) $(LIBS) testupnpdescgen: $(TESTUPNPDESCGENOBJS) - @echo Linking $@ - @$(CC) $(CFLAGS) -o $@ $(TESTUPNPDESCGENOBJS) config.h: genconfig.sh ./genconfig.sh @@ -80,54 +73,29 @@ depend: config.h # DO NOT DELETE -minidlna.o: config.h upnpglobalvars.h minidlnatypes.h -minidlna.o: upnphttp.h upnpdescgen.h minidlnapath.h getifaddr.h upnpsoap.h -minidlna.o: options.h minissdp.h daemonize.h upnpevents.h log.h -upnphttp.o: config.h upnphttp.h upnpdescgen.h minidlnapath.h upnpsoap.h -upnphttp.o: upnpevents.h image_utils.h sql.h log.h icons.c -upnpdescgen.o: config.h upnpdescgen.h minidlnapath.h upnpglobalvars.h -upnpdescgen.o: minidlnatypes.h upnpdescstrings.h log.h -upnpsoap.o: config.h upnpglobalvars.h minidlnatypes.h log.h utils.h sql.h -upnpsoap.o: upnphttp.h upnpsoap.h upnpreplyparse.h getifaddr.h log.h -upnpreplyparse.o: upnpreplyparse.h minixml.h log.h +minidlna.o: config.h upnpglobalvars.h miniupnpdtypes.h +minidlna.o: upnphttp.h upnpdescgen.h miniupnpdpath.h getifaddr.h upnpsoap.h +minidlna.o: options.h minissdp.h daemonize.h upnpevents.h +minidlna.o: commonrdr.h +upnphttp.o: config.h upnphttp.h upnpdescgen.h miniupnpdpath.h upnpsoap.h +upnphttp.o: upnpevents.h +upnpdescgen.o: config.h upnpdescgen.h miniupnpdpath.h upnpglobalvars.h +upnpdescgen.o: miniupnpdtypes.h upnpdescstrings.h +upnpsoap.o: config.h upnpglobalvars.h miniupnpdtypes.h +upnpsoap.o: upnphttp.h upnpsoap.h upnpreplyparse.h getifaddr.h +upnpreplyparse.o: upnpreplyparse.h minixml.h minixml.o: minixml.h -getifaddr.o: getifaddr.h log.h -daemonize.o: daemonize.h config.h log.h +getifaddr.o: getifaddr.h +daemonize.o: daemonize.h config.h upnpglobalvars.o: config.h upnpglobalvars.h -upnpglobalvars.o: minidlnatypes.h +upnpglobalvars.o: miniupnpdtypes.h options.o: options.h config.h upnpglobalvars.h -options.o: minidlnatypes.h -minissdp.o: config.h upnpdescstrings.h minidlnapath.h upnphttp.h -minissdp.o: upnpglobalvars.h minidlnatypes.h minissdp.h log.h -upnpevents.o: config.h upnpevents.h minidlnapath.h upnpglobalvars.h -upnpevents.o: minidlnatypes.h upnpdescgen.h log.h uuid.h -uuid.o: uuid.h +options.o: miniupnpdtypes.h +minissdp.o: config.h upnpdescstrings.h miniupnpdpath.h upnphttp.h +minissdp.o: upnpglobalvars.h miniupnpdtypes.h minissdp.h +upnpevents.o: config.h upnpevents.h miniupnpdpath.h upnpglobalvars.h +upnpevents.o: miniupnpdtypes.h upnpdescgen.h +netfilter/iptcrdr.o: netfilter/iptcrdr.h commonrdr.h config.h testupnpdescgen.o: config.h upnpdescgen.h -upnpdescgen.o: config.h upnpdescgen.h minidlnapath.h upnpglobalvars.h -upnpdescgen.o: minidlnatypes.h upnpdescstrings.h -scanner.o: upnpglobalvars.h metadata.h utils.h sql.h scanner.h log.h playlist.h -metadata.o: upnpglobalvars.h metadata.h albumart.h utils.h sql.h log.h -albumart.o: upnpglobalvars.h albumart.h utils.h image_utils.h sql.h log.h -tagutils/misc.o: tagutils/misc.h -tagutils/textutils.o: tagutils/misc.h tagutils/textutils.h log.h -tagutils/tagutils.o: tagutils/tagutils-asf.c tagutils/tagutils-flc.c tagutils/tagutils-plist.c tagutils/tagutils-misc.c -tagutils/tagutils.o: tagutils/tagutils-aac.c tagutils/tagutils-asf.h tagutils/tagutils-flc.h tagutils/tagutils-mp3.c tagutils/tagutils-wav.c -tagutils/tagutils.o: tagutils/tagutils-ogg.c tagutils/tagutils-aac.h tagutils/tagutils.h tagutils/tagutils-mp3.h tagutils/tagutils-ogg.h log.h -playlist.o: playlist.h -inotify.o: inotify.h playlist.h -image_utils.o: image_utils.h -tivo_utils.o: config.h tivo_utils.h -tivo_beacon.o: config.h tivo_beacon.h tivo_utils.h -tivo_commands.o: config.h tivo_commands.h tivo_utils.h utils.h -utils.o: utils.h -sql.o: sql.h -log.o: log.h - -.SUFFIXES: .c .o - -.c.o: - @echo Compiling $*.c - @$(CC) $(CFLAGS) -o $@ -c $< && exit 0;\ - echo "The following command failed:" 1>&2;\ - echo "$(CC) $(CFLAGS) -o $@ -c $<";\ - $(CC) $(CFLAGS) -o $@ -c $< &>/dev/null +upnpdescgen.o: config.h upnpdescgen.h miniupnpdpath.h upnpglobalvars.h +upnpdescgen.o: miniupnpdtypes.h upnpdescstrings.h @@ -1,9 +1,9 @@ MiniDLNA project -(c) 2009 Justin Maggard -Portions (c) 2006-2007 Thomas Bernard +(c) 2008 Justin Maggard +Parts (c) 2006-2007 Thomas Bernard webpage: http://sourceforge.net/projects/minidlna/ -This directory contains the MiniDLNA daemon software. +This directory contain the MiniDLNA daemon software. This software is subject to the conditions detailed in the LICENCE file provided with this distribution. diff --git a/commonrdr.h b/commonrdr.h new file mode 100644 index 0000000..7efc3de --- /dev/null +++ b/commonrdr.h @@ -0,0 +1,35 @@ +/* MiniUPnP project + * (c) 2006-2007 Thomas Bernard + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#ifndef __COMMONRDR_H__ +#define __COMMONRDR_H__ + +#include "config.h" + +/* init and shutdown functions */ +int +init_redirect(void); + +void +shutdown_redirect(void); + +/* get_redirect_rule() gets internal IP and port from + * interface, external port and protocl + */ +int +get_redirect_rule(const char * ifname, unsigned short eport, int proto, + char * iaddr, int iaddrlen, unsigned short * iport, + char * desc, int desclen, + u_int64_t * packets, u_int64_t * bytes); + +int +get_redirect_rule_by_index(int index, + char * ifname, unsigned short * eport, + char * iaddr, int iaddrlen, unsigned short * iport, + int * proto, char * desc, int desclen, + u_int64_t * packets, u_int64_t * bytes); + +#endif + diff --git a/config.h b/config.h new file mode 100644 index 0000000..530d926 --- /dev/null +++ b/config.h @@ -0,0 +1,55 @@ +/* MiniUPnP Project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * generated by ./genconfig.sh on Thu Sep 11 15:05:26 PDT 2008 */ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#define UPNP_VERSION "20070827" +#define USE_NETFILTER 1 +#define OS_NAME "JM" +#define OS_VERSION "Linux/2.6.25.14-108.fc9.i686" +#define OS_URL "http://www.kernel.org/" + +/* syslog facility to be used by miniupnpd */ +#define LOG_MINIUPNPD LOG_DAEMON + +/* Uncomment the following line to allow miniupnpd to be + * controlled by miniupnpdctl */ +/*#define USE_MINIUPNPDCTL*/ + +/* Comment the following line to disable NAT-PMP operations */ +//#define ENABLE_NATPMP + +/* Uncomment the following line to enable generation of + * filter rules with pf */ +/*#define PF_ENABLE_FILTER_RULES*/ + +/* Uncomment the following line to enable caching of results of + * the getifstats() function */ +/*#define ENABLE_GETIFSTATS_CACHING*/ +/* The cache duration is indicated in seconds */ +#define GETIFSTATS_CACHING_DURATION 2 + +/* Uncomment the following line to enable multiple external ip support */ +/* note : Thas is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */ +/*#define MULTIPLE_EXTERNAL_IP*/ + +/* Comment the following line to use home made daemonize() func instead + * of BSD daemon() */ +#define USE_DAEMON + +/* Uncomment the following line to enable lease file support */ +/*#define ENABLE_LEASEFILE*/ + +/* Define one or none of the two following macros in order to make some + * clients happy. It will change the XML Root Description of the IGD. + * Enabling the Layer3Forwarding Service seems to be the more compatible + * option. */ +/*#define HAS_DUMMY_SERVICE*/ +#define ENABLE_L3F_SERVICE + +/* Experimental UPnP Events support. */ +#define ENABLE_EVENTS + +#endif diff --git a/daemonize.c b/daemonize.c index b1bfe9c..4bfbc00 100644 --- a/daemonize.c +++ b/daemonize.c @@ -1,45 +1,22 @@ /* $Id$ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> -#include <errno.h> +#include <syslog.h> #include <string.h> #include <signal.h> #include "daemonize.h" #include "config.h" -#include "log.h" #ifndef USE_DAEMON @@ -93,24 +70,24 @@ writepidfile(const char * fname, int pid) if(!fname || (strlen(fname) == 0)) return -1; - if( (pidfile = open(fname, O_WRONLY|O_CREAT, 0644)) < 0) + if( (pidfile = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "Unable to open pidfile for writing %s: %s\n", fname, strerror(errno)); + syslog(LOG_ERR, "Unable to open pidfile for writing %s: %m", fname); return -1; } pidstringlen = snprintf(pidstring, sizeof(pidstring), "%d\n", pid); if(pidstringlen <= 0) { - DPRINTF(E_ERROR, L_GENERAL, - "Unable to write to pidfile %s: snprintf(): FAILED\n", fname); + syslog(LOG_ERR, + "Unable to write to pidfile %s: snprintf(): FAILED", fname); close(pidfile); return -1; } else { if(write(pidfile, pidstring, pidstringlen) < 0) - DPRINTF(E_ERROR, L_GENERAL, "Unable to write to pidfile %s: %s\n", fname, strerror(errno)); + syslog(LOG_ERR, "Unable to write to pidfile %s: %m", fname); } close(pidfile); diff --git a/daemonize.h b/daemonize.h index ee763bd..4939afe 100644 --- a/daemonize.h +++ b/daemonize.h @@ -1,31 +1,9 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __DAEMONIZE_H__ #define __DAEMONIZE_H__ diff --git a/genconfig.sh b/genconfig.sh index ea933fd..3d59456 100755 --- a/genconfig.sh +++ b/genconfig.sh @@ -1,76 +1,42 @@ #! /bin/sh # $Id$ -# MiniDLNA project -# http://sourceforge.net/projects/minidlna/ -# -# MiniDLNA media server -# Copyright (C) 2008-2009 Justin Maggard -# -# This file is part of MiniDLNA. -# -# MiniDLNA is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# MiniDLNA is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. +# miniupnp daemon +# http://miniupnp.free.fr or http://miniupnp.tuxfamily.org/ +# (c) 2006-2007 Thomas Bernard +# This software is subject to the conditions detailed in the +# LICENCE file provided within the distribution RM="rm -f" CONFIGFILE="config.h" CONFIGMACRO="__CONFIG_H__" -# Database path -DB_PATH="/tmp/minidlna" -# Log path -LOG_PATH="${DB_PATH}" +# version reported in XML descriptions +UPNP_VERSION=20070827 +# Facility to syslog +LOG_MINIUPNPD="LOG_DAEMON" # detecting the OS name and version OS_NAME=`uname -s` OS_VERSION=`uname -r` -TIVO="/*#define TIVO_SUPPORT*/" -NETGEAR="/*#define NETGEAR*/" -READYNAS="/*#define READYNAS*/" -PNPX="#define PNPX 0" -${RM} ${CONFIGFILE} - -# Detect if there are missing headers -# NOTE: This check only works with a normal distro -[ ! -e "/usr/include/sqlite3.h" ] && MISSING="libsqlite3 $MISSING" -[ ! -e "/usr/include/jpeglib.h" ] && MISSING="libjpeg $MISSING" -[ ! -e "/usr/include/libexif/exif-loader.h" ] && MISSING="libexif $MISSING" -[ ! -e "/usr/include/id3tag.h" ] && MISSING="libid3tag $MISSING" -[ ! -e "/usr/include/ogg/ogg.h" ] && MISSING="libogg $MISSING" -[ ! -e "/usr/include/vorbis/codec.h" ] && MISSING="libvorbis $MISSING" -[ ! -e "/usr/include/FLAC/metadata.h" ] && MISSING="libflac $MISSING" -[ ! -e "/usr/include/ffmpeg/avutil.h" -a \ - ! -e "/usr/include/libavutil/avutil.h" -a \ - ! -e "/usr/include/ffmpeg/libavutil/avutil.h" ] && MISSING="libavutil $MISSING" -[ ! -e "/usr/include/ffmpeg/avformat.h" -a \ - ! -e "/usr/include/libavformat/avformat.h" -a \ - ! -e "/usr/include/ffmpeg/libavformat/avformat.h" ] && MISSING="libavformat $MISSING" -[ ! -e "/usr/include/ffmpeg/avcodec.h" -a \ - ! -e "/usr/include/libavcodec/avcodec.h" -a \ - ! -e "/usr/include/ffmpeg/libavcodec/avcodec.h" ] && MISSING="libavcodec $MISSING" -if [ -n "$MISSING" ]; then - echo -e "\nERROR! Cannot continue." - echo -e "The following required libraries are either missing, or are missing development headers:\n" - echo -e "$MISSING\n" - exit 1 +# pfSense special case +if [ -f /etc/platform ]; then + if [ `cat /etc/platform` = "pfSense" ]; then + OS_NAME=pfSense + OS_VERSION=`cat /etc/version` + fi fi -echo "/* MiniDLNA Project" >> ${CONFIGFILE} -echo " * http://sourceforge.net/projects/minidlna/" >> ${CONFIGFILE} -echo " * (c) 2008-2009 Justin Maggard" >> ${CONFIGFILE} +${RM} ${CONFIGFILE} + +echo "/* MiniUPnP Project" >> ${CONFIGFILE} +echo " * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/" >> ${CONFIGFILE} +echo " * (c) 2006-2008 Thomas Bernard" >> ${CONFIGFILE} echo " * generated by $0 on `date` */" >> ${CONFIGFILE} echo "#ifndef $CONFIGMACRO" >> ${CONFIGFILE} echo "#define $CONFIGMACRO" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} +echo "#define UPNP_VERSION \"$UPNP_VERSION\"" >> ${CONFIGFILE} # OS Specific stuff case $OS_NAME in @@ -86,6 +52,7 @@ case $OS_NAME in if [ \( $MAJORVER -ge 4 \) -o \( $MAJORVER -eq 3 -a $MINORVER -ge 8 \) ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi + echo "#define USE_PF 1" >> ${CONFIGFILE} OS_URL=http://www.openbsd.org/ ;; FreeBSD) @@ -93,14 +60,25 @@ case $OS_NAME in if [ $VER -ge 700049 ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi + if [ -f /usr/include/net/pfvar.h ] ; then + echo "#define USE_PF 1" >> ${CONFIGFILE} + else + echo "#define USE_IPF 1" >> ${CONFIGFILE} + fi OS_URL=http://www.freebsd.org/ ;; pfSense) # we need to detect if PFRULE_INOUT_COUNTS macro is needed + echo "#define USE_PF 1" >> ${CONFIGFILE} OS_URL=http://www.pfsense.com/ ;; NetBSD) OS_URL=http://www.netbsd.org/ + if [ -f /usr/include/net/pfvar.h ] ; then + echo "#define USE_PF 1" >> ${CONFIGFILE} + else + echo "#define USE_IPF 1" >> ${CONFIGFILE} + fi ;; SunOS) echo "#define USE_IPF 1" >> ${CONFIGFILE} @@ -119,55 +97,60 @@ case $OS_NAME in KERNVERC=`echo $OS_VERSION | awk -F. '{print $3}'` KERNVERD=`echo $OS_VERSION | awk -F. '{print $4}'` #echo "$KERNVERA.$KERNVERB.$KERNVERC.$KERNVERD" - # NETGEAR ReadyNAS special case - if [ -f /etc/raidiator_version ]; then - OS_NAME=$(awk -F'!!|=' '{ print $1 }' /etc/raidiator_version) - OS_VERSION=$(awk -F'!!|[=,.]' '{ print $3"."$4 }' /etc/raidiator_version) - OS_URL="http://www.readynas.com/" - LOG_PATH="/var/log" - DB_PATH="/var/cache/minidlna" - TIVO="#define TIVO_SUPPORT" - NETGEAR="#define NETGEAR" - READYNAS="#define READYNAS" - PNPX="#define PNPX 5" # Debian GNU/Linux special case - elif [ -f /etc/debian_version ]; then + if [ -f /etc/debian_version ]; then OS_NAME=Debian OS_VERSION=`cat /etc/debian_version` OS_URL=http://www.debian.org/ - LOG_PATH="/var/log" - # use lsb_release (Linux Standard Base) when available - LSB_RELEASE=`which lsb_release 2>/dev/null` - if [ 0 -eq $? ]; then - OS_NAME=`${LSB_RELEASE} -i -s` - OS_VERSION=`${LSB_RELEASE} -r -s` - fi - else - # use lsb_release (Linux Standard Base) when available - LSB_RELEASE=`which lsb_release 2>/dev/null` - if [ 0 -eq $? ]; then - OS_NAME=`${LSB_RELEASE} -i -s` - OS_VERSION=`${LSB_RELEASE} -r -s` - fi fi + # use lsb_release (Linux Standard Base) when available + LSB_RELEASE=`which lsb_release` + if [ 0 -eq $? ]; then + OS_NAME=`${LSB_RELEASE} -i -s` + OS_VERSION=`${LSB_RELEASE} -r -s` + fi + echo "#define USE_NETFILTER 1" >> ${CONFIGFILE} ;; *) echo "Unknown OS : $OS_NAME" + echo "Please contact the author at http://miniupnp.free.fr/" exit 1 ;; esac -echo "#define OS_NAME \"$OS_NAME\"" >> ${CONFIGFILE} -echo "#define OS_VERSION \"$OS_NAME/$OS_VERSION\"" >> ${CONFIGFILE} -echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE} +echo "#define OS_NAME \"$OS_NAME\"" >> ${CONFIGFILE} +echo "#define OS_VERSION \"$OS_NAME/$OS_VERSION\"" >> ${CONFIGFILE} +echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* syslog facility to be used by miniupnpd */" >> ${CONFIGFILE} +echo "#define LOG_MINIUPNPD ${LOG_MINIUPNPD}" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* full path of the file database */" >> ${CONFIGFILE} -echo "#define DEFAULT_DB_PATH \"${DB_PATH}\"" >> ${CONFIGFILE} +echo "/* Uncomment the following line to allow miniupnpd to be" >> ${CONFIGFILE} +echo " * controlled by miniupnpdctl */" >> ${CONFIGFILE} +echo "/*#define USE_MINIUPNPDCTL*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* full path of the log directory */" >> ${CONFIGFILE} -echo "#define DEFAULT_LOG_PATH \"${LOG_PATH}\"" >> ${CONFIGFILE} +echo "/* Comment the following line to disable NAT-PMP operations */" >> ${CONFIGFILE} +echo "#define ENABLE_NATPMP" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable generation of" >> ${CONFIGFILE} +echo " * filter rules with pf */" >> ${CONFIGFILE} +echo "/*#define PF_ENABLE_FILTER_RULES*/">> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable caching of results of" >> ${CONFIGFILE} +echo " * the getifstats() function */" >> ${CONFIGFILE} +echo "/*#define ENABLE_GETIFSTATS_CACHING*/" >> ${CONFIGFILE} +echo "/* The cache duration is indicated in seconds */" >> ${CONFIGFILE} +echo "#define GETIFSTATS_CACHING_DURATION 2" >> ${CONFIGFILE} +echo "" >> ${CONFIGFILE} + +echo "/* Uncomment the following line to enable multiple external ip support */" >> ${CONFIGFILE} +echo "/* note : Thas is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */" >> ${CONFIGFILE} +echo "/*#define MULTIPLE_EXTERNAL_IP*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Comment the following line to use home made daemonize() func instead" >> ${CONFIGFILE} @@ -175,39 +158,20 @@ echo " * of BSD daemon() */" >> ${CONFIGFILE} echo "#define USE_DAEMON" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* Enable if the system inotify.h exists. Otherwise our own inotify.h will be used. */" >> ${CONFIGFILE} -if [ -f /usr/include/sys/inotify.h ]; then -echo "#define HAVE_INOTIFY_H" >> ${CONFIGFILE} -else -echo "/*#define HAVE_INOTIFY_H*/" >> ${CONFIGFILE} -fi +echo "/* Uncomment the following line to enable lease file support */" >> ${CONFIGFILE} +echo "/*#define ENABLE_LEASEFILE*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* Enable if the system iconv.h exists. ID3 tag reading in various character sets will not work properly otherwise. */" >> ${CONFIGFILE} -if [ -f /usr/include/iconv.h ]; then -echo "#define HAVE_ICONV_H" >> ${CONFIGFILE} -else -echo -e "\nWARNING!! Iconv support not found. ID3 tag reading may not work." -echo "/*#define HAVE_ICONV_H*/" >> ${CONFIGFILE} -fi -echo "" >> ${CONFIGFILE} - -echo "/* Enable if the system libintl.h exists for NLS support. */" >> ${CONFIGFILE} -if [ -f /usr/include/libintl.h ]; then -echo "#define ENABLE_NLS" >> ${CONFIGFILE} -else -echo "/*#define ENABLE_NLS*/" >> ${CONFIGFILE} -fi +echo "/* Define one or none of the two following macros in order to make some" >> ${CONFIGFILE} +echo " * clients happy. It will change the XML Root Description of the IGD." >> ${CONFIGFILE} +echo " * Enabling the Layer3Forwarding Service seems to be the more compatible" >> ${CONFIGFILE} +echo " * option. */" >> ${CONFIGFILE} +echo "/*#define HAS_DUMMY_SERVICE*/" >> ${CONFIGFILE} +echo "#define ENABLE_L3F_SERVICE" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} -echo "/* Enable NETGEAR-specific tweaks. */" >> ${CONFIGFILE} -echo "${NETGEAR}" >> ${CONFIGFILE} -echo "/* Enable ReadyNAS-specific tweaks. */" >> ${CONFIGFILE} -echo "${READYNAS}" >> ${CONFIGFILE} -echo "/* Compile in TiVo support. */" >> ${CONFIGFILE} -echo "${TIVO}" >> ${CONFIGFILE} -echo "/* Enable PnPX support. */" >> ${CONFIGFILE} -echo "${PNPX}" >> ${CONFIGFILE} +echo "/* Experimental UPnP Events support. */" >> ${CONFIGFILE} +echo "/*#define ENABLE_EVENTS*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "#endif" >> ${CONFIGFILE} diff --git a/getifaddr.c b/getifaddr.c index f0cbdfc..c7d10a0 100644 --- a/getifaddr.c +++ b/getifaddr.c @@ -1,35 +1,12 @@ -/* $Id: getifaddr.c,v 1.14 2011/05/02 23:50:52 jmaggard Exp $ */ +/* $Id$ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -#include <stdio.h> -#include <stdlib.h> + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <string.h> +#include <syslog.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/types.h> @@ -37,14 +14,11 @@ #include <net/if.h> #include <arpa/inet.h> #include <netinet/in.h> -#include <netdb.h> -#include <errno.h> #if defined(sun) #include <sys/sockio.h> #endif #include "getifaddr.h" -#include "log.h" int getifaddr(const char * ifname, char * buf, int len) @@ -54,186 +28,28 @@ getifaddr(const char * ifname, char * buf, int len) struct ifreq ifr; int ifrlen; struct sockaddr_in * addr; - uint32_t mask; - int i; - ifrlen = sizeof(ifr); s = socket(PF_INET, SOCK_DGRAM, 0); if(s < 0) { - DPRINTF(E_ERROR, L_GENERAL, "socket(PF_INET, SOCK_DGRAM): %s\n", strerror(errno)); + syslog(LOG_ERR, "socket(PF_INET, SOCK_DGRAM): %m"); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ); if(ioctl(s, SIOCGIFADDR, &ifr, &ifrlen) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "ioctl(s, SIOCGIFADDR, ...): %s\n", strerror(errno)); + syslog(LOG_ERR, "ioctl(s, SIOCGIFADDR, ...): %m"); close(s); return -1; } addr = (struct sockaddr_in *)&ifr.ifr_addr; if(!inet_ntop(AF_INET, &addr->sin_addr, buf, len)) { - DPRINTF(E_ERROR, L_GENERAL, "inet_ntop(): %s\n", strerror(errno)); + syslog(LOG_ERR, "inet_ntop(): %m"); close(s); return -1; } - if(ioctl(s, SIOCGIFNETMASK, &ifr, &ifrlen) == 0) - { - addr = (struct sockaddr_in *)&ifr.ifr_netmask; - mask = ntohl(addr->sin_addr.s_addr); - for (i = 0; i < 32; i++) - { - if ((mask >> i) & 1) - break; - } - mask = 32 - i; - if (mask) - { - i = strlen(buf); - snprintf(buf+i, len-i, "/%u", mask); - } - } - else - DPRINTF(E_ERROR, L_GENERAL, "ioctl(s, SIOCGIFNETMASK, ...): %s\n", strerror(errno)); close(s); return 0; } -int -getsysaddr(char * buf, int len) -{ - int i; - int s = socket(PF_INET, SOCK_STREAM, 0); - struct sockaddr_in addr; - struct ifreq ifr; - uint32_t mask; - int ret = -1; - - for (i=1; i > 0; i++) - { - ifr.ifr_ifindex = i; - if( ioctl(s, SIOCGIFNAME, &ifr) < 0 ) - break; - if(ioctl(s, SIOCGIFADDR, &ifr, sizeof(struct ifreq)) < 0) - continue; - memcpy(&addr, &ifr.ifr_addr, sizeof(addr)); - if(strncmp(inet_ntoa(addr.sin_addr), "127.", 4) == 0) - continue; - if(ioctl(s, SIOCGIFNETMASK, &ifr, sizeof(struct ifreq)) < 0) - continue; - if(!inet_ntop(AF_INET, &addr.sin_addr, buf, len)) - { - DPRINTF(E_ERROR, L_GENERAL, "inet_ntop(): %s\n", strerror(errno)); - close(s); - break; - } - ret = 0; - - memcpy(&addr, &ifr.ifr_netmask, sizeof(addr)); - mask = ntohl(addr.sin_addr.s_addr); - for (i = 0; i < 32; i++) - { - if ((mask >> i) & 1) - break; - } - mask = 32 - i; - if (mask) - { - i = strlen(buf); - snprintf(buf+i, len-i, "/%u", mask); - } - break; - } - close(s); - - return(ret); -} - -int -getsyshwaddr(char * buf, int len) -{ - struct if_nameindex *ifaces, *if_idx; - unsigned char mac[6]; - struct ifreq ifr; - int fd; - int ret = -1; - - memset(&mac, '\0', sizeof(mac)); - /* Get the spatially unique node identifier */ - fd = socket(AF_INET, SOCK_DGRAM, 0); - if( fd < 0 ) - return(ret); - - ifaces = if_nameindex(); - if(!ifaces) - return(ret); - - for(if_idx = ifaces; if_idx->if_index; if_idx++) - { - strncpy(ifr.ifr_name, if_idx->if_name, IFNAMSIZ); - if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) - continue; - if(ifr.ifr_ifru.ifru_flags & IFF_LOOPBACK) - continue; - if( ioctl(fd, SIOCGIFHWADDR, &ifr) < 0 ) - continue; - if( MACADDR_IS_ZERO(ifr.ifr_hwaddr.sa_data) ) - continue; - ret = 0; - break; - } - if_freenameindex(ifaces); - close(fd); - - if(ret == 0) - { - if(len > 12) - { - memmove(mac, ifr.ifr_hwaddr.sa_data, 6); - sprintf(buf, "%02x%02x%02x%02x%02x%02x", - mac[0]&0xFF, mac[1]&0xFF, mac[2]&0xFF, - mac[3]&0xFF, mac[4]&0xFF, mac[5]&0xFF); - } - else if(len == 6) - { - memmove(buf, ifr.ifr_hwaddr.sa_data, 6); - } - } - return ret; -} - -int -get_remote_mac(struct in_addr ip_addr, unsigned char * mac) -{ - struct in_addr arp_ent; - FILE * arp; - char remote_ip[16]; - int matches, hwtype, flags; - memset(mac, 0xFF, 6); - - arp = fopen("/proc/net/arp", "r"); - if( !arp ) - return 1; - while( !feof(arp) ) - { - matches = fscanf(arp, "%15s 0x%8X 0x%8X %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", - remote_ip, &hwtype, &flags, - &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); - if( matches != 9 ) - continue; - inet_pton(AF_INET, remote_ip, &arp_ent); - if( ip_addr.s_addr == arp_ent.s_addr ) - break; - mac[0] = 0xFF; - } - fclose(arp); - - if( mac[0] == 0xFF ) - { - memset(mac, 0xFF, 6); - return 1; - } - - return 0; -} diff --git a/getifaddr.h b/getifaddr.h index 331a051..053c2c1 100644 --- a/getifaddr.h +++ b/getifaddr.h @@ -1,42 +1,11 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __GETIFADDR_H__ #define __GETIFADDR_H__ -#include <arpa/inet.h> - -#define MACADDR_IS_ZERO(x) \ - ((x[0] == 0x00) && \ - (x[1] == 0x00) && \ - (x[2] == 0x00) && \ - (x[3] == 0x00) && \ - (x[4] == 0x00) && \ - (x[5] == 0x00)) /* getifaddr() * take a network interface name and write the @@ -45,14 +14,5 @@ int getifaddr(const char * ifname, char * buf, int len); -int -getsysaddr(char * buf, int len); - -int -getsyshwaddr(char * buf, int len); - -int -get_remote_mac(struct in_addr ip_addr, unsigned char * mac); - #endif diff --git a/linux/miniupnpd.init.d.script b/linux/miniupnpd.init.d.script new file mode 100644 index 0000000..1f25cf0 --- /dev/null +++ b/linux/miniupnpd.init.d.script @@ -0,0 +1,40 @@ +#!/bin/sh +# $Id$ +# MiniUPnP project +# author: Thomas Bernard +# website: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + +MINIUPNPD=/usr/sbin/miniupnpd +ARGS='-f /etc/miniupnpd/miniupnpd.conf' + +IPTABLES_CREATE=/etc/miniupnpd/iptables_init.sh +IPTABLES_REMOVE=/etc/miniupnpd/iptables_removeall.sh + +test -f $MINIUPNPD || exit 0 + +. /lib/lsb/init-functions + +case "$1" in +start) log_daemon_msg "Starting miniupnpd" "miniupnpd" + $IPTABLES_CREATE > /dev/null 2>&1 + start-stop-daemon --start --quiet --pidfile /var/run/miniupnpd.pid --startas $MINIUPNPD -- $ARGS $LSBNAMES + log_end_msg $? + ;; +stop) log_daemon_msg "Stopping miniupnpd" "miniupnpd" + start-stop-daemon --stop --quiet --pidfile /var/run/miniupnpd.pid + log_end_msg $? + $IPTABLES_REMOVE > /dev/null 2>&1 + ;; +restart|reload|force-reload) + log_daemon_msg "Restarting miniupnpd" "miniupnpd" + start-stop-daemon --stop --retry 5 --quiet --pidfile /var/run/miniupnpd.pid + $IPTABLES_REMOVE > /dev/null 2>&1 + $IPTABLES_CREATE > /dev/null 2>&1 + start-stop-daemon --start --quiet --pidfile /var/run/miniupnpd.pid --startas $MINIUPNPD -- $ARGS $LSBNAMES + log_end_msg $? + ;; +*) log_action_msg "Usage: /etc/init.d/miniupnpd {start|stop|restart|reload|force-reload}" + exit 2 + ;; +esac +exit 0 @@ -1,19 +1,19 @@ -/* MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard +/* MiniDLNA media server + * Copyright (C) 2008 Justin Maggard * - * This file is part of MiniDLNA. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdio.h> #include <ctype.h> @@ -21,1464 +21,386 @@ #include <stdlib.h> #include <sys/stat.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - +#include <sqlite3.h> +#include <taglib/tag_c.h> #include <libexif/exif-loader.h> -#include "image_utils.h" -#include <jpeglib.h> -#include <setjmp.h> -#include <avutil.h> -#include <avcodec.h> -#include <avformat.h> -#include "tagutils/tagutils.h" #include "upnpglobalvars.h" -#include "upnpreplyparse.h" #include "metadata.h" -#include "albumart.h" -#include "utils.h" -#include "sql.h" -#include "log.h" -#ifndef FF_PROFILE_H264_BASELINE -#define FF_PROFILE_H264_BASELINE 66 -#endif -#ifndef FF_PROFILE_H264_MAIN -#define FF_PROFILE_H264_MAIN 77 -#endif -#ifndef FF_PROFILE_H264_HIGH -#define FF_PROFILE_H264_HIGH 100 -#endif +#define FLAG_ARTIST 0x01 -#define FLAG_TITLE 0x00000001 -#define FLAG_ARTIST 0x00000002 -#define FLAG_ALBUM 0x00000004 -#define FLAG_GENRE 0x00000008 -#define FLAG_COMMENT 0x00000010 -#define FLAG_CREATOR 0x00000020 -#define FLAG_DATE 0x00000040 -#define FLAG_DLNA_PN 0x00000080 -#define FLAG_MIME 0x00000100 -#define FLAG_DURATION 0x00000200 -#define FLAG_RESOLUTION 0x00000400 -#define FLAG_BITRATE 0x00000800 -#define FLAG_FREQUENCY 0x00001000 -#define FLAG_BPS 0x00002000 -#define FLAG_CHANNELS 0x00004000 - -/* Audio profile flags */ -enum audio_profiles { - PROFILE_AUDIO_UNKNOWN, - PROFILE_AUDIO_MP3, - PROFILE_AUDIO_AC3, - PROFILE_AUDIO_WMA_BASE, - PROFILE_AUDIO_WMA_FULL, - PROFILE_AUDIO_WMA_PRO, - PROFILE_AUDIO_MP2, - PROFILE_AUDIO_PCM, - PROFILE_AUDIO_AAC, - PROFILE_AUDIO_AAC_MULT5, - PROFILE_AUDIO_AMR -}; - -/* This function shamelessly copied from libdlna */ -#define MPEG_TS_SYNC_CODE 0x47 -#define MPEG_TS_PACKET_LENGTH 188 /* prepends 4 bytes to TS packet */ -#define MPEG_TS_PACKET_LENGTH_DLNA 192 /* prepends 4 bytes to TS packet */ -int -dlna_timestamp_is_present(const char * filename) +char * +trim(char *str) { - unsigned char buffer[2*MPEG_TS_PACKET_LENGTH_DLNA+1]; - int fd, i; - - /* read file header */ - fd = open(filename, O_RDONLY); - read(fd, buffer, MPEG_TS_PACKET_LENGTH_DLNA*2); - close(fd); - for( i=0; i < MPEG_TS_PACKET_LENGTH_DLNA; i++ ) - { - if( buffer[i] == MPEG_TS_SYNC_CODE ) - { - if (buffer[i + MPEG_TS_PACKET_LENGTH_DLNA] == MPEG_TS_SYNC_CODE) - { - if (buffer[i+MPEG_TS_PACKET_LENGTH] == 0x00 && - buffer[i+MPEG_TS_PACKET_LENGTH+1] == 0x00 && - buffer[i+MPEG_TS_PACKET_LENGTH+2] == 0x00 && - buffer[i+MPEG_TS_PACKET_LENGTH+3] == 0x00) - break; - else - return 1; - } - } - } - return 0; + if (!str) + return(NULL); + int i; + for (i=0; i <= strlen(str) && (isspace(str[i]) || str[i] == '"'); i++) { + str++; + } + for (i=(strlen(str)-1); i >= 0 && (isspace(str[i]) || str[i] == '"'); i--) { + str[i] = '\0'; + } + return str; } -#ifdef TIVO_SUPPORT -int -is_tivo_file(const char * path) +char * +modifyString(char * string, const char * before, const char * after, short like) { - unsigned char buf[5]; - unsigned char hdr[5] = { 'T','i','V','o','\0' }; - int fd; - - /* read file header */ - fd = open(path, O_RDONLY); - read(fd, buf, 5); - close(fd); + int oldlen, newlen, chgcnt = 0; + char *s, *p, *t; - return( !memcmp(buf, hdr, 5) ); -} -#endif - -void -check_for_captions(const char * path, sqlite_int64 detailID) -{ - char *file = malloc(PATH_MAX); - char *id = NULL; - - sprintf(file, "%s", path); - strip_ext(file); - - /* If we weren't given a detail ID, look for one. */ - if( !detailID ) + oldlen = strlen(before); + newlen = strlen(after); + if( newlen > oldlen ) { - id = sql_get_text_field(db, "SELECT ID from DETAILS where PATH glob '%q.*'" - " and MIME glob 'video/*' limit 1", file); - if( id ) + s = string; + while( (p = strstr(s, before)) ) { - //DEBUG DPRINTF(E_DEBUG, L_METADATA, "New file %s looks like a caption file.\n", path); - detailID = strtoll(id, NULL, 10); - } - else - { - //DPRINTF(E_DEBUG, L_METADATA, "No file found for caption %s.\n", path); - goto no_source_video; + chgcnt++; + s = p+oldlen; } + string = realloc(string, strlen(string)+((newlen-oldlen)*chgcnt)+1); } - strcat(file, ".srt"); - if( access(file, R_OK) == 0 ) + s = string; + while( s ) { - sql_exec(db, "INSERT into CAPTIONS" - " (ID, PATH) " - "VALUES" - " (%lld, %Q)", detailID, file); - } -no_source_video: - if( id ) - sqlite3_free(id); - free(file); -} - -void -parse_nfo(const char * path, metadata_t * m) -{ - FILE *nfo; - char buf[65536]; - struct NameValueParserData xml; - struct stat file; - size_t nread; - char *val, *val2; - - if( stat(path, &file) != 0 || - file.st_size > 65536 ) - { - DPRINTF(E_INFO, L_METADATA, "Not parsing very large .nfo file %s\n", path); - return; - } - DPRINTF(E_DEBUG, L_METADATA, "Parsing .nfo file: %s\n", path); - nfo = fopen(path, "r"); - if( !nfo ) - return; - nread = fread(&buf, 1, sizeof(buf), nfo); - - ParseNameValue(buf, nread, &xml); - - //printf("\ttype: %s\n", GetValueFromNameValueList(&xml, "rootElement")); - val = GetValueFromNameValueList(&xml, "title"); - if( val ) - { - val2 = GetValueFromNameValueList(&xml, "episodetitle"); - if( val2 ) - asprintf(&m->title, "%s - %s", val, val2); - else - m->title = strdup(val); + p = strcasestr(s, before); + if( !p ) + return string; + if( like ) + { + t = p+oldlen; + while( isspace(*t) ) + t++; + if( *t == '"' ) + while( *++t != '"' ) + continue; + memmove(t+1, t, strlen(t)+1); + *t = '%'; + } + memmove(p + newlen, p + oldlen, strlen(p + oldlen) + 1); + memcpy(p, after, newlen); + s = p + newlen; } + if( newlen < oldlen ) + string = realloc(string, strlen(string)+1); - val = GetValueFromNameValueList(&xml, "plot"); - if( val ) - m->comment = strdup(val); - - val = GetValueFromNameValueList(&xml, "capturedate"); - if( val ) - m->date = strdup(val); - - val = GetValueFromNameValueList(&xml, "genre"); - if( val ) - m->genre = strdup(val); - - ClearNameValueList(&xml); - fclose(nfo); + return string; } void -free_metadata(metadata_t * m, uint32_t flags) +strip_ext(char * name) { - if( m->title && (flags & FLAG_TITLE) ) - free(m->title); - if( m->artist && (flags & FLAG_ARTIST) ) - free(m->artist); - if( m->album && (flags & FLAG_ALBUM) ) - free(m->album); - if( m->genre && (flags & FLAG_GENRE) ) - free(m->genre); - if( m->creator && (flags & FLAG_CREATOR) ) - free(m->creator); - if( m->date && (flags & FLAG_DATE) ) - free(m->date); - if( m->comment && (flags & FLAG_COMMENT) ) - free(m->comment); - if( m->dlna_pn && (flags & FLAG_DLNA_PN) ) - free(m->dlna_pn); - if( m->mime && (flags & FLAG_MIME) ) - free(m->mime); - if( m->duration && (flags & FLAG_DURATION) ) - free(m->duration); - if( m->resolution && (flags & FLAG_RESOLUTION) ) - free(m->resolution); - if( m->bitrate && (flags & FLAG_BITRATE) ) - free(m->bitrate); - if( m->frequency && (flags & FLAG_FREQUENCY) ) - free(m->frequency); - if( m->bps && (flags & FLAG_BPS) ) - free(m->bps); - if( m->channels && (flags & FLAG_CHANNELS) ) - free(m->channels); -} - -sqlite_int64 -GetFolderMetadata(const char * name, const char * path, const char * artist, const char * genre, const char * album_art) -{ - int ret; - - ret = sql_exec(db, "INSERT into DETAILS" - " (TITLE, PATH, CREATOR, ARTIST, GENRE, ALBUM_ART) " - "VALUES" - " ('%q', %Q, %Q, %Q, %Q, %lld);", - name, path, artist, artist, genre, - album_art ? strtoll(album_art, NULL, 10) : 0); - if( ret != SQLITE_OK ) - ret = 0; - else - ret = sqlite3_last_insert_rowid(db); - - return ret; + if( rindex(name, '.') ) + *rindex(name, '.') = '\0'; } sqlite_int64 GetAudioMetadata(const char * path, char * name) { - char type[4]; - static char lang[6] = { '\0' }; + size_t size = 0; + char date[16], duration[16], dlna_pn[24], mime[16]; struct stat file; + int seconds, minutes; sqlite_int64 ret; - char *esc_tag; - int i; - sqlite_int64 album_art = 0; - struct song_metadata song; - metadata_t m; - uint32_t free_flags = FLAG_MIME|FLAG_DURATION|FLAG_DLNA_PN|FLAG_DATE; - memset(&m, '\0', sizeof(metadata_t)); - - if ( stat(path, &file) != 0 ) + TagLib_File *audio_file; + TagLib_Tag *tag; + const TagLib_AudioProperties *properties; + char *sql; + char *zErrMsg = NULL; + char *title, *artist, *album, *genre, *comment; + int free_flags = 0; + + if ( stat(path, &file) == 0 ) + size = file.st_size; + else return 0; strip_ext(name); - if( ends_with(path, ".mp3") ) - { - strcpy(type, "mp3"); - m.mime = strdup("audio/mpeg"); - } - else if( ends_with(path, ".m4a") || ends_with(path, ".mp4") || - ends_with(path, ".aac") || ends_with(path, ".m4p") ) - { - strcpy(type, "aac"); - m.mime = strdup("audio/mp4"); - } - else if( ends_with(path, ".3gp") ) - { - strcpy(type, "aac"); - m.mime = strdup("audio/3gpp"); - } - else if( ends_with(path, ".wma") || ends_with(path, ".asf") ) - { - strcpy(type, "asf"); - m.mime = strdup("audio/x-ms-wma"); - } - else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") ) - { - strcpy(type, "flc"); - m.mime = strdup("audio/x-flac"); - } - else if( ends_with(path, ".wav") ) - { - strcpy(type, "wav"); - m.mime = strdup("audio/x-wav"); - } - else if( ends_with(path, ".ogg") || ends_with(path, ".oga") ) - { - strcpy(type, "ogg"); - m.mime = strdup("audio/ogg"); - } - else if( ends_with(path, ".pcm") ) + taglib_set_strings_unicode(1); + + audio_file = taglib_file_new(path); + if(audio_file == NULL) + return 0; + + tag = taglib_file_tag(audio_file); + properties = taglib_file_audioproperties(audio_file); + + seconds = taglib_audioproperties_length(properties) % 60; + minutes = (taglib_audioproperties_length(properties) - seconds) / 60; + + date[0] = '\0'; + if( taglib_tag_year(tag) ) + sprintf(date, "%04d-01-01", taglib_tag_year(tag)); + sprintf(duration, "%d:%02d:%02d.000", minutes/60, minutes, seconds); + + title = taglib_tag_title(tag); + if( strlen(title) ) { - strcpy(type, "pcm"); - m.mime = strdup("audio/L16"); + title = trim(title); + if( index(title, '&') ) + { + title = modifyString(strdup(title), "&", "&amp;", 0); + } } else { - DPRINTF(E_WARN, L_GENERAL, "Unhandled file extension on %s\n", path); - return 0; + title = name; } - - if( !(*lang) ) + artist = taglib_tag_artist(tag); + if( strlen(artist) ) { - if( !getenv("LANG") ) - strcpy(lang, "en_US"); - else - strncpy(lang, getenv("LANG"), 5); - lang[5] = '\0'; + artist = trim(artist); + if( index(artist, '&') ) + { + free_flags |= FLAG_ARTIST; + artist = modifyString(strdup(artist), "&", "&amp;", 0); + } } - - if( readtags((char *)path, &song, &file, lang, type) != 0 ) + else { - DPRINTF(E_WARN, L_GENERAL, "Cannot extract tags from %s!\n", path); - freetags(&song); - free_metadata(&m, free_flags); - return 0; + artist = NULL; } - - if( song.dlna_pn ) - asprintf(&m.dlna_pn, "%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", song.dlna_pn); - if( song.year ) - asprintf(&m.date, "%04d-01-01", song.year); - asprintf(&m.duration, "%d:%02d:%02d.%03d", - (song.song_length/3600000), - (song.song_length/60000%60), - (song.song_length/1000%60), - (song.song_length%1000)); - if( song.title && *song.title ) + album = taglib_tag_album(tag); + if( strlen(album) ) { - m.title = trim(song.title); - if( (esc_tag = escape_tag(m.title, 0)) ) + album = trim(album); + if( index(album, '&') ) { - free_flags |= FLAG_TITLE; - m.title = esc_tag; + album = modifyString(strdup(album), "&", "&amp;", 0); } } else { - m.title = name; + album = NULL; } - for( i=ROLE_START; i<N_ROLE; i++ ) + genre = taglib_tag_genre(tag); + if( strlen(genre) ) { - if( song.contributor[i] && *song.contributor[i] ) + genre = trim(genre); + if( index(genre, '&') ) { - m.creator = trim(song.contributor[i]); - if( strlen(m.creator) > 48 ) - { - m.creator = strdup("Various Artists"); - free_flags |= FLAG_CREATOR; - } - else if( (esc_tag = escape_tag(m.creator, 0)) ) - { - m.creator = esc_tag; - free_flags |= FLAG_CREATOR; - } - m.artist = m.creator; - break; + genre = modifyString(strdup(genre), "&", "&amp;", 0); } } - /* If there is a band associated with the album, use it for virtual containers. */ - if( (i != ROLE_BAND) && (i != ROLE_ALBUMARTIST) ) + else { - if( song.contributor[ROLE_BAND] && *song.contributor[ROLE_BAND] ) - { - i = ROLE_BAND; - m.artist = trim(song.contributor[i]); - if( strlen(m.artist) > 48 ) - { - m.artist = strdup("Various Artists"); - free_flags |= FLAG_ARTIST; - } - else if( (esc_tag = escape_tag(m.artist, 0)) ) - { - m.artist = esc_tag; - free_flags |= FLAG_ARTIST; - } - } + genre = NULL; } - if( song.album && *song.album ) + comment = taglib_tag_comment(tag); + if( strlen(comment) ) { - m.album = trim(song.album); - if( (esc_tag = escape_tag(m.album, 0)) ) + comment = trim(comment); + if( index(comment, '&') ) { - free_flags |= FLAG_ALBUM; - m.album = esc_tag; + comment = modifyString(strdup(comment), "&", "&amp;", 0); } } - if( song.genre && *song.genre ) + else { - m.genre = trim(song.genre); - if( (esc_tag = escape_tag(m.genre, 0)) ) - { - free_flags |= FLAG_GENRE; - m.genre = esc_tag; - } + comment = NULL; } - if( song.comment && *song.comment ) + + + if( 1 ) // Switch on audio file type { - m.comment = trim(song.comment); - if( (esc_tag = escape_tag(m.comment, 0)) ) - { - free_flags |= FLAG_COMMENT; - m.comment = esc_tag; - } + strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01"); + strcpy(mime, "audio/mpeg"); } - album_art = find_album_art(path, song.image, song.image_size); + sql = sqlite3_mprintf( "INSERT into DETAILS" + " (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," + " TITLE, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) " + "VALUES" + " (%d, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');", + size, duration, taglib_audioproperties_channels(properties), + taglib_audioproperties_bitrate(properties)*1024, + taglib_audioproperties_samplerate(properties), + strlen(date) ? date : NULL, + title, + artist, + album, + genre, + comment, + taglib_tag_track(tag), + dlna_pn, mime); + + taglib_tag_free_strings(); + taglib_file_free(audio_file); + + if( free_flags & FLAG_ARTIST ) + free(artist); - ret = sql_exec(db, "INSERT into DETAILS" - " (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," - " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) " - "VALUES" - " (%Q, %lld, %ld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);", - path, file.st_size, file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date, - m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track, - m.dlna_pn, song.mime?song.mime:m.mime, album_art); - if( ret != SQLITE_OK ) + //DEBUG printf("SQL: %s\n", sql); + if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) { - fprintf(stderr, "Error inserting details for '%s'!\n", path); + fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg); + if (zErrMsg) + sqlite3_free(zErrMsg); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } - freetags(&song); - free_metadata(&m, free_flags); - + sqlite3_free(sql); return ret; } -/* For libjpeg error handling */ -jmp_buf setjmp_buffer; -static void -libjpeg_error_handler(j_common_ptr cinfo) -{ - cinfo->err->output_message (cinfo); - longjmp(setjmp_buffer, 1); - return; -} - sqlite_int64 GetImageMetadata(const char * path, char * name) { ExifData *ed; ExifEntry *e = NULL; - ExifLoader *l; - struct jpeg_decompress_struct cinfo; - struct jpeg_error_mgr jerr; - FILE *infile; + ExifTag tag; int width=0, height=0, thumb=0; - char make[32], model[64] = {'\0'}; + size_t size; + char date[64], make[32], model[64], dlna_pn[64]; char b[1024]; struct stat file; sqlite_int64 ret; - image_s * imsrc; - metadata_t m; - uint32_t free_flags = 0xFFFFFFFF; - memset(&m, '\0', sizeof(metadata_t)); + char *sql; + char *zErrMsg = NULL; + + date[0] = '\0'; + model[0] = '\0'; + dlna_pn[0] = '\0'; - //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path); - if ( stat(path, &file) != 0 ) + //DEBUG printf("Parsing %s...\n", path); + if ( stat(path, &file) == 0 ) + size = file.st_size; + else return 0; strip_ext(name); - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size); + //DEBUG printf(" * size: %d\n", size); - /* MIME hard-coded to JPEG for now, until we add PNG support */ - asprintf(&m.mime, "image/jpeg"); - - l = exif_loader_new(); + ExifLoader * l = exif_loader_new(); exif_loader_write_file(l, path); ed = exif_loader_get_data(l); exif_loader_unref(l); - if( !ed ) - goto no_exifdata; - e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL); - if( e || (e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED)) ) { - m.date = strdup(exif_entry_get_value(e, b, sizeof(b))); - if( strlen(m.date) > 10 ) + tag = EXIF_TAG_PIXEL_X_DIMENSION; + e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], tag); + if( e ) + width = atoi( exif_entry_get_value(e, b, sizeof(b)) ); + + tag = EXIF_TAG_PIXEL_Y_DIMENSION; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag); + if( e ) + height = atoi( exif_entry_get_value(e, b, sizeof(b)) ); + //DEBUG printf(" * resolution: %dx%d\n", width, height); + + tag = EXIF_TAG_DATE_TIME_ORIGINAL; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag); + if( e ) { + strncpy(date, exif_entry_get_value(e, b, sizeof(b)), sizeof(date)); + if( strlen(date) > 10 ) { - m.date[4] = '-'; - m.date[7] = '-'; - m.date[10] = 'T'; + date[4] = '-'; + date[7] = '-'; + date[10] = 'T'; } else { - free(m.date); - m.date = NULL; + strcpy(date, "0000-00-00"); } } else { - /* One last effort to get the date from XMP */ - image_get_jpeg_date_xmp(path, &m.date); + strcpy(date, "0000-00-00"); } - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * date: %s\n", m.date); + //DEBUG printf(" * date: %s\n", date); - e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_MAKE); + model[0] = '\0'; + tag = EXIF_TAG_MAKE; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag); if( e ) { strncpy(make, exif_entry_get_value(e, b, sizeof(b)), sizeof(make)); - e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_MODEL); + tag = EXIF_TAG_MODEL; + e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag); if( e ) { strncpy(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model)); if( !strcasestr(model, make) ) snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b))); - m.creator = strdup(model); } } - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * model: %s\n", model); + if( !strlen(model) ) + strcpy(model, "Unknown"); + //DEBUG printf(" * model: %s\n", model); if( ed->size ) - { - /* We might need to verify that the thumbnail is 160x160 or smaller */ - if( ed->size > 12000 ) - { - imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1); - if( imsrc ) - { - if( (imsrc->width <= 160) && (imsrc->height <= 160) ) - thumb = 1; - image_free(imsrc); - } - } - else - thumb = 1; - } - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * thumbnail: %d\n", thumb); + thumb = 1; + else + thumb = 0; + //DEBUG printf(" * thumbnail: %d\n", thumb); exif_data_unref(ed); -no_exifdata: - /* If SOF parsing fails, then fall through to reading the JPEG data with libjpeg to get the resolution */ - if( image_get_jpeg_resolution(path, &width, &height) != 0 || !width || !height ) - { - infile = fopen(path, "r"); - cinfo.err = jpeg_std_error(&jerr); - jerr.error_exit = libjpeg_error_handler; - jpeg_create_decompress(&cinfo); - if( setjmp(setjmp_buffer) ) - goto error; - jpeg_stdio_src(&cinfo, infile); - jpeg_read_header(&cinfo, TRUE); - jpeg_start_decompress(&cinfo); - width = cinfo.output_width; - height = cinfo.output_height; - error: - jpeg_destroy_decompress(&cinfo); - fclose(infile); - } - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * resolution: %dx%d\n", width, height); - - if( !width || !height ) - { - free_metadata(&m, free_flags); - return 0; - } if( width <= 640 && height <= 480 ) - asprintf(&m.dlna_pn, "JPEG_SM;%s", dlna_no_conv); + strcpy(dlna_pn, "JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); else if( width <= 1024 && height <= 768 ) - asprintf(&m.dlna_pn, "JPEG_MED;%s", dlna_no_conv); - else if( (width <= 4096 && height <= 4096) || !(GETFLAG(DLNA_STRICT_MASK)) ) - asprintf(&m.dlna_pn, "JPEG_LRG;%s", dlna_no_conv); - asprintf(&m.resolution, "%dx%d", width, height); - - ret = sql_exec(db, "INSERT into DETAILS" - " (PATH, TITLE, SIZE, TIMESTAMP, DATE, RESOLUTION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " - "VALUES" - " (%Q, '%q', %lld, %ld, %Q, %Q, %d, %Q, %Q, %Q);", - path, name, file.st_size, file.st_mtime, m.date, m.resolution, thumb, m.creator, m.dlna_pn, m.mime); - if( ret != SQLITE_OK ) - { - fprintf(stderr, "Error inserting details for '%s'!\n", path); + strcpy(dlna_pn, "JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + else if( width <= 4096 && height <= 4096 ) + strcpy(dlna_pn, "JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); + else + strcpy(dlna_pn, "JPEG_XL"); + + sql = sqlite3_mprintf( "INSERT into DETAILS" + " (TITLE, SIZE, DATE, WIDTH, HEIGHT, THUMBNAIL, CREATOR, DLNA_PN, MIME) " + "VALUES" + " ('%q', %d, '%s', %d, %d, %d, '%q', '%s', '%s');", + name, size, date, width, height, thumb, model, dlna_pn, "image/jpeg"); + //DEBUG printf("SQL: %s\n", sql); + if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) + { + fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg); + if (zErrMsg) + sqlite3_free(zErrMsg); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } - free_metadata(&m, free_flags); - + sqlite3_free(sql); return ret; } sqlite_int64 GetVideoMetadata(const char * path, char * name) { + size_t size = 0; struct stat file; - int ret, i; - struct tm *modtime; - AVFormatContext *ctx; - AVCodecContext *ac = NULL, *vc = NULL; - int audio_stream = -1, video_stream = -1; - enum audio_profiles audio_profile = PROFILE_AUDIO_UNKNOWN; - tsinfo_t *ts; - char fourcc[4]; - sqlite_int64 album_art = 0; - char nfo[PATH_MAX], *ext; - struct song_metadata video; - metadata_t m; - uint32_t free_flags = 0xFFFFFFFF; - memset(&m, '\0', sizeof(m)); - memset(&video, '\0', sizeof(video)); + char *sql; + char *zErrMsg = NULL; + int ret; - //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing video %s...\n", name); - if ( stat(path, &file) != 0 ) - return 0; + //DEBUG printf("Parsing %s...\n", path); + if ( stat(path, &file) == 0 ) + size = file.st_size; strip_ext(name); - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size); - - av_register_all(); - if( av_open_input_file(&ctx, path, NULL, 0, NULL) != 0 ) - { - DPRINTF(E_WARN, L_METADATA, "Opening %s failed!\n", path); - return 0; - } - av_find_stream_info(ctx); - //dump_format(ctx, 0, NULL, 0); - for( i=0; i<ctx->nb_streams; i++) - { - if( audio_stream == -1 && - ctx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) - { - audio_stream = i; - ac = ctx->streams[audio_stream]->codec; - continue; - } - else if( video_stream == -1 && - ctx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) - { - video_stream = i; - vc = ctx->streams[video_stream]->codec; - continue; - } - } - /* This must not be a video file. */ - if( !vc ) - { - av_close_input_file(ctx); - if( !is_audio(path) ) - DPRINTF(E_DEBUG, L_METADATA, "File %s does not contain a video stream.\n", basename(path)); - return 0; - } - - strcpy(nfo, path); - ext = strrchr(nfo, '.'); - if( ext ) - { - strcpy(ext+1, "nfo"); - if( access(nfo, F_OK) == 0 ) - { - parse_nfo(nfo, &m); - } - } - - if( !m.date ) - { - m.date = malloc(20); - modtime = localtime(&file.st_mtime); - strftime(m.date, 20, "%FT%T", modtime); - } - - if( ac ) - { - aac_object_type_t aac_type = AAC_INVALID; - switch( ac->codec_id ) - { - case CODEC_ID_MP3: - audio_profile = PROFILE_AUDIO_MP3; - break; - case CODEC_ID_AAC: - if( !ac->extradata_size || - !ac->extradata ) - { - DPRINTF(E_DEBUG, L_METADATA, "No AAC type\n"); - } - else - { - uint8_t data; - memcpy(&data, ac->extradata, 1); - aac_type = data >> 3; - } - switch( aac_type ) - { - /* AAC Low Complexity variants */ - case AAC_LC: - case AAC_LC_ER: - if( ac->sample_rate < 8000 || - ac->sample_rate > 48000 ) - { - DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n", - ac->sample_rate); - break; - } - /* AAC @ Level 1/2 */ - if( ac->channels <= 2 && - ac->bit_rate <= 576000 ) - audio_profile = PROFILE_AUDIO_AAC; - else if( ac->channels <= 6 && - ac->bit_rate <= 1440000 ) - audio_profile = PROFILE_AUDIO_AAC_MULT5; - else - DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n", - ac->channels, - ac->bit_rate); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", aac_type); - break; - } - break; - case CODEC_ID_AC3: - case CODEC_ID_DTS: - audio_profile = PROFILE_AUDIO_AC3; - break; - case CODEC_ID_WMAV1: - case CODEC_ID_WMAV2: - /* WMA Baseline: stereo, up to 48 KHz, up to 192,999 bps */ - if ( ac->bit_rate <= 193000 ) - audio_profile = PROFILE_AUDIO_WMA_BASE; - /* WMA Full: stereo, up to 48 KHz, up to 385 Kbps */ - else if ( ac->bit_rate <= 385000 ) - audio_profile = PROFILE_AUDIO_WMA_FULL; - break; - #if LIBAVCODEC_VERSION_INT > ((51<<16)+(50<<8)+1) - case CODEC_ID_WMAPRO: - audio_profile = PROFILE_AUDIO_WMA_PRO; - break; - #endif - case CODEC_ID_MP2: - audio_profile = PROFILE_AUDIO_MP2; - break; - case CODEC_ID_AMR_NB: - audio_profile = PROFILE_AUDIO_AMR; - break; - default: - if( (ac->codec_id >= CODEC_ID_PCM_S16LE) && - (ac->codec_id < CODEC_ID_ADPCM_IMA_QT) ) - audio_profile = PROFILE_AUDIO_PCM; - else - DPRINTF(E_DEBUG, L_METADATA, "Unhandled audio codec [0x%X]\n", ac->codec_id); - break; - } - asprintf(&m.frequency, "%u", ac->sample_rate); - #if LIBAVCODEC_VERSION_INT < (52<<16) - asprintf(&m.bps, "%u", ac->bits_per_sample); - #else - asprintf(&m.bps, "%u", ac->bits_per_coded_sample); - #endif - asprintf(&m.channels, "%u", ac->channels); - } - if( vc ) - { - int off; - int duration, hours, min, sec, ms; - ts_timestamp_t ts_timestamp = NONE; - DPRINTF(E_DEBUG, L_METADATA, "Container: '%s' [%s]\n", ctx->iformat->name, basename(path)); - asprintf(&m.resolution, "%dx%d", vc->width, vc->height); - if( ctx->bit_rate > 8 ) - asprintf(&m.bitrate, "%u", ctx->bit_rate / 8); - if( ctx->duration > 0 ) { - duration = (int)(ctx->duration / AV_TIME_BASE); - hours = (int)(duration / 3600); - min = (int)(duration / 60 % 60); - sec = (int)(duration % 60); - ms = (int)(ctx->duration / (AV_TIME_BASE/1000) % 1000); - asprintf(&m.duration, "%d:%02d:%02d.%03d", hours, min, sec, ms); - } - - /* NOTE: The DLNA spec only provides for ASF (WMV), TS, PS, and MP4 containers. Skip DLNA parsing for everything else. */ - if( strcmp(ctx->iformat->name, "avi") == 0 ) - { - asprintf(&m.mime, "video/x-msvideo"); - if( vc->codec_id == CODEC_ID_MPEG4 ) - { - fourcc[0] = vc->codec_tag & 0xff; - fourcc[1] = vc->codec_tag>>8 & 0xff; - fourcc[2] = vc->codec_tag>>16 & 0xff; - fourcc[3] = vc->codec_tag>>24 & 0xff; - if( memcmp(fourcc, "XVID", 4) == 0 || - memcmp(fourcc, "DX50", 4) == 0 || - memcmp(fourcc, "DIVX", 4) == 0 ) - asprintf(&m.creator, "DiVX"); - } - } - else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 && - ends_with(path, ".mov") ) - asprintf(&m.mime, "video/quicktime"); - else if( strncmp(ctx->iformat->name, "matroska", 8) == 0 ) - asprintf(&m.mime, "video/x-matroska"); - else if( strcmp(ctx->iformat->name, "flv") == 0 ) - asprintf(&m.mime, "video/x-flv"); - if( m.mime ) - goto video_no_dlna; - - switch( vc->codec_id ) - { - case CODEC_ID_MPEG1VIDEO: - if( strcmp(ctx->iformat->name, "mpeg") == 0 ) - { - if( (vc->width == 352) && - (vc->height <= 288) ) - { - asprintf(&m.dlna_pn, "MPEG1;%s", dlna_no_conv); - } - asprintf(&m.mime, "video/mpeg"); - } - break; - case CODEC_ID_MPEG2VIDEO: - m.dlna_pn = malloc(64); - off = sprintf(m.dlna_pn, "MPEG_"); - if( strcmp(ctx->iformat->name, "mpegts") == 0 ) - { - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s MPEG2 TS\n", video_stream, basename(path), m.resolution); - off += sprintf(m.dlna_pn+off, "TS_"); - if( (vc->width >= 1280) && - (vc->height >= 720) ) - { - off += sprintf(m.dlna_pn+off, "HD_NA"); - } - else - { - off += sprintf(m.dlna_pn+off, "SD_"); - if( (vc->height == 576) || - (vc->height == 288) ) - off += sprintf(m.dlna_pn+off, "EU"); - else - off += sprintf(m.dlna_pn+off, "NA"); - } - ts = ctx->priv_data; - if( ts->packet_size == 192 ) - { - if( dlna_timestamp_is_present(path) ) - ts_timestamp = VALID; - else - ts_timestamp = EMPTY; - } - else if( ts->packet_size != 188 ) - { - DPRINTF(E_DEBUG, L_METADATA, "Invalid TS packet size [%s]\n", basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - } - switch( ts_timestamp ) - { - case NONE: - asprintf(&m.mime, "video/mpeg"); - off += sprintf(m.dlna_pn+off, "_ISO"); - break; - case VALID: - off += sprintf(m.dlna_pn+off, "_T"); - case EMPTY: - asprintf(&m.mime, "video/vnd.dlna.mpeg-tts"); - default: - break; - } - } - else if( strcmp(ctx->iformat->name, "mpeg") == 0 ) - { - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s MPEG2 PS\n", video_stream, basename(path), m.resolution); - off += sprintf(m.dlna_pn+off, "PS_"); - if( (vc->height == 576) || - (vc->height == 288) ) - off += sprintf(m.dlna_pn+off, "PAL"); - else - off += sprintf(m.dlna_pn+off, "NTSC"); - asprintf(&m.mime, "video/mpeg"); - } - else - { - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s [UNKNOWN CONTAINER] is %s MPEG2\n", video_stream, basename(path), m.resolution); - free(m.dlna_pn); - m.dlna_pn = NULL; - } - if( m.dlna_pn ) - sprintf(m.dlna_pn+off, ";%s", dlna_no_conv); - break; - case CODEC_ID_H264: - m.dlna_pn = malloc(128); - off = sprintf(m.dlna_pn, "AVC_"); - - if( strcmp(ctx->iformat->name, "mpegts") == 0 ) - { - off += sprintf(m.dlna_pn+off, "TS_"); - switch( vc->profile ) - { - case FF_PROFILE_H264_BASELINE: - off += sprintf(m.dlna_pn+off, "BL_"); - if( vc->width <= 352 && - vc->height <= 288 && - vc->bit_rate <= 384000 ) - { - off += sprintf(m.dlna_pn+off, "CIF15_"); - break; - } - else if( vc->width <= 352 && - vc->height <= 288 && - vc->bit_rate <= 3000000 ) - { - off += sprintf(m.dlna_pn+off, "CIF30_"); - break; - } - /* Fall back to Main Profile if it doesn't match a Baseline DLNA profile. */ - else - off -= 3; - default: - case FF_PROFILE_H264_MAIN: - off += sprintf(m.dlna_pn+off, "MP_"); - if( vc->profile != FF_PROFILE_H264_BASELINE && - vc->profile != FF_PROFILE_H264_MAIN ) - { - DPRINTF(E_DEBUG, L_METADATA, "Unknown AVC profile %d; assuming MP. [%s]\n", - vc->profile, basename(path)); - } - if( vc->width <= 720 && - vc->height <= 576 && - vc->bit_rate <= 10000000 ) - { - off += sprintf(m.dlna_pn+off, "SD_"); - } - else if( vc->width <= 1920 && - vc->height <= 1152 && - vc->bit_rate <= 20000000 ) - { - off += sprintf(m.dlna_pn+off, "HD_"); - } - else - { - DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 video profile! [%s, %dx%d, %dbps : %s]\n", - m.dlna_pn, - vc->width, - vc->height, - vc->bit_rate, - basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - } - break; - case FF_PROFILE_H264_HIGH: - off += sprintf(m.dlna_pn+off, "HP_"); - if( vc->width <= 1920 && - vc->height <= 1152 && - vc->bit_rate <= 30000000 && - audio_profile == PROFILE_AUDIO_AC3 ) - { - off += sprintf(m.dlna_pn+off, "HD_"); - } - else - { - DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 HP video profile! [%dbps, %d audio : %s]\n", - vc->bit_rate, - audio_profile, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - } - break; - } - if( !m.dlna_pn ) - break; - switch( audio_profile ) - { - case PROFILE_AUDIO_MP3: - off += sprintf(m.dlna_pn+off, "MPEG1_L3"); - break; - case PROFILE_AUDIO_AC3: - off += sprintf(m.dlna_pn+off, "AC3"); - break; - case PROFILE_AUDIO_AAC: - case PROFILE_AUDIO_AAC_MULT5: - off += sprintf(m.dlna_pn+off, "AAC_MULT5"); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for %s file [%s]\n", - m.dlna_pn, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - if( !m.dlna_pn ) - break; - ts = ctx->priv_data; - if( ts->packet_size == 192 ) - { - if( vc->profile == FF_PROFILE_H264_HIGH || - dlna_timestamp_is_present(path) ) - ts_timestamp = VALID; - else - ts_timestamp = EMPTY; - } - switch( ts_timestamp ) - { - case NONE: - off += sprintf(m.dlna_pn+off, "_ISO"); - break; - case VALID: - off += sprintf(m.dlna_pn+off, "_T"); - case EMPTY: - asprintf(&m.mime, "video/vnd.dlna.mpeg-tts"); - default: - break; - } - } - else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) - { - off += sprintf(m.dlna_pn+off, "MP4_"); - - switch( vc->profile ) { - case FF_PROFILE_H264_BASELINE: - if( vc->width <= 352 && - vc->height <= 288 ) - { - if( ctx->bit_rate < 600000 ) - off += sprintf(m.dlna_pn+off, "BL_CIF15_"); - else if( ctx->bit_rate < 5000000 ) - off += sprintf(m.dlna_pn+off, "BL_CIF30_"); - else - goto mp4_mp_fallback; - - if( audio_profile == PROFILE_AUDIO_AMR ) - { - off += sprintf(m.dlna_pn+off, "AMR"); - } - else if( audio_profile == PROFILE_AUDIO_AAC ) - { - off += sprintf(m.dlna_pn+off, "AAC_"); - if( ctx->bit_rate < 520000 ) - { - off += sprintf(m.dlna_pn+off, "520"); - } - else if( ctx->bit_rate < 940000 ) - { - off += sprintf(m.dlna_pn+off, "940"); - } - else - { - off -= 13; - goto mp4_mp_fallback; - } - } - else - { - off -= 9; - goto mp4_mp_fallback; - } - } - else if( vc->width <= 720 && - vc->height <= 576 ) - { - if( vc->level == 30 && - audio_profile == PROFILE_AUDIO_AAC && - ctx->bit_rate <= 5000000 ) - off += sprintf(m.dlna_pn+off, "BL_L3L_SD_AAC"); - else if( vc->level <= 31 && - audio_profile == PROFILE_AUDIO_AAC && - ctx->bit_rate <= 15000000 ) - off += sprintf(m.dlna_pn+off, "BL_L31_HD_AAC"); - else - goto mp4_mp_fallback; - } - else if( vc->width <= 1280 && - vc->height <= 720 ) - { - if( vc->level <= 31 && - audio_profile == PROFILE_AUDIO_AAC && - ctx->bit_rate <= 15000000 ) - off += sprintf(m.dlna_pn+off, "BL_L31_HD_AAC"); - else if( vc->level <= 32 && - audio_profile == PROFILE_AUDIO_AAC && - ctx->bit_rate <= 21000000 ) - off += sprintf(m.dlna_pn+off, "BL_L32_HD_AAC"); - else - goto mp4_mp_fallback; - } - else - goto mp4_mp_fallback; - break; - case FF_PROFILE_H264_MAIN: - mp4_mp_fallback: - off += sprintf(m.dlna_pn+off, "MP_"); - /* AVC MP4 SD profiles - 10 Mbps max */ - if( vc->width <= 720 && - vc->height <= 576 && - vc->bit_rate <= 10000000 ) - { - sprintf(m.dlna_pn+off, "SD_"); - if( audio_profile == PROFILE_AUDIO_AC3 ) - off += sprintf(m.dlna_pn+off, "AC3"); - else if( audio_profile == PROFILE_AUDIO_AAC || - audio_profile == PROFILE_AUDIO_AAC_MULT5 ) - off += sprintf(m.dlna_pn+off, "AAC_MULT5"); - else if( audio_profile == PROFILE_AUDIO_MP3 ) - off += sprintf(m.dlna_pn+off, "MPEG1_L3"); - else - m.dlna_pn[10] = '\0'; - } - else if( vc->width <= 1280 && - vc->height <= 720 && - vc->bit_rate <= 15000000 && - audio_profile == PROFILE_AUDIO_AAC ) - { - off += sprintf(m.dlna_pn+off, "HD_720p_AAC"); - } - else if( vc->width <= 1920 && - vc->height <= 1080 && - vc->bit_rate <= 21000000 && - audio_profile == PROFILE_AUDIO_AAC ) - { - off += sprintf(m.dlna_pn+off, "HD_1080i_AAC"); - } - if( strlen(m.dlna_pn) <= 11 ) - { - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for %s file %s\n", - m.dlna_pn, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - } - break; - case FF_PROFILE_H264_HIGH: - if( vc->width <= 1920 && - vc->height <= 1080 && - vc->bit_rate <= 25000000 && - audio_profile == PROFILE_AUDIO_AAC ) - { - off += sprintf(m.dlna_pn+off, "HP_HD_AAC"); - } - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "AVC profile [%d] not recognized for file %s\n", - vc->profile, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - } - else - { - free(m.dlna_pn); - m.dlna_pn = NULL; - } - if( m.dlna_pn ) - sprintf(m.dlna_pn+off, ";%s", dlna_no_conv); - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is h.264\n", video_stream, basename(path)); - break; - case CODEC_ID_MPEG4: - fourcc[0] = vc->codec_tag & 0xff; - fourcc[1] = vc->codec_tag>>8 & 0xff; - fourcc[2] = vc->codec_tag>>16 & 0xff; - fourcc[3] = vc->codec_tag>>24 & 0xff; - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is MPEG4 [%c%c%c%c/0x%X]\n", - video_stream, basename(path), - isprint(fourcc[0]) ? fourcc[0] : '_', - isprint(fourcc[1]) ? fourcc[1] : '_', - isprint(fourcc[2]) ? fourcc[2] : '_', - isprint(fourcc[3]) ? fourcc[3] : '_', - vc->codec_tag); - - if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) - { - m.dlna_pn = malloc(128); - off = sprintf(m.dlna_pn, "MPEG4_P2_"); - - if( ends_with(path, ".3gp") ) - { - asprintf(&m.mime, "video/3gpp"); - switch( audio_profile ) - { - case PROFILE_AUDIO_AAC: - off += sprintf(m.dlna_pn+off, "3GPP_SP_L0B_AAC"); - break; - case PROFILE_AUDIO_AMR: - off += sprintf(m.dlna_pn+off, "3GPP_SP_L0B_AMR"); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for MPEG4-P2 3GP/0x%X file %s\n", - ac->codec_id, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - } - else - { - if( ctx->bit_rate <= 1000000 && - audio_profile == PROFILE_AUDIO_AAC ) - { - off += sprintf(m.dlna_pn+off, "MP4_ASP_AAC"); - } - else if( ctx->bit_rate <= 4000000 && - vc->width <= 640 && - vc->height <= 480 && - audio_profile == PROFILE_AUDIO_AAC ) - { - off += sprintf(m.dlna_pn+off, "MP4_SP_VGA_AAC"); - } - else - { - DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 video profile! [%dx%d, %dbps]\n", - vc->width, - vc->height, - ctx->bit_rate); - free(m.dlna_pn); - m.dlna_pn = NULL; - } - } - if( m.dlna_pn ) - sprintf(m.dlna_pn+off, ";%s", dlna_no_conv); - } - break; - case CODEC_ID_WMV3: - /* I'm not 100% sure this is correct, but it works on everything I could get my hands on */ - if( vc->extradata_size > 0 ) - { - if( !((vc->extradata[0] >> 3) & 1) ) - vc->level = 0; - if( !((vc->extradata[0] >> 6) & 1) ) - vc->profile = 0; - } - case CODEC_ID_VC1: - if( strcmp(ctx->iformat->name, "asf") != 0 ) - { - DPRINTF(E_DEBUG, L_METADATA, "Skipping DLNA parsing for non-ASF VC1 file %s\n", path); - break; - } - m.dlna_pn = malloc(64); - off = sprintf(m.dlna_pn, "WMV"); - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is VC1\n", video_stream, basename(path)); - asprintf(&m.mime, "video/x-ms-wmv"); - if( (vc->width <= 176) && - (vc->height <= 144) && - (vc->level == 0) ) - { - off += sprintf(m.dlna_pn+off, "SPLL_"); - switch( audio_profile ) - { - case PROFILE_AUDIO_MP3: - off += sprintf(m.dlna_pn+off, "MP3"); - break; - case PROFILE_AUDIO_WMA_BASE: - off += sprintf(m.dlna_pn+off, "BASE"); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVSPLL/0x%X file %s\n", audio_profile, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - } - else if( (vc->width <= 352) && - (vc->height <= 288) && - (vc->profile == 0) && - (ctx->bit_rate/8 <= 384000) ) - { - off += sprintf(m.dlna_pn+off, "SPML_"); - switch( audio_profile ) - { - case PROFILE_AUDIO_MP3: - off += sprintf(m.dlna_pn+off, "MP3"); - break; - case PROFILE_AUDIO_WMA_BASE: - off += sprintf(m.dlna_pn+off, "BASE"); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVSPML/0x%X file %s\n", audio_profile, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - } - else if( (vc->width <= 720) && - (vc->height <= 576) && - (ctx->bit_rate/8 <= 10000000) ) - { - off += sprintf(m.dlna_pn+off, "MED_"); - switch( audio_profile ) - { - case PROFILE_AUDIO_WMA_PRO: - off += sprintf(m.dlna_pn+off, "PRO"); - break; - case PROFILE_AUDIO_WMA_FULL: - off += sprintf(m.dlna_pn+off, "FULL"); - break; - case PROFILE_AUDIO_WMA_BASE: - off += sprintf(m.dlna_pn+off, "BASE"); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVMED/0x%X file %s\n", audio_profile, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - } - else if( (vc->width <= 1920) && - (vc->height <= 1080) && - (ctx->bit_rate/8 <= 20000000) ) - { - off += sprintf(m.dlna_pn+off, "HIGH_"); - switch( audio_profile ) - { - case PROFILE_AUDIO_WMA_PRO: - off += sprintf(m.dlna_pn+off, "PRO"); - break; - case PROFILE_AUDIO_WMA_FULL: - off += sprintf(m.dlna_pn+off, "FULL"); - break; - default: - DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVHIGH/0x%X file %s\n", audio_profile, basename(path)); - free(m.dlna_pn); - m.dlna_pn = NULL; - break; - } - } - if( m.dlna_pn ) - sprintf(m.dlna_pn+off, ";%s", dlna_no_conv); - break; - case CODEC_ID_MSMPEG4V3: - asprintf(&m.mime, "video/x-msvideo"); - default: - DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s [type %d]\n", video_stream, basename(path), m.resolution, vc->codec_id); - break; - } - } - if( !m.mime ) - { - if( strcmp(ctx->iformat->name, "avi") == 0 ) - asprintf(&m.mime, "video/x-msvideo"); - else if( strncmp(ctx->iformat->name, "mpeg", 4) == 0 ) - asprintf(&m.mime, "video/mpeg"); - else if( strcmp(ctx->iformat->name, "asf") == 0 ) - asprintf(&m.mime, "video/x-ms-wmv"); - else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) - if( ends_with(path, ".mov") ) - asprintf(&m.mime, "video/quicktime"); - else - asprintf(&m.mime, "video/mp4"); - else if( strncmp(ctx->iformat->name, "matroska", 8) == 0 ) - asprintf(&m.mime, "video/x-matroska"); - else if( strcmp(ctx->iformat->name, "flv") == 0 ) - asprintf(&m.mime, "video/x-flv"); - else - DPRINTF(E_WARN, L_METADATA, "%s: Unhandled format: %s\n", path, ctx->iformat->name); - } - - if( strcmp(ctx->iformat->name, "asf") == 0 ) - { - if( readtags((char *)path, &video, &file, "en_US", "asf") == 0 ) - { - if( video.title && *video.title ) - { - m.title = escape_tag(trim(video.title), 1); - } - if( video.genre && *video.genre ) - { - m.genre = escape_tag(trim(video.genre), 1); - } - if( video.contributor[ROLE_TRACKARTIST] && *video.contributor[ROLE_TRACKARTIST] ) - { - m.artist = escape_tag(trim(video.contributor[ROLE_TRACKARTIST]), 1); - } - if( video.contributor[ROLE_ALBUMARTIST] && *video.contributor[ROLE_ALBUMARTIST] ) - { - m.creator = escape_tag(trim(video.contributor[ROLE_ALBUMARTIST]), 1); - } - else - { - m.creator = m.artist; - free_flags &= ~FLAG_CREATOR; - } - } - } - #ifndef NETGEAR - #if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) - else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) - { - if( ctx->metadata ) - { - AVMetadataTag *tag = NULL; - - //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Metadata:\n"); - while( (tag = av_metadata_get(ctx->metadata, "", tag, AV_METADATA_IGNORE_SUFFIX)) ) - { - //DEBUG DPRINTF(E_DEBUG, L_METADATA, " %-16s: %s\n", tag->key, tag->value); - if( strcmp(tag->key, "title") == 0 ) - m.title = escape_tag(trim(tag->value), 1); - else if( strcmp(tag->key, "genre") == 0 ) - m.genre = escape_tag(trim(tag->value), 1); - else if( strcmp(tag->key, "artist") == 0 ) - m.artist = escape_tag(trim(tag->value), 1); - else if( strcmp(tag->key, "comment") == 0 ) - m.comment = escape_tag(trim(tag->value), 1); - } - } - } - #endif - #endif -video_no_dlna: - av_close_input_file(ctx); - -#ifdef TIVO_SUPPORT - if( ends_with(path, ".TiVo") && is_tivo_file(path) ) - { - if( m.dlna_pn ) - { - free(m.dlna_pn); - m.dlna_pn = NULL; - } - m.mime = realloc(m.mime, 18); - strcpy(m.mime, "video/x-tivo-mpeg"); - } -#endif - if( !m.title ) - m.title = strdup(name); - - album_art = find_album_art(path, video.image, video.image_size); - freetags(&video); - - ret = sql_exec(db, "INSERT into DETAILS" - " (PATH, SIZE, TIMESTAMP, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," - " TITLE, CREATOR, ARTIST, GENRE, COMMENT, DLNA_PN, MIME, ALBUM_ART) " - "VALUES" - " (%Q, %lld, %ld, %Q, %Q, %Q, %Q, %Q, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);", - path, file.st_size, file.st_mtime, m.duration, - m.date, m.channels, m.bitrate, m.frequency, m.resolution, - m.title, m.creator, m.artist, m.genre, m.comment, m.dlna_pn, - m.mime, album_art); - if( ret != SQLITE_OK ) - { - fprintf(stderr, "Error inserting details for '%s'!\n", path); + //DEBUG printf(" * size: %d\n", size); + + sql = sqlite3_mprintf( "INSERT into DETAILS" + " (TITLE, SIZE, MIME) " + "VALUES" + " ('%q', %d, %Q);", + name, size, "video/mpeg"); + //DEBUG printf("SQL: %s\n", sql); + if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) + { + fprintf(stderr, "Error inserting details for '%s'! [%s]\n", path, zErrMsg); + if (zErrMsg) + sqlite3_free(zErrMsg); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); - check_for_captions(path, ret); } - free_metadata(&m, free_flags); - + sqlite3_free(sql); return ret; } @@ -3,98 +3,16 @@ * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - */ + * Copyright (c) 2008 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ #ifndef __METADATA_H__ #define __METADATA_H__ -typedef struct metadata_s { - char *title; - char *artist; - char *creator; - char *album; - char *genre; - char *comment; - char *channels; - char *bitrate; - char *frequency; - char *bps; - char *resolution; - char *duration; - char *date; - char *mime; - char *dlna_pn; -} metadata_t; - -typedef struct tsinfo_s { - int x; - int packet_size; -} tsinfo_t; - -typedef enum { - AAC_INVALID = 0, - AAC_MAIN = 1, /* AAC Main */ - AAC_LC = 2, /* AAC Low complexity */ - AAC_SSR = 3, /* AAC SSR */ - AAC_LTP = 4, /* AAC Long term prediction */ - AAC_HE = 5, /* AAC High efficiency (SBR) */ - AAC_SCALE = 6, /* Scalable */ - AAC_TWINVQ = 7, /* TwinVQ */ - AAC_CELP = 8, /* CELP */ - AAC_HVXC = 9, /* HVXC */ - AAC_TTSI = 12, /* TTSI */ - AAC_MS = 13, /* Main synthetic */ - AAC_WAVE = 14, /* Wavetable synthesis */ - AAC_MIDI = 15, /* General MIDI */ - AAC_FX = 16, /* Algorithmic Synthesis and Audio FX */ - AAC_LC_ER = 17, /* AAC Low complexity with error recovery */ - AAC_LTP_ER = 19, /* AAC Long term prediction with error recovery */ - AAC_SCALE_ER = 20, /* AAC scalable with error recovery */ - AAC_TWINVQ_ER = 21, /* TwinVQ with error recovery */ - AAC_BSAC_ER = 22, /* BSAC with error recovery */ - AAC_LD_ER = 23, /* AAC LD with error recovery */ - AAC_CELP_ER = 24, /* CELP with error recovery */ - AAC_HXVC_ER = 25, /* HXVC with error recovery */ - AAC_HILN_ER = 26, /* HILN with error recovery */ - AAC_PARAM_ER = 27, /* Parametric with error recovery */ - AAC_SSC = 28, /* AAC SSC */ - AAC_HE_L3 = 31, /* Reserved : seems to be HeAAC L3 */ -} aac_object_type_t; - -typedef enum { - NONE, - EMPTY, - VALID -} ts_timestamp_t; - -int -ends_with(const char * haystack, const char * needle); - char * modifyString(char * string, const char * before, const char * after, short like); -void -check_for_captions(const char * path, sqlite_int64 detailID); - -sqlite_int64 -GetFolderMetadata(const char * name, const char * path, const char * artist, const char * genre, const char * album_art); - sqlite_int64 GetAudioMetadata(const char * path, char * name); @@ -1,50 +1,12 @@ /* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ + * (c) 2008 Justin Maggard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Portions of the code (c) Thomas Bernard, subject to + * the conditions detailed in the LICENSE.miniupnpd file. */ #include <stdlib.h> #include <unistd.h> @@ -57,46 +19,43 @@ #include <arpa/inet.h> #include <fcntl.h> #include <sys/file.h> +#include <syslog.h> #include <sys/time.h> #include <time.h> #include <signal.h> #include <sys/param.h> -#include <errno.h> -#include <pthread.h> -#include <pwd.h> +#if defined(sun) +#include <kstat.h> +#else +/* for BSD's sysctl */ +#include <sys/sysctl.h> +#endif -#include "config.h" +#include <sqlite3.h> -#ifdef ENABLE_NLS -#include <libintl.h> -#endif +/* unix sockets */ +#include "config.h" #include "upnpglobalvars.h" -#include "sql.h" #include "upnphttp.h" #include "upnpdescgen.h" -#include "minidlnapath.h" +#include "miniupnpdpath.h" #include "getifaddr.h" #include "upnpsoap.h" #include "options.h" -#include "utils.h" #include "minissdp.h" -#include "minidlnatypes.h" +#include "miniupnpdtypes.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 +#include "commonrdr.h" + +/* MAX_LAN_ADDR : maximum number of interfaces + * to listen to SSDP traffic */ +/*#define MAX_LAN_ADDR (4)*/ + +static volatile int quitting = 0; -#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 - /* OpenAndConfHTTPSocket() : * setup the socket used to handle incoming HTTP connections. */ static int @@ -106,18 +65,15 @@ OpenAndConfHTTPSocket(unsigned short port) int i = 1; struct sockaddr_in listenname; - /* Initialize client type cache */ - 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)); + syslog(LOG_ERR, "socket(http): %m"); 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)); + syslog(LOG_WARNING, "setsockopt(http, SO_REUSEADDR): %m"); } memset(&listenname, 0, sizeof(struct sockaddr_in)); @@ -127,14 +83,14 @@ OpenAndConfHTTPSocket(unsigned short port) if(bind(s, (struct sockaddr *)&listenname, sizeof(struct sockaddr_in)) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "bind(http): %s\n", strerror(errno)); + syslog(LOG_ERR, "bind(http): %m"); close(s); return -1; } if(listen(s, 6) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno)); + syslog(LOG_ERR, "listen(http): %m"); close(s); return -1; } @@ -150,7 +106,7 @@ sigterm(int sig) /*int save_errno = errno;*/ signal(sig, SIG_IGN); /* Ignore this signal while we are quitting */ - DPRINTF(E_WARN, L_GENERAL, "received signal %d, good-bye\n", sig); + syslog(LOG_NOTICE, "received signal %d, good-bye", sig); quitting = 1; /*errno = save_errno;*/ @@ -158,11 +114,45 @@ sigterm(int sig) /* record the startup time, for returning uptime */ static void -set_startup_time(void) +set_startup_time(int sysuptime) { startup_time = time(NULL); + if(sysuptime) + { + /* use system uptime instead of daemon uptime */ + char buff[64]; + int uptime, fd; + fd = open("/proc/uptime", O_RDONLY); + if(fd < 0) + { + syslog(LOG_ERR, "open(\"/proc/uptime\" : %m"); + } + else + { + memset(buff, 0, sizeof(buff)); + read(fd, buff, sizeof(buff) - 1); + uptime = atoi(buff); + syslog(LOG_INFO, "system uptime is %d seconds", uptime); + close(fd); + startup_time -= uptime; + } + } } +/* structure containing variables used during "main loop" + * that are filled during the init */ +struct runtime_vars { + /* LAN IP addresses for SSDP traffic and HTTP */ + /* moved to global vars */ + /*int n_lan_addr;*/ + /*struct lan_addr_s lan_addr[MAX_LAN_ADDR];*/ + int port; /* HTTP Port */ + int notify_interval; /* seconds between SSDP announces */ + /* unused rules cleaning related variables : */ + int clean_ruleset_threshold; /* threshold for removing unused rules */ + int clean_ruleset_interval; /* (minimum) interval between checks */ +}; + /* parselanaddr() * parse address with mask * ex: 192.168.1.1/24 @@ -187,164 +177,58 @@ parselanaddr(struct lan_addr_s * lan_addr, const char * str) } if(n>15) { - DPRINTF(E_OFF, L_GENERAL, "Error parsing address/mask: %s\n", str); + fprintf(stderr, "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); + fprintf(stderr, "Error parsing address/mask : %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 ) - { - strncpy(buf, hn, len-1); - buf[len] = '\0'; - 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 ) - { - strncpy(modelnumber, key+1, MODELNUMBER_MAX_LEN); - modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; - *key = '\0'; - } - snprintf(modelname, MODELNAME_MAX_LEN, - "Windows Media Connect compatible (%s)", val); - } - else if( strcmp(key, "serial") == 0 ) - { - strncpy(serialnumber, val, SERIALNUMBER_MAX_LEN); - serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; - if( serialnumber[0] == '\0' ) - { - char mac_str[13]; - if( getsyshwaddr(mac_str, sizeof(mac_str)) == 0 ) - strcpy(serialnumber, mac_str); - else - strcpy(serialnumber, "0"); +#ifdef MULTIPLE_EXTERNAL_IP + while(*p && isspace(*p)) + p++; + if(*p) { + n = 0; + while(p[n] && !isspace(*p)) + n++; + if(n<=15) { + memcpy(lan_addr->ext_ip_str, p, n); + lan_addr->ext_ip_str[n] = '\0'; + if(!inet_aton(lan_addr->ext_ip_str, &lan_addr->ext_ip_addr)) { + /* error */ + fprintf(stderr, "Error parsing address : %s\n", lan_addr->ext_ip_str); } - break; } } - fclose(info); - if( strcmp(modelnumber, "NVX") == 0 ) - memcpy(pnpx_hwid+4, "01F2&DEV_0101", 17); - else if( strcmp(modelnumber, "Pro") == 0 || - strcmp(modelnumber, "Pro 6") == 0 || - strncmp(modelnumber, "Ultra 6", 7) == 0 ) - memcpy(pnpx_hwid+4, "01F2&DEV_0102", 17); - else if( strcmp(modelnumber, "Pro 2") == 0 || - strncmp(modelnumber, "Ultra 2", 7) == 0 ) - memcpy(pnpx_hwid+4, "01F2&DEV_0103", 17); - else if( strcmp(modelnumber, "Pro 4") == 0 || - strncmp(modelnumber, "Ultra 4", 7) == 0 ) - memcpy(pnpx_hwid+4, "01F2&DEV_0104", 17); - else if( strcmp(modelnumber+1, "100") == 0 ) - memcpy(pnpx_hwid+4, "01F2&DEV_0105", 17); - else if( strcmp(modelnumber+1, "200") == 0 ) - memcpy(pnpx_hwid+4, "01F2&DEV_0106", 17); -#else - char * logname; - logname = getenv("LOGNAME"); -#ifndef STATIC // Disable for static linking - 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; + return 0; } /* init phase : * 1) read configuration file * 2) read command line arguments * 3) daemonize - * 4) check and write pid file - * 5) set startup time stamp - * 6) compute presentation URL - * 7) set signal handlers */ + * 4) open syslog + * 5) check and write pid file + * 6) set startup time stamp + * 7) compute presentation URL + * 8) set signal handlers */ static int -init(int argc, char * * argv) +init(int argc, char * * argv, struct runtime_vars * v) { int i; int pid; int debug_flag = 0; int options_flag = 0; + int openlog_option; struct sigaction sa; /*const char * logfilename = 0;*/ const char * presurl = 0; const char * optionsfile = "/etc/minidlna.conf"; - char mac_str[13]; - char * string, * word; - enum media_types type; - char * path; - char real_path[PATH_MAX]; - char ext_ip_addr[INET_ADDRSTRLEN + 3] = {'\0'}; /* first check if "-f" option is used */ for(i=2; i<argc; i++) @@ -357,19 +241,20 @@ init(int argc, char * * argv) } } - /* set up uuid based on mac address */ - if( getsyshwaddr(mac_str, sizeof(mac_str)) < 0 ) + /*v->n_lan_addr = 0;*/ + char ext_ip_addr[INET_ADDRSTRLEN]; + if( (getifaddr("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) && + (getifaddr("eth1", ext_ip_addr, INET_ADDRSTRLEN) < 0) ) { - DPRINTF(E_OFF, L_GENERAL, "No MAC address found. Falling back to generic UUID.\n"); - strcpy(mac_str, "554e4b4e4f57"); + printf("No IP!\n"); + return 1; } - 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; /* seconds between SSDP announces */ + if( parselanaddr(&lan_addr[n_lan_addr], ext_ip_addr) == 0 ) + n_lan_addr++; + v->port = -1; + v->notify_interval = 30; /* seconds between SSDP announces */ + v->clean_ruleset_threshold = 20; + v->clean_ruleset_interval = 0; /* interval between ruleset check. 0=disabled */ /* read options file first since * command line arguments have final say */ @@ -385,21 +270,19 @@ init(int argc, char * * argv) { switch(ary_options[i].id) { - case UPNPIFNAME: - if(getifaddr(ary_options[i].value, ext_ip_addr, sizeof(ext_ip_addr)) >= 0) - { - if( *ext_ip_addr && parselanaddr(&lan_addr[n_lan_addr], ext_ip_addr) == 0 ) - n_lan_addr++; - } - else - fprintf(stderr, "Interface %s not found, ignoring.\n", ary_options[i].value); + case UPNPEXT_IFNAME: + ext_if_name = ary_options[i].value; + break; + case UPNPEXT_IP: + use_ext_ip_addr = ary_options[i].value; break; case UPNPLISTENING_IP: - if(n_lan_addr < MAX_LAN_ADDR) + if(n_lan_addr < MAX_LAN_ADDR)/* if(v->n_lan_addr < MAX_LAN_ADDR)*/ { + /*if(parselanaddr(&v->lan_addr[v->n_lan_addr],*/ if(parselanaddr(&lan_addr[n_lan_addr], ary_options[i].value) == 0) - n_lan_addr++; + n_lan_addr++; /*v->n_lan_addr++; */ } else { @@ -408,137 +291,73 @@ init(int argc, char * * argv) } break; case UPNPPORT: - runtime_vars.port = atoi(ary_options[i].value); + v->port = atoi(ary_options[i].value); + break; + case UPNPBITRATE_UP: + upstream_bitrate = strtoul(ary_options[i].value, 0, 0); + break; + case UPNPBITRATE_DOWN: + downstream_bitrate = strtoul(ary_options[i].value, 0, 0); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: - runtime_vars.notify_interval = atoi(ary_options[i].value); + v->notify_interval = atoi(ary_options[i].value); + break; + case UPNPSYSTEM_UPTIME: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(SYSUPTIMEMASK); /*sysuptime = 1;*/ + break; + case UPNPPACKET_LOG: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(LOGPACKETSMASK); /*logpackets = 1;*/ + break; + case UPNPUUID: + strncpy(uuidvalue+5, ary_options[i].value, + strlen(uuidvalue+5) + 1); break; case UPNPSERIAL: strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; break; - case UPNPMODEL_NAME: - strncpy(modelname, ary_options[i].value, MODELNAME_MAX_LEN); - modelname[MODELNAME_MAX_LEN-1] = '\0'; - break; case UPNPMODEL_NUMBER: strncpy(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; break; - case UPNPFRIENDLYNAME: - strncpy(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); - friendly_name[FRIENDLYNAME_MAX_LEN-1] = '\0'; + case UPNPCLEANTHRESHOLD: + v->clean_ruleset_threshold = atoi(ary_options[i].value); break; - case UPNPMEDIADIR: - type = ALL_MEDIA; - char * myval = NULL; - switch( ary_options[i].value[0] ) - { - case 'A': - case 'a': - if( ary_options[i].value[0] == 'A' || ary_options[i].value[0] == 'a' ) - type = AUDIO_ONLY; - case 'V': - case 'v': - if( ary_options[i].value[0] == 'V' || ary_options[i].value[0] == 'v' ) - type = VIDEO_ONLY; - case 'P': - case 'p': - if( ary_options[i].value[0] == 'P' || ary_options[i].value[0] == 'p' ) - type = IMAGES_ONLY; - myval = index(ary_options[i].value, '/'); - case '/': - path = realpath(myval ? myval:ary_options[i].value, real_path); - if( !path ) - path = (myval ? myval:ary_options[i].value); - if( access(path, F_OK) != 0 ) - { - fprintf(stderr, "Media directory not accessible! [%s]\n", - path); - 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; - default: - fprintf(stderr, "Media directory entry not understood! [%s]\n", - ary_options[i].value); - break; - } + case UPNPCLEANINTERVAL: + v->clean_ruleset_interval = atoi(ary_options[i].value); 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; - } - } +#ifdef USE_PF + case UPNPQUEUE: + queue = ary_options[i].value; break; - case UPNPDBDIR: - path = realpath(ary_options[i].value, real_path); - 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; - } - strncpy(db_path, path, PATH_MAX); + case UPNPTAG: + tag = ary_options[i].value; break; - case UPNPLOGDIR: - path = realpath(ary_options[i].value, real_path); - 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; - } - strncpy(log_path, path, PATH_MAX); +#endif +#ifdef PF_ENABLE_FILTER_RULES + case UPNPQUICKRULES: + if(strcmp(ary_options[i].value, "no") == 0) + SETFLAG(PFNOQUICKRULESMASK); break; - case UPNPINOTIFY: - if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) ) - CLEARFLAG(INOTIFY_MASK); +#endif + case UPNPSECUREMODE: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(SECUREMODEMASK); break; - case ENABLE_TIVO: - if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) - SETFLAG(TIVO_MASK); +#ifdef ENABLE_LEASEFILE + case UPNPLEASEFILE: + lease_file = ary_options[i].value; + remove(lease_file); break; - case ENABLE_DLNA_STRICT: - if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) - SETFLAG(DLNA_STRICT_MASK); +#endif + case UPNPMEDIADIR: + strncpy(media_dir, ary_options[i].value, MEDIADIR_MAX_LEN); + media_dir[MEDIADIR_MAX_LEN-1] = '\0'; break; default: fprintf(stderr, "Unknown option in file %s\n", @@ -546,15 +365,6 @@ init(int argc, char * * argv) } } } - if( log_path[0] == '\0' ) - { - if( db_path[0] == '\0' ) - strncpy(log_path, DEFAULT_LOG_PATH, PATH_MAX); - else - strncpy(log_path, db_path, PATH_MAX); - } - if( db_path[0] == '\0' ) - strncpy(db_path, DEFAULT_DB_PATH, PATH_MAX); /* command line arguments processing */ for(i=1; i<argc; i++) @@ -563,16 +373,23 @@ init(int argc, char * * argv) { fprintf(stderr, "Unknown option: %s\n", argv[i]); } - else if(strcmp(argv[i], "--help")==0) - { - runtime_vars.port = 0; - break; - } else switch(argv[i][1]) { + case 'o': + if(i+1 < argc) + use_ext_ip_addr = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; case 't': if(i+1 < argc) - runtime_vars.notify_interval = atoi(argv[++i]); + v->notify_interval = atoi(argv[++i]); + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'u': + if(i+1 < argc) + strncpy(uuidvalue+5, argv[++i], strlen(uuidvalue+5) + 1); else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; @@ -590,12 +407,43 @@ init(int argc, char * * argv) fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; break; + case 'U': + /*sysuptime = 1;*/ + SETFLAG(SYSUPTIMEMASK); + break; /*case 'l': logfilename = argv[++i]; break;*/ + case 'L': + /*logpackets = 1;*/ + SETFLAG(LOGPACKETSMASK); + break; + case 'S': + SETFLAG(SECUREMODEMASK); + break; + case 'i': + if(i+1 < argc) + ext_if_name = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; +#ifdef USE_PF + case 'q': + if(i+1 < argc) + queue = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; + case 'T': + if(i+1 < argc) + tag = argv[++i]; + else + fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + break; +#endif case 'p': if(i+1 < argc) - runtime_vars.port = atoi(argv[++i]); + v->port = atoi(argv[++i]); else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; @@ -614,60 +462,37 @@ init(int argc, char * * argv) else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; - case 'a': - if(i+1 < argc) + case 'B': + if(i+2<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 - { - fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", - MAX_LAN_ADDR, argv[i]); - } + downstream_bitrate = strtoul(argv[++i], 0, 0); + upstream_bitrate = strtoul(argv[++i], 0, 0); } else - fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); + fprintf(stderr, "Option -%c takes two arguments.\n", argv[i][1]); break; - case 'i': + case 'a': if(i+1 < argc) { int address_already_there = 0; int j; i++; - if( getifaddr(argv[i], ext_ip_addr, sizeof(ext_ip_addr)) < 0 ) - { - fprintf(stderr, "Network interface '%s' not found.\n", - argv[i]); - exit(-1); - } - for(j=0; j<n_lan_addr; j++) + for(j=0; j<n_lan_addr; j++)/* for(j=0; j<v->n_lan_addr; j++)*/ { struct lan_addr_s tmpaddr; - parselanaddr(&tmpaddr, ext_ip_addr); + parselanaddr(&tmpaddr, argv[i]); + /*if(0 == strcmp(v->lan_addr[j].str, tmpaddr.str))*/ 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(n_lan_addr < MAX_LAN_ADDR) /*if(v->n_lan_addr < MAX_LAN_ADDR)*/ { - if(parselanaddr(&lan_addr[n_lan_addr], ext_ip_addr) == 0) - n_lan_addr++; + /*v->lan_addr[v->n_lan_addr++] = argv[i];*/ + /*if(parselanaddr(&v->lan_addr[v->n_lan_addr], argv[i]) == 0)*/ + if(parselanaddr(&lan_addr[n_lan_addr], argv[i]) == 0) + n_lan_addr++; /*v->n_lan_addr++;*/ } else { @@ -681,60 +506,44 @@ init(int argc, char * * argv) case 'f': i++; /* discarding, the config file is already read */ break; - case 'h': - runtime_vars.port = 0; // triggers help display - break; - case 'R': - snprintf(real_path, sizeof(real_path), "rm -rf %s/files.db %s/art_cache", db_path, db_path); - system(real_path); - break; - case 'V': - printf("Version " MINIDLNA_VERSION "\n"); - exit(0); - break; default: fprintf(stderr, "Unknown option: %s\n", argv[i]); } } - /* If no IP was specified, try to detect one */ - if( n_lan_addr < 1 ) - { - if( (getsysaddr(ext_ip_addr, sizeof(ext_ip_addr)) < 0) && - (getifaddr("eth0", ext_ip_addr, sizeof(ext_ip_addr)) < 0) && - (getifaddr("eth1", ext_ip_addr, sizeof(ext_ip_addr)) < 0) ) - { - DPRINTF(E_OFF, L_GENERAL, "No IP address automatically detected!\n"); - } - if( *ext_ip_addr && parselanaddr(&lan_addr[n_lan_addr], ext_ip_addr) == 0 ) - { - n_lan_addr++; - } - } - - if( (n_lan_addr==0) || (runtime_vars.port<=0) ) + if(!ext_if_name || (/*v->*/n_lan_addr==0) || v->port<=0) { fprintf(stderr, "Usage:\n\t" - "%s [-d] [-f config_file]\n" - "\t\t[-a listening_ip] [-p port]\n" - /*"[-l logfile] " not functionnal */ - "\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-V print the version number\n", - argv[0], pidfilename); + "%s [-f config_file] [-i ext_ifname] [-o ext_ip]\n" + "\t\t[-a listening_ip] [-p port] [-d] [-L] [-U] [-S]\n" + /*"[-l logfile] " not functionnal */ + "\t\t[-u uuid] [-s serial] [-m model_number] \n" + "\t\t[-t notify_interval] [-P pid_filename]\n" +#ifdef USE_PF + "\t\t[-B down up] [-w url] [-q queue] [-T tag]\n" +#else + "\t\t[-B down up] [-w url]\n" +#endif + "\nNotes:\n\tThere can be one or several listening_ips.\n" + "\tNotify interval is in seconds. Default is 30 seconds.\n" + "\tDefault pid file is %s.\n" + "\tWith -d miniupnpd will run as a standard program.\n" + "\t-L sets packet log in pf and ipf on.\n" + "\t-S sets \"secure\" mode : clients can only add mappings to their own ip\n" + "\t-U causes miniupnpd to report system uptime instead " + "of daemon uptime.\n" + "\t-B sets bitrates reported by daemon in bits per second.\n" + "\t-w sets the presentation url. Default is http address on port 80\n" +#ifdef USE_PF + "\t-q sets the ALTQ queue in pf.\n" + "\t-T sets the tag name in pf.\n" +#endif + "", argv[0], pidfilename); return 1; } if(debug_flag) { pid = getpid(); - log_init(NULL, "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=debug"); } else { @@ -746,23 +555,29 @@ init(int argc, char * * argv) #else pid = daemonize(); #endif - #ifdef READYNAS - log_init("/var/log/upnp-av.log", "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"); - #else - if( access(db_path, F_OK) != 0 ) - make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); - sprintf(real_path, "%s/minidlna.log", log_path); - log_init(real_path, "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"); - #endif + } + + openlog_option = LOG_PID|LOG_CONS; + if(debug_flag) + { + openlog_option |= LOG_PERROR; /* also log on stderr */ + } + + openlog("miniupnpd", openlog_option, LOG_MINIUPNPD); + + if(!debug_flag) + { + /* speed things up and ignore LOG_INFO and LOG_DEBUG */ + setlogmask(LOG_UPTO(LOG_NOTICE)); } if(checkforrunning(pidfilename) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "MiniDLNA is already running. EXITING.\n"); + syslog(LOG_ERR, "MiniUPnPd is already running. EXITING"); return 1; } - set_startup_time(); + set_startup_time(GETFLAG(SYSUPTIMEMASK)/*sysuptime*/); /* presentation url */ if(presurl) @@ -772,30 +587,29 @@ init(int argc, char * * argv) } else { -#ifdef READYNAS snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, - "http://%s/admin/", lan_addr[0].str); -#else - snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, - "http://%s:%d/", lan_addr[0].str, runtime_vars.port); -#endif + "http://%s/", lan_addr[0].str); + /*"http://%s:%d/", lan_addr[0].str, 80);*/ + /*"http://%s:%d/", v->lan_addr[0].str, 80);*/ } /* set signal handler */ - signal(SIGCLD, SIG_IGN); memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; + if (sigaction(SIGTERM, &sa, NULL)) { - DPRINTF(E_FATAL, L_GENERAL, "Failed to set SIGTERM handler. EXITING.\n"); + syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM"); + return 1; } if (sigaction(SIGINT, &sa, NULL)) { - DPRINTF(E_FATAL, L_GENERAL, "Failed to set SIGINT handler. EXITING.\n"); + syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGINT"); + return 1; } if(signal(SIGPIPE, SIG_IGN) == SIG_ERR) { - DPRINTF(E_FATAL, L_GENERAL, "Failed to ignore SIGPIPE signals. EXITING.\n"); + syslog(LOG_ERR, "Failed to ignore SIGPIPE signals"); } writepidfile(pidfilename, pid); @@ -815,159 +629,54 @@ main(int argc, char * * argv) struct upnphttp * e = 0; struct upnphttp * next; fd_set readset; /* for select() */ +#ifdef ENABLE_EVENTS fd_set writeset; - struct timeval timeout, timeofday, lastnotifytime = {0, 0}; - time_t lastupdatetime = 0; - int max_fd = -1; - int last_changecnt = 0; - short int new_db = 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 - -#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 + struct timeval timeout, timeofday, lasttimeofday = {0, 0}; + int max_fd = -1; + struct runtime_vars v; - if(init(argc, argv) != 0) + if(init(argc, argv, &v) != 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); - new_db = open_db(); - if( !new_db ) + if( access("/tmp/files.db", F_OK) ) { - updateID = sql_get_int_field(db, "SELECT UPDATE_ID from SETTINGS"); + sqlite3_open("/tmp/files.db", &db); + ScanDirectory(media_dir, NULL); + return 0; } - if( sql_get_int_field(db, "pragma user_version") != DB_VERSION ) - { - if( new_db ) - { - DPRINTF(E_WARN, L_GENERAL, "Creating new database...\n"); - } - else - { - DPRINTF(E_WARN, L_GENERAL, "Database version mismatch; need to recreate...\n"); - } - sqlite3_close(db); - char *cmd; - asprintf(&cmd, "rm -rf %s/files.db %s/art_cache", db_path, db_path); - system(cmd); - 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 ) // child (scanner) process - { - 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 - } - if( sqlite3_threadsafe() && sqlite3_libversion_number() >= 3005001 && - GETFLAG(INOTIFY_MASK) && pthread_create(&inotify_thread, NULL, start_inotify, NULL) ) + else { - DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify.\n"); + sqlite3_open("/tmp/files.db", &db); } + + /* open socket for SSDP connections */ + /*sudp = OpenAndConfSSDPReceiveSocket(v.n_lan_addr, v.lan_addr);*/ sudp = OpenAndConfSSDPReceiveSocket(n_lan_addr, lan_addr); if(sudp < 0) { - DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for receiving SSDP. EXITING\n"); + syslog(LOG_ERR, "Failed to open socket for receiving SSDP. EXITING"); + return 1; } /* open socket for HTTP connections. Listen on the 1st LAN address */ - shttpl = OpenAndConfHTTPSocket(runtime_vars.port); + shttpl = OpenAndConfHTTPSocket(v.port); if(shttpl < 0) { - DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n"); + syslog(LOG_ERR, "Failed to open socket for HTTP. EXITING"); + return 1; } - DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port); + syslog(LOG_NOTICE, "HTTP listening on port %d", v.port); /* open socket for sending notifications */ 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"); - /* Add TiVo-specific randomize function to sqlite */ - 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"); - } - /* open socket for sending Tivo notifications */ - 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; + syslog(LOG_ERR, "Failed to open sockets for sending SSDP notify " + "messages. EXITING"); + return 1; } -#endif SendSSDPGoodbye(snotify, n_lan_addr); @@ -978,71 +687,37 @@ main(int argc, char * * argv) * needed */ if(gettimeofday(&timeofday, 0) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); - timeout.tv_sec = runtime_vars.notify_interval; + syslog(LOG_ERR, "gettimeofday(): %m"); + timeout.tv_sec = v.notify_interval; timeout.tv_usec = 0; } else { /* the comparaison is not very precise but who cares ? */ - if(timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) + if(timeofday.tv_sec >= (lasttimeofday.tv_sec + v.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; + (unsigned short)v.port, + (v.notify_interval << 1)+10); + memcpy(&lasttimeofday, &timeofday, sizeof(struct timeval)); + timeout.tv_sec = v.notify_interval; timeout.tv_usec = 0; } else { - timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval + timeout.tv_sec = lasttimeofday.tv_sec + v.notify_interval - timeofday.tv_sec; - if(timeofday.tv_usec > lastnotifytime.tv_usec) + if(timeofday.tv_usec > lasttimeofday.tv_usec) { - timeout.tv_usec = 1000000 + lastnotifytime.tv_usec + timeout.tv_usec = 1000000 + lasttimeofday.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; - } - /* Beacons should be sent every 5 seconds or so for the first minute, - * then every minute or so thereafter. */ - 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; + timeout.tv_usec = lasttimeofday.tv_usec - timeofday.tv_usec; } } -#endif - } - - if( scanning ) - { - if( !scanner_pid || kill(scanner_pid, 0) ) - { - scanning = 0; - updateID++; - } } /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ @@ -1051,74 +726,61 @@ main(int argc, char * * argv) if (sudp >= 0) { FD_SET(sudp, &readset); - max_fd = MAX(max_fd, sudp); + 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); + max_fd = MAX( max_fd, shttpl); } -#endif + i = 0; /* active HTTP connections count */ 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); + max_fd = MAX( max_fd, e->socket); i++; } } -#ifdef DEBUG /* for debug */ +#ifdef DEBUG if(i > 1) { - DPRINTF(E_DEBUG, L_GENERAL, "%d active incoming HTTP connections\n", i); + syslog(LOG_DEBUG, "%d active incoming HTTP connections", i); } #endif + +#ifdef ENABLE_EVENTS FD_ZERO(&writeset); upnpevents_selectfds(&readset, &writeset, &max_fd); +#endif +#ifdef ENABLE_EVENTS if(select(max_fd+1, &readset, &writeset, 0, &timeout) < 0) +#else + if(select(max_fd+1, &readset, 0, 0, &timeout) < 0) +#endif { 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"); + syslog(LOG_ERR, "select(all): %m"); + syslog(LOG_ERR, "Failed to select open sockets. EXITING"); + return 1; /* very serious cause of error */ } +#ifdef ENABLE_EVENTS upnpevents_processfds(&readset, &writeset); +#endif /* process SSDP packets */ if(sudp >= 0 && FD_ISSET(sudp, &readset)) { - /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/ - ProcessSSDPRequest(sudp, (unsigned short)runtime_vars.port); - } -#ifdef TIVO_SUPPORT - if(sbeacon >= 0 && FD_ISSET(sbeacon, &readset)) - { - /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/ - ProcessTiVoBeacon(sbeacon); - } -#endif - /* increment SystemUpdateID if the content database has changed, - * and if there is an active HTTP connection, at most once every 2 seconds */ - 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; - } + /*syslog(LOG_INFO, "Received UDP Packet");*/ + /*ProcessSSDPRequest(sudp, v.lan_addr, v.n_lan_addr,*/ + ProcessSSDPRequest(sudp, (unsigned short)v.port); } /* process active HTTP connections */ + /* LIST_FOREACH macro is not available under linux */ for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if( (e->socket >= 0) && (e->state <= 2) @@ -1137,16 +799,16 @@ main(int argc, char * * argv) shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); if(shttp<0) { - DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno)); + syslog(LOG_ERR, "accept(http): %m"); } else { struct upnphttp * tmp = 0; - DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n", + syslog(LOG_INFO, "HTTP connection from %s:%d", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port) ); /*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "fcntl F_SETFL, O_NONBLOCK\n"); + syslog(LOG_ERR, "fcntl F_SETFL, O_NONBLOCK"); }*/ /* Create a new upnphttp object and add it to * the active upnphttp object list */ @@ -1158,7 +820,7 @@ main(int argc, char * * argv) } else { - DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n"); + syslog(LOG_ERR, "New_upnphttp() failed"); close(shttp); } } @@ -1177,11 +839,6 @@ main(int argc, char * * argv) } shutdown: - /* kill the scanner */ - if( scanning && scanner_pid ) - { - kill(scanner_pid, 9); - } /* close out open sockets */ while(upnphttphead.lh_first != NULL) { @@ -1192,47 +849,24 @@ shutdown: 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"); + syslog(LOG_ERR, "Failed to broadcast good-bye notifications"); } - for(i=0; i<n_lan_addr; i++) + for(i=0; i<n_lan_addr; i++)/* for(i=0; i<v.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); - 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(unlink(pidfilename) < 0) { - DPRINTF(E_ERROR, L_GENERAL, "Failed to remove pidfile %s: %s\n", pidfilename, strerror(errno)); + syslog(LOG_ERR, "Failed to remove pidfile %s: %m", pidfilename); } + closelog(); freeoptions(); - - exit(EXIT_SUCCESS); + + return 0; } @@ -1,50 +1,23 @@ -/* $Id: minissdp.c,v 1.18 2011/05/03 06:14:25 jmaggard Exp $ */ +/* $Id$ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdio.h> -#include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> -#include <errno.h> - +#include <syslog.h> #include "config.h" #include "upnpdescstrings.h" -#include "minidlnapath.h" +#include "miniupnpdpath.h" #include "upnphttp.h" #include "upnpglobalvars.h" -#include "upnpreplyparse.h" -#include "getifaddr.h" #include "minissdp.h" -#include "log.h" /* SSDP ip/port */ #define SSDP_PORT (1900) @@ -55,22 +28,24 @@ AddMulticastMembership(int s, in_addr_t ifaddr/*const char * ifaddr*/) { struct ip_mreq imr; /* Ip multicast membership */ - /* setting up imr structure */ - imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); - /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/ - imr.imr_interface.s_addr = ifaddr; /*inet_addr(ifaddr);*/ + /* setting up imr structure */ + imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); + /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/ + imr.imr_interface.s_addr = ifaddr; /*inet_addr(ifaddr);*/ if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(struct ip_mreq)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n", strerror(errno)); + syslog(LOG_ERR, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m"); return -1; - } + } return 0; } int OpenAndConfSSDPReceiveSocket() +/*OpenAndConfSSDPReceiveSocket(int n_lan_addr, + struct lan_addr_s * lan_addr)*/ { int s; int i = 1; @@ -78,29 +53,29 @@ OpenAndConfSSDPReceiveSocket() if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "socket(udp): %s\n", strerror(errno)); + syslog(LOG_ERR, "socket(udp): %m"); return -1; } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { - DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno)); + syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEADDR): %m"); } memset(&sockname, 0, sizeof(struct sockaddr_in)); - sockname.sin_family = AF_INET; - sockname.sin_port = htons(SSDP_PORT); + sockname.sin_family = AF_INET; + sockname.sin_port = htons(SSDP_PORT); /* NOTE : it seems it doesnt work when binding on the specific address */ - /*sockname.sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/ - sockname.sin_addr.s_addr = htonl(INADDR_ANY); - /*sockname.sin_addr.s_addr = inet_addr(ifaddr);*/ + /*sockname.sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/ + sockname.sin_addr.s_addr = htonl(INADDR_ANY); + /*sockname.sin_addr.s_addr = inet_addr(ifaddr);*/ - if(bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) + if(bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "bind(udp): %s\n", strerror(errno)); + syslog(LOG_ERR, "bind(udp): %m"); close(s); return -1; - } + } i = n_lan_addr; while(i>0) @@ -108,8 +83,8 @@ OpenAndConfSSDPReceiveSocket() i--; if(AddMulticastMembership(s, lan_addr[i].addr.s_addr) < 0) { - DPRINTF(E_WARN, L_SSDP, - "Failed to add multicast membership for address %s\n", + syslog(LOG_WARNING, + "Failed to add multicast membership for address %s", lan_addr[i].str ); } } @@ -130,7 +105,7 @@ OpenAndConfSSDPNotifySocket(in_addr_t addr) if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "socket(udp_notify): %s\n", strerror(errno)); + syslog(LOG_ERR, "socket(udp_notify): %m"); return -1; } @@ -138,41 +113,43 @@ OpenAndConfSSDPNotifySocket(in_addr_t addr) if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno)); + syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %m"); close(s); return -1; } if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno)); + syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_IF): %m"); close(s); return -1; } if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, SO_BROADCAST): %s\n", strerror(errno)); + syslog(LOG_ERR, "setsockopt(udp_notify, SO_BROADCAST): %m"); close(s); return -1; } memset(&sockname, 0, sizeof(struct sockaddr_in)); - sockname.sin_family = AF_INET; - sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ + sockname.sin_family = AF_INET; + sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ - if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) + if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) { - DPRINTF(E_ERROR, L_SSDP, "bind(udp_notify): %s\n", strerror(errno)); + syslog(LOG_ERR, "bind(udp_notify): %m"); close(s); return -1; - } + } return s; } int OpenAndConfSSDPNotifySockets(int * sockets) +/*OpenAndConfSSDPNotifySockets(int * sockets, + struct lan_addr_s * lan_addr, int n_lan_addr)*/ { int i, j; for(i=0; i<n_lan_addr; i++) @@ -212,31 +189,11 @@ USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice EXT: */ -static const char * const known_service_types[] = -{ - uuidvalue, - "upnp:rootdevice", - "urn:schemas-upnp-org:device:MediaServer:", - "urn:schemas-upnp-org:service:ContentDirectory:", - "urn:schemas-upnp-org:service:ConnectionManager:", - "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:", - 0 -}; - -static void -_usleep(long usecs) -{ - struct timespec sleep_time; - - sleep_time.tv_sec = 0; - sleep_time.tv_nsec = usecs * 1000; - nanosleep(&sleep_time, NULL); -} - /* not really an SSDP "announce" as it is the response * to a SSDP "M-SEARCH" */ static void -SendSSDPAnnounce2(int s, struct sockaddr_in sockname, int st_no, +SendSSDPAnnounce2(int s, struct sockaddr_in sockname, + const char * st, int st_len, const char * suffix, const char * host, unsigned short port) { int l, n; @@ -245,7 +202,7 @@ SendSSDPAnnounce2(int s, struct sockaddr_in sockname, int st_no, * follow guideline from document "UPnP Device Architecture 1.0" * put in uppercase. * DATE: is recommended - * SERVER: OS/ver UPnP/1.0 minidlna/1.0 + * SERVER: OS/ver UPnP/1.0 miniupnpd/1.0 * - check what to put in the 'Cache-Control' header * */ char szTime[30]; @@ -253,29 +210,39 @@ SendSSDPAnnounce2(int s, struct sockaddr_in sockname, int st_no, strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&tTime)); l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n" - "CACHE-CONTROL: max-age=%u\r\n" + "CACHE-CONTROL: max-age=1810\r\n" "DATE: %s\r\n" - "ST: %s%s\r\n" - "USN: %s%s%s%s\r\n" + "Ext:\r\n" + "ST: %.*s%s\r\n" + "USN: %s::%.*s%s\r\n" "EXT:\r\n" - "SERVER: " MINIDLNA_SERVER_STRING "\r\n" + "SERVER: " MINIUPNPD_SERVER_STRING "\r\n" "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n" "Content-Length: 0\r\n" "\r\n", - (runtime_vars.notify_interval<<1)+10, szTime, - known_service_types[st_no], (st_no>1?"1":""), - uuidvalue, (st_no>0?"::":""), (st_no>0?known_service_types[st_no]:""), (st_no>1?"1":""), + st_len, st, suffix, + uuidvalue, st_len, st, suffix, host, (unsigned int)port); - //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending M-SEARCH response:\n%s", buf); n = sendto(s, buf, l, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); if(n < 0) { - DPRINTF(E_ERROR, L_SSDP, "sendto(udp): %s\n", strerror(errno)); + syslog(LOG_ERR, "sendto(udp): %m"); } } +static const char * const known_service_types[] = +{ + "upnp:rootdevice", + "urn:schemas-upnp-org:device:MediaServer:", + "urn:schemas-upnp-org:service:ContentDirectory:", + "urn:schemas-upnp-org:service:ConnectionManager:", + "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:", + uuidvalue, + 0 +}; + static void SendSSDPNotifies(int s, const char * host, unsigned short port, unsigned int lifetime) @@ -292,7 +259,7 @@ SendSSDPNotifies(int s, const char * host, unsigned short port, for( dup=0; dup<2; dup++ ) { if( dup ) - _usleep(200000); + usleep(200000); i=0; while(known_service_types[i]) { @@ -301,27 +268,27 @@ SendSSDPNotifies(int s, const char * host, unsigned short port, "HOST:%s:%d\r\n" "CACHE-CONTROL:max-age=%u\r\n" "LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n" - "SERVER: " MINIDLNA_SERVER_STRING "\r\n" + "SERVER: " MINIUPNPD_SERVER_STRING "\r\n" "NT:%s%s\r\n" - "USN:%s%s%s%s\r\n" + "USN:%s::%s%s\r\n" "NTS:ssdp:alive\r\n" "\r\n", SSDP_MCAST_ADDR, SSDP_PORT, lifetime, host, port, - known_service_types[i], (i>1?"1":""), - uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") ); + known_service_types[i], (i==0?"":"1"), + uuidvalue, known_service_types[i], (i==0?"":"1") ); if(l>=sizeof(bufr)) { - DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n"); + syslog(LOG_WARNING, "SendSSDPNotifies(): truncated output"); l = sizeof(bufr); } - //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending NOTIFY:\n%s", bufr); + //DEBUG printf("Sending NOTIFY:\n%s", bufr); n = sendto(s, bufr, l, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); if(n < 0) { - DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno)); + syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, host); } i++; } @@ -337,145 +304,12 @@ SendSSDPNotifies2(int * sockets, unsigned int lifetime)*/ { int i; - DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n"); for(i=0; i<n_lan_addr; i++) { SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime); } } -void -ParseUPnPClient(char *location) -{ - char buf[8192]; - struct sockaddr_in dest; - int s, n, do_headers = 0, nread = 0; - struct timeval tv; - char *addr, *path, *port_str; - long port = 80; - char *off = NULL, *p; - int content_len = sizeof(buf); - struct NameValueParserData xml; - int client; - enum client_types type = -1; - uint32_t flags = 0; - char *model; - - if (strncmp(location, "http://", 7) != 0) - return; - path = location + 7; - port_str = strsep(&path, "/"); - if (!path) - return; - addr = strsep(&port_str, ":"); - if (port_str) - { - port = strtol(port_str, NULL, 10); - if (!port) - port = 80; - } - - memset(&dest, '\0', sizeof(dest)); - if (!inet_aton(addr, &dest.sin_addr)) - return; - /* Check if the client is already in cache */ - dest.sin_family = AF_INET; - dest.sin_port = htons(port); - - s = socket(PF_INET, SOCK_STREAM, 0); - if( s < 0 ) - return; - - tv.tv_sec = 0; - tv.tv_usec = 500000; - setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - - if( connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0 ) - goto close; - - n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n" - "HOST: %s:%ld\r\n\r\n", - path, addr, port); - if( write(s, buf, n) < 1 ) - goto close; - - while( (n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0 ) - { - nread += n; - buf[nread] = '\0'; - n = nread; - p = buf; - - while( !off && n-- > 0 ) - { - if(p[0]=='\r' && p[1]=='\n' && p[2]=='\r' && p[3]=='\n') - { - off = p + 4; - do_headers = 1; - } - p++; - } - if( !off ) - continue; - - if( do_headers ) - { - p = buf; - if( strncmp(p, "HTTP/", 5) != 0 ) - goto close; - while(*p != ' ' && *p != '\t') p++; - /* If we don't get a 200 status, ignore it */ - if( strtol(p, NULL, 10) != 200 ) - goto close; - if( (p = strcasestr(p, "Content-Length:")) ) - content_len = strtol(p+15, NULL, 10); - do_headers = 0; - } - if( buf + nread - off >= content_len ) - break; - } -close: - close(s); - if( !off ) - return; - nread -= off - buf; - ParseNameValue(off, nread, &xml); - model = GetValueFromNameValueList(&xml, "modelName"); - if( model ) - { - DPRINTF(E_DEBUG, L_SSDP, "Model: %s\n", model); - if (strstr(model, "Roku SoundBridge")) - { - type = ERokuSoundBridge; - flags |= FLAG_AUDIO_ONLY; - } - } - - if( type < 0 ) - return; - client = SearchClientCache(dest.sin_addr, 1); - /* Add this client to the cache if it's not there already. */ - if( client < 0 ) - { - for( client=0; client<CLIENT_CACHE_SLOTS; client++ ) - { - if( clients[client].addr.s_addr ) - continue; - get_remote_mac(dest.sin_addr, clients[client].mac); - clients[client].addr = dest.sin_addr; - DPRINTF(E_DEBUG, L_SSDP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", - type, inet_ntoa(clients[client].addr), - clients[client].mac[0], clients[client].mac[1], clients[client].mac[2], - clients[client].mac[3], clients[client].mac[4], clients[client].mac[5], client); - break; - } - } - clients[client].type = type; - clients[client].flags = flags; - clients[client].age = time(NULL); -} - /* ProcessSSDPRequest() * process SSDP M-SEARCH requests and responds to them */ void @@ -487,140 +321,60 @@ ProcessSSDPRequest(int s, unsigned short port) char bufr[1500]; socklen_t len_r; struct sockaddr_in sendername; - int i; - char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL, *loc = NULL, *srv = NULL; - int man_len = 0; + int i, l; + int lan_addr_index = 0; + char * st = 0; + int st_len = 0; len_r = sizeof(struct sockaddr_in); - n = recvfrom(s, bufr, sizeof(bufr)-1, 0, + n = recvfrom(s, bufr, sizeof(bufr), 0, (struct sockaddr *)&sendername, &len_r); if(n < 0) { - DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno)); + syslog(LOG_ERR, "recvfrom(udp): %m"); return; } - bufr[n] = '\0'; if(memcmp(bufr, "NOTIFY", 6) == 0) { - int loc_len = 0, srv_len = 0; - //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr); - for(i=0; i < n; i++) - { - if( bufr[i] == '*' ) - break; - } - if( !strcasestr(bufr+i, "HTTP/1.1") ) - { - return; - } - while(i < n) - { - while((i < n - 2) && (bufr[i] != '\r' || bufr[i+1] != '\n')) - i++; - i += 2; - if(strncasecmp(bufr+i, "SERVER:", 7) == 0) - { - srv = bufr+i+7; - srv_len = 0; - while(*srv == ' ' || *srv == '\t') srv++; - while(srv[srv_len]!='\r' && srv[srv_len]!='\n') srv_len++; - } - else if(strncasecmp(bufr+i, "LOCATION:", 9) == 0) - { - loc = bufr+i+9; - loc_len = 0; - while(*loc == ' ' || *loc == '\t') loc++; - while(loc[loc_len]!='\r' && loc[loc_len]!='\n') loc_len++; - loc[loc_len] = '\0'; - } - else if(strncasecmp(bufr+i, "NTS:", 4) == 0) - { - man = bufr+i+4; - man_len = 0; - while(*man == ' ' || *man == '\t') man++; - while(man[man_len]!='\r' && man[man_len]!='\n') man_len++; - } - } - if (!loc || !srv || !man || (strncmp(man, "ssdp:alive", man_len) != 0)) - { - return; - } - if (strncmp(srv, "Allegro-Software-RomPlug", 24) == 0) - { - /* Check if the client is already in cache */ - i = SearchClientCache(sendername.sin_addr, 1); - if( i >= 0 && clients[i].type < EStandardDLNA150 ) - { - clients[i].age = time(NULL); - return; - } - ParseUPnPClient(loc); - } + /* ignore NOTIFY packets. We could log the sender and device type */ return; } else if(memcmp(bufr, "M-SEARCH", 8) == 0) { - int st_len = 0, mx_len = 0, mx_val = 0; - //DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s", n, bufr); - for(i=0; i < n; i++) - { - if( bufr[i] == '*' ) - break; - } - if( !strcasestr(bufr+i, "HTTP/1.1") ) - { - return; - } + i = 0; while(i < n) { - while((i < n - 2) && (bufr[i] != '\r' || bufr[i+1] != '\n')) + while(bufr[i] != '\r' || bufr[i+1] != '\n') i++; i += 2; - if(strncasecmp(bufr+i, "ST:", 3) == 0) + if(strncasecmp(bufr+i, "st:", 3) == 0) { st = bufr+i+3; st_len = 0; while(*st == ' ' || *st == '\t') st++; while(st[st_len]!='\r' && st[st_len]!='\n') st_len++; - } - else if(strncasecmp(bufr+i, "MX:", 3) == 0) - { - mx = bufr+i+3; - mx_len = 0; - while(*mx == ' ' || *mx == '\t') mx++; - while(mx[mx_len]!='\r' && mx[mx_len]!='\n') mx_len++; - mx_val = strtol(mx, &mx_end, 10); - } - else if(strncasecmp(bufr+i, "MAN:", 4) == 0) - { - man = bufr+i+4; - man_len = 0; - while(*man == ' ' || *man == '\t') man++; - while(man[man_len]!='\r' && man[man_len]!='\n') man_len++; + /*syslog(LOG_INFO, "ST: %.*s", st_len, st);*/ + /*j = 0;*/ + /*while(bufr[i+j]!='\r') j++;*/ + /*syslog(LOG_INFO, "%.*s", j, bufr+i);*/ } } - /*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n", + /*syslog(LOG_INFO, "SSDP M-SEARCH packet received from %s:%d", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/ if( ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900 ) { - DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n", + syslog(LOG_INFO, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } - else if( !man || (strncmp(man, "\"ssdp:discover\"", 15) != 0) ) - { - DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad MAN header %.*s]\n", - inet_ntoa(sendername.sin_addr), man_len, man); - } - else if( !mx || mx == mx_end || mx_val < 0 ) { - DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad MX header %.*s]\n", - inet_ntoa(sendername.sin_addr), mx_len, mx); - } - else if( st && (st_len > 0) ) + else if(st) { - int l; - int lan_addr_index = 0; + /* TODO : doesnt answer at once but wait for a random time */ + syslog(LOG_INFO, "SSDP M-SEARCH from %s:%d ST: %.*s", + inet_ntoa(sendername.sin_addr), + ntohs(sendername.sin_port), + st_len, st); /* find in which sub network the client is */ for(i = 0; i<n_lan_addr; i++) { @@ -631,28 +385,14 @@ ProcessSSDPRequest(int s, unsigned short port) break; } } - if( i == n_lan_addr ) - { - DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH on other interface [%s]\n", - inet_ntoa(sendername.sin_addr)); - return; - } - DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n", - inet_ntoa(sendername.sin_addr), - ntohs(sendername.sin_port), - st_len, st, mx_len, mx, man_len, man); /* Responds to request with a device as ST header */ for(i = 0; known_service_types[i]; i++) { - l = strlen(known_service_types[i]); + l = (int)strlen(known_service_types[i]); if(l<=st_len && (0 == memcmp(st, known_service_types[i], l))) { - /* Check version number - must always be 1 currently. */ - if( (st[st_len-2] == ':') && (atoi(st+st_len-1) != 1) ) - break; - _usleep(random()>>20); SendSSDPAnnounce2(s, sendername, - i, + st, st_len, "", lan_addr[lan_addr_index].str, port); break; } @@ -665,20 +405,27 @@ ProcessSSDPRequest(int s, unsigned short port) { l = (int)strlen(known_service_types[i]); SendSSDPAnnounce2(s, sendername, - i, + known_service_types[i], l, i==0?"":"1", lan_addr[lan_addr_index].str, port); } } + /* responds to request by UUID value */ + l = (int)strlen(uuidvalue); + if(l==st_len && (0 == memcmp(st, uuidvalue, l))) + { + SendSSDPAnnounce2(s, sendername, st, st_len, "", + lan_addr[lan_addr_index].str, port); + } } else { - DPRINTF(E_INFO, L_SSDP, "Invalid SSDP M-SEARCH from %s:%d\n", + syslog(LOG_INFO, "Invalid SSDP M-SEARCH from %s:%d", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } } else { - DPRINTF(E_WARN, L_SSDP, "Unknown udp packet received from %s:%d\n", + syslog(LOG_NOTICE, "Unknown udp packet received from %s:%d", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } } @@ -693,34 +440,34 @@ SendSSDPGoodbye(int * sockets, int n_sockets) int i, j; char bufr[512]; - memset(&sockname, 0, sizeof(struct sockaddr_in)); - sockname.sin_family = AF_INET; - sockname.sin_port = htons(SSDP_PORT); - sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); + memset(&sockname, 0, sizeof(struct sockaddr_in)); + sockname.sin_family = AF_INET; + sockname.sin_port = htons(SSDP_PORT); + sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); for(j=0; j<n_sockets; j++) { - for(i=0; known_service_types[i]; i++) - { - l = snprintf(bufr, sizeof(bufr), - "NOTIFY * HTTP/1.1\r\n" - "HOST:%s:%d\r\n" - "NT:%s%s\r\n" - "USN:%s%s%s%s\r\n" - "NTS:ssdp:byebye\r\n" - "\r\n", - SSDP_MCAST_ADDR, SSDP_PORT, - known_service_types[i], (i>1?"1":""), - uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") ); - //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending NOTIFY:\n%s", bufr); - n = sendto(sockets[j], bufr, l, 0, - (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); + for(i=0; known_service_types[i]; i++) + { + l = snprintf(bufr, sizeof(bufr), + "NOTIFY * HTTP/1.1\r\n" + "HOST:%s:%d\r\n" + "NT:%s%s\r\n" + "USN:%s::%s%s\r\n" + "NTS:ssdp:byebye\r\n" + "\r\n", + SSDP_MCAST_ADDR, SSDP_PORT, + known_service_types[i], (i==0?"":"1"), + uuidvalue, known_service_types[i], (i==0?"":"1")); + n = sendto(sockets[j], bufr, l, 0, + (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); if(n < 0) { - DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", sockets[j], strerror(errno)); + syslog(LOG_ERR, "sendto(udp_shutdown=%d): %m", sockets[j]); return -1; } - } + } } return 0; } + @@ -1,35 +1,12 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006-2007 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ #ifndef __MINISSDP_H__ #define __MINISSDP_H__ -/*#include "minidlnatypes.h"*/ +/*#include "miniupnpdtypes.h"*/ int OpenAndConfSSDPReceiveSocket(); diff --git a/miniupnpd.1 b/miniupnpd.1 new file mode 100644 index 0000000..a421232 --- /dev/null +++ b/miniupnpd.1 @@ -0,0 +1,73 @@ +.TH miniupnpd 1 +.SH NAME +miniupnpd \- UPnP Internet Gateway Device Daemon +.SH SYNOPSIS +.B miniupnpd +[-f file] [-i interface] [-o address] +[-a address] [-p port] [-d] [-L] [-U] +[-u uuid] [-s serial] [-m model_number] +[-q queue] +[-t interval] [-P file] +[-B down up] [-w url] +.SH DESCRIPTION +miniupnpd act as a UPnP Internet Gateway Device. It is designed +to run on the gateway between the internet and a NAT'ed LAN. It provides +an interface, as defined in the UPnP standard, for enabling +clients on the LAN to ask for port redirections. +.SH OPTIONS +.TP +.B \-f file +load the config from file. default is /etc/miniupnpd.conf. +.TP +.B \-i interface +interface used to connect to the internet. +.TP +.B \-o address +address used to connect to the internet. +default address of the interface will be used if not specified. +.TP +.B \-a address +address on the LAN. -a option can by used multiple time if LAN is +subdivised in several subnetworks. +.TP +.B \-p port +port used for HTTP. +.TP +.B \-d +debug mode : do not go to background, output messages on console +and do not filter out low priority messages. +.TP +.B \-L +set packet log in pf on +.TP +.B \-q queue +set ALTQ queue in pf. filter rules must be enabled for this option +to have any effect. +.TP +.B \-U +report system uptime instead of daemon uptime to clients. +.TP +.B \-u uuid +set the uuid of the UPnP Internet Gateway Device. +.TP +.B \-s serial +serial number for the UPnP Internet Gateway Device. +.TP +.B \-m number +model number for the UPnP Internet Gateway Device. +.TP +.B \-t interval +SSDP notify interval in seconds : +SSDP announce messages will be broadcasted at this interval. +.TP +.B \-P file +pid file. default is /var/run/miniupnpd.pid +.TP +.B \-B down up +download and upload bitrates reported to clients. +.TP +.B \-w url +presentation url. default is first address on LAN, port 80. +.SH "SEE ALSO" +minissdpd(1) miniupnpc(3) +.SH BUGS diff --git a/miniupnpd.conf b/miniupnpd.conf new file mode 100644 index 0000000..02b3f34 --- /dev/null +++ b/miniupnpd.conf @@ -0,0 +1,73 @@ +# WAN network interface +ext_ifname=eth0 +# if the WAN interface has several IP addresses, you +# can specify the one to use below +#ext_ip= + +# LAN network interfaces IPs / networks +# there can be multiple listening ips for SSDP traffic. +# should be under the form nnn.nnn.nnn.nnn/nn +# HTTP is available on all interfaces +#listening_ip=192.168.0.1/24 +#listening_ip=10.1.11.65/24 +#listening_ip=192.168.10.112 +#listening_ip= +# port for HTTP (descriptions and SOAP) traffic +port=5555 + +# enable UPNP support (default is yes) +enable_upnp=yes + +media_dir=/opt +#media_dir=/c/DLNA + +# lease file location +#lease_file=/var/log/upnp.leases + +# bitrates reported by daemon in bits per second +bitrate_up=1000000 +bitrate_down=10000000 + +# "secure" mode : UPnP client are allowed to add mappings only +# to their IP +secure_mode=no + +# default presentation url is http address on port 80 +#presentation_url=http://www.mylan/index.php + +# report system uptime instead of daemon uptime +system_uptime=no + +# notify interval in seconds. default is 30 seconds. +notify_interval=900 + +# unused rules cleaning. +# never remove any rule before this threshold for the number +# of redirections is exceeded. default to 20 +#clean_ruleset_threshold=10 +# clean process work interval in seconds. default to 0 (disabled). +# a 600 seconds (10 minutes) interval makes sense +clean_ruleset_interval=600 + +# log packets in pf +#packet_log=no + +# ALTQ queue in pf +# filter rules must be used for this to be used. +# compile with PF_ENABLE_FILTER_RULES (see config.h file) +#queue=queue_name1 + +# tag name in pf +#tag=tag_name1 + +# make filter rules in pf quick or not. default is yes +# active when compiled with PF_ENABLE_FILTER_RULES (see config.h file) +#quickrules=no + +# uuid : generate your own with "make genuuid" +uuid=fc4ec57e-b051-11db-88f8-0060085db3f6 + +# serial and model number the daemon will report to clients +# in its XML description +serial=12345678 +model_number=1 diff --git a/miniupnpdpath.h b/miniupnpdpath.h new file mode 100644 index 0000000..3f29dd6 --- /dev/null +++ b/miniupnpdpath.h @@ -0,0 +1,47 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __MINIUPNPDPATH_H__ +#define __MINIUPNPDPATH_H__ + +#include "config.h" + +/* Paths and other URLs in the miniupnpd http server */ + +#define ROOTDESC_PATH "/rootDesc.xml" + +#ifdef HAS_DUMMY_SERVICE +#define DUMMY_PATH "/dummy.xml" +#endif + +#define WANCFG_PATH "/WANCfg.xml" +#define WANCFG_CONTROLURL "/ctl/CmnIfCfg" +#define WANCFG_EVENTURL "/evt/CmnIfCfg" + +#define WANIPC_PATH "/WANIPCn.xml" +#define WANIPC_CONTROLURL "/ctl/IPConn" +#define WANIPC_EVENTURL "/evt/IPConn" + +#define CONTENTDIRECTORY_PATH "/ContentDir.xml" +#define CONTENTDIRECTORY_CONTROLURL "/ctl/ContentDir" +#define CONTENTDIRECTORY_EVENTURL "/evt/ContentDir" + +#define CONNECTIONMGR_PATH "/ConnectionMgr.xml" +#define CONNECTIONMGR_CONTROLURL "/ctl/ConnectionMgr" +#define CONNECTIONMGR_EVENTURL "/evt/ConnectionMgr" + +#define X_MS_MEDIARECEIVERREGISTRAR_PATH "/X_MS_MediaReceiverRegistrar.xml" +#define X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL "/ctl/X_MS_MediaReceiverRegistrar" +#define X_MS_MEDIARECEIVERREGISTRAR_EVENTURL "/evt/X_MS_MediaReceiverRegistrar" + +#ifdef ENABLE_L3F_SERVICE +#define L3F_PATH "/L3F.xml" +#define L3F_CONTROLURL "/ctl/L3F" +#define L3F_EVENTURL "/evt/L3F" +#endif + +#endif + diff --git a/miniupnpdtypes.h b/miniupnpdtypes.h new file mode 100644 index 0000000..70ba799 --- /dev/null +++ b/miniupnpdtypes.h @@ -0,0 +1,23 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2007 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#ifndef __MINIUPNPDTYPES_H__ +#define __MINIUPNPDTYPES_H__ + +#include "config.h" +#include <netinet/in.h> + +/* structure for storing lan addresses + * with ascii representation and mask */ +struct lan_addr_s { + char str[16]; /* example: 192.168.0.1 */ + struct in_addr addr, mask; /* ip/mask */ +#ifdef MULTIPLE_EXTERNAL_IP + char ext_ip_str[16]; + struct in_addr ext_ip_addr; +#endif +}; + +#endif @@ -3,32 +3,10 @@ * Project : miniupnp * Website : http://miniupnp.free.fr/ * Author : Thomas Bernard - * - * Copyright (c) 2005, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ #ifndef __MINIXML_H__ #define __MINIXML_H__ #define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) @@ -2,36 +2,15 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * author: Ryan Wagoner - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> +#include <syslog.h> #include "options.h" #include "upnpglobalvars.h" @@ -42,24 +21,37 @@ static const struct { enum upnpconfigoptions id; const char * name; } optionids[] = { - { UPNPIFNAME, "network_interface" }, + { UPNPEXT_IFNAME, "ext_ifname" }, + { UPNPEXT_IP, "ext_ip" }, { UPNPLISTENING_IP, "listening_ip" }, { UPNPPORT, "port" }, + { UPNPBITRATE_UP, "bitrate_up" }, + { UPNPBITRATE_DOWN, "bitrate_down" }, { UPNPPRESENTATIONURL, "presentation_url" }, { UPNPNOTIFY_INTERVAL, "notify_interval" }, { UPNPSYSTEM_UPTIME, "system_uptime" }, + { UPNPPACKET_LOG, "packet_log" }, { UPNPUUID, "uuid"}, { UPNPSERIAL, "serial"}, - { UPNPMODEL_NAME, "model_name"}, { UPNPMODEL_NUMBER, "model_number"}, - { UPNPFRIENDLYNAME, "friendly_name"}, + { UPNPCLEANTHRESHOLD, "clean_ruleset_threshold"}, + { UPNPCLEANINTERVAL, "clean_ruleset_interval"}, +#ifdef ENABLE_NATPMP + { UPNPENABLENATPMP, "enable_natpmp"}, +#endif + { UPNPENABLE, "enable_upnp"}, +#ifdef USE_PF + { UPNPQUEUE, "queue"}, + { UPNPTAG, "tag"}, +#endif +#ifdef PF_ENABLE_FILTER_RULES + { UPNPQUICKRULES, "quickrules" }, +#endif +#ifdef ENABLE_LEASEFILE + { UPNPLEASEFILE, "lease_file"}, +#endif { UPNPMEDIADIR, "media_dir"}, - { UPNPALBUMART_NAMES, "album_art_names"}, - { UPNPINOTIFY, "inotify" }, - { UPNPDBDIR, "db_dir" }, - { UPNPLOGDIR, "log_dir" }, - { ENABLE_TIVO, "enable_tivo" }, - { ENABLE_DLNA_STRICT, "strict_dlna" } + { UPNPSECUREMODE, "secure_mode"} }; int @@ -1,32 +1,10 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * author: Ryan Wagoner - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __OPTIONS_H__ #define __OPTIONS_H__ @@ -35,24 +13,35 @@ /* enum of option available in the miniupnpd.conf */ enum upnpconfigoptions { UPNP_INVALID = 0, - UPNPIFNAME = 1, /* ext_ifname */ + UPNPEXT_IFNAME = 1, /* ext_ifname */ + UPNPEXT_IP, /* ext_ip */ UPNPLISTENING_IP, /* listening_ip */ - UPNPPORT, /* port */ - UPNPPRESENTATIONURL, /* presentation_url */ - UPNPNOTIFY_INTERVAL, /* notify_interval */ - UPNPSYSTEM_UPTIME, /* system_uptime */ - UPNPUUID, /* uuid */ - UPNPSERIAL, /* serial */ - UPNPMODEL_NAME, /* model_name */ + UPNPPORT, /* "port" */ + UPNPBITRATE_UP, /* "bitrate_up" */ + UPNPBITRATE_DOWN, /* "bitrate_down" */ + UPNPPRESENTATIONURL, /* presentation_url */ + UPNPNOTIFY_INTERVAL, /* notify_interval */ + UPNPSYSTEM_UPTIME, /* "system_uptime" */ + UPNPPACKET_LOG, /* "packet_log" */ + UPNPUUID, /* uuid */ + UPNPSERIAL, /* serial */ UPNPMODEL_NUMBER, /* model_number */ - UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */ + UPNPCLEANTHRESHOLD, /* clean_ruleset_threshold */ + UPNPCLEANINTERVAL, /* clean_ruleset_interval */ + UPNPENABLENATPMP, /* enable_natpmp */ +#ifdef USE_PF + UPNPQUEUE, /* queue */ + UPNPTAG, /* tag */ +#endif +#ifdef PF_ENABLE_FILTER_RULES + UPNPQUICKRULES, /* quickrules */ +#endif + UPNPSECUREMODE, /* secure_mode */ +#ifdef ENABLE_LEASEFILE + UPNPLEASEFILE, /* lease_file */ +#endif UPNPMEDIADIR, /* directory to search for UPnP-A/V content */ - UPNPALBUMART_NAMES, /* list of '/'-delimited file names to check for album art */ - UPNPINOTIFY, /* enable inotify on the media directories */ - UPNPDBDIR, /* base directory to store the database and album art cache */ - UPNPLOGDIR, /* base directory to store the log file */ - ENABLE_TIVO, /* enable support for streaming images and music to TiVo */ - ENABLE_DLNA_STRICT /* strictly adhere to DLNA specs */ + UPNPENABLE /* enable_upnp */ }; /* readoptionsfile() @@ -66,7 +55,7 @@ readoptionsfile(const char * fname); void freeoptions(void); -#define MAX_OPTION_VALUE_LEN (200) +#define MAX_OPTION_VALUE_LEN (80) struct option { enum upnpconfigoptions id; @@ -1,19 +1,19 @@ -/* MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard +/* MiniDLNA media server + * Copyright (C) 2008 Justin Maggard * - * This file is part of MiniDLNA. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdio.h> #include <string.h> @@ -21,625 +21,417 @@ #include <unistd.h> #include <dirent.h> #include <locale.h> -#include <libgen.h> -#include <inttypes.h> #include <sys/stat.h> -#include <sys/time.h> -#include <sys/resource.h> - -#include "config.h" -#ifdef ENABLE_NLS -#include <libintl.h> -#endif #include <sqlite3.h> #include "upnpglobalvars.h" #include "metadata.h" -#include "playlist.h" -#include "utils.h" #include "sql.h" #include "scanner.h" -#include "log.h" -int valid_cache = 0; - -struct virtual_item +int +ends_with(const char * haystack, const char * needle) { - sqlite3_int64 objectID; - char parentID[64]; - char name[256]; -}; + const char *found = strcasestr(haystack, needle); + return (found && found[strlen(needle)] == '\0'); +} -sqlite_int64 -get_next_available_id(const char * table, const char * parentID) +int +is_video(const char * file) { - char *ret, *base; - sqlite_int64 objectID = 0; - - ret = sql_get_text_field(db, "SELECT OBJECT_ID from %s where ID = " - "(SELECT max(ID) from %s where PARENT_ID = '%s')", - table, table, parentID); - if( ret ) - { - base = strrchr(ret, '$'); - if( base ) - objectID = strtoll(base+1, NULL, 16) + 1; - sqlite3_free(ret); - } + return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") || + ends_with(file, ".ts") || ends_with(file, ".avi")); +} - return objectID; +int +is_audio(const char * file) +{ + return (ends_with(file, ".mp3") || + ends_with(file, ".m4a") || ends_with(file, ".aac")); } int -insert_container(const char * item, const char * rootParent, const char * refID, const char *class, - const char *artist, const char *genre, const char *album_art, sqlite3_int64 *objectID, sqlite3_int64 *parentID) +is_image(const char * file) { - char *result; - char *base; - int ret = 0; - sqlite_int64 detailID = 0; + return (ends_with(file, ".jpg") || ends_with(file, ".jpeg")); +} - result = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" - " where PARENT_ID = '%s'" - " and NAME = '%q'" - " and CLASS = 'container.%s' limit 1", - rootParent, item, class); - if( result ) +long long int +insert_container(const char * tmpTable, const char * item, const char * rootParent, const char *subParent, const char *class, long unsigned int detailID) +{ + char **result; + char *sql; + int cols, rows, ret; + char *zErrMsg = NULL; + int parentID = 0, objectID = 0; + + sql = sqlite3_mprintf("SELECT * from %s where ITEM = '%q' and SUBITEM = '%q'", tmpTable, item, subParent); + ret = sql_get_table(db, sql, &result, &rows, &cols, &zErrMsg); + sqlite3_free(sql); + if( cols ) { - base = strrchr(result, '$'); - if( base ) - *parentID = strtoll(base+1, NULL, 16); - else - *parentID = 0; - *objectID = get_next_available_id("OBJECTS", result); + sscanf(result[4], "%X", &parentID); + sqlite3_free_table(result); + sql = sqlite3_mprintf("SELECT OBJECT_ID, max(ID) from OBJECTS where PARENT_ID = '%s$%X'", rootParent, parentID); + ret = sql_get_table(db, sql, &result, 0, &cols, &zErrMsg); + sqlite3_free(sql); + if( result[2] && (sscanf(rindex(result[2], '$')+1, "%X", &objectID) == 1) ) + { + objectID++; + } } else { - *objectID = 0; - *parentID = get_next_available_id("OBJECTS", rootParent); - if( refID ) + sqlite3_free_table(result); + sql = sqlite3_mprintf("SELECT OBJECT_ID, max(ID) from OBJECTS where PARENT_ID = '%s'", rootParent); + sql_get_table(db, sql, &result, &rows, &cols, &zErrMsg); + sqlite3_free(sql); + if( result[2] && (sscanf(rindex(result[2], '$')+1, "%X", &parentID) == 1) ) { - result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = %Q", refID); - if( result ) - detailID = strtoll(result, NULL, 10); + parentID++; } - if( !detailID ) + else { - detailID = GetFolderMetadata(item, NULL, artist, genre, album_art); + parentID = 0; } - ret = sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', %Q, %"PRId64", 'container.%s', '%q')", - rootParent, *parentID, rootParent, refID, detailID, class, item); + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME) " + "VALUES" + " ('%s$%X', '%s', %lu, 'container.%s', '%q')", + rootParent, parentID, rootParent, detailID, class, item); + ret = sql_exec(db, sql); + sqlite3_free(sql); + sql = sqlite3_mprintf("INSERT into %s values ('%q', '%X', '%q')", tmpTable, item, parentID, subParent); + sql_exec(db, sql); + sqlite3_free(sql); } - sqlite3_free(result); + sqlite3_free_table(result); - return ret; + return (long long)parentID<<32|objectID; } -static void -insert_containers(const char * name, const char *path, const char * refID, const char * class, sqlite3_int64 detailID) +void +insert_containers(const char * name, const char *path, const char * refID, const char * class, long unsigned int detailID) { + char sql_buf[128]; char *sql; char **result; int ret; int cols, row; - sqlite_int64 objectID, parentID; + char *zErrMsg = NULL; + long long int container; + int parentID; + int objectID = -1; + + sprintf(sql_buf, "SELECT * from DETAILS where ID = %lu", detailID); + ret = sql_get_table(db, sql_buf, &result, &row, &cols, &zErrMsg); if( strstr(class, "imageItem") ) { - char *date = NULL, *cam = NULL; - char date_taken[13], camera[64]; - static struct virtual_item last_date; - static struct virtual_item last_cam; - static struct virtual_item last_camdate; - static sqlite_int64 last_all_objectID = 0; - - asprintf(&sql, "SELECT DATE, CREATOR from DETAILS where ID = %"PRId64, detailID); - ret = sql_get_table(db, sql, &result, &row, &cols); - free(sql); - if( ret == SQLITE_OK ) - { - date = result[2]; - cam = result[3]; - } - + char *date = result[12+cols], *cam = result[16+cols]; + char date_taken[11]; date_taken[10] = '\0'; + static int last_all_objectID = 0; if( date ) - { strncpy(date_taken, date, 10); - date_taken[10] = '\0'; - } - else - { - strcpy(date_taken, _("Unknown Date")); - } - if( valid_cache && strcmp(last_date.name, date_taken) == 0 ) - { - last_date.objectID++; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); - } - else - { - insert_container(date_taken, IMAGE_DATE_ID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID); - sprintf(last_date.parentID, IMAGE_DATE_ID"$%"PRIX64, parentID); - last_date.objectID = objectID; - strcpy(last_date.name, date_taken); - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_date.parentID, last_date.objectID, last_date.parentID, refID, class, detailID, name); - - if( cam ) - { - strncpy(camera, cam, 63); - camera[63] = '\0'; - } - else - { - strcpy(camera, _("Unknown Camera")); - } - if( !valid_cache || strcmp(camera, last_cam.name) != 0 ) - { - insert_container(camera, IMAGE_CAMERA_ID, NULL, "storageFolder", NULL, NULL, NULL, &objectID, &parentID); - sprintf(last_cam.parentID, IMAGE_CAMERA_ID"$%"PRIX64, parentID); - strncpy(last_cam.name, camera, 255); - last_camdate.name[0] = '\0'; - } - if( valid_cache && strcmp(last_camdate.name, date_taken) == 0 ) + if( date ) { - last_camdate.objectID++; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); + container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", 0); + parentID = container>>32; + objectID = container; + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('3$12$%X$%X', '3$12$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } - else + if( cam && date ) { - insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID); - sprintf(last_camdate.parentID, "%s$%"PRIX64, last_cam.parentID, parentID); - last_camdate.objectID = objectID; - strcpy(last_camdate.name, date_taken); - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); + container = insert_container("CAMS", cam, "3$13", NULL, "storageFolder", 0); + parentID = container>>32; + //objectID = container; + char parent[64]; + sprintf(parent, "3$13$%X", parentID); + long long int subcontainer = insert_container("CAMDATE", date_taken, parent, cam, "storageFolder", 0); + int subParentID = subcontainer>>32; + int subObjectID = subcontainer; + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('3$13$%X$%X$%X', '3$13$%X$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, subParentID, subObjectID, parentID, subParentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, detailID, name); /* All Images */ - if( !last_all_objectID ) - { - last_all_objectID = get_next_available_id("OBJECTS", IMAGE_ALL_ID); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('"IMAGE_ALL_ID"$%"PRIX64"', '"IMAGE_ALL_ID"', '%s', '%s', %"PRId64", %Q)", - last_all_objectID++, refID, class, detailID, name); + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('3$11$%X', '3$11', '%s', '%s', %lu, %Q, %Q)", + last_all_objectID++, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } else if( strstr(class, "audioItem") ) { - asprintf(&sql, "SELECT ALBUM, ARTIST, GENRE, ALBUM_ART from DETAILS where ID = %"PRId64, detailID); - ret = sql_get_table(db, sql, &result, &row, &cols); - free(sql); - if( ret != SQLITE_OK ) - return; - if( !row ) - { - sqlite3_free_table(result); - return; - } - char *album = result[4], *artist = result[5], *genre = result[6]; - char *album_art = result[7]; - static struct virtual_item last_album; - static struct virtual_item last_artist; - static struct virtual_item last_artistAlbum; - static struct virtual_item last_artistAlbumAll; - static struct virtual_item last_genre; - static struct virtual_item last_genreArtist; - static struct virtual_item last_genreArtistAll; - static sqlite_int64 last_all_objectID = 0; + char *artist = result[6+cols], *album = result[7+cols], *genre = result[8+cols]; + static char last_artist[1024]; + static int last_artist_parentID, last_artist_objectID; + static char last_album[1024]; + static int last_album_parentID, last_album_objectID; + static char last_genre[1024]; + static int last_genre_parentID, last_genre_objectID; + static int last_all_objectID = 0; - if( album ) + if( artist ) { - if( valid_cache && strcmp(album, last_album.name) == 0 ) + if( strcmp(artist, last_artist) == 0 ) { - last_album.objectID++; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); + objectID = ++last_artist_objectID; + parentID = last_artist_parentID; } else { - strcpy(last_album.name, album); - insert_container(album, MUSIC_ALBUM_ID, NULL, "album.musicAlbum", artist, genre, album_art, &objectID, &parentID); - sprintf(last_album.parentID, MUSIC_ALBUM_ID"$%llX", parentID); - last_album.objectID = objectID; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); + strcpy(last_artist, artist); + container = insert_container("ARTISTS", artist, "1$6", NULL, "person.musicArtist", 0); + parentID = container>>32; + objectID = container; + last_artist_objectID = objectID; + last_artist_parentID = parentID; } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_album.parentID, last_album.objectID, last_album.parentID, refID, class, detailID, name); + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$6$%X$%X', '1$6$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } - if( artist ) + if( album ) { - if( !valid_cache || strcmp(artist, last_artist.name) != 0 ) + if( strcmp(album, last_album) == 0 ) { - insert_container(artist, MUSIC_ARTIST_ID, NULL, "person.musicArtist", NULL, genre, NULL, &objectID, &parentID); - sprintf(last_artist.parentID, MUSIC_ARTIST_ID"$%"PRIX64, parentID); - strcpy(last_artist.name, artist); - last_artistAlbum.name[0] = '\0'; - /* Add this file to the "- All Albums -" container as well */ - insert_container(_("- All Albums -"), last_artist.parentID, NULL, "album", artist, genre, NULL, &objectID, &parentID); - sprintf(last_artistAlbumAll.parentID, "%s$%"PRIX64, last_artist.parentID, parentID); - last_artistAlbumAll.objectID = objectID; + objectID = ++last_album_objectID; + parentID = last_album_parentID; } else { - last_artistAlbumAll.objectID++; + strcpy(last_album, album); + container = insert_container("ALBUMS", album, "1$7", NULL, "album.musicAlbum", detailID); + parentID = container>>32; + objectID = container; + last_album_objectID = objectID; + last_album_parentID = parentID; } - if( valid_cache && strcmp(album?album:_("Unknown Album"), last_artistAlbum.name) == 0 ) - { - last_artistAlbum.objectID++; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); - } - else - { - insert_container(album?album:_("Unknown Album"), last_artist.parentID, album?last_album.parentID:NULL, - "album.musicAlbum", artist, genre, album_art, &objectID, &parentID); - sprintf(last_artistAlbum.parentID, "%s$%"PRIX64, last_artist.parentID, parentID); - last_artistAlbum.objectID = objectID; - strcpy(last_artistAlbum.name, album?album:_("Unknown Album")); - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_artistAlbum.parentID, last_artistAlbum.objectID, last_artistAlbum.parentID, refID, class, detailID, name); - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_artistAlbumAll.parentID, last_artistAlbumAll.objectID, last_artistAlbumAll.parentID, refID, class, detailID, name); + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$7$%X$%X', '1$7$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } if( genre ) { - if( !valid_cache || strcmp(genre, last_genre.name) != 0 ) + if( strcmp(genre, last_genre) == 0 ) { - insert_container(genre, MUSIC_GENRE_ID, NULL, "genre.musicGenre", NULL, NULL, NULL, &objectID, &parentID); - sprintf(last_genre.parentID, MUSIC_GENRE_ID"$%"PRIX64, parentID); - strcpy(last_genre.name, genre); - last_genreArtist.name[0] = '\0'; - /* Add this file to the "- All Artists -" container as well */ - insert_container(_("- All Artists -"), last_genre.parentID, NULL, "person", NULL, genre, NULL, &objectID, &parentID); - sprintf(last_genreArtistAll.parentID, "%s$%"PRIX64, last_genre.parentID, parentID); - last_genreArtistAll.objectID = objectID; + objectID = ++last_genre_objectID; + parentID = last_genre_parentID; } else { - last_genreArtistAll.objectID++; + strcpy(last_genre, genre); + container = insert_container("GENRES", genre, "1$5", NULL, "genre.musicGenre", 0); + parentID = container>>32; + objectID = container; + last_genre_objectID = objectID; + last_genre_parentID = parentID; } - if( valid_cache && strcmp(artist?artist:_("Unknown Artist"), last_genreArtist.name) == 0 ) - { - last_genreArtist.objectID++; - } - else - { - insert_container(artist?artist:_("Unknown Artist"), last_genre.parentID, artist?last_artist.parentID:NULL, - "person.musicArtist", NULL, genre, NULL, &objectID, &parentID); - sprintf(last_genreArtist.parentID, "%s$%"PRIX64, last_genre.parentID, parentID); - last_genreArtist.objectID = objectID; - strcpy(last_genreArtist.name, artist?artist:_("Unknown Artist")); - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre/artist item: %s/%s/%X\n", last_genreArtist.name, last_genreArtist.parentID, last_genreArtist.objectID); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_genreArtist.parentID, last_genreArtist.objectID, last_genreArtist.parentID, refID, class, detailID, name); - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%"PRIX64"', '%s', '%s', '%s', %"PRId64", %Q)", - last_genreArtistAll.parentID, last_genreArtistAll.objectID, last_genreArtistAll.parentID, refID, class, detailID, name); + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$5$%X$%X', '1$5$%X', '%s', '%s', %lu, %Q, %Q)", + parentID, objectID, parentID, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } /* All Music */ - if( !last_all_objectID ) - { - last_all_objectID = get_next_available_id("OBJECTS", MUSIC_ALL_ID); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('"MUSIC_ALL_ID"$%"PRIX64"', '"MUSIC_ALL_ID"', '%s', '%s', %"PRId64", %Q)", - last_all_objectID++, refID, class, detailID, name); - } - else if( strstr(class, "videoItem") ) - { - static sqlite_int64 last_all_objectID = 0; - - /* All Videos */ - if( !last_all_objectID ) - { - last_all_objectID = get_next_available_id("OBJECTS", VIDEO_ALL_ID); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('"VIDEO_ALL_ID"$%"PRIX64"', '"VIDEO_ALL_ID"', '%s', '%s', %"PRId64", %Q)", - last_all_objectID++, refID, class, detailID, name); - return; - } - else - { - return; + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('1$4$%X', '1$4', '%s', '%s', %lu, %Q, %Q)", + last_all_objectID++, refID, class, detailID, path, name); + sql_exec(db, sql); + sqlite3_free(sql); } sqlite3_free_table(result); - valid_cache = 1; } int -insert_directory(const char * name, const char * path, const char * base, const char * parentID, int objectID) +insert_directory(const char * name, const char * path, const char * parentID, int objectID) { - int found = 0; - sqlite_int64 detailID = 0; - char * refID = NULL; + char *sql; + int ret, i; char class[] = "container.storageFolder"; - char * id_buf = NULL; - char * parent_buf = NULL; - char *dir_buf, *dir; - char *result, *p; - static char last_found[256] = "-1"; - - if( strcmp(base, BROWSEDIR_ID) != 0 ) - asprintf(&refID, "%s%s$%X", BROWSEDIR_ID, parentID, objectID); + const char * const base[] = { BROWSEDIR_ID, MUSIC_DIR_ID, VIDEO_DIR_ID, IMAGE_DIR_ID, 0 }; - if( refID ) + for( i=0; base[i]; i++ ) { - dir_buf = strdup(path); - dir = dirname(dir_buf); - asprintf(&id_buf, "%s%s$%X", base, parentID, objectID); - asprintf(&parent_buf, "%s%s", base, parentID); - while( !found ) - { - if( strcmp(id_buf, last_found) == 0 ) - break; - if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf) > 0 ) - { - strcpy(last_found, id_buf); - break; - } - /* Does not exist. Need to create, and may need to create parents also */ - result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID); - if( result ) - { - detailID = strtoll(result, NULL, 10); - sqlite3_free(result); - } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " - "VALUES" - " ('%s', '%s', %Q, %"PRId64", '%s', '%q')", - id_buf, parent_buf, refID, detailID, class, strrchr(dir, '/')+1); - if( (p = strrchr(id_buf, '$')) ) - *p = '\0'; - if( (p = strrchr(parent_buf, '$')) ) - *p = '\0'; - if( (p = strrchr(refID, '$')) ) - *p = '\0'; - dir = dirname(dir); - } - free(refID); - free(parent_buf); - free(id_buf); - free(dir_buf); - return 1; + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, CLASS, PATH, NAME) " + "VALUES" + " ('%s%s$%X', '%s%s', '%s', '%q', '%q')", + base[i], parentID, objectID, base[i], parentID, class, path, name); + //DEBUG printf("SQL: %s\n", sql); + ret = sql_exec(db, sql); + sqlite3_free(sql); } - - detailID = GetFolderMetadata(name, path, NULL, NULL, NULL); - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " - "VALUES" - " ('%s%s$%X', '%s%s', %Q, %"PRId64", '%s', '%q')", - base, parentID, objectID, base, parentID, refID, detailID, class, name); - if( refID ) - free(refID); - return -1; } int insert_file(char * name, const char * path, const char * parentID, int object) { + char *sql; char class[32]; char objectID[64]; - sqlite3_int64 detailID = 0; + unsigned long int detailID = 0; char base[8]; - char * typedir_parentID; - int typedir_objectID; - char * baseid; - char * orig_name = NULL; + + static long unsigned int fileno = 0; + printf("Scanned %lu files...\r", fileno++); fflush(stdout); + + sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); if( is_image(name) ) { - if( is_album_art(name) ) - return -1; strcpy(base, IMAGE_DIR_ID); - strcpy(class, "item.imageItem.photo"); + strcpy(class, "item.imageItem"); detailID = GetImageMetadata(path, name); } - else if( is_video(name) ) - { - orig_name = strdup(name); - strcpy(base, VIDEO_DIR_ID); - strcpy(class, "item.videoItem"); - detailID = GetVideoMetadata(path, name); - if( !detailID ) - strcpy(name, orig_name); - } - else if( is_playlist(name) ) - { - if( insert_playlist(path, name) == 0 ) - return 1; - } - if( !detailID && is_audio(name) ) + else if( is_audio(name) ) { strcpy(base, MUSIC_DIR_ID); strcpy(class, "item.audioItem.musicTrack"); detailID = GetAudioMetadata(path, name); } - if( orig_name ) - free(orig_name); - if( !detailID ) - { - DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path); - return -1; - } - - sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); - - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s', '%s%s', '%s', %"PRId64", '%q')", - objectID, BROWSEDIR_ID, parentID, class, detailID, name); - - if( *parentID ) + else if( is_video(name) ) { - typedir_objectID = 0; - typedir_parentID = strdup(parentID); - baseid = strrchr(typedir_parentID, '$'); - if( baseid ) - { - typedir_objectID = strtol(baseid+1, NULL, 16); - *baseid = '\0'; - } - insert_directory(name, path, base, typedir_parentID, typedir_objectID); - free(typedir_parentID); + strcpy(base, VIDEO_DIR_ID); + strcpy(class, "item.videoItem"); + detailID = GetVideoMetadata(path, name); } - sql_exec(db, "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s%s$%X', '%s%s', '%s', '%s', %"PRId64", '%q')", - base, parentID, object, base, parentID, objectID, class, detailID, name); - + //DEBUG printf("Got DetailID %lu!\n", detailID); + + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('%s', '%s%s', '%s', %lu, '%q', '%q')", + objectID, BROWSEDIR_ID, parentID, class, detailID, path, name); + //DEBUG printf("SQL: %s\n", sql); + sql_exec(db, sql); + sqlite3_free(sql); + #if 0 + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + "VALUES" + " ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q', '%q')", + base, parentID, object, base, parentID, objectID, class, detailID, path, name); + //DEBUG printf("SQL: %s\n", sql); + sql_exec(db, sql); + sqlite3_free(sql); + #else insert_containers(name, path, objectID, class, detailID); - return 0; + #endif + return -1; } int -CreateDatabase(void) +create_database(void) { int ret, i; - const char * containers[] = { "0","-1", "root", - MUSIC_ID, "0", _("Music"), - MUSIC_ALL_ID, MUSIC_ID, _("All Music"), - MUSIC_GENRE_ID, MUSIC_ID, _("Genre"), - MUSIC_ARTIST_ID, MUSIC_ID, _("Artist"), - MUSIC_ALBUM_ID, MUSIC_ID, _("Album"), - MUSIC_DIR_ID, MUSIC_ID, _("Folders"), - MUSIC_PLIST_ID, MUSIC_ID, _("Playlists"), - - VIDEO_ID, "0", _("Video"), - VIDEO_ALL_ID, VIDEO_ID, _("All Video"), - VIDEO_DIR_ID, VIDEO_ID, _("Folders"), - - IMAGE_ID, "0", _("Pictures"), - IMAGE_ALL_ID, IMAGE_ID, _("All Pictures"), - IMAGE_DATE_ID, IMAGE_ID, _("Date Taken"), - IMAGE_CAMERA_ID, IMAGE_ID, _("Camera"), - IMAGE_DIR_ID, IMAGE_ID, _("Folders"), - - BROWSEDIR_ID, "0", _("Browse Folders"), - 0 }; + char sql_buf[512]; + const char * containers[] = { "0","-1", "root", + "1", "0", "Music", + "1$4", "1", "All Music", + "1$5", "1", "Genre", + "1$6", "1", "Artist", + "1$7", "1", "Album", + "1$20", "1", "Folders", + "2", "0", "Video", + "2$8", "2", "All Video", + "2$21", "2", "Folders", + "3", "0", "Pictures", + "3$11", "3", "All Pictures", + "3$12", "3", "Date Taken", + "3$13", "3", "Camera", + "3$22", "3", "Folders", + "64", "0", "Browse Folders", + 0 }; + + sql_exec(db, "pragma temp_store = MEMORY"); + sql_exec(db, "pragma synchronous = OFF;"); + sql_exec(db, "pragma cache_size = 8192;"); ret = sql_exec(db, "CREATE TABLE OBJECTS ( " "ID INTEGER PRIMARY KEY AUTOINCREMENT, " - "OBJECT_ID TEXT UNIQUE NOT NULL, " + "OBJECT_ID TEXT NOT NULL, " "PARENT_ID TEXT NOT NULL, " "REF_ID TEXT DEFAULT NULL, " "CLASS TEXT NOT NULL, " "DETAIL_ID INTEGER DEFAULT NULL, " + "PATH TEXT DEFAULT NULL, " "NAME TEXT DEFAULT NULL" ");"); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, "CREATE TABLE DETAILS ( " "ID INTEGER PRIMARY KEY AUTOINCREMENT, " - "PATH TEXT DEFAULT NULL, " "SIZE INTEGER, " - "TITLE TEXT COLLATE NOCASE, " + "TITLE TEXT, " "DURATION TEXT, " "BITRATE INTEGER, " "SAMPLERATE INTEGER, " - "ARTIST TEXT COLLATE NOCASE, " - "ALBUM TEXT COLLATE NOCASE, " - "GENRE TEXT COLLATE NOCASE, " + "ARTIST TEXT, " + "ALBUM TEXT, " + "GENRE TEXT, " "COMMENT TEXT, " "CHANNELS INTEGER, " "TRACK INTEGER, " "DATE DATE, " - "RESOLUTION TEXT, " + "WIDTH TEXT, " + "HEIGHT TEXT, " "THUMBNAIL BOOL DEFAULT 0, " - "CREATOR TEXT COLLATE NOCASE, " + "CREATOR TEXT, " "DLNA_PN TEXT, " - "MIME TEXT, " - "ALBUM_ART INTEGER DEFAULT 0, " - "DISC INTEGER, " - "TIMESTAMP INTEGER" - ")"); - if( ret != SQLITE_OK ) - goto sql_failed; - ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( " - "ID INTEGER PRIMARY KEY AUTOINCREMENT, " - "PATH TEXT NOT NULL" - ")"); - if( ret != SQLITE_OK ) - goto sql_failed; - ret = sql_exec(db, "CREATE TABLE CAPTIONS (" - "ID INTEGER PRIMARY KEY, " - "PATH TEXT NOT NULL" - ")"); - if( ret != SQLITE_OK ) - goto sql_failed; - ret = sql_exec(db, "CREATE TABLE PLAYLISTS (" - "ID INTEGER PRIMARY KEY AUTOINCREMENT, " - "NAME TEXT NOT NULL, " - "PATH TEXT NOT NULL, " - "ITEMS INTEGER DEFAULT 0, " - "FOUND INTEGER DEFAULT 0" - ")"); - if( ret != SQLITE_OK ) - goto sql_failed; - ret = sql_exec(db, "CREATE TABLE SETTINGS (" - "UPDATE_ID INTEGER PRIMARY KEY DEFAULT 0, " - "FLAGS INTEGER DEFAULT 0" - ")"); - if( ret != SQLITE_OK ) - goto sql_failed; - ret = sql_exec(db, "INSERT into SETTINGS values (0, 0)"); + "MIME TEXT" + ");"); if( ret != SQLITE_OK ) goto sql_failed; for( i=0; containers[i]; i=i+3 ) { - ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" - " values " - "('%s', '%s', %lld, 'container.storageFolder', '%q')", - containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL, NULL), containers[i+2]); + sprintf(sql_buf, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, CLASS, NAME) values ( '%s', '%s', 'container.storageFolder', '%s')", + containers[i], containers[i+1], containers[i+2]); + ret = sql_exec(db, sql_buf); if( ret != SQLITE_OK ) goto sql_failed; } + sql_exec(db, "create TEMP TABLE ARTISTS (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE ALBUMS (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE GENRES (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE DATES (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE CAMS (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create TEMP TABLE CAMDATE (ITEM TEXT, OBJECT_ID TEXT, SUBITEM TEXT DEFAULT NULL);"); + sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); - sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);"); + sql_exec(db, "create INDEX IDX_OBJECTS_PATH ON OBJECTS(PATH);"); sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); - sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);"); - sql_exec(db, "create INDEX IDX_SCANNER_OPT ON OBJECTS(PARENT_ID, NAME, OBJECT_ID);"); + sql_failed: if( ret != SQLITE_OK ) @@ -648,181 +440,71 @@ sql_failed: } int -filter_audio(const struct dirent *d) -{ - return ( (*d->d_name != '.') && - ((d->d_type == DT_DIR) || - (d->d_type == DT_LNK) || - (d->d_type == DT_UNKNOWN) || - ((d->d_type == DT_REG) && - (is_audio(d->d_name) || - is_playlist(d->d_name) - ) - ) )); -} - -int -filter_video(const struct dirent *d) -{ - return ( (*d->d_name != '.') && - ((d->d_type == DT_DIR) || - (d->d_type == DT_LNK) || - (d->d_type == DT_UNKNOWN) || - ((d->d_type == DT_REG) && - is_video(d->d_name) ) - ) ); -} - -int -filter_images(const struct dirent *d) -{ - return ( (*d->d_name != '.') && - ((d->d_type == DT_DIR) || - (d->d_type == DT_LNK) || - (d->d_type == DT_UNKNOWN) || - ((d->d_type == DT_REG) && - is_image(d->d_name) ) - ) ); -} - -int filter_media(const struct dirent *d) { + struct stat entry; return ( (*d->d_name != '.') && - ((d->d_type == DT_DIR) || - (d->d_type == DT_LNK) || - (d->d_type == DT_UNKNOWN) || - ((d->d_type == DT_REG) && - (is_image(d->d_name) || - is_audio(d->d_name) || - is_video(d->d_name) || - is_playlist(d->d_name) - ) - ) )); + (stat(d->d_name, &entry) == 0) && + (S_ISDIR(entry.st_mode) || + (S_ISREG(entry.st_mode) && + (is_image(d->d_name) || + is_audio(d->d_name) || + is_video(d->d_name) + ) + )) ); } void -ScanDirectory(const char * dir, const char * parent, enum media_types dir_type) +ScanDirectory(const char * dir, const char * parent) { struct dirent **namelist; - int i, n, startID=0; + struct stat entry; + int n, i; char parent_id[PATH_MAX]; char full_path[PATH_MAX]; - char * name = NULL; - static long long unsigned int fileno = 0; - enum file_types type; + char * name; + + if( !parent ) + { + if( create_database() != 0 ) + { + fprintf(stderr, "Error creating database!\n"); + return; + } + } setlocale(LC_COLLATE, ""); if( chdir(dir) != 0 ) return; - - DPRINTF(parent?E_INFO:E_WARN, L_SCANNER, _("Scanning %s\n"), dir); - switch( dir_type ) - { - case ALL_MEDIA: - n = scandir(".", &namelist, filter_media, alphasort); - break; - case AUDIO_ONLY: - n = scandir(".", &namelist, filter_audio, alphasort); - break; - case VIDEO_ONLY: - n = scandir(".", &namelist, filter_video, alphasort); - break; - case IMAGES_ONLY: - n = scandir(".", &namelist, filter_images, alphasort); - break; - default: - n = -1; - break; - } + n = scandir(".", &namelist, filter_media, alphasort); if (n < 0) { fprintf(stderr, "Error scanning %s [scandir]\n", dir); return; } - - if( !parent ) - { - startID = get_next_available_id("OBJECTS", BROWSEDIR_ID); - } - - for (i=0; i < n; i++) - { -#if !USE_FORK - if( quitting ) - break; -#endif - type = TYPE_UNKNOWN; - sprintf(full_path, "%s/%s", dir, namelist[i]->d_name); - name = escape_tag(namelist[i]->d_name, 1); - if( namelist[i]->d_type == DT_DIR ) - { - type = TYPE_DIR; - } - else if( namelist[i]->d_type == DT_REG ) - { - type = TYPE_FILE; - } - else - { - type = resolve_unknown_type(full_path, dir_type); - } - if( (type == TYPE_DIR) && (access(full_path, R_OK|X_OK) == 0) ) - { - insert_directory(name, full_path, BROWSEDIR_ID, (parent ? parent:""), i+startID); - sprintf(parent_id, "%s$%X", (parent ? parent:""), i+startID); - ScanDirectory(full_path, parent_id, dir_type); - } - else if( type == TYPE_FILE ) + for (i=0; i < n; i++) { + if( stat(namelist[i]->d_name, &entry) == 0 ) { - if( insert_file(name, full_path, (parent ? parent:""), i+startID) == 0 ) - fileno++; + name = NULL; + sprintf(full_path, "%s/%s", dir, namelist[i]->d_name); + if( index(namelist[i]->d_name, '&') ) + { + name = modifyString(strdup(namelist[i]->d_name), "&", "&amp;", 0); + } + if( S_ISDIR(entry.st_mode) ) + { + insert_directory(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i); + sprintf(parent_id, "%s$%X", (parent ? parent:""), i); + ScanDirectory(full_path, parent_id); + } + else + { + insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i); + } } - free(name); + if( name ) + free(name); free(namelist[i]); } free(namelist); - if( parent ) - { - chdir(dirname((char*)dir)); - } - else - { - DPRINTF(E_WARN, L_SCANNER, _("Scanning %s finished (%llu files)!\n"), dir, fileno); - } -} - -void -start_scanner() -{ - struct media_dir_s * media_path = media_dirs; - - if (setpriority(PRIO_PROCESS, 0, 15) == -1) - DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce scanner thread priority\n"); - -#ifdef READYNAS - FILE * flag = fopen("/ramfs/.upnp-av_scan", "w"); - if( flag ) - fclose(flag); -#endif - freopen("/dev/null", "a", stderr); - while( media_path ) - { - ScanDirectory(media_path->path, NULL, media_path->type); - media_path = media_path->next; - } - freopen("/proc/self/fd/2", "a", stderr); -#ifdef READYNAS - if( access("/ramfs/.rescan_done", F_OK) == 0 ) - system("/bin/sh /ramfs/.rescan_done"); - unlink("/ramfs/.upnp-av_scan"); -#endif - /* Create this index after scanning, so it doesn't slow down the scanning process. - * This index is very useful for large libraries used with an XBox360 (or any - * client that uses UPnPSearch on large containers). */ - sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);"); - - fill_playlists(); - - //JM: Set up a db version number, so we know if we need to rebuild due to a new structure. - sql_exec(db, "pragma user_version = %d;", DB_VERSION); + chdir(".."); } @@ -3,84 +3,19 @@ * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - */ + * Copyright (c) 2008 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ #ifndef __SCANNER_H__ #define __SCANNER_H__ -/* Try to be generally PlaysForSure compatible by using similar IDs */ -#define BROWSEDIR_ID "64" - -#define MUSIC_ID "1" -#define MUSIC_ALL_ID "1$4" -#define MUSIC_GENRE_ID "1$5" -#define MUSIC_ARTIST_ID "1$6" -#define MUSIC_ALBUM_ID "1$7" -#define MUSIC_PLIST_ID "1$F" -#define MUSIC_DIR_ID "1$14" -#define MUSIC_CONTRIB_ARTIST_ID "1$100" -#define MUSIC_ALBUM_ARTIST_ID "1$107" -#define MUSIC_COMPOSER_ID "1$108" -#define MUSIC_RATING_ID "1$101" - -#define VIDEO_ID "2" -#define VIDEO_ALL_ID "2$8" -#define VIDEO_GENRE_ID "2$9" -#define VIDEO_ACTOR_ID "2$A" -#define VIDEO_SERIES_ID "2$E" -#define VIDEO_PLIST_ID "2$10" -#define VIDEO_DIR_ID "2$15" -#define VIDEO_RATING_ID "2$200" - -#define IMAGE_ID "3" -#define IMAGE_ALL_ID "3$B" -#define IMAGE_DATE_ID "3$C" -#define IMAGE_ALBUM_ID "3$D" -#define IMAGE_CAMERA_ID "3$D2" // PlaysForSure == Keyword -#define IMAGE_PLIST_ID "3$11" -#define IMAGE_DIR_ID "3$16" -#define IMAGE_RATING_ID "3$300" - -extern int valid_cache; - -int -is_video(const char * file); - -int -is_audio(const char * file); - -int -is_image(const char * file); - -sqlite_int64 -get_next_available_id(const char * table, const char * parentID); - -int -insert_directory(const char * name, const char * path, const char * base, const char * parentID, int objectID); - -int -insert_file(char * name, const char * path, const char * parentID, int object); - -int -CreateDatabase(void); +#define BROWSEDIR_ID "64" +#define MUSIC_DIR_ID "1$20" +#define VIDEO_DIR_ID "2$21" +#define IMAGE_DIR_ID "3$22" void -start_scanner(); +ScanDirectory(const char * dir, const char * parent); #endif @@ -1,200 +1,53 @@ -/* MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard +/* MiniDLNA media server + * Copyright (C) 2008 Justin Maggard * - * This file is part of MiniDLNA. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdio.h> -#include <string.h> -#include <unistd.h> - #include "sql.h" -#include "log.h" int -sql_exec(sqlite3 *db, const char *fmt, ...) +sql_exec(sqlite3 * db, const char * sql) { int ret; char *errMsg = NULL; - char *sql; - va_list ap; - //DPRINTF(E_DEBUG, L_DB_SQL, "SQL: %s\n", sql); - - va_start(ap, fmt); + //DEBUG printf("SQL: %s\n", sql); - sql = sqlite3_vmprintf(fmt, ap); + //ret = sqlite3_exec(db, sql, 0, 0, &errMsg); ret = sqlite3_exec(db, sql, 0, 0, &errMsg); if( ret != SQLITE_OK ) { - DPRINTF(E_ERROR, L_DB_SQL, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); + fprintf(stderr, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); if (errMsg) sqlite3_free(errMsg); } - sqlite3_free(sql); - return ret; } int -sql_get_table(sqlite3 *db, const char *sql, char ***pazResult, int *pnRow, int *pnColumn) +sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg) { + //DEBUG printf("SQL: %s\n", zSql); int ret; - char *errMsg = NULL; - //DPRINTF(E_DEBUG, L_DB_SQL, "SQL: %s\n", sql); - - ret = sqlite3_get_table(db, sql, pazResult, pnRow, pnColumn, &errMsg); + ret = sqlite3_get_table(db, zSql, pazResult, pnRow, pnColumn, pzErrmsg); if( ret != SQLITE_OK ) { - DPRINTF(E_ERROR, L_DB_SQL, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); - if (errMsg) - sqlite3_free(errMsg); + fprintf(stderr, "SQL ERROR [%s]\n%s\n", *pzErrmsg, zSql); + if (*pzErrmsg) + sqlite3_free(*pzErrmsg); } - - return ret; -} - -int -sql_get_int_field(sqlite3 *db, const char *fmt, ...) -{ - va_list ap; - int counter, result; - char *sql; - int ret; - sqlite3_stmt *stmt; - - va_start(ap, fmt); - - sql = sqlite3_vmprintf(fmt, ap); - - //DPRINTF(E_DEBUG, L_DB_SQL, "sql: %s\n", sql); - - switch (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) - { - case SQLITE_OK: - break; - default: - DPRINTF(E_ERROR, L_DB_SQL, "prepare failed: %s\n%s\n", sqlite3_errmsg(db), sql); - sqlite3_free(sql); - return -1; - } - sqlite3_free(sql); - - for (counter = 0; - ((result = sqlite3_step(stmt)) == SQLITE_BUSY || result == SQLITE_LOCKED) && counter < 2; - counter++) { - /* While SQLITE_BUSY has a built in timeout, - SQLITE_LOCKED does not, so sleep */ - if (result == SQLITE_LOCKED) - sleep(1); - } - - switch (result) - { - case SQLITE_DONE: - /* no rows returned */ - ret = 0; - break; - case SQLITE_ROW: - if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) - { - ret = 0; - break; - } - ret = sqlite3_column_int(stmt, 0); - break; - default: - DPRINTF(E_WARN, L_DB_SQL, "%s: step failed: %s\n", __func__, sqlite3_errmsg(db)); - ret = -1; - break; - } - - sqlite3_finalize(stmt); return ret; } -char * -sql_get_text_field(void *db, const char *fmt, ...) -{ - va_list ap; - int counter, result, len; - char *sql; - char *str; - sqlite3_stmt *stmt; - - va_start(ap, fmt); - - if (db == NULL) - { - DPRINTF(E_WARN, L_DB_SQL, "db is NULL\n"); - return NULL; - } - - sql = sqlite3_vmprintf(fmt, ap); - - //DPRINTF(E_DEBUG, L_DB_SQL, "sql: %s\n", sql); - - switch (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) - { - case SQLITE_OK: - break; - default: - DPRINTF(E_ERROR, L_DB_SQL, "prepare failed: %s\n%s\n", sqlite3_errmsg(db), sql); - sqlite3_free(sql); - return NULL; - } - sqlite3_free(sql); - - for (counter = 0; - ((result = sqlite3_step(stmt)) == SQLITE_BUSY || result == SQLITE_LOCKED) && counter < 2; - counter++) - { - /* While SQLITE_BUSY has a built in timeout, - * SQLITE_LOCKED does not, so sleep */ - if (result == SQLITE_LOCKED) - sleep(1); - } - - switch (result) - { - case SQLITE_DONE: - /* no rows returned */ - str = NULL; - break; - - case SQLITE_ROW: - if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) - { - str = NULL; - break; - } - - len = sqlite3_column_bytes(stmt, 0); - if ((str = sqlite3_malloc(len + 1)) == NULL) - { - DPRINTF(E_ERROR, L_DB_SQL, "malloc failed\n"); - break; - } - - strncpy(str, (char *)sqlite3_column_text(stmt, 0), len + 1); - break; - - default: - DPRINTF(E_WARN, L_DB_SQL, "SQL step failed: %s\n", sqlite3_errmsg(db)); - str = NULL; - break; - } - - sqlite3_finalize(stmt); - return str; -} @@ -3,39 +3,19 @@ * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - */ + * Copyright (c) 2008 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ #ifndef __SQL_H__ #define __SQL_H__ #include <sqlite3.h> int -sql_exec(sqlite3 *db, const char *fmt, ...); - -int -sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn); +sql_exec(sqlite3 * db, const char * sql); int -sql_get_int_field(sqlite3 *db, const char *fmt, ...); - -char * -sql_get_text_field(void *dbh, const char *fmt, ...); +sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg); #endif diff --git a/testupnpdescgen.c b/testupnpdescgen.c index 398d731..d072d7a 100644 --- a/testupnpdescgen.c +++ b/testupnpdescgen.c @@ -1,32 +1,10 @@ /* $Id$ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006-2008, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -35,15 +13,12 @@ #include "upnpdescgen.h" char uuidvalue[] = "uuid:12345678-0000-0000-0000-00000000abcd"; -char friendly_name[] = "localhost: system_type"; char serialnumber[] = "12345678"; -char modelname[] = "MiniDLNA"; char modelnumber[] = "1"; char presentationurl[] = "http://192.168.0.1:8080/"; -unsigned int updateID = 0; -#if PNPX -char pnpx_hwid[] = "VEN_01F2&DEV_0101&REV_01 VEN_0033&DEV_0001&REV_01"; -#endif + +char * use_ext_ip_addr = NULL; +const char * ext_if_name = "eth0"; int getifaddr(const char * ifname, char * buf, int len) { @@ -127,25 +102,17 @@ main(int argc, char * * argv) rootDesc = genRootDesc(&rootDescLen); xml_pretty_print(rootDesc, rootDescLen, stdout); free(rootDesc); - printf("\n----------------\n"); - printf("ContentDirectory\n"); - printf("----------------\n"); + printf("\n-------------\n"); s = genContentDirectory(&l); xml_pretty_print(s, l, stdout); free(s); - printf("\n----------------\n"); - printf("ConnectionManager\n"); - printf("----------------\n"); + printf("\n-------------\n"); s = genConnectionManager(&l); xml_pretty_print(s, l, stdout); free(s); - printf("\n----------------\n"); - printf("X_MS_MRR\n"); - printf("----------------\n"); - s = genX_MS_MediaReceiverRegistrar(&l); - xml_pretty_print(s, l, stdout); - free(s); printf("\n-------------\n"); +#ifdef ENABLE_EVENTS +#endif /* stupid_test(); */ diff --git a/upnpdescgen.c b/upnpdescgen.c index 97eaabd..b937b78 100644 --- a/upnpdescgen.c +++ b/upnpdescgen.c @@ -1,45 +1,25 @@ -/* $Id: upnpdescgen.c,v 1.18 2011/05/02 23:50:52 jmaggard Exp $ */ +/* $Id$ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006-2008, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <syslog.h> + #include "config.h" +#ifdef ENABLE_EVENTS #include "getifaddr.h" +#endif #include "upnpdescgen.h" -#include "minidlnapath.h" +#include "miniupnpdpath.h" #include "upnpglobalvars.h" #include "upnpdescstrings.h" -#undef DESC_DEBUG - static const char * const upnptypes[] = { "string", @@ -104,11 +84,29 @@ static const char * const upnpallowedvalues[] = "IN_PROGRESS", "STOPPED", 0, - RESOURCE_PROTOCOL_INFO_VALUES, /* 44 */ - 0, - "0", /* 46 */ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," /* 44 */ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," + "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," + "http-get:*:audio/x-ms-wma:*," + "http-get:*:audio/wav:*," + "http-get:*:audio/mp4:*," + "http-get:*:audio/x-aiff:*," + "http-get:*:audio/x-flac:*," + "http-get:*:application/ogg:*," + "http-get:*:image/jpeg:*," + "http-get:*:image/gif:*," + "http-get:*:audio/x-mpegurl:*," + "http-get:*:video/mpeg:*," + "http-get:*:video/x-msvideo:*," + "http-get:*:video/avi:*," + "http-get:*:video/mpeg2:*," + "http-get:*:video/dvd:*," + "http-get:*:video/x-ms-wmv:*", 0, - "", /* 48 */ + "", /* 46 */ 0 }; @@ -117,69 +115,35 @@ static const char xmlver[] = static const char root_service[] = "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\""; static const char root_device[] = - "root xmlns=\"urn:schemas-upnp-org:device-1-0\"" -#if PNPX - " xmlns:pnpx=\"http://schemas.microsoft.com/windows/pnpx/2005/11\"" - " xmlns:df=\"http://schemas.microsoft.com/windows/2008/09/devicefoundation\"" -#endif - ; + "root xmlns=\"urn:schemas-upnp-org:device-1-0\""; -/* root Description of the UPnP Device */ +/* root Description of the UPnP Device + * fixed to match UPnP_IGD_InternetGatewayDevice 1.0.pdf + * presentationURL is only "recommended" but the router doesn't appears + * in "Network connections" in Windows XP if it is not present. */ static const struct XMLElt rootDesc[] = { {root_device, INITHELPER(1,2)}, {"specVersion", INITHELPER(3,2)}, - {"device", INITHELPER(5,(14+PNPX))}, + {"device", INITHELPER(5,13)}, {"/major", "1"}, {"/minor", "0"}, {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, -#if PNPX == 5 - {"/pnpx:X_hardwareId", pnpx_hwid}, - {"/pnpx:X_compatibleId", "MS_DigitalMediaDeviceClass_DMS_V001"}, - {"/pnpx:X_deviceCategory", "MediaDevices"}, - {"/df:X_deviceCategory", "Multimedia.DMS"}, - {"/microsoft:magicPacketWakeSupported xmlns:microsoft=\"urn:schemas-microsoft-com:WMPNSS-1-0\"", "0"}, -#endif - {"/friendlyName", friendly_name}, /* required */ + {"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ - {"/modelName", modelname}, /* required */ + {"/modelName", ROOTDEV_MODELNAME}, /* required */ {"/modelNumber", modelnumber}, {"/modelURL", ROOTDEV_MODELURL}, {"/serialNumber", serialnumber}, {"/UDN", uuidvalue}, /* required */ {"/dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"", "DMS-1.50"}, {"/presentationURL", presentationurl}, /* recommended */ - {"iconList", INITHELPER((19+PNPX),4)}, - {"serviceList", INITHELPER((43+PNPX),3)}, - {"icon", INITHELPER((23+PNPX),5)}, - {"icon", INITHELPER((28+PNPX),5)}, - {"icon", INITHELPER((33+PNPX),5)}, - {"icon", INITHELPER((38+PNPX),5)}, - {"/mimetype", "image/png"}, - {"/width", "48"}, - {"/height", "48"}, - {"/depth", "24"}, - {"/url", "/icons/sm.png"}, - {"/mimetype", "image/png"}, - {"/width", "120"}, - {"/height", "120"}, - {"/depth", "24"}, - {"/url", "/icons/lrg.png"}, - {"/mimetype", "image/jpeg"}, - {"/width", "48"}, - {"/height", "48"}, - {"/depth", "24"}, - {"/url", "/icons/sm.jpg"}, - {"/mimetype", "image/jpeg"}, - {"/width", "120"}, - {"/height", "120"}, - {"/depth", "24"}, - {"/url", "/icons/lrg.jpg"}, - {"service", INITHELPER((46+PNPX),5)}, - {"service", INITHELPER((51+PNPX),5)}, - {"service", INITHELPER((56+PNPX),5)}, + {"serviceList", INITHELPER(18,3)}, + {"service", INITHELPER(21,5)}, + {"service", INITHELPER(26,5)}, + {"service", INITHELPER(31,5)}, {"/serviceType", "urn:schemas-upnp-org:service:ContentDirectory:1"}, {"/serviceId", "urn:upnp-org:serviceId:ContentDirectory"}, {"/controlURL", CONTENTDIRECTORY_CONTROLURL}, @@ -211,6 +175,12 @@ static const struct argument AddPortMappingArgs[] = {NULL, 0, 0} }; +static const struct argument GetExternalIPAddressArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + static const struct argument DeletePortMappingArgs[] = { {NULL, 1, 11}, @@ -232,6 +202,14 @@ static const struct argument GetConnectionTypeInfoArgs[] = {NULL, 0, 0} }; +static const struct argument GetStatusInfoArgs[] = +{ + {NULL, 2, 2}, + {NULL, 2, 4}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + static const struct argument GetNATRSIPStatusArgs[] = { {NULL, 2, 5}, @@ -323,9 +301,9 @@ static const struct action ConnectionManagerActions[] = static const struct stateVar ConnectionManagerVars[] = { - {"SourceProtocolInfo", 1<<7, 0, 0, 44}, /* required */ - {"SinkProtocolInfo", 1<<7, 0, 0, 48}, /* required */ - {"CurrentConnectionIDs", 1<<7, 0, 0, 46}, /* required */ + {"SourceProtocolInfo", 1<<7, 0, 44, 44}, /* required */ + {"SinkProtocolInfo", 1<<7, 0}, /* required */ + {"CurrentConnectionIDs", 1<<7, 0}, /* required */ {"A_ARG_TYPE_ConnectionStatus", 0, 0, 27}, /* required */ {"A_ARG_TYPE_ConnectionManager", 0, 0}, /* required */ {"A_ARG_TYPE_Direction", 0, 0, 33}, /* required */ @@ -338,19 +316,19 @@ static const struct stateVar ConnectionManagerVars[] = static const struct argument GetSearchCapabilitiesArgs[] = { - {"SearchCaps", 2, 10}, + {"SearchCaps", 2, 16}, {0, 0} }; static const struct argument GetSortCapabilitiesArgs[] = { - {"SortCaps", 2, 11}, + {"SortCaps", 2, 17}, {0, 0} }; static const struct argument GetSystemUpdateIDArgs[] = { - {"Id", 2, 12}, + {"Id", 2, 18}, {0, 0} }; @@ -407,7 +385,7 @@ static const struct action ContentDirectoryActions[] = static const struct stateVar ContentDirectoryVars[] = { - {"TransferIDs", 1<<7, 0, 0, 48}, /* 0 */ + {"TransferIDs", 1<<7, 0, 46, 46}, /* 0 */ {"A_ARG_TYPE_ObjectID", 0, 0}, {"A_ARG_TYPE_Result", 0, 0}, {"A_ARG_TYPE_SearchCriteria", 0, 0}, @@ -418,6 +396,12 @@ static const struct stateVar ContentDirectoryVars[] = {"A_ARG_TYPE_Index", 3, 0}, {"A_ARG_TYPE_Count", 3, 0}, {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, //JM{"A_ARG_TYPE_TransferID", 3, 0}, /* 10 */ //JM{"A_ARG_TYPE_TransferStatus", 0, 0, 39}, /* Allowed Values : COMPLETED / ERROR / IN_PROGRESS / STOPPED */ @@ -427,7 +411,7 @@ static const struct stateVar ContentDirectoryVars[] = //JM{"A_ARG_TYPE_URI", 5, 0}, /* 15 */ {"SearchCapabilities", 0, 0}, {"SortCapabilities", 0, 0}, - {"SystemUpdateID", 3|0x80, 0, 0, 255}, + {"SystemUpdateID", 3|0x80, 0, 46, 46}, //{"ContainerUpdateIDs", 0, 0}, {0, 0} }; @@ -457,9 +441,7 @@ static const struct action X_MS_MediaReceiverRegistrarActions[] = { {"IsAuthorized", GetIsAuthorizedArgs}, /* R */ {"IsValidated", GetIsValidatedArgs}, /* R */ -#if 0 // Not needed? WMP12 still works. Need to check with 360 and WMP11. {"RegisterDevice", GetRegisterDeviceArgs}, /* R */ -#endif {0, 0} }; @@ -469,10 +451,10 @@ static const struct stateVar X_MS_MediaReceiverRegistrarVars[] = {"A_ARG_TYPE_RegistrationReqMsg", 7, 0}, {"A_ARG_TYPE_RegistrationRespMsg", 7, 0}, {"A_ARG_TYPE_Result", 6, 0}, - {"AuthorizationDeniedUpdateID", 3, 0}, - {"AuthorizationGrantedUpdateID", 3, 0}, - {"ValidationRevokedUpdateID", 3, 0}, - {"ValidationSucceededUpdateID", 3, 0}, + {"AuthorizationDeniedUpdateID", (1<<7)|3, 0}, + {"AuthorizationGrantedUpdateID", (1<<7)|3, 0}, + {"ValidationRevokedUpdateID", (1<<7)|3, 0}, + {"ValidationSucceededUpdateID", (1<<7)|3, 0}, {0, 0} }; @@ -512,8 +494,74 @@ static const struct argument GetTotalPacketsReceivedArgs[] = {NULL, 0, 0} }; +static const struct action WANCfgActions[] = +{ + {"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */ + {"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */ + {"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */ + {"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */ + {"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */ + {0, 0} +}; + +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ +static const struct stateVar WANCfgVars[] = +{ + {"WANAccessType", 0, 0, 1}, + /* Allowed Values : DSL / POTS / Cable / Ethernet + * Default value : empty string */ + {"Layer1UpstreamMaxBitRate", 3, 0}, + {"Layer1DownstreamMaxBitRate", 3, 0}, + {"PhysicalLinkStatus", 0|0x80, 0, 6, 6}, + /* allowed values : + * Up / Down / Initializing (optional) / Unavailable (optionnal) + * no Default value + * Evented */ + {"TotalBytesSent", 3, 0}, /* Optional */ + {"TotalBytesReceived", 3, 0}, /* Optional */ + {"TotalPacketsSent", 3, 0}, /* Optional */ + {"TotalPacketsReceived", 3, 0},/* Optional */ + /*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */ + {0, 0} +}; + +static const struct serviceDesc scpdWANCfg = +{ WANCfgActions, WANCfgVars }; + +#ifdef ENABLE_L3F_SERVICE +/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */ +static const struct argument SetDefaultConnectionServiceArgs[] = +{ + {NULL, 1, 0}, /* in */ + {NULL, 0, 0} +}; + +static const struct argument GetDefaultConnectionServiceArgs[] = +{ + {NULL, 2, 0}, /* out */ + {0, 0} +}; + +static const struct action L3FActions[] = +{ + {"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */ + {"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */ + {0, 0} +}; + +static const struct stateVar L3FVars[] = +{ + {"DefaultConnectionService", 0|0x80, 0, 0, 255}, /* Required */ + {0, 0} +}; + +static const struct serviceDesc scpdL3F = +{ L3FActions, L3FVars }; +#endif + static const struct serviceDesc scpdContentDirectory = { ContentDirectoryActions, ContentDirectoryVars }; +//{ ContentDirectoryActions, ContentDirectoryVars }; static const struct serviceDesc scpdConnectionManager = { ConnectionManagerActions, ConnectionManagerVars }; @@ -527,7 +575,6 @@ static const struct serviceDesc scpdX_MS_MediaReceiverRegistrar = static char * strcat_str(char * str, int * len, int * tmplen, const char * s2) { - char *p; int s2len; s2len = (int)strlen(s2); if(*tmplen <= (*len + s2len)) @@ -536,17 +583,7 @@ strcat_str(char * str, int * len, int * tmplen, const char * s2) *tmplen += 256; else *tmplen += s2len + 1; - p = realloc(str, *tmplen); - if (!p) - { - if(s2len < 256) - *tmplen -= 256; - else - *tmplen -= s2len + 1; - return str; - } - else - str = p; + str = (char *)realloc(str, *tmplen); } /*strcpy(str + *len, s2); */ memcpy(str + *len, s2, s2len + 1); @@ -560,18 +597,10 @@ strcat_str(char * str, int * len, int * tmplen, const char * s2) static char * strcat_char(char * str, int * len, int * tmplen, char c) { - char *p; if(*tmplen <= (*len + 1)) { *tmplen += 256; - p = (char *)realloc(str, *tmplen); - if (!p) - { - *tmplen -= 256; - return str; - } - else - str = p; + str = (char *)realloc(str, *tmplen); } str[*len] = c; (*len)++; @@ -584,7 +613,7 @@ static char * genXML(char * str, int * len, int * tmplen, const struct XMLElt * p) { - u_int16_t i, j, k; + unsigned short i, j, k; int top; const char * eltname, *s; char c; @@ -604,9 +633,7 @@ genXML(char * str, int * len, int * tmplen, return str; if(eltname[0] == '/') { - #ifdef DESC_DEBUG - printf("DBG: <%s>%s<%s>\n", eltname+1, p[i].data, eltname); - #endif + /*printf("<%s>%s<%s>\n", eltname+1, p[i].data, eltname); */ str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname+1); str = strcat_char(str, len, tmplen, '>'); @@ -621,14 +648,10 @@ genXML(char * str, int * len, int * tmplen, return str; i = ++(pile[top].i); j = pile[top].j; - #ifdef DESC_DEBUG - printf("DBG: pile[%d]\t%d %d\n", top, i, j); - #endif + /*printf(" pile[%d]\t%d %d\n", top, i, j); */ if(i==j) { - #ifdef DESC_DEBUG - printf("DBG: i==j, </%s>\n", pile[top].eltname); - #endif + /*printf("</%s>\n", pile[top].eltname); */ str = strcat_char(str, len, tmplen, '<'); str = strcat_char(str, len, tmplen, '/'); s = pile[top].eltname; @@ -643,21 +666,17 @@ genXML(char * str, int * len, int * tmplen, } else { - #ifdef DESC_DEBUG - printf("DBG: [%d] <%s>\n", i, eltname); - #endif + /*printf("<%s>\n", eltname); */ str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname); str = strcat_char(str, len, tmplen, '>'); k = i; /*i = p[k].index; */ /*j = i + p[k].nchild; */ - i = (unsigned long)p[k].data & 0xffff; - j = i + ((unsigned long)p[k].data >> 16); + i = (unsigned)p[k].data & 0xffff; + j = i + ((unsigned)p[k].data >> 16); top++; - #ifdef DESC_DEBUG - printf("DBG: +pile[%d]\t%d %d\n", top, i, j); - #endif + /*printf(" +pile[%d]\t%d %d\n", top, i, j); */ pile[top].i = i; pile[top].j = j; pile[top].eltname = eltname; @@ -679,12 +698,23 @@ genRootDesc(int * len) str = (char *)malloc(tmplen); if(str == NULL) return NULL; +#if 1 * len = strlen(xmlver); /*strcpy(str, xmlver); */ memcpy(str, xmlver, *len + 1); str = genXML(str, len, &tmplen, rootDesc); str[*len] = '\0'; return str; +#else +char *ret = calloc(1, 8192); +sprintf(ret, "<?xml version='1.0' encoding='UTF-8' ?>\r\n" +"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n" +//"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNACAP>av-upload,image-upload,create-child-container,audio-upload</dlna:X_DLNACAP><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n" +"<serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service></serviceList></device></root>"); +//"</iconList><serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service><service><serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType><serviceId>763f907c-8cfb-11dd-a382-c9c0ad9eae41</serviceId><SCPDURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</SCPDURL><controlURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/control/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</controlURL><eventSubURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/events/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</eventSubURL></service></serviceList></device></root>\r\n"); + * len = strlen(ret); +return ret; +#endif } /* genServiceDesc() : @@ -733,9 +763,18 @@ genServiceDesc(int * len, const struct serviceDesc * s) j = 0; while(args[j].dir) { + //JM str = strcat_str(str, len, &tmplen, "<argument><name>New"); str = strcat_str(str, len, &tmplen, "<argument><name>"); p = vars[args[j].relatedVar].name; - str = strcat_str(str, len, &tmplen, (args[j].name ? args[j].name : p)); + if(0 == memcmp(p, "PortMapping", 11) + && 0 != memcmp(p + 11, "Description", 11)) { + if(0 == memcmp(p + 11, "NumberOfEntries", 15)) + str = strcat_str(str, len, &tmplen, "PortMappingIndex"); + else + str = strcat_str(str, len, &tmplen, p + 11); + } else { + str = strcat_str(str, len, &tmplen, (args[j].name ? args[j].name : p)); + } str = strcat_str(str, len, &tmplen, "</name><direction>"); str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out"); str = strcat_str(str, len, &tmplen, @@ -757,7 +796,13 @@ genServiceDesc(int * len, const struct serviceDesc * s) { str = strcat_str(str, len, &tmplen, "<stateVariable sendEvents=\""); +#ifdef ENABLE_EVENTS str = strcat_str(str, len, &tmplen, (vars[i].itype & 0x80)?"yes":"no"); +#else + /* for the moment allways send no. Wait for SUBSCRIBE implementation + * before setting it to yes */ + str = strcat_str(str, len, &tmplen, "no"); +#endif str = strcat_str(str, len, &tmplen, "\"><name>"); str = strcat_str(str, len, &tmplen, vars[i].name); str = strcat_str(str, len, &tmplen, "</name><dataType>"); @@ -815,42 +860,40 @@ genX_MS_MediaReceiverRegistrar(int * len) return genServiceDesc(len, &scpdX_MS_MediaReceiverRegistrar); } +#ifdef ENABLE_EVENTS static char * genEventVars(int * len, const struct serviceDesc * s, const char * servns) { const struct stateVar * v; char * str; int tmplen; - char buf[512]; tmplen = 512; str = (char *)malloc(tmplen); if(str == NULL) return NULL; *len = 0; v = s->serviceStateTable; - snprintf(buf, sizeof(buf), "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\" xmlns:s=\"%s\">", servns); - str = strcat_str(str, len, &tmplen, buf); + str = strcat_str(str, len, &tmplen, "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\" xmlns:s=\""); + str = strcat_str(str, len, &tmplen, servns); + str = strcat_str(str, len, &tmplen, "\">"); while(v->name) { if(v->itype & 0x80) { - snprintf(buf, sizeof(buf), "<e:property><%s>", v->name); - str = strcat_str(str, len, &tmplen, buf); + str = strcat_str(str, len, &tmplen, "<e:property><"); + str = strcat_str(str, len, &tmplen, v->name); + str = strcat_str(str, len, &tmplen, ">"); //printf("<e:property><s:%s>", v->name); switch(v->ieventvalue) { case 0: break; case 255: /* Magical values should go around here */ - if( strcmp(v->name, "SystemUpdateID") == 0 ) - { - snprintf(buf, sizeof(buf), "%d", updateID); - str = strcat_str(str, len, &tmplen, buf); - } break; default: str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]); //printf("%s", upnpallowedvalues[v->ieventvalue]); } - snprintf(buf, sizeof(buf), "</%s></e:property>", v->name); - str = strcat_str(str, len, &tmplen, buf); + str = strcat_str(str, len, &tmplen, "</"); + str = strcat_str(str, len, &tmplen, v->name); + str = strcat_str(str, len, &tmplen, "></e:property>"); //printf("</s:%s></e:property>\n", v->name); } v++; @@ -887,3 +930,4 @@ getVarsX_MS_MediaReceiverRegistrar(int * l) "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"); } +#endif diff --git a/upnpdescgen.c.dlna b/upnpdescgen.c.dlna new file mode 100644 index 0000000..c17b3c9 --- /dev/null +++ b/upnpdescgen.c.dlna @@ -0,0 +1,944 @@ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <syslog.h> + +#include "config.h" +#ifdef ENABLE_EVENTS +#include "getifaddr.h" +#include "upnpredirect.h" +#endif +#include "upnpdescgen.h" +#include "miniupnpdpath.h" +#include "upnpglobalvars.h" +#include "upnpdescstrings.h" + +static const char * const upnptypes[] = +{ + "string", + "boolean", + "ui2", + "ui4", + "i4", + "uri" +}; + +static const char * const upnpdefaultvalues[] = +{ + 0, + "Unconfigured" +}; + +static const char * const upnpallowedvalues[] = +{ + 0, /* 0 */ + "DSL", /* 1 */ + "POTS", + "Cable", + "Ethernet", + 0, + "Up", /* 6 */ + "Down", + "Initializing", + "Unavailable", + 0, + "TCP", /* 11 */ + "UDP", + 0, + "Unconfigured", /* 14 */ + "IP_Routed", + "IP_Bridged", + 0, + "Unconfigured", /* 18 */ + "Connecting", + "Connected", + "PendingDisconnect", + "Disconnecting", + "Disconnected", + 0, + "ERROR_NONE", /* 25 */ + 0, + "OK", /* 27 */ + "ContentFormatMismatch", + "InsufficientBandwidth", + "UnreliableChannel", + "Unknown", + 0, + "Input", /* 33 */ + "Output", + 0, + "BrowseMetadata", /* 36 */ + "BrowseDirectChildren", + 0, + "COMPLETED", /* 39 */ + "ERROR", + "IN_PROGRESS", + "STOPPED", + 0, + "", /* 44 */ + 0 +}; + +static const char xmlver[] = + "<?xml version=\"1.0\"?>\r\n"; +static const char root_service[] = + "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\""; +static const char root_device[] = + "root xmlns=\"urn:schemas-upnp-org:device-1-0\""; + +/* root Description of the UPnP Device + * fixed to match UPnP_IGD_InternetGatewayDevice 1.0.pdf + * presentationURL is only "recommended" but the router doesn't appears + * in "Network connections" in Windows XP if it is not present. */ +static const struct XMLElt rootDesc[] = +{ + {root_device, INITHELPER(1,3)}, + {"specVersion", INITHELPER(4,2)}, + {"/URLBase", "http://192.168.10.112:5555/"}, + {"device", INITHELPER(6,12)}, + {"/major", "1"}, + {"/minor", "0"}, + {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, + {"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ + {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ + {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ + {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ + {"/modelName", ROOTDEV_MODELNAME}, /* required */ + {"/modelNumber", modelnumber}, + {"/modelURL", ROOTDEV_MODELURL}, + {"/serialNumber", serialnumber}, + {"/UDN", uuidvalue}, /* required */ + {"serviceList", INITHELPER(18,2)}, + {"/presentationURL", presentationurl}, /* recommended */ + {"service", INITHELPER(20,5)}, + {"service", INITHELPER(25,5)}, + {"/serviceType", "urn:schemas-upnp-org:service:ContentDirectory:1"}, + {"/serviceId", "urn:upnp-org:serviceId:ContentDirectory"}, + {"/controlURL", CONTENTDIRECTORY_CONTROLURL}, + {"/eventSubURL", CONTENTDIRECTORY_EVENTURL}, + {"/SCPDURL", CONTENTDIRECTORY_PATH}, + {"/serviceType", "urn:schemas-upnp-org:service:ConnectionManager:1"}, + {"/serviceId", "urn:upnp-org:serviceId:ConnectionManager"}, + {"/controlURL", CONNECTIONMGR_CONTROLURL}, + {"/eventSubURL", CONNECTIONMGR_EVENTURL}, + {"/SCPDURL", CONNECTIONMGR_PATH}, + {0, 0} +}; + +/* WANIPCn.xml */ +/* see UPnP_IGD_WANIPConnection 1.0.pdf +static struct XMLElt scpdWANIPCn[] = +{ + {root_service, {INITHELPER(1,2)}}, + {0, {0}} +}; +*/ +static const struct argument AddPortMappingArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 1, 13}, + {NULL, 1, 15}, + {NULL, 1, 9}, + {NULL, 1, 16}, + {NULL, 1, 10}, + {NULL, 0, 0} +}; + +static const struct argument GetExternalIPAddressArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + +static const struct argument DeletePortMappingArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 0, 0} +}; + +static const struct argument SetConnectionTypeArgs[] = +{ + {NULL, 1, 0}, + {NULL, 0, 0} +}; + +static const struct argument GetConnectionTypeInfoArgs[] = +{ + {NULL, 2, 0}, + {NULL, 2, 1}, + {NULL, 0, 0} +}; + +static const struct argument GetStatusInfoArgs[] = +{ + {NULL, 2, 2}, + {NULL, 2, 4}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetNATRSIPStatusArgs[] = +{ + {NULL, 2, 5}, + {NULL, 2, 6}, + {NULL, 0, 0} +}; + +static const struct argument GetGenericPortMappingEntryArgs[] = +{ + {NULL, 1, 8}, + {NULL, 2, 11}, + {NULL, 2, 12}, + {NULL, 2, 14}, + {NULL, 2, 13}, + {NULL, 2, 15}, + {NULL, 2, 9}, + {NULL, 2, 16}, + {NULL, 2, 10}, + {NULL, 0, 0} +}; + +static const struct argument GetSpecificPortMappingEntryArgs[] = +{ + {NULL, 1, 11}, + {NULL, 1, 12}, + {NULL, 1, 14}, + {NULL, 2, 13}, + {NULL, 2, 15}, + {NULL, 2, 9}, + {NULL, 2, 16}, + {NULL, 2, 10}, + {NULL, 0, 0} +}; + +/* For ConnectionManager */ +static const struct argument GetProtocolInfoArgs[] = +{ + {"Source", 2, 0}, + {"Sink", 2, 1}, + {NULL, 0, 0} +}; + +static const struct argument PrepareForConnectionArgs[] = +{ + {"RemoteProtocolInfo", 1, 6}, + {"PeerConnectionManager", 1, 4}, + {"PeerConnectionID", 1, 7}, + {"Direction", 1, 5}, + {"ConnectionID", 2, 7}, + {"AVTransportID", 2, 8}, + {"RcsID", 2, 9}, + {NULL, 0, 0} +}; + +static const struct argument ConnectionCompleteArgs[] = +{ + {"ConnectionID", 1, 7}, + {NULL, 0, 0} +}; + +static const struct argument GetCurrentConnectionIDsArgs[] = +{ + {"ConnectionIDs", 2, 2}, + {NULL, 0, 0} +}; + +static const struct argument GetCurrentConnectionInfoArgs[] = +{ + {"ConnectionID", 1, 7}, + {"RcsID", 2, 9}, + {"AVTransportID", 2, 8}, + {"ProtocolInfo", 2, 6}, + {"PeerConnectionManager", 2, 4}, + {"PeerConnectionID", 2, 7}, + {"Direction", 2, 5}, + {"Status", 2, 3}, + {NULL, 0, 0} +}; + +static const struct action ConnectionManagerActions[] = +{ + {"GetProtocolInfo", GetProtocolInfoArgs}, /* R */ + {"PrepareForConnection", PrepareForConnectionArgs}, /* R */ + {"ConnectionComplete", ConnectionCompleteArgs}, /* R */ + {"GetCurrentConnectionIDs", GetCurrentConnectionIDsArgs}, /* R */ + {"GetCurrentConnectionInfo", GetCurrentConnectionInfoArgs}, /* R */ + {0, 0} +}; + +static const struct stateVar ConnectionManagerVars[] = +{ + {"SourceProtocolInfo", 1<<7, 0}, /* required */ + {"SinkProtocolInfo", 1<<7, 0}, /* required */ + {"CurrentConnectionIDs", 1<<7, 0}, /* required */ + {"A_ARG_TYPE_ConnectionStatus", 0, 0, 27}, /* required */ + {"A_ARG_TYPE_ConnectionManager", 0, 0}, /* required */ + {"A_ARG_TYPE_Direction", 0, 0, 33}, /* required */ + {"A_ARG_TYPE_ProtocolInfo", 0, 0}, /* required */ + {"A_ARG_TYPE_ConnectionID", 4, 0}, /* required */ + {"A_ARG_TYPE_AVTransportID", 4, 0}, /* required */ + {"A_ARG_TYPE_RcsID", 4, 0}, /* required */ + {0, 0} +}; + +static const struct argument GetSearchCapabilitiesArgs[] = +{ + {"SearchCaps", 2, 16}, + {0, 0} +}; + +static const struct argument GetSortCapabilitiesArgs[] = +{ + {"SortCaps", 2, 17}, + {0, 0} +}; + +static const struct argument GetSystemUpdateIDArgs[] = +{ + {"Id", 2, 18}, + {0, 0} +}; + +static const struct argument BrowseArgs[] = +{ + {"ObjectID", 1, 1}, + {"BrowseFlag", 1, 4}, + {"Filter", 1, 5}, + {"StartingIndex", 1, 7}, + {"RequestedCount", 1, 8}, + {"SortCriteria", 1, 6}, + {"Result", 2, 2}, + {"NumberReturned", 2, 8}, + {"TotalMatches", 2, 8}, + {"UpdateID", 2, 9}, + {0, 0} +}; + +static const struct action ContentDirectoryActions[] = +{ + {"GetSearchCapabilities", GetSearchCapabilitiesArgs}, /* R */ + {"GetSortCapabilities", GetSortCapabilitiesArgs}, /* R */ + {"GetSystemUpdateID", GetSystemUpdateIDArgs}, /* R */ + {"Browse", BrowseArgs}, /* R */ +#if 0 // Not implementing optional features yet... + {"Search", SearchArgs}, /* O */ + {"CreateObject", CreateObjectArgs}, /* O */ + {"DestroyObject", DestroyObjectArgs}, /* O */ + {"UpdateObject", UpdateObjectArgs}, /* O */ + {"ImportResource", ImportResourceArgs}, /* O */ + {"ExportResource", ExportResourceArgs}, /* O */ + {"StopTransferResource", StopTransferResourceArgs}, /* O */ + {"GetTransferProgress", GetTransferProgressArgs}, /* O */ + {"DeleteResource", DeleteResourceArgs}, /* O */ + {"CreateReference", CreateReferenceArgs}, /* O */ +#endif + {0, 0} +}; + +static const struct stateVar ContentDirectoryVars[] = +{ + {"TransferIDs", 1<<7, 0}, /* 0 */ + {"A_ARG_TYPE_ObjectID", 0, 0}, + {"A_ARG_TYPE_Result", 0, 0}, + {"A_ARG_TYPE_SearchCriteria", 0, 0}, + {"A_ARG_TYPE_BrowseFlag", 0, 0, 36}, + /* Allowed Values : BrowseMetadata / BrowseDirectChildren */ + {"A_ARG_TYPE_Filter", 0, 0}, /* 5 */ + {"A_ARG_TYPE_SortCriteria", 0, 0}, + {"A_ARG_TYPE_Index", 3, 0}, + {"A_ARG_TYPE_Count", 3, 0}, + {"A_ARG_TYPE_UpdateID", 3, 0}, + {"A_ARG_TYPE_TransferID", 3, 0}, /* 10 */ + {"A_ARG_TYPE_TransferStatus", 0, 0, 39}, + /* Allowed Values : COMPLETED / ERROR / IN_PROGRESS / STOPPED */ + {"A_ARG_TYPE_TransferLength", 0, 0}, + {"A_ARG_TYPE_TransferTotal", 0, 0}, + {"A_ARG_TYPE_TagValueList", 0, 0}, + {"A_ARG_TYPE_URI", 5, 0}, /* 15 */ + {"SearchCapabilities", 0, 0}, + {"SortCapabilities", 0, 0}, + {"SystemUpdateID", 3, 0}, + {"ContainerUpdateIDs", 0, 0}, + {0, 0} +}; + +static const struct action WANIPCnActions[] = +{ + {"AddPortMapping", AddPortMappingArgs}, /* R */ + {"GetExternalIPAddress", GetExternalIPAddressArgs}, /* R */ + {"DeletePortMapping", DeletePortMappingArgs}, /* R */ + {"SetConnectionType", SetConnectionTypeArgs}, /* R */ + {"GetConnectionTypeInfo", GetConnectionTypeInfoArgs}, /* R */ + {"RequestConnection", 0}, /* R */ + {"ForceTermination", 0}, /* R */ + {"GetStatusInfo", GetStatusInfoArgs}, /* R */ + {"GetNATRSIPStatus", GetNATRSIPStatusArgs}, /* R */ + {"GetGenericPortMappingEntry", GetGenericPortMappingEntryArgs}, /* R */ + {"GetSpecificPortMappingEntry", GetSpecificPortMappingEntryArgs}, /* R */ + {0, 0} +}; +/* R=Required, O=Optional */ + +static const struct stateVar WANIPCnVars[] = +{ +/* 0 */ + {"ConnectionType", 0, 0/*1*/}, /* required */ + {"PossibleConnectionTypes", 0|0x80, 0, 14, 15}, + /* Required + * Allowed values : Unconfigured / IP_Routed / IP_Bridged */ + {"ConnectionStatus", 0|0x80, 0/*1*/, 18, 20}, /* required */ + /* Allowed Values : Unconfigured / Connecting(opt) / Connected + * PendingDisconnect(opt) / Disconnecting (opt) + * Disconnected */ + {"Uptime", 3, 0}, /* Required */ + {"LastConnectionError", 0, 0, 25}, /* required : */ + /* Allowed Values : ERROR_NONE(req) / ERROR_COMMAND_ABORTED(opt) + * ERROR_NOT_ENABLED_FOR_INTERNET(opt) + * ERROR_USER_DISCONNECT(opt) + * ERROR_ISP_DISCONNECT(opt) + * ERROR_IDLE_DISCONNECT(opt) + * ERROR_FORCED_DISCONNECT(opt) + * ERROR_NO_CARRIER(opt) + * ERROR_IP_CONFIGURATION(opt) + * ERROR_UNKNOWN(opt) */ + {"RSIPAvailable", 1, 0}, /* required */ + {"NATEnabled", 1, 0}, /* required */ + {"ExternalIPAddress", 0|0x80, 0, 0, 254}, /* required. Default : empty string */ + {"PortMappingNumberOfEntries", 2|0x80, 0, 0, 253}, /* required >= 0 */ + {"PortMappingEnabled", 1, 0}, /* Required */ + {"PortMappingLeaseDuration", 3, 0}, /* required */ + {"RemoteHost", 0, 0}, /* required. Default : empty string */ + {"ExternalPort", 2, 0}, /* required */ + {"InternalPort", 2, 0}, /* required */ + {"PortMappingProtocol", 0, 0, 11}, /* required allowedValues: TCP/UDP */ + {"InternalClient", 0, 0}, /* required */ + {"PortMappingDescription", 0, 0}, /* required default: empty string */ + {0, 0} +}; + +static const struct serviceDesc scpdWANIPCn = +{ WANIPCnActions, WANIPCnVars }; + +/* WANCfg.xml */ +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ + +static const struct argument GetCommonLinkPropertiesArgs[] = +{ + {NULL, 2, 0}, + {NULL, 2, 1}, + {NULL, 2, 2}, + {NULL, 2, 3}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalBytesSentArgs[] = +{ + {NULL, 2, 4}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalBytesReceivedArgs[] = +{ + {NULL, 2, 5}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalPacketsSentArgs[] = +{ + {NULL, 2, 6}, + {NULL, 0, 0} +}; + +static const struct argument GetTotalPacketsReceivedArgs[] = +{ + {NULL, 2, 7}, + {NULL, 0, 0} +}; + +static const struct action WANCfgActions[] = +{ + {"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */ + {"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */ + {"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */ + {"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */ + {"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */ + {0, 0} +}; + +/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ +static const struct stateVar WANCfgVars[] = +{ + {"WANAccessType", 0, 0, 1}, + /* Allowed Values : DSL / POTS / Cable / Ethernet + * Default value : empty string */ + {"Layer1UpstreamMaxBitRate", 3, 0}, + {"Layer1DownstreamMaxBitRate", 3, 0}, + {"PhysicalLinkStatus", 0|0x80, 0, 6, 6}, + /* allowed values : + * Up / Down / Initializing (optional) / Unavailable (optionnal) + * no Default value + * Evented */ + {"TotalBytesSent", 3, 0}, /* Optional */ + {"TotalBytesReceived", 3, 0}, /* Optional */ + {"TotalPacketsSent", 3, 0}, /* Optional */ + {"TotalPacketsReceived", 3, 0},/* Optional */ + /*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */ + {0, 0} +}; + +static const struct serviceDesc scpdWANCfg = +{ WANCfgActions, WANCfgVars }; + +#ifdef ENABLE_L3F_SERVICE +/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */ +static const struct argument SetDefaultConnectionServiceArgs[] = +{ + {NULL, 1, 0}, /* in */ + {NULL, 0, 0} +}; + +static const struct argument GetDefaultConnectionServiceArgs[] = +{ + {NULL, 2, 0}, /* out */ + {0, 0} +}; + +static const struct action L3FActions[] = +{ + {"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */ + {"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */ + {0, 0} +}; + +static const struct stateVar L3FVars[] = +{ + {"DefaultConnectionService", 0|0x80, 0, 0, 255}, /* Required */ + {0, 0} +}; + +static const struct serviceDesc scpdL3F = +{ L3FActions, L3FVars }; +#endif + +static const struct serviceDesc scpdContentDirectory = +{ ContentDirectoryActions, ContentDirectoryVars }; +//{ ContentDirectoryActions, ContentDirectoryVars }; + +static const struct serviceDesc scpdConnectionManager = +{ ConnectionManagerActions, ConnectionManagerVars }; + +/* strcat_str() + * concatenate the string and use realloc to increase the + * memory buffer if needed. */ +static char * +strcat_str(char * str, int * len, int * tmplen, const char * s2) +{ + int s2len; + s2len = (int)strlen(s2); + if(*tmplen <= (*len + s2len)) + { + if(s2len < 256) + *tmplen += 256; + else + *tmplen += s2len + 1; + str = (char *)realloc(str, *tmplen); + } + /*strcpy(str + *len, s2); */ + memcpy(str + *len, s2, s2len + 1); + *len += s2len; + return str; +} + +/* strcat_char() : + * concatenate a character and use realloc to increase the + * size of the memory buffer if needed */ +static char * +strcat_char(char * str, int * len, int * tmplen, char c) +{ + if(*tmplen <= (*len + 1)) + { + *tmplen += 256; + str = (char *)realloc(str, *tmplen); + } + str[*len] = c; + (*len)++; + return str; +} + +/* iterative subroutine using a small stack + * This way, the progam stack usage is kept low */ +static char * +genXML(char * str, int * len, int * tmplen, + const struct XMLElt * p) +{ + unsigned short i, j, k; + int top; + const char * eltname, *s; + char c; + struct { + unsigned short i; + unsigned short j; + const char * eltname; + } pile[16]; /* stack */ + top = -1; + i = 0; /* current node */ + j = 1; /* i + number of nodes*/ + for(;;) + { + eltname = p[i].eltname; + if(!eltname) + return str; + if(eltname[0] == '/') + { + /*printf("<%s>%s<%s>\n", eltname+1, p[i].data, eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname+1); + str = strcat_char(str, len, tmplen, '>'); + str = strcat_str(str, len, tmplen, p[i].data); + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname); + str = strcat_char(str, len, tmplen, '>'); + for(;;) + { + if(top < 0) + return str; + i = ++(pile[top].i); + j = pile[top].j; + /*printf(" pile[%d]\t%d %d\n", top, i, j); */ + if(i==j) + { + /*printf("</%s>\n", pile[top].eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_char(str, len, tmplen, '/'); + s = pile[top].eltname; + for(c = *s; c > ' '; c = *(++s)) + str = strcat_char(str, len, tmplen, c); + str = strcat_char(str, len, tmplen, '>'); + top--; + } + else + break; + } + } + else + { + /*printf("<%s>\n", eltname); */ + str = strcat_char(str, len, tmplen, '<'); + str = strcat_str(str, len, tmplen, eltname); + str = strcat_char(str, len, tmplen, '>'); + k = i; + /*i = p[k].index; */ + /*j = i + p[k].nchild; */ + i = (unsigned)p[k].data & 0xffff; + j = i + ((unsigned)p[k].data >> 16); + top++; + /*printf(" +pile[%d]\t%d %d\n", top, i, j); */ + pile[top].i = i; + pile[top].j = j; + pile[top].eltname = eltname; + } + } +} + +/* genRootDesc() : + * - Generate the root description of the UPnP device. + * - the len argument is used to return the length of + * the returned string. + * - tmp_uuid argument is used to build the uuid string */ +char * +genRootDesc(int * len) +{ + char * str; + int tmplen; + tmplen = 2048; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + * len = strlen(xmlver); + /*strcpy(str, xmlver); */ + memcpy(str, xmlver, *len + 1); + str = genXML(str, len, &tmplen, rootDesc); + str[*len] = '\0'; + return str; +} + +/* genServiceDesc() : + * Generate service description with allowed methods and + * related variables. */ +static char * +genServiceDesc(int * len, const struct serviceDesc * s) +{ + int i, j; + const struct action * acts; + const struct stateVar * vars; + const struct argument * args; + const char * p; + char * str; + int tmplen; + tmplen = 2048; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + /*strcpy(str, xmlver); */ + *len = strlen(xmlver); + memcpy(str, xmlver, *len + 1); + + acts = s->actionList; + vars = s->serviceStateTable; + + str = strcat_char(str, len, &tmplen, '<'); + str = strcat_str(str, len, &tmplen, root_service); + str = strcat_char(str, len, &tmplen, '>'); + + str = strcat_str(str, len, &tmplen, + "<specVersion><major>1</major><minor>0</minor></specVersion>"); + + i = 0; + str = strcat_str(str, len, &tmplen, "<actionList>"); + while(acts[i].name) + { + str = strcat_str(str, len, &tmplen, "<action><name>"); + str = strcat_str(str, len, &tmplen, acts[i].name); + str = strcat_str(str, len, &tmplen, "</name>"); + /* argument List */ + args = acts[i].args; + if(args) + { + str = strcat_str(str, len, &tmplen, "<argumentList>"); + j = 0; + while(args[j].dir) + { + //JM str = strcat_str(str, len, &tmplen, "<argument><name>New"); + str = strcat_str(str, len, &tmplen, "<argument><name>"); + p = vars[args[j].relatedVar].name; + if(0 == memcmp(p, "PortMapping", 11) + && 0 != memcmp(p + 11, "Description", 11)) { + if(0 == memcmp(p + 11, "NumberOfEntries", 15)) + str = strcat_str(str, len, &tmplen, "PortMappingIndex"); + else + str = strcat_str(str, len, &tmplen, p + 11); + } else { + str = strcat_str(str, len, &tmplen, (args[j].name ? args[j].name : p)); + } + str = strcat_str(str, len, &tmplen, "</name><direction>"); + str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out"); + str = strcat_str(str, len, &tmplen, + "</direction><relatedStateVariable>"); + str = strcat_str(str, len, &tmplen, p); + str = strcat_str(str, len, &tmplen, + "</relatedStateVariable></argument>"); + j++; + } + str = strcat_str(str, len, &tmplen,"</argumentList>"); + } + str = strcat_str(str, len, &tmplen, "</action>"); + /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ + i++; + } + str = strcat_str(str, len, &tmplen, "</actionList><serviceStateTable>"); + i = 0; + while(vars[i].name) + { + str = strcat_str(str, len, &tmplen, + "<stateVariable sendEvents=\""); +#ifdef ENABLE_EVENTS + str = strcat_str(str, len, &tmplen, (vars[i].itype & 0x80)?"yes":"no"); +#else + /* for the moment allways send no. Wait for SUBSCRIBE implementation + * before setting it to yes */ + str = strcat_str(str, len, &tmplen, "no"); +#endif + str = strcat_str(str, len, &tmplen, "\"><name>"); + str = strcat_str(str, len, &tmplen, vars[i].name); + str = strcat_str(str, len, &tmplen, "</name><dataType>"); + str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]); + str = strcat_str(str, len, &tmplen, "</dataType>"); + if(vars[i].iallowedlist) + { + str = strcat_str(str, len, &tmplen, "<allowedValueList>"); + for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++) + { + str = strcat_str(str, len, &tmplen, "<allowedValue>"); + str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]); + str = strcat_str(str, len, &tmplen, "</allowedValue>"); + } + str = strcat_str(str, len, &tmplen, "</allowedValueList>"); + } + /*if(vars[i].defaultValue) */ + if(vars[i].idefault) + { + str = strcat_str(str, len, &tmplen, "<defaultValue>"); + /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */ + str = strcat_str(str, len, &tmplen, upnpdefaultvalues[vars[i].idefault]); + str = strcat_str(str, len, &tmplen, "</defaultValue>"); + } + str = strcat_str(str, len, &tmplen, "</stateVariable>"); + /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ + i++; + } + str = strcat_str(str, len, &tmplen, "</serviceStateTable></scpd>"); + str[*len] = '\0'; + return str; +} + +/* genContentDirectory() : + * Generate the ContentDirectory xml description */ +char * +genContentDirectory(int * len) +{ + return genServiceDesc(len, &scpdContentDirectory); +} + +/* genConnectionManager() : + * Generate the ConnectionManager xml description */ +char * +genConnectionManager(int * len) +{ + return genServiceDesc(len, &scpdConnectionManager); +} + +/* genWANIPCn() : + * Generate the WANIPConnection xml description */ +char * +genWANIPCn(int * len) +{ + return genServiceDesc(len, &scpdWANIPCn); +} + +/* genWANCfg() : + * Generate the WANInterfaceConfig xml description. */ +char * +genWANCfg(int * len) +{ + return genServiceDesc(len, &scpdWANCfg); +} + +#ifdef ENABLE_L3F_SERVICE +char * +genL3F(int * len) +{ + return genServiceDesc(len, &scpdL3F); +} +#endif + +#ifdef ENABLE_EVENTS +static char * +genEventVars(int * len, const struct serviceDesc * s, const char * servns) +{ + char tmp[16]; + const struct stateVar * v; + char * str; + int tmplen; + tmplen = 512; + str = (char *)malloc(tmplen); + if(str == NULL) + return NULL; + *len = 0; + v = s->serviceStateTable; + str = strcat_str(str, len, &tmplen, "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\" xmlns:s=\""); + str = strcat_str(str, len, &tmplen, servns); + str = strcat_str(str, len, &tmplen, "\">"); + while(v->name) { + if(v->itype & 0x80) { + str = strcat_str(str, len, &tmplen, "<e:property><s:"); + str = strcat_str(str, len, &tmplen, v->name); + str = strcat_str(str, len, &tmplen, ">"); + //printf("<e:property><s:%s>", v->name); + switch(v->ieventvalue) { + case 0: + break; + case 253: /* Port mapping number of entries magical value */ + snprintf(tmp, sizeof(tmp), "%d", upnp_get_portmapping_number_of_entries()); + str = strcat_str(str, len, &tmplen, tmp); + break; + case 254: /* External ip address magical value */ + if(use_ext_ip_addr) + str = strcat_str(str, len, &tmplen, use_ext_ip_addr); + else { + char ext_ip_addr[INET_ADDRSTRLEN]; + if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN) < 0) { + str = strcat_str(str, len, &tmplen, "0.0.0.0"); + } else { + str = strcat_str(str, len, &tmplen, ext_ip_addr); + } + } + /*str = strcat_str(str, len, &tmplen, "0.0.0.0");*/ + break; + case 255: /* DefaultConnectionService magical value */ + str = strcat_str(str, len, &tmplen, uuidvalue); + str = strcat_str(str, len, &tmplen, ":WANConnectionDevice:1,urn:upnp-org:serviceId:WANIPConn1"); + //printf("%s:WANConnectionDevice:1,urn:upnp-org:serviceId:WANIPConn1", uuidvalue); + break; + default: + str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]); + //printf("%s", upnpallowedvalues[v->ieventvalue]); + } + str = strcat_str(str, len, &tmplen, "</s:"); + str = strcat_str(str, len, &tmplen, v->name); + str = strcat_str(str, len, &tmplen, "></e:property>"); + //printf("</s:%s></e:property>\n", v->name); + } + v++; + } + str = strcat_str(str, len, &tmplen, "</e:propertyset>"); + //printf("</e:propertyset>\n"); + //printf("\n"); + //printf("%d\n", tmplen); + str[*len] = '\0'; + return str; +} + +char * +getVarsContentDirectory(int * l) +{ + return genEventVars(l, + &scpdContentDirectory, + "urn:schemas-upnp-org:service:ContentDirectory:1"); +} + +char * +getVarsConnectionManager(int * l) +{ + return genEventVars(l, + &scpdConnectionManager, + "urn:schemas-upnp-org:service:ConnectionManager:1"); +} + +char * +getVarsWANIPCn(int * l) +{ + return genEventVars(l, + &scpdWANIPCn, + "urn:schemas-upnp-org:service:WANIPConnection:1"); +} + +char * +getVarsWANCfg(int * l) +{ + return genEventVars(l, + &scpdWANCfg, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"); +} + +#ifdef ENABLE_L3F_SERVICE +char * +getVarsL3F(int * l) +{ + return genEventVars(l, + &scpdL3F, + "urn:schemas-upnp-org:service:Layer3Forwarding:1"); +} +#endif +#endif diff --git a/upnpdescgen.h b/upnpdescgen.h index edfd9f5..f84da77 100644 --- a/upnpdescgen.h +++ b/upnpdescgen.h @@ -1,31 +1,9 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006-2008, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __UPNPDESCGEN_H__ #define __UPNPDESCGEN_H__ @@ -85,13 +63,32 @@ char * genX_MS_MediaReceiverRegistrar(int * len); char * +genWANIPCn(int * len); + +char * +genWANCfg(int * len); + +#ifdef ENABLE_L3F_SERVICE +char * +genL3F(int * len); +#endif + +#ifdef ENABLE_EVENTS +char * getVarsContentDirectory(int * len); char * getVarsConnectionManager(int * len); char * -getVarsX_MS_MediaReceiverRegistrar(int * len); +getVarsWANIPCn(int * len); + +char * +getVarsWANCfg(int * len); + +char * +getVarsL3F(int * len); +#endif #endif diff --git a/upnpdescstrings.h b/upnpdescstrings.h index 991f532..57b543e 100644 --- a/upnpdescstrings.h +++ b/upnpdescstrings.h @@ -1,53 +1,38 @@ /* miniupnp project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the coditions detailed in + * the LICENCE file provided within the distribution */ #ifndef __UPNPDESCSTRINGS_H__ #define __UPNPDESCSTRINGS_H__ #include "config.h" /* strings used in the root device xml description */ -#ifdef NETGEAR - #define ROOTDEV_MANUFACTURERURL "http://www.netgear.com/" - #define ROOTDEV_MANUFACTURER "NETGEAR" - #define ROOTDEV_MODELNAME "Windows Media Connect compatible (ReadyDLNA)" - #define ROOTDEV_MODELURL OS_URL - #ifdef READYNAS - #define ROOTDEV_MODELDESCRIPTION "ReadyDLNA on ReadyNAS RAIDiator OS" - #else - #define ROOTDEV_MODELDESCRIPTION "ReadyDLNA" - #endif -#else - #define ROOTDEV_MANUFACTURERURL OS_URL - #define ROOTDEV_MANUFACTURER "Justin Maggard" - #define ROOTDEV_MODELNAME "Windows Media Connect compatible (MiniDLNA)" - #define ROOTDEV_MODELDESCRIPTION "MiniDLNA on " OS_NAME - #define ROOTDEV_MODELURL OS_URL -#endif +#define ROOTDEV_FRIENDLYNAME "MiniDLNA ReadyNAS:" +#define ROOTDEV_MANUFACTURER "NETGEAR" +#define ROOTDEV_MANUFACTURERURL OS_URL +#define ROOTDEV_MODELNAME "Windows Media Connect compatible (minidlna)" +#define ROOTDEV_MODELDESCRIPTION OS_NAME " *ReadyNAS dev DLNA" +#define ROOTDEV_MODELURL OS_URL + +#define WANDEV_FRIENDLYNAME "WANDevice" +#define WANDEV_MANUFACTURER "MiniUPnP" +#define WANDEV_MANUFACTURERURL "http://miniupnp.free.fr/" +#define WANDEV_MODELNAME "WAN Device" +#define WANDEV_MODELDESCRIPTION "WAN Device" +#define WANDEV_MODELNUMBER UPNP_VERSION +#define WANDEV_MODELURL "http://miniupnp.free.fr/" +#define WANDEV_UPC "MINIUPNPD" + +#define WANCDEV_FRIENDLYNAME "WANConnectionDevice" +#define WANCDEV_MANUFACTURER WANDEV_MANUFACTURER +#define WANCDEV_MANUFACTURERURL WANDEV_MANUFACTURERURL +#define WANCDEV_MODELNAME "MiniUPnPd" +#define WANCDEV_MODELDESCRIPTION "MiniUPnP daemon" +#define WANCDEV_MODELNUMBER UPNP_VERSION +#define WANCDEV_MODELURL "http://miniupnp.free.fr/" +#define WANCDEV_UPC "MINIUPNPD" #endif + diff --git a/upnpevents.c b/upnpevents.c index e9f8e08..07e06fb 100644 --- a/upnpevents.c +++ b/upnpevents.c @@ -1,53 +1,13 @@ -/* MiniDLNA project - * http://minidlna.sourceforge.net/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdio.h> #include <string.h> -#include <errno.h> +#include <syslog.h> #include <sys/queue.h> #include <stdlib.h> #include <unistd.h> @@ -58,14 +18,18 @@ #include <arpa/inet.h> #include <fcntl.h> #include <errno.h> - #include "config.h" #include "upnpevents.h" -#include "minidlnapath.h" +#include "miniupnpdpath.h" #include "upnpglobalvars.h" #include "upnpdescgen.h" -#include "uuid.h" -#include "log.h" + +#ifdef ENABLE_EVENTS +/*enum subscriber_service_enum { + EWanCFG = 1, + EWanIPC, + EL3F +};*/ /* stuctures definitions */ struct subscriber { @@ -116,12 +80,18 @@ newSubscriber(const char * eventurl, const char * callback, int callbacklen) if(!eventurl || !callback || !callbacklen) return NULL; tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); - if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) + if(strcmp(eventurl, WANCFG_EVENTURL)==0) + tmp->service = EWanCFG; + else if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) tmp->service = EContentDirectory; else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) tmp->service = EConnectionManager; - else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0) - tmp->service = EMSMediaReceiverRegistrar; + else if(strcmp(eventurl, WANIPC_EVENTURL)==0) + tmp->service = EWanIPC; +#ifdef ENABLE_L3F_SERVICE + else if(strcmp(eventurl, L3F_EVENTURL)==0) + tmp->service = EL3F; +#endif else { free(tmp); return NULL; @@ -129,13 +99,10 @@ newSubscriber(const char * eventurl, const char * callback, int callbacklen) memcpy(tmp->callback, callback, callbacklen); tmp->callback[callbacklen] = '\0'; /* make a dummy uuid */ + /* TODO: improve that */ strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); - if( get_uuid_string(tmp->uuid+5) != 0 ) - { - tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; - snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); - } - + tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; + snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); return tmp; } @@ -149,7 +116,7 @@ upnpevents_addSubscriber(const char * eventurl, struct subscriber * tmp; /*static char uuid[42];*/ /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */ - DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n", + syslog(LOG_DEBUG, "addSubscriber(%s, %.*s, %d)", eventurl, callbacklen, callback, timeout); /*strncpy(uuid, uuidvalue, sizeof(uuid)); uuid[sizeof(uuid)-1] = '\0';*/ @@ -169,7 +136,7 @@ renewSubscription(const char * sid, int sidlen, int timeout) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { - if(memcmp(sid, sub->uuid, 41) == 0) { + if(memcmp(sid, sub->uuid, 41)) { sub->timeout = (timeout ? time(NULL) + timeout : 0); return 0; } @@ -183,10 +150,8 @@ upnpevents_removeSubscriber(const char * sid, int sidlen) struct subscriber * sub; if(!sid) return -1; - DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n", - sidlen, sid); for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { - if(memcmp(sid, sub->uuid, 41) == 0) { + if(memcmp(sid, sub->uuid, 41)) { if(sub->notify) { sub->notify->sub = NULL; } @@ -218,24 +183,24 @@ upnp_event_create_notify(struct subscriber * sub) int flags; obj = calloc(1, sizeof(struct upnp_event_notify)); if(!obj) { - DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno)); + syslog(LOG_ERR, "%s: calloc(): %m", "upnp_event_create_notify"); return; } obj->sub = sub; obj->state = ECreated; obj->s = socket(PF_INET, SOCK_STREAM, 0); if(obj->s<0) { - DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno)); + syslog(LOG_ERR, "%s: socket(): %m", "upnp_event_create_notify"); goto error; } if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) { - DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n", - "upnp_event_create_notify", strerror(errno)); + syslog(LOG_ERR, "%s: fcntl(..F_GETFL..): %m", + "upnp_event_create_notify"); goto error; } if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) { - DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n", - "upnp_event_create_notify", strerror(errno)); + syslog(LOG_ERR, "%s: fcntl(..F_SETFL..): %m", + "upnp_event_create_notify"); goto error; } if(sub) @@ -273,7 +238,7 @@ upnp_event_notify_connect(struct upnp_event_notify * obj) i = 1; p++; port = (unsigned short)atoi(p); - while(*p != '/' && *p != '\0') { + while(*p != '/') { if(i<7) obj->portstr[i++] = *p; p++; } @@ -282,19 +247,16 @@ upnp_event_notify_connect(struct upnp_event_notify * obj) port = 80; obj->portstr[0] = '\0'; } - if( *p ) - obj->path = p; - else - obj->path = "/"; + obj->path = p; addr.sin_family = AF_INET; inet_aton(obj->addrstr, &addr.sin_addr); addr.sin_port = htons(port); - DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect", + syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect", obj->addrstr, port, obj->path); obj->state = EConnecting; if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { if(errno != EINPROGRESS && errno != EWOULDBLOCK) { - DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno)); + syslog(LOG_ERR, "%s: connect(): %m", "upnp_event_notify_connect"); obj->state = EError; } } @@ -328,38 +290,37 @@ static void upnp_event_prepare(struct upnp_event_notify * obj) case EConnectionManager: xml = getVarsConnectionManager(&l); break; - case EMSMediaReceiverRegistrar: - xml = getVarsX_MS_MediaReceiverRegistrar(&l); - break; default: xml = NULL; l = 0; } - obj->tosend = asprintf(&(obj->buffer), notifymsg, + obj->buffersize = 1536; + obj->buffer = malloc(obj->buffersize); + /*if(!obj->buffer) { + }*/ + obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg, obj->path, obj->addrstr, obj->portstr, l+2, obj->sub->uuid, obj->sub->seq, l, xml); - obj->buffersize = obj->tosend; if(xml) { free(xml); xml = NULL; } - DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer); + //DEBUG printf("Preparing buffer:\n%s\n", obj->buffer); obj->state = ESending; } static void upnp_event_send(struct upnp_event_notify * obj) { int i; - //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent); i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); if(i<0) { - DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno)); + syslog(LOG_NOTICE, "%s: send(): %m", "upnp_event_send"); obj->state = EError; return; } else if(i != (obj->tosend - obj->sent)) - DPRINTF(E_WARN, L_HTTP, "%s: %d bytes send out of %d\n", + syslog(LOG_NOTICE, "%s: %d bytes send out of %d", "upnp_event_send", i, obj->tosend - obj->sent); obj->sent += i; if(obj->sent == obj->tosend) @@ -371,11 +332,11 @@ static void upnp_event_recv(struct upnp_event_notify * obj) int n; n = recv(obj->s, obj->buffer, obj->buffersize, 0); if(n<0) { - DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno)); + syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv"); obj->state = EError; return; } - DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv", + syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv", n, n, obj->buffer); obj->state = EFinished; if(obj->sub) @@ -402,7 +363,7 @@ upnp_event_process_notify(struct upnp_event_notify * obj) obj->s = -1; break; default: - DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n"); + syslog(LOG_ERR, "upnp_event_process_notify: unknown state"); } } @@ -410,7 +371,7 @@ void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) { struct upnp_event_notify * obj; for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { - DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n", + syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d", obj, obj->state, obj->s); if(obj->s >= 0) { switch(obj->state) { @@ -444,7 +405,7 @@ void upnpevents_processfds(fd_set *readset, fd_set *writeset) struct subscriber * subnext; time_t curtime; for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { - DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n", + syslog(LOG_DEBUG, "%s: %p %d %d %d %d", "upnpevents_processfds", obj, obj->state, obj->s, FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); if(obj->s >= 0) { @@ -461,13 +422,11 @@ void upnpevents_processfds(fd_set *readset, fd_set *writeset) } if(obj->sub) obj->sub->notify = NULL; -#if 0 /* Just let it time out instead of explicitly removing the subscriber */ /* remove also the subscriber from the list if there was an error */ if(obj->state == EError && obj->sub) { LIST_REMOVE(obj->sub, entries); free(obj->sub); } -#endif if(obj->buffer) { free(obj->buffer); } @@ -488,7 +447,7 @@ void upnpevents_processfds(fd_set *readset, fd_set *writeset) } } -#ifdef USE_MINIDLNACTL +#ifdef USE_MINIUPNPDCTL void write_events_details(int s) { int n; char buff[80]; @@ -514,3 +473,6 @@ void write_events_details(int s) { } } #endif + +#endif + diff --git a/upnpevents.h b/upnpevents.h index c9748af..b4d2566 100644 --- a/upnpevents.h +++ b/upnpevents.h @@ -1,56 +1,18 @@ -/* MiniDLNA project - * http://minidlna.sourceforge.net/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __UPNPEVENTS_H__ #define __UPNPEVENTS_H__ +#ifdef ENABLE_EVENTS enum subscriber_service_enum { - EContentDirectory = 1, + EWanCFG = 1, + EContentDirectory, EConnectionManager, - EMSMediaReceiverRegistrar + EWanIPC, + EL3F }; void @@ -75,3 +37,4 @@ void write_events_details(int s); #endif #endif +#endif diff --git a/upnpglobalvars.c b/upnpglobalvars.c index d059daa..0459352 100644 --- a/upnpglobalvars.c +++ b/upnpglobalvars.c @@ -1,94 +1,81 @@ -/* MiniDLNA project - * - * http://sourceforge.net/projects/minidlna/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <sys/types.h> #include <netinet/in.h> -#include <linux/limits.h> #include "config.h" #include "upnpglobalvars.h" -#include "upnpdescstrings.h" + +/* network interface for internet */ +const char * ext_if_name = 0; + +/* file to store leases */ +#ifdef ENABLE_LEASEFILE +const char* lease_file = 0; +#endif + +/* forced ip address to use for this interface + * when NULL, getifaddr() is used */ +const char * use_ext_ip_addr = 0; /* LAN address */ /*const char * listen_addr = 0;*/ +unsigned long downstream_bitrate = 0; +unsigned long upstream_bitrate = 0; + /* startup time */ time_t startup_time = 0; -struct runtime_vars_s runtime_vars; -int runtime_flags = INOTIFY_MASK; +#if 0 +/* use system uptime */ +int sysuptime = 0; + +/* log packets flag */ +int logpackets = 0; + +#ifdef ENABLE_NATPMP +int enablenatpmp = 0; +#endif +#endif + +int runtime_flags = 0; const char * pidfilename = "/var/run/minidlna.pid"; char uuidvalue[] = "uuid:00000000-0000-0000-0000-000000000000"; -char modelname[MODELNAME_MAX_LEN] = ROOTDEV_MODELNAME; -char modelnumber[MODELNUMBER_MAX_LEN] = MINIDLNA_VERSION; char serialnumber[SERIALNUMBER_MAX_LEN] = "00000000"; -#if PNPX -char pnpx_hwid[] = "VEN_0000&DEV_0000&REV_01 VEN_0033&DEV_0001&REV_01"; -#endif + +char modelnumber[MODELNUMBER_MAX_LEN] = "1"; /* presentation url : * http://nnn.nnn.nnn.nnn:ppppp/ => max 30 bytes including terminating 0 */ char presentationurl[PRESENTATIONURL_MAX_LEN]; +/* UPnP permission rules : */ +struct upnpperm * upnppermlist = 0; +unsigned int num_upnpperm = 0; + +#ifdef ENABLE_NATPMP +/* NAT-PMP */ +unsigned int nextnatpmptoclean_timestamp = 0; +unsigned short nextnatpmptoclean_eport = 0; +unsigned short nextnatpmptoclean_proto = 0; +#endif + +#ifdef USE_PF +const char * queue = 0; +const char * tag = 0; +#endif + int n_lan_addr = 0; struct lan_addr_s lan_addr[MAX_LAN_ADDR]; /* UPnP-A/V [DLNA] */ -sqlite3 * db; -char dlna_no_conv[] = "DLNA.ORG_OP=01;DLNA.ORG_CI=0"; -char friendly_name[FRIENDLYNAME_MAX_LEN]; -char db_path[PATH_MAX] = {'\0'}; -char log_path[PATH_MAX] = {'\0'}; -struct media_dir_s * media_dirs = NULL; -struct album_art_name_s * album_art_names = NULL; -struct client_cache_s clients[CLIENT_CACHE_SLOTS]; -short int scanning = 0; -volatile short int quitting = 0; -volatile uint32_t updateID = 0; +sqlite3 *db; +char media_dir[256]; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 099c7da..4d32f9f 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -1,180 +1,50 @@ -/* MiniDLNA project - * - * http://sourceforge.net/projects/minidlna/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __UPNPGLOBALVARS_H__ #define __UPNPGLOBALVARS_H__ #include <time.h> - -#include "minidlnatypes.h" +#include "miniupnpdtypes.h" #include "config.h" #include <sqlite3.h> -#define MINIDLNA_VERSION "1.0.19.2" - -#ifdef NETGEAR -# define SERVER_NAME "ReadyDLNA" -#else -# define SERVER_NAME "MiniDLNA" -#endif - -#define CLIENT_CACHE_SLOTS 20 -#define USE_FORK 1 -#define DB_VERSION 5 +/* name of the network interface used to acces internet */ +extern const char * ext_if_name; -#ifdef ENABLE_NLS -#define _(string) gettext(string) -#else -#define _(string) (string) +/* file to store all leases */ +#ifdef ENABLE_LEASEFILE +extern const char * lease_file; #endif -#ifndef PNPX -#define PNPX 0 -#endif +/* forced ip address to use for this interface + * when NULL, getifaddr() is used */ +extern const char * use_ext_ip_addr; -#if 0 // Add these once the newer ffmpeg libs that can detect WMAPRO are more widely used - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," -#endif -#define RESOURCE_PROTOCOL_INFO_VALUES \ - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," \ - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HP_HD_AC3_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AC3_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_CIF15_AAC_520;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_CIF30_AAC_940;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_L31_HD_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_L32_HD_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_L3L_SD_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_VGA_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_NA_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPLL_BASE;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_BASE;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAPRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/3gpp:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_MULT5_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:image/jpeg:*," \ - "http-get:*:video/avi:*," \ - "http-get:*:video/divx:*," \ - "http-get:*:video/x-matroska:*," \ - "http-get:*:video/mpeg:*," \ - "http-get:*:video/mp4:*," \ - "http-get:*:video/x-ms-wmv:*," \ - "http-get:*:video/x-msvideo:*," \ - "http-get:*:video/x-flv:*," \ - "http-get:*:video/x-tivo-mpeg:*," \ - "http-get:*:video/quicktime:*," \ - "http-get:*:audio/mp4:*," \ - "http-get:*:audio/x-wav:*," \ - "http-get:*:audio/x-flac:*," \ - "http-get:*:application/ogg:*" +/* parameters to return to upnp client when asked */ +extern unsigned long downstream_bitrate; +extern unsigned long upstream_bitrate; /* statup time */ extern time_t startup_time; -extern struct runtime_vars_s runtime_vars; /* runtime boolean flags */ extern int runtime_flags; -#define INOTIFY_MASK 0x0001 -#define TIVO_MASK 0x0002 -#define DLNA_STRICT_MASK 0x0004 +#define LOGPACKETSMASK 0x0001 +#define SYSUPTIMEMASK 0x0002 +#ifdef ENABLE_NATPMP +#define ENABLENATPMPMASK 0x0004 +#endif +#define CHECKCLIENTIPMASK 0x0008 +#define SECUREMODEMASK 0x0010 + +#ifdef PF_ENABLE_FILTER_RULES +#define PFNOQUICKRULESMASK 0x0040 +#endif #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) runtime_flags & mask @@ -184,20 +54,30 @@ extern const char * pidfilename; extern char uuidvalue[]; -#define MODELNAME_MAX_LEN (64) -extern char modelname[]; +#define SERIALNUMBER_MAX_LEN (10) +extern char serialnumber[]; -#define MODELNUMBER_MAX_LEN (16) +#define MODELNUMBER_MAX_LEN (48) extern char modelnumber[]; -#define SERIALNUMBER_MAX_LEN (16) -extern char serialnumber[]; - #define PRESENTATIONURL_MAX_LEN (64) extern char presentationurl[]; -#if PNPX -extern char pnpx_hwid[]; +/* UPnP permission rules : */ +extern struct upnpperm * upnppermlist; +extern unsigned int num_upnpperm; + +#ifdef ENABLE_NATPMP +/* NAT-PMP */ +extern unsigned int nextnatpmptoclean_timestamp; +extern unsigned short nextnatpmptoclean_eport; +extern unsigned short nextnatpmptoclean_proto; +#endif + +#ifdef USE_PF +/* queue and tag for PF rules */ +extern const char * queue; +extern const char * tag; #endif /* lan addresses */ @@ -209,16 +89,7 @@ extern struct lan_addr_s lan_addr[]; /* UPnP-A/V [DLNA] */ extern sqlite3 *db; -extern char dlna_no_conv[]; -#define FRIENDLYNAME_MAX_LEN (64) -extern char friendly_name[]; -extern char db_path[]; -extern char log_path[]; -extern struct media_dir_s * media_dirs; -extern struct album_art_name_s * album_art_names; -extern struct client_cache_s clients[CLIENT_CACHE_SLOTS]; -extern short int scanning; -extern volatile short int quitting; -extern volatile uint32_t updateID; +#define MEDIADIR_MAX_LEN (256) +extern char media_dir[]; #endif @@ -1,51 +1,11 @@ -/* MiniDLNA project - * - * http://sourceforge.net/projects/minidlna/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +/* $Id$ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + * Copyright (c) 2005-2008 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file included in this distribution. + * */ #include <stdlib.h> #include <unistd.h> #include <stdio.h> @@ -53,11 +13,12 @@ #include <sys/types.h> #include <sys/socket.h> #include <sys/param.h> +#include <syslog.h> #include <ctype.h> #include "config.h" #include "upnphttp.h" #include "upnpdescgen.h" -#include "minidlnapath.h" +#include "miniupnpdpath.h" #include "upnpsoap.h" #include "upnpevents.h" @@ -66,24 +27,13 @@ #include <fcntl.h> #include <errno.h> #include <sys/sendfile.h> -#include <arpa/inet.h> #include "upnpglobalvars.h" -#include "utils.h" -#include "getifaddr.h" -#include "image_utils.h" -#include "log.h" -#include "sql.h" +#include <sqlite3.h> #include <libexif/exif-loader.h> -#ifdef TIVO_SUPPORT -#include "tivo_utils.h" -#include "tivo_commands.h" +#if 0 //JPEG_RESIZE +#include <gd.h> #endif -//#define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much? -#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much? -#define MIN_BUFFER_SIZE 65536 - -#include "icons.c" struct upnphttp * New_upnphttp(int s) @@ -104,7 +54,7 @@ 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)); + syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); } h->socket = -1; h->state = 100; @@ -125,40 +75,6 @@ Delete_upnphttp(struct upnphttp * 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 ) - { - /* Invalidate this client cache if it's older than 1 hour */ - 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 ) - { - /* Same MAC as last time when we were able to identify the client, - * so extend the timeout by another hour. */ - 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; -} - /* parse HttpHeaders of the REQUEST */ static void ParseHttpHeaders(struct upnphttp * h) @@ -180,6 +96,9 @@ ParseHttpHeaders(struct upnphttp * h) while(*p < '0' || *p > '9') p++; h->req_contentlen = atoi(p); + /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen); + printf(" readbufflen=%d contentoff = %d\n", + h->req_buflen, h->req_contentoff);*/ } else if(strncasecmp(line, "SOAPAction", 10)==0) { @@ -199,6 +118,7 @@ ParseHttpHeaders(struct upnphttp * h) h->req_soapAction = p; h->req_soapActionLen = n; } +#ifdef ENABLE_EVENTS else if(strncasecmp(line, "Callback", 8)==0) { p = colon; @@ -212,26 +132,14 @@ ParseHttpHeaders(struct upnphttp * h) } else if(strncasecmp(line, "SID", 3)==0) { - //zqiu: fix bug for test 4.0.5 - //Skip extra headers like "SIDHEADER: xxxxxx xxx" - for(p=line+3;p<colon;p++) - { - if(!isspace(*p)) - { - p = NULL; //unexpected header - 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; - } + p = colon + 1; + while(isspace(*p)) + p++; + n = 0; + while(!isspace(p[n])) + n++; + h->req_SID = p; + h->req_SIDLen = n; } /* Timeout: Seconds-nnnn */ /* TIMEOUT @@ -249,6 +157,8 @@ intervening space) by either an integer or the keyword "infinite". */ h->req_Timeout = atoi(p+7); } } +#endif +#if 1 // Range: bytes=xxx-yyy else if(strncasecmp(line, "Range", 5)==0) { @@ -259,107 +169,13 @@ intervening space) by either an integer or the keyword "infinite". */ h->reqflags |= FLAG_RANGE; h->req_RangeEnd = atoll(index(p+6, '-')+1); h->req_RangeStart = atoll(p+6); - DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n", - h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); +printf("Range Start-End: %lld-%lld\n", h->req_RangeStart, h->req_RangeEnd); } } else if(strncasecmp(line, "Host", 4)==0) { h->reqflags |= FLAG_HOST; } - else if(strncasecmp(line, "User-Agent", 10)==0) - { - /* Skip client detection if we already detected it. */ - 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(strncmp(p, "SamsungWiselinkPro", 18)==0 || - strncmp(p, "SEC_HHP_", 8)==0) - { - h->req_client = ESamsungTV; - h->reqflags |= FLAG_DLNA; - h->reqflags |= FLAG_NO_RESIZE; - //h->reqflags |= FLAG_MIME_AVI_DIVX; - } - 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, "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) - { - /* Skip client detection if we already detected it. */ - 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; - } - /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */ - /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */ - /* Sony SMP-100 needs the same treatment as their BDP-S370 */ - /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */ - 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; - } - /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */ - /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */ - 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; @@ -375,17 +191,13 @@ intervening space) by either an integer or the keyword "infinite". */ p = colon + 1; while(isspace(*p)) p++; - if( (*p != '1') || !isspace(p[1]) ) + if( strcmp(p, "1") != 0 ) 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; @@ -408,77 +220,29 @@ intervening space) by either an integer or the keyword "infinite". */ h->reqflags |= FLAG_XFERBACKGROUND; } } - else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0) - { - h->reqflags |= FLAG_CAPTION; - } +#endif } -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) ) + if( h->req_buflen > h->req_contentoff ) { - while(!(endptr[0] == '\r' && endptr[1] == '\n')) + h->req_chunklen = strtol(line, NULL, 16); + while(!(line[0] == '\r' && line[1] == '\n')) { - endptr++; + line++; + h->req_contentoff++; } - line = endptr+h->req_chunklen+2; + h->req_contentoff += 2; } - - if( endptr == line ) + else { h->req_chunklen = -1; - return; } } - /* If the client type wasn't found, search the cache. - * This is done because a lot of clients like to send a - * different User-Agent with different types of requests. */ - n = SearchClientCache(h->clientaddr, 0); - if( h->req_client ) - { - /* Add this client to the cache if it's not there already. */ - 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) ) - { - /* If we know the client and our new detection is generic, use our cached info */ - 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; - } } /* very minimalistic 400 error message */ @@ -500,6 +264,20 @@ Send400(struct upnphttp * h) static void Send404(struct upnphttp * h) { +/* + static const char error404[] = "HTTP/1.1 404 Not found\r\n" + "Connection: close\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<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"; + int n; + n = send(h->socket, error404, sizeof(error404) - 1, 0); + if(n < 0) + { + syslog(LOG_ERR, "Send404: send(http): %m"); + }*/ static const char body404[] = "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" "<BODY><H1>Not Found</H1>The requested URL was not found" @@ -511,13 +289,13 @@ Send404(struct upnphttp * h) CloseSocket_upnphttp(h); } -/* very minimalistic 406 error message */ +/* very minimalistic 404 error message */ 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" + "<BODY><H1>Not Acceptable</H1>An unsupported operation " " was requested.</BODY></HTML>\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 406, "Not Acceptable", @@ -526,7 +304,7 @@ Send406(struct upnphttp * h) CloseSocket_upnphttp(h); } -/* very minimalistic 416 error message */ +/* very minimalistic 404 error message */ static void Send416(struct upnphttp * h) { @@ -541,25 +319,25 @@ Send416(struct upnphttp * h) CloseSocket_upnphttp(h); } -/* very minimalistic 500 error message */ -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); -} - /* very minimalistic 501 error message */ -void +static void Send501(struct upnphttp * h) { +/* + static const char error501[] = "HTTP/1.1 501 Not Implemented\r\n" + "Connection: close\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<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"; + int n; + n = send(h->socket, error501, sizeof(error501) - 1, 0); + if(n < 0) + { + syslog(LOG_ERR, "Send501: send(http): %m"); + } +*/ static const char body501[] = "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" "<BODY><H1>Not Implemented</H1>The HTTP Method " @@ -574,7 +352,7 @@ Send501(struct upnphttp * h) static const char * findendheaders(const char * s, int len) { - while(len-- > 0) + while(len-->0) { if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') return s; @@ -583,6 +361,25 @@ findendheaders(const char * s, int len) return NULL; } +#ifdef HAS_DUMMY_SERVICE +static void +sendDummyDesc(struct upnphttp * h) +{ + static const char xml_desc[] = "<?xml version=\"1.0\"?>\r\n" + "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">" + " <specVersion>" + " <major>1</major>" + " <minor>0</minor>" + " </specVersion>" + " <actionList />" + " <serviceStateTable />" + "</scpd>\r\n"; + BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} +#endif + /* Sends the description generated by the parameter */ static void sendXMLdesc(struct upnphttp * h, char * (f)(int *)) @@ -592,11 +389,17 @@ sendXMLdesc(struct upnphttp * h, char * (f)(int *)) desc = f(&len); if(!desc) { - DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n"); - Send500(h); - return; + static const char error500[] = "<HTML><HEAD><TITLE>Error 500</TITLE>" + "</HEAD><BODY>Internal Server Error</BODY></HTML>\r\n"; + syslog(LOG_ERR, "Failed to generate XML description"); + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 500, "Internal Server Error", + error500, sizeof(error500)-1); + } + else + { + BuildResp_upnphttp(h, desc, len); } - BuildResp_upnphttp(h, desc, len); SendResp_upnphttp(h); CloseSocket_upnphttp(h); free(desc); @@ -612,7 +415,10 @@ ProcessHTTPPOST_upnphttp(struct upnphttp * h) if(h->req_soapAction) { /* we can process the request */ - DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction); +//printf("__LINE %d__ SOAPAction: %s [%d]\n", __LINE__, h->req_soapAction, h->req_soapActionLen); +// syslog(LOG_INFO, "SOAPAction: %.*s", +// h->req_soapActionLen, h->req_soapAction); +//printf("__LINE %d__ SOAPAction: %.*s\n", __LINE__, h->req_soapActionLen, h->req_soapAction); ExecuteSoapAction(h, h->req_soapAction, h->req_soapActionLen); @@ -621,7 +427,7 @@ ProcessHTTPPOST_upnphttp(struct upnphttp * h) { static const char err400str[] = "<html><body>Bad request</body></html>"; - DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n"); + syslog(LOG_INFO, "No SOAPAction in HTTP headers"); h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", err400str, sizeof(err400str) - 1); @@ -636,14 +442,15 @@ ProcessHTTPPOST_upnphttp(struct upnphttp * h) } } +#ifdef ENABLE_EVENTS 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", + syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); + syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", h->req_CallbackLen, h->req_Callback, h->req_Timeout); - DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); + syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); if(!h->req_Callback && !h->req_SID) { /* Missing or invalid CALLBACK : 412 Precondition Failed. * If CALLBACK header is missing or does not contain a valid HTTP URL, @@ -661,7 +468,7 @@ ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if(sid) { - DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid); + syslog(LOG_DEBUG, "generated sid=%s", sid); h->respflags |= FLAG_SID; h->req_SID = sid; h->req_SIDLen = strlen(sid); @@ -679,7 +486,6 @@ with HTTP error 412 Precondition Failed. */ /* A DLNA device must enforce a 5 minute timeout */ h->respflags = FLAG_TIMEOUT; h->req_Timeout = 300; - h->respflags |= FLAG_SID; BuildResp_upnphttp(h, 0, 0); } } @@ -691,8 +497,8 @@ with HTTP error 412 Precondition Failed. */ 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); + syslog(LOG_DEBUG, "ProcessHTTPUnSubscribe %s", path); + syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); /* Remove from the list */ if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); @@ -702,6 +508,7 @@ ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) SendResp_upnphttp(h); CloseSocket_upnphttp(h); } +#endif /* Parse and process Http Query * called once all the HTTP headers have been received. */ @@ -709,7 +516,7 @@ static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { char HttpCommand[16]; - char HttpUrl[512]; + char HttpUrl[128]; char * HttpVer; char * p; int i; @@ -727,7 +534,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) while(*p!='/') p++; } - for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++) + for(i = 0; i<127 && *p != ' ' && *p != '\r'; i++) HttpUrl[i] = *(p++); HttpUrl[i] = '\0'; while(*p==' ') @@ -736,41 +543,18 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) for(i = 0; i<15 && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; - /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n", - HttpCommand, HttpUrl, HttpVer);*/ + syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)", + HttpCommand, HttpUrl, HttpVer); ParseHttpHeaders(h); - /* see if we need to wait for remaining data */ - if( (h->reqflags & FLAG_CHUNKED) ) + if( (h->reqflags & FLAG_CHUNKED) && (h->req_chunklen > (h->req_buflen - h->req_contentoff) || h->req_chunklen < 0) ) { - 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; + /* waiting for remaining data */ + printf("*** %d < %d\n", (h->req_buflen - h->req_contentoff), h->req_contentlen); + printf("Chunked request [%ld]. Need more input.\n", h->req_chunklen); + h->state = 2; } - - DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); - if(strcmp("POST", HttpCommand) == 0) + else if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp(h); @@ -779,20 +563,14 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) { 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"); + syslog(LOG_NOTICE, "No Host specified in HTTP headers, responding ERROR 400"); Send400(h); - return; } - #if 1 /* 7.3.33.4 */ - else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) && - !(h->reqflags & FLAG_RANGE) ) + else if( h->reqflags & FLAG_TIMESEEK ) { - DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n", - h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed"); + syslog(LOG_NOTICE, "DLNA TimeSeek requested, responding ERROR 406"); Send406(h); - return; } - #endif else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; @@ -803,18 +581,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) } if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) { - /* If it's a Xbox360, we might need a special friendly_name to be recognized */ - 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 - { - sendXMLdesc(h, genRootDesc); - } + sendXMLdesc(h, genRootDesc); } else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) { @@ -828,6 +595,12 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) { sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); } +#ifdef HAS_DUMMY_SERVICE + else if(strcmp(DUMMY_PATH, HttpUrl) == 0) + { + sendDummyDesc(h); + } +#endif else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) { SendResp_dlnafile(h, HttpUrl+12); @@ -836,56 +609,22 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) { SendResp_thumbnail(h, HttpUrl+12); - } - else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) - { - SendResp_albumArt(h, HttpUrl+10); CloseSocket_upnphttp(h); } - #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) +#if 0 //JPEG_RESIZE + else if(strncmp(HttpUrl, "/Resized/", 7) == 0) { - SendResp_resizedimg(h, HttpUrl+9); - CloseSocket_upnphttp(h); - } - else if(strncmp(HttpUrl, "/icons/", 7) == 0) - { - SendResp_icon(h, HttpUrl+7); - CloseSocket_upnphttp(h); - } - else if(strncmp(HttpUrl, "/Captions/", 10) == 0) - { - SendResp_caption(h, HttpUrl+10); + SendResp_resizedimg(h, HttpUrl+7); CloseSocket_upnphttp(h); } +#endif else { - DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl); + syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); Send404(h); } } +#ifdef ENABLE_EVENTS else if(strcmp("SUBSCRIBE", HttpCommand) == 0) { h->req_command = ESubscribe; @@ -896,9 +635,16 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) h->req_command = EUnSubscribe; ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); } +#else + else if(strcmp("SUBSCRIBE", HttpCommand) == 0) + { + syslog(LOG_NOTICE, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled"); + Send501(h); + } +#endif else { - DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand); + syslog(LOG_NOTICE, "Unsupported HTTP Command %s", HttpCommand); Send501(h); } } @@ -917,12 +663,12 @@ Process_upnphttp(struct upnphttp * h) n = recv(h->socket, buf, 2048, 0); if(n<0) { - DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno)); + syslog(LOG_ERR, "recv (state0): %m"); h->state = 100; } else if(n==0) { - DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); + syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); h->state = 100; } else @@ -939,7 +685,6 @@ Process_upnphttp(struct upnphttp * h) if(endheaders) { h->req_contentoff = endheaders - h->req_buf + 4; - h->req_contentlen = h->req_buflen - h->req_contentoff; ProcessHttpQuery_upnphttp(h); } } @@ -949,12 +694,12 @@ Process_upnphttp(struct upnphttp * h) n = recv(h->socket, buf, 2048, 0); if(n<0) { - DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno)); + syslog(LOG_ERR, "recv (state1): %m"); h->state = 100; } else if(n==0) { - DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); + syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); h->state = 100; } else @@ -965,31 +710,28 @@ Process_upnphttp(struct upnphttp * h) h->req_buflen += n; if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { - /* Need the struct to point to the realloc'd memory locations */ 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); + syslog(LOG_WARNING, "Unexpected state: %d", h->state); } } static const char httpresphead[] = "%s %d %s\r\n" + /*"Content-Type: text/xml; charset=\"utf-8\"\r\n"*/ "Content-Type: %s\r\n" "Connection: close\r\n" "Content-Length: %d\r\n" - "Server: " MINIDLNA_SERVER_STRING "\r\n" + /*"Server: miniupnpd/1.0 UPnP/1.0\r\n"*/ // "Accept-Ranges: bytes\r\n" +// "DATE: Wed, 24 Sep 2008 05:57:19 GMT\r\n" + //"Server: " MINIUPNPD_SERVER_STRING "\r\n" ; /*"\r\n";*/ /* "<?xml version=\"1.0\"?>\n" @@ -1011,7 +753,7 @@ BuildHeader_upnphttp(struct upnphttp * h, int respcode, int templen; if(!h->res_buf) { - templen = sizeof(httpresphead) + 192 + bodylen; + templen = sizeof(httpresphead) + 128 + bodylen; h->res_buf = (char *)malloc(templen); h->res_buf_alloclen = templen; } @@ -1022,6 +764,7 @@ BuildHeader_upnphttp(struct upnphttp * h, int respcode, (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", bodylen); /* Additional headers */ +#ifdef ENABLE_EVENTS if(h->respflags & FLAG_TIMEOUT) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, @@ -1040,21 +783,25 @@ BuildHeader_upnphttp(struct upnphttp * h, int respcode, 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); + "SID: %s\r\n", h->req_SID); } +#endif #if 0 // DLNA + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Server: Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0\r\n"); char szTime[30]; time_t curtime = time(NULL); strftime(szTime, 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", szTime); - h->res_buflen += snprintf(h->res_buf + h->res_buflen, - h->res_buf_alloclen - h->res_buflen, - "contentFeatures.dlna.org: \r\n"); - h->res_buflen += snprintf(h->res_buf + h->res_buflen, - h->res_buf_alloclen - h->res_buflen, - "EXT:\r\n"); +// h->res_buflen += snprintf(h->res_buf + h->res_buflen, +// h->res_buf_alloclen - h->res_buflen, +// "contentFeatures.dlna.org: \r\n"); +// h->res_buflen += snprintf(h->res_buf + h->res_buflen, +// h->res_buf_alloclen - h->res_buflen, +// "EXT:\r\n"); #endif h->res_buf[h->res_buflen++] = '\r'; h->res_buf[h->res_buflen++] = '\n'; @@ -1090,602 +837,222 @@ void SendResp_upnphttp(struct upnphttp * h) { int n; - DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf); +printf("HTTP RESPONSE:\n%.*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) - { - /* TODO : handle correctly this case */ - DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", - n, h->res_buflen); + syslog(LOG_ERR, "send(res_buf): %m"); } -} - -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) { /* TODO : handle correctly this case */ - DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", 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; - int try_sendfile = 1; - - while( offset < end_offset ) - { - if( try_sendfile ) - { - send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); - ret = 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 sendfile isn't supported on the filesystem, don't bother trying to use it again. */ - if( errno == EOVERFLOW || errno == EINVAL ) - try_sendfile = 0; - else if( errno != EAGAIN ) - break; - } - else - { - //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); - continue; - } - } - /* Fall back to regular I/O */ - 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; - } - if( buf ) - free(buf); -} - -void -SendResp_icon(struct upnphttp * h, char * icon) -{ - char * header; - char * data; - int size, ret; - char mime[12]; - char date[30]; - 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, "image/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, "image/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, "image/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, "image/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 = asprintf(&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) && (h->req_command != EHead) ) - { - send_data(h, data, size, 0); - } - free(header); -} - -void -SendResp_albumArt(struct upnphttp * h, char * object) +SendResp_thumbnail(struct upnphttp * h, char * object) { char header[1500]; char sql_buf[256]; char **result; - int rows = 0; - char *path; - char *dash; char date[30]; time_t curtime = time(NULL); - off_t offset = 0, size; - int sendfh; + int n; + ExifData *ed; + ExifLoader *l; memset(header, 0, 1500); if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) { - DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); Send406(h); return; } - dash = strchr(object, '-'); - if( dash ) - *dash = '\0'; - sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object); - sql_get_table(db, sql_buf, &result, &rows, NULL); - if( !rows ) - { - DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object); - Send404(h); - goto error; - } - path = result[1]; - DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path); + sprintf(sql_buf, "SELECT PATH from OBJECTS where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); + printf("Serving up thumbnail for ObjectId: %s [%s]\n", object, result[1]); - if( access(path, F_OK) == 0 ) + if( access(result[1], F_OK) == 0 ) { strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); - sendfh = open(path, O_RDONLY); - if( sendfh < 0 ) { - DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); + l = exif_loader_new(); + exif_loader_write_file(l, result[1]); + ed = exif_loader_get_data(l); + exif_loader_unref(l); + + if( !ed->size ) + { + Send404(h); goto error; } - size = lseek(sendfh, 0, SEEK_END); - lseek(sendfh, 0, SEEK_SET); - sprintf(header, "HTTP/1.1 200 OK\r\n" "Content-Type: image/jpeg\r\n" - "Content-Length: %jd\r\n" + "Content-Length: %d\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", - size, date); + "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n", + ed->size, date); if( h->reqflags & FLAG_XFERBACKGROUND ) { - strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); + strcat(header, "transferMode.dlna.org: Background\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { - strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); + strcat(header, "transferMode.dlna.org: Interactive\r\n"); } + strcat(header, "\r\n"); - - if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) ) + n = send(h->socket, header, strlen(header), 0); + if(n<0) { - send_file(h, sendfh, offset, size); + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); } - close(sendfh); - } - error: - sqlite3_free_table(result); -} -void -SendResp_caption(struct upnphttp * h, char * object) -{ - char header[1500]; - char sql_buf[256]; - char **result; - int rows = 0; - char *path; - char date[30]; - time_t curtime = time(NULL); - off_t offset = 0, size; - int sendfh, ret; - - memset(header, 0, 1500); - - strip_ext(object); - sprintf(sql_buf, "SELECT PATH from CAPTIONS where ID = %s", object); - sql_get_table(db, sql_buf, &result, &rows, NULL); - if( !rows ) - { - DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object); - Send404(h); - goto error; - } - path = result[1]; - DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path); - - if( access(path, F_OK) != 0 ) - goto error; - - strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); - sendfh = open(path, O_RDONLY); - if( sendfh < 0 ) { - DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); - goto error; - } - size = lseek(sendfh, 0, SEEK_END); - lseek(sendfh, 0, SEEK_SET); - - 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", - size, date); + if( h->req_command == EHead ) + { + exif_data_unref(ed); + goto error; + } - if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) ) - { - send_file(h, sendfh, offset, size); + n = send(h->socket, ed->data, ed->size, 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + exif_data_unref(ed); } - close(sendfh); - error: sqlite3_free_table(result); } +#if 0 //JPEG_RESIZE void -SendResp_thumbnail(struct upnphttp * h, char * object) +SendResp_resizedimg(struct upnphttp * h, char * object) { char header[1500]; char sql_buf[256]; char **result; - int rows = 0; - char *path; char date[30]; time_t curtime = time(NULL); - ExifData *ed; - ExifLoader *l; + int n; + FILE *imgfile; + gdImagePtr imsrc = 0, imdst = 0; + int dstw, dsth, srcw, srch, size; + char * data; memset(header, 0, 1500); if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) { - DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); + syslog(LOG_NOTICE, "You can't specify transferMode as Streaming with a resized image!"); Send406(h); return; } - strip_ext(object); - sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object); - sql_get_table(db, sql_buf, &result, &rows, NULL); - if( !rows ) - { - DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); - Send404(h); - goto error; - } - path = result[1]; - DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path); + sprintf(sql_buf, "SELECT o.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); + printf("Serving up resized image for ObjectId: %s [%s]\n", object, result[1]); - if( access(path, F_OK) == 0 ) + if( access(result[3], F_OK) == 0 ) { strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); - l = exif_loader_new(); - exif_loader_write_file(l, path); - ed = exif_loader_get_data(l); - exif_loader_unref(l); + imgfile = fopen(result[3], "r"); + imsrc = gdImageCreateFromJpeg(imgfile); + imdst = gdImageCreateTrueColor(dstw, dsth); + srcw = atoi(result[4]); + srch = atoi(result[5]); + dstw = 640; + dsth = ((((640<<10)/srcw)*srch)>>10); - if( !ed || !ed->size ) + if( !imsrc ) { Send404(h); - if( ed ) - exif_data_unref(ed); goto error; } - sprintf(header, "HTTP/1.1 200 OK\r\n" + if( dsth > 480 ) + { + dsth = 480; + dstw = (((480<<10)/srch) * srcw>>10); + } + gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, srcw, srch); + data = (char *)gdImageJpegPtr(imdst, &size, -1); + sprintf(header, "%s 200 OK\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %d\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", - ed->size, date); + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n", + h->HttpVer, size, date); - if( h->reqflags & FLAG_XFERBACKGROUND ) + if( h->reqflags & FLAG_XFERINTERACTIVE ) { - strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); + strcat(header, "transferMode.dlna.org: Interactive\r\n"); } - else //if( h->reqflags & FLAG_XFERINTERACTIVE ) + else if( h->reqflags & FLAG_XFERBACKGROUND ) { - strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); + strcat(header, "transferMode.dlna.org: Background\r\n"); } + strcat(header, "\r\n"); - if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) ) - { - send_data(h, (char *)ed->data, ed->size, 0); - } - exif_data_unref(ed); - } - CloseSocket_upnphttp(h); - error: - sqlite3_free_table(result); -} - -void -SendResp_resizedimg(struct upnphttp * h, char * object) -{ - char header[1500]; - char str_buf[256]; - char **result; - char date[30]; - char dlna_pn[4]; - time_t curtime = time(NULL); - int width=640, height=480, dstw, dsth, size; - long srcw, srch; - unsigned char * data = NULL; - char *path, *file_path; - char *resolution; - char *key, *val; - char *saveptr=NULL, *item=NULL; - /* Not implemented yet * - char *pixelshape=NULL; - int rotation; */ - sqlite_int64 id; - int rows=0, chunked=0, ret; -#ifdef __sparc__ - char *tn; - ExifData *ed; - ExifLoader *l; -#endif - image_s *imsrc = NULL, *imdst = NULL; - int scale = 1; - - id = strtoll(object, NULL, 10); - sprintf(str_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id); - ret = sql_get_table(db, str_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 || (access(result[3], F_OK) != 0) ) - { - DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); - sqlite3_free_table(result); - Send404(h); - return; - } -#if USE_FORK - pid_t newpid = 0; - newpid = fork(); - if( newpid ) - goto resized_error; -#endif - file_path = result[3]; - resolution = result[4]; - srcw = strtol(resolution, &saveptr, 10); - srch = strtol(saveptr+1, NULL, 10); - - path = strdup(object); - if( strtok_r(path, "?", &saveptr) ) - { - item = strtok_r(NULL, "&,", &saveptr); - } - while( item != NULL ) - { - #ifdef TIVO_SUPPORT - decodeString(item, 1); - #endif - val = item; - key = strsep(&val, "="); - DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); - if( strcasecmp(key, "width") == 0 ) - { - width = atoi(val); - } - else if( strcasecmp(key, "height") == 0 ) + n = send(h->socket, header, strlen(header), 0); + if(n<0) { - height = atoi(val); - } - /* Not implemented yet * - else if( strcasecmp(key, "rotation") == 0 ) + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) { - rotation = atoi(val); + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); } - else if( strcasecmp(key, "pixelshape") == 0 ) - { - pixelshape = val; - } */ - item = strtok_r(NULL, "&,", &saveptr); - } - free(path); - - if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) - { - DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n"); - Send406(h); - goto resized_error; - } - - DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path); - - /* Figure out the best destination resolution we can use */ - 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, "SM"); - else if( dstw <= 1024 && dsth <= 768 ) - strcpy(dlna_pn, "MED"); - else - strcpy(dlna_pn, "LRG"); - - if( srcw>>3 >= dstw && srch>>3 >= dsth) - scale = 8; - else if( srcw>>2 >= dstw && srch>>2 >= dsth ) - scale = 4; - else if( srcw>>1 >= dstw && srch>>1 >= dsth ) - scale = 2; - - strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); - snprintf(header, sizeof(header)-100, "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: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n" - "Server: " MINIDLNA_SERVER_STRING "\r\n", - date, dlna_pn); - if( h->reqflags & FLAG_XFERINTERACTIVE ) - { - strcat(header, "transferMode.dlna.org: Interactive\r\n"); - } - else if( h->reqflags & FLAG_XFERBACKGROUND ) - { - strcat(header, "transferMode.dlna.org: Background\r\n"); - } - - /* Resizing from a thumbnail is much faster than from a large image */ -#ifdef __sparc__ - tn = result[5]; - if( dstw <= 160 && dsth <= 120 && atoi(tn) ) - { - l = exif_loader_new(); - exif_loader_write_file(l, file_path); - ed = exif_loader_get_data(l); - exif_loader_unref(l); - - if( !ed || !ed->size ) - { - if( ed ) - exif_data_unref(ed); - DPRINTF(E_WARN, L_HTTP, "Unable to access image thumbnail!\n"); - Send500(h); - goto resized_error; - } - imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1); - exif_data_unref(ed); - } - else -#endif - if( strcmp(h->HttpVer, "HTTP/1.0") == 0 ) - { - imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale); - } - else - { - chunked = 1; - strcat(header, "Transfer-Encoding: chunked\r\n\r\n"); - } - - if( !chunked ) - { - if( !imsrc ) + if( h->req_command == EHead ) { - DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); - Send500(h); - goto resized_error; + goto error; } - imdst = image_resize(imsrc, dstw, dsth); - data = image_save_to_jpeg_buf(imdst, &size); - - sprintf(str_buf, "Content-Length: %d\r\n\r\n", size); - strcat(header, str_buf); - } - - if( (send_data(h, header, strlen(header), 0) == 0) && (h->req_command != EHead) ) - { - if( chunked ) + n = send(h->socket, data, size, 0); + if(n<0) { - imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale); - 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(str_buf, "%x\r\n", size); - send_data(h, str_buf, ret, MSG_MORE); - send_data(h, (char *)data, size, MSG_MORE); - send_data(h, "\r\n0\r\n\r\n", 7, 0); - } - else + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) { - send_data(h, (char *)data, size, 0); + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); } + gdFree(data); } - DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); - if( imsrc ) - image_free(imsrc); - if( imdst ) - image_free(imdst); - resized_error: + error: + gdImageDestroy(imsrc); + gdImageDestroy(imdst); sqlite3_free_table(result); -#if USE_FORK - if( !newpid ) - _exit(0); -#endif } +#endif void SendResp_dlnafile(struct upnphttp * h, char * object) @@ -1694,149 +1061,96 @@ SendResp_dlnafile(struct upnphttp * h, char * object) char hdr_buf[512]; char sql_buf[256]; char **result; - int rows, ret; + int rows; char date[30]; time_t curtime = time(NULL); - off_t total, offset, size; - sqlite_int64 id; - int sendfh; - static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[96]; } last_file = { 0 }; -#if USE_FORK - pid_t newpid = 0; -#endif + off_t total; + char *path, *mime, *dlna; + + memset(header, 0, 1500); - id = strtoll(object, NULL, 10); - if( id != last_file.id ) + sprintf(sql_buf, "SELECT o.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); + if( !rows ) { - sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id); - ret = sql_get_table(db, sql_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 ) - { - DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); - sqlite3_free_table(result); - Send404(h); - return; - } - /* Cache the result */ - last_file.id = id; - 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); - /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ - if( h->req_client == ESamsungTV ) - { - if( strcmp(last_file.mime+6, "x-matroska") == 0 ) - strcpy(last_file.mime+8, "mkv"); - } - /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */ - 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"); - } - } - else - { - last_file.mime[0] = '\0'; - } - if( result[5] ) - snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]); - else if( h->reqflags & FLAG_DLNA ) - strcpy(last_file.dlna, dlna_no_conv); - else - last_file.dlna[0] = '\0'; - sqlite3_free_table(result); + syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); + Send404(h); + goto error; } -#if USE_FORK - newpid = fork(); - if( newpid ) - return; -#endif - DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path); + path = result[3]; + mime = result[4]; + dlna = result[5]; + printf("ObjectId: %s [%s]\n", object, path); if( h->reqflags & FLAG_XFERSTREAMING ) { - if( strncmp(last_file.mime, "image", 5) == 0 ) + if( strncmp(mime, "imag", 4) == 0 ) { - DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); Send406(h); goto error; } } - else if( h->reqflags & FLAG_XFERINTERACTIVE ) + if( h->reqflags & FLAG_XFERINTERACTIVE ) { if( h->reqflags & FLAG_REALTIMEINFO ) { - DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n"); + syslog(LOG_NOTICE, "Bad realTimeInfo flag with Interactive request!"); Send400(h); goto error; } - if( strncmp(last_file.mime, "image", 5) != 0 ) + if( strncmp(mime, "imag", 4) != 0 ) { - DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n"); - /* Samsung TVs (well, at least the A950) do this for some reason, - * and I don't see them fixing this bug any time soon. */ - if( h->req_client != ESamsungTV || GETFLAG(DLNA_STRICT_MASK) ) - { - Send406(h); - goto error; - } + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Interactive without an image!"); + Send406(h); + goto error; } } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); - offset = h->req_RangeStart; - sendfh = open(last_file.path, O_RDONLY); + off_t offset = h->req_RangeStart; + int sendfh = open(path, O_RDONLY); if( sendfh < 0 ) { - DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path); + printf("Error opening %s\n", result[2]); goto error; } - size = lseek(sendfh, 0, SEEK_END); + off_t size = lseek(sendfh, 0, SEEK_END); lseek(sendfh, 0, SEEK_SET); - sprintf(header, "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; if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) ) { - DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n"); + syslog(LOG_NOTICE, "Specified range was invalid!"); Send400(h); close(sendfh); goto error; } if( h->req_RangeEnd > size ) { - DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n"); + syslog(LOG_NOTICE, "Specified range was outside file boundaries!"); Send416(h); close(sendfh); goto error; } - if( h->req_RangeEnd < size ) + sprintf(hdr_buf, "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n", mime); + strcpy(header, hdr_buf); + if( h->req_RangeEnd && (h->req_RangeEnd < size) ) { total = h->req_RangeEnd - h->req_RangeStart + 1; - sprintf(hdr_buf, "Content-Length: %jd\r\n" - "Content-Range: bytes %jd-%jd/%jd\r\n", + sprintf(hdr_buf, "Content-Length: %llu\r\n" + "Content-Range: bytes %lld-%lld/%llu\r\n", total, h->req_RangeStart, h->req_RangeEnd, size); } else { h->req_RangeEnd = size; total = size - h->req_RangeStart; - sprintf(hdr_buf, "Content-Length: %jd\r\n" - "Content-Range: bytes %jd-%jd/%jd\r\n", + sprintf(hdr_buf, "Content-Length: %llu\r\n" + "Content-Range: bytes %lld-%llu/%llu\r\n", total, h->req_RangeStart, size-1, size); } } @@ -1844,7 +1158,11 @@ SendResp_dlnafile(struct upnphttp * h, char * object) { h->req_RangeEnd = size; total = size; - sprintf(hdr_buf, "Content-Length: %jd\r\n", total); + sprintf(hdr_buf, "%s 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %llu\r\n", + "HTTP/1.1", mime, total); + //h->HttpVer, mime, total); } strcat(header, hdr_buf); @@ -1854,52 +1172,55 @@ SendResp_dlnafile(struct upnphttp * h, char * object) } else if( h->reqflags & FLAG_XFERBACKGROUND ) { - if( strncmp(last_file.mime, "image", 5) == 0 ) + if( strncmp(mime, "imag", 4) == 0 ) strcat(header, "transferMode.dlna.org: Background\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { - if( (strncmp(last_file.mime, "video", 5) == 0) || - (strncmp(last_file.mime, "audio", 5) == 0) ) - { - strcat(header, "transferMode.dlna.org: Streaming\r\n"); - } - else - { - strcat(header, "transferMode.dlna.org: Interactive\r\n"); - } - } - - if( h->reqflags & FLAG_CAPTION ) - { - if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 ) - { - sprintf(hdr_buf, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n", - lan_addr[0].str, runtime_vars.port, id); - strcat(header, hdr_buf); - } + strcat(header, "transferMode.dlna.org: Interactive\r\n"); } sprintf(hdr_buf, "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: %s\r\n" - "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", - date, last_file.dlna); + "contentFeatures.dlna.org: DLNA.ORG_PN=%s\r\n" + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n\r\n", + date, dlna); strcat(header, hdr_buf); - if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) ) + int n; + n = send(h->socket, header, strlen(header), 0); + if(n<0) { - send_file(h, sendfh, offset, h->req_RangeEnd); + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); } - close(sendfh); + if( h->req_command == EHead ) + { + close(sendfh); + } + else if( sendfh > 0 ) + { + while( offset < h->req_RangeEnd ) { + int ret = sendfile(h->socket, sendfh, &offset, (h->req_RangeEnd - offset + 1)); + if( ret == -1 ) { + printf("sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); + if( errno == 32 || errno == 9 || errno == 54 || errno == 104 ) + break; + } + else { + printf("sent %d bytes to %d. offset is now %d.\n", ret, h->socket, (int)offset); + } + } + close(sendfh); + } error: -#if USE_FORK - if( !newpid ) - _exit(0); -#endif - return; + sqlite3_free_table(result); } @@ -1,42 +1,19 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __UPNPHTTP_H__ #define __UPNPHTTP_H__ #include <netinet/in.h> #include <sys/queue.h> -#include "minidlnatypes.h" #include "config.h" /* server: HTTP header returned in all HTTP responses : */ -#define MINIDLNA_SERVER_STRING OS_VERSION " DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.0" +#define MINIUPNPD_SERVER_STRING OS_VERSION " UPnP/1.0 miniupnpd/1.0" /* states : @@ -65,19 +42,20 @@ struct upnphttp { int req_contentlen; int req_contentoff; /* header length */ enum httpCommands req_command; - enum client_types req_client; const char * req_soapAction; int req_soapActionLen; +#ifdef ENABLE_EVENTS const char * req_Callback; /* For SUBSCRIBE */ int req_CallbackLen; int req_Timeout; const char * req_SID; /* For UNSUBSCRIBE */ int req_SIDLen; - off_t req_RangeStart; - off_t req_RangeEnd; +#endif + long long int req_RangeStart; + long long int req_RangeEnd; long int req_chunklen; - uint32_t reqflags; - uint32_t respflags; + int reqflags; + int respflags; /* response */ char * res_buf; int res_buflen; @@ -87,32 +65,20 @@ struct upnphttp { LIST_ENTRY(upnphttp) entries; }; -#define FLAG_TIMEOUT 0x00000001 -#define FLAG_SID 0x00000002 -#define FLAG_RANGE 0x00000004 -#define FLAG_HOST 0x00000008 - -#define FLAG_HTML 0x00000080 -#define FLAG_INVALID_REQ 0x00000010 - -#define FLAG_CHUNKED 0x00000100 -#define FLAG_TIMESEEK 0x00000200 -#define FLAG_REALTIMEINFO 0x00000400 -#define FLAG_PLAYSPEED 0x00000800 -#define FLAG_XFERSTREAMING 0x00001000 -#define FLAG_XFERINTERACTIVE 0x00002000 -#define FLAG_XFERBACKGROUND 0x00004000 -#define FLAG_CAPTION 0x00008000 - -#define FLAG_DLNA 0x00100000 -#define FLAG_MIME_AVI_DIVX 0x00200000 -#define FLAG_MIME_AVI_AVI 0x00400000 -#define FLAG_MIME_FLAC_FLAC 0x00800000 -#define FLAG_NO_RESIZE 0x01000000 -#define FLAG_MS_PFS 0x02000000 // Microsoft PlaysForSure client -#define FLAG_AUDIO_ONLY 0x04000000 - -#define FLAG_FREE_OBJECT_ID 0x00000001 +#define FLAG_TIMEOUT 0x01 +#define FLAG_SID 0x02 +#define FLAG_RANGE 0x04 +#define FLAG_HOST 0x08 + +#define FLAG_HTML 0x80 +#define FLAG_INVALID_REQ 0x10 + +#define FLAG_CHUNKED 0x0100 +#define FLAG_TIMESEEK 0x0200 +#define FLAG_REALTIMEINFO 0x0400 +#define FLAG_XFERSTREAMING 0x1000 +#define FLAG_XFERINTERACTIVE 0x2000 +#define FLAG_XFERBACKGROUND 0x4000 /* New_upnphttp() */ struct upnphttp * @@ -151,25 +117,10 @@ BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen); -/* Error messages */ -void -Send500(struct upnphttp *); -void -Send501(struct upnphttp *); - /* SendResp_upnphttp() */ void SendResp_upnphttp(struct upnphttp *); -int -SearchClientCache(struct in_addr addr, int quiet); - -void -SendResp_icon(struct upnphttp *, char * url); -void -SendResp_albumArt(struct upnphttp *, char * url); -void -SendResp_caption(struct upnphttp *, char * url); void SendResp_resizedimg(struct upnphttp *, char * url); void diff --git a/upnpreplyparse.c b/upnpreplyparse.c index 5bf5311..b19915a 100644 --- a/upnpreplyparse.c +++ b/upnpreplyparse.c @@ -1,32 +1,10 @@ /* $Id$ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdlib.h> #include <string.h> #include <stdio.h> @@ -38,21 +16,10 @@ static void NameValueParserStartElt(void * d, const char * name, int l) { struct NameValueParserData * data = (struct NameValueParserData *)d; - if(l>63) - l = 63; + if(l>511) + l = 511; memcpy(data->curelt, name, l); data->curelt[l] = '\0'; - - /* store root element */ - if(!data->head.lh_first) - { - struct NameValue * nv; - nv = malloc(sizeof(struct NameValue)); - strcpy(nv->name, "rootElement"); - memcpy(nv->value, name, l); - nv->value[l] = '\0'; - LIST_INSERT_HEAD( &(data->head), nv, entries); - } } static void @@ -63,8 +30,8 @@ NameValueParserGetData(void * d, const char * datas, int l) nv = malloc(sizeof(struct NameValue)); if(l>511) l = 511; - strncpy(nv->name, data->curelt, 64); - nv->name[63] = '\0'; + strncpy(nv->name, data->curelt, 512); + nv->name[511] = '\0'; memcpy(nv->value, datas, l); nv->value[l] = '\0'; LIST_INSERT_HEAD( &(data->head), nv, entries); @@ -83,7 +50,7 @@ ParseNameValue(const char * buffer, int bufsize, parser.starteltfunc = NameValueParserStartElt; parser.endeltfunc = 0; parser.datafunc = NameValueParserGetData; - parser.attfunc = 0; + parser.attfunc = 0; parsexml(&parser); } @@ -114,6 +81,31 @@ GetValueFromNameValueList(struct NameValueParserData * pdata, return p; } +#if 0 +/* useless now that minixml ignores namespaces by itself */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + char * pname; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + pname = strrchr(nv->name, ':'); + if(pname) + pname++; + else + pname = nv->name; + if(strcmp(pname, Name)==0) + p = nv->value; + } + return p; +} +#endif + /* debug all-in-one function * do parsing then display to stdout */ #ifdef DEBUG diff --git a/upnpreplyparse.h b/upnpreplyparse.h index 34f0473..8ddb04a 100644 --- a/upnpreplyparse.h +++ b/upnpreplyparse.h @@ -1,31 +1,9 @@ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * - * Copyright (c) 2006, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __UPNPREPLYPARSE_H__ #define __UPNPREPLYPARSE_H__ @@ -1,51 +1,10 @@ -/* MiniDLNA project - * - * http://sourceforge.net/projects/minidlna/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - * - * Portions of the code from the MiniUPnP project: - * - * Copyright (c) 2006-2007, Thomas Bernard - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ +/* $Id$ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2008 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -53,29 +12,29 @@ #include <unistd.h> #include <dirent.h> #include <sys/stat.h> +#include <syslog.h> #include <sys/types.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netdb.h> -#include <ctype.h> #include "config.h" #include "upnpglobalvars.h" -#include "utils.h" #include "upnphttp.h" #include "upnpsoap.h" #include "upnpreplyparse.h" #include "getifaddr.h" -#include "scanner.h" -#include "sql.h" -#include "log.h" + +#include <ctype.h> +#include "metadata.h" +#include <sqlite3.h> static void BuildSendAndCloseSoapResp(struct upnphttp * h, const char * body, int bodylen) { static const char beforebody[] = - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + "<?xml version=\"1.0\"?>\r\n" "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<s:Body>"; @@ -101,6 +60,28 @@ BuildSendAndCloseSoapResp(struct upnphttp * h, } static void +GetStatusInfo(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "<u:%sResponse " + "xmlns:u=\"%s\">" + "<NewConnectionStatus>Connected</NewConnectionStatus>" + "<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>" + "<NewUptime>%ld</NewUptime>" + "</u:%sResponse>"; + + char body[512]; + int bodylen; + time_t uptime; + + uptime = (time(NULL) - startup_time); + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:WANIPConnection:1", + (long)uptime, action); + BuildSendAndCloseSoapResp(h, body, bodylen); +} + +static void GetSystemUpdateID(struct upnphttp * h, const char * action) { static const char resp[] = @@ -114,7 +95,7 @@ GetSystemUpdateID(struct upnphttp * h, const char * action) bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", - updateID, action); + 1, action); BuildSendAndCloseSoapResp(h, body, bodylen); } @@ -143,19 +124,44 @@ GetProtocolInfo(struct upnphttp * h, const char * action) "<u:%sResponse " "xmlns:u=\"%s\">" "<Source>" - RESOURCE_PROTOCOL_INFO_VALUES + /*"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01,"*/ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," + "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," + "http-get:*:audio/x-ms-wma:*," + "http-get:*:audio/wav:*," + "http-get:*:audio/mp4:*," + "http-get:*:audio/x-aiff:*," + "http-get:*:audio/x-flac:*," + "http-get:*:application/ogg:*," + "http-get:*:image/jpeg:*," + "http-get:*:image/gif:*," + "http-get:*:audio/x-mpegurl:*," + "http-get:*:video/mpeg:*," + "http-get:*:video/x-msvideo:*," + "http-get:*:video/avi:*," + "http-get:*:video/mpeg2:*," + "http-get:*:video/dvd:*," + "http-get:*:video/x-ms-wmv:*" "</Source>" "<Sink></Sink>" "</u:%sResponse>"; - char * body; + + char body[1536]; int bodylen; - bodylen = asprintf(&body, resp, + bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); - free(body); } static void @@ -164,12 +170,7 @@ GetSortCapabilities(struct upnphttp * h, const char * action) static const char resp[] = "<u:%sResponse " "xmlns:u=\"%s\">" - "<SortCaps>" - "dc:title," - "dc:date," - "upnp:class," - "upnp:originalTrackNumber" - "</SortCaps>" + "<SortCaps></SortCaps>" "</u:%sResponse>"; char body[512]; @@ -185,17 +186,9 @@ static void GetSearchCapabilities(struct upnphttp * h, const char * action) { static const char resp[] = - "<u:%sResponse xmlns:u=\"%s\">" - "<SearchCaps>" - "dc:creator," - "dc:title," - "upnp:album," - "upnp:actor," - "upnp:artist," - "upnp:class," - "upnp:genre," - "@refID" - "</SearchCaps>" + "<u:%sResponse " + "xmlns:u=\"%s\">" + "<SearchCaps>dc:title,dc:creator,upnp:class,upnp:artist,upnp:album,@refID</SearchCaps>" "</u:%sResponse>"; char body[512]; @@ -214,7 +207,7 @@ GetCurrentConnectionIDs(struct upnphttp * h, const char * action) static const char resp[] = "<u:%sResponse " "xmlns:u=\"%s\">" - "<ConnectionIDs>0</ConnectionIDs>" + "<ConnectionIDs>-1</ConnectionIDs>" "</u:%sResponse>"; char body[512]; @@ -235,11 +228,13 @@ GetCurrentConnectionInfo(struct upnphttp * h, const char * action) "xmlns:u=\"%s\">" "<RcsID>-1</RcsID>" "<AVTransportID>-1</AVTransportID>" - "<ProtocolInfo></ProtocolInfo>" - "<PeerConnectionManager></PeerConnectionManager>" + "<ProtocolInfo>" + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," + "</ProtocolInfo>" + "<PeerConnectionManager>0</PeerConnectionManager>" "<PeerConnectionID>-1</PeerConnectionID>" - "<Direction>Output</Direction>" - "<Status>Unknown</Status>" + "<Direction>0</Direction>" + "<Status>0</Status>" "</u:%sResponse>"; char body[sizeof(resp)+128]; @@ -251,686 +246,149 @@ GetCurrentConnectionInfo(struct upnphttp * h, const char * action) BuildSendAndCloseSoapResp(h, body, bodylen); } -static void -mime_to_ext(const char * mime, char * buf) -{ - switch( *mime ) - { - /* Audio extensions */ - case 'a': - if( strcmp(mime+6, "mpeg") == 0 ) - strcpy(buf, "mp3"); - else if( strcmp(mime+6, "mp4") == 0 ) - strcpy(buf, "m4a"); - else if( strcmp(mime+6, "x-ms-wma") == 0 ) - strcpy(buf, "wma"); - else if( strcmp(mime+6, "x-flac") == 0 ) - strcpy(buf, "flac"); - else if( strcmp(mime+6, "flac") == 0 ) - strcpy(buf, "flac"); - else if( strcmp(mime+6, "x-wav") == 0 ) - strcpy(buf, "wav"); - else if( strncmp(mime+6, "L16", 3) == 0 ) - strcpy(buf, "pcm"); - else if( strcmp(mime+6, "3gpp") == 0 ) - strcpy(buf, "3gp"); - else if( strcmp(mime, "application/ogg") == 0 ) - strcpy(buf, "ogg"); - else - strcpy(buf, "dat"); - break; - case 'v': - if( strcmp(mime+6, "avi") == 0 ) - strcpy(buf, "avi"); - else if( strcmp(mime+6, "divx") == 0 ) - strcpy(buf, "avi"); - else if( strcmp(mime+6, "x-msvideo") == 0 ) - strcpy(buf, "avi"); - else if( strcmp(mime+6, "mpeg") == 0 ) - strcpy(buf, "mpg"); - else if( strcmp(mime+6, "mp4") == 0 ) - strcpy(buf, "mp4"); - else if( strcmp(mime+6, "x-ms-wmv") == 0 ) - strcpy(buf, "wmv"); - else if( strcmp(mime+6, "x-matroska") == 0 ) - strcpy(buf, "mkv"); - else if( strcmp(mime+6, "x-mkv") == 0 ) - strcpy(buf, "mkv"); - else if( strcmp(mime+6, "x-flv") == 0 ) - strcpy(buf, "flv"); - else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 ) - strcpy(buf, "mpg"); - else if( strcmp(mime+6, "quicktime") == 0 ) - strcpy(buf, "mov"); - else if( strcmp(mime+6, "3gpp") == 0 ) - strcpy(buf, "3gp"); - else if( strcmp(mime+6, "x-tivo-mpeg") == 0 ) - strcpy(buf, "TiVo"); - else - strcpy(buf, "dat"); - break; - case 'i': - if( strcmp(mime+6, "jpeg") == 0 ) - strcpy(buf, "jpg"); - else if( strcmp(mime+6, "png") == 0 ) - strcpy(buf, "png"); - else - strcpy(buf, "dat"); - break; - default: - strcpy(buf, "dat"); - break; - } -} - -#define FILTER_CHILDCOUNT 0x00000001 -#define FILTER_DC_CREATOR 0x00000002 -#define FILTER_DC_DATE 0x00000004 -#define FILTER_DC_DESCRIPTION 0x00000008 -#define FILTER_DLNA_NAMESPACE 0x00000010 -#define FILTER_REFID 0x00000020 -#define FILTER_RES 0x00000040 -#define FILTER_RES_BITRATE 0x00000080 -#define FILTER_RES_DURATION 0x00000100 -#define FILTER_RES_NRAUDIOCHANNELS 0x00000200 -#define FILTER_RES_RESOLUTION 0x00000400 -#define FILTER_RES_SAMPLEFREQUENCY 0x00000800 -#define FILTER_RES_SIZE 0x00001000 -#define FILTER_UPNP_ACTOR 0x00002000 -#define FILTER_UPNP_ALBUM 0x00004000 -#define FILTER_UPNP_ALBUMARTURI 0x00008000 -#define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00010000 -#define FILTER_UPNP_ARTIST 0x00020000 -#define FILTER_UPNP_GENRE 0x00040000 -#define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00080000 -#define FILTER_UPNP_SEARCHCLASS 0x00100000 - -static u_int32_t -set_filter_flags(char * filter, enum client_types client) -{ - char *item, *saveptr = NULL; - u_int32_t flags = 0; - - if( !filter || (strlen(filter) <= 1) ) - return 0xFFFFFFFF; - if( client == ESamsungTV ) - flags |= FILTER_DLNA_NAMESPACE; - item = strtok_r(filter, ",", &saveptr); - while( item != NULL ) - { - if( saveptr ) - *(item-1) = ','; - while( isspace(*item) ) - item++; - if( strcmp(item, "@childCount") == 0 ) - { - flags |= FILTER_CHILDCOUNT; - } - else if( strcmp(item, "dc:creator") == 0 ) - { - flags |= FILTER_DC_CREATOR; - } - else if( strcmp(item, "dc:date") == 0 ) - { - flags |= FILTER_DC_DATE; - } - else if( strcmp(item, "dc:description") == 0 ) - { - flags |= FILTER_DC_DESCRIPTION; - } - else if( strcmp(item, "dlna") == 0 ) - { - flags |= FILTER_DLNA_NAMESPACE; - } - else if( strcmp(item, "@refID") == 0 ) - { - flags |= FILTER_REFID; - } - else if( strcmp(item, "upnp:album") == 0 ) - { - flags |= FILTER_UPNP_ALBUM; - } - else if( strcmp(item, "upnp:albumArtURI") == 0 ) - { - flags |= FILTER_UPNP_ALBUMARTURI; - if( client == ESamsungTV ) - flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID; - } - else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 ) - { - flags |= FILTER_UPNP_ALBUMARTURI; - flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID; - } - else if( strcmp(item, "upnp:artist") == 0 ) - { - flags |= FILTER_UPNP_ARTIST; - } - else if( strcmp(item, "upnp:actor") == 0 ) - { - flags |= FILTER_UPNP_ACTOR; - } - else if( strcmp(item, "upnp:genre") == 0 ) - { - flags |= FILTER_UPNP_GENRE; - } - else if( strcmp(item, "upnp:originalTrackNumber") == 0 ) - { - flags |= FILTER_UPNP_ORIGINALTRACKNUMBER; - } - else if( strcmp(item, "upnp:searchClass") == 0 ) - { - flags |= FILTER_UPNP_SEARCHCLASS; - } - else if( strcmp(item, "res") == 0 ) - { - flags |= FILTER_RES; - } - else if( (strcmp(item, "res@bitrate") == 0) || - (strcmp(item, "@bitrate") == 0) || - ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) ) - { - flags |= FILTER_RES; - flags |= FILTER_RES_BITRATE; - } - else if( (strcmp(item, "res@duration") == 0) || - (strcmp(item, "@duration") == 0) || - ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) ) - { - flags |= FILTER_RES; - flags |= FILTER_RES_DURATION; - } - else if( (strcmp(item, "res@nrAudioChannels") == 0) || - (strcmp(item, "@nrAudioChannels") == 0) || - ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) ) - { - flags |= FILTER_RES; - flags |= FILTER_RES_NRAUDIOCHANNELS; - } - else if( (strcmp(item, "res@resolution") == 0) || - (strcmp(item, "@resolution") == 0) || - ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) ) - { - flags |= FILTER_RES; - flags |= FILTER_RES_RESOLUTION; - } - else if( (strcmp(item, "res@sampleFrequency") == 0) || - (strcmp(item, "@sampleFrequency") == 0) || - ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) ) - { - flags |= FILTER_RES; - flags |= FILTER_RES_SAMPLEFREQUENCY; - } - else if( (strcmp(item, "res@size") == 0) || - (strcmp(item, "@size") == 0) || - (strcmp(item, "size") == 0) ) - { - flags |= FILTER_RES; - flags |= FILTER_RES_SIZE; - } - item = strtok_r(NULL, ",", &saveptr); - } - - return flags; -} - -char * -parse_sort_criteria(char * sortCriteria, int * error) +static int callback(void *args, int argc, char **argv, char **azColName) { - char *order = NULL; - char *item, *saveptr; - int i, ret, reverse, title_sorted = 0; - *error = 0; - - if( !sortCriteria ) - return NULL; - - if( (item = strtok_r(sortCriteria, ",", &saveptr)) ) - { - order = malloc(4096); - strcpy(order, "order by "); - } - for( i=0; item != NULL; i++ ) - { - reverse=0; - if( i ) - strcat(order, ", "); - if( *item == '+' ) - { - item++; - } - else if( *item == '-' ) - { - reverse = 1; - item++; - } - if( strcasecmp(item, "upnp:class") == 0 ) - { - strcat(order, "o.CLASS"); - } - else if( strcasecmp(item, "dc:title") == 0 ) - { - strcat(order, "d.TITLE"); - title_sorted = 1; - } - else if( strcasecmp(item, "dc:date") == 0 ) - { - strcat(order, "d.DATE"); - } - else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 ) - { - strcat(order, "d.DISC, d.TRACK"); - } - else - { - printf("Unhandled SortCriteria [%s]\n", item); - *error = 1; - if( i ) - { - ret = strlen(order); - order[ret-2] = '\0'; - } - i--; - goto unhandled_order; - } - - if( reverse ) - strcat(order, " DESC"); - unhandled_order: - item = strtok_r(NULL, ",", &saveptr); - } - if( i <= 0 ) - { - free(order); - return NULL; - } - /* Add a "tiebreaker" sort order */ - if( !title_sorted ) - strcat(order, ", TITLE ASC"); - - return order; -} - -inline static void -add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn, - char *detailID, struct Response *passed_args) -{ - int dstw = reqw; - int dsth = reqh; - - if( passed_args->flags & FLAG_NO_RESIZE ) - return; - - strcatf(passed_args->str, "<res "); - if( passed_args->filter & FILTER_RES_RESOLUTION ) - { - dstw = reqw; - dsth = ((((reqw<<10)/srcw)*srch)>>10); - if( dsth > reqh ) { - dsth = reqh; - dstw = (((reqh<<10)/srch) * srcw>>10); - } - strcatf(passed_args->str, "resolution=\"%dx%d\" ", dstw, dsth); - } - strcatf(passed_args->str, "protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=%s;DLNA.ORG_CI=1\">" - "http://%s:%d/Resized/%s.jpg?width=%d,height=%d" - "</res>", - dlna_pn, lan_addr[0].str, runtime_vars.port, detailID, dstw, dsth); -} - -inline static void -add_res(char *size, char *duration, char *bitrate, char *sampleFrequency, - char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime, - char *detailID, char *ext, struct Response *passed_args) -{ - strcatf(passed_args->str, "<res "); - if( size && (passed_args->filter & FILTER_RES_SIZE) ) { - strcatf(passed_args->str, "size=\"%s\" ", size); - } - if( duration && (passed_args->filter & FILTER_RES_DURATION) ) { - strcatf(passed_args->str, "duration=\"%s\" ", duration); - } - if( bitrate && (passed_args->filter & FILTER_RES_BITRATE) ) { - strcatf(passed_args->str, "bitrate=\"%s\" ", bitrate); - } - if( sampleFrequency && (passed_args->filter & FILTER_RES_SAMPLEFREQUENCY) ) { - strcatf(passed_args->str, "sampleFrequency=\"%s\" ", sampleFrequency); - } - if( nrAudioChannels && (passed_args->filter & FILTER_RES_NRAUDIOCHANNELS) ) { - strcatf(passed_args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels); - } - if( resolution && (passed_args->filter & FILTER_RES_RESOLUTION) ) { - strcatf(passed_args->str, "resolution=\"%s\" ", resolution); - } - strcatf(passed_args->str, "protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:%d/MediaItems/%s.%s" - "</res>", - mime, dlna_pn, lan_addr[0].str, - runtime_vars.port, detailID, ext); -} + struct Response { char *resp; int returned; int requested; int total; char *filter; } *passed_args = (struct Response *)args; + char *id = argv[1], *parent = argv[2], *refID = argv[3], *class = argv[4], *name = argv[7], *size = argv[9], + *title = argv[10], *duration = argv[11], *bitrate = argv[12], *sampleFrequency = argv[13], + *artist = argv[14], *album = argv[15], *genre = argv[16], *comment = argv[17], *nrAudioChannels = argv[18], + *track = argv[19], *date = argv[20], *width = argv[21], *height = argv[22], *tn = argv[23], + *creator = argv[24], *dlna_pn = argv[25], *mime = argv[26]; + char dlna_buf[64]; + char str_buf[4096]; + //char * str_buf = malloc(4096); + char **result; + int ret; -#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, o.DETAIL_ID, o.CLASS," \ - " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \ - " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \ - " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC " + passed_args->total++; -static int -callback(void *args, int argc, char **argv, char **azColName) -{ - struct Response *passed_args = (struct Response *)args; - char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6], - *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11], - *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17], - *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22]; - char dlna_buf[96]; - char ext[5]; - struct string_s *str = passed_args->str; - int ret = 0; - - /* Make sure we have at least 8KB left of allocated memory to finish the response. */ - if( str->off > (str->size - 8192) ) - { -#if MAX_RESPONSE_SIZE > 0 - if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE ) - { -#endif - str->data = realloc(str->data, (str->off+DEFAULT_RESP_SIZE)); - if( str->data ) - { - str->size += DEFAULT_RESP_SIZE; - DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %d. [%d results so far]\n", - str->size, passed_args->returned); - } - else - { - DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n"); - return -1; - } -#if MAX_RESPONSE_SIZE > 0 - } - else - { - DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response cut short, to not exceed the max response size [%lld]!\n", (long long int)MAX_RESPONSE_SIZE); - return -1; - } -#endif - } + if( passed_args->requested && (passed_args->returned >= passed_args->requested) ) + return 0; + //if( (strncmp(class, "item", 4) == 0) && !mime ) // Useless listing if there is no MIME type + // return 0; passed_args->returned++; if( dlna_pn ) + //sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", dlna_pn); sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn); - else if( passed_args->flags & FLAG_DLNA ) - strcpy(dlna_buf, dlna_no_conv); else strcpy(dlna_buf, "*"); + /*for(i=0; i<argc; i++){ + printf("%s = %s\n", azColName[i], argv[i]); + }*/ + //printf("\n"); if( strncmp(class, "item", 4) == 0 ) { - /* We may need special handling for certain MIME types */ - if( *mime == 'v' ) - { - if( passed_args->flags & FLAG_MIME_AVI_DIVX ) - { - if( strcmp(mime, "video/x-msvideo") == 0 ) - { - if( creator ) - strcpy(mime+6, "divx"); - else - strcpy(mime+6, "avi"); - } + sprintf(str_buf, "<item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent); + strcat(passed_args->resp, str_buf); + if( refID && (!passed_args->filter || strstr(passed_args->filter, "@refID")) ) { + sprintf(str_buf, " refID=\"%s\"", refID); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, ">" + "<dc:title>%s</dc:title>" + "<upnp:class>object.%s</upnp:class>", + title?title:name, class); + strcat(passed_args->resp, str_buf); + if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) { + sprintf(str_buf, "<dc:description>%s</dc:description>", comment); + strcat(passed_args->resp, str_buf); + } + if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) { + sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator); + strcat(passed_args->resp, str_buf); + } + if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) { + sprintf(str_buf, "<dc:date>%s</dc:date>", date); + strcat(passed_args->resp, str_buf); + } + if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) { + sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", artist); + strcat(passed_args->resp, str_buf); + } + if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) { + sprintf(str_buf, "<upnp:album>%s</upnp:album>", album); + strcat(passed_args->resp, str_buf); + } + if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) { + sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre); + strcat(passed_args->resp, str_buf); + } + if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) { + sprintf(str_buf, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track); + strcat(passed_args->resp, str_buf); + } + if( !passed_args->filter || strstr(passed_args->filter, "res") ) { + strcat(passed_args->resp, "<res "); + if( size && (!passed_args->filter || strstr(passed_args->filter, "res@size")) ) { + sprintf(str_buf, "size=\"%s\" ", size); + strcat(passed_args->resp, str_buf); } - else if( passed_args->flags & FLAG_MIME_AVI_AVI ) - { - if( strcmp(mime, "video/x-msvideo") == 0 ) - { - strcpy(mime+6, "avi"); - } + if( duration && (!passed_args->filter || strstr(passed_args->filter, "res@duration")) ) { + sprintf(str_buf, "duration=\"%s\" ", duration); + strcat(passed_args->resp, str_buf); } - if( !(passed_args->flags & FLAG_DLNA) ) - { - if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 ) - { - strcpy(mime+6, "mpeg"); - } + if( bitrate && (!passed_args->filter || strstr(passed_args->filter, "res@bitrate")) ) { + sprintf(str_buf, "bitrate=\"%s\" ", bitrate); + strcat(passed_args->resp, str_buf); } - /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ - if( passed_args->client == ESamsungTV ) - { - if( strcmp(mime+6, "x-matroska") == 0 ) - { - strcpy(mime+8, "mkv"); - } + if( sampleFrequency && (!passed_args->filter || strstr(passed_args->filter, "res@sampleFrequency")) ) { + sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency); + strcat(passed_args->resp, str_buf); } - } - else if( *mime == 'a' ) - { - if( strcmp(mime+6, "x-flac") == 0 ) - { - if( passed_args->flags & FLAG_MIME_FLAC_FLAC ) - { - strcpy(mime+6, "flac"); - } + if( nrAudioChannels && (!passed_args->filter || strstr(passed_args->filter, "res@nrAudioChannels")) ) { + sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels); + strcat(passed_args->resp, str_buf); } - } - - ret = strcatf(str, "<item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent); - if( refID && (passed_args->filter & FILTER_REFID) ) { - ret = strcatf(str, " refID=\"%s\"", refID); - } - ret = strcatf(str, ">" - "<dc:title>%s</dc:title>" - "<upnp:class>object.%s</upnp:class>", - title, class); - if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) { - ret = strcatf(str, "<dc:description>%.384s</dc:description>", comment); - } - if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { - ret = strcatf(str, "<dc:creator>%s</dc:creator>", creator); - } - if( date && (passed_args->filter & FILTER_DC_DATE) ) { - ret = strcatf(str, "<dc:date>%s</dc:date>", date); - } - if( artist ) { - if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) { - ret = strcatf(str, "<upnp:actor>%s</upnp:actor>", artist); + if( width && height && (!passed_args->filter || strstr(passed_args->filter, "res@resolution")) ) { + sprintf(str_buf, "resolution=\"%sx%s\" ", width, height); + strcat(passed_args->resp, str_buf); } - if( passed_args->filter & FILTER_UPNP_ARTIST ) { - ret = strcatf(str, "<upnp:artist>%s</upnp:artist>", artist); + sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" + "http://%s:5555/MediaItems/%s" + "</res>", + mime, dlna_buf, lan_addr[0].str, id); + #if 0 //JPEG_RESIZE + if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) { + strcat(passed_args->resp, str_buf); + sprintf(str_buf, "<res " + "protocolInfo=\"http-get:*:%s:%s\">" + "http://%s:5555/Resized/%s" + "</res>", + mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, id); } - } - if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) { - ret = strcatf(str, "<upnp:album>%s</upnp:album>", album); - } - if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { - ret = strcatf(str, "<upnp:genre>%s</upnp:genre>", genre); - } - if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { - track = strrchr(id, '$')+1; - } - if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) { - ret = strcatf(str, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track); - } - if( album_art && atoi(album_art) ) - { - /* Video and audio album art is handled differently */ - if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) { - ret = strcatf(str, "<res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\">" - "http://%s:%d/AlbumArt/%s-%s.jpg" - "</res>", - lan_addr[0].str, runtime_vars.port, album_art, detailID); - } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) { - ret = strcatf(str, "<upnp:albumArtURI"); - if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) { - ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""); - } - ret = strcatf(str, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>", - lan_addr[0].str, runtime_vars.port, album_art, detailID); + #endif + if( tn && atoi(tn) && dlna_pn ) { + strcat(passed_args->resp, str_buf); + strcat(passed_args->resp, "<res "); + sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" + "http://%s:5555/Thumbnails/%s" + "</res>", + mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, id); } + strcat(passed_args->resp, str_buf); } -#ifdef PFS_HACK - if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) { - ret = strcatf(str, "<upnp:album>%s</upnp:album>", "[No Keywords]"); - - if( tn && atoi(tn) ) { - ret = strcatf(str, "<upnp:albumArtURI>" - "http://%s:%d/Thumbnails/%s.jpg" - "</upnp:albumArtURI>", - lan_addr[0].str, runtime_vars.port, detailID); - } else { - ret = strcatf(str, "<upnp:albumArtURI>" - "http://%s:%d/Resized/%s.jpg?width=160,height=160" - "</upnp:albumArtURI>", - lan_addr[0].str, runtime_vars.port, detailID); - } - } -#endif - if( passed_args->filter & FILTER_RES ) { - mime_to_ext(mime, ext); - if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) { - ret = strcatf(str, "<res protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:%d/Thumbnails/%s.jpg" - "</res>", - mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, - runtime_vars.port, detailID); - } - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - if( (*mime == 'i') && (passed_args->client != EFreeBox) ) { -#if 1 //JPEG_RESIZE - int srcw = atoi(strsep(&resolution, "x")); - int srch = atoi(resolution); - if( !dlna_pn ) { - add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args); - } - if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) { - add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args); - } -#endif - if( tn && atoi(tn) ) { - ret = strcatf(str, "<res protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:%d/Thumbnails/%s.jpg" - "</res>", - mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, - runtime_vars.port, detailID); - } - } - else if( *mime == 'v' ) { - switch( passed_args->client ) { - case EToshibaTV: - if( dlna_pn && - (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 || - strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 || - strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 || - strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0)) - { - sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - } - break; - case ESonyBDP: - if( dlna_pn && - (strncmp(dlna_pn, "AVC_TS", 6) == 0 || - strncmp(dlna_pn, "MPEG_TS", 7) == 0) ) - { - if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 ) - { - sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - } - if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 ) - { - sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - } - } - else if( (dlna_pn && - (strncmp(dlna_pn, "AVC_MP4", 7) == 0 || - strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) || - strcmp(mime+6, "x-matroska") == 0 || - strcmp(mime+6, "x-msvideo") == 0 || - strcmp(mime+6, "mpeg") == 0 ) - { - strcpy(mime+6, "avi"); - if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 ) - { - sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - } - if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 ) - { - sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - } - } - break; - case ESonyBravia: - /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but - require profile to be renamed (applies to _T and _ISO variants also) */ - if( dlna_pn && - (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 || - strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 || - strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0)) - { - sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0"); - add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, - resolution, dlna_buf, mime, detailID, ext, passed_args); - } - break; - default: - break; - } - } - } - ret = strcatf(str, "</item>"); + strcpy(str_buf, "</item>"); } else if( strncmp(class, "container", 9) == 0 ) { - ret = strcatf(str, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent); - if( passed_args->filter & FILTER_CHILDCOUNT ) - { - int children; - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id); - children = (ret > 0) ? ret : 0; - ret = strcatf(str, "childCount=\"%d\"", children); - } - /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */ - if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) ) - { - if( passed_args->filter & FILTER_UPNP_SEARCHCLASS ) - { - ret = strcatf(str, ">" - "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>" - "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>" - "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass"); - } - } - ret = strcatf(str, ">" - "<dc:title>%s</dc:title>" - "<upnp:class>object.%s</upnp:class>", - title, class); - if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { - ret = strcatf(str, "<dc:creator>%s</dc:creator>", creator); - } - if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { - ret = strcatf(str, "<upnp:genre>%s</upnp:genre>", genre); - } - if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) { - ret = strcatf(str, "<upnp:artist>%s</upnp:artist>", artist); - } - if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) { - ret = strcatf(str, "<upnp:albumArtURI "); - if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) { - ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""); - } - ret = strcatf(str, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>", - lan_addr[0].str, runtime_vars.port, album_art, detailID); - } - ret = strcatf(str, "</container>"); + sprintf(str_buf, "SELECT count(*) from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '%s' order by d.TRACK, d.TITLE, o.NAME;", id); + ret = sqlite3_get_table(db, str_buf, &result, 0, 0, 0); + sprintf(str_buf, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent); + strcat(passed_args->resp, str_buf); + if( !passed_args->filter || strstr(passed_args->filter, "@childCount")) { + sprintf(str_buf, "childCount=\"%s\"", result[1]); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, ">" + "<dc:title>%s</dc:title>" + "<upnp:class>object.%s</upnp:class>" + "</container>", + name, class); + sqlite3_free_table(result); } + strcat(passed_args->resp, str_buf); return 0; } @@ -942,176 +400,80 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) "<u:BrowseResponse " "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" "<Result>" - "<DIDL-Lite" - CONTENT_DIRECTORY_SCHEMAS; + "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n"; + static const char resp1[] = "</DIDL-Lite></Result>"; + static const char resp2[] = "<UpdateID>0</UpdateID></u:BrowseResponse>"; + + char *resp = calloc(1, 1048576); + strcpy(resp, resp0); + + char str_buf[4096]; + char str_buf2[4096]; + memset(str_buf, '\0', sizeof(str_buf)); + memset(str_buf2, '\0', sizeof(str_buf2)); char *zErrMsg = 0; - char *sql, *ptr; int ret; - struct Response args; - struct string_s str; - int totalMatches; - struct NameValueParserData data; + char sql_buf[4096]; + struct Response { char *resp; int returned; int requested; int total; char *filter; } args; + struct NameValueParserData data; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); + int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") ); + int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") ); char * ObjectId = GetValueFromNameValueList(&data, "ObjectID"); char * Filter = GetValueFromNameValueList(&data, "Filter"); char * BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag"); char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); - char * orderBy = NULL; - int RequestedCount = 0; - int StartingIndex = 0; - if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) ) - RequestedCount = atoi(ptr); - if( !RequestedCount ) - RequestedCount = -1; - if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) ) - StartingIndex = atoi(ptr); - if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) ) - { - SoapError(h, 402, "Invalid Args"); - if( h->reqflags & FLAG_MS_PFS ) - ObjectId = sqlite3_malloc(1); - goto browse_error; - } - if( !ObjectId && !(ObjectId = GetValueFromNameValueList(&data, "ContainerID")) ) - { - SoapError(h, 701, "No such object error"); - if( h->reqflags & FLAG_MS_PFS ) - ObjectId = sqlite3_malloc(1); - goto browse_error; - } - memset(&args, 0, sizeof(args)); - memset(&str, 0, sizeof(str)); - - str.data = malloc(DEFAULT_RESP_SIZE); - str.size = DEFAULT_RESP_SIZE; - str.off = sprintf(str.data, "%s", resp0); - args.str = &str; - /* See if we need to include DLNA namespace reference */ - args.filter = set_filter_flags(Filter, h->req_client); - if( args.filter & FILTER_DLNA_NAMESPACE ) - { - ret = strcatf(&str, DLNA_NAMESPACE); - } - strcatf(&str, ">\n"); + if( !ObjectId ) + ObjectId = GetValueFromNameValueList(&data, "ContainerID"); + memset(&args, 0, sizeof(args)); + args.total = 0; args.returned = 0; args.requested = RequestedCount; - args.client = h->req_client; - args.flags = h->reqflags; - if( args.flags & FLAG_MS_PFS ) - { - if( !strchr(ObjectId, '$') && (strcmp(ObjectId, "0") != 0) ) - { - ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" - " where OBJECT_ID in " - "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')", - ObjectId, ObjectId, ObjectId); - if( ptr ) - { - ObjectId = ptr; - args.flags |= FLAG_FREE_OBJECT_ID; - } - } - } - DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" - " * ObjectID: %s\n" - " * Count: %d\n" - " * StartingIndex: %d\n" - " * BrowseFlag: %s\n" - " * Filter: %s\n" - " * SortCriteria: %s\n", - ObjectId, RequestedCount, StartingIndex, - BrowseFlag, Filter, SortCriteria); - - if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(ObjectId, "0") == 0) ) + args.resp = NULL; + args.filter = NULL; + printf("Asked for ObjectID: %s\n", ObjectId); + printf("Asked for Count: %d\n", RequestedCount); + printf("Asked for StartingIndex: %d\n", StartingIndex); + printf("Asked for BrowseFlag: %s\n", BrowseFlag); + printf("Asked for Filter: %s\n", Filter); + if( SortCriteria ) printf("Asked for SortCriteria: %s\n", SortCriteria); + + if( !Filter ) { - ObjectId = sqlite3_mprintf("%s", MUSIC_ID); - args.flags |= FLAG_FREE_OBJECT_ID; + ClearNameValueList(&data); + SoapError(h, 402, "Invalid Args"); + return; } + if( strlen(Filter) > 1 ) + args.filter = Filter; - if( strcmp(BrowseFlag+6, "Metadata") == 0 ) + args.resp = resp; + if( strcmp(BrowseFlag, "BrowseMetadata") == 0 ) { args.requested = 1; - sql = sqlite3_mprintf( SELECT_COLUMNS - "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where OBJECT_ID = '%s';" - , ObjectId); - ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); - totalMatches = args.returned; + sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s';", ObjectId); + ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); } else { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectId); - totalMatches = (ret > 0) ? ret : 0; - ret = 0; - if( SortCriteria ) - { -#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ - if( totalMatches < 10000 ) -#endif - orderBy = parse_sort_criteria(SortCriteria, &ret); - } - else - { - if( strncmp(ObjectId, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) - { - if( strcmp(ObjectId, MUSIC_PLIST_ID) == 0 ) - asprintf(&orderBy, "order by d.TITLE"); - else - asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID"); - } - else if( args.client == ERokuSoundBridge ) - { -#ifdef __sparc__ - if( totalMatches < 10000 ) -#endif - asprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE"); - } - } - /* If it's a DLNA client, return an error for bad sort criteria */ - if( (args.flags & FLAG_DLNA) && ret ) - { - SoapError(h, 709, "Unsupported or invalid sort criteria"); - goto browse_error; - } - - sql = sqlite3_mprintf( SELECT_COLUMNS - "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where PARENT_ID = '%s' %s limit %d, %d;", - ObjectId, orderBy, StartingIndex, RequestedCount); - DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql); - ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); + sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where PARENT_ID = '%s' order by d.TRACK, d.TITLE, o.NAME limit %d, -1;", + ObjectId, StartingIndex); + ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); } - sqlite3_free(sql); - if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) - { - DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql); + if( ret != SQLITE_OK ){ + printf("SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } - /* Does the object even exist? */ - if( !totalMatches ) - { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectId); - if( ret <= 0 ) - { - SoapError(h, 701, "No such object error"); - goto browse_error; - } - } - ret = strcatf(&str, "</DIDL-Lite></Result>\n" - "<NumberReturned>%u</NumberReturned>\n" - "<TotalMatches>%u</TotalMatches>\n" - "<UpdateID>%u</UpdateID>" - "</u:BrowseResponse>", - args.returned, totalMatches, updateID); - BuildSendAndCloseSoapResp(h, str.data, str.off); -browse_error: + strcat(resp, resp1); + sprintf(str_buf, "\n<NumberReturned>%u</NumberReturned>\n<TotalMatches>%u</TotalMatches>\n", args.returned, args.total); + strcat(resp, str_buf); + strcat(resp, resp2); + BuildSendAndCloseSoapResp(h, resp, strlen(resp)); ClearNameValueList(&data); - if( args.flags & FLAG_FREE_OBJECT_ID ) - sqlite3_free(ObjectId); - free(orderBy); - free(str.data); + free(resp); } static void @@ -1121,232 +483,136 @@ SearchContentDirectory(struct upnphttp * h, const char * action) "<u:SearchResponse " "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" "<Result>" - "<DIDL-Lite" - CONTENT_DIRECTORY_SCHEMAS; - char *zErrMsg = 0; - char *sql, *ptr; - char **result; - struct Response args; - struct string_s str; - int totalMatches = 0; - int ret; + "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n"; + static const char resp1[] = "</DIDL-Lite></Result>"; + static const char resp2[] = "<UpdateID>0</UpdateID></u:SearchResponse>"; + + char *resp = calloc(8, 16384); + strcpy(resp, resp0); + + char str_buf[4096]; + char str_buf2[4096]; + memset(str_buf, '\0', sizeof(str_buf)); + memset(str_buf2, '\0', sizeof(str_buf2)); + struct Response { char *resp; int returned; int requested; int total; char *filter; } args; struct NameValueParserData data; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); + int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") ); + int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") ); char * ContainerID = GetValueFromNameValueList(&data, "ContainerID"); char * Filter = GetValueFromNameValueList(&data, "Filter"); char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria"); char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); - char * newSearchCriteria = NULL; - char * orderBy = NULL; - char groupBy[] = "group by DETAIL_ID"; - int RequestedCount = 0; - int StartingIndex = 0; - if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) ) - RequestedCount = atoi(ptr); - if( !RequestedCount ) - RequestedCount = -1; - if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) ) - StartingIndex = atoi(ptr); - if( !ContainerID ) - { - if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) ) - { - SoapError(h, 701, "No such object error"); - if( h->reqflags & FLAG_MS_PFS ) - ContainerID = sqlite3_malloc(1); - goto search_error; - } - } - memset(&args, 0, sizeof(args)); - memset(&str, 0, sizeof(str)); - - str.data = malloc(DEFAULT_RESP_SIZE); - str.size = DEFAULT_RESP_SIZE; - str.off = sprintf(str.data, "%s", resp0); - /* See if we need to include DLNA namespace reference */ - args.filter = set_filter_flags(Filter, h->req_client); - if( args.filter & FILTER_DLNA_NAMESPACE ) - { - ret = strcatf(&str, DLNA_NAMESPACE); - } - ret = strcatf(&str, ">\n"); + memset(&args, 0, sizeof(args)); + args.total = 0; args.returned = 0; args.requested = RequestedCount; - args.client = h->req_client; - args.flags = h->reqflags; - args.str = &str; - if( h->reqflags & FLAG_MS_PFS ) + args.resp = NULL; + args.filter = NULL; + printf("Asked for ContainerID: %s\n", ContainerID); + printf("Asked for Count: %d\n", RequestedCount); + printf("Asked for StartingIndex: %d\n", StartingIndex); + printf("Asked for SearchCriteria: %s\n", SearchCriteria); + printf("Asked for Filter: %s\n", Filter); + if( SortCriteria ) printf("Asked for SortCriteria: %s\n", SortCriteria); + + if( !Filter ) { - if( strchr(ContainerID, '$') || (strcmp(ContainerID, "0") == 0) ) - { - ContainerID = sqlite3_mprintf("%s", ContainerID); - } - else - { - ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" - " where OBJECT_ID in " - "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')", - ContainerID, ContainerID, ContainerID); - if( ptr ) - ContainerID = ptr; - else - ContainerID = sqlite3_mprintf("%s", ContainerID); - } - #if 0 // Looks like the 360 already does this - /* Sort by track number for some containers */ - if( orderBy && - ((strncmp(ContainerID, MUSIC_GENRE_ID, 3) == 0) || - (strncmp(ContainerID, MUSIC_ARTIST_ID, 3) == 0) || - (strncmp(ContainerID, MUSIC_ALBUM_ID, 3) == 0)) ) - { - DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy); - sprintf(str_buf, "d.TRACK, "); - memmove(orderBy+18, orderBy+9, strlen(orderBy)+1); - memmove(orderBy+9, &str_buf, 9); - DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy); - } - #endif + ClearNameValueList(&data); + SoapError(h, 402, "Invalid Args"); + return; } - DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n" - " * ObjectID: %s\n" - " * Count: %d\n" - " * StartingIndex: %d\n" - " * SearchCriteria: %s\n" - " * Filter: %s\n" - " * SortCriteria: %s\n", - ContainerID, RequestedCount, StartingIndex, - SearchCriteria, Filter, SortCriteria); - + if( strlen(Filter) > 1 ) + args.filter = Filter; if( strcmp(ContainerID, "0") == 0 ) - *ContainerID = '*'; - else if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 ) - groupBy[0] = '\0'; + *ContainerID = '%'; if( !SearchCriteria ) { - newSearchCriteria = strdup("1 = 1"); - SearchCriteria = newSearchCriteria; + asprintf(&SearchCriteria, "1 = 1"); } else { SearchCriteria = modifyString(SearchCriteria, """, "\"", 0); SearchCriteria = modifyString(SearchCriteria, "'", "'", 0); - SearchCriteria = modifyString(SearchCriteria, "\\\"", "\"\"", 0); - SearchCriteria = modifyString(SearchCriteria, "object.", "", 0); SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1); - SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2); + SearchCriteria = modifyString(SearchCriteria, "contains", "like", 1); SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0); SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0); SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0); - SearchCriteria = modifyString(SearchCriteria, "upnp:actor", "d.ARTIST", 0); SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0); SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0); - SearchCriteria = modifyString(SearchCriteria, "upnp:genre", "d.GENRE", 0); SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0); SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0); SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0); - if( strstr(SearchCriteria, "@id") ) - { - newSearchCriteria = strdup(SearchCriteria); - SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "@id", "OBJECT_ID", 0); - } - if( strstr(SearchCriteria, "res is ") ) - { - if( !newSearchCriteria ) - newSearchCriteria = strdup(SearchCriteria); - SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0); - } - #if 0 // Does 360 need this? - if( strstr(SearchCriteria, "&") ) - { - if( newSearchCriteria ) - newSearchCriteria = modifyString(newSearchCriteria, "&", "&amp;", 0); - else - newSearchCriteria = modifyString(strdup(SearchCriteria), "&", "&amp;", 0); - SearchCriteria = newSearchCriteria; - } - #endif + SearchCriteria = modifyString(SearchCriteria, "object.", "", 0); } - DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria); - - sql = sqlite3_mprintf("SELECT (select count(distinct DETAIL_ID)" - " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" - " where (OBJECT_ID glob '%s$*') and (%s))" - " + " - "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" - " where (OBJECT_ID = '%s') and (%s))", - ContainerID, SearchCriteria, ContainerID, SearchCriteria); - //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Count SQL: %s\n", sql); - ret = sql_get_table(db, sql, &result, NULL, NULL); - sqlite3_free(sql); - if( ret == SQLITE_OK ) - { - totalMatches = atoi(result[1]); - sqlite3_free_table(result); + printf("Asked for SearchCriteria: %s\n", SearchCriteria); + + char *zErrMsg = 0; + int ret; + char sql_buf[4096]; + args.resp = resp; + sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where OBJECT_ID like '%s$%%' and (%s) order by d.TRACK, d.TITLE, o.NAME limit %d, -1;", + ContainerID, SearchCriteria, StartingIndex); + printf("Search SQL: %s\n", sql_buf); + ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); + if( ret != SQLITE_OK ){ + printf("SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); } - else + strcat(resp, resp1); + sprintf(str_buf, "\n<NumberReturned>%u</NumberReturned>\n<TotalMatches>%u</TotalMatches>\n", args.returned, args.total); + strcat(resp, str_buf); + strcat(resp, resp2); + BuildSendAndCloseSoapResp(h, resp, strlen(resp)); + ClearNameValueList(&data); + free(resp); +} + +static void +GetExternalIPAddress(struct upnphttp * h, const char * action) +{ + static const char resp[] = + "<u:%sResponse " + "xmlns:u=\"%s\">" + "<NewExternalIPAddress>%s</NewExternalIPAddress>" + "</u:%sResponse>"; + + char body[512]; + int bodylen; + char ext_ip_addr[INET_ADDRSTRLEN]; + +#ifndef MULTIPLE_EXTERNAL_IP + if(use_ext_ip_addr) { - /* Must be invalid SQL, so most likely bad or unhandled search criteria. */ - SoapError(h, 708, "Unsupported or invalid search criteria"); - goto search_error; + strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN); } - /* Does the object even exist? */ - if( !totalMatches ) + else if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN) < 0) + { + syslog(LOG_ERR, "Failed to get ip address for interface %s", + ext_if_name); + strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); + } +#else + int i; + strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); + for(i = 0; i<n_lan_addr; i++) { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", - !strcmp(ContainerID, "*")?"0":ContainerID); - if( ret <= 0 ) + if( (h->clientaddr.s_addr & lan_addr[i].mask.s_addr) + == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { - SoapError(h, 710, "No such container"); - goto search_error; + strncpy(ext_ip_addr, lan_addr[i].ext_ip_str, INET_ADDRSTRLEN); + break; } } -#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ - ret = 0; - if( totalMatches < 10000 ) #endif - orderBy = parse_sort_criteria(SortCriteria, &ret); - /* If it's a DLNA client, return an error for bad sort criteria */ - if( (args.flags & FLAG_DLNA) && ret ) - { - SoapError(h, 709, "Unsupported or invalid sort criteria"); - goto search_error; - } - - sql = sqlite3_mprintf( SELECT_COLUMNS - "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where OBJECT_ID glob '%s$*' and (%s) %s " - "%z %s" - " limit %d, %d", - ContainerID, SearchCriteria, groupBy, - (*ContainerID == '*') ? NULL : - sqlite3_mprintf("UNION ALL " SELECT_COLUMNS - "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria), - orderBy, StartingIndex, RequestedCount); - DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql); - ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); - if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) - { - DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql); - sqlite3_free(zErrMsg); - } - sqlite3_free(sql); - ret = strcatf(&str, "</DIDL-Lite></Result>\n" - "<NumberReturned>%u</NumberReturned>\n" - "<TotalMatches>%u</TotalMatches>\n" - "<UpdateID>%u</UpdateID>" - "</u:SearchResponse>", - args.returned, totalMatches, updateID); - BuildSendAndCloseSoapResp(h, str.data, str.off); -search_error: - ClearNameValueList(&data); - if( h->reqflags & FLAG_MS_PFS ) - sqlite3_free(ContainerID); - free(orderBy); - free(newSearchCriteria); - free(str.data); + bodylen = snprintf(body, sizeof(body), resp, + action, "urn:schemas-upnp-org:service:WANIPConnection:1", + ext_ip_addr, action); + BuildSendAndCloseSoapResp(h, body, bodylen); } /* @@ -1376,7 +642,7 @@ QueryStateVariable(struct upnphttp * h, const char * action) /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/ var_name = GetValueFromNameValueList(&data, "varName"); - DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name); + /*syslog(LOG_INFO, "QueryStateVariable(%.40s)", var_name); */ if(!var_name) { @@ -1389,35 +655,28 @@ QueryStateVariable(struct upnphttp * h, const char * action) "Connected", action); BuildSendAndCloseSoapResp(h, body, bodylen); } +#if 0 + /* not usefull */ + else if(strcmp(var_name, "ConnectionType") == 0) + { + bodylen = snprintf(body, sizeof(body), resp, "IP_Routed"); + BuildSendAndCloseSoapResp(h, body, bodylen); + } + else if(strcmp(var_name, "LastConnectionError") == 0) + { + bodylen = snprintf(body, sizeof(body), resp, "ERROR_NONE"); + BuildSendAndCloseSoapResp(h, body, bodylen); + } +#endif else { - DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:""); + syslog(LOG_NOTICE, "%s: Unknown: %s", action, var_name?var_name:""); SoapError(h, 404, "Invalid Var"); } ClearNameValueList(&data); } -static void -SamsungGetFeatureList(struct upnphttp * h, const char * action) -{ - static const char resp[] = - "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" - "<FeatureList>" - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" - "<Features xmlns=\"urn:schemas-upnp-org:av:avs\"" - " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" - " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">" - "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">" - "<container id=\"1\" type=\"object.item.audioItem\"/>" - "<container id=\"2\" type=\"object.item.videoItem\"/>" - "<container id=\"3\" type=\"object.item.imageItem\"/>" - "</Feature>" - "</FeatureList></u:X_GetFeatureListResponse>"; - - BuildSendAndCloseSoapResp(h, resp, sizeof(resp)); -} - static const struct { const char * methodName; @@ -1425,7 +684,9 @@ static const struct } soapMethods[] = { + { "GetExternalIPAddress", GetExternalIPAddress}, { "QueryStateVariable", QueryStateVariable}, + { "GetStatusInfo", GetStatusInfo}, { "Browse", BrowseContentDirectory}, { "Search", SearchContentDirectory}, { "GetSearchCapabilities", GetSearchCapabilities}, @@ -1436,7 +697,6 @@ soapMethods[] = { "GetCurrentConnectionInfo", GetCurrentConnectionInfo}, { "IsAuthorized", IsAuthorizedValidated}, { "IsValidated", IsAuthorizedValidated}, - { "X_GetFeatureList", SamsungGetFeatureList}, { 0, 0 } }; @@ -1444,21 +704,21 @@ void ExecuteSoapAction(struct upnphttp * h, const char * action, int n) { char * p; + char * p2; + int i, len, methodlen; + i = 0; p = strchr(action, '#'); + if(p) { - int i = 0; - int len; - int methodlen; - char * p2; p++; p2 = strchr(p, '"'); if(p2) methodlen = p2 - p; else methodlen = n - (p - action); - DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p); + /*syslog(LOG_DEBUG, "SoapMethod: %.*s", methodlen, p);*/ while(soapMethods[i].methodName) { len = strlen(soapMethods[i].methodName); @@ -1470,7 +730,7 @@ ExecuteSoapAction(struct upnphttp * h, const char * action, int n) i++; } - DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p); + syslog(LOG_NOTICE, "SoapMethod: Unknown: %.*s", methodlen, p); } SoapError(h, 401, "Invalid Action"); @@ -1518,7 +778,7 @@ SoapError(struct upnphttp * h, int errCode, const char * errDesc) char body[2048]; int bodylen; - DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc); + syslog(LOG_INFO, "Returning UPnPError %d: %s", errCode, errDesc); bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc); BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen); SendResp_upnphttp(h); @@ -1,47 +1,12 @@ -/* MiniDLNA project - * http://minidlna.sourceforge.net/ - * - * MiniDLNA media server - * Copyright (C) 2008-2009 Justin Maggard - * - * This file is part of MiniDLNA. - * - * MiniDLNA is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * MiniDLNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. - */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + #ifndef __UPNPSOAP_H__ #define __UPNPSOAP_H__ -#define DEFAULT_RESP_SIZE 131072 -#define MAX_RESPONSE_SIZE 2097152 - -#define CONTENT_DIRECTORY_SCHEMAS \ - " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"" \ - " xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\"" \ - " xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"" -#define DLNA_NAMESPACE \ - " xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"" - -struct Response -{ - struct string_s *str; - int start; - int returned; - int requested; - uint32_t filter; - uint32_t flags; - enum client_types client; -}; - /* ExecuteSoapAction(): * this method executes the requested Soap Action */ void |