2 * http://minidlna.sourceforge.net/
4 * MiniDLNA media server
5 * Copyright (C) 2008-2009 Justin Maggard
7 * This file is part of MiniDLNA.
9 * MiniDLNA is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
13 * MiniDLNA is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
21 * Portions of the code from the MiniUPnP project:
23 * Copyright (c) 2006-2007, Thomas Bernard
24 * All rights reserved.
26 * Redistribution and use in source and binary forms, with or without
27 * modification, are permitted provided that the following conditions are met:
28 * * Redistributions of source code must retain the above copyright
29 * notice, this list of conditions and the following disclaimer.
30 * * Redistributions in binary form must reproduce the above copyright
31 * notice, this list of conditions and the following disclaimer in the
32 * documentation and/or other materials provided with the distribution.
33 * * The name of the author may not be used to endorse or promote products
34 * derived from this software without specific prior written permission.
36 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
37 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
40 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 * POSSIBILITY OF SUCH DAMAGE.
51 #include <sys/queue.h>
55 #include <sys/types.h>
56 #include <sys/socket.h>
57 #include <sys/param.h>
58 #include <netinet/in.h>
59 #include <arpa/inet.h>
64 #include "upnpevents.h"
65 #include "minidlnapath.h"
66 #include "upnpglobalvars.h"
67 #include "upnpdescgen.h"
71 /* stuctures definitions */
73 LIST_ENTRY(subscriber
) entries
;
74 struct upnp_event_notify
* notify
;
77 enum subscriber_service_enum service
;
82 struct upnp_event_notify
{
83 LIST_ENTRY(upnp_event_notify
) entries
;
91 struct subscriber
* sub
;
103 upnp_event_create_notify(struct subscriber
* sub
);
105 /* Subscriber list */
106 LIST_HEAD(listhead
, subscriber
) subscriberlist
= { NULL
};
109 LIST_HEAD(listheadnotif
, upnp_event_notify
) notifylist
= { NULL
};
111 /* create a new subscriber */
112 static struct subscriber
*
113 newSubscriber(const char * eventurl
, const char * callback
, int callbacklen
)
115 struct subscriber
* tmp
;
116 if(!eventurl
|| !callback
|| !callbacklen
)
118 tmp
= calloc(1, sizeof(struct subscriber
)+callbacklen
+1);
119 if(strcmp(eventurl
, CONTENTDIRECTORY_EVENTURL
)==0)
120 tmp
->service
= EContentDirectory
;
121 else if(strcmp(eventurl
, CONNECTIONMGR_EVENTURL
)==0)
122 tmp
->service
= EConnectionManager
;
123 else if(strcmp(eventurl
, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL
)==0)
124 tmp
->service
= EMSMediaReceiverRegistrar
;
129 memcpy(tmp
->callback
, callback
, callbacklen
);
130 tmp
->callback
[callbacklen
] = '\0';
131 /* make a dummy uuid */
132 strncpy(tmp
->uuid
, uuidvalue
, sizeof(tmp
->uuid
));
133 if( get_uuid_string(tmp
->uuid
+5) != 0 )
135 tmp
->uuid
[sizeof(tmp
->uuid
)-1] = '\0';
136 snprintf(tmp
->uuid
+37, 5, "%04lx", random() & 0xffff);
142 /* creates a new subscriber and adds it to the subscriber list
143 * also initiate 1st notify */
145 upnpevents_addSubscriber(const char * eventurl
,
146 const char * callback
, int callbacklen
,
149 struct subscriber
* tmp
;
150 DPRINTF(E_DEBUG
, L_HTTP
, "addSubscriber(%s, %.*s, %d)\n",
151 eventurl
, callbacklen
, callback
, timeout
);
152 tmp
= newSubscriber(eventurl
, callback
, callbacklen
);
156 tmp
->timeout
= time(NULL
) + timeout
;
157 LIST_INSERT_HEAD(&subscriberlist
, tmp
, entries
);
158 upnp_event_create_notify(tmp
);
162 /* renew a subscription (update the timeout) */
164 renewSubscription(const char * sid
, int sidlen
, int timeout
)
166 struct subscriber
* sub
;
167 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
168 if(memcmp(sid
, sub
->uuid
, 41) == 0) {
169 sub
->timeout
= (timeout
? time(NULL
) + timeout
: 0);
177 upnpevents_removeSubscriber(const char * sid
, int sidlen
)
179 struct subscriber
* sub
;
182 DPRINTF(E_DEBUG
, L_HTTP
, "removeSubscriber(%.*s)\n",
184 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
185 if(memcmp(sid
, sub
->uuid
, 41) == 0) {
187 sub
->notify
->sub
= NULL
;
189 LIST_REMOVE(sub
, entries
);
198 upnpevents_removeSubscribers(void)
200 struct subscriber
* sub
;
202 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= subscriberlist
.lh_first
) {
203 upnpevents_removeSubscriber(sub
->uuid
, sizeof(sub
->uuid
));
207 /* notifies all subscribers of a SystemUpdateID change */
209 upnp_event_var_change_notify(enum subscriber_service_enum service
)
211 struct subscriber
* sub
;
212 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
213 if(sub
->service
== service
&& sub
->notify
== NULL
)
214 upnp_event_create_notify(sub
);
218 /* create and add the notify object to the list */
220 upnp_event_create_notify(struct subscriber
* sub
)
222 struct upnp_event_notify
* obj
;
224 obj
= calloc(1, sizeof(struct upnp_event_notify
));
226 DPRINTF(E_ERROR
, L_HTTP
, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno
));
230 obj
->state
= ECreated
;
231 obj
->s
= socket(PF_INET
, SOCK_STREAM
, 0);
233 DPRINTF(E_ERROR
, L_HTTP
, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno
));
236 if((flags
= fcntl(obj
->s
, F_GETFL
, 0)) < 0) {
237 DPRINTF(E_ERROR
, L_HTTP
, "%s: fcntl(..F_GETFL..): %s\n",
238 "upnp_event_create_notify", strerror(errno
));
241 if(fcntl(obj
->s
, F_SETFL
, flags
| O_NONBLOCK
) < 0) {
242 DPRINTF(E_ERROR
, L_HTTP
, "%s: fcntl(..F_SETFL..): %s\n",
243 "upnp_event_create_notify", strerror(errno
));
248 LIST_INSERT_HEAD(¬ifylist
, obj
, entries
);
257 upnp_event_notify_connect(struct upnp_event_notify
* obj
)
262 struct sockaddr_in addr
;
265 memset(&addr
, 0, sizeof(addr
));
267 if(obj
->sub
== NULL
) {
271 p
= obj
->sub
->callback
;
272 p
+= 7; /* http:// */
273 while(*p
!= '/' && *p
!= ':' && i
< (sizeof(obj
->addrstr
)-1))
274 obj
->addrstr
[i
++] = *(p
++);
275 obj
->addrstr
[i
] = '\0';
277 obj
->portstr
[0] = *p
;
280 port
= (unsigned short)atoi(p
);
281 while(*p
!= '/' && *p
!= '\0') {
282 if(i
<7) obj
->portstr
[i
++] = *p
;
288 obj
->portstr
[0] = '\0';
294 addr
.sin_family
= AF_INET
;
295 inet_aton(obj
->addrstr
, &addr
.sin_addr
);
296 addr
.sin_port
= htons(port
);
297 DPRINTF(E_DEBUG
, L_HTTP
, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect",
298 obj
->addrstr
, port
, obj
->path
);
299 obj
->state
= EConnecting
;
300 if(connect(obj
->s
, (struct sockaddr
*)&addr
, sizeof(addr
)) < 0) {
301 if(errno
!= EINPROGRESS
&& errno
!= EWOULDBLOCK
) {
302 DPRINTF(E_ERROR
, L_HTTP
, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno
));
308 static void upnp_event_prepare(struct upnp_event_notify
* obj
)
310 static const char notifymsg
[] =
311 "NOTIFY %s HTTP/1.1\r\n"
313 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
314 "Content-Length: %d\r\n"
316 "NTS: upnp:propchange\r\n"
319 "Connection: close\r\n"
320 "Cache-Control: no-cache\r\n"
325 if(obj
->sub
== NULL
) {
329 switch(obj
->sub
->service
) {
330 case EContentDirectory
:
331 xml
= getVarsContentDirectory(&l
);
333 case EConnectionManager
:
334 xml
= getVarsConnectionManager(&l
);
336 case EMSMediaReceiverRegistrar
:
337 xml
= getVarsX_MS_MediaReceiverRegistrar(&l
);
343 obj
->tosend
= asprintf(&(obj
->buffer
), notifymsg
,
344 obj
->path
, obj
->addrstr
, obj
->portstr
, l
+2,
345 obj
->sub
->uuid
, obj
->sub
->seq
,
347 obj
->buffersize
= obj
->tosend
;
352 DPRINTF(E_DEBUG
, L_HTTP
, "Sending UPnP Event response:\n%s\n", obj
->buffer
);
353 obj
->state
= ESending
;
356 static void upnp_event_send(struct upnp_event_notify
* obj
)
359 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent);
360 while( obj
->sent
< obj
->tosend
) {
361 i
= send(obj
->s
, obj
->buffer
+ obj
->sent
, obj
->tosend
- obj
->sent
, 0);
363 DPRINTF(E_WARN
, L_HTTP
, "%s: send(): %s\n", "upnp_event_send", strerror(errno
));
369 if(obj
->sent
== obj
->tosend
)
370 obj
->state
= EWaitingForResponse
;
373 static void upnp_event_recv(struct upnp_event_notify
* obj
)
376 n
= recv(obj
->s
, obj
->buffer
, obj
->buffersize
, 0);
378 DPRINTF(E_ERROR
, L_HTTP
, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno
));
382 DPRINTF(E_DEBUG
, L_HTTP
, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
384 obj
->state
= EFinished
;
394 upnp_event_process_notify(struct upnp_event_notify
* obj
)
398 /* now connected or failed to connect */
399 upnp_event_prepare(obj
);
400 upnp_event_send(obj
);
403 upnp_event_send(obj
);
405 case EWaitingForResponse
:
406 upnp_event_recv(obj
);
413 DPRINTF(E_ERROR
, L_HTTP
, "upnp_event_process_notify: unknown state\n");
417 void upnpevents_selectfds(fd_set
*readset
, fd_set
*writeset
, int * max_fd
)
419 struct upnp_event_notify
* obj
;
420 for(obj
= notifylist
.lh_first
; obj
!= NULL
; obj
= obj
->entries
.le_next
) {
421 DPRINTF(E_DEBUG
, L_HTTP
, "upnpevents_selectfds: %p %d %d\n",
422 obj
, obj
->state
, obj
->s
);
426 upnp_event_notify_connect(obj
);
427 if(obj
->state
!= EConnecting
)
431 FD_SET(obj
->s
, writeset
);
435 case EWaitingForResponse
:
436 FD_SET(obj
->s
, readset
);
447 void upnpevents_processfds(fd_set
*readset
, fd_set
*writeset
)
449 struct upnp_event_notify
* obj
;
450 struct upnp_event_notify
* next
;
451 struct subscriber
* sub
;
452 struct subscriber
* subnext
;
454 for(obj
= notifylist
.lh_first
; obj
!= NULL
; obj
= obj
->entries
.le_next
) {
455 DPRINTF(E_DEBUG
, L_HTTP
, "%s: %p %d %d %d %d\n",
456 "upnpevents_processfds", obj
, obj
->state
, obj
->s
,
457 FD_ISSET(obj
->s
, readset
), FD_ISSET(obj
->s
, writeset
));
459 if(FD_ISSET(obj
->s
, readset
) || FD_ISSET(obj
->s
, writeset
))
460 upnp_event_process_notify(obj
);
463 obj
= notifylist
.lh_first
;
465 next
= obj
->entries
.le_next
;
466 if(obj
->state
== EError
|| obj
->state
== EFinished
) {
471 obj
->sub
->notify
= NULL
;
472 #if 0 /* Just let it time out instead of explicitly removing the subscriber */
473 /* remove also the subscriber from the list if there was an error */
474 if(obj
->state
== EError
&& obj
->sub
) {
475 LIST_REMOVE(obj
->sub
, entries
);
482 LIST_REMOVE(obj
, entries
);
487 /* remove timeouted subscribers */
488 curtime
= time(NULL
);
489 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; ) {
490 subnext
= sub
->entries
.le_next
;
491 if(sub
->timeout
&& curtime
> sub
->timeout
&& sub
->notify
== NULL
) {
492 LIST_REMOVE(sub
, entries
);