1 /*
2  * zphoto - a zooming photo album generator.
3  *
4  * Copyright (C) 2002-2004  Satoru Takabayashi <satoru@namazu.org>
5  * All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 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  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <errno.h>
28 #include <dirent.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <utime.h>
33 #include <assert.h>
34 #include <zphoto.h>
35 #include "config.h"
36 
37 static char *packagename = PACKAGE;
38 
39 static void
xprintf_console(const char * fmt,va_list args)40 xprintf_console (const char *fmt, va_list args)
41 {
42     fflush(stdout);
43     if (packagename != NULL)
44 	fprintf(stderr, "%s: ", packagename);
45 
46     vfprintf(stderr, fmt, args);
47 
48     if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':')
49 	fprintf(stderr, " %s", strerror(errno));
50     fprintf(stderr, "\n");
51     fflush(stderr);
52 }
53 
54 ZphotoXprintfFunc xprintf = xprintf_console;
55 
56 void
zphoto_set_xprintf(ZphotoXprintfFunc func)57 zphoto_set_xprintf (ZphotoXprintfFunc func)
58 {
59     xprintf = func;
60 }
61 
62 void
zphoto_eprintf(const char * fmt,...)63 zphoto_eprintf (const char *fmt, ...)
64 {
65     va_list args;
66     va_start(args, fmt);
67     xprintf(fmt, args);
68     va_end(args);
69     exit(2);
70 }
71 
72 
73 void
zphoto_wprintf(const char * fmt,...)74 zphoto_wprintf (const char *fmt, ...)
75 {
76     va_list args;
77     va_start(args, fmt);
78     xprintf(fmt, args);
79     va_end(args);
80 }
81 
82 #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
83 /*
84  * They have the declaration of vasprintf in stdio.h
85  */
86 #else
87 extern int vasprintf (char **ptr, const char *fmt, ...);
88 #endif
89 
90 char *
zphoto_asprintf(const char * fmt,...)91 zphoto_asprintf (const char *fmt, ...)
92 {
93     char *str;
94     int val;
95     va_list args;
96 
97     va_start(args, fmt);
98     val = vasprintf(&str, fmt, args);
99     va_end(args);
100 
101     if (val == -1)
102 	zphoto_eprintf("vasprintf of %s failed:", fmt);
103 
104     return str;
105 }
106 
107 
108 FILE *
zphoto_efopen(const char * file_name,const char * mode)109 zphoto_efopen (const char *file_name, const char *mode)
110 {
111     FILE *fp = fopen(file_name, mode);
112     if (fp == NULL)
113 	zphoto_eprintf("%s:", file_name);
114     return fp;
115 }
116 
117 void *
zphoto_emalloc(size_t n)118 zphoto_emalloc (size_t n)
119 {
120     void *p = malloc(n);
121     if (p == NULL)
122 	zphoto_eprintf("malloc of %u bytes failed:", n);
123     return p;
124 }
125 
126 int
zphoto_directory_p(const char * dir_name)127 zphoto_directory_p (const char *dir_name)
128 {
129     struct stat st;
130     if (stat(dir_name, &st) == 0) {
131         return S_ISDIR(st.st_mode);
132     } else {
133         return 0;
134     }
135 }
136 
137 
138 #ifdef __MINGW32__
139 #  define mkdir(dir_name, mode) mkdir(dir_name)
140 #endif
141 void
zphoto_mkdir(const char * dir_name)142 zphoto_mkdir (const char *dir_name)
143 {
144     if (!zphoto_directory_p(dir_name))
145 	if (mkdir(dir_name, 0777) == -1)
146 	    zphoto_eprintf("%s:", dir_name);
147 }
148 
149 time_t
zphoto_get_mtime(const char * file_name)150 zphoto_get_mtime (const char *file_name)
151 {
152     struct stat sb;
153     if (stat(file_name, &sb))
154 	zphoto_eprintf("%s:", file_name);
155     return sb.st_mtime;
156 }
157 
158 char *
zphoto_strdup(const char * str)159 zphoto_strdup (const char *str)
160 {
161     char *p = strdup(str);
162     if (p == NULL) {
163 	zphoto_eprintf("strdup failed:");
164     }
165     return p;
166 }
167 
168 int
zphoto_file_p(const char * file_name)169 zphoto_file_p (const char *file_name)
170 {
171     struct stat sb;
172     if (stat(file_name, &sb))
173 	return 0;
174     return S_ISREG(sb.st_mode);
175 }
176 
177 static int
executable_file_p(const char * file_name)178 executable_file_p (const char *file_name)
179 {
180     struct stat sb;
181     if (stat(file_name, &sb))
182 	return 0;
183 #ifdef __MINGW32__
184     return S_ISREG(sb.st_mode) && (sb.st_mode & S_IEXEC);
185 #else
186     return S_ISREG(sb.st_mode) &&
187         (sb.st_mode & S_IXUSR) &&
188         (sb.st_mode & S_IXGRP) &&
189         (sb.st_mode & S_IXOTH);
190 #endif
191 }
192 
193 DIR *
zphoto_eopendir(const char * dir_name)194 zphoto_eopendir (const char *dir_name)
195 {
196     DIR *dir = opendir(dir_name);
197     if (dir == NULL)
198 	zphoto_eprintf("%s:", dir_name);
199     return dir;
200 }
201 
202 char *
zphoto_time_string(time_t time)203 zphoto_time_string (time_t time)
204 {
205     static char time_string[BUFSIZ];
206     struct tm *tm;
207     tm = localtime(&time);
208     strftime(time_string, BUFSIZ, "%Y-%m-%d %H:%M:%S", tm);
209     return time_string;
210 }
211 
212 char *
zphoto_get_suffix(const char * file_name)213 zphoto_get_suffix (const char *file_name)
214 {
215     char *p = strrchr(file_name, '.');
216     if (p) return p+1;
217     return NULL;
218 }
219 
220 char *
zphoto_suppress_suffix(char * file_name)221 zphoto_suppress_suffix (char *file_name)
222 {
223     char *p = strrchr(file_name, '.');
224     if (p) *p = '\0';
225     return file_name;
226 }
227 
228 char *
zphoto_modify_suffix(const char * file_name,const char * suffix)229 zphoto_modify_suffix (const char *file_name, const char *suffix)
230 {
231     char *tmp, *new;
232     tmp = zphoto_strdup(file_name);
233     zphoto_suppress_suffix(tmp);
234     new = zphoto_asprintf("%s.%s", tmp, suffix);
235     free(tmp);
236     return new;
237 }
238 
239 #ifdef __MINGW32__
240 #include <windows.h>
241 
242 static int
shift_jis_first_byte_p(int c)243 shift_jis_first_byte_p (int c)
244 {
245     return (c >= 0x81 && c <= 0x9f) ||
246            (c >= 0xe0 && c <= 0xfc);
247 }
248 
249 static int
shift_jis_second_byte_p(int c)250 shift_jis_second_byte_p (int c)
251 {
252     return (c >= 0x40 && c <= 0x7e) ||
253            (c >= 0x80 && c <= 0xfc);
254 }
255 
256 static char *
find_last_separator_jpn(const char * file_name)257 find_last_separator_jpn (const char *file_name)
258 {
259     unsigned char *p, *separator = NULL;
260     for (p = (unsigned char *)file_name; *p != '\0'; p++) {
261         if (shift_jis_first_byte_p(*p)) {
262             p++;
263             if (! shift_jis_second_byte_p(*p))
264                 zphoto_eprintf("broken Shift_JIS: %s", file_name);
265         } else if (*p == '/' || *p == '\\') {
266             separator = p;
267         }
268     }
269     return (char *)separator;
270 }
271 
272 static char *
find_last_separator(const char * file_name)273 find_last_separator (const char *file_name)
274 {
275     if (zphoto_platform_w32_jpn_p()) {
276         return find_last_separator_jpn(file_name);
277     } else {
278         unsigned char *p, *separator = NULL;
279         for (p = (unsigned char *)file_name; *p != '\0'; p++) {
280             if (*p == '/' || *p == '\\')
281                 separator = p;
282         }
283         return (char *)separator;
284     }
285 }
286 
287 
288 #else
289 
290 static char *
find_last_separator(const char * file_name)291 find_last_separator (const char *file_name)
292 {
293     return strrchr(file_name, '/');
294 }
295 
296 #endif
297 
298 static int
root_p(const char * file_name)299 root_p (const char *file_name)
300 {
301     if (zphoto_platform_w32_p()) {
302         if (strcmp(file_name, "\\") == 0) {
303             return 1;
304         } else if (strcmp(file_name, "/") == 0) {
305             return 1;
306         } else {
307             return 0;
308         }
309     } else {
310         return strcmp(file_name, "/") == 0;
311     }
312 }
313 
314 char *
zphoto_basename(const char * file_name)315 zphoto_basename (const char *file_name)
316 {
317     char *p;
318 
319     if (root_p(file_name)) {
320         return (char *)file_name;
321     } else if ((p = find_last_separator(file_name))) {
322         return p + 1;
323     } else {
324         return (char *)file_name;
325     }
326 }
327 
328 char *
zphoto_dirname(const char * file_name)329 zphoto_dirname (const char *file_name)
330 {
331     char *p;
332 
333     if (root_p(file_name)) {
334         return zphoto_strdup(file_name);
335     } else if ((p = find_last_separator(file_name))) {
336         char *dirname = zphoto_strdup(file_name);
337         dirname[p - file_name] = '\0';
338         return dirname;
339     } else {
340         return zphoto_strdup(file_name);
341     }
342 }
343 
344 static char *
downcase(const char * str)345 downcase (const char *str)
346 {
347     char *new = zphoto_strdup(str);
348     char *p = new;
349 
350     while (*p) {
351 	*p = tolower(*p);
352 	p++;
353     }
354     return new;
355 }
356 
357 /*
358  * 'x' to avoid confliction.
359  */
360 static char *
xstrcasestr(const char * str1,const char * str2)361 xstrcasestr (const char *str1, const char *str2)
362 {
363     char *p1  = downcase(str1);
364     char *p2  = downcase(str2);
365     char *val = strstr(p1, p2);
366 
367     free(p1);
368     free(p2);
369     return val;
370 }
371 
372 int
zphoto_strsuffixcasecmp(const char * str1,const char * str2)373 zphoto_strsuffixcasecmp (const char *str1, const char *str2)
374 {
375     int len1, len2;
376 
377     len1 = strlen(str1);
378     len2 = strlen(str2);
379 
380     if (len1 > len2) {
381         return strcasecmp(str1 + len1 - len2, str2);
382     } else {
383         /* return strcasecmp(str2 + len2 - len1, str1); */
384         return -1;
385     }
386 }
387 
388 char **
zphoto_get_image_suffixes(void)389 zphoto_get_image_suffixes (void)
390 {
391     static char *empty[] = { NULL };
392     static char *suffixes[] = { "jpeg", "jpg", "png", "gif", "bmp", NULL };
393 
394     if (zphoto_support_image_p()) {
395         return suffixes;
396     } else {
397         return empty;
398     }
399 }
400 
401 char **
zphoto_get_movie_suffixes(void)402 zphoto_get_movie_suffixes (void)
403 {
404     static char *empty[] = { NULL };
405     static char *suffixes[] = { "avi", "mpg", NULL };
406 
407     if (zphoto_support_movie_p()) {
408         return suffixes;
409     } else {
410         return empty;
411     }
412 }
413 
414 static int
match_suffix(const char * file_name,char ** suffixes)415 match_suffix (const char *file_name, char **suffixes)
416 {
417     char **p = suffixes;
418     while (*p != NULL) {
419         char *tmp = zphoto_asprintf(".%s", *p);
420         if (zphoto_strsuffixcasecmp(file_name, tmp) == 0 ) {
421             free(tmp);
422             return 1;
423         }
424         free(tmp);
425         p++;
426     }
427     return 0;
428 }
429 
430 /*
431  * Check by its file name only (not its content).
432  */
433 int
zphoto_supported_file_p(const char * file_name)434 zphoto_supported_file_p (const char *file_name)
435 {
436     return (zphoto_support_image_p() && zphoto_image_file_p(file_name)) ||
437            (zphoto_support_movie_p() && zphoto_movie_file_p(file_name));
438 }
439 
440 int
zphoto_image_file_p(const char * file_name)441 zphoto_image_file_p (const char *file_name)
442 {
443     return match_suffix(file_name, zphoto_get_image_suffixes());
444 }
445 
446 int
zphoto_movie_file_p(const char * file_name)447 zphoto_movie_file_p (const char *file_name)
448 {
449     return match_suffix(file_name, zphoto_get_movie_suffixes());
450 }
451 
452 int
zphoto_web_file_p(const char * file_name)453 zphoto_web_file_p (const char *file_name)
454 {
455 
456     if ((zphoto_strsuffixcasecmp(file_name, ".html")  == 0) ||
457         (zphoto_strsuffixcasecmp(file_name, ".css")   == 0) ||
458         (zphoto_strsuffixcasecmp(file_name, ".js")   == 0) ||
459         (xstrcasestr(file_name, ".html.")) || /* multiview: index.html.ja */
460         (xstrcasestr(file_name, ".js.")))    /* multiview: foo.js.ja */
461     {
462 	return 1;
463     } else {
464 	return 0;
465     }
466 }
467 
468 int
zphoto_dot_file_p(const char * file_name)469 zphoto_dot_file_p (const char *file_name)
470 {
471     if (zphoto_basename(file_name)[0] == '.')
472         return 1;
473     else
474         return 0;
475 }
476 
477 int
zphoto_path_exist_p(const char * file_name)478 zphoto_path_exist_p (const char *file_name)
479 {
480     struct stat st;
481     if (stat(file_name, &st) == 0) {
482         return 1;
483     } else {
484         return 0;
485     }
486 }
487 
488 int
zphoto_support_movie_p(void)489 zphoto_support_movie_p (void)
490 {
491 #if HAVE_AVIFILE && HAVE_IMLIB2
492     return 1;
493 #else
494     return 0;
495 #endif
496 }
497 
498 int
zphoto_support_image_p(void)499 zphoto_support_image_p (void)
500 {
501 #if HAVE_IMLIB2 || HAVE_MAGICK
502     return 1;
503 #else
504     return 0;
505 #endif
506 }
507 
508 int
zphoto_support_zip_p(void)509 zphoto_support_zip_p (void)
510 {
511 #ifdef HAVE_ZIP
512     return 1;
513 #else
514     return 0;
515 #endif
516 }
517 
518 int
zphoto_platform_w32_p(void)519 zphoto_platform_w32_p (void)
520 {
521 #ifdef __MINGW32__
522     return 1;
523 #else
524     return 0;
525 #endif
526 }
527 
528 int
zphoto_platform_w32_jpn_p(void)529 zphoto_platform_w32_jpn_p (void)
530 {
531 #ifdef __MINGW32__
532     char locale_name[32];
533     GetLocaleInfo(LOCALE_USER_DEFAULT,
534                   LOCALE_SABBREVLANGNAME | LOCALE_USE_CP_ACP,
535                   locale_name, sizeof(locale_name));
536     return strcmp(locale_name, "JPN") == 0;
537 #else
538     return 0;
539 #endif
540 }
541 
542 char *
zphoto_get_program_file_name(void)543 zphoto_get_program_file_name (void)
544 {
545 #ifdef __MINGW32__
546     int length, maxlen = 1024;
547     char file_name[maxlen];
548     length = GetModuleFileName(NULL, file_name, maxlen);
549     file_name[length] = '\0';
550     return zphoto_strdup(file_name);
551 #else
552     assert(0); /* unsupported */
553     return NULL;
554 #endif
555 }
556 
557 
558 /*
559  * ImageMagick depended codes.
560  */
561 #ifdef HAVE_MAGICK
562 #include <magick/api.h>
563 
564 void
zphoto_init_magick(void)565 zphoto_init_magick (void)
566 {
567     InitializeMagick(PACKAGE);
568 }
569 
570 void
zphoto_finalize_magick(void)571 zphoto_finalize_magick (void)
572 {
573     DestroyMagick();
574 }
575 
576 static int
magick_has_readdir_bug_p(void)577 magick_has_readdir_bug_p (void)
578 {
579     return MagickLibVersion <= 0x557;
580 }
581 
582 #else
583 void
zphoto_init_magick(void)584 zphoto_init_magick (void)
585 {
586 }
587 
588 void
zphoto_finalize_magick(void)589 zphoto_finalize_magick (void)
590 {
591 }
592 
593 static int
magick_has_readdir_bug_p(void)594 magick_has_readdir_bug_p (void)
595 {
596     return 0;
597 }
598 #endif
599 
600 char *
zphoto_d_name_workaround(struct dirent * d)601 zphoto_d_name_workaround (struct dirent *d)
602 {
603     /*
604      * FIXME: very very dirty workaround for ImageMagick's own
605      * opendir/readdir in magick/nt_base.c.
606      * I've reported the bug in 2004-04-02 but for the moment...
607      */
608     if (zphoto_platform_w32_p() && magick_has_readdir_bug_p()) {
609         return d->d_name - 8;
610     } else {
611         return d->d_name;
612     }
613 }
614 
615 int
zphoto_blank_line_p(const char * line)616 zphoto_blank_line_p (const char *line)
617 {
618     return line[strspn(line, "\t ")] == '\0';
619 }
620 
621 int
zphoto_complete_line_p(const char * line)622 zphoto_complete_line_p (const char *line)
623 {
624     return line[strlen(line) - 1] == '\n';
625 }
626 
627 void
zphoto_chomp(char * line)628 zphoto_chomp (char *line)
629 {
630     line[strcspn(line, "\r\n")] = '\0';
631 }
632 
633 int
zphoto_valid_color_string_p(const char * string)634 zphoto_valid_color_string_p (const char *string)
635 {
636     const char *p;
637     int len = strlen(string);
638 
639     if (!(string[0] == '#' && (len == 9 || len == 7)))
640         return 0;
641 
642     p = string + 1;
643     while (*p != '\0') {
644         if (!isxdigit(*p))
645             return 0;
646         p++;
647     }
648 
649     return 1;
650 }
651 
652 void
zphoto_decode_color_string(const char * string,int * r,int * g,int * b,int * a)653 zphoto_decode_color_string (const char *string, int *r, int *g, int *b, int *a)
654 {
655     unsigned long int value;
656     int len;
657 
658     if (!zphoto_valid_color_string_p(string))
659         zphoto_eprintf("%s: malformed color value", string);
660 
661     value = strtoul(string + 1, NULL, 16);
662     len = strlen(string);
663     if (len ==  9) { /* has alpha */
664         *r = (value >> 24) & 255;
665         *g = (value >> 16) & 255;
666         *b = (value >>  8) & 255;
667         *a = value & 255;
668     } else {
669         *r = (value >> 16) & 255;
670         *g = (value >>  8) & 255;
671         *b = value & 255;
672         *a = -1;
673     }
674 }
675 
676 char *
zphoto_encode_color_string(int r,int g,int b,int a)677 zphoto_encode_color_string (int r, int g, int b, int a)
678 {
679     if (a > 0) {
680         return zphoto_asprintf("#%02x%02x%02x%02x", r, g, b, a);
681     } else {
682         return zphoto_asprintf("#%02x%02x%02x", r, g, b);
683     }
684 }
685 
686 static char *
find_browser(void)687 find_browser (void)
688 {
689     char *browsers[] = {
690         "/usr/bin/x-www-browser", /* debian */
691         "/usr/bin/htmlview",      /* red hat */
692         NULL
693     };
694     char **p = browsers;
695 
696     while (*p != NULL) {
697         if (executable_file_p(*p)) {
698             return *p;
699         }
700         p++;
701     }
702     return NULL;
703 }
704 
705 int
zphoto_support_browser_p(void)706 zphoto_support_browser_p (void)
707 {
708     if (zphoto_platform_w32_p()) {
709         return 1;
710     } else {
711         if (find_browser() != NULL) {
712             return 1;
713         } else {
714             return 0;
715         }
716     }
717 }
718 
719 void
zphoto_launch_browser(const char * url)720 zphoto_launch_browser (const char *url)
721 {
722 #ifdef __MINGW32__
723     ShellExecute(NULL, "open", url, NULL, "", SW_SHOWNORMAL);
724 #else
725     if (zphoto_support_browser_p()) {
726         char *browser = find_browser();
727         char *command = zphoto_asprintf("%s %s &", browser, url);
728         system(command);
729         free(command);
730     }
731 #endif
732 }
733 
734 int
zphoto_path_separator(void)735 zphoto_path_separator (void)
736 {
737     if (zphoto_platform_w32_p()) {
738         return '\\';
739     } else {
740         return '/';
741     }
742 }
743 
744 static int
has_drive_letter(const char * path)745 has_drive_letter (const char *path)
746 {
747     return isalpha(*path) && path[1] == ':';
748 }
749 
750 static int
relative_path_p(const char * path)751 relative_path_p (const char *path)
752 {
753     if (zphoto_platform_w32_p()) {
754         return !(*path == '/' || *path == '\\' || has_drive_letter(path));
755     } else {
756         return *path != '/';
757     }
758 }
759 
760 /*
761  * FIXME: Very ad hoc implementation.
762  */
763 char *
zphoto_expand_path(const char * path,const char * dir_name)764 zphoto_expand_path (const char *path, const char *dir_name)
765 {
766     char current_dir[BUFSIZ];
767     if (relative_path_p(path)) {
768         if (dir_name == NULL) {
769             getcwd(current_dir, BUFSIZ);
770             dir_name = current_dir;
771         }
772         return zphoto_asprintf("%s%c%s",
773                                dir_name,
774                                zphoto_path_separator(),
775                                path);
776     } else {
777         return zphoto_strdup(path);
778     }
779 }
780 
781 int
zphoto_directory_empty_p(const char * dir_name)782 zphoto_directory_empty_p (const char *dir_name)
783 {
784     int count = 0;
785     DIR *dir = zphoto_eopendir(dir_name);
786     struct dirent *d;
787 
788     while ((d = readdir(dir))) {
789         count++;
790     }
791     closedir(dir);
792     return count == 2; /* "." and ".." */
793 }
794 
795 char *
zphoto_escape_url(const char * url)796 zphoto_escape_url (const char *url)
797 {
798     const char *p;
799     char *new_url = zphoto_emalloc(strlen(url) * 3 + 1), *pp;
800 
801     for (p = url, pp = new_url; *p != '\0'; p++, pp++) {
802         /*
803          * We intentionally don't apply the following conversion.
804          * if (*p == ' ') { *pp = '+'; }
805          */
806         if ((isascii(*p) && isalnum(*p)) ||
807                    (strchr("_.-", *p) != NULL)) {
808             *pp = *p;
809         } else {
810             snprintf(pp, 4, "%%%02X", (unsigned char)*p);
811             pp += 2;
812         }
813     }
814     *pp = '\0';
815     return new_url;
816 }
817 
818 
819