1 | /* MiniDLNA project |
---|
2 | * |
---|
3 | * http://sourceforge.net/projects/minidlna/ |
---|
4 | * |
---|
5 | * MiniDLNA media server |
---|
6 | * Copyright (C) 2008-2009 Justin Maggard |
---|
7 | * |
---|
8 | * This file is part of MiniDLNA. |
---|
9 | * |
---|
10 | * MiniDLNA is free software; you can redistribute it and/or modify |
---|
11 | * it under the terms of the GNU General Public License version 2 as |
---|
12 | * published by the Free Software Foundation. |
---|
13 | * |
---|
14 | * MiniDLNA is distributed in the hope that it will be useful, |
---|
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
17 | * GNU General Public License for more details. |
---|
18 | * |
---|
19 | * You should have received a copy of the GNU General Public License |
---|
20 | * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. |
---|
21 | * |
---|
22 | * Portions of the code from the MiniUPnP project: |
---|
23 | * |
---|
24 | * Copyright (c) 2006-2007, Thomas Bernard |
---|
25 | * All rights reserved. |
---|
26 | * |
---|
27 | * Redistribution and use in source and binary forms, with or without |
---|
28 | * modification, are permitted provided that the following conditions are met: |
---|
29 | * * Redistributions of source code must retain the above copyright |
---|
30 | * notice, this list of conditions and the following disclaimer. |
---|
31 | * * Redistributions in binary form must reproduce the above copyright |
---|
32 | * notice, this list of conditions and the following disclaimer in the |
---|
33 | * documentation and/or other materials provided with the distribution. |
---|
34 | * * The name of the author may not be used to endorse or promote products |
---|
35 | * derived from this software without specific prior written permission. |
---|
36 | * |
---|
37 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
---|
38 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
---|
39 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
---|
40 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
---|
41 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
---|
42 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
---|
43 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
---|
44 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
---|
45 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
---|
46 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
---|
47 | * POSSIBILITY OF SUCH DAMAGE. |
---|
48 | */ |
---|
49 | #include <stdlib.h> |
---|
50 | #include <unistd.h> |
---|
51 | #include <stdio.h> |
---|
52 | #include <string.h> |
---|
53 | #include <sys/types.h> |
---|
54 | #include <sys/socket.h> |
---|
55 | #include <sys/param.h> |
---|
56 | #include <ctype.h> |
---|
57 | #include "config.h" |
---|
58 | #include "upnphttp.h" |
---|
59 | #include "upnpdescgen.h" |
---|
60 | #include "minidlnapath.h" |
---|
61 | #include "upnpsoap.h" |
---|
62 | #include "upnpevents.h" |
---|
63 | |
---|
64 | #include <sys/types.h> |
---|
65 | #include <sys/stat.h> |
---|
66 | #include <fcntl.h> |
---|
67 | #include <errno.h> |
---|
68 | #include <sys/sendfile.h> |
---|
69 | #include <arpa/inet.h> |
---|
70 | |
---|
71 | #include "upnpglobalvars.h" |
---|
72 | #include "utils.h" |
---|
73 | #include "getifaddr.h" |
---|
74 | #include "image_utils.h" |
---|
75 | #include "log.h" |
---|
76 | #include "sql.h" |
---|
77 | #include <libexif/exif-loader.h> |
---|
78 | #ifdef TIVO_SUPPORT |
---|
79 | #include "tivo_utils.h" |
---|
80 | #include "tivo_commands.h" |
---|
81 | #endif |
---|
82 | //#define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much? |
---|
83 | #define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much? |
---|
84 | #define MIN_BUFFER_SIZE 65536 |
---|
85 | |
---|
86 | #include "icons.c" |
---|
87 | |
---|
88 | struct upnphttp * |
---|
89 | New_upnphttp(int s) |
---|
90 | { |
---|
91 | struct upnphttp * ret; |
---|
92 | if(s<0) |
---|
93 | return NULL; |
---|
94 | ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); |
---|
95 | if(ret == NULL) |
---|
96 | return NULL; |
---|
97 | memset(ret, 0, sizeof(struct upnphttp)); |
---|
98 | ret->socket = s; |
---|
99 | return ret; |
---|
100 | } |
---|
101 | |
---|
102 | void |
---|
103 | CloseSocket_upnphttp(struct upnphttp * h) |
---|
104 | { |
---|
105 | if(close(h->socket) < 0) |
---|
106 | { |
---|
107 | DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->socket, strerror(errno)); |
---|
108 | } |
---|
109 | h->socket = -1; |
---|
110 | h->state = 100; |
---|
111 | } |
---|
112 | |
---|
113 | void |
---|
114 | Delete_upnphttp(struct upnphttp * h) |
---|
115 | { |
---|
116 | if(h) |
---|
117 | { |
---|
118 | if(h->socket >= 0) |
---|
119 | CloseSocket_upnphttp(h); |
---|
120 | free(h->req_buf); |
---|
121 | free(h->res_buf); |
---|
122 | free(h); |
---|
123 | } |
---|
124 | } |
---|
125 | |
---|
126 | int |
---|
127 | SearchClientCache(struct in_addr addr, int quiet) |
---|
128 | { |
---|
129 | int i; |
---|
130 | for( i=0; i<CLIENT_CACHE_SLOTS; i++ ) |
---|
131 | { |
---|
132 | if( clients[i].addr.s_addr == addr.s_addr ) |
---|
133 | { |
---|
134 | /* Invalidate this client cache if it's older than 1 hour */ |
---|
135 | if( (time(NULL) - clients[i].age) > 3600 ) |
---|
136 | { |
---|
137 | unsigned char mac[6]; |
---|
138 | if( get_remote_mac(addr, mac) == 0 && |
---|
139 | memcmp(mac, clients[i].mac, 6) == 0 ) |
---|
140 | { |
---|
141 | /* Same MAC as last time when we were able to identify the client, |
---|
142 | * so extend the timeout by another hour. */ |
---|
143 | clients[i].age = time(NULL); |
---|
144 | } |
---|
145 | else |
---|
146 | { |
---|
147 | memset(&clients[i], 0, sizeof(struct client_cache_s)); |
---|
148 | return -1; |
---|
149 | } |
---|
150 | } |
---|
151 | if( !quiet ) |
---|
152 | DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n", |
---|
153 | clients[i].type, i); |
---|
154 | return i; |
---|
155 | } |
---|
156 | } |
---|
157 | return -1; |
---|
158 | } |
---|
159 | |
---|
160 | /* parse HttpHeaders of the REQUEST */ |
---|
161 | static void |
---|
162 | ParseHttpHeaders(struct upnphttp * h) |
---|
163 | { |
---|
164 | char * line; |
---|
165 | char * colon; |
---|
166 | char * p; |
---|
167 | int n; |
---|
168 | line = h->req_buf; |
---|
169 | /* TODO : check if req_buf, contentoff are ok */ |
---|
170 | while(line < (h->req_buf + h->req_contentoff)) |
---|
171 | { |
---|
172 | colon = strchr(line, ':'); |
---|
173 | if(colon) |
---|
174 | { |
---|
175 | if(strncasecmp(line, "Content-Length", 14)==0) |
---|
176 | { |
---|
177 | p = colon; |
---|
178 | while(*p < '0' || *p > '9') |
---|
179 | p++; |
---|
180 | h->req_contentlen = atoi(p); |
---|
181 | } |
---|
182 | else if(strncasecmp(line, "SOAPAction", 10)==0) |
---|
183 | { |
---|
184 | p = colon; |
---|
185 | n = 0; |
---|
186 | while(*p == ':' || *p == ' ' || *p == '\t') |
---|
187 | p++; |
---|
188 | while(p[n]>=' ') |
---|
189 | { |
---|
190 | n++; |
---|
191 | } |
---|
192 | if((p[0] == '"' && p[n-1] == '"') |
---|
193 | || (p[0] == '\'' && p[n-1] == '\'')) |
---|
194 | { |
---|
195 | p++; n -= 2; |
---|
196 | } |
---|
197 | h->req_soapAction = p; |
---|
198 | h->req_soapActionLen = n; |
---|
199 | } |
---|
200 | else if(strncasecmp(line, "Callback", 8)==0) |
---|
201 | { |
---|
202 | p = colon; |
---|
203 | while(*p != '<' && *p != '\r' ) |
---|
204 | p++; |
---|
205 | n = 0; |
---|
206 | while(p[n] != '>' && p[n] != '\r' ) |
---|
207 | n++; |
---|
208 | h->req_Callback = p + 1; |
---|
209 | h->req_CallbackLen = MAX(0, n - 1); |
---|
210 | } |
---|
211 | else if(strncasecmp(line, "SID", 3)==0) |
---|
212 | { |
---|
213 | //zqiu: fix bug for test 4.0.5 |
---|
214 | //Skip extra headers like "SIDHEADER: xxxxxx xxx" |
---|
215 | for(p=line+3;p<colon;p++) |
---|
216 | { |
---|
217 | if(!isspace(*p)) |
---|
218 | { |
---|
219 | p = NULL; //unexpected header |
---|
220 | break; |
---|
221 | } |
---|
222 | } |
---|
223 | if(p) { |
---|
224 | p = colon + 1; |
---|
225 | while(isspace(*p)) |
---|
226 | p++; |
---|
227 | n = 0; |
---|
228 | while(!isspace(p[n])) |
---|
229 | n++; |
---|
230 | h->req_SID = p; |
---|
231 | h->req_SIDLen = n; |
---|
232 | } |
---|
233 | } |
---|
234 | /* Timeout: Seconds-nnnn */ |
---|
235 | /* TIMEOUT |
---|
236 | Recommended. Requested duration until subscription expires, |
---|
237 | either number of seconds or infinite. Recommendation |
---|
238 | by a UPnP Forum working committee. Defined by UPnP vendor. |
---|
239 | Consists of the keyword "Second-" followed (without an |
---|
240 | intervening space) by either an integer or the keyword "infinite". */ |
---|
241 | else if(strncasecmp(line, "Timeout", 7)==0) |
---|
242 | { |
---|
243 | p = colon + 1; |
---|
244 | while(isspace(*p)) |
---|
245 | p++; |
---|
246 | if(strncasecmp(p, "Second-", 7)==0) { |
---|
247 | h->req_Timeout = atoi(p+7); |
---|
248 | } |
---|
249 | } |
---|
250 | // Range: bytes=xxx-yyy |
---|
251 | else if(strncasecmp(line, "Range", 5)==0) |
---|
252 | { |
---|
253 | p = colon + 1; |
---|
254 | while(isspace(*p)) |
---|
255 | p++; |
---|
256 | if(strncasecmp(p, "bytes=", 6)==0) { |
---|
257 | h->reqflags |= FLAG_RANGE; |
---|
258 | h->req_RangeStart = strtoll(p+6, &colon, 10); |
---|
259 | h->req_RangeEnd = colon ? atoll(colon+1) : 0; |
---|
260 | DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n", |
---|
261 | h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); |
---|
262 | } |
---|
263 | } |
---|
264 | else if(strncasecmp(line, "Host", 4)==0) |
---|
265 | { |
---|
266 | int i; |
---|
267 | h->reqflags |= FLAG_HOST; |
---|
268 | p = colon + 1; |
---|
269 | while(isspace(*p)) |
---|
270 | p++; |
---|
271 | for(n = 0; n<n_lan_addr; n++) |
---|
272 | { |
---|
273 | for(i=0; lan_addr[n].str[i]; i++) |
---|
274 | { |
---|
275 | if(lan_addr[n].str[i] != p[i]) |
---|
276 | break; |
---|
277 | } |
---|
278 | if(!lan_addr[n].str[i]) |
---|
279 | { |
---|
280 | h->iface = n; |
---|
281 | break; |
---|
282 | } |
---|
283 | } |
---|
284 | } |
---|
285 | else if(strncasecmp(line, "User-Agent", 10)==0) |
---|
286 | { |
---|
287 | /* Skip client detection if we already detected it. */ |
---|
288 | if( h->req_client ) |
---|
289 | goto next_header; |
---|
290 | p = colon + 1; |
---|
291 | while(isspace(*p)) |
---|
292 | p++; |
---|
293 | if(strncasecmp(p, "Xbox/", 5)==0) |
---|
294 | { |
---|
295 | h->req_client = EXbox; |
---|
296 | h->reqflags |= FLAG_MIME_AVI_AVI; |
---|
297 | h->reqflags |= FLAG_MS_PFS; |
---|
298 | } |
---|
299 | else if(strncmp(p, "PLAYSTATION", 11)==0) |
---|
300 | { |
---|
301 | h->req_client = EPS3; |
---|
302 | h->reqflags |= FLAG_DLNA; |
---|
303 | h->reqflags |= FLAG_MIME_AVI_DIVX; |
---|
304 | } |
---|
305 | else if(strstrc(p, "SEC_HHP_", '\r')) |
---|
306 | { |
---|
307 | h->req_client = ESamsungTV; |
---|
308 | h->reqflags |= FLAG_SAMSUNG; |
---|
309 | h->reqflags |= FLAG_DLNA; |
---|
310 | h->reqflags |= FLAG_NO_RESIZE; |
---|
311 | } |
---|
312 | else if(strncmp(p, "SamsungWiselinkPro", 18)==0) |
---|
313 | { |
---|
314 | h->req_client = ESamsungSeriesA; |
---|
315 | h->reqflags |= FLAG_SAMSUNG; |
---|
316 | h->reqflags |= FLAG_DLNA; |
---|
317 | h->reqflags |= FLAG_NO_RESIZE; |
---|
318 | } |
---|
319 | else if(strstrc(p, "bridgeCo-DMP/3", '\r')) |
---|
320 | { |
---|
321 | h->req_client = EDenonReceiver; |
---|
322 | h->reqflags |= FLAG_DLNA; |
---|
323 | } |
---|
324 | else if(strstrc(p, "fbxupnpav/", '\r')) |
---|
325 | { |
---|
326 | h->req_client = EFreeBox; |
---|
327 | } |
---|
328 | else if(strncmp(p, "SMP8634", 7)==0) |
---|
329 | { |
---|
330 | h->req_client = EPopcornHour; |
---|
331 | h->reqflags |= FLAG_MIME_FLAC_FLAC; |
---|
332 | } |
---|
333 | else if(strstrc(p, "Microsoft-IPTV-Client", '\r')) |
---|
334 | { |
---|
335 | h->req_client = EMediaRoom; |
---|
336 | h->reqflags |= FLAG_MS_PFS; |
---|
337 | } |
---|
338 | else if(strstrc(p, "LGE_DLNA_SDK", '\r')) |
---|
339 | { |
---|
340 | h->req_client = ELGDevice; |
---|
341 | h->reqflags |= FLAG_DLNA; |
---|
342 | } |
---|
343 | else if(strncmp(p, "Verismo,", 8)==0) |
---|
344 | { |
---|
345 | h->req_client = ENetgearEVA2000; |
---|
346 | h->reqflags |= FLAG_MS_PFS; |
---|
347 | } |
---|
348 | else if(strstrc(p, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r')) |
---|
349 | { |
---|
350 | h->req_client = EToshibaTV; |
---|
351 | h->reqflags |= FLAG_DLNA; |
---|
352 | } |
---|
353 | else if(strstrc(p, "DLNADOC/1.50", '\r')) |
---|
354 | { |
---|
355 | h->req_client = EStandardDLNA150; |
---|
356 | h->reqflags |= FLAG_DLNA; |
---|
357 | h->reqflags |= FLAG_MIME_AVI_AVI; |
---|
358 | } |
---|
359 | } |
---|
360 | else if(strncasecmp(line, "X-AV-Client-Info", 16)==0) |
---|
361 | { |
---|
362 | /* Skip client detection if we already detected it. */ |
---|
363 | if( h->req_client && h->req_client < EStandardDLNA150 ) |
---|
364 | goto next_header; |
---|
365 | p = colon + 1; |
---|
366 | while(isspace(*p)) |
---|
367 | p++; |
---|
368 | if(strstrc(p, "PLAYSTATION 3", '\r')) |
---|
369 | { |
---|
370 | h->req_client = EPS3; |
---|
371 | h->reqflags |= FLAG_DLNA; |
---|
372 | h->reqflags |= FLAG_MIME_AVI_DIVX; |
---|
373 | } |
---|
374 | /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */ |
---|
375 | /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */ |
---|
376 | /* Sony SMP-100 needs the same treatment as their BDP-S370 */ |
---|
377 | /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */ |
---|
378 | else if(strstrc(p, "Blu-ray Disc Player", '\r') || |
---|
379 | strstrc(p, "BLU-RAY HOME THEATRE SYSTEM", '\r') || |
---|
380 | strstrc(p, "Media Player", '\r')) |
---|
381 | { |
---|
382 | h->req_client = ESonyBDP; |
---|
383 | h->reqflags |= FLAG_DLNA; |
---|
384 | } |
---|
385 | /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */ |
---|
386 | /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */ |
---|
387 | else if(strstrc(p, "BRAVIA", '\r') || |
---|
388 | strstrc(p, "INTERNET TV", '\r')) |
---|
389 | { |
---|
390 | h->req_client = ESonyBravia; |
---|
391 | h->reqflags |= FLAG_DLNA; |
---|
392 | } |
---|
393 | } |
---|
394 | else if(strncasecmp(line, "Transfer-Encoding", 17)==0) |
---|
395 | { |
---|
396 | p = colon + 1; |
---|
397 | while(isspace(*p)) |
---|
398 | p++; |
---|
399 | if(strncasecmp(p, "chunked", 7)==0) |
---|
400 | { |
---|
401 | h->reqflags |= FLAG_CHUNKED; |
---|
402 | } |
---|
403 | } |
---|
404 | else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0) |
---|
405 | { |
---|
406 | p = colon + 1; |
---|
407 | while(isspace(*p)) |
---|
408 | p++; |
---|
409 | if( (*p != '1') || !isspace(p[1]) ) |
---|
410 | h->reqflags |= FLAG_INVALID_REQ; |
---|
411 | } |
---|
412 | else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0) |
---|
413 | { |
---|
414 | h->reqflags |= FLAG_TIMESEEK; |
---|
415 | } |
---|
416 | else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0) |
---|
417 | { |
---|
418 | h->reqflags |= FLAG_PLAYSPEED; |
---|
419 | } |
---|
420 | else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0) |
---|
421 | { |
---|
422 | h->reqflags |= FLAG_REALTIMEINFO; |
---|
423 | } |
---|
424 | else if(strncasecmp(line, "transferMode.dlna.org", 21)==0) |
---|
425 | { |
---|
426 | p = colon + 1; |
---|
427 | while(isspace(*p)) |
---|
428 | p++; |
---|
429 | if(strncasecmp(p, "Streaming", 9)==0) |
---|
430 | { |
---|
431 | h->reqflags |= FLAG_XFERSTREAMING; |
---|
432 | } |
---|
433 | if(strncasecmp(p, "Interactive", 11)==0) |
---|
434 | { |
---|
435 | h->reqflags |= FLAG_XFERINTERACTIVE; |
---|
436 | } |
---|
437 | if(strncasecmp(p, "Background", 10)==0) |
---|
438 | { |
---|
439 | h->reqflags |= FLAG_XFERBACKGROUND; |
---|
440 | } |
---|
441 | } |
---|
442 | else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0) |
---|
443 | { |
---|
444 | h->reqflags |= FLAG_CAPTION; |
---|
445 | } |
---|
446 | } |
---|
447 | next_header: |
---|
448 | while(!(line[0] == '\r' && line[1] == '\n')) |
---|
449 | line++; |
---|
450 | line += 2; |
---|
451 | } |
---|
452 | if( h->reqflags & FLAG_CHUNKED ) |
---|
453 | { |
---|
454 | char *endptr; |
---|
455 | h->req_chunklen = -1; |
---|
456 | if( h->req_buflen <= h->req_contentoff ) |
---|
457 | return; |
---|
458 | while( (line < (h->req_buf + h->req_buflen)) && |
---|
459 | (h->req_chunklen = strtol(line, &endptr, 16)) && |
---|
460 | (endptr != line) ) |
---|
461 | { |
---|
462 | while(!(endptr[0] == '\r' && endptr[1] == '\n')) |
---|
463 | { |
---|
464 | endptr++; |
---|
465 | } |
---|
466 | line = endptr+h->req_chunklen+2; |
---|
467 | } |
---|
468 | |
---|
469 | if( endptr == line ) |
---|
470 | { |
---|
471 | h->req_chunklen = -1; |
---|
472 | return; |
---|
473 | } |
---|
474 | } |
---|
475 | /* If the client type wasn't found, search the cache. |
---|
476 | * This is done because a lot of clients like to send a |
---|
477 | * different User-Agent with different types of requests. */ |
---|
478 | n = SearchClientCache(h->clientaddr, 0); |
---|
479 | if( h->req_client ) |
---|
480 | { |
---|
481 | /* Add this client to the cache if it's not there already. */ |
---|
482 | if( n < 0 ) |
---|
483 | { |
---|
484 | for( n=0; n<CLIENT_CACHE_SLOTS; n++ ) |
---|
485 | { |
---|
486 | if( clients[n].addr.s_addr ) |
---|
487 | continue; |
---|
488 | get_remote_mac(h->clientaddr, clients[n].mac); |
---|
489 | clients[n].addr = h->clientaddr; |
---|
490 | DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", |
---|
491 | h->req_client, inet_ntoa(clients[n].addr), |
---|
492 | clients[n].mac[0], clients[n].mac[1], clients[n].mac[2], |
---|
493 | clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n); |
---|
494 | break; |
---|
495 | } |
---|
496 | } |
---|
497 | else if( (clients[n].type < EStandardDLNA150 && h->req_client == EStandardDLNA150) || |
---|
498 | (clients[n].type == ESamsungSeriesB && h->req_client == ESamsungSeriesA) ) |
---|
499 | { |
---|
500 | /* If we know the client and our new detection is generic, use our cached info */ |
---|
501 | /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */ |
---|
502 | h->reqflags |= clients[n].flags; |
---|
503 | h->req_client = clients[n].type; |
---|
504 | return; |
---|
505 | } |
---|
506 | clients[n].type = h->req_client; |
---|
507 | clients[n].flags = h->reqflags & 0xFFF00000; |
---|
508 | clients[n].age = time(NULL); |
---|
509 | } |
---|
510 | else if( n >= 0 ) |
---|
511 | { |
---|
512 | h->reqflags |= clients[n].flags; |
---|
513 | h->req_client = clients[n].type; |
---|
514 | } |
---|
515 | } |
---|
516 | |
---|
517 | /* very minimalistic 400 error message */ |
---|
518 | static void |
---|
519 | Send400(struct upnphttp * h) |
---|
520 | { |
---|
521 | static const char body400[] = |
---|
522 | "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>" |
---|
523 | "<BODY><H1>Bad Request</H1>The request is invalid" |
---|
524 | " for this HTTP version.</BODY></HTML>\r\n"; |
---|
525 | h->respflags = FLAG_HTML; |
---|
526 | BuildResp2_upnphttp(h, 400, "Bad Request", |
---|
527 | body400, sizeof(body400) - 1); |
---|
528 | SendResp_upnphttp(h); |
---|
529 | CloseSocket_upnphttp(h); |
---|
530 | } |
---|
531 | |
---|
532 | /* very minimalistic 404 error message */ |
---|
533 | static void |
---|
534 | Send404(struct upnphttp * h) |
---|
535 | { |
---|
536 | static const char body404[] = |
---|
537 | "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" |
---|
538 | "<BODY><H1>Not Found</H1>The requested URL was not found" |
---|
539 | " on this server.</BODY></HTML>\r\n"; |
---|
540 | h->respflags = FLAG_HTML; |
---|
541 | BuildResp2_upnphttp(h, 404, "Not Found", |
---|
542 | body404, sizeof(body404) - 1); |
---|
543 | SendResp_upnphttp(h); |
---|
544 | CloseSocket_upnphttp(h); |
---|
545 | } |
---|
546 | |
---|
547 | /* very minimalistic 406 error message */ |
---|
548 | static void |
---|
549 | Send406(struct upnphttp * h) |
---|
550 | { |
---|
551 | static const char body406[] = |
---|
552 | "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>" |
---|
553 | "<BODY><H1>Not Acceptable</H1>An unsupported operation" |
---|
554 | " was requested.</BODY></HTML>\r\n"; |
---|
555 | h->respflags = FLAG_HTML; |
---|
556 | BuildResp2_upnphttp(h, 406, "Not Acceptable", |
---|
557 | body406, sizeof(body406) - 1); |
---|
558 | SendResp_upnphttp(h); |
---|
559 | CloseSocket_upnphttp(h); |
---|
560 | } |
---|
561 | |
---|
562 | /* very minimalistic 416 error message */ |
---|
563 | static void |
---|
564 | Send416(struct upnphttp * h) |
---|
565 | { |
---|
566 | static const char body416[] = |
---|
567 | "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>" |
---|
568 | "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range" |
---|
569 | " was outside the file's size.</BODY></HTML>\r\n"; |
---|
570 | h->respflags = FLAG_HTML; |
---|
571 | BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable", |
---|
572 | body416, sizeof(body416) - 1); |
---|
573 | SendResp_upnphttp(h); |
---|
574 | CloseSocket_upnphttp(h); |
---|
575 | } |
---|
576 | |
---|
577 | /* very minimalistic 500 error message */ |
---|
578 | void |
---|
579 | Send500(struct upnphttp * h) |
---|
580 | { |
---|
581 | static const char body500[] = |
---|
582 | "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>" |
---|
583 | "<BODY><H1>Internal Server Error</H1>Server encountered " |
---|
584 | "and Internal Error.</BODY></HTML>\r\n"; |
---|
585 | h->respflags = FLAG_HTML; |
---|
586 | BuildResp2_upnphttp(h, 500, "Internal Server Errror", |
---|
587 | body500, sizeof(body500) - 1); |
---|
588 | SendResp_upnphttp(h); |
---|
589 | CloseSocket_upnphttp(h); |
---|
590 | } |
---|
591 | |
---|
592 | /* very minimalistic 501 error message */ |
---|
593 | void |
---|
594 | Send501(struct upnphttp * h) |
---|
595 | { |
---|
596 | static const char body501[] = |
---|
597 | "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" |
---|
598 | "<BODY><H1>Not Implemented</H1>The HTTP Method " |
---|
599 | "is not implemented by this server.</BODY></HTML>\r\n"; |
---|
600 | h->respflags = FLAG_HTML; |
---|
601 | BuildResp2_upnphttp(h, 501, "Not Implemented", |
---|
602 | body501, sizeof(body501) - 1); |
---|
603 | SendResp_upnphttp(h); |
---|
604 | CloseSocket_upnphttp(h); |
---|
605 | } |
---|
606 | |
---|
607 | static const char * |
---|
608 | findendheaders(const char * s, int len) |
---|
609 | { |
---|
610 | while(len-- > 0) |
---|
611 | { |
---|
612 | if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') |
---|
613 | return s; |
---|
614 | s++; |
---|
615 | } |
---|
616 | return NULL; |
---|
617 | } |
---|
618 | |
---|
619 | /* Sends the description generated by the parameter */ |
---|
620 | static void |
---|
621 | sendXMLdesc(struct upnphttp * h, char * (f)(int *)) |
---|
622 | { |
---|
623 | char * desc; |
---|
624 | int len; |
---|
625 | desc = f(&len); |
---|
626 | if(!desc) |
---|
627 | { |
---|
628 | DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n"); |
---|
629 | Send500(h); |
---|
630 | return; |
---|
631 | } |
---|
632 | BuildResp_upnphttp(h, desc, len); |
---|
633 | SendResp_upnphttp(h); |
---|
634 | CloseSocket_upnphttp(h); |
---|
635 | free(desc); |
---|
636 | } |
---|
637 | |
---|
638 | /* ProcessHTTPPOST_upnphttp() |
---|
639 | * executes the SOAP query if it is possible */ |
---|
640 | static void |
---|
641 | ProcessHTTPPOST_upnphttp(struct upnphttp * h) |
---|
642 | { |
---|
643 | if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) |
---|
644 | { |
---|
645 | if(h->req_soapAction) |
---|
646 | { |
---|
647 | /* we can process the request */ |
---|
648 | DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction); |
---|
649 | ExecuteSoapAction(h, |
---|
650 | h->req_soapAction, |
---|
651 | h->req_soapActionLen); |
---|
652 | } |
---|
653 | else |
---|
654 | { |
---|
655 | static const char err400str[] = |
---|
656 | "<html><body>Bad request</body></html>"; |
---|
657 | DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n"); |
---|
658 | h->respflags = FLAG_HTML; |
---|
659 | BuildResp2_upnphttp(h, 400, "Bad Request", |
---|
660 | err400str, sizeof(err400str) - 1); |
---|
661 | SendResp_upnphttp(h); |
---|
662 | CloseSocket_upnphttp(h); |
---|
663 | } |
---|
664 | } |
---|
665 | else |
---|
666 | { |
---|
667 | /* waiting for remaining data */ |
---|
668 | h->state = 1; |
---|
669 | } |
---|
670 | } |
---|
671 | |
---|
672 | static void |
---|
673 | ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) |
---|
674 | { |
---|
675 | const char * sid; |
---|
676 | DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path); |
---|
677 | DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n", |
---|
678 | h->req_CallbackLen, h->req_Callback, h->req_Timeout); |
---|
679 | DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); |
---|
680 | if(!h->req_Callback && !h->req_SID) { |
---|
681 | /* Missing or invalid CALLBACK : 412 Precondition Failed. |
---|
682 | * If CALLBACK header is missing or does not contain a valid HTTP URL, |
---|
683 | * the publisher must respond with HTTP error 412 Precondition Failed*/ |
---|
684 | BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
---|
685 | SendResp_upnphttp(h); |
---|
686 | CloseSocket_upnphttp(h); |
---|
687 | } else { |
---|
688 | /* - add to the subscriber list |
---|
689 | * - respond HTTP/x.x 200 OK |
---|
690 | * - Send the initial event message */ |
---|
691 | /* Server:, SID:; Timeout: Second-(xx|infinite) */ |
---|
692 | if(h->req_Callback) { |
---|
693 | sid = upnpevents_addSubscriber(path, h->req_Callback, |
---|
694 | h->req_CallbackLen, h->req_Timeout); |
---|
695 | h->respflags = FLAG_TIMEOUT; |
---|
696 | if(sid) { |
---|
697 | DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid); |
---|
698 | h->respflags |= FLAG_SID; |
---|
699 | h->req_SID = sid; |
---|
700 | h->req_SIDLen = strlen(sid); |
---|
701 | } |
---|
702 | BuildResp_upnphttp(h, 0, 0); |
---|
703 | } else { |
---|
704 | /* subscription renew */ |
---|
705 | /* Invalid SID |
---|
706 | 412 Precondition Failed. If a SID does not correspond to a known, |
---|
707 | un-expired subscription, the publisher must respond |
---|
708 | with HTTP error 412 Precondition Failed. */ |
---|
709 | if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { |
---|
710 | BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
---|
711 | } else { |
---|
712 | /* A DLNA device must enforce a 5 minute timeout */ |
---|
713 | h->respflags = FLAG_TIMEOUT; |
---|
714 | h->req_Timeout = 300; |
---|
715 | h->respflags |= FLAG_SID; |
---|
716 | BuildResp_upnphttp(h, 0, 0); |
---|
717 | } |
---|
718 | } |
---|
719 | SendResp_upnphttp(h); |
---|
720 | CloseSocket_upnphttp(h); |
---|
721 | } |
---|
722 | } |
---|
723 | |
---|
724 | static void |
---|
725 | ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) |
---|
726 | { |
---|
727 | DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path); |
---|
728 | DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); |
---|
729 | /* Remove from the list */ |
---|
730 | if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) { |
---|
731 | BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
---|
732 | } else { |
---|
733 | BuildResp_upnphttp(h, 0, 0); |
---|
734 | } |
---|
735 | SendResp_upnphttp(h); |
---|
736 | CloseSocket_upnphttp(h); |
---|
737 | } |
---|
738 | |
---|
739 | /* Parse and process Http Query |
---|
740 | * called once all the HTTP headers have been received. */ |
---|
741 | static void |
---|
742 | ProcessHttpQuery_upnphttp(struct upnphttp * h) |
---|
743 | { |
---|
744 | char HttpCommand[16]; |
---|
745 | char HttpUrl[512]; |
---|
746 | char * HttpVer; |
---|
747 | char * p; |
---|
748 | int i; |
---|
749 | p = h->req_buf; |
---|
750 | if(!p) |
---|
751 | return; |
---|
752 | for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) |
---|
753 | HttpCommand[i] = *(p++); |
---|
754 | HttpCommand[i] = '\0'; |
---|
755 | while(*p==' ') |
---|
756 | p++; |
---|
757 | if(strncmp(p, "http://", 7) == 0) |
---|
758 | { |
---|
759 | p = p+7; |
---|
760 | while(*p!='/') |
---|
761 | p++; |
---|
762 | } |
---|
763 | for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++) |
---|
764 | HttpUrl[i] = *(p++); |
---|
765 | HttpUrl[i] = '\0'; |
---|
766 | while(*p==' ') |
---|
767 | p++; |
---|
768 | HttpVer = h->HttpVer; |
---|
769 | for(i = 0; i<15 && *p != '\r'; i++) |
---|
770 | HttpVer[i] = *(p++); |
---|
771 | HttpVer[i] = '\0'; |
---|
772 | /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n", |
---|
773 | HttpCommand, HttpUrl, HttpVer);*/ |
---|
774 | ParseHttpHeaders(h); |
---|
775 | |
---|
776 | /* see if we need to wait for remaining data */ |
---|
777 | if( (h->reqflags & FLAG_CHUNKED) ) |
---|
778 | { |
---|
779 | if( h->req_chunklen ) |
---|
780 | { |
---|
781 | h->state = 2; |
---|
782 | return; |
---|
783 | } |
---|
784 | char *chunkstart, *chunk, *endptr, *endbuf; |
---|
785 | chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff; |
---|
786 | |
---|
787 | while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) ) |
---|
788 | { |
---|
789 | while(!(endptr[0] == '\r' && endptr[1] == '\n')) |
---|
790 | { |
---|
791 | endptr++; |
---|
792 | } |
---|
793 | endptr += 2; |
---|
794 | |
---|
795 | memmove(endbuf, endptr, h->req_chunklen); |
---|
796 | |
---|
797 | endbuf += h->req_chunklen; |
---|
798 | chunk = endptr + h->req_chunklen; |
---|
799 | } |
---|
800 | h->req_contentlen = endbuf - chunkstart; |
---|
801 | h->req_buflen = endbuf - h->req_buf; |
---|
802 | h->state = 100; |
---|
803 | } |
---|
804 | |
---|
805 | DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); |
---|
806 | if(strcmp("POST", HttpCommand) == 0) |
---|
807 | { |
---|
808 | h->req_command = EPost; |
---|
809 | ProcessHTTPPOST_upnphttp(h); |
---|
810 | } |
---|
811 | else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0)) |
---|
812 | { |
---|
813 | if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) ) |
---|
814 | { |
---|
815 | DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n"); |
---|
816 | Send400(h); |
---|
817 | return; |
---|
818 | } |
---|
819 | #if 1 /* 7.3.33.4 */ |
---|
820 | else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) && |
---|
821 | !(h->reqflags & FLAG_RANGE) ) |
---|
822 | { |
---|
823 | DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n", |
---|
824 | h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed"); |
---|
825 | Send406(h); |
---|
826 | return; |
---|
827 | } |
---|
828 | #endif |
---|
829 | else if(strcmp("GET", HttpCommand) == 0) |
---|
830 | { |
---|
831 | h->req_command = EGet; |
---|
832 | } |
---|
833 | else |
---|
834 | { |
---|
835 | h->req_command = EHead; |
---|
836 | } |
---|
837 | if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) |
---|
838 | { |
---|
839 | /* If it's a Xbox360, we might need a special friendly_name to be recognized */ |
---|
840 | if( (h->req_client == EXbox) && !strchr(friendly_name, ':') ) |
---|
841 | { |
---|
842 | i = strlen(friendly_name); |
---|
843 | snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1"); |
---|
844 | sendXMLdesc(h, genRootDesc); |
---|
845 | friendly_name[i] = '\0'; |
---|
846 | } |
---|
847 | else |
---|
848 | { |
---|
849 | sendXMLdesc(h, genRootDesc); |
---|
850 | } |
---|
851 | } |
---|
852 | else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) |
---|
853 | { |
---|
854 | sendXMLdesc(h, genContentDirectory); |
---|
855 | } |
---|
856 | else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0) |
---|
857 | { |
---|
858 | sendXMLdesc(h, genConnectionManager); |
---|
859 | } |
---|
860 | else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0) |
---|
861 | { |
---|
862 | sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); |
---|
863 | } |
---|
864 | else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) |
---|
865 | { |
---|
866 | SendResp_dlnafile(h, HttpUrl+12); |
---|
867 | CloseSocket_upnphttp(h); |
---|
868 | } |
---|
869 | else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) |
---|
870 | { |
---|
871 | SendResp_thumbnail(h, HttpUrl+12); |
---|
872 | } |
---|
873 | else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) |
---|
874 | { |
---|
875 | SendResp_albumArt(h, HttpUrl+10); |
---|
876 | CloseSocket_upnphttp(h); |
---|
877 | } |
---|
878 | #ifdef TIVO_SUPPORT |
---|
879 | else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0) |
---|
880 | { |
---|
881 | if( GETFLAG(TIVO_MASK) ) |
---|
882 | { |
---|
883 | if( *(HttpUrl+12) == '?' ) |
---|
884 | { |
---|
885 | ProcessTiVoCommand(h, HttpUrl+13); |
---|
886 | } |
---|
887 | else |
---|
888 | { |
---|
889 | DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12); |
---|
890 | Send404(h); |
---|
891 | } |
---|
892 | } |
---|
893 | else |
---|
894 | { |
---|
895 | DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n", |
---|
896 | HttpUrl+12); |
---|
897 | Send404(h); |
---|
898 | } |
---|
899 | } |
---|
900 | #endif |
---|
901 | else if(strncmp(HttpUrl, "/Resized/", 9) == 0) |
---|
902 | { |
---|
903 | SendResp_resizedimg(h, HttpUrl+9); |
---|
904 | CloseSocket_upnphttp(h); |
---|
905 | } |
---|
906 | else if(strncmp(HttpUrl, "/icons/", 7) == 0) |
---|
907 | { |
---|
908 | SendResp_icon(h, HttpUrl+7); |
---|
909 | CloseSocket_upnphttp(h); |
---|
910 | } |
---|
911 | else if(strncmp(HttpUrl, "/Captions/", 10) == 0) |
---|
912 | { |
---|
913 | SendResp_caption(h, HttpUrl+10); |
---|
914 | CloseSocket_upnphttp(h); |
---|
915 | } |
---|
916 | else |
---|
917 | { |
---|
918 | DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl); |
---|
919 | Send404(h); |
---|
920 | } |
---|
921 | } |
---|
922 | else if(strcmp("SUBSCRIBE", HttpCommand) == 0) |
---|
923 | { |
---|
924 | h->req_command = ESubscribe; |
---|
925 | ProcessHTTPSubscribe_upnphttp(h, HttpUrl); |
---|
926 | } |
---|
927 | else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) |
---|
928 | { |
---|
929 | h->req_command = EUnSubscribe; |
---|
930 | ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); |
---|
931 | } |
---|
932 | else |
---|
933 | { |
---|
934 | DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand); |
---|
935 | Send501(h); |
---|
936 | } |
---|
937 | } |
---|
938 | |
---|
939 | |
---|
940 | void |
---|
941 | Process_upnphttp(struct upnphttp * h) |
---|
942 | { |
---|
943 | char buf[2048]; |
---|
944 | int n; |
---|
945 | if(!h) |
---|
946 | return; |
---|
947 | switch(h->state) |
---|
948 | { |
---|
949 | case 0: |
---|
950 | n = recv(h->socket, buf, 2048, 0); |
---|
951 | if(n<0) |
---|
952 | { |
---|
953 | DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno)); |
---|
954 | h->state = 100; |
---|
955 | } |
---|
956 | else if(n==0) |
---|
957 | { |
---|
958 | DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); |
---|
959 | h->state = 100; |
---|
960 | } |
---|
961 | else |
---|
962 | { |
---|
963 | const char * endheaders; |
---|
964 | /* if 1st arg of realloc() is null, |
---|
965 | * realloc behaves the same as malloc() */ |
---|
966 | h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); |
---|
967 | memcpy(h->req_buf + h->req_buflen, buf, n); |
---|
968 | h->req_buflen += n; |
---|
969 | h->req_buf[h->req_buflen] = '\0'; |
---|
970 | /* search for the string "\r\n\r\n" */ |
---|
971 | endheaders = findendheaders(h->req_buf, h->req_buflen); |
---|
972 | if(endheaders) |
---|
973 | { |
---|
974 | h->req_contentoff = endheaders - h->req_buf + 4; |
---|
975 | h->req_contentlen = h->req_buflen - h->req_contentoff; |
---|
976 | ProcessHttpQuery_upnphttp(h); |
---|
977 | } |
---|
978 | } |
---|
979 | break; |
---|
980 | case 1: |
---|
981 | case 2: |
---|
982 | n = recv(h->socket, buf, 2048, 0); |
---|
983 | if(n<0) |
---|
984 | { |
---|
985 | DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno)); |
---|
986 | h->state = 100; |
---|
987 | } |
---|
988 | else if(n==0) |
---|
989 | { |
---|
990 | DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); |
---|
991 | h->state = 100; |
---|
992 | } |
---|
993 | else |
---|
994 | { |
---|
995 | /*fwrite(buf, 1, n, stdout);*/ /* debug */ |
---|
996 | h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); |
---|
997 | memcpy(h->req_buf + h->req_buflen, buf, n); |
---|
998 | h->req_buflen += n; |
---|
999 | if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) |
---|
1000 | { |
---|
1001 | /* Need the struct to point to the realloc'd memory locations */ |
---|
1002 | if( h->state == 1 ) |
---|
1003 | { |
---|
1004 | ParseHttpHeaders(h); |
---|
1005 | ProcessHTTPPOST_upnphttp(h); |
---|
1006 | } |
---|
1007 | else if( h->state == 2 ) |
---|
1008 | { |
---|
1009 | ProcessHttpQuery_upnphttp(h); |
---|
1010 | } |
---|
1011 | } |
---|
1012 | } |
---|
1013 | break; |
---|
1014 | default: |
---|
1015 | DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state); |
---|
1016 | } |
---|
1017 | } |
---|
1018 | |
---|
1019 | static const char httpresphead[] = |
---|
1020 | "%s %d %s\r\n" |
---|
1021 | "Content-Type: %s\r\n" |
---|
1022 | "Connection: close\r\n" |
---|
1023 | "Content-Length: %d\r\n" |
---|
1024 | "Server: " MINIDLNA_SERVER_STRING "\r\n" |
---|
1025 | // "Accept-Ranges: bytes\r\n" |
---|
1026 | ; /*"\r\n";*/ |
---|
1027 | /* |
---|
1028 | "<?xml version=\"1.0\"?>\n" |
---|
1029 | "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " |
---|
1030 | "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" |
---|
1031 | "<s:Body>" |
---|
1032 | |
---|
1033 | "</s:Body>" |
---|
1034 | "</s:Envelope>"; |
---|
1035 | */ |
---|
1036 | /* with response code and response message |
---|
1037 | * also allocate enough memory */ |
---|
1038 | |
---|
1039 | void |
---|
1040 | BuildHeader_upnphttp(struct upnphttp * h, int respcode, |
---|
1041 | const char * respmsg, |
---|
1042 | int bodylen) |
---|
1043 | { |
---|
1044 | int templen; |
---|
1045 | if(!h->res_buf) |
---|
1046 | { |
---|
1047 | templen = sizeof(httpresphead) + 192 + bodylen; |
---|
1048 | h->res_buf = (char *)malloc(templen); |
---|
1049 | h->res_buf_alloclen = templen; |
---|
1050 | } |
---|
1051 | h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, |
---|
1052 | //httpresphead, h->HttpVer, |
---|
1053 | httpresphead, "HTTP/1.1", |
---|
1054 | respcode, respmsg, |
---|
1055 | (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", |
---|
1056 | bodylen); |
---|
1057 | /* Additional headers */ |
---|
1058 | if(h->respflags & FLAG_TIMEOUT) { |
---|
1059 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1060 | h->res_buf_alloclen - h->res_buflen, |
---|
1061 | "Timeout: Second-"); |
---|
1062 | if(h->req_Timeout) { |
---|
1063 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1064 | h->res_buf_alloclen - h->res_buflen, |
---|
1065 | "%d\r\n", h->req_Timeout); |
---|
1066 | } else { |
---|
1067 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1068 | h->res_buf_alloclen - h->res_buflen, |
---|
1069 | "300\r\n"); |
---|
1070 | //JM DLNA must force to 300 - "infinite\r\n"); |
---|
1071 | } |
---|
1072 | } |
---|
1073 | if(h->respflags & FLAG_SID) { |
---|
1074 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1075 | h->res_buf_alloclen - h->res_buflen, |
---|
1076 | "SID: %.*s\r\n", h->req_SIDLen, h->req_SID); |
---|
1077 | } |
---|
1078 | #if 0 // DLNA |
---|
1079 | char szTime[30]; |
---|
1080 | time_t curtime = time(NULL); |
---|
1081 | strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1082 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1083 | h->res_buf_alloclen - h->res_buflen, |
---|
1084 | "Date: %s\r\n", szTime); |
---|
1085 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1086 | h->res_buf_alloclen - h->res_buflen, |
---|
1087 | "contentFeatures.dlna.org: \r\n"); |
---|
1088 | h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
---|
1089 | h->res_buf_alloclen - h->res_buflen, |
---|
1090 | "EXT:\r\n"); |
---|
1091 | #endif |
---|
1092 | h->res_buf[h->res_buflen++] = '\r'; |
---|
1093 | h->res_buf[h->res_buflen++] = '\n'; |
---|
1094 | if(h->res_buf_alloclen < (h->res_buflen + bodylen)) |
---|
1095 | { |
---|
1096 | h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); |
---|
1097 | h->res_buf_alloclen = h->res_buflen + bodylen; |
---|
1098 | } |
---|
1099 | } |
---|
1100 | |
---|
1101 | void |
---|
1102 | BuildResp2_upnphttp(struct upnphttp * h, int respcode, |
---|
1103 | const char * respmsg, |
---|
1104 | const char * body, int bodylen) |
---|
1105 | { |
---|
1106 | BuildHeader_upnphttp(h, respcode, respmsg, bodylen); |
---|
1107 | if( h->req_command == EHead ) |
---|
1108 | return; |
---|
1109 | if(body) |
---|
1110 | memcpy(h->res_buf + h->res_buflen, body, bodylen); |
---|
1111 | h->res_buflen += bodylen; |
---|
1112 | } |
---|
1113 | |
---|
1114 | /* responding 200 OK ! */ |
---|
1115 | void |
---|
1116 | BuildResp_upnphttp(struct upnphttp * h, |
---|
1117 | const char * body, int bodylen) |
---|
1118 | { |
---|
1119 | BuildResp2_upnphttp(h, 200, "OK", body, bodylen); |
---|
1120 | } |
---|
1121 | |
---|
1122 | void |
---|
1123 | SendResp_upnphttp(struct upnphttp * h) |
---|
1124 | { |
---|
1125 | int n; |
---|
1126 | DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf); |
---|
1127 | n = send(h->socket, h->res_buf, h->res_buflen, 0); |
---|
1128 | if(n<0) |
---|
1129 | { |
---|
1130 | DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); |
---|
1131 | } |
---|
1132 | else if(n < h->res_buflen) |
---|
1133 | { |
---|
1134 | /* TODO : handle correctly this case */ |
---|
1135 | DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", |
---|
1136 | n, h->res_buflen); |
---|
1137 | } |
---|
1138 | } |
---|
1139 | |
---|
1140 | int |
---|
1141 | send_data(struct upnphttp * h, char * header, size_t size, int flags) |
---|
1142 | { |
---|
1143 | int n; |
---|
1144 | |
---|
1145 | n = send(h->socket, header, size, flags); |
---|
1146 | if(n<0) |
---|
1147 | { |
---|
1148 | DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); |
---|
1149 | } |
---|
1150 | else if(n < h->res_buflen) |
---|
1151 | { |
---|
1152 | /* TODO : handle correctly this case */ |
---|
1153 | DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", |
---|
1154 | n, h->res_buflen); |
---|
1155 | } |
---|
1156 | else |
---|
1157 | { |
---|
1158 | return 0; |
---|
1159 | } |
---|
1160 | return 1; |
---|
1161 | } |
---|
1162 | |
---|
1163 | void |
---|
1164 | send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) |
---|
1165 | { |
---|
1166 | off_t send_size; |
---|
1167 | off_t ret; |
---|
1168 | char *buf = NULL; |
---|
1169 | int try_sendfile = 1; |
---|
1170 | |
---|
1171 | while( offset < end_offset ) |
---|
1172 | { |
---|
1173 | if( try_sendfile ) |
---|
1174 | { |
---|
1175 | send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); |
---|
1176 | ret = sendfile(h->socket, sendfd, &offset, send_size); |
---|
1177 | if( ret == -1 ) |
---|
1178 | { |
---|
1179 | DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); |
---|
1180 | /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */ |
---|
1181 | if( errno == EOVERFLOW || errno == EINVAL ) |
---|
1182 | try_sendfile = 0; |
---|
1183 | else if( errno != EAGAIN ) |
---|
1184 | break; |
---|
1185 | } |
---|
1186 | else |
---|
1187 | { |
---|
1188 | //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); |
---|
1189 | continue; |
---|
1190 | } |
---|
1191 | } |
---|
1192 | /* Fall back to regular I/O */ |
---|
1193 | if( !buf ) |
---|
1194 | buf = malloc(MIN_BUFFER_SIZE); |
---|
1195 | send_size = ( ((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE); |
---|
1196 | lseek(sendfd, offset, SEEK_SET); |
---|
1197 | ret = read(sendfd, buf, send_size); |
---|
1198 | if( ret == -1 ) { |
---|
1199 | DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno)); |
---|
1200 | if( errno != EAGAIN ) |
---|
1201 | break; |
---|
1202 | } |
---|
1203 | ret = write(h->socket, buf, ret); |
---|
1204 | if( ret == -1 ) { |
---|
1205 | DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno)); |
---|
1206 | if( errno != EAGAIN ) |
---|
1207 | break; |
---|
1208 | } |
---|
1209 | offset+=ret; |
---|
1210 | } |
---|
1211 | free(buf); |
---|
1212 | } |
---|
1213 | |
---|
1214 | void |
---|
1215 | SendResp_icon(struct upnphttp * h, char * icon) |
---|
1216 | { |
---|
1217 | char header[512]; |
---|
1218 | char mime[12] = "image/"; |
---|
1219 | char date[30]; |
---|
1220 | char *data; |
---|
1221 | int size, ret; |
---|
1222 | time_t curtime = time(NULL); |
---|
1223 | |
---|
1224 | if( strcmp(icon, "sm.png") == 0 ) |
---|
1225 | { |
---|
1226 | DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n"); |
---|
1227 | data = (char *)png_sm; |
---|
1228 | size = sizeof(png_sm)-1; |
---|
1229 | strcpy(mime+6, "png"); |
---|
1230 | } |
---|
1231 | else if( strcmp(icon, "lrg.png") == 0 ) |
---|
1232 | { |
---|
1233 | DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n"); |
---|
1234 | data = (char *)png_lrg; |
---|
1235 | size = sizeof(png_lrg)-1; |
---|
1236 | strcpy(mime+6, "png"); |
---|
1237 | } |
---|
1238 | else if( strcmp(icon, "sm.jpg") == 0 ) |
---|
1239 | { |
---|
1240 | DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n"); |
---|
1241 | data = (char *)jpeg_sm; |
---|
1242 | size = sizeof(jpeg_sm)-1; |
---|
1243 | strcpy(mime+6, "jpeg"); |
---|
1244 | } |
---|
1245 | else if( strcmp(icon, "lrg.jpg") == 0 ) |
---|
1246 | { |
---|
1247 | DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n"); |
---|
1248 | data = (char *)jpeg_lrg; |
---|
1249 | size = sizeof(jpeg_lrg)-1; |
---|
1250 | strcpy(mime+6, "jpeg"); |
---|
1251 | } |
---|
1252 | else |
---|
1253 | { |
---|
1254 | DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon); |
---|
1255 | Send404(h); |
---|
1256 | return; |
---|
1257 | } |
---|
1258 | |
---|
1259 | strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1260 | ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" |
---|
1261 | "Content-Type: %s\r\n" |
---|
1262 | "Content-Length: %d\r\n" |
---|
1263 | "Connection: close\r\n" |
---|
1264 | "Date: %s\r\n" |
---|
1265 | "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", |
---|
1266 | mime, size, date); |
---|
1267 | |
---|
1268 | if( send_data(h, header, ret, MSG_MORE) == 0 ) |
---|
1269 | { |
---|
1270 | if( h->req_command != EHead ) |
---|
1271 | send_data(h, data, size, 0); |
---|
1272 | } |
---|
1273 | } |
---|
1274 | |
---|
1275 | void |
---|
1276 | SendResp_albumArt(struct upnphttp * h, char * object) |
---|
1277 | { |
---|
1278 | char header[512]; |
---|
1279 | char *path; |
---|
1280 | char *dash; |
---|
1281 | char date[30]; |
---|
1282 | time_t curtime = time(NULL); |
---|
1283 | off_t size; |
---|
1284 | int fd; |
---|
1285 | int ret; |
---|
1286 | |
---|
1287 | if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) |
---|
1288 | { |
---|
1289 | DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); |
---|
1290 | Send406(h); |
---|
1291 | return; |
---|
1292 | } |
---|
1293 | |
---|
1294 | dash = strchr(object, '-'); |
---|
1295 | if( dash ) |
---|
1296 | *dash = '\0'; |
---|
1297 | |
---|
1298 | path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%s'", object); |
---|
1299 | if( !path ) |
---|
1300 | { |
---|
1301 | DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object); |
---|
1302 | Send404(h); |
---|
1303 | return; |
---|
1304 | } |
---|
1305 | DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path); |
---|
1306 | |
---|
1307 | strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1308 | |
---|
1309 | fd = open(path, O_RDONLY); |
---|
1310 | if( fd < 0 ) { |
---|
1311 | DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); |
---|
1312 | sqlite3_free(path); |
---|
1313 | Send404(h); |
---|
1314 | return; |
---|
1315 | } |
---|
1316 | sqlite3_free(path); |
---|
1317 | size = lseek(fd, 0, SEEK_END); |
---|
1318 | lseek(fd, 0, SEEK_SET); |
---|
1319 | |
---|
1320 | ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" |
---|
1321 | "Content-Type: image/jpeg\r\n" |
---|
1322 | "Content-Length: %jd\r\n" |
---|
1323 | "Connection: close\r\n" |
---|
1324 | "Date: %s\r\n" |
---|
1325 | "EXT:\r\n" |
---|
1326 | "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" |
---|
1327 | "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" |
---|
1328 | "Server: " MINIDLNA_SERVER_STRING "\r\n" |
---|
1329 | "transferMode.dlna.org: %s\r\n\r\n", |
---|
1330 | (intmax_t)size, date, |
---|
1331 | (h->reqflags & FLAG_XFERBACKGROUND) ? "Background" : "Interactive"); |
---|
1332 | |
---|
1333 | if( send_data(h, header, ret, MSG_MORE) == 0 ) |
---|
1334 | { |
---|
1335 | if( h->req_command != EHead ) |
---|
1336 | send_file(h, fd, 0, size-1); |
---|
1337 | } |
---|
1338 | close(fd); |
---|
1339 | } |
---|
1340 | |
---|
1341 | void |
---|
1342 | SendResp_caption(struct upnphttp * h, char * object) |
---|
1343 | { |
---|
1344 | char header[512]; |
---|
1345 | char *path; |
---|
1346 | char date[30]; |
---|
1347 | time_t curtime = time(NULL); |
---|
1348 | off_t size; |
---|
1349 | int fd, ret; |
---|
1350 | |
---|
1351 | strip_ext(object); |
---|
1352 | path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %s", object); |
---|
1353 | if( !path ) |
---|
1354 | { |
---|
1355 | DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object); |
---|
1356 | Send404(h); |
---|
1357 | return; |
---|
1358 | } |
---|
1359 | DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path); |
---|
1360 | |
---|
1361 | fd = open(path, O_RDONLY); |
---|
1362 | if( fd < 0 ) { |
---|
1363 | DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); |
---|
1364 | sqlite3_free(path); |
---|
1365 | Send404(h); |
---|
1366 | return; |
---|
1367 | } |
---|
1368 | sqlite3_free(path); |
---|
1369 | size = lseek(fd, 0, SEEK_END); |
---|
1370 | lseek(fd, 0, SEEK_SET); |
---|
1371 | strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1372 | |
---|
1373 | ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" |
---|
1374 | "Content-Type: smi/caption\r\n" |
---|
1375 | "Content-Length: %jd\r\n" |
---|
1376 | "Connection: close\r\n" |
---|
1377 | "Date: %s\r\n" |
---|
1378 | "EXT:\r\n" |
---|
1379 | "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", |
---|
1380 | (intmax_t)size, date); |
---|
1381 | |
---|
1382 | if( send_data(h, header, ret, MSG_MORE) == 0 ) |
---|
1383 | { |
---|
1384 | if( h->req_command != EHead ) |
---|
1385 | send_file(h, fd, 0, size-1); |
---|
1386 | } |
---|
1387 | close(fd); |
---|
1388 | } |
---|
1389 | |
---|
1390 | void |
---|
1391 | SendResp_thumbnail(struct upnphttp * h, char * object) |
---|
1392 | { |
---|
1393 | char header[512]; |
---|
1394 | char *path; |
---|
1395 | char date[30]; |
---|
1396 | time_t curtime = time(NULL); |
---|
1397 | int ret; |
---|
1398 | ExifData *ed; |
---|
1399 | ExifLoader *l; |
---|
1400 | |
---|
1401 | if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) |
---|
1402 | { |
---|
1403 | DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); |
---|
1404 | Send406(h); |
---|
1405 | return; |
---|
1406 | } |
---|
1407 | |
---|
1408 | strip_ext(object); |
---|
1409 | path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%s'", object); |
---|
1410 | if( !path ) |
---|
1411 | { |
---|
1412 | DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object); |
---|
1413 | Send404(h); |
---|
1414 | return; |
---|
1415 | } |
---|
1416 | DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path); |
---|
1417 | |
---|
1418 | if( access(path, F_OK) != 0 ) |
---|
1419 | { |
---|
1420 | DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path); |
---|
1421 | sqlite3_free(path); |
---|
1422 | return; |
---|
1423 | } |
---|
1424 | strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1425 | |
---|
1426 | l = exif_loader_new(); |
---|
1427 | exif_loader_write_file(l, path); |
---|
1428 | ed = exif_loader_get_data(l); |
---|
1429 | exif_loader_unref(l); |
---|
1430 | sqlite3_free(path); |
---|
1431 | |
---|
1432 | if( !ed || !ed->size ) |
---|
1433 | { |
---|
1434 | Send404(h); |
---|
1435 | if( ed ) |
---|
1436 | exif_data_unref(ed); |
---|
1437 | return; |
---|
1438 | } |
---|
1439 | ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" |
---|
1440 | "Content-Type: image/jpeg\r\n" |
---|
1441 | "Content-Length: %d\r\n" |
---|
1442 | "Connection: close\r\n" |
---|
1443 | "Date: %s\r\n" |
---|
1444 | "EXT:\r\n" |
---|
1445 | "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" |
---|
1446 | "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" |
---|
1447 | "Server: " MINIDLNA_SERVER_STRING "\r\n" |
---|
1448 | "transferMode.dlna.org: %s\r\n\r\n", |
---|
1449 | ed->size, date, |
---|
1450 | (h->reqflags & FLAG_XFERBACKGROUND) ? "Background" : "Interactive"); |
---|
1451 | |
---|
1452 | if( send_data(h, header, ret, MSG_MORE) == 0 ) |
---|
1453 | { |
---|
1454 | if( h->req_command != EHead ) |
---|
1455 | send_data(h, (char *)ed->data, ed->size, 0); |
---|
1456 | } |
---|
1457 | exif_data_unref(ed); |
---|
1458 | CloseSocket_upnphttp(h); |
---|
1459 | } |
---|
1460 | |
---|
1461 | void |
---|
1462 | SendResp_resizedimg(struct upnphttp * h, char * object) |
---|
1463 | { |
---|
1464 | char header[512]; |
---|
1465 | char str_buf[256]; |
---|
1466 | struct string_s str; |
---|
1467 | char **result; |
---|
1468 | char date[30]; |
---|
1469 | char dlna_pn[4]; |
---|
1470 | time_t curtime = time(NULL); |
---|
1471 | int width=640, height=480, dstw, dsth, size; |
---|
1472 | long srcw, srch; |
---|
1473 | unsigned char * data = NULL; |
---|
1474 | char *path, *file_path; |
---|
1475 | char *resolution; |
---|
1476 | char *key, *val; |
---|
1477 | char *saveptr=NULL, *item=NULL; |
---|
1478 | /* Not implemented yet * |
---|
1479 | char *pixelshape=NULL; |
---|
1480 | int rotation; */ |
---|
1481 | sqlite_int64 id; |
---|
1482 | int rows=0, chunked, ret; |
---|
1483 | #ifdef __sparc__ |
---|
1484 | char *tn; |
---|
1485 | ExifData *ed; |
---|
1486 | ExifLoader *l; |
---|
1487 | #endif |
---|
1488 | image_s *imsrc = NULL, *imdst = NULL; |
---|
1489 | int scale = 1; |
---|
1490 | |
---|
1491 | id = strtoll(object, NULL, 10); |
---|
1492 | sprintf(str_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id); |
---|
1493 | ret = sql_get_table(db, str_buf, &result, &rows, NULL); |
---|
1494 | if( (ret != SQLITE_OK) ) |
---|
1495 | { |
---|
1496 | DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id); |
---|
1497 | Send500(h); |
---|
1498 | return; |
---|
1499 | } |
---|
1500 | if( !rows || (access(result[3], F_OK) != 0) ) |
---|
1501 | { |
---|
1502 | DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); |
---|
1503 | sqlite3_free_table(result); |
---|
1504 | Send404(h); |
---|
1505 | return; |
---|
1506 | } |
---|
1507 | #if USE_FORK |
---|
1508 | pid_t newpid = 0; |
---|
1509 | newpid = fork(); |
---|
1510 | if( newpid ) |
---|
1511 | goto resized_error; |
---|
1512 | #endif |
---|
1513 | file_path = result[3]; |
---|
1514 | resolution = result[4]; |
---|
1515 | srcw = strtol(resolution, &saveptr, 10); |
---|
1516 | srch = strtol(saveptr+1, NULL, 10); |
---|
1517 | |
---|
1518 | path = strdup(object); |
---|
1519 | if( strtok_r(path, "?", &saveptr) ) |
---|
1520 | { |
---|
1521 | item = strtok_r(NULL, "&,", &saveptr); |
---|
1522 | } |
---|
1523 | while( item != NULL ) |
---|
1524 | { |
---|
1525 | #ifdef TIVO_SUPPORT |
---|
1526 | decodeString(item, 1); |
---|
1527 | #endif |
---|
1528 | val = item; |
---|
1529 | key = strsep(&val, "="); |
---|
1530 | DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); |
---|
1531 | if( strcasecmp(key, "width") == 0 ) |
---|
1532 | { |
---|
1533 | width = atoi(val); |
---|
1534 | } |
---|
1535 | else if( strcasecmp(key, "height") == 0 ) |
---|
1536 | { |
---|
1537 | height = atoi(val); |
---|
1538 | } |
---|
1539 | /* Not implemented yet * |
---|
1540 | else if( strcasecmp(key, "rotation") == 0 ) |
---|
1541 | { |
---|
1542 | rotation = atoi(val); |
---|
1543 | } |
---|
1544 | else if( strcasecmp(key, "pixelshape") == 0 ) |
---|
1545 | { |
---|
1546 | pixelshape = val; |
---|
1547 | } */ |
---|
1548 | item = strtok_r(NULL, "&,", &saveptr); |
---|
1549 | } |
---|
1550 | free(path); |
---|
1551 | |
---|
1552 | if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) |
---|
1553 | { |
---|
1554 | DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n"); |
---|
1555 | Send406(h); |
---|
1556 | goto resized_error; |
---|
1557 | } |
---|
1558 | |
---|
1559 | DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path); |
---|
1560 | |
---|
1561 | /* Figure out the best destination resolution we can use */ |
---|
1562 | dstw = width; |
---|
1563 | dsth = ((((width<<10)/srcw)*srch)>>10); |
---|
1564 | if( dsth > height ) |
---|
1565 | { |
---|
1566 | dsth = height; |
---|
1567 | dstw = (((height<<10)/srch) * srcw>>10); |
---|
1568 | } |
---|
1569 | |
---|
1570 | if( dstw <= 640 && dsth <= 480 ) |
---|
1571 | strcpy(dlna_pn, "SM"); |
---|
1572 | else if( dstw <= 1024 && dsth <= 768 ) |
---|
1573 | strcpy(dlna_pn, "MED"); |
---|
1574 | else |
---|
1575 | strcpy(dlna_pn, "LRG"); |
---|
1576 | |
---|
1577 | if( srcw>>3 >= dstw && srch>>3 >= dsth) |
---|
1578 | scale = 8; |
---|
1579 | else if( srcw>>2 >= dstw && srch>>2 >= dsth ) |
---|
1580 | scale = 4; |
---|
1581 | else if( srcw>>1 >= dstw && srch>>1 >= dsth ) |
---|
1582 | scale = 2; |
---|
1583 | |
---|
1584 | strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1585 | |
---|
1586 | str.data = header; |
---|
1587 | str.size = sizeof(header); |
---|
1588 | str.off = 0; |
---|
1589 | |
---|
1590 | strcatf(&str, "HTTP/1.1 200 OK\r\n" |
---|
1591 | "Content-Type: image/jpeg\r\n" |
---|
1592 | "Connection: close\r\n" |
---|
1593 | "Date: %s\r\n" |
---|
1594 | "EXT:\r\n" |
---|
1595 | "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" |
---|
1596 | "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n" |
---|
1597 | "Server: " MINIDLNA_SERVER_STRING "\r\n", |
---|
1598 | date, dlna_pn); |
---|
1599 | if( h->reqflags & FLAG_XFERINTERACTIVE ) |
---|
1600 | { |
---|
1601 | strcatf(&str, "transferMode.dlna.org: Interactive\r\n"); |
---|
1602 | } |
---|
1603 | else if( h->reqflags & FLAG_XFERBACKGROUND ) |
---|
1604 | { |
---|
1605 | strcatf(&str, "transferMode.dlna.org: Background\r\n"); |
---|
1606 | } |
---|
1607 | |
---|
1608 | /* Resizing from a thumbnail is much faster than from a large image */ |
---|
1609 | #ifdef __sparc__ |
---|
1610 | tn = result[5]; |
---|
1611 | if( dstw <= 160 && dsth <= 120 && atoi(tn) ) |
---|
1612 | { |
---|
1613 | l = exif_loader_new(); |
---|
1614 | exif_loader_write_file(l, file_path); |
---|
1615 | ed = exif_loader_get_data(l); |
---|
1616 | exif_loader_unref(l); |
---|
1617 | |
---|
1618 | if( !ed || !ed->size ) |
---|
1619 | { |
---|
1620 | if( ed ) |
---|
1621 | exif_data_unref(ed); |
---|
1622 | DPRINTF(E_WARN, L_HTTP, "Unable to access image thumbnail!\n"); |
---|
1623 | Send500(h); |
---|
1624 | goto resized_error; |
---|
1625 | } |
---|
1626 | imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1); |
---|
1627 | exif_data_unref(ed); |
---|
1628 | } |
---|
1629 | else |
---|
1630 | #endif |
---|
1631 | if( strcmp(h->HttpVer, "HTTP/1.0") == 0 ) |
---|
1632 | { |
---|
1633 | chunked = 0; |
---|
1634 | imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale); |
---|
1635 | } |
---|
1636 | else |
---|
1637 | { |
---|
1638 | chunked = 1; |
---|
1639 | strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n"); |
---|
1640 | } |
---|
1641 | |
---|
1642 | if( !chunked ) |
---|
1643 | { |
---|
1644 | if( !imsrc ) |
---|
1645 | { |
---|
1646 | DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); |
---|
1647 | Send500(h); |
---|
1648 | goto resized_error; |
---|
1649 | } |
---|
1650 | |
---|
1651 | imdst = image_resize(imsrc, dstw, dsth); |
---|
1652 | data = image_save_to_jpeg_buf(imdst, &size); |
---|
1653 | |
---|
1654 | strcatf(&str, "Content-Length: %d\r\n\r\n", size); |
---|
1655 | } |
---|
1656 | |
---|
1657 | if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) ) |
---|
1658 | { |
---|
1659 | if( chunked ) |
---|
1660 | { |
---|
1661 | imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale); |
---|
1662 | if( !imsrc ) |
---|
1663 | { |
---|
1664 | DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); |
---|
1665 | Send500(h); |
---|
1666 | goto resized_error; |
---|
1667 | } |
---|
1668 | imdst = image_resize(imsrc, dstw, dsth); |
---|
1669 | data = image_save_to_jpeg_buf(imdst, &size); |
---|
1670 | |
---|
1671 | ret = sprintf(str_buf, "%x\r\n", size); |
---|
1672 | send_data(h, str_buf, ret, MSG_MORE); |
---|
1673 | send_data(h, (char *)data, size, MSG_MORE); |
---|
1674 | send_data(h, "\r\n0\r\n\r\n", 7, 0); |
---|
1675 | } |
---|
1676 | else |
---|
1677 | { |
---|
1678 | send_data(h, (char *)data, size, 0); |
---|
1679 | } |
---|
1680 | } |
---|
1681 | DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); |
---|
1682 | if( imsrc ) |
---|
1683 | image_free(imsrc); |
---|
1684 | if( imdst ) |
---|
1685 | image_free(imdst); |
---|
1686 | resized_error: |
---|
1687 | sqlite3_free_table(result); |
---|
1688 | #if USE_FORK |
---|
1689 | if( !newpid ) |
---|
1690 | _exit(0); |
---|
1691 | #endif |
---|
1692 | } |
---|
1693 | |
---|
1694 | void |
---|
1695 | SendResp_dlnafile(struct upnphttp * h, char * object) |
---|
1696 | { |
---|
1697 | char header[1024]; |
---|
1698 | struct string_s str; |
---|
1699 | char sql_buf[256]; |
---|
1700 | char **result; |
---|
1701 | int rows, ret; |
---|
1702 | char date[30]; |
---|
1703 | time_t curtime = time(NULL); |
---|
1704 | off_t total, offset, size; |
---|
1705 | sqlite_int64 id; |
---|
1706 | int sendfh; |
---|
1707 | static struct { sqlite_int64 id; |
---|
1708 | enum client_types client; |
---|
1709 | char path[PATH_MAX]; |
---|
1710 | char mime[32]; |
---|
1711 | char dlna[96]; |
---|
1712 | } last_file = { 0, 0 }; |
---|
1713 | #if USE_FORK |
---|
1714 | pid_t newpid = 0; |
---|
1715 | #endif |
---|
1716 | |
---|
1717 | id = strtoll(object, NULL, 10); |
---|
1718 | if( id != last_file.id || h->req_client != last_file.client ) |
---|
1719 | { |
---|
1720 | sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id); |
---|
1721 | ret = sql_get_table(db, sql_buf, &result, &rows, NULL); |
---|
1722 | if( (ret != SQLITE_OK) ) |
---|
1723 | { |
---|
1724 | DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id); |
---|
1725 | Send500(h); |
---|
1726 | return; |
---|
1727 | } |
---|
1728 | if( !rows ) |
---|
1729 | { |
---|
1730 | DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); |
---|
1731 | sqlite3_free_table(result); |
---|
1732 | Send404(h); |
---|
1733 | return; |
---|
1734 | } |
---|
1735 | /* Cache the result */ |
---|
1736 | last_file.id = id; |
---|
1737 | last_file.client = h->req_client; |
---|
1738 | strncpy(last_file.path, result[3], sizeof(last_file.path)-1); |
---|
1739 | if( result[4] ) |
---|
1740 | { |
---|
1741 | strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1); |
---|
1742 | /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ |
---|
1743 | if( h->reqflags & FLAG_SAMSUNG ) |
---|
1744 | { |
---|
1745 | if( strcmp(last_file.mime+6, "x-matroska") == 0 ) |
---|
1746 | strcpy(last_file.mime+8, "mkv"); |
---|
1747 | /* Samsung TV's such as the A750 can natively support many |
---|
1748 | Xvid/DivX AVI's however, the DLNA server needs the |
---|
1749 | mime type to say video/mpeg */ |
---|
1750 | else if( h->req_client == ESamsungSeriesA && |
---|
1751 | strcmp(last_file.mime+6, "x-msvideo") == 0 ) |
---|
1752 | strcpy(last_file.mime+6, "mpeg"); |
---|
1753 | } |
---|
1754 | /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */ |
---|
1755 | else if( h->req_client == ESonyBDP ) |
---|
1756 | { |
---|
1757 | if( strcmp(last_file.mime+6, "x-matroska") == 0 || |
---|
1758 | strcmp(last_file.mime+6, "mpeg") == 0 ) |
---|
1759 | strcpy(last_file.mime+6, "divx"); |
---|
1760 | } |
---|
1761 | } |
---|
1762 | else |
---|
1763 | { |
---|
1764 | last_file.mime[0] = '\0'; |
---|
1765 | } |
---|
1766 | if( result[5] ) |
---|
1767 | snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]); |
---|
1768 | else if( h->reqflags & FLAG_DLNA ) |
---|
1769 | strcpy(last_file.dlna, dlna_no_conv); |
---|
1770 | else |
---|
1771 | last_file.dlna[0] = '\0'; |
---|
1772 | sqlite3_free_table(result); |
---|
1773 | } |
---|
1774 | #if USE_FORK |
---|
1775 | newpid = fork(); |
---|
1776 | if( newpid ) |
---|
1777 | return; |
---|
1778 | #endif |
---|
1779 | |
---|
1780 | DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path); |
---|
1781 | |
---|
1782 | if( h->reqflags & FLAG_XFERSTREAMING ) |
---|
1783 | { |
---|
1784 | if( strncmp(last_file.mime, "image", 5) == 0 ) |
---|
1785 | { |
---|
1786 | DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); |
---|
1787 | Send406(h); |
---|
1788 | goto error; |
---|
1789 | } |
---|
1790 | } |
---|
1791 | else if( h->reqflags & FLAG_XFERINTERACTIVE ) |
---|
1792 | { |
---|
1793 | if( h->reqflags & FLAG_REALTIMEINFO ) |
---|
1794 | { |
---|
1795 | DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n"); |
---|
1796 | Send400(h); |
---|
1797 | goto error; |
---|
1798 | } |
---|
1799 | if( strncmp(last_file.mime, "image", 5) != 0 ) |
---|
1800 | { |
---|
1801 | DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n"); |
---|
1802 | /* Samsung TVs (well, at least the A950) do this for some reason, |
---|
1803 | * and I don't see them fixing this bug any time soon. */ |
---|
1804 | if( !(h->reqflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) ) |
---|
1805 | { |
---|
1806 | Send406(h); |
---|
1807 | goto error; |
---|
1808 | } |
---|
1809 | } |
---|
1810 | } |
---|
1811 | |
---|
1812 | strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); |
---|
1813 | offset = h->req_RangeStart; |
---|
1814 | sendfh = open(last_file.path, O_RDONLY); |
---|
1815 | if( sendfh < 0 ) { |
---|
1816 | DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path); |
---|
1817 | Send404(h); |
---|
1818 | goto error; |
---|
1819 | } |
---|
1820 | size = lseek(sendfh, 0, SEEK_END); |
---|
1821 | lseek(sendfh, 0, SEEK_SET); |
---|
1822 | |
---|
1823 | str.data = header; |
---|
1824 | str.size = sizeof(header); |
---|
1825 | str.off = 0; |
---|
1826 | |
---|
1827 | strcatf(&str, "HTTP/1.1 20%c OK\r\n" |
---|
1828 | "Content-Type: %s\r\n", |
---|
1829 | (h->reqflags & FLAG_RANGE ? '6' : '0'), |
---|
1830 | last_file.mime); |
---|
1831 | if( h->reqflags & FLAG_RANGE ) |
---|
1832 | { |
---|
1833 | if( !h->req_RangeEnd || h->req_RangeEnd == size ) |
---|
1834 | { |
---|
1835 | h->req_RangeEnd = size - 1; |
---|
1836 | } |
---|
1837 | if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) ) |
---|
1838 | { |
---|
1839 | DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n"); |
---|
1840 | Send400(h); |
---|
1841 | close(sendfh); |
---|
1842 | goto error; |
---|
1843 | } |
---|
1844 | if( h->req_RangeEnd >= size ) |
---|
1845 | { |
---|
1846 | DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n"); |
---|
1847 | Send416(h); |
---|
1848 | close(sendfh); |
---|
1849 | goto error; |
---|
1850 | } |
---|
1851 | |
---|
1852 | total = h->req_RangeEnd - h->req_RangeStart + 1; |
---|
1853 | strcatf(&str, "Content-Length: %jd\r\n" |
---|
1854 | "Content-Range: bytes %jd-%jd/%jd\r\n", |
---|
1855 | (intmax_t)total, (intmax_t)h->req_RangeStart, |
---|
1856 | (intmax_t)h->req_RangeEnd, (intmax_t)size); |
---|
1857 | } |
---|
1858 | else |
---|
1859 | { |
---|
1860 | h->req_RangeEnd = size - 1; |
---|
1861 | total = size; |
---|
1862 | strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total); |
---|
1863 | } |
---|
1864 | |
---|
1865 | if( h->reqflags & FLAG_XFERSTREAMING ) |
---|
1866 | { |
---|
1867 | strcatf(&str, "transferMode.dlna.org: Streaming\r\n"); |
---|
1868 | } |
---|
1869 | else if( h->reqflags & FLAG_XFERBACKGROUND ) |
---|
1870 | { |
---|
1871 | if( strncmp(last_file.mime, "image", 5) == 0 ) |
---|
1872 | strcatf(&str, "transferMode.dlna.org: Background\r\n"); |
---|
1873 | } |
---|
1874 | else //if( h->reqflags & FLAG_XFERINTERACTIVE ) |
---|
1875 | { |
---|
1876 | if( (strncmp(last_file.mime, "video", 5) == 0) || |
---|
1877 | (strncmp(last_file.mime, "audio", 5) == 0) ) |
---|
1878 | { |
---|
1879 | strcatf(&str, "transferMode.dlna.org: Streaming\r\n"); |
---|
1880 | } |
---|
1881 | else |
---|
1882 | { |
---|
1883 | strcatf(&str, "transferMode.dlna.org: Interactive\r\n"); |
---|
1884 | } |
---|
1885 | } |
---|
1886 | |
---|
1887 | if( h->reqflags & FLAG_CAPTION ) |
---|
1888 | { |
---|
1889 | if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 ) |
---|
1890 | { |
---|
1891 | strcatf(&str, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n", |
---|
1892 | lan_addr[h->iface].str, runtime_vars.port, id); |
---|
1893 | } |
---|
1894 | } |
---|
1895 | |
---|
1896 | strcatf(&str, "Accept-Ranges: bytes\r\n" |
---|
1897 | "Connection: close\r\n" |
---|
1898 | "Date: %s\r\n" |
---|
1899 | "EXT:\r\n" |
---|
1900 | "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" |
---|
1901 | "contentFeatures.dlna.org: %s\r\n" |
---|
1902 | "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", |
---|
1903 | date, last_file.dlna); |
---|
1904 | |
---|
1905 | //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data); |
---|
1906 | if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) |
---|
1907 | { |
---|
1908 | if( h->req_command != EHead ) |
---|
1909 | send_file(h, sendfh, offset, h->req_RangeEnd); |
---|
1910 | } |
---|
1911 | close(sendfh); |
---|
1912 | |
---|
1913 | error: |
---|
1914 | #if USE_FORK |
---|
1915 | if( !newpid ) |
---|
1916 | _exit(0); |
---|
1917 | #endif |
---|
1918 | return; |
---|
1919 | } |
---|