void dpap_share_server_info (DMAPShare * share, SoupServer * server, SoupMessage * message, const char *path, GHashTable * query, SoupClientContext * context) { /* MSRV server info response * MSTT status * MPRO dpap version * PPRO dpap version * MINM name * MSAU authentication method * MSLR login required * MSTM timeout interval * MSAL supports auto logout * MSUP supports update * MSPI supports persistent ids * MSEX supports extensions * MSBR supports browse * MSQY supports query * MSIX supports index * MSRS supports resolve * MSDC databases count */ gchar *nameprop; GNode *msrv; g_debug ("Path is %s.", path); g_object_get ((gpointer) share, "name", &nameprop, NULL); msrv = dmap_structure_add (NULL, DMAP_CC_MSRV); dmap_structure_add (msrv, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK); dmap_structure_add (msrv, DMAP_CC_MPRO, (gdouble) DMAP_VERSION); dmap_structure_add (msrv, DMAP_CC_PPRO, (gdouble) DPAP_VERSION); dmap_structure_add (msrv, DMAP_CC_MINM, nameprop); /*dmap_structure_add (msrv, DMAP_CC_MSAU, _dmap_share_get_auth_method (share)); */ /* authentication method * 0 is nothing * 1 is name & password * 2 is password only */ dmap_structure_add (msrv, DMAP_CC_MSLR, 0); dmap_structure_add (msrv, DMAP_CC_MSTM, (gint32) DPAP_TIMEOUT); dmap_structure_add (msrv, DMAP_CC_MSAL, (gchar) 0); /*dmap_structure_add (msrv, DMAP_CC_MSUP, (gchar) 1); *dmap_structure_add (msrv, DMAP_CC_MSPI, (gchar) 0); *dmap_structure_add (msrv, DMAP_CC_MSEX, (gchar) 0); *dmap_structure_add (msrv, DMAP_CC_MSBR, (gchar) 0); *dmap_structure_add (msrv, DMAP_CC_MSQY, (gchar) 0); */ dmap_structure_add (msrv, DMAP_CC_MSIX, (gchar) 0); /* dmap_structure_add (msrv, DMAP_CC_MSRS, (gchar) 0); */ dmap_structure_add (msrv, DMAP_CC_MSDC, (gint32) 1); _dmap_share_message_set_from_dmap_structure (share, message, msrv); dmap_structure_destroy (msrv); g_free (nameprop); }
void dacp_share_ctrl_int (DMAPShare * share, SoupServer * server, SoupMessage * message, const char *path, GHashTable * query, SoupClientContext * context) { const char *rest_of_path; DACPShare *dacp_share = DACP_SHARE (share); g_debug ("Path is %s.", path); if (query) { g_hash_table_foreach (query, debug_param, NULL); } rest_of_path = strchr (path + 1, '/'); /* If calling /ctrl-int without args, the client doesnt need a * session-id, otherwise it does and it should be validated. */ if ((rest_of_path != NULL) && (!_dmap_share_session_id_validate (share, context, message, query, NULL))) { soup_message_set_status (message, SOUP_STATUS_FORBIDDEN); return; } if (rest_of_path == NULL) { /* CACI control-int * MSTT status * MUTY update type * MTCO specified total count * MRCO returned count * MLCL listing * MLIT listing item * MIID item id * CMIK Unknown (TRUE) * CMSP Unknown (TRUE) * CMSV Unknown (TRUE) * CASS Unknown (TRUE) * CASU Unknown (TRUE) * CASG Unknown (TRUE) */ GNode *caci; GNode *mlcl; GNode *mlit; // dacp.controlint caci = dmap_structure_add (NULL, DMAP_CC_CACI); // dmap.status dmap_structure_add (caci, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK); // dmap.updatetype dmap_structure_add (caci, DMAP_CC_MUTY, 0); // dmap.specifiedtotalcount dmap_structure_add (caci, DMAP_CC_MTCO, (gint32) 1); // dmap.returnedcount dmap_structure_add (caci, DMAP_CC_MRCO, (gint32) 1); // dmap.listing mlcl = dmap_structure_add (caci, DMAP_CC_MLCL); // dmap.listingitem mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT); // dmap.itemid dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1); // Unknown (TRUE) dmap_structure_add (mlit, DMAP_CC_CMIK, (gint32) 1); // Unknown (TRUE) dmap_structure_add (mlit, DMAP_CC_CMSP, (gint32) 1); // Unknown (TRUE) dmap_structure_add (mlit, DMAP_CC_CMSV, (gint32) 1); // Unknown (TRUE) dmap_structure_add (mlit, DMAP_CC_CASS, (gint32) 1); // Unknown (TRUE) dmap_structure_add (mlit, DMAP_CC_CASU, (gint32) 1); // Unknown (TRUE) dmap_structure_add (mlit, DMAP_CC_CASG, (gint32) 1); _dmap_share_message_set_from_dmap_structure (share, message, caci); dmap_structure_destroy (caci); } else if (g_ascii_strcasecmp ("/1/getproperty", rest_of_path) == 0) { gchar *properties_query, **properties, **property; GNode *cmgt; properties_query = g_hash_table_lookup (query, "properties"); if (!properties_query) { g_warning ("No property specified"); return; } cmgt = dmap_structure_add (NULL, DMAP_CC_CMGT); dmap_structure_add (cmgt, DMAP_CC_MSTT, DMAP_STATUS_OK); properties = g_strsplit (properties_query, ",", -1); for (property = properties; *property; property++) { if (g_ascii_strcasecmp (*property, "dmcp.volume") == 0) { gulong volume; g_object_get (dacp_share->priv->player, "volume", &volume, NULL); //g_debug ("Sending volume: %lu", volume); dmap_structure_add (cmgt, DMAP_CC_CMVO, volume); } else { g_warning ("Unhandled property %s", *property); } } g_strfreev (properties); _dmap_share_message_set_from_dmap_structure (share, message, cmgt); dmap_structure_destroy (cmgt); } else if (g_ascii_strcasecmp ("/1/setproperty", rest_of_path) == 0) { if (g_hash_table_lookup (query, "dmcp.volume")) { gdouble volume = strtod (g_hash_table_lookup (query, "dmcp.volume"), NULL); g_object_set (dacp_share->priv->player, "volume", (gulong) volume, NULL); } soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); } else if (g_ascii_strcasecmp ("/1/getspeakers", rest_of_path) == 0) { GNode *casp; casp = dmap_structure_add (NULL, DMAP_CC_CASP); dmap_structure_add (casp, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK); dmap_structure_add (casp, DMAP_CC_MDCL); dmap_structure_add (casp, DMAP_CC_CAIA, TRUE); dmap_structure_add (casp, DMAP_CC_MINM, "Computer"); dmap_structure_add (casp, DMAP_CC_MSMA, (gint32) 0); _dmap_share_message_set_from_dmap_structure (share, message, casp); dmap_structure_destroy (casp); } else if (g_ascii_strcasecmp ("/1/playstatusupdate", rest_of_path) == 0) { gchar *revision = g_hash_table_lookup (query, "revision-number"); gint revision_number = atoi (revision); if (revision_number >= dacp_share->priv->current_revision) { g_object_ref (message); dacp_share->priv->update_queue = g_slist_prepend (dacp_share-> priv->update_queue, message); g_signal_connect_object (message, "finished", G_CALLBACK (status_update_message_finished), dacp_share, 0); soup_server_pause_message (server, message); } else { dacp_share_fill_playstatusupdate (dacp_share, message); } } else if (g_ascii_strcasecmp ("/1/playpause", rest_of_path) == 0) { dacp_player_play_pause (dacp_share->priv->player); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); } else if (g_ascii_strcasecmp ("/1/pause", rest_of_path) == 0) { dacp_player_pause (dacp_share->priv->player); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); } else if (g_ascii_strcasecmp ("/1/nextitem", rest_of_path) == 0) { dacp_player_next_item (dacp_share->priv->player); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); } else if (g_ascii_strcasecmp ("/1/previtem", rest_of_path) == 0) { dacp_player_prev_item (dacp_share->priv->player); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); } else if (g_ascii_strcasecmp ("/1/nowplayingartwork", rest_of_path) == 0) { guint width = 320; guint height = 320; gchar *artwork_filename; gchar *buffer; gsize buffer_len; if (g_hash_table_lookup (query, "mw")) width = atoi (g_hash_table_lookup (query, "mw")); if (g_hash_table_lookup (query, "mh")) height = atoi (g_hash_table_lookup (query, "mh")); artwork_filename = dacp_player_now_playing_artwork (dacp_share-> priv->player, width, height); if (!artwork_filename) { g_debug ("No artwork for currently playing song"); soup_message_set_status (message, SOUP_STATUS_NOT_FOUND); return; } #ifdef HAVE_GDKPIXBUF GdkPixbuf *artwork = gdk_pixbuf_new_from_file_at_scale (artwork_filename, width, height, TRUE, NULL); if (!artwork) { g_debug ("Error loading image file"); g_free (artwork_filename); soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR); return; } if (!gdk_pixbuf_save_to_buffer (artwork, &buffer, &buffer_len, "png", NULL, NULL)) { g_debug ("Error saving artwork to PNG"); g_object_unref (artwork); g_free (artwork_filename); soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR); return; } g_object_unref (artwork); #else if (!g_file_get_contents (artwork_filename, &buffer, &buffer_len, NULL)) { g_debug ("Error getting artwork data"); g_free (artwork_filename); soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR); return; } #endif g_free (artwork_filename); soup_message_set_status (message, SOUP_STATUS_OK); soup_message_set_response (message, "image/png", SOUP_MEMORY_TAKE, buffer, buffer_len); } else if (g_ascii_strcasecmp ("/1/cue", rest_of_path) == 0) { gchar *command; command = g_hash_table_lookup (query, "command"); if (!command) { g_debug ("No CUE command specified"); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); return; } else if (g_ascii_strcasecmp ("clear", command) == 0) { dacp_player_cue_clear (dacp_share->priv->player); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); } else if (g_ascii_strcasecmp ("play", command) == 0) { GNode *cacr; gchar *record_query; gchar *sort_by; GHashTable *records; GList *sorted_records; GSList *filter_def; DMAPDb *db; gint index = atoi (g_hash_table_lookup (query, "index")); g_object_get (share, "db", &db, NULL); record_query = g_hash_table_lookup (query, "query"); filter_def = _dmap_share_build_filter (record_query); records = dmap_db_apply_filter (db, filter_def); sorted_records = g_hash_table_get_values (records); sort_by = g_hash_table_lookup (query, "sort"); if (g_strcmp0 (sort_by, "album") == 0) { sorted_records = g_list_sort_with_data (sorted_records, (GCompareDataFunc) daap_record_cmp_by_album, db); } else if (sort_by != NULL) { g_warning ("Unknown sort column: %s", sort_by); } dacp_player_cue_play (dacp_share->priv->player, sorted_records, index); g_list_free (sorted_records); g_hash_table_unref (records); dmap_share_free_filter (filter_def); cacr = dmap_structure_add (NULL, DMAP_CC_CACR); dmap_structure_add (cacr, DMAP_CC_MSTT, DMAP_STATUS_OK); dmap_structure_add (cacr, DMAP_CC_MIID, index); _dmap_share_message_set_from_dmap_structure (share, message, cacr); dmap_structure_destroy (cacr); } else { g_warning ("Unhandled cue command: %s", command); soup_message_set_status (message, SOUP_STATUS_NO_CONTENT); return; } } else { g_warning ("Unhandled ctrl-int command: %s", rest_of_path); soup_message_set_status (message, SOUP_STATUS_BAD_REQUEST); } }
static void dacp_share_fill_playstatusupdate (DACPShare * share, SoupMessage * message) { GNode *cmst; DAAPRecord *record; DACPPlayState play_state; DACPRepeatState repeat_state; gboolean shuffle_state; guint playing_time; g_object_get (share->priv->player, "play-state", &play_state, "repeat-state", &repeat_state, "shuffle-state", &shuffle_state, "playing-time", &playing_time, NULL); record = dacp_player_now_playing_record (share->priv->player); cmst = dmap_structure_add (NULL, DMAP_CC_CMST); dmap_structure_add (cmst, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK); dmap_structure_add (cmst, DMAP_CC_CMSR, share->priv->current_revision); dmap_structure_add (cmst, DMAP_CC_CAPS, (gint32) play_state); dmap_structure_add (cmst, DMAP_CC_CASH, shuffle_state ? 1 : 0); dmap_structure_add (cmst, DMAP_CC_CARP, (gint32) repeat_state); if (record) { gchar *title; gchar *artist; gchar *album; gint duration; guint track_time; g_object_get (record, "title", &title, "songartist", &artist, "songalbum", &album, "duration", &duration, NULL); track_time = duration * 1000; //dmap_structure_add (cmst, DMAP_CC_CAVC, 1); dmap_structure_add (cmst, DMAP_CC_CAAS, 2); dmap_structure_add (cmst, DMAP_CC_CAAR, 6); dmap_structure_add (cmst, DMAP_CC_CANP, (gint64) 0); if (title) dmap_structure_add (cmst, DMAP_CC_CANN, title); if (artist) dmap_structure_add (cmst, DMAP_CC_CANA, artist); if (album) dmap_structure_add (cmst, DMAP_CC_CANL, album); dmap_structure_add (cmst, DMAP_CC_CANG, ""); dmap_structure_add (cmst, DMAP_CC_ASAI, 0); //dmap_structure_add (cmst, DMAP_CC_AEMK, 1); g_debug ("Playing time: %u, Track time: %u", playing_time, track_time); dmap_structure_add (cmst, DMAP_CC_CANT, track_time - playing_time); dmap_structure_add (cmst, DMAP_CC_CAST, track_time); g_free (title); g_free (artist); g_free (album); g_object_unref (record); } _dmap_share_message_set_from_dmap_structure (DMAP_SHARE (share), message, cmst); dmap_structure_destroy (cmst); }
static void actual_http_response_handler (DAAPResponseData * data) { DMAPConnectionPrivate *priv; GNode *structure; char *new_response = NULL; const char *response; const char *encoding_header; char *message_path; int response_length; gboolean compatible_server = TRUE; priv = data->connection->priv; structure = NULL; encoding_header = NULL; response = data->message->response_body->data; response_length = data->message->response_body->length; message_path = soup_uri_to_string (soup_message_get_uri (data->message), FALSE); g_debug ("Received response from %s: %d, %s\n", message_path, data->message->status_code, data->message->reason_phrase); if (data->message->response_headers) { const char *server; encoding_header = soup_message_headers_get (data-> message->response_headers, "Content-Encoding"); server = soup_message_headers_get (data-> message->response_headers, "DAAP-Server"); if (server != NULL && strstr (server, ITUNES_7_SERVER) != NULL) { g_debug ("giving up. we can't talk to %s", server); compatible_server = FALSE; } } if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && encoding_header && strcmp (encoding_header, "gzip") == 0) { #ifdef HAVE_LIBZ z_stream stream; unsigned int factor = 4; unsigned int unc_size = response_length * factor; stream.next_in = (unsigned char *) response; stream.avail_in = response_length; stream.total_in = 0; new_response = g_malloc (unc_size + 1); stream.next_out = (unsigned char *) new_response; stream.avail_out = unc_size; stream.total_out = 0; stream.zalloc = g_zalloc_wrapper; stream.zfree = g_zfree_wrapper; stream.opaque = NULL; if (inflateInit2 (&stream, 32 /* auto-detect */ + 15 /* max */ ) != Z_OK) { inflateEnd (&stream); g_free (new_response); g_debug ("Unable to decompress response from %s", message_path); data->status = SOUP_STATUS_MALFORMED; } else { do { int z_res; z_res = inflate (&stream, Z_FINISH); if (z_res == Z_STREAM_END) { break; } if ((z_res != Z_OK && z_res != Z_BUF_ERROR) || stream.avail_out != 0 || unc_size > 40 * 1000 * 1000) { inflateEnd (&stream); g_free (new_response); new_response = NULL; break; } factor *= 4; unc_size = (response_length * factor); /* unc_size can't grow bigger than 40MB, so * unc_size can't overflow, and this realloc * call is safe */ new_response = g_realloc (new_response, unc_size + 1); stream.next_out = (unsigned char *) (new_response + stream.total_out); stream.avail_out = unc_size - stream.total_out; } while (1); } if (new_response) { response = new_response; response_length = stream.total_out; } #else g_debug ("Received compressed response from %s but can't handle it", message_path); data->status = SOUP_STATUS_MALFORMED; #endif } if (compatible_server == FALSE) { /* leaving structure == NULL here causes the connection process * to fail at the first step. */ connection_set_error_message (data->connection, ("libdmapsharing is not able to connect to iTunes 7 shares")); } else if (SOUP_STATUS_IS_SUCCESSFUL (data->status)) { DMAPStructureItem *item; if ( /* FIXME: !rb_is_main_thread () */ TRUE) { priv->progress = -1.0f; if (priv->emit_progress_id != 0) { g_source_remove (priv->emit_progress_id); } priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, data->connection); } structure = dmap_structure_parse (response, response_length); if (structure == NULL) { g_debug ("No daap structure returned from %s", message_path); data->status = SOUP_STATUS_MALFORMED; } else { int dmap_status = 0; item = dmap_structure_find_item (structure, DMAP_CC_MSTT); if (item) { dmap_status = g_value_get_int (&(item->content)); if (dmap_status != 200) { g_debug ("Error, dmap.status is not 200 in response from %s", message_path); data->status = SOUP_STATUS_MALFORMED; } } } if ( /* FIXME: ! rb_is_main_thread () */ TRUE) { priv->progress = 1.0f; if (priv->emit_progress_id != 0) { g_source_remove (priv->emit_progress_id); } priv->emit_progress_id = g_idle_add ((GSourceFunc) emit_progress_idle, data->connection); } } else { g_debug ("Error getting %s: %d, %s\n", message_path, data->message->status_code, data->message->reason_phrase); connection_set_error_message (data->connection, data->message->reason_phrase); } if (data->response_handler) { (*(data->response_handler)) (data->connection, data->status, structure, data->user_data); } if (structure) { dmap_structure_destroy (structure); } g_free (new_response); g_free (message_path); g_object_unref (G_OBJECT (data->connection)); g_object_unref (G_OBJECT (data->message)); g_free (data); }
static void databases_browse_xxx (DMAPShare * share, SoupServer * server, SoupMessage * msg, const char *path, GHashTable * query, SoupClientContext * context) { /* ABRO database browse * MSTT status * MUTY update type * MTCO specified total count * MRCO returned count * ABGN genre listing * MLIT listing item * ... */ DMAPDb *db; const gchar *rest_of_path; GNode *abro, *node; gchar *filter; GSList *filter_def; GHashTable *filtered; guint num_genre; const gchar *browse_category; GHashTable *category_items; DMAPContentCode category_cc; GList *values; rest_of_path = strchr (path + 1, '/'); browse_category = rest_of_path + 10; category_items = g_hash_table_new (g_str_hash, g_str_equal); filter = g_hash_table_lookup (query, "filter"); filter_def = _dmap_share_build_filter (filter); g_object_get (share, "db", &db, NULL); filtered = dmap_db_apply_filter (db, filter_def); if (g_ascii_strcasecmp (browse_category, "genres") == 0) { g_hash_table_foreach (filtered, (GHFunc) genre_tabulator, category_items); category_cc = DMAP_CC_ABGN; } else if (g_ascii_strcasecmp (browse_category, "artists") == 0) { g_hash_table_foreach (filtered, (GHFunc) artist_tabulator, category_items); category_cc = DMAP_CC_ABAR; } else if (g_ascii_strcasecmp (browse_category, "albums") == 0) { g_hash_table_foreach (filtered, (GHFunc) album_tabulator, category_items); category_cc = DMAP_CC_ABAL; } else { g_warning ("Unsupported browse category: %s", browse_category); goto _bad_category; } abro = dmap_structure_add (NULL, DMAP_CC_ABRO); dmap_structure_add (abro, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK); dmap_structure_add (abro, DMAP_CC_MUTY, 0); num_genre = g_hash_table_size (category_items); dmap_structure_add (abro, DMAP_CC_MTCO, (gint32) num_genre); dmap_structure_add (abro, DMAP_CC_MRCO, (gint32) num_genre); node = dmap_structure_add (abro, category_cc); values = g_hash_table_get_keys (category_items); if (values && g_hash_table_lookup (query, "include-sort-headers")) { g_debug ("Sorting..."); values = g_list_sort (values, (GCompareFunc) g_ascii_strcasecmp); } g_list_foreach (values, add_to_category_listing, node); g_list_free (values); _dmap_share_message_set_from_dmap_structure (share, msg, abro); dmap_structure_destroy (abro); _bad_category: dmap_share_free_filter (filter_def); /* Free's hash table but not data (points into real DB): */ g_hash_table_destroy (filtered); g_hash_table_destroy (category_items); }