2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / utils.cpp
blobc6143f1eb34eea438305b5cfcc57a9be1484541a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * utils.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <glib/gstdio.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <stdlib.h>
20 #include "utils.h"
21 #include "application.h"
22 #include "deployment.h"
24 static gpointer
25 managed_stream_open (gpointer context, const char *filename, int mode)
27 // minizip expects to get a FILE* here, we'll just shuffle our context around.
29 return context;
32 static unsigned long
33 managed_stream_read (gpointer context, gpointer stream, void *buf, unsigned long size)
35 ManagedStreamCallbacks *s = (ManagedStreamCallbacks *) context;
36 unsigned long left = size;
37 unsigned long nread = 0;
38 int n;
40 do {
41 if ((n = s->Read (s->handle, (char *) buf + nread, 0, MIN (left, G_MAXINT32))) <= 0)
42 break;
44 nread += n;
45 left -= n;
46 } while (nread < size);
48 return nread;
51 static unsigned long
52 managed_stream_write (gpointer context, gpointer stream, const void *buf, unsigned long size)
54 ManagedStreamCallbacks *s = (ManagedStreamCallbacks *) context;
55 unsigned long nwritten = 0;
56 unsigned long left = size;
57 int n;
59 do {
60 n = MIN (left, G_MAXINT32);
61 s->Write (s->handle, (char *) buf + nwritten, 0, n);
62 nwritten += n;
63 left -= n;
64 } while (nwritten < size);
66 return nwritten;
69 static long
70 managed_stream_tell (gpointer context, gpointer stream)
72 ManagedStreamCallbacks *s = (ManagedStreamCallbacks *) context;
74 return s->Position (s->handle);
77 static long
78 managed_stream_seek (gpointer context, gpointer stream, unsigned long offset, int origin)
80 ManagedStreamCallbacks *s = (ManagedStreamCallbacks *) context;
82 s->Seek (s->handle, offset, origin);
84 return 0;
87 static int
88 managed_stream_close (gpointer context, gpointer stream)
90 return 0;
93 static int
94 managed_stream_error (gpointer context, gpointer stream)
96 return 0;
100 gboolean
101 managed_unzip_stream_to_stream_first_file (ManagedStreamCallbacks *source, ManagedStreamCallbacks *dest)
103 zlib_filefunc_def funcs;
104 unzFile zipFile;
105 gboolean ret;
107 ret = FALSE;
109 funcs.zopen_file = managed_stream_open;
110 funcs.zread_file = managed_stream_read;
111 funcs.zwrite_file = managed_stream_write;
112 funcs.ztell_file = managed_stream_tell;
113 funcs.zseek_file = managed_stream_seek;
114 funcs.zclose_file = managed_stream_close;
115 funcs.zerror_file = managed_stream_error;
116 funcs.opaque = source;
118 zipFile = unzOpen2 (NULL, &funcs);
120 if (!zipFile)
121 return FALSE;
123 if (unzGoToFirstFile (zipFile) != UNZ_OK)
124 goto cleanup;
126 if (unzOpenCurrentFile (zipFile) != UNZ_OK)
127 goto cleanup;
129 ret = managed_unzip_extract_to_stream (zipFile, dest);
131 cleanup:
132 unzCloseCurrentFile (zipFile);
133 unzClose (zipFile);
135 return ret;
138 gboolean
139 managed_unzip_stream_to_stream (ManagedStreamCallbacks *source, ManagedStreamCallbacks *dest, const char *partname)
141 zlib_filefunc_def funcs;
142 unzFile zipFile;
143 gboolean ret;
145 ret = FALSE;
147 funcs.zopen_file = managed_stream_open;
148 funcs.zread_file = managed_stream_read;
149 funcs.zwrite_file = managed_stream_write;
150 funcs.ztell_file = managed_stream_tell;
151 funcs.zseek_file = managed_stream_seek;
152 funcs.zclose_file = managed_stream_close;
153 funcs.zerror_file = managed_stream_error;
154 funcs.opaque = source;
156 zipFile = unzOpen2 (NULL, &funcs);
158 if (!zipFile)
159 return FALSE;
161 if (unzLocateFile (zipFile, partname, 2) != UNZ_OK)
162 goto cleanup;
164 if (unzOpenCurrentFile (zipFile) != UNZ_OK)
165 goto cleanup;
167 ret = managed_unzip_extract_to_stream (zipFile, dest);
169 cleanup:
170 unzCloseCurrentFile (zipFile);
171 unzClose (zipFile);
173 return ret;
176 gboolean
177 managed_unzip_extract_to_stream (unzFile zipFile, ManagedStreamCallbacks *dest)
179 char buf[4096];
180 int nread;
182 do {
183 if ((nread = unzReadCurrentFile (zipFile, buf, sizeof (buf))) > 0) {
184 dest->Write (dest->handle, buf, 0, nread);
186 } while (nread > 0);
188 return TRUE;
192 * MID:
193 * @lo: the low bound
194 * @hi: the high bound
196 * Finds the midpoint between positive integer values, @lo and @hi.
198 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
199 * when @lo and @hi are sufficiently large enough that combining them
200 * would overflow their integer type. To work around this, we use the
201 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
202 * from occuring.
204 * Returns the midpoint between @lo and @hi (rounded down).
206 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
209 static guint
210 bsearch (GPtrArray *array, bool stable, GCompareFunc cmp, void *item)
212 register guint lo, hi;
213 guint m;
214 int c;
216 if (array->len == 0)
217 return 0;
219 lo = 0, hi = array->len;
221 do {
222 m = MID (lo, hi);
223 if ((c = cmp (&item, &array->pdata[m])) > 0) {
224 lo = m + 1;
225 m = lo;
226 } else if (c < 0) {
227 hi = m;
228 } else if (stable) {
229 lo = m + 1;
230 m = lo;
231 } else {
232 break;
234 } while (lo < hi);
236 return m;
239 void
240 g_ptr_array_insert_sorted (GPtrArray *array, GCompareFunc cmp, void *item)
242 guint index = bsearch (array, true, cmp, item);
244 g_ptr_array_insert (array, index, item);
247 void
248 g_ptr_array_insert (GPtrArray *array, guint index, void *item)
250 guint8 *dest, *src;
251 guint n;
253 if (index >= array->len) {
254 g_ptr_array_add (array, item);
255 return;
258 g_ptr_array_set_size (array, array->len + 1);
260 dest = ((guint8 *) array->pdata) + (sizeof (void *) * (index + 1));
261 src = ((guint8 *) array->pdata) + (sizeof (void *) * index);
262 n = array->len - index - 1;
264 memmove (dest, src, (sizeof (void *) * n));
265 array->pdata[index] = item;
269 write_all (int fd, char *buf, size_t len)
271 size_t nwritten = 0;
272 ssize_t n;
274 do {
275 do {
276 n = write (fd, buf + nwritten, len - nwritten);
277 } while (n == -1 && errno == EINTR);
279 if (n == -1)
280 return -1;
282 nwritten += n;
283 } while (nwritten < len);
285 return 0;
288 static bool
289 is_dll_or_mdb (const char *filename, int n)
291 return n > 4 && (!g_ascii_strcasecmp (filename + (n - 4), ".dll") || !g_ascii_strcasecmp (filename + (n - 4), ".mdb"));
294 const char *
295 CanonicalizeFilename (char *filename, int n, CanonMode mode)
297 char *inptr = filename;
298 char *inend;
300 if (n < 0)
301 n = strlen (filename);
303 inend = inptr + n;
305 if (mode == CanonModeXap && is_dll_or_mdb (filename, n)) {
306 // Note: We don't want to change the casing of the dll's
307 // basename since Mono requires the basename to match the
308 // expected case.
310 inend -= 5;
311 while (inend > inptr && *inend != '\\' && *inend != '/')
312 inend--;
314 if (*inend == '\\') {
315 // include the trailing '\\'
316 inend++;
320 while (inptr < inend) {
321 if (*inptr != '\\') {
322 if (mode != CanonModeNone)
323 *inptr = g_ascii_tolower (*inptr);
324 } else
325 *inptr = G_DIR_SEPARATOR;
327 inptr++;
330 return filename;
333 bool
334 ExtractFile (unzFile zip, int fd)
336 char buf[4096];
337 int nread;
338 ssize_t n;
340 do {
341 n = 0;
342 if ((nread = unzReadCurrentFile (zip, buf, sizeof (buf))) > 0) {
343 if ((n = write_all (fd, buf, nread)) == -1)
344 break;
346 } while (nread > 0);
348 if (nread != 0 || n == -1 /*|| fsync (fd) == -1*/) {
349 close (fd);
351 return false;
354 close (fd);
356 return true;
359 bool
360 ExtractAll (unzFile zip, const char *dir, CanonMode mode)
362 char *filename, *dirname, *path, *altpath;
363 unz_file_info info;
364 int fd;
366 if (unzGoToFirstFile (zip) != UNZ_OK)
367 return false;
369 do {
370 unzGetCurrentFileInfo (zip, &info, NULL, 0, NULL, 0, NULL, 0);
371 if (info.external_fa & (1 << 4))
372 continue;
374 if (!(filename = (char *) g_malloc (info.size_filename + 1)))
375 return false;
377 unzGetCurrentFileInfo (zip, NULL, filename, info.size_filename + 1, NULL, 0, NULL, 0);
379 CanonicalizeFilename (filename, info.size_filename, mode);
381 path = g_build_filename (dir, filename, NULL);
383 dirname = g_path_get_dirname (path);
384 if (g_mkdir_with_parents (dirname, 0700) == -1 && errno != EEXIST) {
385 g_free (filename);
386 g_free (dirname);
387 g_free (path);
388 return false;
391 g_free (dirname);
393 if ((fd = g_open (path, O_CREAT | O_WRONLY | O_TRUNC, 0600)) == -1) {
394 g_free (filename);
395 g_free (path);
396 return false;
399 if (unzOpenCurrentFile (zip) != UNZ_OK) {
400 g_free (filename);
401 g_free (path);
402 close (fd);
403 return false;
406 if (!ExtractFile (zip, fd)) {
407 unzCloseCurrentFile (zip);
408 g_free (filename);
409 g_free (path);
410 return false;
413 unzCloseCurrentFile (zip);
415 if (mode == CanonModeXap && is_dll_or_mdb (filename, info.size_filename)) {
416 CanonicalizeFilename (filename, info.size_filename, CanonModeResource);
417 altpath = g_build_filename (dir, filename, NULL);
418 if (strcmp (path, altpath) != 0)
419 symlink (path, altpath);
420 g_free (altpath);
423 g_free (filename);
424 g_free (path);
425 } while (unzGoToNextFile (zip) == UNZ_OK);
427 return true;
430 char *
431 MakeTempDir (char *tmpdir)
433 char *xxx;
434 int attempts = 0;
435 size_t n;
437 if ((n = strlen (tmpdir)) < 6) {
438 errno = EINVAL;
439 return NULL;
442 xxx = tmpdir + (n - 6);
443 if (strcmp (xxx, "XXXXXX") != 0) {
444 errno = EINVAL;
445 return NULL;
448 do {
450 if (!mktemp (tmpdir))
451 return NULL;
453 if (g_mkdir (tmpdir, 0700) != -1)
454 return tmpdir;
456 if (errno != EEXIST) {
457 // don't bother trying again...
458 return NULL;
461 // that path already exists, try a new one...
462 strcpy (xxx, "XXXXXX");
463 attempts++;
464 } while (attempts < 100);
466 return NULL;
470 static int
471 rmdir_real (GString *path)
473 const gchar *dirname;
474 struct stat st;
475 size_t len;
476 GDir *dir;
478 if (!(dir = g_dir_open (path->str, 0, NULL)))
479 return -1;
481 g_string_append_c (path, G_DIR_SEPARATOR);
482 len = path->len;
484 while ((dirname = g_dir_read_name (dir))) {
485 if (!strcmp (dirname, ".") || !strcmp (dirname, ".."))
486 continue;
488 g_string_truncate (path, len);
489 g_string_append (path, dirname);
491 if (g_lstat (path->str, &st) == -1)
492 continue;
494 if (S_ISDIR (st.st_mode))
495 rmdir_real (path);
496 else
497 g_unlink (path->str);
500 g_dir_close (dir);
502 g_string_truncate (path, len - 1);
504 return g_rmdir (path->str);
508 // Creates a temporary directory, based on the @filename template
510 // Returns: a g-allocated string name that points to the created
511 // directory, or NULL on failure
513 char *
514 CreateTempDir (const char *filename)
516 const char *name;
517 char *path, *buf;
519 if (!(name = strrchr (filename, '/')))
520 name = filename;
521 else
522 name++;
524 buf = g_strdup_printf ("%s.XXXXXX", name);
526 if (Application::GetCurrent())
527 path = g_build_filename (Application::GetCurrent()->GetResourceRoot(), buf, NULL);
528 else {
529 path = g_build_filename (g_get_tmp_dir (), buf, NULL);
530 Deployment::GetCurrent()->TrackPath (path);
532 g_free (buf);
534 if (!MakeTempDir (path)) {
535 g_free (path);
536 return NULL;
539 return path;
543 RemoveDir (const char *dir)
545 GString *path;
546 int rv;
548 path = g_string_new (dir);
549 rv = rmdir_real (path);
550 g_string_free (path, true);
552 return rv;
556 CopyFileTo (const char *filename, int fd)
558 char buf[4096];
559 ssize_t nread;
560 int in;
562 if ((in = g_open (filename, O_RDONLY)) == -1) {
563 close (fd);
564 return -1;
567 do {
568 do {
569 nread = read (in, buf, sizeof (buf));
570 } while (nread == -1 && errno == EINTR);
572 if (nread == -1)
573 goto exception;
575 if (nread == 0)
576 break;
578 if (write_all (fd, buf, nread) == -1)
579 goto exception;
580 } while (true);
582 if (fsync (fd) == -1)
583 goto exception;
585 close (in);
586 close (fd);
588 return 0;
590 exception:
592 close (in);
593 close (fd);
595 return -1;
598 cairo_t*
599 measuring_context_create (void)
601 cairo_surface_t* surf = cairo_image_surface_create (CAIRO_FORMAT_A1, 1, 1);
602 return cairo_create (surf);
605 void
606 measuring_context_destroy (cairo_t *cr)
608 cairo_surface_destroy (cairo_get_target (cr));
609 cairo_destroy (cr);
613 static ssize_t
614 read_internal (int fd, char *buf, size_t n)
616 ssize_t nread;
618 do {
619 nread = read (fd, buf, n);
620 } while (nread == -1 && errno == EINTR);
622 return nread;
626 TextStream::TextStream ()
628 cd = (GIConv) -1;
629 bufptr = buffer;
630 buflen = 0;
631 fd = -1;
633 eof = true;
636 TextStream::~TextStream ()
638 if (fd != -1)
639 close (fd);
641 if (cd != (GIConv) -1) {
642 g_iconv_close (cd);
643 cd = (GIConv) -1;
647 #define BOM ((gunichar2) 0xFEFF)
648 #define ANTIBOM ((gunichar2) 0xFFFE)
650 enum Encoding {
651 UTF16_BE,
652 UTF16_LE,
653 UTF32_BE,
654 UTF32_LE,
655 UTF8,
656 UNKNOWN,
659 static const char *encoding_names[] = { "UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE", "UTF-8" };
662 bool
663 TextStream::OpenBuffer (const char *buf, int size)
665 fmode = false;
667 textbufptr = textbuf = (char *) buf;
668 textbufsize = size;
670 if (size > 0)
671 eof = false;
673 return ReadBOM (false);
676 bool
677 TextStream::OpenFile (const char *filename, bool force)
679 fmode = true;
681 if (fd != -1)
682 Close ();
684 if ((fd = g_open (filename, O_RDONLY)) == -1)
685 return false;
687 return ReadBOM (force);
690 bool
691 TextStream::ReadBOM (bool force)
693 Encoding encoding = UNKNOWN;
694 gunichar2 bom;
695 ssize_t nread;
697 // prefetch the first chunk of data in order to determine encoding
698 if ((nread = ReadInternal (buffer, sizeof (buffer))) == -1) {
699 Close ();
701 return false;
704 bufptr = buffer;
705 buflen = nread;
707 if (nread >= 2) {
708 memcpy (&bom, buffer, 2);
709 switch (bom) {
710 case ANTIBOM:
711 encoding = UTF16_BE;
712 buflen -= 2;
713 bufptr += 2;
714 break;
715 case BOM:
716 encoding = UTF16_LE;
717 buflen -= 2;
718 bufptr += 2;
719 break;
720 case 0:
721 if (nread >= 4) {
722 memcpy (&bom, buffer + 2, 2);
723 if (bom == ANTIBOM) {
724 encoding = UTF32_BE;
725 buflen -= 4;
726 bufptr += 4;
727 } else if (bom == BOM) {
728 encoding = UTF32_LE;
729 buflen -= 4;
730 bufptr += 4;
733 break;
734 default:
735 encoding = UTF8;
736 break;
738 } else {
739 // assume utf-8
740 encoding = UTF8;
743 if (encoding == UNKNOWN) {
744 if (!force) {
745 Close ();
747 return false;
750 encoding = UTF8;
753 if (encoding != UTF8 && (cd = g_iconv_open ("UTF-8", encoding_names[encoding])) == (GIConv) -1) {
754 Close ();
756 return false;
759 eof = false;
761 return true;
764 void
765 TextStream::Close ()
767 if (fd != -1) {
768 close (fd);
769 fd = -1;
772 if (cd != (GIConv) -1) {
773 g_iconv_close (cd);
774 cd = (GIConv) -1;
777 bufptr = buffer;
778 buflen = 0;
779 eof = true;
782 bool
783 TextStream::Eof ()
785 return eof && buflen == 0;
788 ssize_t
789 TextStream::Read (char *buf, size_t n)
791 size_t inleft = buflen;
792 char *inbuf = bufptr;
793 char *outbuf = buf;
794 size_t outleft = n;
795 ssize_t nread;
796 size_t r;
798 do {
799 if (cd != (GIConv) -1) {
800 if (g_iconv (cd, &inbuf, &inleft, &outbuf, &outleft) == (size_t) -1) {
801 switch (errno) {
802 case E2BIG:
803 // not enough space available in the output buffer
804 goto out;
805 case EINVAL:
806 // incomplete multibyte character sequence
807 goto out;
808 case EILSEQ:
809 // illegal multibyte sequence
810 return -1;
811 default:
812 // unknown error, fail
813 return -1;
816 } else {
817 r = MIN (inleft, outleft);
818 memcpy (outbuf, inbuf, r);
819 outleft -= r;
820 outbuf += r;
821 inleft -= r;
822 inbuf += r;
825 if (outleft == 0 || eof)
826 break;
828 // buffer more data
829 if (inleft > 0)
830 memmove (buffer, inbuf, inleft);
832 inbuf = buffer + inleft;
833 if ((nread = ReadInternal (inbuf, sizeof (buffer) - inleft)) <= 0) {
834 eof = true;
835 break;
838 inleft += nread;
839 inbuf = buffer;
840 } while (true);
842 if (eof && cd != (GIConv) -1)
843 g_iconv (cd, NULL, NULL, &outbuf, &outleft);
845 out:
847 buflen = inleft;
848 bufptr = inbuf;
850 return (outbuf - buf);
853 ssize_t
854 TextStream::ReadInternal (char *buffer, ssize_t size)
856 if (fmode) {
857 return read_internal (fd, buffer, size);
858 } else {
859 ssize_t nread = size;
861 if (eof)
862 return -1;
864 if (textbufptr + size > textbuf + textbufsize) {
865 eof = true;
866 nread = textbuf + textbufsize - textbufptr;
868 memcpy (buffer, textbufptr, nread);
870 textbufptr += nread;
872 return nread;
877 GArray *double_garray_from_str (const char *s, gint max)
879 char *next = (char *)s;
880 GArray *values = g_array_sized_new (false, true, sizeof (double), max > 0 ? max : 16);
881 double coord = 0.0;
882 guint end = max > 0 ? max : G_MAXINT;
884 while (next && values->len < end) {
885 while (g_ascii_isspace (*next) || *next == ',')
886 next = g_utf8_next_char (next);
888 if (next) {
889 errno = 0;
890 char *prev = next;
891 coord = g_ascii_strtod (prev, &next);
892 if (errno != 0 || next == prev)
893 goto error;
895 g_array_append_val (values, coord);
899 error:
900 while (values->len < (guint) max) {
901 coord = 0.0;
902 g_array_append_val (values, coord);
905 return values;
908 char * parse_rfc_1945_quoted_string (char *input, char *c, char **end)
910 char *start;
912 if (input == NULL || input [0] != '"')
913 return NULL;
915 *end = NULL;
917 input++;
918 start = input;
920 do {
922 * We're parsing a quoted-string according to RFC 1945:
924 * quoted-string = ( <"> *(qdtext) <"> )
926 * qdtext = <any CHAR except <"> and CTLs,
927 * but including LWS>
929 * LWS = [CRLF] 1*( SP | HT )
930 * CTL = <any US-ASCII control character
931 * (octets 0 - 31) and DEL (127)>
934 char h = *input;
935 *c = h;
937 // LWS
938 if (h == 10 || h == 13 || h == ' ' || h == 9)
939 continue;
941 // CTL
942 if (h == 0)
943 return start;
945 if ((h > 0 && h <= 31) || h == 127) {
946 *end = input + 1;
947 *input = 0;
948 return start;
951 // quote
952 if (h == '"') {
953 *end = input + 1;
954 *input = 0;
955 return start;
958 } while (*(input++));
960 return start;
963 char *parse_rfc_1945_token (char *input, char *c, char **end)
965 char *start = input;
966 bool first = false;
968 if (input == NULL || c == NULL || end == NULL)
969 return NULL;
971 *c = 0;
972 *end = NULL;
975 do {
977 * We're parsing a token according to RFC 1945:
979 * token = 1*<any CHAR except CTLs or tspecials>
981 * tspecials = "(" | ")" | "<" | ">" | "@"
982 * | "," | ";" | ":" | "\" | <">
983 * | "/" | "[" | "]" | "?" | "="
984 * | "{" | "}" | SP | HT
986 * CTL = <any US-ASCII control character
987 * (octets 0 - 31) and DEL (127)>
988 * SP = <US-ASCII SP, space (32)>
989 * HT = <US-ASCII HT, horizontal-tab (9)>
992 char h = *input;
993 *c = h;
995 // CTL
996 if (h == 0)
997 return start;
999 if ((h > 0 && h <= 31) || h == 127) {
1000 if (!first) {
1001 start = input + 1;
1002 continue;
1004 *end = input + 1;
1005 *input = 0;
1006 continue;
1009 // tspecials
1010 switch (h) {
1011 case '(':
1012 case ')':
1013 case '<':
1014 case '>':
1015 case '@':
1016 case ',':
1017 case ';':
1018 case ':':
1019 case '\\':
1020 case '"':
1021 case '/':
1022 case '[':
1023 case ']':
1024 case '?':
1025 case '=':
1026 case '{':
1027 case '}':
1028 case 32:
1029 case 9:
1030 if (!first) {
1031 start = input + 1;
1032 continue;
1034 *end = input + 1;
1035 *input = 0;
1036 return start;
1038 first = true;
1039 } while (*(input++));
1042 // reached the end of the input, only one token
1043 return start;
1046 Cancellable::Cancellable ()
1048 cancel_cb = NULL;
1049 downloader = NULL;
1052 Cancellable::~Cancellable ()
1054 if (downloader)
1055 downloader->unref ();
1058 void
1059 Cancellable::Cancel ()
1061 if (cancel_cb)
1062 cancel_cb (downloader, context);
1065 void
1066 Cancellable::SetCancelFuncAndData (CancelCallback cb, Downloader *downloader, void *_context)
1068 cancel_cb = cb;
1069 context = _context;
1070 if (this->downloader)
1071 this->downloader->unref ();
1072 this->downloader = downloader;
1073 if (this->downloader)
1074 this->downloader->ref ();