/* MiniDLNA project * http://minidlna.sourceforge.net/ * (c) 2008 Justin Maggard * * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution * * Portions of the code from the MiniUPnP Project * (c) Thomas Bernard licensed under BSD revised license * detailed in the LICENSE.miniupnpd file provided within * the distribution. */ #include #include #include #include #include #include #include #include #include #include "config.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "miniupnpdpath.h" #include "upnpsoap.h" #include "upnpevents.h" #include #include #include #include #include #include "upnpglobalvars.h" #include "utils.h" #include #include #if 0 //JPEG_RESIZE #include #endif //#define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much? #define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much? struct upnphttp * New_upnphttp(int s) { struct upnphttp * ret; if(s<0) return NULL; ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); if(ret == NULL) return NULL; memset(ret, 0, sizeof(struct upnphttp)); ret->socket = s; return ret; } void CloseSocket_upnphttp(struct upnphttp * h) { if(close(h->socket) < 0) { syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); } h->socket = -1; h->state = 100; } void Delete_upnphttp(struct upnphttp * h) { if(h) { if(h->socket >= 0) CloseSocket_upnphttp(h); if(h->req_buf) free(h->req_buf); if(h->res_buf) free(h->res_buf); free(h); } } /* parse HttpHeaders of the REQUEST */ static void ParseHttpHeaders(struct upnphttp * h) { char * line; char * colon; char * p; int n; line = h->req_buf; /* TODO : check if req_buf, contentoff are ok */ while(line < (h->req_buf + h->req_contentoff)) { colon = strchr(line, ':'); if(colon) { if(strncasecmp(line, "Content-Length", 14)==0) { p = colon; while(*p < '0' || *p > '9') p++; h->req_contentlen = atoi(p); /*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) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>=' ') { n++; } if((p[0] == '"' && p[n-1] == '"') || (p[0] == '\'' && p[n-1] == '\'')) { p++; n -= 2; } h->req_soapAction = p; h->req_soapActionLen = n; } #ifdef ENABLE_EVENTS else if(strncasecmp(line, "Callback", 8)==0) { p = colon; while(*p != '<' && *p != '\r' ) p++; n = 0; while(p[n] != '>' && p[n] != '\r' ) n++; h->req_Callback = p + 1; h->req_CallbackLen = MAX(0, n - 1); } else if(strncasecmp(line, "SID", 3)==0) { 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 Recommended. Requested duration until subscription expires, either number of seconds or infinite. Recommendation by a UPnP Forum working committee. Defined by UPnP vendor. Consists of the keyword "Second-" followed (without an intervening space) by either an integer or the keyword "infinite". */ else if(strncasecmp(line, "Timeout", 7)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Second-", 7)==0) { h->req_Timeout = atoi(p+7); } } #endif #if 1 // Range: bytes=xxx-yyy else if(strncasecmp(line, "Range", 5)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "bytes=", 6)==0) { h->reqflags |= FLAG_RANGE; h->req_RangeEnd = atoll(index(p+6, '-')+1); h->req_RangeStart = atoll(p+6); printf("Range Start-End: %lld - %lld\n", h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); } } else if(strncasecmp(line, "Host", 4)==0) { h->reqflags |= FLAG_HOST; } else if(strncasecmp(line, "Transfer-Encoding", 17)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "chunked", 7)==0) { h->reqflags |= FLAG_CHUNKED; } } else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0) { p = colon + 1; while(isspace(*p)) p++; if( (*p != '1') || !isspace(p[1]) ) h->reqflags |= FLAG_INVALID_REQ; } else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0) { h->reqflags |= FLAG_TIMESEEK; } else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0) { h->reqflags |= FLAG_REALTIMEINFO; } else if(strncasecmp(line, "transferMode.dlna.org", 21)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Streaming", 9)==0) { h->reqflags |= FLAG_XFERSTREAMING; } if(strncasecmp(p, "Interactive", 11)==0) { h->reqflags |= FLAG_XFERINTERACTIVE; } if(strncasecmp(p, "Background", 10)==0) { h->reqflags |= FLAG_XFERBACKGROUND; } } #endif } while(!(line[0] == '\r' && line[1] == '\n')) line++; line += 2; } if( h->reqflags & FLAG_CHUNKED ) { if( h->req_buflen > h->req_contentoff ) { h->req_chunklen = strtol(line, NULL, 16); while(!(line[0] == '\r' && line[1] == '\n')) { line++; h->req_contentoff++; } h->req_contentoff += 2; } else { h->req_chunklen = -1; } } } /* very minimalistic 400 error message */ static void Send400(struct upnphttp * h) { static const char body400[] = "400 Bad Request" "

Bad Request

The request is invalid" " for this HTTP version.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", body400, sizeof(body400) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 404 error message */ 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" "404 Not Found" "

Not Found

The requested URL was not found" " on this server.\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[] = "404 Not Found" "

Not Found

The requested URL was not found" " on this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 404, "Not Found", body404, sizeof(body404) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 404 error message */ static void Send406(struct upnphttp * h) { static const char body406[] = "406 Not Acceptable" "

Not Acceptable

An unsupported operation " " was requested.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 406, "Not Acceptable", body406, sizeof(body406) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 404 error message */ static void Send416(struct upnphttp * h) { static const char body416[] = "416 Requested Range Not Satisfiable" "

Requested Range Not Satisfiable

The requested range" " was outside the file's size.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable", body416, sizeof(body416) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 501 error message */ 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" "501 Not Implemented" "

Not Implemented

The HTTP Method " "is not implemented by this server.\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[] = "501 Not Implemented" "

Not Implemented

The HTTP Method " "is not implemented by this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 501, "Not Implemented", body501, sizeof(body501) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static const char * findendheaders(const char * s, int len) { while(len-->0) { if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') return s; s++; } return NULL; } /* Sends the description generated by the parameter */ static void sendXMLdesc(struct upnphttp * h, char * (f)(int *)) { char * desc; int len; desc = f(&len); if(!desc) { static const char error500[] = "Error 500" "Internal Server Error\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); } SendResp_upnphttp(h); CloseSocket_upnphttp(h); free(desc); } /* ProcessHTTPPOST_upnphttp() * executes the SOAP query if it is possible */ static void ProcessHTTPPOST_upnphttp(struct upnphttp * h) { if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { if(h->req_soapAction) { /* we can process the request */ //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); } else { static const char err400str[] = "Bad request"; syslog(LOG_INFO, "No SOAPAction in HTTP headers"); h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", err400str, sizeof(err400str) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } } else { /* waiting for remaining data */ h->state = 1; } } #ifdef ENABLE_EVENTS static void ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) { const char * sid; syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", h->req_CallbackLen, h->req_Callback, h->req_Timeout); 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, * the publisher must respond with HTTP error 412 Precondition Failed*/ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } else { /* - add to the subscriber list * - respond HTTP/x.x 200 OK * - Send the initial event message */ /* Server:, SID:; Timeout: Second-(xx|infinite) */ if(h->req_Callback) { sid = upnpevents_addSubscriber(path, h->req_Callback, h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if(sid) { syslog(LOG_DEBUG, "generated sid=%s", sid); h->respflags |= FLAG_SID; h->req_SID = sid; h->req_SIDLen = strlen(sid); } BuildResp_upnphttp(h, 0, 0); } else { /* subscription renew */ /* Invalid SID 412 Precondition Failed. If a SID does not correspond to a known, un-expired subscription, the publisher must respond with HTTP error 412 Precondition Failed. */ if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { /* A DLNA device must enforce a 5 minute timeout */ h->respflags = FLAG_TIMEOUT; h->req_Timeout = 300; BuildResp_upnphttp(h, 0, 0); } } SendResp_upnphttp(h); CloseSocket_upnphttp(h); } } static void ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) { 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); } else { BuildResp_upnphttp(h, 0, 0); } SendResp_upnphttp(h); CloseSocket_upnphttp(h); } #endif /* Parse and process Http Query * called once all the HTTP headers have been received. */ static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { char HttpCommand[16]; char HttpUrl[128]; char * HttpVer; char * p; int i; p = h->req_buf; if(!p) return; for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) HttpCommand[i] = *(p++); HttpCommand[i] = '\0'; while(*p==' ') p++; if(strncmp(p, "http://", 7) == 0) { p = p+7; while(*p!='/') p++; } for(i = 0; i<127 && *p != ' ' && *p != '\r'; i++) HttpUrl[i] = *(p++); HttpUrl[i] = '\0'; while(*p==' ') p++; HttpVer = h->HttpVer; for(i = 0; i<15 && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)", HttpCommand, HttpUrl, HttpVer); printf("HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf); ParseHttpHeaders(h); /* see if we need to wait for remaining data */ if( (h->reqflags & FLAG_CHUNKED) ) { char * chunkstart = h->req_buf+h->req_contentoff; char * numstart; h->state = 2; while( h->req_chunklen ) { if( chunkstart >= (h->req_buf+h->req_buflen) ) return; numstart = chunkstart+h->req_chunklen+2; h->req_chunklen = strtol(numstart, &chunkstart, 16); if( !h->req_chunklen && (chunkstart == numstart) ) { printf("Chunked request needs more input.\n"); return; } chunkstart = chunkstart+2; } h->state = 100; } if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp(h); } else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0)) { if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) ) { syslog(LOG_NOTICE, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)"); Send400(h); } else if( h->reqflags & FLAG_TIMESEEK ) { syslog(LOG_NOTICE, "DLNA TimeSeek requested, responding ERROR 406"); Send406(h); } else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; } else { h->req_command = EHead; } if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) { sendXMLdesc(h, genRootDesc); } else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) { sendXMLdesc(h, genContentDirectory); } else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0) { sendXMLdesc(h, genConnectionManager); } else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0) { sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); } else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) { SendResp_dlnafile(h, HttpUrl+12); CloseSocket_upnphttp(h); } else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) { SendResp_thumbnail(h, HttpUrl+12); CloseSocket_upnphttp(h); } else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) { SendResp_albumArt(h, HttpUrl+10); CloseSocket_upnphttp(h); } #if 0 //JPEG_RESIZE else if(strncmp(HttpUrl, "/Resized/", 7) == 0) { SendResp_resizedimg(h, HttpUrl+7); CloseSocket_upnphttp(h); } #endif else { 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; ProcessHTTPSubscribe_upnphttp(h, HttpUrl); } else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) { 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 { syslog(LOG_NOTICE, "Unsupported HTTP Command %s", HttpCommand); Send501(h); } } void Process_upnphttp(struct upnphttp * h) { char buf[2048]; int n; if(!h) return; switch(h->state) { case 0: n = recv(h->socket, buf, 2048, 0); if(n<0) { syslog(LOG_ERR, "recv (state0): %m"); h->state = 100; } else if(n==0) { syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); h->state = 100; } else { const char * endheaders; /* if 1st arg of realloc() is null, * realloc behaves the same as malloc() */ h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; h->req_buf[h->req_buflen] = '\0'; /* search for the string "\r\n\r\n" */ endheaders = findendheaders(h->req_buf, h->req_buflen); if(endheaders) { h->req_contentoff = endheaders - h->req_buf + 4; ProcessHttpQuery_upnphttp(h); } } break; case 1: case 2: n = recv(h->socket, buf, 2048, 0); if(n<0) { syslog(LOG_ERR, "recv (state1): %m"); h->state = 100; } else if(n==0) { syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); h->state = 100; } else { /*fwrite(buf, 1, n, stdout);*/ /* debug */ h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { if( h->state == 1 ) ProcessHTTPPOST_upnphttp(h); else if( h->state == 2 ) ProcessHttpQuery_upnphttp(h); } } break; default: 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: 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";*/ /* "\n" "" "" "" ""; */ /* with response code and response message * also allocate enough memory */ void BuildHeader_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, int bodylen) { int templen; if(!h->res_buf) { templen = sizeof(httpresphead) + 128 + bodylen; h->res_buf = (char *)malloc(templen); h->res_buf_alloclen = templen; } h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, //httpresphead, h->HttpVer, httpresphead, "HTTP/1.1", respcode, respmsg, (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, "Timeout: Second-"); if(h->req_Timeout) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "%d\r\n", h->req_Timeout); } else { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "300\r\n"); //JM DLNA must force to 300 - "infinite\r\n"); } } if(h->respflags & FLAG_SID) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "SID: %s\r\n", h->req_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"); #endif h->res_buf[h->res_buflen++] = '\r'; h->res_buf[h->res_buflen++] = '\n'; if(h->res_buf_alloclen < (h->res_buflen + bodylen)) { h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); h->res_buf_alloclen = h->res_buflen + bodylen; } } void BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen) { BuildHeader_upnphttp(h, respcode, respmsg, bodylen); if( h->req_command == EHead ) return; if(body) memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; } /* responding 200 OK ! */ void BuildResp_upnphttp(struct upnphttp * h, const char * body, int bodylen) { BuildResp2_upnphttp(h, 200, "OK", body, bodylen); } void SendResp_upnphttp(struct upnphttp * h) { int n; 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) { 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); } } int send_data(struct upnphttp * h, char * header, size_t size) { int n; n = send(h->socket, header, 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); } else { return 0; } return 1; } void send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) { off_t send_size; while( offset < end_offset ) { send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); off_t ret = sendfile(h->socket, sendfd, &offset, send_size); 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 %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); }*/ } } void SendResp_albumArt(struct upnphttp * h, char * object) { char header[1500]; char sql_buf[256]; char **result; int rows; char *path; char date[30]; time_t curtime = time(NULL); off_t offset = 0, size; int sendfh; memset(header, 0, 1500); if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) { syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); Send406(h); return; } strip_ext(object); sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); if( !rows ) { syslog(LOG_NOTICE, "ALBUM_ART ID %s not found, responding ERROR 404", object); Send404(h); goto error; } path = result[1]; printf("Serving album art ID: %s [%s]\n", object, path); if( access(path, F_OK) == 0 ) { strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); sendfh = open(path, O_RDONLY); if( sendfh < 0 ) { printf("Error opening %s\n", path); 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: %lld\r\n" "Connection: close\r\n" "Date: %s\r\n" "EXT:\r\n" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n", size, date); if( h->reqflags & FLAG_XFERBACKGROUND ) { strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); } if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) && (sendfh > 0) ) { send_file(h, sendfh, offset, size); } close(sendfh); } error: sqlite3_free_table(result); } void SendResp_thumbnail(struct upnphttp * h, char * object) { char header[1500]; char sql_buf[256]; char **result; int rows; char *path; char date[30]; time_t curtime = time(NULL); ExifData *ed; ExifLoader *l; memset(header, 0, 1500); if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) { syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); Send406(h); return; } strip_ext(object); sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); if( !rows ) { syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); Send404(h); goto error; } path = result[1]; printf("Serving thumbnail for ObjectId: %s [%s]\n", object, path); if( access(path, 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); if( !ed || !ed->size ) { Send404(h); if( ed ) exif_data_unref(ed); goto error; } sprintf(header, "HTTP/1.1 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" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n", ed->size, date); if( h->reqflags & FLAG_XFERBACKGROUND ) { strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); } if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) ) { send_data(h, (char *)ed->data, ed->size); } exif_data_unref(ed); } error: sqlite3_free_table(result); } #if 0 //JPEG_RESIZE void SendResp_resizedimg(struct upnphttp * h, char * object) { char header[1500]; char sql_buf[256]; char **result; char date[30]; time_t curtime = time(NULL); 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 ) { syslog(LOG_NOTICE, "You can't specify transferMode as Streaming with a resized image!"); Send406(h); return; } sprintf(sql_buf, "SELECT d.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(result[3], F_OK) == 0 ) { strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 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( !imsrc ) { Send404(h); goto error; } 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" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n", h->HttpVer, size, date); 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"); } strcat(header, "\r\n"); n = send(h->socket, header, strlen(header), 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); } if( h->req_command == EHead ) { goto error; } n = send(h->socket, data, 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); } gdFree(data); } error: gdImageDestroy(imsrc); gdImageDestroy(imdst); sqlite3_free_table(result); } #endif void SendResp_dlnafile(struct upnphttp * h, char * object) { char header[1500]; char hdr_buf[512]; char sql_buf[256]; char **result; int rows; char date[30]; time_t curtime = time(NULL); off_t total, offset, size; char *path, *mime, *dlna; int sendfh; #if USE_FORK pid_t newpid = 0; #endif memset(header, 0, 1500); strip_ext(object); sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%s'", object); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); if( !rows ) { syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); Send404(h); sqlite3_free_table(result); return; } #if USE_FORK newpid = fork(); if( newpid ) return; #endif path = result[3]; mime = result[4]; dlna = result[5]; printf("Serving DetailID: %s [%s]\n", object, path); if( h->reqflags & FLAG_XFERSTREAMING ) { if( strncmp(mime, "image", 5) == 0 ) { syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); Send406(h); goto error; } } if( h->reqflags & FLAG_XFERINTERACTIVE ) { if( h->reqflags & FLAG_REALTIMEINFO ) { syslog(LOG_NOTICE, "Bad realTimeInfo flag with Interactive request!"); Send400(h); goto error; } if( strncmp(mime, "image", 5) != 0 ) { 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(path, O_RDONLY); if( sendfh < 0 ) { printf("Error opening %s\n", path); goto error; } 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'), 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) ) { syslog(LOG_NOTICE, "Specified range was invalid!"); Send400(h); close(sendfh); goto error; } if( h->req_RangeEnd > size ) { syslog(LOG_NOTICE, "Specified range was outside file boundaries!"); Send416(h); close(sendfh); goto error; } if( h->req_RangeEnd < size ) { total = h->req_RangeEnd - h->req_RangeStart + 1; 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: %llu\r\n" "Content-Range: bytes %lld-%llu/%llu\r\n", total, h->req_RangeStart, size-1, size); } } else { h->req_RangeEnd = size; total = size; sprintf(hdr_buf, "Content-Length: %llu\r\n", total); } strcat(header, hdr_buf); if( h->reqflags & FLAG_XFERSTREAMING ) { strcat(header, "transferMode.dlna.org: Streaming\r\n"); } else if( h->reqflags & FLAG_XFERBACKGROUND ) { if( strncmp(mime, "image", 5) == 0 ) strcat(header, "transferMode.dlna.org: Background\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { if( (strncmp(mime, "video", 5) == 0) || (strncmp(mime, "audio", 5) == 0) ) { strcat(header, "transferMode.dlna.org: Streaming\r\n"); } else { 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" "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)) == 0) && (h->req_command != EHead) && (sendfh > 0) ) { send_file(h, sendfh, offset, h->req_RangeEnd); } close(sendfh); error: sqlite3_free_table(result); #if USE_FORK _exit(0); #endif }