32
32
#include <libsoup/soup-message.h>
33
33
#include <libsoup/soup-uri.h>
34
34
#include <libsoup/soup-server.h>
35
#include <libsoup/soup-server-auth.h>
35
36
#include <libsoup/soup-server-message.h>
36
37
#include <libgnomevfs/gnome-vfs.h>
38
39
#include "rb-daap-share.h"
39
40
#include "rb-daap-structure.h"
40
#include "rb-daap-mdns.h"
41
#include "rb-daap-mdns-publisher.h"
41
42
#include "rb-daap-dialog.h"
43
44
#include "rb-playlist-source.h"
54
55
GParamSpec *pspec);
55
56
static void rb_daap_share_dispose (GObject *object);
57
static void rb_daap_share_start_publish (RBDAAPShare *share);
58
static void rb_daap_share_stop_publish (RBDAAPShare *share);
57
static void rb_daap_share_maybe_restart (RBDAAPShare *share);
58
static gboolean rb_daap_share_publish_start (RBDAAPShare *share);
59
static gboolean rb_daap_share_publish_stop (RBDAAPShare *share);
60
static gboolean rb_daap_share_server_start (RBDAAPShare *share);
61
static gboolean rb_daap_share_server_stop (RBDAAPShare *share);
59
62
static void rb_daap_share_playlist_created (RBPlaylistManager *mgr,
60
63
RBSource *playlist,
61
64
RBDAAPShare *share);
66
69
static void rb_daap_share_forget_playlist (gpointer data,
67
70
RBDAAPShare *share);
70
#define CONF_NAME CONF_PREFIX "/sharing/share_name"
71
72
#define STANDARD_DAAP_PORT 3689
73
74
/* HTTP chunk size used to send files to clients */
74
75
#define DAAP_SHARE_CHUNK_SIZE 16384
78
RB_DAAP_SHARE_AUTH_METHOD_NONE = 0,
79
RB_DAAP_SHARE_AUTH_METHOD_NAME_AND_PASSWORD = 1,
80
RB_DAAP_SHARE_AUTH_METHOD_PASSWORD = 2
81
} RBDAAPShareAuthMethod;
76
83
struct RBDAAPSharePrivate {
87
RBDAAPShareAuthMethod auth_method;
80
89
/* mdns/dns-sd publishing things */
90
gboolean server_active;
81
91
gboolean published;
82
RBDAAPmDNSPublisher publisher;
92
RBDaapMdnsPublisher *publisher;
84
94
/* http server things */
85
95
SoupServer *server;
86
96
guint revision_number;
98
GHashTable *session_ids;
90
102
gint32 next_song_id;
177
rb_daap_share_set_name (RBDAAPShare *share,
183
g_return_if_fail (share != NULL);
185
g_free (share->priv->name);
186
share->priv->name = g_strdup (name);
189
res = rb_daap_mdns_publisher_set_name (share->priv->publisher, name, &error);
191
g_warning ("Unable to change MDNS service name: %s", error->message);
192
g_error_free (error);
197
published_cb (RBDaapMdnsPublisher *publisher,
201
if (share->priv->name == NULL || name == NULL) {
205
if (strcmp (name, share->priv->name) == 0) {
206
rb_debug ("mDNS publish successful");
207
share->priv->published = TRUE;
212
name_collision_cb (RBDaapMdnsPublisher *publisher,
218
if (share->priv->name == NULL || name == NULL) {
222
if (strcmp (name, share->priv->name) == 0) {
223
rb_debug ("Duplicate share name on mDNS");
225
new_name = rb_daap_collision_dialog_new_run (NULL, share->priv->name);
227
rb_daap_share_set_name (share, new_name);
157
235
rb_daap_share_init (RBDAAPShare *share)
159
237
share->priv = RB_DAAP_SHARE_GET_PRIVATE (share);
161
239
share->priv->revision_number = 5;
241
share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_NONE;
242
share->priv->publisher = rb_daap_mdns_publisher_new ();
243
g_signal_connect_object (share->priv->publisher,
245
G_CALLBACK (published_cb),
247
g_signal_connect_object (share->priv->publisher,
249
G_CALLBACK (name_collision_cb),
255
rb_daap_share_set_password (RBDAAPShare *share,
256
const char *password)
258
g_return_if_fail (share != NULL);
260
if (share->priv->password && password &&
261
strcmp (password, share->priv->password) == 0) {
265
g_free (share->priv->password);
266
share->priv->password = g_strdup (password);
267
if (password != NULL) {
268
share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_PASSWORD;
270
share->priv->auth_method = RB_DAAP_SHARE_AUTH_METHOD_NONE;
273
rb_daap_share_maybe_restart (share);
277
rb_daap_share_set_playlist_manager (RBDAAPShare *share,
278
RBPlaylistManager *playlist_manager)
282
g_return_if_fail (share != NULL);
284
share->priv->playlist_manager = playlist_manager;
286
g_signal_connect_object (G_OBJECT (share->priv->playlist_manager),
288
G_CALLBACK (rb_daap_share_playlist_created),
291
/* Currently, there are no playlists when this object is created, but in
294
playlists = rb_playlist_manager_get_playlists (share->priv->playlist_manager);
295
g_list_foreach (playlists, (GFunc) rb_daap_share_process_playlist, share);
296
g_list_free (playlists);
170
305
RBDAAPShare *share = RB_DAAP_SHARE (object);
172
307
switch (prop_id) {
174
gboolean restart_publish = FALSE;
175
const char *name = g_value_get_string (value);
177
/* check if the name hasn't really changed */
178
if (share->priv->name && name &&
179
strcmp (name, share->priv->name) == 0) {
183
if (share->priv->name) {
184
g_free (share->priv->name);
186
if (share->priv->published) {
187
rb_daap_share_stop_publish (share);
188
restart_publish = TRUE;
192
share->priv->name = g_strdup (name);
194
if (restart_publish) {
195
rb_daap_share_start_publish (share);
309
rb_daap_share_set_name (share, g_value_get_string (value));
312
rb_daap_share_set_password (share, g_value_get_string (value));
201
315
share->priv->db = g_value_get_object (value);
203
317
case PROP_PLAYLIST_MANAGER:
206
share->priv->playlist_manager = g_value_get_object (value);
207
g_signal_connect_object (G_OBJECT (share->priv->playlist_manager),
209
G_CALLBACK (rb_daap_share_playlist_created),
212
/* Currently, there are no playlists when this object is created, but in
215
playlists = rb_playlist_manager_get_playlists (share->priv->playlist_manager);
216
g_list_foreach (playlists, (GFunc) rb_daap_share_process_playlist, share);
217
g_list_free (playlists);
318
rb_daap_share_set_playlist_manager (share, g_value_get_object (value));
221
321
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
323
425
rb_daap_share_dispose (GObject *object)
325
427
RBDAAPShare *share = RB_DAAP_SHARE (object);
327
429
if (share->priv->published) {
328
rb_daap_share_stop_publish (share);
332
g_free (share->priv->name);
333
g_object_unref (share->priv->db);
334
g_object_unref (share->priv->playlist_manager);
430
rb_daap_share_publish_stop (share);
433
if (share->priv->server_active) {
434
rb_daap_share_server_stop (share);
437
g_free (share->priv->name);
438
g_object_unref (share->priv->db);
439
g_object_unref (share->priv->playlist_manager);
336
g_list_foreach (share->priv->playlist_ids, (GFunc) rb_daap_share_forget_playlist, share);
337
g_list_foreach (share->priv->playlist_ids, (GFunc) g_free, NULL);
441
g_list_foreach (share->priv->playlist_ids, (GFunc) rb_daap_share_forget_playlist, share);
442
g_list_foreach (share->priv->playlist_ids, (GFunc) g_free, NULL);
444
if (share->priv->publisher) {
445
g_object_unref (share->priv->publisher);
340
448
G_OBJECT_CLASS (rb_daap_share_parent_class)->dispose (object);
502
616
rb_daap_structure_destroy (mccr);
505
/* This is arbitrary. iTunes communicates with a session id for
506
* reasons relating to updates to the database and such, I think
507
* Since we don't do that, and since we don't keep track of connections
508
* like iTunes do, everyone can just share the same, special, arbitrary
511
#define DAAP_SESSION_ID 42
620
message_get_session_id (SoupMessage *message,
631
uri = soup_message_get_uri (message);
636
position = strstr (uri->query, "session-id=");
638
if (position == NULL) {
639
rb_debug ("session id not found");
644
session_id = (guint32) strtoul (position, NULL, 10);
654
message_get_revision_number (SoupMessage *message,
659
guint revision_number;
665
uri = soup_message_get_uri (message);
670
position = strstr (uri->query, "revision-number=");
672
if (position == NULL) {
673
rb_debug ("client asked for an update without a revision number?!?\n");
678
revision_number = atoi (position);
681
*number = revision_number;
688
session_id_validate (RBDAAPShare *share,
689
SoupServerContext *context,
690
SoupMessage *message,
696
const char *remote_address;
702
res = message_get_session_id (message, &session_id);
704
rb_debug ("Validation failed: Unable to parse session id from message");
708
/* check hash for remote address */
709
addr = g_hash_table_lookup (share->priv->session_ids, GUINT_TO_POINTER (session_id));
711
rb_debug ("Validation failed: Unable to lookup session id %u", session_id);
715
remote_address = soup_server_context_get_client_host (context);
716
rb_debug ("Validating session id %u from %s matches %s",
717
session_id, remote_address, addr);
718
if (remote_address == NULL || strcmp (addr, remote_address) != 0) {
719
rb_debug ("Validation failed: Remote address does not match stored address");
731
session_id_generate (RBDAAPShare *share,
732
SoupServerContext *context)
736
id = g_random_int ();
742
session_id_create (RBDAAPShare *share,
743
SoupServerContext *context)
747
char *remote_address;
750
/* create a unique session id */
751
id = session_id_generate (share, context);
752
rb_debug ("Generated session id %u", id);
754
/* if already used, try again */
755
addr = g_hash_table_lookup (share->priv->session_ids, GUINT_TO_POINTER (id));
756
} while (addr != NULL);
758
/* store session id and remote address */
759
remote_address = g_strdup (soup_server_context_get_client_host (context));
760
g_hash_table_insert (share->priv->session_ids, GUINT_TO_POINTER (id), remote_address);
766
session_id_remove (RBDAAPShare *share,
767
SoupServerContext *context,
770
g_hash_table_remove (share->priv->session_ids, GUINT_TO_POINTER (id));
514
login_cb (RBDAAPShare *share,
774
login_cb (RBDAAPShare *share,
775
SoupServerContext *context,
515
776
SoupMessage *message)
517
778
/* MLOG login response
519
780
* MLID session id
785
session_id = session_id_create (share, context);
787
rb_debug ("Handling login session id %u", session_id);
523
789
mlog = rb_daap_structure_add (NULL, RB_DAAP_CC_MLOG);
524
790
rb_daap_structure_add (mlog, RB_DAAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
525
rb_daap_structure_add (mlog, RB_DAAP_CC_MLID, (gint32) DAAP_SESSION_ID);
791
rb_daap_structure_add (mlog, RB_DAAP_CC_MLID, session_id);
527
793
message_set_from_rb_daap_structure (message, mlog);
528
794
rb_daap_structure_destroy (mlog);
798
logout_cb (RBDAAPShare *share,
799
SoupServerContext *context,
800
SoupMessage *message)
805
if (session_id_validate (share, context, message, &id)) {
806
rb_debug ("Handling logout session id %u", id);
807
session_id_remove (share, context, id);
809
status = SOUP_STATUS_NO_CONTENT;
811
status = SOUP_STATUS_FORBIDDEN;
814
soup_message_set_status (message, status);
815
soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CONTENT_LENGTH);
532
819
update_cb (RBDAAPShare *share,
820
SoupServerContext *context,
533
821
SoupMessage *message)
536
gchar *revision_number_position;
537
guint revision_number;
539
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
541
revision_number_position = strstr (path, "revision-number=");
543
if (revision_number_position == NULL) {
544
g_print ("client asked for an update without a revision number?!?\n");
549
revision_number_position += 16;
550
revision_number = atoi (revision_number_position);
554
if (revision_number != share->priv->revision_number) {
823
guint revision_number;
826
res = message_get_revision_number (message, &revision_number);
828
if (res && revision_number != share->priv->revision_number) {
555
829
/* MUPD update response
557
831
* MUSR server revision
772
1046
if (client_requested (mb->bits, SONG_TRACK_NUMBER))
773
1047
rb_daap_structure_add (mlit, RB_DAAP_CC_ASTN, (gint32) rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
774
1048
if (client_requested (mb->bits, SONG_USER_RATING))
775
rb_daap_structure_add (mlit, RB_DAAP_CC_ASUR, 0); // fixme
1049
rb_daap_structure_add (mlit, RB_DAAP_CC_ASUR, 0); /* FIXME */
776
1050
if (client_requested (mb->bits, SONG_YEAR))
777
1051
rb_daap_structure_add (mlit, RB_DAAP_CC_ASYR, 0);
908
1179
databases_cb (RBDAAPShare *share,
1180
SoupServerContext *context,
909
1181
SoupMessage *message)
912
1184
gchar *rest_of_path;
913
// guint revision_number;
1185
/*guint revision_number;*/
1187
if (! session_id_validate (share, context, message, NULL)) {
1188
soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
1189
soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CONTENT_LENGTH);
915
1193
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
1112
1390
GnomeVFSFileOffset range;
1113
1391
gchar *content_range;
1115
s = range_header + 6; // bytes=
1393
s = range_header + 6; /* bytes= */
1116
1394
range = atoll (s);
1118
1396
result = gnome_vfs_seek (handle, GNOME_VFS_SEEK_START, range);
1120
1398
if (result != GNOME_VFS_OK) {
1121
g_message ("Error seeking: %s", gnome_vfs_result_to_string (result));
1399
g_warning ("Error seeking: %s", gnome_vfs_result_to_string (result));
1122
1400
soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
1159
1439
{"/server-info", 12, server_info_cb},
1160
1440
{"/content-codes", 14, content_codes_cb},
1161
1441
{"/login", 6, login_cb},
1442
{"/logout", 7, logout_cb},
1162
1443
{"/update", 7, update_cb},
1163
1444
{"/databases", 10, databases_cb}
1174
1455
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
1456
rb_debug ("request for %s", path);
1176
1458
for (i = 0; i < G_N_ELEMENTS (paths_to_functions); i++) {
1177
1459
if (g_ascii_strncasecmp (paths_to_functions[i].path, path, paths_to_functions[i].path_length) == 0) {
1178
paths_to_functions[i].function (share, message);
1460
paths_to_functions[i].function (share, context, message);
1183
g_warning ("unhandled path %s\n", soup_uri_to_string (soup_message_get_uri (message), TRUE));
1465
g_warning ("unhandled path %s\n", path);
1227
1509
GSList *changes,
1228
1510
RBDAAPShare *share)
1230
if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
1512
if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN)) {
1231
1513
db_entry_deleted_cb (db, entry, share);
1233
1515
db_entry_added_cb (db, entry, share);
1238
publish_cb (RBDAAPmDNSPublisher publisher,
1239
RBDAAPmDNSPublisherStatus status,
1520
soup_auth_callback (SoupServerAuthContext *auth_ctx,
1521
SoupServerAuth *auth,
1522
SoupMessage *message,
1243
case RB_DAAP_MDNS_PUBLISHER_STARTED:
1244
rb_debug ("mDNS publish successful");
1245
share->priv->published = TRUE;
1247
case RB_DAAP_MDNS_PUBLISHER_COLLISION: {
1250
rb_debug ("Duplicate share name on mDNS");
1252
new_name = rb_daap_collision_dialog_new_run (share->priv->name);
1525
const char *username;
1529
path = soup_uri_to_string (soup_message_get_uri (message), TRUE);
1530
rb_debug ("Auth request for %s", path);
1534
/* This is to workaround libsoup looking up handlers by directory.
1535
We require auth for "/databases?" but not "/databases/" */
1536
if (g_str_has_prefix (path, "/databases/")) {
1541
rb_debug ("Auth DENIED: information not provided");
1546
username = soup_server_auth_get_user (auth);
1547
rb_debug ("Auth request for user: %s", username);
1549
allowed = soup_server_auth_check_passwd (auth, share->priv->password);
1550
rb_debug ("Auth request: %s", allowed ? "ALLOWED" : "DENIED");
1262
rb_daap_share_start_publish (RBDAAPShare *share)
1559
rb_daap_share_server_start (RBDAAPShare *share)
1264
gint port = STANDARD_DAAP_PORT;
1561
int port = STANDARD_DAAP_PORT;
1562
gboolean password_required;
1563
SoupServerAuthContext auth_ctx = { 0 };
1267
1565
share->priv->server = soup_server_new (SOUP_SERVER_PORT, port, NULL);
1268
1566
if (share->priv->server == NULL) {
1272
1570
if (share->priv->server == NULL) {
1273
1571
g_warning ("Unable to start music sharing server");
1278
share->priv->port = soup_server_get_port (share->priv->server);
1279
rb_debug ("Started DAAP server on port %d", port);
1576
share->priv->port = (guint)soup_server_get_port (share->priv->server);
1577
rb_debug ("Started DAAP server on port %u", share->priv->port);
1579
password_required = (share->priv->auth_method != RB_DAAP_SHARE_AUTH_METHOD_NONE);
1581
if (password_required) {
1582
auth_ctx.types = SOUP_AUTH_TYPE_BASIC;
1583
auth_ctx.callback = (SoupServerAuthCallbackFn)soup_auth_callback;
1584
auth_ctx.user_data = share;
1585
auth_ctx.basic_info.realm = "Music Sharing";
1587
soup_server_add_handler (share->priv->server,
1590
(SoupServerCallbackFn)server_cb,
1593
soup_server_add_handler (share->priv->server,
1596
(SoupServerCallbackFn)server_cb,
1599
soup_server_add_handler (share->priv->server,
1602
(SoupServerCallbackFn)server_cb,
1281
1607
soup_server_add_handler (share->priv->server,
1287
1613
soup_server_run_async (share->priv->server);
1289
ret = rb_daap_mdns_publish (&(share->priv->publisher),
1292
(RBDAAPmDNSPublisherCallback) publish_cb,
1296
g_warning ("Unable to notify network of music sharing");
1300
rb_debug ("Published DAAP server information to mdns");
1302
1616
share->priv->id_to_entry = g_hash_table_new (NULL, NULL);
1303
1617
share->priv->entry_to_id = g_hash_table_new (NULL, NULL);
1618
/* using direct since there is no g_uint_hash or g_uint_equal */
1619
share->priv->session_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
1304
1621
share->priv->next_playlist_id = 2; /* 1 already used */
1306
1623
rhythmdb_entry_foreach (share->priv->db, (GFunc)add_db_entry, share);
1317
1634
"entry-changed",
1318
1635
G_CALLBACK (db_entry_changed_cb),
1638
share->priv->server_active = TRUE;
1323
rb_daap_share_stop_publish (RBDAAPShare *share)
1644
rb_daap_share_server_stop (RBDAAPShare *share)
1646
rb_debug ("Stopping music sharing server on port %d", share->priv->port);
1325
1648
if (share->priv->server) {
1328
* GLib-CRITICAL **: g_main_loop_quit: assertion `loop != NULL' failed
1329
* But it doesn't seem to matter.
1331
// soup_server_quit (share->priv->server);
1332
g_object_unref (G_OBJECT (share->priv->server));
1649
soup_server_quit (share->priv->server);
1650
g_object_unref (share->priv->server);
1333
1651
share->priv->server = NULL;
1358
1681
share->priv->entry_changed_id = 0;
1684
share->priv->server_active = FALSE;
1690
rb_daap_share_publish_start (RBDAAPShare *share)
1694
gboolean password_required;
1696
password_required = (share->priv->auth_method != RB_DAAP_SHARE_AUTH_METHOD_NONE);
1699
res = rb_daap_mdns_publisher_publish (share->priv->publisher,
1706
if (error != NULL) {
1707
g_warning ("Unable to notify network of music sharing: %s", error->message);
1708
g_error_free (error);
1710
g_warning ("Unable to notify network of music sharing");
1714
rb_debug ("Published DAAP server information to mdns");
1721
rb_daap_share_publish_stop (RBDAAPShare *share)
1361
1723
if (share->priv->publisher) {
1362
rb_daap_mdns_publish_cancel (share->priv->publisher);
1363
share->priv->publisher = 0;
1727
res = rb_daap_mdns_publisher_withdraw (share->priv->publisher, &error);
1728
if (error != NULL) {
1729
g_warning ("Unable to withdraw music sharing service: %s", error->message);
1730
g_error_free (error);
1366
1735
share->priv->published = FALSE;
1740
rb_daap_share_restart (RBDAAPShare *share)
1744
rb_daap_share_server_stop (share);
1745
res = rb_daap_share_server_start (share);
1747
/* To update information just publish again */
1748
rb_daap_share_publish_start (share);
1750
rb_daap_share_publish_stop (share);
1755
rb_daap_share_maybe_restart (RBDAAPShare *share)
1757
if (share->priv->published) {
1758
rb_daap_share_restart (share);