diff --git a/metadata.c b/metadata.c index 1d76a04..064e762 100644 --- a/metadata.c +++ b/metadata.c @@ -202,11 +202,11 @@ GetAudioMetadata(const char * path, char * name) asprintf(&date, "%04d-01-01", song.year); sprintf(duration, "%d:%02d:%02d.%03d", (song.song_length/3600000), - (song.song_length/60000), + (song.song_length/60000%60), (song.song_length/1000%60), (song.song_length%1000)); title = song.title; - if( title ) + if( title && *title ) { title = trim(title); if( (esc_tag = escape_tag(title)) ) @@ -221,7 +221,7 @@ GetAudioMetadata(const char * path, char * name) } for( i=ROLE_START; i1?"1":""), + uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") ); if(l>=sizeof(bufr)) { DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n"); @@ -399,11 +399,13 @@ ProcessSSDPRequest(int s, unsigned short port) /* strlen("ssdp:all") == 8 */ if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8))) { - for(i=0; known_service_types[i]; i++) + SendSSDPAnnounce2(s, sendername, "", 0, "", + lan_addr[lan_addr_index].str, port); + for(i=1; known_service_types[i]; i++) { l = (int)strlen(known_service_types[i]); SendSSDPAnnounce2(s, sendername, - known_service_types[i], l, i==0?"":"1", + known_service_types[i], l, i==1?"":"1", lan_addr[lan_addr_index].str, port); } } @@ -451,19 +453,20 @@ SendSSDPGoodbye(int * sockets, int n_sockets) "NOTIFY * HTTP/1.1\r\n" "HOST:%s:%d\r\n" "NT:%s%s\r\n" - "USN:%s::%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==0?"":"1"), - uuidvalue, known_service_types[i], (i==0?"":"1")); + known_service_types[i], (i>1?"1":""), + uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") ); + //DEBUG printf("Sending NOTIFY:\n%s", bufr); 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)); - return -1; - } + if(n < 0) + { + DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", sockets[j], strerror(errno)); + return -1; + } } } return 0; diff --git a/upnpdescgen.c b/upnpdescgen.c index 1eae2b8..5116f84 100644 --- a/upnpdescgen.c +++ b/upnpdescgen.c @@ -127,22 +127,22 @@ static const struct XMLElt rootDesc[] = {"/mimetype", "image/png"}, {"/width", "48"}, {"/height", "48"}, - {"/depth", "32"}, + {"/depth", "24"}, {"/url", "/icons/sm.png"}, {"/mimetype", "image/png"}, {"/width", "120"}, {"/height", "120"}, - {"/depth", "32"}, + {"/depth", "24"}, {"/url", "/icons/lrg.png"}, {"/mimetype", "image/jpeg"}, {"/width", "48"}, {"/height", "48"}, - {"/depth", "32"}, + {"/depth", "24"}, {"/url", "/icons/sm.jpg"}, {"/mimetype", "image/jpeg"}, {"/width", "120"}, {"/height", "120"}, - {"/depth", "32"}, + {"/depth", "24"}, {"/url", "/icons/lrg.jpg"}, {"service", INITHELPER(46,5)}, {"service", INITHELPER(51,5)}, diff --git a/upnpglobalvars.h b/upnpglobalvars.h index e4abb44..d2bc219 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -22,26 +22,18 @@ #define DB_VERSION 1 #define RESOURCE_PROTOCOL_INFO_VALUES \ - "http-get:*:image/jpeg:*," \ "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/avi:*," \ - "http-get:*:video/divx:*," \ - "http-get:*:video/x-matroska:*," \ - "http-get:*:video/mpeg:*," \ "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=MPEG1;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/mp4:*," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;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=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-ms-wmv:*," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ - "http-get:*:video/x-msvideo:*," \ "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01," \ @@ -49,6 +41,18 @@ "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01," \ "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01," \ "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_MULT5_ISO;DLNA.ORG_OP=01," \ + "http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01," \ + "http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01," \ + "http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01," \ + "http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01," \ + "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:*:audio/mp4:*," \ "http-get:*:audio/wav:*," \ "http-get:*:audio/x-flac:*," \ diff --git a/upnphttp.c b/upnphttp.c index c33de63..7f973d9 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -649,12 +649,15 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n"); Send400(h); } - else if( (h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED) ) + #if 1 /* 7.3.33.4 */ + else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) && + !(h->reqflags & FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n", h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed"); Send406(h); } + #endif else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; @@ -1544,7 +1547,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object) "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" - "contentFeatures.dlna.org: DLNA.ORG_PN=%s\r\n" + "contentFeatures.dlna.org: %s\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", date, last_file.dlna); strcat(header, hdr_buf); diff --git a/upnpsoap.c b/upnpsoap.c index 676bca4..aa73de8 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -191,13 +191,11 @@ GetCurrentConnectionInfo(struct upnphttp * h, const char * action) "xmlns:u=\"%s\">" "-1" "-1" - "" - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," - "" - "0" + "" + "" "-1" - "0" - "0" + "Output" + "Unknown" ""; char body[sizeof(resp)+128]; @@ -329,6 +327,7 @@ set_filter_flags(char * filter) } 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 ) @@ -396,11 +395,12 @@ set_filter_flags(char * filter) } char * -parse_sort_criteria(char * sortCriteria) +parse_sort_criteria(char * sortCriteria, int * error) { char *order = NULL; char *item, *saveptr; int i, ret, reverse, title_sorted = 0; + *error = 0; if( !sortCriteria ) return NULL; @@ -444,6 +444,7 @@ parse_sort_criteria(char * sortCriteria) else { printf("Unhandled SortCriteria [%s]\n", item); + *error = 1; if( i ) { ret = strlen(order); @@ -842,9 +843,16 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) sqlite3_free_table(result); } #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ + ret = 0; if( totalMatches < 10000 ) #endif - orderBy = parse_sort_criteria(SortCriteria); + 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 browse_error; + } sql = sqlite3_mprintf( SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" @@ -859,6 +867,23 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } + /* Does the object even exist? */ + if( !totalMatches ) + { + ret = 0; + sql = sqlite3_mprintf("SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", ObjectId); + if( sql_get_table(db, sql, &result, NULL, NULL) == SQLITE_OK ) + { + ret = atoi(result[1]); + sqlite3_free_table(result); + } + sqlite3_free(sql); + if( !ret ) + { + SoapError(h, 701, "No such object error"); + goto browse_error; + } + } ret = snprintf(str_buf, sizeof(str_buf), "</DIDL-Lite>\n" "%u\n" "%u\n" @@ -868,6 +893,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) memcpy(resp+args.size, &str_buf, ret+1); args.size += ret; BuildSendAndCloseSoapResp(h, resp, args.size); +browse_error: ClearNameValueList(&data); if( orderBy ) free(orderBy); @@ -959,7 +985,7 @@ SearchContentDirectory(struct upnphttp * h, const char * action) } #endif } - DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" + DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" " * StartingIndex: %d\n" @@ -1011,14 +1037,46 @@ SearchContentDirectory(struct upnphttp * h, const char * action) ContainerID, SearchCriteria, ContainerID, SearchCriteria); //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Count SQL: %s\n", sql); ret = sql_get_table(db, str_buf, &result, NULL, NULL); - if( ret == SQLITE_OK ) { + if( ret == SQLITE_OK ) + { totalMatches = atoi(result[1]); sqlite3_free_table(result); } + else + { + /* Must be invalid SQL, so most likely bad or unhandled search criteria. */ + SoapError(h, 708, "Unsupported or invalid search criteria"); + goto search_error; + } + /* Does the object even exist? */ + if( !totalMatches ) + { + ret = 0; + sql = sqlite3_mprintf("SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", + !strcmp(ContainerID, "*")?"0":ContainerID); + if( sql_get_table(db, sql, &result, NULL, NULL) == SQLITE_OK ) + { + ret = atoi(result[1]); + sqlite3_free_table(result); + } + sqlite3_free(sql); + if( !ret ) + { + SoapError(h, 710, "No such container"); + goto search_error; + } + } #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ + ret = 0; if( totalMatches < 10000 ) #endif - orderBy = parse_sort_criteria(SortCriteria); + 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)" @@ -1049,6 +1107,7 @@ SearchContentDirectory(struct upnphttp * h, const char * action) memcpy(resp+args.size, &str_buf, ret+1); args.size += ret; BuildSendAndCloseSoapResp(h, resp, args.size); +search_error: ClearNameValueList(&data); if( orderBy ) free(orderBy);