1 /* Implmentation of DACP (e.g., iTunes Remote) sharing
2  *
3  * Copyright (C) 2010 Alexandre Rosenfeld <airmind@gmail.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20 
21 #include "config.h"
22 
23 #include <time.h>
24 #include <string.h>
25 #include <stdlib.h>
26 
27 #include <glib.h>
28 #include <glib-object.h>
29 
30 #ifdef HAVE_GDKPIXBUF
31 #include <gdk-pixbuf/gdk-pixbuf.h>
32 #endif /* HAVE_GDKPIXBUF */
33 
34 #include <libsoup/soup.h>
35 #include <libsoup/soup-address.h>
36 #include <libsoup/soup-message.h>
37 #include <libsoup/soup-uri.h>
38 #include <libsoup/soup-server.h>
39 
40 #include <libdmapsharing/dmap.h>
41 #include <libdmapsharing/dmap-structure.h>
42 
43 #include <libdmapsharing/dmap-share.h>
44 #include <libdmapsharing/dacp-share.h>
45 #include <libdmapsharing/dacp-connection.h>
46 #include <libdmapsharing/dacp-player.h>
47 
48 static void dacp_share_set_property (GObject * object,
49 				     guint prop_id,
50 				     const GValue * value,
51 				     GParamSpec * pspec);
52 static void dacp_share_get_property (GObject * object,
53 				     guint prop_id,
54 				     GValue * value, GParamSpec * pspec);
55 static void dacp_share_dispose (GObject * object);
56 const char *dacp_share_get_type_of_service (DMAPShare * share);
57 void dacp_share_ctrl_int (DMAPShare * share,
58 			  SoupServer * server,
59 			  SoupMessage * message,
60 			  const char *path,
61 			  GHashTable * query, SoupClientContext * context);
62 void dacp_share_login (DMAPShare * share,
63 		       SoupServer * server,
64 		       SoupMessage * message,
65 		       const char *path,
66 		       GHashTable * query, SoupClientContext * context);
67 
68 static gchar *dacp_share_pairing_code (DACPShare * share, gchar * pair_txt,
69 				       gchar passcode[4]);
70 static void dacp_share_send_playstatusupdate (DACPShare * share);
71 static void dacp_share_fill_playstatusupdate (DACPShare * share,
72 					      SoupMessage * message);
73 
74 #define DACP_TYPE_OF_SERVICE "_touch-able._tcp"
75 #define DACP_PORT 3689
76 
77 struct DACPSharePrivate
78 {
79 	DMAPMdnsBrowser *mdns_browser;
80 
81 	gchar *library_name;
82 	GHashTable *remotes;
83 
84 	gint current_revision;
85 
86 	GSList *update_queue;
87 
88 	DACPPlayer *player;
89 };
90 
91 /*
92  * Internal representation of a DACP remote.
93  */
94 typedef struct
95 {
96 	gchar *host;
97 	guint port;
98 	gchar *pair_txt;
99 	DMAPConnection *connection;
100 } DACPRemoteInfo;
101 
102 enum
103 {
104 	PROP_0,
105 	PROP_LIBRARY_NAME,
106 	PROP_PLAYER
107 };
108 
109 enum
110 {
111 	REMOTE_FOUND,
112 	REMOTE_LOST,
113 	REMOTE_PAIRED,
114 
115 	LOOKUP_GUID,
116 	ADD_GUID,
117 
118 	LAST_SIGNAL
119 };
120 
121 static guint signals[LAST_SIGNAL] = { 0, };
122 
123 G_DEFINE_TYPE_WITH_PRIVATE (DACPShare, dacp_share, DAAP_TYPE_SHARE);
124 
125 static void
dacp_share_class_init(DACPShareClass * klass)126 dacp_share_class_init (DACPShareClass * klass)
127 {
128 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
129 	DMAPShareClass *dmap_class = DMAP_SHARE_CLASS (object_class);
130 
131 	object_class->get_property = dacp_share_get_property;
132 	object_class->set_property = dacp_share_set_property;
133 	object_class->dispose = dacp_share_dispose;
134 
135 	dmap_class->get_type_of_service = dacp_share_get_type_of_service;
136 	dmap_class->ctrl_int = dacp_share_ctrl_int;
137 	dmap_class->login = dacp_share_login;
138 
139 	g_object_class_install_property (object_class,
140 					 PROP_LIBRARY_NAME,
141 					 g_param_spec_string ("library-name",
142 							      "Library Name",
143 							      "Library name as will be shown in the Remote",
144 							      NULL,
145 							      G_PARAM_READWRITE));
146 
147 	g_object_class_install_property (object_class,
148 					 PROP_PLAYER,
149 					 g_param_spec_object ("player",
150 							      "Player",
151 							      "Player",
152 							      G_TYPE_OBJECT,
153 							      G_PARAM_READWRITE
154 							      |
155 							      G_PARAM_CONSTRUCT_ONLY));
156 
157 	/**
158 	 * DACPShare::remote-found
159 	 * @share: the #DACPShare that received the signal.
160 	 * @service_name: the remote identifier.
161 	 * @remote_name: the remote friendly name.
162 	 *
163 	 * Signal emited when a remote is found in the local network.
164 	 */
165 	signals[REMOTE_FOUND] =
166 		g_signal_new ("remote-found",
167 			      G_TYPE_FROM_CLASS (object_class),
168 			      G_SIGNAL_RUN_LAST,
169 			      G_STRUCT_OFFSET (DACPShareClass, remote_found),
170 			      NULL,
171 			      NULL,
172 			      NULL,
173 			      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
174 
175 	/**
176 	 * DACPShare::remote-lost
177 	 * @share: the #DACPShare that received the signal
178 	 * @service_name: the remote identifier.
179 	 *
180 	 * Signal emited when a remote is lost in the local network.
181 	 */
182 	signals[REMOTE_LOST] =
183 		g_signal_new ("remote-lost",
184 			      G_TYPE_FROM_CLASS (object_class),
185 			      G_SIGNAL_RUN_LAST,
186 			      G_STRUCT_OFFSET (DACPShareClass, remote_lost),
187 			      NULL,
188 			      NULL,
189 			      NULL,
190 			      G_TYPE_NONE, 1, G_TYPE_STRING);
191 
192 	/**
193 	 * DACPShare::remote-paired
194 	 * @share: the #DACPShare that received the signal
195 	 * @service_name: the remote identifier.
196 	 * @connected: indicates if the connection was succesfull or not.
197 	 *
198 	 * Signal emited when a remote is paired.
199 	 */
200 	signals[REMOTE_PAIRED] =
201 		g_signal_new ("remote-paired",
202 			      G_TYPE_FROM_CLASS (object_class),
203 			      G_SIGNAL_RUN_LAST,
204 			      G_STRUCT_OFFSET (DACPShareClass, remote_paired),
205 			      NULL,
206 			      NULL,
207 			      NULL,
208 			      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);
209 
210 	/**
211 	 * DACPShare::lookup-guid
212 	 * @share: the #DACPShare that received the signal
213 	 * @guid: a string containing the guid to be validated.
214 	 *
215 	 * Signal emited when the remote has logged in before and wants to be
216 	 * validated. An implementation must implement this signal to lookup
217 	 * for guids saved by ::add-guid
218 	 */
219 	signals[LOOKUP_GUID] =
220 		g_signal_new ("lookup-guid",
221 			      G_TYPE_FROM_CLASS (object_class),
222 			      G_SIGNAL_RUN_LAST,
223 			      G_STRUCT_OFFSET (DACPShareClass, lookup_guid),
224 			      NULL,
225 			      NULL,
226 			      NULL,
227 			      G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
228 
229 	/**
230 	 * DACPShare::add-guid
231 	 * @share: the #DACPShare that received the signal
232 	 * @guid: a string containing the guid to be saved.
233 	 *
234 	 * Signal emited when the remote wants to log in and save a special guid
235 	 * which will be used later when it wants to reconnect. With this guid,
236 	 * we know that this remote has connected before, thus this signal must
237 	 * save somewhere all guids that connected before, so that ::lookup-guid
238 	 * will find this remote. The user interface probably wants to include
239 	 * a button to forget previously connected remotes, so that the user may
240 	 * disconnect all previously connected remotes.
241 	 */
242 	signals[ADD_GUID] =
243 		g_signal_new ("add-guid",
244 			      G_TYPE_FROM_CLASS (object_class),
245 			      G_SIGNAL_RUN_LAST,
246 			      G_STRUCT_OFFSET (DACPShareClass, add_guid),
247 			      NULL,
248 			      NULL,
249 			      NULL,
250 			      G_TYPE_NONE, 1, G_TYPE_STRING);
251 }
252 
253 static void
dacp_share_init(DACPShare * share)254 dacp_share_init (DACPShare * share)
255 {
256 	share->priv = dacp_share_get_instance_private(share);
257 
258 	share->priv->current_revision = 2;
259 
260 	share->priv->remotes = g_hash_table_new_full ((GHashFunc) g_str_hash,
261 						      (GEqualFunc)
262 						      g_str_equal,
263 						      (GDestroyNotify) g_free,
264 						      (GDestroyNotify)
265 						      g_free);
266 }
267 
268 static gchar *
get_dbid(void)269 get_dbid (void)
270 {
271 	static gchar *dbid;
272 
273 	if (!dbid) {
274 		GString *name;
275 
276 		// Creates a service name 14 characters long concatenating the hostname
277 		// hash hex value with itself.
278 		// Idea taken from stereo.
279 		name = g_string_new (NULL);
280 		g_string_printf (name, "%.8x",
281 				 g_str_hash (g_get_host_name ()));
282 		g_string_ascii_up (name);
283 		g_string_append_len (name, name->str, 4);
284 
285 		dbid = name->str;
286 
287 		g_string_free (name, FALSE);
288 	}
289 	return dbid;
290 }
291 
292 static void
dacp_share_update_txt_records(DACPShare * share)293 dacp_share_update_txt_records (DACPShare * share)
294 {
295 	gchar *dbid_record;
296 	gchar *library_name_record;
297 
298 	library_name_record =
299 		g_strdup_printf ("CtlN=%s", share->priv->library_name);
300 	dbid_record = g_strdup_printf ("DbId=%s", get_dbid ());
301 
302 	gchar *txt_records[] = { "Ver=131073",
303 		"DvSv=2049",
304 		dbid_record,
305 		"DvTy=iTunes",
306 		"OSsi=0x1F6",
307 		"txtvers=1",
308 		library_name_record,
309 		NULL
310 	};
311 
312 	g_object_set (share, "txt-records", txt_records, NULL);
313 
314 	g_free (dbid_record);
315 	g_free (library_name_record);
316 }
317 
318 static void
dacp_share_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)319 dacp_share_set_property (GObject * object,
320 			 guint prop_id,
321 			 const GValue * value, GParamSpec * pspec)
322 {
323 	DACPShare *share = DACP_SHARE (object);
324 
325 	switch (prop_id) {
326 	case PROP_LIBRARY_NAME:
327 		g_free (share->priv->library_name);
328 		share->priv->library_name = g_value_dup_string (value);
329 		dacp_share_update_txt_records (share);
330 		break;
331 	case PROP_PLAYER:
332 		if (share->priv->player)
333 			g_object_unref (share->priv->player);
334 		share->priv->player =
335 			DACP_PLAYER (g_value_dup_object (value));
336 		break;
337 	default:
338 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
339 		break;
340 	}
341 }
342 
343 static void
dacp_share_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)344 dacp_share_get_property (GObject * object,
345 			 guint prop_id, GValue * value, GParamSpec * pspec)
346 {
347 	DACPShare *share = DACP_SHARE (object);
348 
349 	switch (prop_id) {
350 	case PROP_LIBRARY_NAME:
351 		g_value_set_string (value, share->priv->library_name);
352 		break;
353 	case PROP_PLAYER:
354 		g_value_set_object (value, G_OBJECT (share->priv->player));
355 		break;
356 	default:
357 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
358 		break;
359 	}
360 }
361 
362 static void
dacp_share_dispose(GObject * object)363 dacp_share_dispose (GObject * object)
364 {
365 	DACPShare *share = DACP_SHARE (object);
366 
367 	g_free (share->priv->library_name);
368 
369 	if (share->priv->mdns_browser)
370 		g_object_unref (share->priv->mdns_browser);
371 
372 	if (share->priv->player)
373 		g_object_unref (share->priv->player);
374 
375 	g_slist_free (share->priv->update_queue);
376 
377 	g_hash_table_destroy (share->priv->remotes);
378 }
379 
380 static void
mdns_remote_added(G_GNUC_UNUSED DMAPMdnsBrowser * browser,DMAPMdnsBrowserService * service,DACPShare * share)381 mdns_remote_added (G_GNUC_UNUSED DMAPMdnsBrowser * browser,
382 		   DMAPMdnsBrowserService * service, DACPShare * share)
383 {
384 	DACPRemoteInfo *remote_info;
385 
386 	remote_info = g_new0 (DACPRemoteInfo, 1);
387 	remote_info->host = g_strdup (service->host);
388 	remote_info->port = service->port;
389 	remote_info->pair_txt = g_strdup (service->pair);
390 	remote_info->connection = NULL;
391 
392 	g_debug ("New Remote found: %s name=%s host=%s port=%u pair=%s",
393 		 service->service_name,
394 		 service->name,
395 		 remote_info->host, remote_info->port, remote_info->pair_txt);
396 
397 	g_hash_table_insert (share->priv->remotes,
398 			     service->service_name, remote_info);
399 
400 	g_signal_emit (share,
401 		       signals[REMOTE_FOUND],
402 		       0, service->service_name, service->name);
403 }
404 
405 static void
mdns_remote_removed(G_GNUC_UNUSED DMAPMdnsBrowser * browser,const char * service_name,DACPShare * share)406 mdns_remote_removed (G_GNUC_UNUSED DMAPMdnsBrowser * browser,
407 		     const char *service_name, DACPShare * share)
408 {
409 	g_signal_emit (share, signals[REMOTE_LOST], 0, service_name);
410 
411 	g_hash_table_remove (share->priv->remotes, service_name);
412 }
413 
414 DACPShare *
dacp_share_new(const gchar * library_name,DACPPlayer * player,DMAPDb * db,DMAPContainerDb * container_db)415 dacp_share_new (const gchar * library_name,
416 		DACPPlayer * player,
417 		DMAPDb * db, DMAPContainerDb * container_db)
418 {
419 	DACPShare *share;
420 
421 	g_object_ref (db);
422 	g_object_ref (container_db);
423 
424 	share = DACP_SHARE (g_object_new (DACP_TYPE_SHARE,
425 					  "name", get_dbid (),
426 					  "library-name", library_name,
427 					  "password", NULL,
428 					  "db", db,
429 					  "container-db", container_db,
430 					  "player", G_OBJECT (player),
431 					  "transcode-mimetype", NULL, NULL));
432 
433 	g_debug ("Starting DACP server");
434 	_dmap_share_server_start (DMAP_SHARE (share));
435 	_dmap_share_publish_start (DMAP_SHARE (share));
436 
437 	return share;
438 }
439 
440 void
dacp_share_start_lookup(DACPShare * share)441 dacp_share_start_lookup (DACPShare * share)
442 {
443 	GError *error;
444 
445 	if (share->priv->mdns_browser) {
446 		g_warning ("DACP browsing already started");
447 		return;
448 	}
449 
450 	share->priv->mdns_browser =
451 		dmap_mdns_browser_new (DMAP_MDNS_BROWSER_SERVICE_TYPE_DACP);
452 
453 	g_signal_connect_object (share->priv->mdns_browser,
454 				 "service-added",
455 				 G_CALLBACK (mdns_remote_added), share, 0);
456 	g_signal_connect_object (share->priv->mdns_browser,
457 				 "service-removed",
458 				 G_CALLBACK (mdns_remote_removed), share, 0);
459 
460 	error = NULL;
461 	dmap_mdns_browser_start (share->priv->mdns_browser, &error);
462 	if (error != NULL) {
463 		g_warning ("Unable to start Remote lookup: %s",
464 			   error->message);
465 		g_error_free (error);
466 	}
467 }
468 
469 static gboolean
remove_remotes_cb(gpointer service_name,G_GNUC_UNUSED gpointer remote_info,gpointer share)470 remove_remotes_cb (gpointer service_name, G_GNUC_UNUSED gpointer remote_info,
471 		   gpointer share)
472 {
473 	g_signal_emit ((DACPShare *) share,
474 		       signals[REMOTE_LOST], 0, (gchar *) service_name);
475 	return TRUE;
476 }
477 
478 void
dacp_share_stop_lookup(DACPShare * share)479 dacp_share_stop_lookup (DACPShare * share)
480 {
481 	GError *error;
482 
483 	if (!share->priv->mdns_browser) {
484 		g_warning ("DACP browsing not started");
485 		return;
486 	}
487 
488 	g_hash_table_foreach_remove (share->priv->remotes, remove_remotes_cb,
489 				     share);
490 
491 	error = NULL;
492 	dmap_mdns_browser_stop (share->priv->mdns_browser, &error);
493 	if (error != NULL) {
494 		g_warning ("Unable to stop Remote lookup: %s",
495 			   error->message);
496 		g_error_free (error);
497 	}
498 
499 	share->priv->mdns_browser = NULL;
500 }
501 
502 const char *
dacp_share_get_type_of_service(G_GNUC_UNUSED DMAPShare * share)503 dacp_share_get_type_of_service (G_GNUC_UNUSED DMAPShare * share)
504 {
505 	return DACP_TYPE_OF_SERVICE;
506 }
507 
508 void
dacp_share_player_updated(DACPShare * share)509 dacp_share_player_updated (DACPShare * share)
510 {
511 	share->priv->current_revision++;
512 	dacp_share_send_playstatusupdate (share);
513 }
514 
515 static void
status_update_message_finished(SoupMessage * message,DACPShare * share)516 status_update_message_finished (SoupMessage * message, DACPShare * share)
517 {
518 	share->priv->update_queue =
519 		g_slist_remove (share->priv->update_queue, message);
520 	g_object_unref (message);
521 }
522 
523 static void
dacp_share_send_playstatusupdate(DACPShare * share)524 dacp_share_send_playstatusupdate (DACPShare * share)
525 {
526 	GSList *list;
527 	SoupServer *server = NULL;
528 
529 	g_object_get (share, "server", &server, NULL);
530 	if (server) {
531 		for (list = share->priv->update_queue; list;
532 		     list = list->next) {
533 			dacp_share_fill_playstatusupdate (share,
534 			                                  (SoupMessage*) list->data);
535 			soup_server_unpause_message (server,
536 			                             (SoupMessage*) list->data);
537 		}
538 		g_object_unref (server);
539 	}
540 	g_slist_free (share->priv->update_queue);
541 	share->priv->update_queue = NULL;
542 }
543 
544 static void
dacp_share_fill_playstatusupdate(DACPShare * share,SoupMessage * message)545 dacp_share_fill_playstatusupdate (DACPShare * share, SoupMessage * message)
546 {
547 	GNode *cmst;
548 	DAAPRecord *record;
549 	DACPPlayState play_state;
550 	DACPRepeatState repeat_state;
551 	gboolean shuffle_state;
552 	guint playing_time;
553 
554 	g_object_get (share->priv->player,
555 		      "play-state", &play_state,
556 		      "repeat-state", &repeat_state,
557 		      "shuffle-state", &shuffle_state,
558 		      "playing-time", &playing_time, NULL);
559 
560 	record = dacp_player_now_playing_record (share->priv->player);
561 
562 	cmst = dmap_structure_add (NULL, DMAP_CC_CMST);
563 	dmap_structure_add (cmst, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
564 	dmap_structure_add (cmst, DMAP_CC_CMSR,
565 			    share->priv->current_revision);
566 	dmap_structure_add (cmst, DMAP_CC_CAVC, 1);
567 	dmap_structure_add (cmst, DMAP_CC_CAPS, (gint32) play_state);
568 	dmap_structure_add (cmst, DMAP_CC_CASH, shuffle_state ? 1 : 0);
569 	dmap_structure_add (cmst, DMAP_CC_CARP, (gint32) repeat_state);
570 	if (record) {
571 		gchar *title;
572 		gchar *artist;
573 		gchar *album;
574 		gint duration;
575 		guint track_time;
576 
577 		g_object_get (record,
578 			      "title", &title,
579 			      "songartist", &artist,
580 			      "songalbum", &album,
581 			      "duration", &duration, NULL);
582 		track_time = duration * 1000;
583 		dmap_structure_add (cmst, DMAP_CC_CAAS, 2);
584 		dmap_structure_add (cmst, DMAP_CC_CAAR, 6);
585 		dmap_structure_add (cmst, DMAP_CC_CANP, (gint64) 0); // FIXME: may be wrong.
586 		if (title)
587 			dmap_structure_add (cmst, DMAP_CC_CANN, title);
588 		if (artist)
589 			dmap_structure_add (cmst, DMAP_CC_CANA, artist);
590 		if (album)
591 			dmap_structure_add (cmst, DMAP_CC_CANL, album);
592 		dmap_structure_add (cmst, DMAP_CC_CANG, "");
593 		dmap_structure_add (cmst, DMAP_CC_ASAI, 0);
594 		//dmap_structure_add (cmst, DMAP_CC_AEMK, 1);
595 		g_debug ("Playing time: %u, Track time: %u", playing_time,
596 			 track_time);
597 		dmap_structure_add (cmst, DMAP_CC_CANT,
598 				    track_time - playing_time);
599 		dmap_structure_add (cmst, DMAP_CC_CAST, track_time);
600 
601 		g_free (title);
602 		g_free (artist);
603 		g_free (album);
604 
605 		g_object_unref (record);
606 	}
607 
608 	_dmap_share_message_set_from_dmap_structure (DMAP_SHARE (share),
609 						     message, cmst);
610 	dmap_structure_destroy (cmst);
611 }
612 
613 static void
debug_param(gpointer key,gpointer val,G_GNUC_UNUSED gpointer user_data)614 debug_param (gpointer key, gpointer val, G_GNUC_UNUSED gpointer user_data)
615 {
616 	g_debug ("%s %s", (char *) key, (char *) val);
617 }
618 
619 void
dacp_share_login(DMAPShare * share,SoupServer * server,SoupMessage * message,const char * path,GHashTable * query,SoupClientContext * context)620 dacp_share_login (DMAPShare * share,
621 		  SoupServer * server,
622 		  SoupMessage * message,
623 		  const char *path,
624 		  GHashTable * query, SoupClientContext * context)
625 {
626 	gchar *pairing_guid;
627 
628 	g_debug ("Path is %s.", path);
629 	if (query) {
630 		g_hash_table_foreach (query, debug_param, NULL);
631 	}
632 
633 	pairing_guid = g_hash_table_lookup (query, "pairing-guid");
634 
635 	if (pairing_guid != NULL) {
636 		gboolean allow_login;
637 
638 		g_signal_emit (share, signals[LOOKUP_GUID], 0, pairing_guid,
639 			       &allow_login);
640 
641 		if (!allow_login) {
642 			g_debug ("Unknown remote trying to connect");
643 			soup_message_set_status (message,
644 						 SOUP_STATUS_FORBIDDEN);
645 			return;
646 		}
647 	}
648 
649 	_dmap_share_login (share, server, message, path, query, context);
650 }
651 
652 void
dacp_share_ctrl_int(DMAPShare * share,SoupServer * server,SoupMessage * message,const char * path,GHashTable * query,SoupClientContext * context)653 dacp_share_ctrl_int (DMAPShare * share,
654 		     SoupServer * server,
655 		     SoupMessage * message,
656 		     const char *path,
657 		     GHashTable * query, SoupClientContext * context)
658 {
659 	const char *rest_of_path;
660 
661 	DACPShare *dacp_share = DACP_SHARE (share);
662 
663 	g_debug ("Path is %s.", path);
664 	if (query) {
665 		g_hash_table_foreach (query, debug_param, NULL);
666 	}
667 
668 	rest_of_path = strchr (path + 1, '/');
669 
670 	/* If calling /ctrl-int without args, the client doesnt need a
671 	 * session-id, otherwise it does and it should be validated. */
672 	if ((rest_of_path != NULL)
673 	    &&
674 	    (!_dmap_share_session_id_validate
675 	     (share, context, message, query, NULL))) {
676 		soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
677 		return;
678 	}
679 
680 	if (rest_of_path == NULL) {
681 		/* CACI control-int
682 		 *      MSTT status
683 		 *      MUTY update type
684 		 *      MTCO specified total count
685 		 *      MRCO returned count
686 		 *      MLCL listing
687 		 *              MLIT listing item
688 		 *                      MIID item id
689 		 *                      CMIK Unknown (TRUE)
690 		 *                      CMSP Unknown (TRUE)
691 		 *                      CMSV Unknown (TRUE)
692 		 *                      CASS Unknown (TRUE)
693 		 *                      CASU Unknown (TRUE)
694 		 *                      CASG Unknown (TRUE)
695 		 */
696 
697 		GNode *caci;
698 		GNode *mlcl;
699 		GNode *mlit;
700 
701 		// dacp.controlint
702 		caci = dmap_structure_add (NULL, DMAP_CC_CACI);
703 		// dmap.status
704 		dmap_structure_add (caci, DMAP_CC_MSTT,
705 				    (gint32) DMAP_STATUS_OK);
706 		// dmap.updatetype
707 		dmap_structure_add (caci, DMAP_CC_MUTY, 0);
708 		// dmap.specifiedtotalcount
709 		dmap_structure_add (caci, DMAP_CC_MTCO, (gint32) 1);
710 		// dmap.returnedcount
711 		dmap_structure_add (caci, DMAP_CC_MRCO, (gint32) 1);
712 		// dmap.listing
713 		mlcl = dmap_structure_add (caci, DMAP_CC_MLCL);
714 		// dmap.listingitem
715 		mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
716 		// dmap.itemid
717 		dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
718 		// Unknown (TRUE)
719 		dmap_structure_add (mlit, DMAP_CC_CMIK, (gint32) 1);
720 		// Unknown (TRUE)
721 		dmap_structure_add (mlit, DMAP_CC_CMPR, (gint32) (2 << 16 | 1));
722 		dmap_structure_add (mlit, DMAP_CC_CAPR, (gint32) (2 << 16 | 2));
723 		dmap_structure_add (mlit, DMAP_CC_CMSP, (gint32) 1);
724 		// Unknown (TRUE)
725 		dmap_structure_add (mlit, DMAP_CC_AEFR, 0x64);
726 		dmap_structure_add (mlit, DMAP_CC_CMSV, (gint32) 1);
727 		// Unknown (TRUE)
728 		dmap_structure_add (mlit, DMAP_CC_CASS, (gint32) 1);
729 		// Unknown (TRUE)
730 		dmap_structure_add (mlit, DMAP_CC_CAOV, 1);
731 		dmap_structure_add (mlit, DMAP_CC_CASU, (gint32) 1);
732 		// Unknown (TRUE)
733 		dmap_structure_add (mlit, DMAP_CC_CASG, (gint32) 1);
734 		dmap_structure_add (mlit, DMAP_CC_CMRL, 1);
735 
736 		_dmap_share_message_set_from_dmap_structure (share, message,
737 							     caci);
738 		dmap_structure_destroy (caci);
739 	} else if (g_ascii_strcasecmp ("/1/getproperty", rest_of_path) == 0) {
740 		gchar *properties_query, **properties, **property;
741 		GNode *cmgt;
742 
743 		properties_query = g_hash_table_lookup (query, "properties");
744 
745 		if (!properties_query) {
746 			g_warning ("No property specified");
747 			return;
748 		}
749 
750 		cmgt = dmap_structure_add (NULL, DMAP_CC_CMGT);
751 		dmap_structure_add (cmgt, DMAP_CC_MSTT, DMAP_STATUS_OK);
752 
753 		properties = g_strsplit (properties_query, ",", -1);
754 		for (property = properties; *property; property++) {
755 			if (g_ascii_strcasecmp (*property, "dmcp.volume") ==
756 			    0) {
757 				gulong volume;
758 
759 				g_object_get (dacp_share->priv->player,
760 					      "volume", &volume, NULL);
761 				dmap_structure_add (cmgt, DMAP_CC_CMVO,
762 						    volume);
763 			} else {
764 				g_warning ("Unhandled property %s",
765 					   *property);
766 			}
767 		}
768 
769 		g_strfreev (properties);
770 
771 		_dmap_share_message_set_from_dmap_structure (share, message,
772 							     cmgt);
773 		dmap_structure_destroy (cmgt);
774 	} else if (g_ascii_strcasecmp ("/1/setproperty", rest_of_path) == 0) {
775 		if (g_hash_table_lookup (query, "dmcp.volume")) {
776 			gdouble volume =
777 				strtod (g_hash_table_lookup
778 					(query, "dmcp.volume"), NULL);
779 			g_object_set (dacp_share->priv->player, "volume",
780 				      (gulong) volume, NULL);
781 		}
782 		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
783 	} else if (g_ascii_strcasecmp ("/1/getspeakers", rest_of_path) == 0) {
784 		GNode *casp;
785 		gulong volume;
786 
787 		casp = dmap_structure_add (NULL, DMAP_CC_CASP);
788 		dmap_structure_add (casp, DMAP_CC_MSTT,
789 				    (gint32) DMAP_STATUS_OK);
790 		dmap_structure_add (casp, DMAP_CC_MDCL);
791 
792 		dmap_structure_add (casp, DMAP_CC_CAIA, TRUE);
793 		dmap_structure_add (casp, DMAP_CC_CAHP, 1);
794 		dmap_structure_add (casp, DMAP_CC_CAIV, 1);
795 		dmap_structure_add (casp, DMAP_CC_MINM, "Computer");
796 		dmap_structure_add (casp, DMAP_CC_MSMA, (gint32) 0);
797 
798 		g_object_get (dacp_share->priv->player, "volume", &volume, NULL);
799 		dmap_structure_add (casp, DMAP_CC_CMVO, volume);
800 
801 		_dmap_share_message_set_from_dmap_structure (share, message,
802 							     casp);
803 		dmap_structure_destroy (casp);
804 	} else if (g_ascii_strcasecmp ("/1/playstatusupdate", rest_of_path) ==
805 		   0) {
806 		gchar *revision =
807 			g_hash_table_lookup (query, "revision-number");
808 		gint revision_number = atoi (revision);
809 
810 		if (revision_number >= dacp_share->priv->current_revision) {
811 			g_object_ref (message);
812 			dacp_share->priv->update_queue =
813 				g_slist_prepend (dacp_share->
814 						 priv->update_queue, message);
815 			g_signal_connect_object (message, "finished",
816 						 G_CALLBACK
817 						 (status_update_message_finished),
818 						 dacp_share, 0);
819 			soup_server_pause_message (server, message);
820 		} else {
821 			dacp_share_fill_playstatusupdate (dacp_share,
822 							  message);
823 		}
824 	} else if (g_ascii_strcasecmp ("/1/playpause", rest_of_path) == 0) {
825 		dacp_player_play_pause (dacp_share->priv->player);
826 		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
827 	} else if (g_ascii_strcasecmp ("/1/pause", rest_of_path) == 0) {
828 		dacp_player_pause (dacp_share->priv->player);
829 		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
830 	} else if (g_ascii_strcasecmp ("/1/nextitem", rest_of_path) == 0) {
831 		dacp_player_next_item (dacp_share->priv->player);
832 		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
833 	} else if (g_ascii_strcasecmp ("/1/previtem", rest_of_path) == 0) {
834 		dacp_player_prev_item (dacp_share->priv->player);
835 		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
836 	} else if (g_ascii_strcasecmp ("/1/nowplayingartwork", rest_of_path)
837 		   == 0) {
838 		guint width = 320;
839 		guint height = 320;
840 		guchar *artwork_filename;
841 		gchar *buffer;
842 		gsize buffer_len;
843 
844 		if (g_hash_table_lookup (query, "mw"))
845 			width = atoi (g_hash_table_lookup (query, "mw"));
846 		if (g_hash_table_lookup (query, "mh"))
847 			height = atoi (g_hash_table_lookup (query, "mh"));
848 		artwork_filename =
849 			dacp_player_now_playing_artwork (dacp_share->
850 							 priv->player, width,
851 							 height);
852 		if (!artwork_filename) {
853 			g_debug ("No artwork for currently playing song");
854 			soup_message_set_status (message,
855 						 SOUP_STATUS_NOT_FOUND);
856 			return;
857 		}
858 #ifdef HAVE_GDKPIXBUF
859 		GdkPixbuf *artwork =
860 			gdk_pixbuf_new_from_file_at_scale ((char *) artwork_filename,
861 							   width, height,
862 							   TRUE, NULL);
863 
864 		if (!artwork) {
865 			g_debug ("Error loading image file");
866 			g_free (artwork_filename);
867 			soup_message_set_status (message,
868 						 SOUP_STATUS_INTERNAL_SERVER_ERROR);
869 			return;
870 		}
871 		if (!gdk_pixbuf_save_to_buffer
872 		    (artwork, &buffer, &buffer_len, "png", NULL, NULL)) {
873 			g_debug ("Error saving artwork to PNG");
874 			g_object_unref (artwork);
875 			g_free (artwork_filename);
876 			soup_message_set_status (message,
877 						 SOUP_STATUS_INTERNAL_SERVER_ERROR);
878 			return;
879 		}
880 		g_object_unref (artwork);
881 #else
882 		if (!g_file_get_contents
883 		    (artwork_filename, &buffer, &buffer_len, NULL)) {
884 			g_debug ("Error getting artwork data");
885 			g_free (artwork_filename);
886 			soup_message_set_status (message,
887 						 SOUP_STATUS_INTERNAL_SERVER_ERROR);
888 			return;
889 		}
890 #endif
891 		g_free (artwork_filename);
892 		soup_message_set_status (message, SOUP_STATUS_OK);
893 		soup_message_set_response (message, "image/png",
894 					   SOUP_MEMORY_TAKE, buffer,
895 					   buffer_len);
896 	} else if (g_ascii_strcasecmp ("/1/cue", rest_of_path) == 0) {
897 		gchar *command;
898 
899 		command = g_hash_table_lookup (query, "command");
900 
901 		if (!command) {
902 			g_debug ("No CUE command specified");
903 			soup_message_set_status (message,
904 						 SOUP_STATUS_NO_CONTENT);
905 			return;
906 		} else if (g_ascii_strcasecmp ("clear", command) == 0) {
907 			dacp_player_cue_clear (dacp_share->priv->player);
908 			soup_message_set_status (message,
909 						 SOUP_STATUS_NO_CONTENT);
910 		} else if (g_ascii_strcasecmp ("play", command) == 0) {
911 			GNode *cacr;
912 			gchar *record_query;
913 			gchar *sort_by;
914 			GHashTable *records;
915 			GList *sorted_records;
916 			GSList *filter_def;
917 			DMAPDb *db;
918 			gint index =
919 				atoi (g_hash_table_lookup (query, "index"));
920 
921 			g_object_get (share, "db", &db, NULL);
922 			record_query = g_hash_table_lookup (query, "query");
923 			filter_def = _dmap_share_build_filter (record_query);
924 			records = dmap_db_apply_filter (db, filter_def);
925 			sorted_records = g_hash_table_get_values (records);
926 			sort_by = g_hash_table_lookup (query, "sort");
927 			if (g_strcmp0 (sort_by, "album") == 0) {
928 				sorted_records =
929 					g_list_sort_with_data (sorted_records,
930 							       (GCompareDataFunc)
931 							       daap_record_cmp_by_album,
932 							       db);
933 			} else if (sort_by != NULL) {
934 				g_warning ("Unknown sort column: %s",
935 					   sort_by);
936 			}
937 
938 			dacp_player_cue_play (dacp_share->priv->player,
939 					      sorted_records, index);
940 
941 			g_list_free (sorted_records);
942 			g_hash_table_unref (records);
943 			dmap_share_free_filter (filter_def);
944 
945 			cacr = dmap_structure_add (NULL, DMAP_CC_CACR);
946 			dmap_structure_add (cacr, DMAP_CC_MSTT,
947 					    DMAP_STATUS_OK);
948 			dmap_structure_add (cacr, DMAP_CC_MIID, index);
949 
950 			_dmap_share_message_set_from_dmap_structure (share,
951 								     message,
952 								     cacr);
953 			dmap_structure_destroy (cacr);
954 		} else {
955 			g_warning ("Unhandled cue command: %s", command);
956 			soup_message_set_status (message,
957 						 SOUP_STATUS_NO_CONTENT);
958 			return;
959 		}
960 	} else {
961 		g_warning ("Unhandled ctrl-int command: %s", rest_of_path);
962 		soup_message_set_status (message, SOUP_STATUS_BAD_REQUEST);
963 	}
964 }
965 
966 #define PAIR_TXT_LENGTH 16
967 #define PASSCODE_LENGTH 4
968 
969 static gchar *
dacp_share_pairing_code(G_GNUC_UNUSED DACPShare * share,gchar * pair_txt,gchar passcode[4])970 dacp_share_pairing_code (G_GNUC_UNUSED DACPShare * share,
971                          gchar * pair_txt,
972 			 gchar passcode[4])
973 {
974 	int i;
975 	GString *pairing_code;
976 	gchar *pairing_string;
977 	gchar *ret;
978 
979 	/* The pairing code is the MD5 sum of the concatenation of pair_txt
980 	 * with the passcode, but the passcode takes 16-bits unicodes characters */
981 	pairing_string =
982 		g_strnfill (PAIR_TXT_LENGTH + PASSCODE_LENGTH * 2, '\0');
983 	g_strlcpy (pairing_string, pair_txt,
984 		   PAIR_TXT_LENGTH + PASSCODE_LENGTH * 2);
985 	for (i = 0; i < 4; i++) {
986 		pairing_string[PAIR_TXT_LENGTH + i * 2] = passcode[i];
987 	}
988 
989 	pairing_code =
990 		g_string_new (g_compute_checksum_for_data
991 			      (G_CHECKSUM_MD5, (guchar *) pairing_string,
992 			       PAIR_TXT_LENGTH + PASSCODE_LENGTH * 2));
993 	g_string_ascii_up (pairing_code);
994 	ret = pairing_code->str;
995 	g_string_free (pairing_code, FALSE);
996 
997 	return ret;
998 }
999 
1000 static void
connection_handler_cb(DMAPConnection * connection,guint status,GNode * structure,gpointer user_data)1001 connection_handler_cb (DMAPConnection * connection, guint status,
1002 		       GNode * structure, gpointer user_data)
1003 {
1004 	gboolean connected;
1005 	GHashTableIter iter;
1006 	gpointer key, value;
1007 	DACPShare *share = user_data;
1008 	DACPRemoteInfo *remote_info = NULL;
1009 	gchar *service_name = NULL;
1010 	DMAPStructureItem *item = NULL;
1011 	gchar *pairing_guid;
1012 
1013 	g_debug ("Pairing returned with code %u", status);
1014 	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
1015 		connected = TRUE;
1016 	} else {
1017 		connected = FALSE;
1018 	}
1019 
1020 	/* Get the pairing-guid to identify this remote in the future. */
1021 	if (structure)
1022 		item = dmap_structure_find_item (structure, DMAP_CC_CMPG);
1023 	if (item) {
1024 		guint64 guid = g_value_get_int64 (&(item->content));
1025 
1026 		pairing_guid =
1027 			g_strdup_printf ("0x%.16" G_GINT64_MODIFIER "X",
1028 					 guid);
1029 		g_signal_emit (share, signals[ADD_GUID], 0, pairing_guid);
1030 		g_free (pairing_guid);
1031 	}
1032 
1033 	/* Find the remote that initiated this connection */
1034 	g_hash_table_iter_init (&iter, share->priv->remotes);
1035 	while (g_hash_table_iter_next (&iter, &key, &value)) {
1036 		if (((DACPRemoteInfo *) value)->connection == connection) {
1037 			service_name = (gchar *) key;
1038 			remote_info = (DACPRemoteInfo *) value;
1039 			break;
1040 		}
1041 	}
1042 
1043 	if (remote_info == NULL) {
1044 		g_warning ("Remote for connection not found");
1045 		return;
1046 	}
1047 
1048 	/* Frees the connection */
1049 	remote_info->connection = NULL;
1050 	g_object_unref (connection);
1051 
1052 	/* FIXME: Send more detailed error info, such as wrong pair code, etc */
1053 	g_signal_emit (share, signals[REMOTE_PAIRED], 0, service_name,
1054 		       connected);
1055 }
1056 
1057 void
dacp_share_pair(DACPShare * share,gchar * service_name,gchar passcode[4])1058 dacp_share_pair (DACPShare * share, gchar * service_name, gchar passcode[4])
1059 {
1060 	gchar *pairing_code;
1061 	gchar *name;
1062 	gchar *path;
1063 	DACPRemoteInfo *remote_info;
1064 
1065 	remote_info = g_hash_table_lookup (share->priv->remotes,
1066 					   service_name);
1067 
1068 	if (remote_info == NULL) {
1069 		g_warning ("Remote %s not found.", service_name);
1070 		return;
1071 	}
1072 
1073 	if (remote_info->connection != NULL) {
1074 		g_warning ("Already pairing remote %s.", service_name);
1075 		return;
1076 	}
1077 
1078 	g_object_get (share, "name", &name, NULL);
1079 
1080 	remote_info->connection = DMAP_CONNECTION (dacp_connection_new (name,
1081 						                        remote_info->host, remote_info->port,
1082 									NULL,
1083 									NULL));
1084 	/* This is required since we don't call DMAPConnection default handler */
1085 	dmap_connection_setup (remote_info->connection);
1086 
1087 	/* Get the remote path for pairing */
1088 	pairing_code =
1089 		dacp_share_pairing_code (share, remote_info->pair_txt,
1090 					 passcode);
1091 	path = g_strdup_printf ("/pair?pairingcode=%s&servicename=%s",
1092 				pairing_code, name);
1093 	g_free (pairing_code);
1094 
1095 	g_debug ("Pairing remote in %s:%d/%s", remote_info->host,
1096 		 remote_info->port, path);
1097 
1098 	/* Let DMAPConnection do the heavy work */
1099 	dmap_connection_get (remote_info->connection, path, FALSE,
1100 			     connection_handler_cb, share);
1101 
1102 	g_free (path);
1103 }
1104