1 #include "loader_common.h"
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include <id3tag.h>
8 
9 #if ! defined (__STDC_VERSION__) || __STDC_VERSION__ < 199901L
10 #if __GNUC__ >= 2
11 #define inline __inline__
12 #else
13 #define inline
14 #endif
15 #endif
16 
17 #ifdef __GNUC__
18 #define UNLIKELY(exp) __builtin_expect ((exp), 0)
19 #else
20 #define UNLIKELY(exp) (exp)
21 #endif
22 
23 typedef struct context {
24    int                 id;
25    char               *filename;
26    struct id3_tag     *tag;
27    int                 refcount;
28    struct context     *next;
29 } context;
30 
31 static context     *id3_ctxs = NULL;
32 
33 static inline struct id3_frame *
id3_tag_get_frame(struct id3_tag * tag,size_t index)34 id3_tag_get_frame(struct id3_tag *tag, size_t index)
35 {
36    return tag->frames[index];
37 }
38 
39 static inline unsigned int
id3_tag_get_numframes(struct id3_tag * tag)40 id3_tag_get_numframes(struct id3_tag *tag)
41 {
42    return tag->nframes;
43 }
44 
45 static inline char const *
id3_frame_id(struct id3_frame * frame)46 id3_frame_id(struct id3_frame *frame)
47 {
48    return frame->id;
49 }
50 
51 static context     *
context_create(const char * filename,FILE * f)52 context_create(const char *filename, FILE * f)
53 {
54    context            *node = (context *) malloc(sizeof(context));
55    context            *ptr, *last;
56    int                 last_id = INT_MAX;
57 
58    node->refcount = 1;
59    {
60       int                 fd;
61       struct id3_file    *file;
62       struct id3_tag     *tag;
63       unsigned int        i;
64 
65       fd = dup(fileno(f));
66       file = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY);
67       if (!file)
68         {
69            close(fd);
70            fprintf(stderr, "Unable to open tagged file %s: %s\n",
71                    filename, strerror(errno));
72            goto fail_free;
73         }
74       tag = id3_file_tag(file);
75       if (!tag)
76         {
77            fprintf(stderr, "Unable to find ID3v2 tags in file %s\n", filename);
78            id3_file_close(file);
79            goto fail_free;
80         }
81       node->tag = id3_tag_new();
82       for (i = 0; i < id3_tag_get_numframes(tag); i++)
83          if (!strcmp(id3_frame_id(id3_tag_get_frame(tag, i)), "APIC"))
84             id3_tag_attachframe(node->tag, id3_tag_get_frame(tag, i));
85       id3_file_close(file);
86    }
87 
88    node->filename = strdup(filename);
89 
90    if (!id3_ctxs)
91      {
92         node->id = 1;
93         node->next = NULL;
94         id3_ctxs = node;
95         return node;
96      }
97    ptr = id3_ctxs;
98 
99    last = NULL;
100    while (UNLIKELY(ptr && (ptr->id + 1) >= last_id))
101      {
102         last_id = ptr->id;
103         last = ptr;
104         ptr = ptr->next;
105      }
106 
107    /* Paranoid! this can occur only if there are INT_MAX contexts :) */
108    if (UNLIKELY(!ptr))
109      {
110         fprintf(stderr, "Too many open ID3 contexts\n");
111         goto fail_close;
112      }
113 
114    node->id = ptr->id + 1;
115 
116    if (UNLIKELY(!!last))
117      {
118         node->next = last->next;
119         last->next = node;
120      }
121    else
122      {
123         node->next = id3_ctxs;
124         id3_ctxs = node;
125      }
126    return node;
127 
128  fail_close:
129    free(node->filename);
130    id3_tag_delete(node->tag);
131  fail_free:
132    free(node);
133    return NULL;
134 }
135 
136 static void
context_destroy(context * ctx)137 context_destroy(context * ctx)
138 {
139    id3_tag_delete(ctx->tag);
140    free(ctx->filename);
141    free(ctx);
142 }
143 
144 static inline void
context_addref(context * ctx)145 context_addref(context * ctx)
146 {
147    ctx->refcount++;
148 }
149 
150 static context     *
context_get(int id)151 context_get(int id)
152 {
153    context            *ptr = id3_ctxs;
154 
155    while (ptr)
156      {
157         if (ptr->id == id)
158           {
159              context_addref(ptr);
160              return ptr;
161           }
162         ptr = ptr->next;
163      }
164    fprintf(stderr, "No context by handle %d found\n", id);
165    return NULL;
166 }
167 
168 static context     *
context_get_by_name(const char * name)169 context_get_by_name(const char *name)
170 {
171    context            *ptr = id3_ctxs;
172 
173    while (ptr)
174      {
175         if (!strcmp(name, ptr->filename))
176           {
177              context_addref(ptr);
178              return ptr;
179           }
180         ptr = ptr->next;
181      }
182    return NULL;
183 }
184 
185 static void
context_delref(context * ctx)186 context_delref(context * ctx)
187 {
188    ctx->refcount--;
189    if (ctx->refcount <= 0)
190      {
191         context            *last = NULL, *ptr = id3_ctxs;
192 
193         while (ptr)
194           {
195              if (ptr == ctx)
196                {
197                   if (last)
198                      last->next = ctx->next;
199                   else
200                      id3_ctxs = ctx->next;
201                   context_destroy(ctx);
202                   return;
203                }
204              last = ptr;
205              ptr = ptr->next;
206           }
207      }
208 }
209 
210 static int
str2int(const char * str,int old)211 str2int(const char *str, int old)
212 {
213    long                index;
214 
215    errno = 0;
216    index = strtol(str, NULL, 10);
217    return ((errno || index > INT_MAX) ? old : (int)index);
218 }
219 
220 static unsigned int
str2uint(const char * str,unsigned int old)221 str2uint(const char *str, unsigned int old)
222 {
223    unsigned long       index;
224 
225    errno = 0;
226    index = strtoul(str, NULL, 10);
227    return ((errno || index > UINT_MAX) ? old : index);
228 }
229 
230 static void
destructor_data(ImlibImage * im,void * data)231 destructor_data(ImlibImage * im, void *data)
232 {
233    free(data);
234 }
235 
236 static void
destructor_context(ImlibImage * im,void * data)237 destructor_context(ImlibImage * im, void *data)
238 {
239    context_delref((context *) data);
240 }
241 
242 typedef struct lopt {
243    context            *ctx;
244    unsigned int        index;
245    int                 traverse;
246    char                cache_level;
247 } lopt;
248 
249 static char
get_options(lopt * opt,const ImlibImage * im)250 get_options(lopt * opt, const ImlibImage * im)
251 {
252    unsigned int        handle = 0, index = 0, traverse = 0;
253    context            *ctx;
254 
255    if (im->key)
256      {
257         char               *key = strdup(im->key);
258         char               *tok = strtok(key, ",");
259 
260         traverse = 0;
261         while (tok)
262           {
263              char               *value = strchr(tok, '=');
264 
265              if (!value)
266                {
267                   value = tok;
268                   tok = (char *)"index";
269                }
270              else
271                {
272                   *value = '\0';
273                   value++;
274                }
275              if (!strcasecmp(tok, "index"))
276                 index = str2uint(value, index);
277              else if (!strcasecmp(tok, "context"))
278                 handle = str2uint(value, handle);
279              else if (!strcasecmp(tok, "traverse"))
280                 traverse = str2int(value, traverse);
281              tok = strtok(NULL, ",");
282           }
283         free(key);
284      }
285    else
286       traverse = 1;
287 
288    if (!handle)
289      {
290         ImlibImageTag      *htag = __imlib_GetTag(im, "context");
291 
292         if (htag && htag->val)
293            handle = htag->val;
294      }
295    if (handle)
296       ctx = context_get(handle);
297    else if (!(ctx = context_get_by_name(im->real_file)) &&
298             !(ctx = context_create(im->real_file, im->fp)))
299       return 0;
300 
301    if (!index)
302      {
303         ImlibImageTag      *htag = __imlib_GetTag(im, "index");
304 
305         if (htag && htag->val)
306            index = htag->val;
307      }
308    if (index > id3_tag_get_numframes(ctx->tag) ||
309        (index == 0 && id3_tag_get_numframes(ctx->tag) < 1))
310      {
311         if (index)
312            fprintf(stderr, "No picture frame # %d found\n", index);
313         context_delref(ctx);
314         return 0;
315      }
316    if (!index)
317       index = 1;
318 
319    opt->ctx = ctx;
320    opt->index = index;
321    opt->traverse = traverse;
322    opt->cache_level = (id3_tag_get_numframes(ctx->tag) > 1 ? 1 : 0);
323    return 1;
324 }
325 
326 static int
extract_pic(struct id3_frame * frame,int dest)327 extract_pic(struct id3_frame *frame, int dest)
328 {
329    union id3_field    *field;
330    unsigned char const *data;
331    id3_length_t        length;
332    int                 done = 0;
333 
334    field = id3_frame_field(frame, 4);
335    data = id3_field_getbinarydata(field, &length);
336    if (!data)
337      {
338         fprintf(stderr, "No image data found for frame\n");
339         return 0;
340      }
341    while (length > 0)
342      {
343         ssize_t             res;
344 
345         if ((res = write(dest, data + done, length)) < 0)
346           {
347              if (errno == EINTR)
348                 continue;
349              perror("Unable to write to file");
350              return 0;
351           }
352         length -= res;
353         done += res;
354      }
355    return 1;
356 }
357 
358 #define EXT_LEN 14
359 
360 static char
get_loader(lopt * opt,ImlibLoader ** loader)361 get_loader(lopt * opt, ImlibLoader ** loader)
362 {
363    union id3_field    *field;
364    char const         *data;
365    char                ext[EXT_LEN + 2];
366 
367    ext[EXT_LEN + 1] = '\0';
368    ext[0] = '.';
369 
370    field = id3_frame_field(id3_tag_get_frame(opt->ctx->tag, opt->index - 1), 1);
371    data = (char const *)id3_field_getlatin1(field);
372    if (!data)
373      {
374         fprintf(stderr, "No mime type data found for image frame\n");
375         return 0;
376      }
377    if (strncasecmp(data, "image/", 6))
378      {
379         if (!strcmp(data, "-->"))
380           {
381              *loader = NULL;
382              return 1;
383           }
384         fprintf(stderr,
385                 "Picture frame with unknown mime-type \'%s\' found\n", data);
386         return 0;
387      }
388    strncpy(ext + 1, data + 6, EXT_LEN);
389    if (!(*loader = __imlib_FindBestLoaderForFile(ext, 0)))
390      {
391         fprintf(stderr, "No loader found for extension %s\n", ext);
392         return 0;
393      }
394    return 1;
395 }
396 
397 static const char  *const id3_pic_types[] = {
398    /* $00 */ "Other",
399    /* $01 */ "32x32 pixels file icon",
400    /* $02 */ "Other file icon",
401    /* $03 */ "Cover (front)",
402    /* $04 */ "Cover (back)",
403    /* $05 */ "Leaflet page",
404    /* $06 */ "Media",
405    /* $07 */ "Lead artist/lead performer/soloist",
406    /* $08 */ "Artist/performer",
407    /* $09 */ "Conductor",
408    /* $0A */ "Band/Orchestra",
409    /* $0B */ "Composer",
410    /* $0C */ "Lyricist/text writer",
411    /* $0D */ "Recording Location",
412    /* $0E */ "During recording",
413    /* $0F */ "During performance",
414    /* $10 */ "Movie/video screen capture",
415    /* $11 */ "A bright coloured fish",
416    /* $12 */ "Illustration",
417    /* $13 */ "Band/artist logotype",
418    /* $14 */ "Publisher/Studio logotype"
419 };
420 
421 #define NUM_OF_ID3_PIC_TYPES \
422     (sizeof(id3_pic_types) / sizeof(id3_pic_types[0]))
423 
424 static const char  *const id3_text_encodings[] = {
425    /* $00 */ "ISO-8859-1",
426    /* $01 */ "UTF-16 encoded Unicode with BOM",
427    /* $02 */ "UTF-16BE encoded Unicode without BOM",
428    /* $03 */ "UTF-8 encoded Unicode"
429 };
430 
431 #define NUM_OF_ID3_TEXT_ENCODINGS \
432     (sizeof(id3_text_encodings) / sizeof(id3_text_encodings[0]))
433 
434 static void
write_tags(ImlibImage * im,lopt * opt)435 write_tags(ImlibImage * im, lopt * opt)
436 {
437    struct id3_frame   *frame = id3_tag_get_frame(opt->ctx->tag, opt->index - 1);
438    union id3_field    *field;
439    unsigned int        num_data;
440    char               *data;
441 
442    if ((field = id3_frame_field(frame, 1)) &&
443        (data = (char *)id3_field_getlatin1(field)))
444       __imlib_AttachTag(im, "mime-type", 0, strdup(data), destructor_data);
445    if ((field = id3_frame_field(frame, 3)) &&
446        (data = (char *)id3_field_getstring(field)))
447      {
448         size_t              length;
449         char               *dup;
450         id3_ucs4_t         *ptr = (id3_ucs4_t *) data;
451 
452         while (*ptr)
453            ptr++;
454         length = (ptr - (id3_ucs4_t *) data + 1) * sizeof(id3_ucs4_t);
455         dup = (char *)malloc(length);
456         memcpy(dup, data, length);
457         __imlib_AttachTag(im, "id3-description", 0, dup, destructor_data);
458      }
459    if ((field = id3_frame_field(frame, 0)))
460      {
461         num_data = id3_field_gettextencoding(field);
462         __imlib_AttachTag(im, "id3-description-text-encoding", num_data,
463                           num_data < NUM_OF_ID3_TEXT_ENCODINGS ?
464                           (char *)id3_text_encodings[num_data] : NULL, NULL);
465      }
466    if ((field = id3_frame_field(frame, 2)))
467      {
468         num_data = id3_field_getint(field);
469         __imlib_AttachTag(im, "id3-picture-type", num_data,
470                           num_data < NUM_OF_ID3_PIC_TYPES ?
471                           (char *)id3_pic_types[num_data] : NULL, NULL);
472      }
473    __imlib_AttachTag(im, "count", id3_tag_get_numframes(opt->ctx->tag),
474                      NULL, NULL);
475    if (opt->cache_level)
476      {
477         context_addref(opt->ctx);
478         __imlib_AttachTag(im, "context", opt->ctx->id,
479                           opt->ctx, destructor_context);
480      }
481    __imlib_AttachTag(im, "index", opt->index, NULL, NULL);
482    if (opt->traverse)
483      {
484         char               *buf = NULL;
485 
486         if ((opt->index + opt->traverse)
487             <= id3_tag_get_numframes(opt->ctx->tag)
488             && (opt->index + opt->traverse) > 0)
489           {
490              buf = (char *)malloc((strlen(im->real_file) + 50) * sizeof(char));
491              sprintf(buf, "%s:index=%d,traverse=%d", im->real_file,
492                      opt->index + opt->traverse, opt->traverse);
493           }
494         __imlib_AttachTag(im, "next", 0, buf, destructor_data);
495      }
496 }
497 
498 int
load2(ImlibImage * im,int load_data)499 load2(ImlibImage * im, int load_data)
500 {
501    ImlibLoader        *loader;
502    lopt                opt;
503    int                 res;
504 
505    res = LOAD_FAIL;
506    opt.ctx = NULL;
507 
508    if (!get_options(&opt, im))
509       goto fail_context;
510 
511    if (!get_loader(&opt, &loader))
512       goto fail_context;
513 
514    if (loader)
515      {
516         char                tmp[] = "/tmp/imlib2_loader_id3-XXXXXX";
517         int                 dest;
518 
519         if ((dest = mkstemp(tmp)) < 0)
520           {
521              fprintf(stderr, "Unable to create a temporary file\n");
522              goto fail_context;
523           }
524 
525         res = extract_pic(id3_tag_get_frame(opt.ctx->tag, opt.index - 1), dest);
526         close(dest);
527 
528         if (!res)
529           {
530              unlink(tmp);
531              goto fail_context;
532           }
533 
534         res = __imlib_LoadEmbedded(loader, im, tmp, load_data);
535 
536         unlink(tmp);
537      }
538    else
539      {
540         /* The tag actually provides a image url rather than image data.
541          * Practically, dunno if such a tag exists on earth :)
542          * Here's the code anyway...
543          */
544         union id3_field    *field;
545         id3_length_t        length;
546         char const         *data;
547         char               *url, *file;
548 
549         field = id3_frame_field
550            (id3_tag_get_frame(opt.ctx->tag, opt.index - 1), 4);
551         data = (char const *)id3_field_getbinarydata(field, &length);
552         if (!data || !length)
553           {
554              fprintf(stderr, "No link image URL present\n");
555              goto fail_context;
556           }
557         url = (char *)malloc((length + 1) * sizeof(char));
558         strncpy(url, data, length);
559         url[length] = '\0';
560         file = (strncmp(url, "file://", 7) ? url : url + 7);
561         if (!(loader = __imlib_FindBestLoaderForFile(file, 0)))
562           {
563              fprintf(stderr, "No loader found for file %s\n", file);
564              free(url);
565              goto fail_context;
566           }
567 
568         res = __imlib_LoadEmbedded(loader, im, file, load_data);
569 
570         if (!im->loader)
571            __imlib_AttachTag(im, "id3-link-url", 0, url, destructor_data);
572         else
573            free(url);
574      }
575 
576    if (!im->loader)
577       write_tags(im, &opt);
578 
579 #ifdef DEBUG
580    if (!im->loader)
581      {
582         ImlibImageTag      *cur = im->tags;
583 
584         fprintf(stderr, "Tags for file %s:\n", im->file);
585         while (cur)
586           {
587              fprintf(stderr, "\t%s: (%d) %s\n", cur->key,
588                      cur->val, (char *)cur->data);
589              cur = cur->next;
590           }
591      }
592 #endif
593 
594    res = LOAD_SUCCESS;
595 
596  fail_context:
597    if (opt.ctx)
598       context_delref(opt.ctx);
599 
600    return res;
601 }
602 
603 void
formats(ImlibLoader * l)604 formats(ImlibLoader * l)
605 {
606    static const char  *const list_formats[] = { "mp3" };
607    __imlib_LoaderSetFormats(l, list_formats,
608                             sizeof(list_formats) / sizeof(char *));
609 }
610