/* $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 #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpsoap.h" #include "upnpreplyparse.h" #include "getifaddr.h" #include #include "metadata.h" #include static void BuildSendAndCloseSoapResp(struct upnphttp * h, const char * body, int bodylen) { static const char beforebody[] = "\r\n" "" ""; static const char afterbody[] = "" "\r\n"; BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1 + sizeof(afterbody) - 1 + bodylen ); memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1); h->res_buflen += sizeof(beforebody) - 1; memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1); h->res_buflen += sizeof(afterbody) - 1; SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void GetStatusInfo(struct upnphttp * h, const char * action) { static const char resp[] = "" "Connected" "ERROR_NONE" "%ld" ""; 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[] = "" "%d" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", 1, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void IsAuthorizedValidated(struct upnphttp * h, const char * action) { static const char resp[] = "" "%d" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", 1, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetProtocolInfo(struct upnphttp * h, const char * action) { static const char resp[] = "" "" /*"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:*" "" "" ""; char body[1536]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetSortCapabilities(struct upnphttp * h, const char * action) { static const char resp[] = "" "" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetSearchCapabilities(struct upnphttp * h, const char * action) { static const char resp[] = "" "dc:title,dc:creator,upnp:class,upnp:artist,upnp:album,@refID" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetCurrentConnectionIDs(struct upnphttp * h, const char * action) { /* TODO: Use real data. - JM */ static const char resp[] = "" "-1" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetCurrentConnectionInfo(struct upnphttp * h, const char * action) { /* TODO: Use real data. - JM */ static const char resp[] = "" "-1" "-1" "" "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," "" "0" "-1" "0" "0" ""; char body[sizeof(resp)+128]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static int callback(void *args, int argc, char **argv, char **azColName) { 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; passed_args->total++; 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 strcpy(dlna_buf, "*"); /*for(i=0; iresp, 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); } if( duration && (!passed_args->filter || strstr(passed_args->filter, "res@duration")) ) { sprintf(str_buf, "duration=\"%s\" ", duration); strcat(passed_args->resp, str_buf); } if( bitrate && (!passed_args->filter || strstr(passed_args->filter, "res@bitrate")) ) { sprintf(str_buf, "bitrate=\"%s\" ", bitrate); strcat(passed_args->resp, str_buf); } if( sampleFrequency && (!passed_args->filter || strstr(passed_args->filter, "res@sampleFrequency")) ) { sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency); strcat(passed_args->resp, str_buf); } if( nrAudioChannels && (!passed_args->filter || strstr(passed_args->filter, "res@nrAudioChannels")) ) { sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels); strcat(passed_args->resp, str_buf); } 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); } 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); } #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); } strcpy(str_buf, "</item>"); } else if( strncmp(class, "container", 9) == 0 ) { 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; } static void BrowseContentDirectory(struct upnphttp * h, const char * action) { static const char resp0[] = "" "" "<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>"; static const char resp2[] = "0"; 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; int ret; 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"); if( !ObjectId ) ObjectId = GetValueFromNameValueList(&data, "ContainerID"); memset(&args, 0, sizeof(args)); args.total = 0; args.returned = 0; args.requested = RequestedCount; 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 ) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } if( strlen(Filter) > 1 ) args.filter = Filter; args.resp = resp; if( strcmp(BrowseFlag, "BrowseMetadata") == 0 ) { args.requested = 1; 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 { 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); } if( ret != SQLITE_OK ){ printf("SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } strcat(resp, resp1); sprintf(str_buf, "\n%u\n%u\n", args.returned, args.total); strcat(resp, str_buf); strcat(resp, resp2); BuildSendAndCloseSoapResp(h, resp, strlen(resp)); ClearNameValueList(&data); free(resp); } static void SearchContentDirectory(struct upnphttp * h, const char * action) { static const char resp0[] = "" "" "<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>"; static const char resp2[] = "0"; 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"); memset(&args, 0, sizeof(args)); args.total = 0; args.returned = 0; args.requested = RequestedCount; 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 ) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } if( strlen(Filter) > 1 ) args.filter = Filter; if( strcmp(ContainerID, "0") == 0 ) *ContainerID = '%'; if( !SearchCriteria ) { asprintf(&SearchCriteria, "1 = 1"); } else { SearchCriteria = modifyString(SearchCriteria, """, "\"", 0); SearchCriteria = modifyString(SearchCriteria, "'", "'", 0); SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1); 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:artist", "d.ARTIST", 0); SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 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); SearchCriteria = modifyString(SearchCriteria, "object.", "", 0); } 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); } strcat(resp, resp1); sprintf(str_buf, "\n%u\n%u\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[] = "" "%s" ""; char body[512]; int bodylen; char ext_ip_addr[INET_ADDRSTRLEN]; #ifndef MULTIPLE_EXTERNAL_IP if(use_ext_ip_addr) { strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN); } 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; iclientaddr.s_addr & lan_addr[i].mask.s_addr) == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { strncpy(ext_ip_addr, lan_addr[i].ext_ip_str, INET_ADDRSTRLEN); break; } } #endif bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:WANIPConnection:1", ext_ip_addr, action); BuildSendAndCloseSoapResp(h, body, bodylen); } /* If a control point calls QueryStateVariable on a state variable that is not buffered in memory within (or otherwise available from) the service, the service must return a SOAP fault with an errorCode of 404 Invalid Var. QueryStateVariable remains useful as a limited test tool but may not be part of some future versions of UPnP. */ static void QueryStateVariable(struct upnphttp * h, const char * action) { static const char resp[] = "" "%s" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * var_name; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */ /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/ var_name = GetValueFromNameValueList(&data, "varName"); /*syslog(LOG_INFO, "QueryStateVariable(%.40s)", var_name); */ if(!var_name) { SoapError(h, 402, "Invalid Args"); } else if(strcmp(var_name, "ConnectionStatus") == 0) { bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:control-1-0", "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 { syslog(LOG_NOTICE, "%s: Unknown: %s", action, var_name?var_name:""); SoapError(h, 404, "Invalid Var"); } ClearNameValueList(&data); } static const struct { const char * methodName; void (*methodImpl)(struct upnphttp *, const char *); } soapMethods[] = { { "GetExternalIPAddress", GetExternalIPAddress}, { "QueryStateVariable", QueryStateVariable}, { "GetStatusInfo", GetStatusInfo}, { "Browse", BrowseContentDirectory}, { "Search", SearchContentDirectory}, { "GetSearchCapabilities", GetSearchCapabilities}, { "GetSortCapabilities", GetSortCapabilities}, { "GetSystemUpdateID", GetSystemUpdateID}, { "GetProtocolInfo", GetProtocolInfo}, { "GetCurrentConnectionIDs", GetCurrentConnectionIDs}, { "GetCurrentConnectionInfo", GetCurrentConnectionInfo}, { "IsAuthorized", IsAuthorizedValidated}, { "IsValidated", IsAuthorizedValidated}, { 0, 0 } }; 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) { p++; p2 = strchr(p, '"'); if(p2) methodlen = p2 - p; else methodlen = n - (p - action); /*syslog(LOG_DEBUG, "SoapMethod: %.*s", methodlen, p);*/ while(soapMethods[i].methodName) { len = strlen(soapMethods[i].methodName); if(strncmp(p, soapMethods[i].methodName, len) == 0) { soapMethods[i].methodImpl(h, soapMethods[i].methodName); return; } i++; } syslog(LOG_NOTICE, "SoapMethod: Unknown: %.*s", methodlen, p); } SoapError(h, 401, "Invalid Action"); } /* Standard Errors: * * errorCode errorDescription Description * -------- ---------------- ----------- * 401 Invalid Action No action by that name at this service. * 402 Invalid Args Could be any of the following: not enough in args, * too many in args, no in arg by that name, * one or more in args are of the wrong data type. * 403 Out of Sync Out of synchronization. * 501 Action Failed May be returned in current state of service * prevents invoking that action. * 600-699 TBD Common action errors. Defined by UPnP Forum * Technical Committee. * 700-799 TBD Action-specific errors for standard actions. * Defined by UPnP Forum working committee. * 800-899 TBD Action-specific errors for non-standard actions. * Defined by UPnP vendor. */ void SoapError(struct upnphttp * h, int errCode, const char * errDesc) { static const char resp[] = "" "" "" "s:Client" "UPnPError" "" "" "%d" "%s" "" "" "" "" ""; char body[2048]; int bodylen; 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); CloseSocket_upnphttp(h); }