1 /* Implmentation of DPAP (e.g., iPhoto Picture) sharing
2 *
3 * Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
4 *
5 * Modifications Copyright (C) 2008 W. Michael Petullo <mike@flyn.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 */
22
23 #include "config.h"
24
25 #include <time.h>
26 #include <string.h>
27 #include <stdlib.h>
28
29 #include <glib/gi18n.h>
30 #include <glib.h>
31
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36
37 #include <libsoup/soup.h>
38 #include <libsoup/soup-address.h>
39 #include <libsoup/soup-message.h>
40 #include <libsoup/soup-uri.h>
41 #include <libsoup/soup-server.h>
42
43 #include <libdmapsharing/dmap.h>
44 #include <libdmapsharing/dmap-private-utils.h>
45 #include <libdmapsharing/dmap-structure.h>
46
47 static void dpap_share_set_property (GObject * object,
48 guint prop_id,
49 const GValue * value,
50 GParamSpec * pspec);
51 static void dpap_share_get_property (GObject * object,
52 guint prop_id,
53 GValue * value, GParamSpec * pspec);
54 static void dpap_share_dispose (GObject * object);
55 guint dpap_share_get_desired_port (DMAPShare * share);
56 const char *dpap_share_get_type_of_service (DMAPShare * share);
57 void dpap_share_server_info (DMAPShare * share,
58 SoupServer * server,
59 SoupMessage * message,
60 const char *path,
61 GHashTable * query, SoupClientContext * context);
62 void dpap_share_message_add_standard_headers (DMAPShare * share,
63 SoupMessage * message);
64 static void databases_browse_xxx (DMAPShare * share,
65 SoupServer * server,
66 SoupMessage * msg,
67 const char *path,
68 GHashTable * query,
69 SoupClientContext * context);
70 static void databases_items_xxx (DMAPShare * share,
71 SoupServer * server,
72 SoupMessage * msg,
73 const char *path,
74 GHashTable * query,
75 SoupClientContext * context);
76 static struct DMAPMetaDataMap *get_meta_data_map (DMAPShare * share);
77 static void add_entry_to_mlcl (gpointer id, DMAPRecord * record, gpointer mb);
78
79 #define DPAP_TYPE_OF_SERVICE "_dpap._tcp"
80 #define DPAP_PORT 8770
81
82 struct DPAPSharePrivate
83 {
84 gchar unused;
85 };
86
87 /* Mmap'ed full image file. Global so that it may be free'ed in a different
88 * function call that the one that set it up.
89 */
90 static GMappedFile *mapped_file = NULL;
91
92 enum
93 {
94 PROP_0,
95 };
96
97 G_DEFINE_TYPE_WITH_PRIVATE (DPAPShare, dpap_share, DMAP_TYPE_SHARE);
98
99 static void
dpap_share_class_init(DPAPShareClass * klass)100 dpap_share_class_init (DPAPShareClass * klass)
101 {
102 GObjectClass *object_class = G_OBJECT_CLASS (klass);
103 DMAPShareClass *parent_class = DMAP_SHARE_CLASS (object_class);
104
105 object_class->get_property = dpap_share_get_property;
106 object_class->set_property = dpap_share_set_property;
107 object_class->dispose = dpap_share_dispose;
108
109 parent_class->get_desired_port = dpap_share_get_desired_port;
110 parent_class->get_type_of_service = dpap_share_get_type_of_service;
111 parent_class->message_add_standard_headers =
112 dpap_share_message_add_standard_headers;
113 parent_class->get_meta_data_map = get_meta_data_map;
114 parent_class->add_entry_to_mlcl = add_entry_to_mlcl;
115 parent_class->databases_browse_xxx = databases_browse_xxx;
116 parent_class->databases_items_xxx = databases_items_xxx;
117 parent_class->server_info = dpap_share_server_info;
118 }
119
120 static void
dpap_share_init(DPAPShare * share)121 dpap_share_init (DPAPShare * share)
122 {
123 /* FIXME: do I need to manually call parent _init? */
124 share->priv = dpap_share_get_instance_private(share);
125 }
126
127 static void
dpap_share_set_property(GObject * object,guint prop_id,G_GNUC_UNUSED const GValue * value,GParamSpec * pspec)128 dpap_share_set_property (GObject * object,
129 guint prop_id,
130 G_GNUC_UNUSED const GValue * value,
131 GParamSpec * pspec)
132 {
133 // DPAPShare *share = DPAP_SHARE (object);
134
135 switch (prop_id) {
136 /* FIXME: */
137 default:
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
139 break;
140 }
141 }
142
143 static void
dpap_share_get_property(GObject * object,guint prop_id,G_GNUC_UNUSED GValue * value,GParamSpec * pspec)144 dpap_share_get_property (GObject * object, guint prop_id,
145 G_GNUC_UNUSED GValue * value, GParamSpec * pspec)
146 {
147 // DPAPShare *share = DPAP_SHARE (object);
148
149 switch (prop_id) {
150 default:
151 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
152 break;
153 }
154 }
155
156 static void
dpap_share_dispose(G_GNUC_UNUSED GObject * object)157 dpap_share_dispose (G_GNUC_UNUSED GObject * object)
158 {
159 /* FIXME: implement in parent */
160 }
161
162 /* FIXME: trancode_mimetype currently not used for DPAP, only DAAP.
163 * Threrfore, it is not passed to g_object_new.
164 */
165 DPAPShare *
dpap_share_new(const char * name,const char * password,gpointer db,gpointer container_db,G_GNUC_UNUSED gchar * transcode_mimetype)166 dpap_share_new (const char *name,
167 const char *password,
168 gpointer db,
169 gpointer container_db,
170 G_GNUC_UNUSED gchar * transcode_mimetype)
171 {
172 DPAPShare *share;
173
174 share = DPAP_SHARE (g_object_new (DPAP_TYPE_SHARE,
175 "name", name,
176 "password", password,
177 "db", db,
178 "container-db", container_db,
179 NULL));
180
181 _dmap_share_server_start (DMAP_SHARE (share));
182 _dmap_share_publish_start (DMAP_SHARE (share));
183
184 return share;
185 }
186
187 void
dpap_share_message_add_standard_headers(G_GNUC_UNUSED DMAPShare * share,SoupMessage * message)188 dpap_share_message_add_standard_headers (G_GNUC_UNUSED DMAPShare * share,
189 SoupMessage * message)
190 {
191 soup_message_headers_append (message->response_headers, "DPAP-Server",
192 "libdmapsharing" VERSION);
193 }
194
195 #define DMAP_VERSION 2.0
196 #define DPAP_VERSION 1.1
197 #define DPAP_TIMEOUT 1800
198
199 guint
dpap_share_get_desired_port(G_GNUC_UNUSED DMAPShare * share)200 dpap_share_get_desired_port (G_GNUC_UNUSED DMAPShare * share)
201 {
202 return DPAP_PORT;
203 }
204
205 const char *
dpap_share_get_type_of_service(G_GNUC_UNUSED DMAPShare * share)206 dpap_share_get_type_of_service (G_GNUC_UNUSED DMAPShare * share)
207 {
208 return DPAP_TYPE_OF_SERVICE;
209 }
210
211 void
dpap_share_server_info(DMAPShare * share,G_GNUC_UNUSED SoupServer * server,SoupMessage * message,const char * path,G_GNUC_UNUSED GHashTable * query,G_GNUC_UNUSED SoupClientContext * context)212 dpap_share_server_info (DMAPShare * share,
213 G_GNUC_UNUSED SoupServer * server,
214 SoupMessage * message,
215 const char *path,
216 G_GNUC_UNUSED GHashTable * query,
217 G_GNUC_UNUSED SoupClientContext * context)
218 {
219 /* MSRV server info response
220 * MSTT status
221 * MPRO dpap version
222 * PPRO dpap version
223 * MINM name
224 * MSAU authentication method
225 * MSLR login required
226 * MSTM timeout interval
227 * MSAL supports auto logout
228 * MSUP supports update
229 * MSPI supports persistent ids
230 * MSEX supports extensions
231 * MSBR supports browse
232 * MSQY supports query
233 * MSIX supports index
234 * MSRS supports resolve
235 * MSDC databases count
236 */
237 gchar *nameprop;
238 GNode *msrv;
239
240 g_debug ("Path is %s.", path);
241
242 g_object_get ((gpointer) share, "name", &nameprop, NULL);
243
244 msrv = dmap_structure_add (NULL, DMAP_CC_MSRV);
245 dmap_structure_add (msrv, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
246 dmap_structure_add (msrv, DMAP_CC_MPRO, (gdouble) DMAP_VERSION);
247 dmap_structure_add (msrv, DMAP_CC_PPRO, (gdouble) DPAP_VERSION);
248 dmap_structure_add (msrv, DMAP_CC_MINM, nameprop);
249 /*dmap_structure_add (msrv, DMAP_CC_MSAU, _dmap_share_get_auth_method (share)); */
250 /* authentication method
251 * 0 is nothing
252 * 1 is name & password
253 * 2 is password only
254 */
255 dmap_structure_add (msrv, DMAP_CC_MSLR, 0);
256 dmap_structure_add (msrv, DMAP_CC_MSTM, (gint32) DPAP_TIMEOUT);
257 dmap_structure_add (msrv, DMAP_CC_MSAL, (gchar) 0);
258 /*dmap_structure_add (msrv, DMAP_CC_MSUP, (gchar) 1);
259 *dmap_structure_add (msrv, DMAP_CC_MSPI, (gchar) 0);
260 *dmap_structure_add (msrv, DMAP_CC_MSEX, (gchar) 0);
261 *dmap_structure_add (msrv, DMAP_CC_MSBR, (gchar) 0);
262 *dmap_structure_add (msrv, DMAP_CC_MSQY, (gchar) 0); */
263 dmap_structure_add (msrv, DMAP_CC_MSIX, (gchar) 0);
264 /* dmap_structure_add (msrv, DMAP_CC_MSRS, (gchar) 0); */
265 dmap_structure_add (msrv, DMAP_CC_MSDC, (gint32) 1);
266
267 _dmap_share_message_set_from_dmap_structure (share, message, msrv);
268 dmap_structure_destroy (msrv);
269
270 g_free (nameprop);
271 }
272
273 typedef enum
274 {
275 ITEM_ID = 0,
276 ITEM_NAME,
277 ITEM_KIND,
278 PERSISTENT_ID,
279 CONTAINER_ITEM_ID,
280 PHOTO_ASPECTRATIO,
281 PHOTO_CREATIONDATE,
282 PHOTO_IMAGEFILENAME,
283 PHOTO_IMAGEFORMAT,
284 PHOTO_IMAGEFILESIZE,
285 PHOTO_IMAGELARGEFILESIZE,
286 PHOTO_IMAGEPIXELHEIGHT,
287 PHOTO_IMAGEPIXELWIDTH,
288 PHOTO_IMAGERATING,
289 PHOTO_HIRES,
290 PHOTO_THUMB,
291 PHOTO_FILEDATA,
292 PHOTO_IMAGECOMMENTS
293 } DPAPMetaData;
294
295 static struct DMAPMetaDataMap meta_data_map[] = {
296 {"dmap.itemid", ITEM_ID},
297 {"dmap.itemname", ITEM_NAME},
298 {"dmap.itemkind", ITEM_KIND},
299 {"dmap.persistentid", PERSISTENT_ID},
300 {"dmap.containeritemid", CONTAINER_ITEM_ID},
301 {"dpap.aspectratio", PHOTO_ASPECTRATIO},
302 {"dpap.creationdate", PHOTO_CREATIONDATE},
303 {"dpap.imagefilename", PHOTO_IMAGEFILENAME},
304 {"dpap.imageformat", PHOTO_IMAGEFORMAT},
305 {"dpap.imagefilesize", PHOTO_IMAGEFILESIZE},
306 {"dpap.imagelargefilesize", PHOTO_IMAGELARGEFILESIZE},
307 {"dpap.imagepixelheight", PHOTO_IMAGEPIXELHEIGHT},
308 {"dpap.imagepixelwidth", PHOTO_IMAGEPIXELWIDTH},
309 {"dpap.imagerating", PHOTO_IMAGERATING},
310 {"dpap.thumb", PHOTO_THUMB},
311 {"dpap.hires", PHOTO_HIRES},
312 {"dpap.filedata", PHOTO_FILEDATA},
313 {"dpap.imagecomments", PHOTO_IMAGECOMMENTS},
314 {NULL, 0}
315 };
316
317 #define DPAP_ITEM_KIND_PHOTO 3 /* This is the constant that dpap-sharp uses. */
318
319 static GMappedFile *
file_to_mmap(const char * location)320 file_to_mmap (const char *location)
321 {
322 GFile *file;
323 GMappedFile *mapped_file = NULL;
324 char *path;
325 GError *error = NULL;
326
327 file = g_file_new_for_uri (location);
328 /* NOTE: this is broken if original filename contains "%20" etc. This
329 * is because g_file_get_path() will translate this to " ", etc. But
330 * the filename really may have used "%20" (not " ").
331 */
332 path = g_file_get_path (file);
333 if (path == NULL) {
334 g_warning ("Couldn't mmap %s: couldn't get path", path);
335 g_object_unref (file);
336 return mapped_file;
337 }
338 g_object_unref (file);
339
340 mapped_file = g_mapped_file_new (path, FALSE, &error);
341 if (mapped_file == NULL) {
342 g_warning ("Unable to map file %s: %s", path, error->message);
343 }
344
345 g_free (path);
346 return mapped_file;
347 }
348
349 static void
add_entry_to_mlcl(gpointer id,DMAPRecord * record,gpointer _mb)350 add_entry_to_mlcl (gpointer id, DMAPRecord * record, gpointer _mb)
351 {
352 GNode *mlit;
353 struct MLCL_Bits *mb = (struct MLCL_Bits *) _mb;
354
355 mlit = dmap_structure_add (mb->mlcl, DMAP_CC_MLIT);
356
357 if (_dmap_share_client_requested (mb->bits, ITEM_KIND))
358 dmap_structure_add (mlit, DMAP_CC_MIKD,
359 (gchar) DPAP_ITEM_KIND_PHOTO);
360 if (_dmap_share_client_requested (mb->bits, ITEM_ID))
361 dmap_structure_add (mlit, DMAP_CC_MIID,
362 GPOINTER_TO_UINT (id));
363 if (_dmap_share_client_requested (mb->bits, ITEM_NAME)) {
364 gchar *filename = NULL;
365
366 g_object_get (record, "filename", &filename, NULL);
367 if (filename) {
368 dmap_structure_add (mlit, DMAP_CC_MINM, filename);
369 g_free (filename);
370 } else
371 g_debug ("Filename requested but not available");
372 }
373 if (_dmap_share_client_requested (mb->bits, PERSISTENT_ID))
374 dmap_structure_add (mlit, DMAP_CC_MPER,
375 GPOINTER_TO_UINT (id));
376 if (TRUE) {
377 /* dpap-sharp claims iPhoto '08 will not show thumbnails without PASP
378 * and this does seem to be the case when testing. */
379 gchar *aspect_ratio = NULL;
380
381 g_object_get (record, "aspect-ratio", &aspect_ratio, NULL);
382 if (aspect_ratio) {
383 dmap_structure_add (mlit, DMAP_CC_PASP, aspect_ratio);
384 g_free (aspect_ratio);
385 } else
386 g_debug
387 ("Aspect ratio requested but not available");
388 }
389 if (_dmap_share_client_requested (mb->bits, PHOTO_CREATIONDATE)) {
390 gint creation_date = 0;
391
392 g_object_get (record, "creation-date", &creation_date, NULL);
393 dmap_structure_add (mlit, DMAP_CC_PICD, creation_date);
394 }
395 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEFILENAME)) {
396 gchar *filename = NULL;
397
398 g_object_get (record, "filename", &filename, NULL);
399 if (filename) {
400 dmap_structure_add (mlit, DMAP_CC_PIMF, filename);
401 g_free (filename);
402 } else
403 g_debug ("Filename requested but not available");
404 }
405 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEFORMAT)) {
406 gchar *format = NULL;
407
408 g_object_get (record, "format", &format, NULL);
409 if (format) {
410 dmap_structure_add (mlit, DMAP_CC_PFMT, format);
411 g_free (format);
412 } else
413 g_debug ("Format requested but not available");
414 }
415 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEFILESIZE)) {
416 GByteArray *thumbnail = NULL;
417
418 g_object_get (record, "thumbnail", &thumbnail, NULL);
419 dmap_structure_add (mlit, DMAP_CC_PIFS,
420 thumbnail ? thumbnail->len : 0);
421 }
422 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGELARGEFILESIZE)) {
423 gint large_filesize = 0;
424
425 g_object_get (record, "large-filesize", &large_filesize,
426 NULL);
427 dmap_structure_add (mlit, DMAP_CC_PLSZ, large_filesize);
428 }
429 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEPIXELHEIGHT)) {
430 gint pixel_height = 0;
431
432 g_object_get (record, "pixel-height", &pixel_height, NULL);
433 dmap_structure_add (mlit, DMAP_CC_PHGT, pixel_height);
434 }
435 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGEPIXELWIDTH)) {
436 gint pixel_width = 0;
437
438 g_object_get (record, "pixel-width", &pixel_width, NULL);
439 dmap_structure_add (mlit, DMAP_CC_PWTH, pixel_width);
440 }
441 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGERATING)) {
442 gint rating = 0;
443
444 g_object_get (record, "rating", &rating, NULL);
445 dmap_structure_add (mlit, DMAP_CC_PRAT, rating);
446 }
447 if (_dmap_share_client_requested (mb->bits, PHOTO_IMAGECOMMENTS)) {
448 gchar *comments = NULL;
449
450 g_object_get (record, "comments", &comments, NULL);
451 if (comments) {
452 dmap_structure_add (mlit, DMAP_CC_PCMT, comments);
453 g_free (comments);
454 } else
455 g_debug ("Comments requested but not available");
456 }
457 if (_dmap_share_client_requested (mb->bits, PHOTO_FILEDATA)) {
458 size_t size = 0;
459 unsigned char *data = NULL;
460 GByteArray *thumbnail = NULL;
461
462 if (_dmap_share_client_requested (mb->bits, PHOTO_THUMB)) {
463 g_object_get (record, "thumbnail", &thumbnail, NULL);
464 if (thumbnail) {
465 data = thumbnail->data;
466 size = thumbnail->len;
467 } else {
468 data = NULL;
469 size = 0;
470 }
471 } else {
472 /* Should be PHOTO_HIRES */
473 char *location = NULL;
474
475 g_object_get (record, "location", &location, NULL);
476 if (mapped_file) {
477 /* Free any previously mapped image */
478 g_mapped_file_unref (mapped_file);
479 mapped_file = NULL;
480 }
481
482 mapped_file = file_to_mmap (location);
483 if (mapped_file == NULL) {
484 g_warning ("Error opening %s", location);
485 data = NULL;
486 size = 0;
487 } else {
488 data = (unsigned char *)
489 g_mapped_file_get_contents
490 (mapped_file);
491 size = g_mapped_file_get_length (mapped_file);
492 }
493 g_free (location);
494 }
495 dmap_structure_add (mlit, DMAP_CC_PFDT, data, size);
496 }
497 }
498
499 static void
databases_browse_xxx(G_GNUC_UNUSED DMAPShare * share,G_GNUC_UNUSED SoupServer * server,G_GNUC_UNUSED SoupMessage * msg,const char * path,G_GNUC_UNUSED GHashTable * query,G_GNUC_UNUSED SoupClientContext * context)500 databases_browse_xxx (G_GNUC_UNUSED DMAPShare * share,
501 G_GNUC_UNUSED SoupServer * server,
502 G_GNUC_UNUSED SoupMessage * msg,
503 const char *path,
504 G_GNUC_UNUSED GHashTable * query,
505 G_GNUC_UNUSED SoupClientContext * context)
506 {
507 g_warning ("Unhandled: %s\n", path);
508 }
509
510 static void
send_chunked_file(SoupServer * server,SoupMessage * message,DPAPRecord * record,guint64 filesize)511 send_chunked_file (SoupServer * server, SoupMessage * message,
512 DPAPRecord * record, guint64 filesize)
513 {
514 GInputStream *stream;
515 const char *location;
516 GError *error = NULL;
517 ChunkData *cd = g_new0 (ChunkData, 1);
518
519 g_object_get (record, "location", &location, NULL);
520
521 cd->server = server;
522
523 stream = G_INPUT_STREAM (dpap_record_read (record, &error));
524
525 if (error != NULL) {
526 g_warning ("Couldn't open %s: %s.", location, error->message);
527 g_error_free (error);
528 soup_message_set_status (message,
529 SOUP_STATUS_INTERNAL_SERVER_ERROR);
530 g_free (cd);
531 return;
532 }
533
534 cd->stream = stream;
535
536 if (cd->stream == NULL) {
537 g_warning ("Could not set up input stream");
538 g_free (cd);
539 return;
540 }
541
542 soup_message_headers_set_encoding (message->response_headers,
543 SOUP_ENCODING_CONTENT_LENGTH);
544 soup_message_headers_set_content_length (message->response_headers,
545 filesize);
546
547 soup_message_headers_append (message->response_headers, "Connection",
548 "Close");
549 soup_message_headers_append (message->response_headers,
550 "Content-Type",
551 "application/x-dmap-tagged");
552
553 g_signal_connect (message, "wrote_headers",
554 G_CALLBACK (dmap_write_next_chunk), cd);
555 g_signal_connect (message, "wrote_chunk",
556 G_CALLBACK (dmap_write_next_chunk), cd);
557 g_signal_connect (message, "finished",
558 G_CALLBACK (dmap_chunked_message_finished), cd);
559 /* NOTE: cd g_free'd by chunked_message_finished(). */
560 }
561
562 static void
databases_items_xxx(DMAPShare * share,SoupServer * server,SoupMessage * msg,const char * path,G_GNUC_UNUSED GHashTable * query,G_GNUC_UNUSED SoupClientContext * context)563 databases_items_xxx (DMAPShare * share,
564 SoupServer * server,
565 SoupMessage * msg,
566 const char *path,
567 G_GNUC_UNUSED GHashTable * query,
568 G_GNUC_UNUSED SoupClientContext * context)
569 {
570 DMAPDb *db;
571 const gchar *rest_of_path;
572 const gchar *id_str;
573 guint id;
574 guint64 filesize;
575 DPAPRecord *record;
576
577 rest_of_path = strchr (path + 1, '/');
578 id_str = rest_of_path + 9;
579 id = strtoul (id_str, NULL, 10);
580
581 g_object_get (share, "db", &db, NULL);
582 record = DPAP_RECORD (dmap_db_lookup_by_id (db, id));
583 g_object_get (record, "large-filesize", &filesize, NULL);
584
585 DMAP_SHARE_GET_CLASS (share)->message_add_standard_headers
586 (share, msg);
587 soup_message_set_status (msg, SOUP_STATUS_OK);
588
589 send_chunked_file (server, msg, record, filesize);
590
591 g_object_unref (record);
592 }
593
594 static struct DMAPMetaDataMap *
get_meta_data_map(G_GNUC_UNUSED DMAPShare * share)595 get_meta_data_map (G_GNUC_UNUSED DMAPShare * share)
596 {
597 return meta_data_map;
598 }
599