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