]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
Embedding feature patch 1: add zipFiles and unzipToDir to support
[lyx.git] / src / support / filetools.cpp
1 /**
2  * \file filetools.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "support/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/fs_extras.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30 #include "support/os.h"
31 #include "support/Package.h"
32 #include "support/Path.h"
33 #include "support/Systemcall.h"
34
35 // FIXME Interface violation
36 #include "gettext.h"
37 #include "debug.h"
38
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
42
43 #include <fcntl.h>
44
45 #include <cerrno>
46 #include <cstdlib>
47 #include <cstdio>
48
49 #include <utility>
50 #include <fstream>
51 #include <sstream>
52
53 #ifdef unix
54 # include <unistd.h>
55 # include <utime.h>
56 # include <sys/types.h>
57 # include <sys/stat.h>
58 #else
59 # include <direct.h>
60 # include <io.h>
61 #endif
62
63 #include "zip.h"
64 #include "unzip.h"
65
66 #ifdef WIN32
67 #define USEWIN32IOAPI
68 #include "iowin32.h"
69 #endif
70
71 #define WRITEBUFFERSIZE (16384)
72 #define MAXFILENAME (256)
73
74
75 #ifndef CXX_GLOBAL_CSTD
76 using std::fgetc;
77 #endif
78
79 using std::endl;
80 using std::getline;
81 using std::make_pair;
82 using std::string;
83 using std::ifstream;
84 using std::ostringstream;
85 using std::vector;
86 using std::pair;
87
88 namespace fs = boost::filesystem;
89
90 namespace lyx {
91 namespace support {
92
93 bool isLyXFilename(string const & filename)
94 {
95         return suffixIs(ascii_lowercase(filename), ".lyx");
96 }
97
98
99 bool isSGMLFilename(string const & filename)
100 {
101         return suffixIs(ascii_lowercase(filename), ".sgml");
102 }
103
104
105 bool isValidLaTeXFilename(string const & filename)
106 {
107         string const invalid_chars("#$%{}()[]\"^");
108         if (filename.find_first_of(invalid_chars) != string::npos)
109                 return false;
110         else
111                 return true;
112 }
113
114
115 string const latex_path(string const & original_path,
116                 latex_path_extension extension,
117                 latex_path_dots dots)
118 {
119         // On cygwin, we may need windows or posix style paths.
120         string path = os::latex_path(original_path);
121         path = subst(path, "~", "\\string~");
122         if (path.find(' ') != string::npos) {
123                 // We can't use '"' because " is sometimes active (e.g. if
124                 // babel is loaded with the "german" option)
125                 if (extension == EXCLUDE_EXTENSION) {
126                         // ChangeExtension calls os::internal_path internally
127                         // so don't use it to remove the extension.
128                         string const ext = getExtension(path);
129                         string const base = ext.empty() ?
130                                 path :
131                                 path.substr(0, path.length() - ext.length() - 1);
132                         // ChangeExtension calls os::internal_path internally
133                         // so don't use it to re-add the extension.
134                         path = "\\string\"" + base + "\\string\"." + ext;
135                 } else {
136                         path = "\\string\"" + path + "\\string\"";
137                 }
138         }
139
140         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
141 }
142
143
144 // Substitutes spaces with underscores in filename (and path)
145 string const makeLatexName(string const & file)
146 {
147         string name = onlyFilename(file);
148         string const path = onlyPath(file);
149
150         // ok so we scan through the string twice, but who cares.
151         string const keep = "abcdefghijklmnopqrstuvwxyz"
152                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
153                 "@!'()*+,-./0123456789:;<=>?[]`|";
154
155         string::size_type pos = 0;
156         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
157                 name[pos++] = '_';
158
159         return addName(path, name);
160 }
161
162
163 string const quoteName(string const & name, quote_style style)
164 {
165         switch(style) {
166         case quote_shell:
167                 // This does not work for filenames containing " (windows)
168                 // or ' (all other OSes). This can't be changed easily, since
169                 // we would need to adapt the command line parser in
170                 // Forkedcall::generateChild. Therefore we don't pass user
171                 // filenames to child processes if possible. We store them in
172                 // a python script instead, where we don't have these
173                 // limitations.
174                 return (os::shell() == os::UNIX) ?
175                         '\'' + name + '\'':
176                         '"' + name + '"';
177         case quote_python:
178                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
179                      + "\"";
180         }
181         // shut up stupid compiler
182         return string();
183 }
184
185
186 bool isFileReadable(FileName const & filename)
187 {
188         std::string const path = filename.toFilesystemEncoding();
189         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
190 }
191
192
193 //returns true: dir writeable
194 //        false: not writeable
195 bool isDirWriteable(FileName const & path)
196 {
197         LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
198
199         FileName const tmpfl(tempName(path, "lyxwritetest"));
200
201         if (tmpfl.empty())
202                 return false;
203
204         unlink(tmpfl);
205         return true;
206 }
207
208
209 #if 0
210 // Uses a string of paths separated by ";"s to find a file to open.
211 // Can't cope with pathnames with a ';' in them. Returns full path to file.
212 // If path entry begins with $$LyX/, use system_lyxdir
213 // If path entry begins with $$User/, use user_lyxdir
214 // Example: "$$User/doc;$$LyX/doc"
215 FileName const fileOpenSearch(string const & path, string const & name,
216                              string const & ext)
217 {
218         FileName real_file;
219         string path_element;
220         bool notfound = true;
221         string tmppath = split(path, path_element, ';');
222
223         while (notfound && !path_element.empty()) {
224                 path_element = os::internal_path(path_element);
225                 if (!suffixIs(path_element, '/'))
226                         path_element += '/';
227                 path_element = subst(path_element, "$$LyX",
228                                      package().system_support().absFilename());
229                 path_element = subst(path_element, "$$User",
230                                      package().user_support().absFilename());
231
232                 real_file = fileSearch(path_element, name, ext);
233
234                 if (real_file.empty()) {
235                         do {
236                                 tmppath = split(tmppath, path_element, ';');
237                         } while (!tmppath.empty() && path_element.empty());
238                 } else {
239                         notfound = false;
240                 }
241         }
242         return real_file;
243 }
244 #endif
245
246
247 /// Returns a vector of all files in directory dir having extension ext.
248 vector<FileName> const dirList(FileName const & dir, string const & ext)
249 {
250         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
251         vector<FileName> dirlist;
252
253         string const encoded_dir = dir.toFilesystemEncoding();
254         if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
255                 LYXERR(Debug::FILES)
256                         << "Directory \"" << dir
257                         << "\" does not exist to DirList." << endl;
258                 return dirlist;
259         }
260
261         string extension;
262         if (!ext.empty() && ext[0] != '.')
263                 extension += '.';
264         extension += ext;
265
266         fs::directory_iterator dit(encoded_dir);
267         fs::directory_iterator end;
268         for (; dit != end; ++dit) {
269                 string const & fil = dit->leaf();
270                 if (suffixIs(fil, extension))
271                         dirlist.push_back(FileName::fromFilesystemEncoding(
272                                         encoded_dir + '/' + fil));
273         }
274         return dirlist;
275 }
276
277
278 // Returns the real name of file name in directory path, with optional
279 // extension ext.
280 FileName const fileSearch(string const & path, string const & name,
281                           string const & ext, search_mode mode)
282 {
283         // if `name' is an absolute path, we ignore the setting of `path'
284         // Expand Environmentvariables in 'name'
285         string const tmpname = replaceEnvironmentPath(name);
286         FileName fullname(makeAbsPath(tmpname, path));
287         // search first without extension, then with it.
288         if (isFileReadable(fullname))
289                 return fullname;
290         if (ext.empty())
291                 // We are done.
292                 return mode == allow_unreadable ? fullname : FileName();
293         // Only add the extension if it is not already the extension of
294         // fullname.
295         if (getExtension(fullname.absFilename()) != ext)
296                 fullname = FileName(addExtension(fullname.absFilename(), ext));
297         if (isFileReadable(fullname) || mode == allow_unreadable)
298                 return fullname;
299         return FileName();
300 }
301
302
303 // Search the file name.ext in the subdirectory dir of
304 //   1) user_lyxdir
305 //   2) build_lyxdir (if not empty)
306 //   3) system_lyxdir
307 FileName const libFileSearch(string const & dir, string const & name,
308                            string const & ext)
309 {
310         FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
311                                      name, ext);
312         if (!fullname.empty())
313                 return fullname;
314
315         if (!package().build_support().empty())
316                 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
317                                       name, ext);
318         if (!fullname.empty())
319                 return fullname;
320
321         return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
322 }
323
324
325 FileName const i18nLibFileSearch(string const & dir, string const & name,
326                   string const & ext)
327 {
328         /* The highest priority value is the `LANGUAGE' environment
329            variable. But we don't use the value if the currently
330            selected locale is the C locale. This is a GNU extension.
331
332            Otherwise, w use a trick to guess what gettext has done:
333            each po file is able to tell us its name. (JMarc)
334         */
335
336         string lang = to_ascii(_("[[Replace with the code of your language]]"));
337         string const language = getEnv("LANGUAGE");
338         if (!lang.empty() && !language.empty())
339                 lang = language;
340
341         string l;
342         lang = split(lang, l, ':');
343         while (!l.empty()) {
344                 FileName tmp;
345                 // First try with the full name
346                 tmp = libFileSearch(addPath(dir, l), name, ext);
347                 if (!tmp.empty())
348                         return tmp;
349
350                 // Then the name without country code
351                 string const shortl = token(l, '_', 0);
352                 if (shortl != l) {
353                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
354                         if (!tmp.empty())
355                                 return tmp;
356                 }
357
358 #if 1
359                 // For compatibility, to be removed later (JMarc)
360                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
361                                     ext);
362                 if (!tmp.empty()) {
363                         lyxerr << "i18nLibFileSearch: File `" << tmp
364                                << "' has been found by the old method" <<endl;
365                         return tmp;
366                 }
367 #endif
368                 lang = split(lang, l, ':');
369         }
370
371         return libFileSearch(dir, name, ext);
372 }
373
374
375 string const libScriptSearch(string const & command_in, quote_style style)
376 {
377         static string const token_scriptpath = "$$s/";
378
379         string command = command_in;
380         // Find the starting position of "$$s/"
381         string::size_type const pos1 = command.find(token_scriptpath);
382         if (pos1 == string::npos)
383                 return command;
384         // Find the end of the "$$s/some_subdir/some_script" word within
385         // command. Assumes that the script name does not contain spaces.
386         string::size_type const start_script = pos1 + 4;
387         string::size_type const pos2 = command.find(' ', start_script);
388         string::size_type const size_script = pos2 == string::npos?
389                 (command.size() - start_script) : pos2 - start_script;
390
391         // Does this script file exist?
392         string const script =
393                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
394
395         if (script.empty()) {
396                 // Replace "$$s/" with ""
397                 command.erase(pos1, 4);
398         } else {
399                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
400                 string::size_type const size_replace = size_script + 4;
401                 command.replace(pos1, size_replace, quoteName(script, style));
402         }
403
404         return command;
405 }
406
407
408 namespace {
409
410 FileName const createTmpDir(FileName const & tempdir, string const & mask)
411 {
412         LYXERR(Debug::FILES)
413                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
414                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
415
416         FileName const tmpfl(tempName(tempdir, mask));
417         // lyx::tempName actually creates a file to make sure that it
418         // stays unique. So we have to delete it before we can create
419         // a dir with the same name. Note also that we are not thread
420         // safe because of the gap between unlink and mkdir. (Lgb)
421         unlink(tmpfl);
422
423         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
424                 lyxerr << "LyX could not create the temporary directory '"
425                        << tmpfl << "'" << endl;
426                 return FileName();
427         }
428
429         return tmpfl;
430 }
431
432 } // namespace anon
433
434
435 bool destroyDir(FileName const & tmpdir)
436 {
437         try {
438                 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
439         } catch (fs::filesystem_error const & fe){
440                 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
441                 return false;
442         }
443 }
444
445
446 string const createBufferTmpDir()
447 {
448         static int count;
449         // We are in our own directory.  Why bother to mangle name?
450         // In fact I wrote this code to circumvent a problematic behaviour
451         // (bug?) of EMX mkstemp().
452         string const tmpfl =
453                 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
454                 convert<string>(count++);
455
456         if (mkdir(FileName(tmpfl), 0777)) {
457                 lyxerr << "LyX could not create the temporary directory '"
458                        << tmpfl << "'" << endl;
459                 return string();
460         }
461         return tmpfl;
462 }
463
464
465 FileName const createLyXTmpDir(FileName const & deflt)
466 {
467         if (!deflt.empty() && deflt.absFilename() != "/tmp") {
468                 if (mkdir(deflt, 0777)) {
469                         if (isDirWriteable(deflt)) {
470                                 // deflt could not be created because it
471                                 // did exist already, so let's create our own
472                                 // dir inside deflt.
473                                 return createTmpDir(deflt, "lyx_tmpdir");
474                         } else {
475                                 // some other error occured.
476                                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
477                         }
478                 } else
479                         return deflt;
480         } else {
481                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
482         }
483 }
484
485
486 bool createDirectory(FileName const & path, int permission)
487 {
488         BOOST_ASSERT(!path.empty());
489         return mkdir(path, permission) == 0;
490 }
491
492
493 // Strip filename from path name
494 string const onlyPath(string const & filename)
495 {
496         // If empty filename, return empty
497         if (filename.empty())
498                 return filename;
499
500         // Find last / or start of filename
501         string::size_type j = filename.rfind('/');
502         return j == string::npos ? "./" : filename.substr(0, j + 1);
503 }
504
505
506 // Convert relative path into absolute path based on a basepath.
507 // If relpath is absolute, just use that.
508 // If basepath is empty, use CWD as base.
509 FileName const makeAbsPath(string const & relPath, string const & basePath)
510 {
511         // checks for already absolute path
512         if (os::is_absolute_path(relPath))
513                 return FileName(relPath);
514
515         // Copies given paths
516         string tempRel = os::internal_path(relPath);
517         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
518         tempRel = subst(tempRel, "//", "/");
519
520         string tempBase;
521
522         if (os::is_absolute_path(basePath))
523                 tempBase = basePath;
524         else
525                 tempBase = addPath(getcwd().absFilename(), basePath);
526
527         // Handle /./ at the end of the path
528         while (suffixIs(tempBase, "/./"))
529                 tempBase.erase(tempBase.length() - 2);
530
531         // processes relative path
532         string rTemp = tempRel;
533         string temp;
534
535         while (!rTemp.empty()) {
536                 // Split by next /
537                 rTemp = split(rTemp, temp, '/');
538
539                 if (temp == ".") continue;
540                 if (temp == "..") {
541                         // Remove one level of TempBase
542                         string::difference_type i = tempBase.length() - 2;
543                         if (i < 0)
544                                 i = 0;
545                         while (i > 0 && tempBase[i] != '/')
546                                 --i;
547                         if (i > 0)
548                                 tempBase.erase(i, string::npos);
549                         else
550                                 tempBase += '/';
551                 } else if (temp.empty() && !rTemp.empty()) {
552                                 tempBase = os::current_root() + rTemp;
553                                 rTemp.erase();
554                 } else {
555                         // Add this piece to TempBase
556                         if (!suffixIs(tempBase, '/'))
557                                 tempBase += '/';
558                         tempBase += temp;
559                 }
560         }
561
562         // returns absolute path
563         return FileName(os::internal_path(tempBase));
564 }
565
566
567 // Correctly append filename to the pathname.
568 // If pathname is '.', then don't use pathname.
569 // Chops any path of filename.
570 string const addName(string const & path, string const & fname)
571 {
572         string const basename = onlyFilename(fname);
573         string buf;
574
575         if (path != "." && path != "./" && !path.empty()) {
576                 buf = os::internal_path(path);
577                 if (!suffixIs(path, '/'))
578                         buf += '/';
579         }
580
581         return buf + basename;
582 }
583
584
585 // Strips path from filename
586 string const onlyFilename(string const & fname)
587 {
588         if (fname.empty())
589                 return fname;
590
591         string::size_type j = fname.rfind('/');
592         if (j == string::npos) // no '/' in fname
593                 return fname;
594
595         // Strip to basename
596         return fname.substr(j + 1);
597 }
598
599
600 /// Returns true is path is absolute
601 bool absolutePath(string const & path)
602 {
603         return os::is_absolute_path(path);
604 }
605
606
607 // Create absolute path. If impossible, don't do anything
608 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
609 string const expandPath(string const & path)
610 {
611         // checks for already absolute path
612         string rTemp = replaceEnvironmentPath(path);
613         if (os::is_absolute_path(rTemp))
614                 return rTemp;
615
616         string temp;
617         string const copy = rTemp;
618
619         // Split by next /
620         rTemp = split(rTemp, temp, '/');
621
622         if (temp == ".")
623                 return getcwd().absFilename() + '/' + rTemp;
624
625         if (temp == "~")
626                 return package().home_dir().absFilename() + '/' + rTemp;
627
628         if (temp == "..")
629                 return makeAbsPath(copy).absFilename();
630
631         // Don't know how to handle this
632         return copy;
633 }
634
635
636 // Normalize a path. Constracts path/../path
637 // Can't handle "../../" or "/../" (Asger)
638 // Also converts paths like /foo//bar ==> /foo/bar
639 string const normalizePath(string const & path)
640 {
641         // Normalize paths like /foo//bar ==> /foo/bar
642         static boost::regex regex("/{2,}");
643         string const tmppath = boost::regex_merge(path, regex, "/");
644
645         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
646
647         if (!npath.is_complete())
648                 return "./" + npath.string() + '/';
649
650         return npath.string() + '/';
651 }
652
653
654 string const getFileContents(FileName const & fname)
655 {
656         string const encodedname = fname.toFilesystemEncoding();
657         if (fs::exists(encodedname)) {
658                 ifstream ifs(encodedname.c_str());
659                 ostringstream ofs;
660                 if (ifs && ofs) {
661                         ofs << ifs.rdbuf();
662                         ifs.close();
663                         return ofs.str();
664                 }
665         }
666         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
667         return string();
668 }
669
670
671 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
672 string const replaceEnvironmentPath(string const & path)
673 {
674         // ${VAR} is defined as
675         // $\{[A-Za-z_][A-Za-z_0-9]*\}
676         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
677
678         // $VAR is defined as:
679         // $\{[A-Za-z_][A-Za-z_0-9]*\}
680         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
681
682         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
683         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
684         boost::smatch what;
685
686         string result = path;
687         while (1) {
688                 regex_match(result, what, envvar_br_re);
689                 if (!what[0].matched) {
690                         regex_match(result, what, envvar_re);
691                         if (!what[0].matched)
692                                 break;
693                 }
694                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
695         }
696         return result;
697 }
698
699
700 // Make relative path out of two absolute paths
701 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
702 // Makes relative path out of absolute path. If it is deeper than basepath,
703 // it's easy. If basepath and abspath share something (they are all deeper
704 // than some directory), it'll be rendered using ..'s. If they are completely
705 // different, then the absolute path will be used as relative path.
706 {
707         docstring::size_type const abslen = abspath.length();
708         docstring::size_type const baselen = basepath.length();
709
710         docstring::size_type i = os::common_path(abspath, basepath);
711
712         if (i == 0) {
713                 // actually no match - cannot make it relative
714                 return abspath;
715         }
716
717         // Count how many dirs there are in basepath above match
718         // and append as many '..''s into relpath
719         docstring buf;
720         docstring::size_type j = i;
721         while (j < baselen) {
722                 if (basepath[j] == '/') {
723                         if (j + 1 == baselen)
724                                 break;
725                         buf += "../";
726                 }
727                 ++j;
728         }
729
730         // Append relative stuff from common directory to abspath
731         if (abspath[i] == '/')
732                 ++i;
733         for (; i < abslen; ++i)
734                 buf += abspath[i];
735         // Remove trailing /
736         if (suffixIs(buf, '/'))
737                 buf.erase(buf.length() - 1);
738         // Substitute empty with .
739         if (buf.empty())
740                 buf = '.';
741         return buf;
742 }
743
744
745 // Append sub-directory(ies) to a path in an intelligent way
746 string const addPath(string const & path, string const & path_2)
747 {
748         string buf;
749         string const path2 = os::internal_path(path_2);
750
751         if (!path.empty() && path != "." && path != "./") {
752                 buf = os::internal_path(path);
753                 if (path[path.length() - 1] != '/')
754                         buf += '/';
755         }
756
757         if (!path2.empty()) {
758                 string::size_type const p2start = path2.find_first_not_of('/');
759                 string::size_type const p2end = path2.find_last_not_of('/');
760                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
761                 buf += tmp + '/';
762         }
763         return buf;
764 }
765
766
767 string const changeExtension(string const & oldname, string const & extension)
768 {
769         string::size_type const last_slash = oldname.rfind('/');
770         string::size_type last_dot = oldname.rfind('.');
771         if (last_dot < last_slash && last_slash != string::npos)
772                 last_dot = string::npos;
773
774         string ext;
775         // Make sure the extension starts with a dot
776         if (!extension.empty() && extension[0] != '.')
777                 ext= '.' + extension;
778         else
779                 ext = extension;
780
781         return os::internal_path(oldname.substr(0, last_dot) + ext);
782 }
783
784
785 string const removeExtension(string const & name)
786 {
787         return changeExtension(name, string());
788 }
789
790
791 string const addExtension(string const & name, string const & extension)
792 {
793         if (!extension.empty() && extension[0] != '.')
794                 return name + '.' + extension;
795         return name + extension;
796 }
797
798
799 /// Return the extension of the file (not including the .)
800 string const getExtension(string const & name)
801 {
802         string::size_type const last_slash = name.rfind('/');
803         string::size_type const last_dot = name.rfind('.');
804         if (last_dot != string::npos &&
805             (last_slash == string::npos || last_dot > last_slash))
806                 return name.substr(last_dot + 1,
807                                    name.length() - (last_dot + 1));
808         else
809                 return string();
810 }
811
812
813 // the different filetypes and what they contain in one of the first lines
814 // (dots are any characters).           (Herbert 20020131)
815 // AGR  Grace...
816 // BMP  BM...
817 // EPS  %!PS-Adobe-3.0 EPSF...
818 // FIG  #FIG...
819 // FITS ...BITPIX...
820 // GIF  GIF...
821 // JPG  JFIF
822 // PDF  %PDF-...
823 // PNG  .PNG...
824 // PBM  P1... or P4     (B/W)
825 // PGM  P2... or P5     (Grayscale)
826 // PPM  P3... or P6     (color)
827 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
828 // SGI  \001\332...     (decimal 474)
829 // TGIF %TGIF...
830 // TIFF II... or MM...
831 // XBM  ..._bits[]...
832 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
833 //      ...static char *...
834 // XWD  \000\000\000\151        (0x00006900) decimal 105
835 //
836 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
837 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
838 // Z    \037\235                UNIX compress
839
840 string const getFormatFromContents(FileName const & filename)
841 {
842         // paranoia check
843         if (filename.empty() || !isFileReadable(filename))
844                 return string();
845
846         ifstream ifs(filename.toFilesystemEncoding().c_str());
847         if (!ifs)
848                 // Couldn't open file...
849                 return string();
850
851         // gnuzip
852         static string const gzipStamp = "\037\213";
853
854         // PKZIP
855         static string const zipStamp = "PK";
856
857         // compress
858         static string const compressStamp = "\037\235";
859
860         // Maximum strings to read
861         int const max_count = 50;
862         int count = 0;
863
864         string str;
865         string format;
866         bool firstLine = true;
867         while ((count++ < max_count) && format.empty()) {
868                 if (ifs.eof()) {
869                         LYXERR(Debug::GRAPHICS)
870                                 << "filetools(getFormatFromContents)\n"
871                                 << "\tFile type not recognised before EOF!"
872                                 << endl;
873                         break;
874                 }
875
876                 getline(ifs, str);
877                 string const stamp = str.substr(0, 2);
878                 if (firstLine && str.size() >= 2) {
879                         // at first we check for a zipped file, because this
880                         // information is saved in the first bytes of the file!
881                         // also some graphic formats which save the information
882                         // in the first line, too.
883                         if (prefixIs(str, gzipStamp)) {
884                                 format =  "gzip";
885
886                         } else if (stamp == zipStamp) {
887                                 format =  "zip";
888
889                         } else if (stamp == compressStamp) {
890                                 format =  "compress";
891
892                         // the graphics part
893                         } else if (stamp == "BM") {
894                                 format =  "bmp";
895
896                         } else if (stamp == "\001\332") {
897                                 format =  "sgi";
898
899                         // PBM family
900                         // Don't need to use str.at(0), str.at(1) because
901                         // we already know that str.size() >= 2
902                         } else if (str[0] == 'P') {
903                                 switch (str[1]) {
904                                 case '1':
905                                 case '4':
906                                         format =  "pbm";
907                                     break;
908                                 case '2':
909                                 case '5':
910                                         format =  "pgm";
911                                     break;
912                                 case '3':
913                                 case '6':
914                                         format =  "ppm";
915                                 }
916                                 break;
917
918                         } else if ((stamp == "II") || (stamp == "MM")) {
919                                 format =  "tiff";
920
921                         } else if (prefixIs(str,"%TGIF")) {
922                                 format =  "tgif";
923
924                         } else if (prefixIs(str,"#FIG")) {
925                                 format =  "fig";
926
927                         } else if (prefixIs(str,"GIF")) {
928                                 format =  "gif";
929
930                         } else if (str.size() > 3) {
931                                 int const c = ((str[0] << 24) & (str[1] << 16) &
932                                                (str[2] << 8)  & str[3]);
933                                 if (c == 105) {
934                                         format =  "xwd";
935                                 }
936                         }
937
938                         firstLine = false;
939                 }
940
941                 if (!format.empty())
942                     break;
943                 else if (contains(str,"EPSF"))
944                         // dummy, if we have wrong file description like
945                         // %!PS-Adobe-2.0EPSF"
946                         format =  "eps";
947
948                 else if (contains(str,"Grace"))
949                         format =  "agr";
950
951                 else if (contains(str,"JFIF"))
952                         format =  "jpg";
953
954                 else if (contains(str,"%PDF"))
955                         format =  "pdf";
956
957                 else if (contains(str,"PNG"))
958                         format =  "png";
959
960                 else if (contains(str,"%!PS-Adobe")) {
961                         // eps or ps
962                         ifs >> str;
963                         if (contains(str,"EPSF"))
964                                 format = "eps";
965                         else
966                             format = "ps";
967                 }
968
969                 else if (contains(str,"_bits[]"))
970                         format = "xbm";
971
972                 else if (contains(str,"XPM") || contains(str, "static char *"))
973                         format = "xpm";
974
975                 else if (contains(str,"BITPIX"))
976                         format = "fits";
977         }
978
979         if (!format.empty()) {
980                 LYXERR(Debug::GRAPHICS)
981                         << "Recognised Fileformat: " << format << endl;
982                 return format;
983         }
984
985         LYXERR(Debug::GRAPHICS)
986                 << "filetools(getFormatFromContents)\n"
987                 << "\tCouldn't find a known format!\n";
988         return string();
989 }
990
991
992 /// check for zipped file
993 bool zippedFile(FileName const & name)
994 {
995         string const type = getFormatFromContents(name);
996         if (contains("gzip zip compress", type) && !type.empty())
997                 return true;
998         return false;
999 }
1000
1001
1002 string const unzippedFileName(string const & zipped_file)
1003 {
1004         string const ext = getExtension(zipped_file);
1005         if (ext == "gz" || ext == "z" || ext == "Z")
1006                 return changeExtension(zipped_file, string());
1007         return "unzipped_" + zipped_file;
1008 }
1009
1010
1011 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
1012 {
1013         FileName const tempfile = FileName(unzipped_file.empty() ?
1014                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
1015                 unzipped_file);
1016         // Run gunzip
1017         string const command = "gunzip -c " +
1018                 zipped_file.toFilesystemEncoding() + " > " +
1019                 tempfile.toFilesystemEncoding();
1020         Systemcall one;
1021         one.startscript(Systemcall::Wait, command);
1022         // test that command was executed successfully (anon)
1023         // yes, please do. (Lgb)
1024         return tempfile;
1025 }
1026
1027
1028 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1029 {
1030         string str = path;
1031
1032         // If file is from LyXDir, display it as if it were relative.
1033         string const system = package().system_support().absFilename();
1034         if (prefixIs(str, system) && str != system)
1035                 return from_utf8("[" + str.erase(0, system.length()) + "]");
1036
1037         // replace /home/blah with ~/
1038         string const home = package().home_dir().absFilename();
1039         if (!home.empty() && prefixIs(str, home))
1040                 str = subst(str, home, "~");
1041
1042         if (str.length() <= threshold)
1043                 return from_utf8(os::external_path(str));
1044
1045         string const prefix = ".../";
1046         string temp;
1047
1048         while (str.length() > threshold)
1049                 str = split(str, temp, '/');
1050
1051         // Did we shorten everything away?
1052         if (str.empty()) {
1053                 // Yes, filename itself is too long.
1054                 // Pick the start and the end of the filename.
1055                 str = onlyFilename(path);
1056                 string const head = str.substr(0, threshold / 2 - 3);
1057
1058                 string::size_type len = str.length();
1059                 string const tail =
1060                         str.substr(len - threshold / 2 - 2, len - 1);
1061                 str = head + "..." + tail;
1062         }
1063
1064         return from_utf8(os::external_path(prefix + str));
1065 }
1066
1067
1068 bool readLink(FileName const & file, FileName & link)
1069 {
1070 #ifdef HAVE_READLINK
1071         char linkbuffer[512];
1072         // Should be PATH_MAX but that needs autconf support
1073         string const encoded = file.toFilesystemEncoding();
1074         int const nRead = ::readlink(encoded.c_str(),
1075                                      linkbuffer, sizeof(linkbuffer) - 1);
1076         if (nRead <= 0)
1077                 return false;
1078         linkbuffer[nRead] = '\0'; // terminator
1079         link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
1080         return true;
1081 #else
1082         return false;
1083 #endif
1084 }
1085
1086
1087 cmd_ret const runCommand(string const & cmd)
1088 {
1089         // FIXME: replace all calls to RunCommand with ForkedCall
1090         // (if the output is not needed) or the code in ISpell.cpp
1091         // (if the output is needed).
1092
1093         // One question is if we should use popen or
1094         // create our own popen based on fork, exec, pipe
1095         // of course the best would be to have a
1096         // pstream (process stream), with the
1097         // variants ipstream, opstream
1098
1099 #if defined (HAVE_POPEN)
1100         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1101 #elif defined (HAVE__POPEN)
1102         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1103 #else
1104 #error No popen() function.
1105 #endif
1106
1107         // (Claus Hentschel) Check if popen was succesful ;-)
1108         if (!inf) {
1109                 lyxerr << "RunCommand:: could not start child process" << endl;
1110                 return make_pair(-1, string());
1111         }
1112
1113         string ret;
1114         int c = fgetc(inf);
1115         while (c != EOF) {
1116                 ret += static_cast<char>(c);
1117                 c = fgetc(inf);
1118         }
1119
1120 #if defined (HAVE_PCLOSE)
1121         int const pret = pclose(inf);
1122 #elif defined (HAVE__PCLOSE)
1123         int const pret = _pclose(inf);
1124 #else
1125 #error No pclose() function.
1126 #endif
1127
1128         if (pret == -1)
1129                 perror("RunCommand:: could not terminate child process");
1130
1131         return make_pair(pret, ret);
1132 }
1133
1134
1135 FileName const findtexfile(string const & fil, string const & /*format*/)
1136 {
1137         /* There is no problem to extend this function too use other
1138            methods to look for files. It could be setup to look
1139            in environment paths and also if wanted as a last resort
1140            to a recursive find. One of the easier extensions would
1141            perhaps be to use the LyX file lookup methods. But! I am
1142            going to implement this until I see some demand for it.
1143            Lgb
1144         */
1145
1146         // If the file can be found directly, we just return a
1147         // absolute path version of it.
1148         FileName const absfile(makeAbsPath(fil));
1149         if (fs::exists(absfile.toFilesystemEncoding()))
1150                 return absfile;
1151
1152         // No we try to find it using kpsewhich.
1153         // It seems from the kpsewhich manual page that it is safe to use
1154         // kpsewhich without --format: "When the --format option is not
1155         // given, the search path used when looking for a file is inferred
1156         // from the name given, by looking for a known extension. If no
1157         // known extension is found, the search path for TeX source files
1158         // is used."
1159         // However, we want to take advantage of the format sine almost all
1160         // the different formats has environment variables that can be used
1161         // to controll which paths to search. f.ex. bib looks in
1162         // BIBINPUTS and TEXBIB. Small list follows:
1163         // bib - BIBINPUTS, TEXBIB
1164         // bst - BSTINPUTS
1165         // graphic/figure - TEXPICTS, TEXINPUTS
1166         // ist - TEXINDEXSTYLE, INDEXSTYLE
1167         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1168         // tex - TEXINPUTS
1169         // tfm - TFMFONTS, TEXFONTS
1170         // This means that to use kpsewhich in the best possible way we
1171         // should help it by setting additional path in the approp. envir.var.
1172         string const kpsecmd = "kpsewhich " + fil;
1173
1174         cmd_ret const c = runCommand(kpsecmd);
1175
1176         LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1177                  << "kpse result = `" << rtrim(c.second, "\n\r")
1178                  << '\'' << endl;
1179         if (c.first != -1)
1180                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1181                                                         "\n\r")));
1182         else
1183                 return FileName();
1184 }
1185
1186
1187 void removeAutosaveFile(string const & filename)
1188 {
1189         string a = onlyPath(filename);
1190         a += '#';
1191         a += onlyFilename(filename);
1192         a += '#';
1193         FileName const autosave(a);
1194         if (fs::exists(autosave.toFilesystemEncoding()))
1195                 unlink(autosave);
1196 }
1197
1198
1199 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1200         string const & message)
1201 {
1202         LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1203                 << message << std::endl;
1204         // FIXME: Why is this func deleting a file? (Lgb)
1205         if (zipped)
1206                 unlink(file);
1207 }
1208
1209
1210 string const readBB_from_PSFile(FileName const & file)
1211 {
1212         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1213         // It seems that every command in the header has an own line,
1214         // getline() should work for all files.
1215         // On the other hand some plot programs write the bb at the
1216         // end of the file. Than we have in the header:
1217         // %%BoundingBox: (atend)
1218         // In this case we must check the end.
1219         bool zipped = zippedFile(file);
1220         FileName const file_ = zipped ? unzipFile(file) : file;
1221         string const format = getFormatFromContents(file_);
1222
1223         if (format != "eps" && format != "ps") {
1224                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1225                 return string();
1226         }
1227
1228         static boost::regex bbox_re(
1229                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1230         std::ifstream is(file_.toFilesystemEncoding().c_str());
1231         while (is) {
1232                 string s;
1233                 getline(is,s);
1234                 boost::smatch what;
1235                 if (regex_match(s, what, bbox_re)) {
1236                         // Our callers expect the tokens in the string
1237                         // separated by single spaces.
1238                         // FIXME: change return type from string to something
1239                         // sensible
1240                         ostringstream os;
1241                         os << what.str(1) << ' ' << what.str(2) << ' '
1242                            << what.str(3) << ' ' << what.str(4);
1243                         string const bb = os.str();
1244                         readBB_lyxerrMessage(file_, zipped, bb);
1245                         return bb;
1246                 }
1247         }
1248         readBB_lyxerrMessage(file_, zipped, "no bb found");
1249         return string();
1250 }
1251
1252
1253 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1254 {
1255         // If the original is newer than the copy, then copy the original
1256         // to the new directory.
1257
1258         string const file1 = filename1.toFilesystemEncoding();
1259         string const file2 = filename2.toFilesystemEncoding();
1260         int cmp = 0;
1261         if (fs::exists(file1) && fs::exists(file2)) {
1262                 double const tmp = difftime(fs::last_write_time(file1),
1263                                             fs::last_write_time(file2));
1264                 if (tmp != 0)
1265                         cmp = tmp > 0 ? 1 : -1;
1266
1267         } else if (fs::exists(file1)) {
1268                 cmp = 1;
1269         } else if (fs::exists(file2)) {
1270                 cmp = -1;
1271         }
1272
1273         return cmp;
1274 }
1275
1276 // the following is adapted from zlib-1.2.3/contrib/minizip.c
1277 // and miniunz.c, except that
1278 // 1. mkdir, makedir is replaced by lyx' own version
1279 // 2. printf is replaced by lyxerr
1280
1281 #ifdef WIN32
1282 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1283 {
1284         int ret = 0;
1285         {
1286                 FILETIME ftLocal;
1287                 HANDLE hFind;
1288                 WIN32_FIND_DATA  ff32;
1289
1290                 hFind = FindFirstFile(f,&ff32);
1291                 if (hFind != INVALID_HANDLE_VALUE)
1292                 {
1293                         FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
1294                         FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
1295                         FindClose(hFind);
1296                         ret = 1;
1297                 }
1298         }
1299         return ret;
1300 }
1301
1302 #else
1303 #ifdef unix
1304 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1305 {
1306         int ret=0;
1307         struct stat s;                                                            /* results of stat() */
1308         struct tm* filedate;
1309         time_t tm_t=0;
1310
1311         if (strcmp(f,"-")!=0) {
1312                 char name[MAXFILENAME+1];
1313                 int len = strlen(f);
1314                 if (len > MAXFILENAME)
1315                         len = MAXFILENAME;
1316
1317                 strncpy(name, f,MAXFILENAME-1);
1318                 /* strncpy doesnt append the trailing NULL, of the string is too long. */
1319                 name[ MAXFILENAME ] = '\0';
1320
1321                 if (name[len - 1] == '/')
1322                         name[len - 1] = '\0';
1323                 /* not all systems allow stat'ing a file with / appended */
1324                 if (stat(name,&s)==0) {
1325                         tm_t = s.st_mtime;
1326                         ret = 1;
1327                 }
1328         }
1329         filedate = localtime(&tm_t);
1330
1331         tmzip->tm_sec  = filedate->tm_sec;
1332         tmzip->tm_min  = filedate->tm_min;
1333         tmzip->tm_hour = filedate->tm_hour;
1334         tmzip->tm_mday = filedate->tm_mday;
1335         tmzip->tm_mon  = filedate->tm_mon ;
1336         tmzip->tm_year = filedate->tm_year;
1337
1338         return ret;
1339 }
1340
1341 #else
1342
1343 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1344 {
1345         return 0;
1346 }
1347 #endif
1348 #endif
1349
1350 bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files)
1351 {
1352         int err = 0;
1353         zipFile zf;
1354         int errclose;
1355         void * buf = NULL;
1356
1357         int size_buf = WRITEBUFFERSIZE;
1358         buf = (void*)malloc(size_buf);
1359         if (buf==NULL) {
1360                 lyxerr << "Error allocating memory" << endl;
1361                 return false;
1362         }
1363         string const zfile = zipfile.toFilesystemEncoding();
1364         const char * fname = zfile.c_str();
1365
1366 #ifdef USEWIN32IOAPI
1367         zlib_filefunc_def ffunc;
1368         fill_win32_filefunc(&ffunc);
1369         // false: not append
1370         zf = zipOpen2(fname, false, NULL, &ffunc);
1371 #else
1372         zf = zipOpen(fname, false);
1373 #endif
1374
1375         if (zf == NULL) {
1376                 lyxerr << "error opening " << zipfile << endl;
1377                 return false;
1378         }
1379
1380         for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) {
1381                 FILE * fin;
1382                 int size_read;
1383                 zip_fileinfo zi;
1384                 const char * diskfilename = it->first.c_str();
1385                 const char * filenameinzip = it->second.c_str();
1386                 unsigned long crcFile=0;
1387
1388                 zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
1389                         zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
1390                 zi.dosDate = 0;
1391                 zi.internal_fa = 0;
1392                 zi.external_fa = 0;
1393                 filetime(filenameinzip, &zi.tmz_date, &zi.dosDate);
1394
1395                 err = zipOpenNewFileInZip3(zf, filenameinzip, &zi,
1396                         NULL,0,NULL,0,NULL /* comment*/,
1397                         Z_DEFLATED,
1398                         Z_DEFAULT_COMPRESSION,            // compression level
1399                         0,
1400                 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
1401                         -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
1402                         NULL,                                             // password,
1403                         crcFile);
1404
1405                 if (err != ZIP_OK) {
1406                         lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl;
1407                         return false;
1408                 }
1409                 fin = fopen(diskfilename, "rb");
1410                 if (fin==NULL) {
1411                         lyxerr << "error in opening " << diskfilename << " for reading" << endl;
1412                         return false;
1413                 }
1414
1415                 do {
1416                         err = ZIP_OK;
1417                         size_read = (int)fread(buf, 1, size_buf, fin);
1418                         if (size_read < size_buf)
1419                                 if (feof(fin)==0) {
1420                                         lyxerr << "error in reading " << filenameinzip << endl;
1421                                         return false;
1422                                 }
1423
1424                         if (size_read>0) {
1425                                 err = zipWriteInFileInZip (zf, buf, size_read);
1426                                 if (err<0) {
1427                                         lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl;
1428                                         return false;
1429                                 }
1430                         }
1431                 } while ((err == ZIP_OK) && (size_read>0));
1432
1433                 if (fin)
1434                         fclose(fin);
1435
1436                 err = zipCloseFileInZip(zf);
1437                 if (err != ZIP_OK) {
1438                         lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl;
1439                         return false;
1440                 }
1441         }
1442         errclose = zipClose(zf, NULL);
1443         if (errclose != ZIP_OK) {
1444                 lyxerr << "error in closing " << zipfile << endl;
1445                 return false;
1446         }
1447         free(buf);
1448         return true;
1449 }
1450
1451 // adapted from miniunz.c
1452
1453 /* change_file_date : change the date/time of a file
1454         filename : the filename of the file where date/time must be modified
1455         dosdate : the new date at the MSDos format (4 bytes)
1456         tmu_date : the SAME new date at the tm_unz format */
1457 void change_file_date(const char * filename, uLong dosdate, tm_unz tmu_date)
1458 {
1459 #ifdef WIN32
1460         HANDLE hFile;
1461         FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
1462
1463         hFile = CreateFile(filename,GENERIC_READ | GENERIC_WRITE,
1464                 0,NULL,OPEN_EXISTING,0,NULL);
1465         GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
1466         DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
1467         LocalFileTimeToFileTime(&ftLocal,&ftm);
1468         SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
1469         CloseHandle(hFile);
1470 #else
1471 #ifdef unix
1472         struct utimbuf ut;
1473         struct tm newdate;
1474
1475         newdate.tm_sec = tmu_date.tm_sec;
1476         newdate.tm_min=tmu_date.tm_min;
1477         newdate.tm_hour=tmu_date.tm_hour;
1478         newdate.tm_mday=tmu_date.tm_mday;
1479         newdate.tm_mon=tmu_date.tm_mon;
1480         if (tmu_date.tm_year > 1900)
1481                 newdate.tm_year=tmu_date.tm_year - 1900;
1482         else
1483                 newdate.tm_year=tmu_date.tm_year ;
1484         newdate.tm_isdst=-1;
1485
1486         ut.actime=ut.modtime=mktime(&newdate);
1487         utime(filename,&ut);
1488 #endif
1489 #endif
1490 }
1491
1492
1493 int do_extract_currentfile(unzFile uf,
1494         const int * popt_extract_without_path,
1495         int * popt_overwrite,
1496         const char * password,
1497         const char * dirname)
1498 {
1499         char filename_inzip[256];
1500         char* filename_withoutpath;
1501         char* p;
1502         int err=UNZ_OK;
1503         FILE *fout=NULL;
1504         void* buf;
1505         uInt size_buf;
1506
1507         unz_file_info file_info;
1508         uLong ratio=0;
1509         err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
1510
1511         if (err!=UNZ_OK) {
1512                 lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl;
1513                 return err;
1514         }
1515
1516         size_buf = WRITEBUFFERSIZE;
1517         buf = (void*)malloc(size_buf);
1518         if (buf==NULL) {
1519                 lyxerr << "Error allocating memory" << endl;
1520                 return UNZ_INTERNALERROR;
1521         }
1522
1523         p = filename_withoutpath = filename_inzip;
1524         while ((*p) != '\0') {
1525                 if (((*p)=='/') || ((*p)=='\\'))
1526                         filename_withoutpath = p+1;
1527                 p++;
1528         }
1529         // this is a directory
1530         if ((*filename_withoutpath)=='\0') {
1531                 if ((*popt_extract_without_path)==0)
1532                         makedir(filename_inzip);
1533         }
1534         // this is a filename
1535         else {
1536                 char write_filename[1024];
1537
1538                 strcpy(write_filename, dirname);
1539                 int len = strlen(write_filename);
1540                 if (write_filename[len-1] != '\\' &&
1541                         write_filename[len-1] != '/')
1542                         strcat(write_filename, "/");
1543
1544                 if ((*popt_extract_without_path)==0)
1545                         strcat(write_filename, filename_inzip);
1546                 else
1547                         strcat(write_filename, filename_withoutpath);
1548
1549                 err = unzOpenCurrentFilePassword(uf,password);
1550                 if (err!=UNZ_OK) {
1551                         lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl;
1552                 } else {
1553                         fout=fopen(write_filename, "wb");
1554
1555                         /* some zipfile don't contain directory alone before file */
1556                         if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
1557                                 (filename_withoutpath!=(char*)filename_inzip)) {
1558                                 char c=*(filename_withoutpath-1);
1559                                 *(filename_withoutpath-1)='\0';
1560                                 makedir(write_filename);
1561                                 *(filename_withoutpath-1)=c;
1562                                 fout=fopen(write_filename,"wb");
1563                         }
1564
1565                         if (fout==NULL) {
1566                                 lyxerr << "error opening " << write_filename << endl;
1567                         }
1568                 }
1569
1570                 if (fout!=NULL) {
1571                         LYXERR(Debug::FILES) << " extracting: " << write_filename << endl;
1572
1573                         do {
1574                                 err = unzReadCurrentFile(uf,buf,size_buf);
1575                                 if (err<0) {
1576                                         lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl;
1577                                         break;
1578                                 }
1579                                 if (err>0)
1580                                         if (fwrite(buf,err,1,fout)!=1) {
1581                                                 lyxerr << "error in writing extracted file" << endl;
1582                                                 err=UNZ_ERRNO;
1583                                                 break;
1584                                         }
1585                         } while (err>0);
1586                         if (fout)
1587                                 fclose(fout);
1588
1589                         if (err==0)
1590                                 change_file_date(write_filename,file_info.dosDate,
1591                                         file_info.tmu_date);
1592                 }
1593
1594                 if (err==UNZ_OK) {
1595                         err = unzCloseCurrentFile (uf);
1596                         if (err!=UNZ_OK) {
1597                                 lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl;
1598                         }
1599                 }
1600                 else
1601                         unzCloseCurrentFile(uf);          /* don't lose the error */
1602         }
1603
1604         free(buf);
1605         return err;
1606 }
1607
1608
1609 bool unzipToDir(string const & zipfile, string const & dirname)
1610 {
1611         unzFile uf=NULL;
1612 #ifdef USEWIN32IOAPI
1613         zlib_filefunc_def ffunc;
1614 #endif
1615
1616         const char * zipfilename = zipfile.c_str();
1617
1618 #ifdef USEWIN32IOAPI
1619         fill_win32_filefunc(&ffunc);
1620         uf = unzOpen2(zipfilename, &ffunc);
1621 #else
1622         uf = unzOpen(zipfilename);
1623 #endif
1624
1625         if (uf==NULL) {
1626                 lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl;
1627                 return false;
1628         }
1629
1630         uLong i;
1631         unz_global_info gi;
1632         int err;
1633         FILE* fout=NULL;
1634         int opt_extract_without_path = 0;
1635         int opt_overwrite = 1;
1636         char * password = NULL;
1637
1638         err = unzGetGlobalInfo (uf, &gi);
1639         if (err != UNZ_OK) {
1640                 lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl;
1641                 return false;
1642         }
1643
1644         for (i=0; i < gi.number_entry; i++) {
1645                 if (do_extract_currentfile(uf, &opt_extract_without_path,
1646                         &opt_overwrite,
1647                         password, dirname.c_str()) != UNZ_OK)
1648                         break;
1649
1650                 if ((i+1)<gi.number_entry) {
1651                         err = unzGoToNextFile(uf);
1652                         if (err != UNZ_OK) {
1653                                 lyxerr << "error " << err << " with zipfile in unzGoToNextFile" << endl;;
1654                                 break;
1655                         }
1656                 }
1657         }
1658
1659         unzCloseCurrentFile(uf);
1660 }
1661
1662 } //namespace support
1663 } // namespace lyx