]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Fix several filename and environment variable encoding problems
[lyx.git] / src / support / filetools.C
1 /**
2  * \file filetools.C
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 <cctype>
46 #include <cerrno>
47 #include <cstdlib>
48 #include <cstdio>
49
50 #include <utility>
51 #include <fstream>
52 #include <sstream>
53
54 #ifndef CXX_GLOBAL_CSTD
55 using std::fgetc;
56 using std::isalnum;
57 using std::isalpha;
58 #endif
59
60 using std::endl;
61 using std::getline;
62 using std::make_pair;
63 using std::string;
64 using std::ifstream;
65 using std::ostringstream;
66 using std::vector;
67
68 namespace fs = boost::filesystem;
69
70 namespace lyx {
71 namespace support {
72
73 bool isLyXFilename(string const & filename)
74 {
75         return suffixIs(ascii_lowercase(filename), ".lyx");
76 }
77
78
79 bool isSGMLFilename(string const & filename)
80 {
81         return suffixIs(ascii_lowercase(filename), ".sgml");
82 }
83
84
85 string const latex_path(string const & original_path,
86                 latex_path_extension extension,
87                 latex_path_dots dots)
88 {
89         // On cygwin, we may need windows or posix style paths.
90         string path = os::latex_path(original_path);
91         path = subst(path, "~", "\\string~");
92         if (path.find(' ') != string::npos) {
93                 // We can't use '"' because " is sometimes active (e.g. if
94                 // babel is loaded with the "german" option)
95                 if (extension == EXCLUDE_EXTENSION) {
96                         // ChangeExtension calls os::internal_path internally
97                         // so don't use it to remove the extension.
98                         string const ext = getExtension(path);
99                         string const base = ext.empty() ?
100                                 path :
101                                 path.substr(0, path.length() - ext.length() - 1);
102                         // ChangeExtension calls os::internal_path internally
103                         // so don't use it to re-add the extension.
104                         path = "\\string\"" + base + "\\string\"." + ext;
105                 } else {
106                         path = "\\string\"" + path + "\\string\"";
107                 }
108         }
109
110         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
111 }
112
113
114 // Substitutes spaces with underscores in filename (and path)
115 string const makeLatexName(string const & file)
116 {
117         string name = onlyFilename(file);
118         string const path = onlyPath(file);
119
120         for (string::size_type i = 0; i < name.length(); ++i)
121                 name[i] &= 0x7f; // set 8th bit to 0
122
123         // ok so we scan through the string twice, but who cares.
124         string const keep = "abcdefghijklmnopqrstuvwxyz"
125                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
126                 "@!\"'()*+,-./0123456789:;<=>?[]`|";
127
128         string::size_type pos = 0;
129         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
130                 name[pos++] = '_';
131
132         return addName(path, name);
133 }
134
135
136 string const quoteName(string const & name, quote_style style)
137 {
138         switch(style) {
139         case quote_shell:
140                 // This does not work for filenames containing " (windows)
141                 // or ' (all other OSes). This can't be changed easily, since
142                 // we would need to adapt the command line parser in
143                 // Forkedcall::generateChild. Therefore we don't pass user
144                 // filenames to child processes if possible. We store them in
145                 // a python script instead, where we don't have these
146                 // limitations.
147                 return (os::shell() == os::UNIX) ?
148                         '\'' + name + '\'':
149                         '"' + name + '"';
150         case quote_python:
151                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
152                      + "\"";
153         }
154         // shut up stupid compiler
155         return string();
156 }
157
158
159 bool isFileReadable(FileName const & filename)
160 {
161         std::string const path = filename.toFilesystemEncoding();
162         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
163 }
164
165
166 //returns true: dir writeable
167 //        false: not writeable
168 bool isDirWriteable(FileName const & path)
169 {
170         lyxerr[Debug::FILES] << "isDirWriteable: " << path << endl;
171
172         FileName const tmpfl(tempName(path, "lyxwritetest"));
173
174         if (tmpfl.empty())
175                 return false;
176
177         unlink(tmpfl);
178         return true;
179 }
180
181
182 #if 0
183 // Uses a string of paths separated by ";"s to find a file to open.
184 // Can't cope with pathnames with a ';' in them. Returns full path to file.
185 // If path entry begins with $$LyX/, use system_lyxdir
186 // If path entry begins with $$User/, use user_lyxdir
187 // Example: "$$User/doc;$$LyX/doc"
188 FileName const fileOpenSearch(string const & path, string const & name,
189                              string const & ext)
190 {
191         FileName real_file;
192         string path_element;
193         bool notfound = true;
194         string tmppath = split(path, path_element, ';');
195
196         while (notfound && !path_element.empty()) {
197                 path_element = os::internal_path(path_element);
198                 if (!suffixIs(path_element, '/'))
199                         path_element += '/';
200                 path_element = subst(path_element, "$$LyX",
201                                      package().system_support());
202                 path_element = subst(path_element, "$$User",
203                                      package().user_support());
204
205                 real_file = fileSearch(path_element, name, ext);
206
207                 if (real_file.empty()) {
208                         do {
209                                 tmppath = split(tmppath, path_element, ';');
210                         } while (!tmppath.empty() && path_element.empty());
211                 } else {
212                         notfound = false;
213                 }
214         }
215         return real_file;
216 }
217 #endif
218
219
220 /// Returns a vector of all files in directory dir having extension ext.
221 vector<FileName> const dirList(FileName const & dir, string const & ext)
222 {
223         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
224         vector<FileName> dirlist;
225
226         string const encoded_dir = dir.toFilesystemEncoding();
227         if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
228                 lyxerr[Debug::FILES]
229                         << "Directory \"" << dir
230                         << "\" does not exist to DirList." << endl;
231                 return dirlist;
232         }
233
234         string extension;
235         if (!ext.empty() && ext[0] != '.')
236                 extension += '.';
237         extension += ext;
238
239         fs::directory_iterator dit(encoded_dir);
240         fs::directory_iterator end;
241         for (; dit != end; ++dit) {
242                 string const & fil = dit->leaf();
243                 if (suffixIs(fil, extension))
244                         dirlist.push_back(FileName::fromFilesystemEncoding(
245                                         encoded_dir + '/' + fil));
246         }
247         return dirlist;
248 }
249
250
251 // Returns the real name of file name in directory path, with optional
252 // extension ext.
253 FileName const fileSearch(string const & path, string const & name,
254                           string const & ext, search_mode mode)
255 {
256         // if `name' is an absolute path, we ignore the setting of `path'
257         // Expand Environmentvariables in 'name'
258         string const tmpname = replaceEnvironmentPath(name);
259         FileName fullname(makeAbsPath(tmpname, path));
260         // search first without extension, then with it.
261         if (isFileReadable(fullname))
262                 return fullname;
263         if (ext.empty())
264                 // We are done.
265                 return mode == allow_unreadable ? fullname : FileName();
266         // Only add the extension if it is not already the extension of
267         // fullname.
268         if (getExtension(fullname.absFilename()) != ext)
269                 fullname = FileName(addExtension(fullname.absFilename(), ext));
270         if (isFileReadable(fullname) || mode == allow_unreadable)
271                 return fullname;
272         return FileName();
273 }
274
275
276 // Search the file name.ext in the subdirectory dir of
277 //   1) user_lyxdir
278 //   2) build_lyxdir (if not empty)
279 //   3) system_lyxdir
280 FileName const libFileSearch(string const & dir, string const & name,
281                            string const & ext)
282 {
283         FileName fullname = fileSearch(addPath(package().user_support(), dir),
284                                      name, ext);
285         if (!fullname.empty())
286                 return fullname;
287
288         if (!package().build_support().empty())
289                 fullname = fileSearch(addPath(package().build_support(), dir),
290                                       name, ext);
291         if (!fullname.empty())
292                 return fullname;
293
294         return fileSearch(addPath(package().system_support(), dir), name, ext);
295 }
296
297
298 FileName const i18nLibFileSearch(string const & dir, string const & name,
299                   string const & ext)
300 {
301         // the following comments are from intl/dcigettext.c. We try
302         // to mimick this behaviour here.
303         /* The highest priority value is the `LANGUAGE' environment
304            variable. But we don't use the value if the currently
305            selected locale is the C locale. This is a GNU extension. */
306         /* [Otherwise] We have to proceed with the POSIX methods of
307            looking to `LC_ALL', `LC_xxx', and `LANG'. */
308
309         string lang = getEnv("LC_ALL");
310         if (lang.empty()) {
311                 lang = getEnv("LC_MESSAGES");
312                 if (lang.empty()) {
313                         lang = getEnv("LANG");
314                         if (lang.empty())
315                                 lang = "C";
316                 }
317         }
318
319         string const language = getEnv("LANGUAGE");
320         if (lang != "C" && lang != "POSIX" && !language.empty())
321                 lang = language;
322
323         string l;
324         lang = split(lang, l, ':');
325         while (!l.empty() && l != "C" && l != "POSIX") {
326                 FileName const tmp = libFileSearch(dir,
327                                                  token(l, '_', 0) + '_' + name,
328                                                  ext);
329                 if (!tmp.empty())
330                         return tmp;
331                 lang = split(lang, l, ':');
332         }
333
334         return libFileSearch(dir, name, ext);
335 }
336
337
338 string const libScriptSearch(string const & command_in, quote_style style)
339 {
340         static string const token_scriptpath = "$$s/";
341
342         string command = command_in;
343         // Find the starting position of "$$s/"
344         string::size_type const pos1 = command.find(token_scriptpath);
345         if (pos1 == string::npos)
346                 return command;
347         // Find the end of the "$$s/some_subdir/some_script" word within
348         // command. Assumes that the script name does not contain spaces.
349         string::size_type const start_script = pos1 + 4;
350         string::size_type const pos2 = command.find(' ', start_script);
351         string::size_type const size_script = pos2 == string::npos?
352                 (command.size() - start_script) : pos2 - start_script;
353
354         // Does this script file exist?
355         string const script = 
356                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
357
358         if (script.empty()) {
359                 // Replace "$$s/" with ""
360                 command.erase(pos1, 4);
361         } else {
362                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
363                 string::size_type const size_replace = size_script + 4;
364                 command.replace(pos1, size_replace, quoteName(script, style));
365         }
366
367         return command;
368 }
369
370
371 namespace {
372
373 FileName const createTmpDir(FileName const & tempdir, string const & mask)
374 {
375         lyxerr[Debug::FILES]
376                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
377                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
378
379         FileName const tmpfl(tempName(tempdir, mask));
380         // lyx::tempName actually creates a file to make sure that it
381         // stays unique. So we have to delete it before we can create
382         // a dir with the same name. Note also that we are not thread
383         // safe because of the gap between unlink and mkdir. (Lgb)
384         unlink(tmpfl);
385
386         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
387                 lyxerr << "LyX could not create the temporary directory '"
388                        << tmpfl << "'" << endl;
389                 return FileName();
390         }
391
392         return tmpfl;
393 }
394
395 } // namespace anon
396
397
398 bool destroyDir(FileName const & tmpdir)
399 {
400         try {
401                 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
402         } catch (fs::filesystem_error const & fe){
403                 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
404                 return false;
405         }
406 }
407
408
409 string const createBufferTmpDir()
410 {
411         static int count;
412         // We are in our own directory.  Why bother to mangle name?
413         // In fact I wrote this code to circumvent a problematic behaviour
414         // (bug?) of EMX mkstemp().
415         string const tmpfl =
416                 package().temp_dir() + "/lyx_tmpbuf" +
417                 convert<string>(count++);
418
419         if (mkdir(FileName(tmpfl), 0777)) {
420                 lyxerr << "LyX could not create the temporary directory '"
421                        << tmpfl << "'" << endl;
422                 return string();
423         }
424         return tmpfl;
425 }
426
427
428 FileName const createLyXTmpDir(FileName const & deflt)
429 {
430         if (!deflt.empty() && deflt.absFilename() != "/tmp") {
431                 if (mkdir(deflt, 0777)) {
432                         if (isDirWriteable(deflt)) {
433                                 // deflt could not be created because it
434                                 // did exist already, so let's create our own
435                                 // dir inside deflt.
436                                 return createTmpDir(deflt, "lyx_tmpdir");
437                         } else {
438                                 // some other error occured.
439                                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
440                         }
441                 } else
442                         return deflt;
443         } else {
444                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
445         }
446 }
447
448
449 bool createDirectory(string const & path, int permission)
450 {
451         string temp = rtrim(os::internal_path(path), "/");
452         BOOST_ASSERT(!temp.empty());
453         return mkdir(FileName(temp), permission) == 0;
454 }
455
456
457 // Strip filename from path name
458 string const onlyPath(string const & filename)
459 {
460         // If empty filename, return empty
461         if (filename.empty())
462                 return filename;
463
464         // Find last / or start of filename
465         string::size_type j = filename.rfind('/');
466         return j == string::npos ? "./" : filename.substr(0, j + 1);
467 }
468
469
470 // Convert relative path into absolute path based on a basepath.
471 // If relpath is absolute, just use that.
472 // If basepath is empty, use CWD as base.
473 FileName const makeAbsPath(string const & relPath, string const & basePath)
474 {
475         // checks for already absolute path
476         if (os::is_absolute_path(relPath))
477                 return FileName(relPath);
478
479         // Copies given paths
480         string tempRel = os::internal_path(relPath);
481         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
482         tempRel = subst(tempRel, "//", "/");
483
484         string tempBase;
485
486         if (os::is_absolute_path(basePath))
487                 tempBase = basePath;
488         else
489                 tempBase = addPath(getcwd().absFilename(), basePath);
490
491         // Handle /./ at the end of the path
492         while (suffixIs(tempBase, "/./"))
493                 tempBase.erase(tempBase.length() - 2);
494
495         // processes relative path
496         string rTemp = tempRel;
497         string temp;
498
499         while (!rTemp.empty()) {
500                 // Split by next /
501                 rTemp = split(rTemp, temp, '/');
502
503                 if (temp == ".") continue;
504                 if (temp == "..") {
505                         // Remove one level of TempBase
506                         string::difference_type i = tempBase.length() - 2;
507                         if (i < 0)
508                                 i = 0;
509                         while (i > 0 && tempBase[i] != '/')
510                                 --i;
511                         if (i > 0)
512                                 tempBase.erase(i, string::npos);
513                         else
514                                 tempBase += '/';
515                 } else if (temp.empty() && !rTemp.empty()) {
516                                 tempBase = os::current_root() + rTemp;
517                                 rTemp.erase();
518                 } else {
519                         // Add this piece to TempBase
520                         if (!suffixIs(tempBase, '/'))
521                                 tempBase += '/';
522                         tempBase += temp;
523                 }
524         }
525
526         // returns absolute path
527         return FileName(os::internal_path(tempBase));
528 }
529
530
531 // Correctly append filename to the pathname.
532 // If pathname is '.', then don't use pathname.
533 // Chops any path of filename.
534 string const addName(string const & path, string const & fname)
535 {
536         string const basename = onlyFilename(fname);
537         string buf;
538
539         if (path != "." && path != "./" && !path.empty()) {
540                 buf = os::internal_path(path);
541                 if (!suffixIs(path, '/'))
542                         buf += '/';
543         }
544
545         return buf + basename;
546 }
547
548
549 // Strips path from filename
550 string const onlyFilename(string const & fname)
551 {
552         if (fname.empty())
553                 return fname;
554
555         string::size_type j = fname.rfind('/');
556         if (j == string::npos) // no '/' in fname
557                 return fname;
558
559         // Strip to basename
560         return fname.substr(j + 1);
561 }
562
563
564 /// Returns true is path is absolute
565 bool absolutePath(string const & path)
566 {
567         return os::is_absolute_path(path);
568 }
569
570
571 // Create absolute path. If impossible, don't do anything
572 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
573 string const expandPath(string const & path)
574 {
575         // checks for already absolute path
576         string rTemp = replaceEnvironmentPath(path);
577         if (os::is_absolute_path(rTemp))
578                 return rTemp;
579
580         string temp;
581         string const copy = rTemp;
582
583         // Split by next /
584         rTemp = split(rTemp, temp, '/');
585
586         if (temp == ".")
587                 return getcwd().absFilename() + '/' + rTemp;
588
589         if (temp == "~")
590                 return package().home_dir() + '/' + rTemp;
591
592         if (temp == "..")
593                 return makeAbsPath(copy).absFilename();
594
595         // Don't know how to handle this
596         return copy;
597 }
598
599
600 // Normalize a path. Constracts path/../path
601 // Can't handle "../../" or "/../" (Asger)
602 // Also converts paths like /foo//bar ==> /foo/bar
603 string const normalizePath(string const & path)
604 {
605         // Normalize paths like /foo//bar ==> /foo/bar
606         static boost::regex regex("/{2,}");
607         string const tmppath = boost::regex_merge(path, regex, "/");
608
609         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
610
611         if (!npath.is_complete())
612                 return "./" + npath.string() + '/';
613
614         return npath.string() + '/';
615 }
616
617
618 string const getFileContents(FileName const & fname)
619 {
620         string const encodedname = fname.toFilesystemEncoding();
621         if (fs::exists(encodedname)) {
622                 ifstream ifs(encodedname.c_str());
623                 ostringstream ofs;
624                 if (ifs && ofs) {
625                         ofs << ifs.rdbuf();
626                         ifs.close();
627                         return ofs.str();
628                 }
629         }
630         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
631         return string();
632 }
633
634
635 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
636 string const replaceEnvironmentPath(string const & path)
637 {
638         // ${VAR} is defined as
639         // $\{[A-Za-z_][A-Za-z_0-9]*\}
640         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
641
642         // $VAR is defined as:
643         // $\{[A-Za-z_][A-Za-z_0-9]*\}
644         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
645
646         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
647         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
648         boost::smatch what;
649
650         string result = path;
651         while (1) {
652                 regex_match(result, what, envvar_br_re);
653                 if (!what[0].matched) {
654                         regex_match(result, what, envvar_re);
655                         if (!what[0].matched)
656                                 break;
657                 }
658                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
659         }
660         return result;
661 }
662
663
664 // Make relative path out of two absolute paths
665 string const makeRelPath(string const & abspath, string const & basepath)
666 // Makes relative path out of absolute path. If it is deeper than basepath,
667 // it's easy. If basepath and abspath share something (they are all deeper
668 // than some directory), it'll be rendered using ..'s. If they are completely
669 // different, then the absolute path will be used as relative path.
670 {
671         string::size_type const abslen = abspath.length();
672         string::size_type const baselen = basepath.length();
673
674         string::size_type i = os::common_path(abspath, basepath);
675
676         if (i == 0) {
677                 // actually no match - cannot make it relative
678                 return abspath;
679         }
680
681         // Count how many dirs there are in basepath above match
682         // and append as many '..''s into relpath
683         string buf;
684         string::size_type j = i;
685         while (j < baselen) {
686                 if (basepath[j] == '/') {
687                         if (j + 1 == baselen)
688                                 break;
689                         buf += "../";
690                 }
691                 ++j;
692         }
693
694         // Append relative stuff from common directory to abspath
695         if (abspath[i] == '/')
696                 ++i;
697         for (; i < abslen; ++i)
698                 buf += abspath[i];
699         // Remove trailing /
700         if (suffixIs(buf, '/'))
701                 buf.erase(buf.length() - 1);
702         // Substitute empty with .
703         if (buf.empty())
704                 buf = '.';
705         return buf;
706 }
707
708
709 // Append sub-directory(ies) to a path in an intelligent way
710 string const addPath(string const & path, string const & path_2)
711 {
712         string buf;
713         string const path2 = os::internal_path(path_2);
714
715         if (!path.empty() && path != "." && path != "./") {
716                 buf = os::internal_path(path);
717                 if (path[path.length() - 1] != '/')
718                         buf += '/';
719         }
720
721         if (!path2.empty()) {
722                 string::size_type const p2start = path2.find_first_not_of('/');
723                 string::size_type const p2end = path2.find_last_not_of('/');
724                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
725                 buf += tmp + '/';
726         }
727         return buf;
728 }
729
730
731 string const changeExtension(string const & oldname, string const & extension)
732 {
733         string::size_type const last_slash = oldname.rfind('/');
734         string::size_type last_dot = oldname.rfind('.');
735         if (last_dot < last_slash && last_slash != string::npos)
736                 last_dot = string::npos;
737
738         string ext;
739         // Make sure the extension starts with a dot
740         if (!extension.empty() && extension[0] != '.')
741                 ext= '.' + extension;
742         else
743                 ext = extension;
744
745         return os::internal_path(oldname.substr(0, last_dot) + ext);
746 }
747
748
749 string const removeExtension(string const & name)
750 {
751         return changeExtension(name, string());
752 }
753
754
755 string const addExtension(string const & name, string const & extension)
756 {
757         if (!extension.empty() && extension[0] != '.')
758                 return name + '.' + extension;
759         return name + extension;
760 }
761
762
763 /// Return the extension of the file (not including the .)
764 string const getExtension(string const & name)
765 {
766         string::size_type const last_slash = name.rfind('/');
767         string::size_type const last_dot = name.rfind('.');
768         if (last_dot != string::npos &&
769             (last_slash == string::npos || last_dot > last_slash))
770                 return name.substr(last_dot + 1,
771                                    name.length() - (last_dot + 1));
772         else
773                 return string();
774 }
775
776
777 // the different filetypes and what they contain in one of the first lines
778 // (dots are any characters).           (Herbert 20020131)
779 // AGR  Grace...
780 // BMP  BM...
781 // EPS  %!PS-Adobe-3.0 EPSF...
782 // FIG  #FIG...
783 // FITS ...BITPIX...
784 // GIF  GIF...
785 // JPG  JFIF
786 // PDF  %PDF-...
787 // PNG  .PNG...
788 // PBM  P1... or P4     (B/W)
789 // PGM  P2... or P5     (Grayscale)
790 // PPM  P3... or P6     (color)
791 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
792 // SGI  \001\332...     (decimal 474)
793 // TGIF %TGIF...
794 // TIFF II... or MM...
795 // XBM  ..._bits[]...
796 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
797 //      ...static char *...
798 // XWD  \000\000\000\151        (0x00006900) decimal 105
799 //
800 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
801 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
802 // Z    \037\235                UNIX compress
803
804 string const getFormatFromContents(FileName const & filename)
805 {
806         // paranoia check
807         if (filename.empty() || !isFileReadable(filename))
808                 return string();
809
810         ifstream ifs(filename.toFilesystemEncoding().c_str());
811         if (!ifs)
812                 // Couldn't open file...
813                 return string();
814
815         // gnuzip
816         static string const gzipStamp = "\037\213";
817
818         // PKZIP
819         static string const zipStamp = "PK";
820
821         // compress
822         static string const compressStamp = "\037\235";
823
824         // Maximum strings to read
825         int const max_count = 50;
826         int count = 0;
827
828         string str;
829         string format;
830         bool firstLine = true;
831         while ((count++ < max_count) && format.empty()) {
832                 if (ifs.eof()) {
833                         lyxerr[Debug::GRAPHICS]
834                                 << "filetools(getFormatFromContents)\n"
835                                 << "\tFile type not recognised before EOF!"
836                                 << endl;
837                         break;
838                 }
839
840                 getline(ifs, str);
841                 string const stamp = str.substr(0, 2);
842                 if (firstLine && str.size() >= 2) {
843                         // at first we check for a zipped file, because this
844                         // information is saved in the first bytes of the file!
845                         // also some graphic formats which save the information
846                         // in the first line, too.
847                         if (prefixIs(str, gzipStamp)) {
848                                 format =  "gzip";
849
850                         } else if (stamp == zipStamp) {
851                                 format =  "zip";
852
853                         } else if (stamp == compressStamp) {
854                                 format =  "compress";
855
856                         // the graphics part
857                         } else if (stamp == "BM") {
858                                 format =  "bmp";
859
860                         } else if (stamp == "\001\332") {
861                                 format =  "sgi";
862
863                         // PBM family
864                         // Don't need to use str.at(0), str.at(1) because
865                         // we already know that str.size() >= 2
866                         } else if (str[0] == 'P') {
867                                 switch (str[1]) {
868                                 case '1':
869                                 case '4':
870                                         format =  "pbm";
871                                     break;
872                                 case '2':
873                                 case '5':
874                                         format =  "pgm";
875                                     break;
876                                 case '3':
877                                 case '6':
878                                         format =  "ppm";
879                                 }
880                                 break;
881
882                         } else if ((stamp == "II") || (stamp == "MM")) {
883                                 format =  "tiff";
884
885                         } else if (prefixIs(str,"%TGIF")) {
886                                 format =  "tgif";
887
888                         } else if (prefixIs(str,"#FIG")) {
889                                 format =  "fig";
890
891                         } else if (prefixIs(str,"GIF")) {
892                                 format =  "gif";
893
894                         } else if (str.size() > 3) {
895                                 int const c = ((str[0] << 24) & (str[1] << 16) &
896                                                (str[2] << 8)  & str[3]);
897                                 if (c == 105) {
898                                         format =  "xwd";
899                                 }
900                         }
901
902                         firstLine = false;
903                 }
904
905                 if (!format.empty())
906                     break;
907                 else if (contains(str,"EPSF"))
908                         // dummy, if we have wrong file description like
909                         // %!PS-Adobe-2.0EPSF"
910                         format =  "eps";
911
912                 else if (contains(str,"Grace"))
913                         format =  "agr";
914
915                 else if (contains(str,"JFIF"))
916                         format =  "jpg";
917
918                 else if (contains(str,"%PDF"))
919                         format =  "pdf";
920
921                 else if (contains(str,"PNG"))
922                         format =  "png";
923
924                 else if (contains(str,"%!PS-Adobe")) {
925                         // eps or ps
926                         ifs >> str;
927                         if (contains(str,"EPSF"))
928                                 format = "eps";
929                         else
930                             format = "ps";
931                 }
932
933                 else if (contains(str,"_bits[]"))
934                         format = "xbm";
935
936                 else if (contains(str,"XPM") || contains(str, "static char *"))
937                         format = "xpm";
938
939                 else if (contains(str,"BITPIX"))
940                         format = "fits";
941         }
942
943         if (!format.empty()) {
944                 lyxerr[Debug::GRAPHICS]
945                         << "Recognised Fileformat: " << format << endl;
946                 return format;
947         }
948
949         lyxerr[Debug::GRAPHICS]
950                 << "filetools(getFormatFromContents)\n"
951                 << "\tCouldn't find a known format!\n";
952         return string();
953 }
954
955
956 /// check for zipped file
957 bool zippedFile(FileName const & name)
958 {
959         string const type = getFormatFromContents(name);
960         if (contains("gzip zip compress", type) && !type.empty())
961                 return true;
962         return false;
963 }
964
965
966 string const unzippedFileName(string const & zipped_file)
967 {
968         string const ext = getExtension(zipped_file);
969         if (ext == "gz" || ext == "z" || ext == "Z")
970                 return changeExtension(zipped_file, string());
971         return "unzipped_" + zipped_file;
972 }
973
974
975 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
976 {
977         FileName const tempfile = FileName(unzipped_file.empty() ?
978                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
979                 unzipped_file);
980         // Run gunzip
981         string const command = "gunzip -c " +
982                 zipped_file.toFilesystemEncoding() + " > " +
983                 tempfile.toFilesystemEncoding();
984         Systemcall one;
985         one.startscript(Systemcall::Wait, command);
986         // test that command was executed successfully (anon)
987         // yes, please do. (Lgb)
988         return tempfile;
989 }
990
991
992 docstring const makeDisplayPath(string const & path, unsigned int threshold)
993 {
994         string str = path;
995         string const home = package().home_dir();
996
997         // replace /home/blah with ~/
998         if (!home.empty() && prefixIs(str, home))
999                 str = subst(str, home, "~");
1000
1001         if (str.length() <= threshold)
1002                 return from_utf8(os::external_path(str));
1003
1004         string const prefix = ".../";
1005         string temp;
1006
1007         while (str.length() > threshold)
1008                 str = split(str, temp, '/');
1009
1010         // Did we shorten everything away?
1011         if (str.empty()) {
1012                 // Yes, filename itself is too long.
1013                 // Pick the start and the end of the filename.
1014                 str = onlyFilename(path);
1015                 string const head = str.substr(0, threshold / 2 - 3);
1016
1017                 string::size_type len = str.length();
1018                 string const tail =
1019                         str.substr(len - threshold / 2 - 2, len - 1);
1020                 str = head + "..." + tail;
1021         }
1022
1023         return from_utf8(os::external_path(prefix + str));
1024 }
1025
1026
1027 bool readLink(string const & file, string & link, bool resolve)
1028 {
1029 #ifdef HAVE_READLINK
1030         char linkbuffer[512];
1031         // Should be PATH_MAX but that needs autconf support
1032         int const nRead = ::readlink(file.c_str(),
1033                                      linkbuffer, sizeof(linkbuffer) - 1);
1034         if (nRead <= 0)
1035                 return false;
1036         linkbuffer[nRead] = '\0'; // terminator
1037         if (resolve)
1038                 link = makeAbsPath(linkbuffer, onlyPath(file)).absFilename();
1039         else
1040                 link = linkbuffer;
1041         return true;
1042 #else
1043         return false;
1044 #endif
1045 }
1046
1047
1048 cmd_ret const runCommand(string const & cmd)
1049 {
1050         // FIXME: replace all calls to RunCommand with ForkedCall
1051         // (if the output is not needed) or the code in ispell.C
1052         // (if the output is needed).
1053
1054         // One question is if we should use popen or
1055         // create our own popen based on fork, exec, pipe
1056         // of course the best would be to have a
1057         // pstream (process stream), with the
1058         // variants ipstream, opstream
1059
1060 #if defined (HAVE_POPEN)
1061         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1062 #elif defined (HAVE__POPEN)
1063         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1064 #else
1065 #error No popen() function.
1066 #endif
1067
1068         // (Claus Hentschel) Check if popen was succesful ;-)
1069         if (!inf) {
1070                 lyxerr << "RunCommand:: could not start child process" << endl;
1071                 return make_pair(-1, string());
1072         }
1073
1074         string ret;
1075         int c = fgetc(inf);
1076         while (c != EOF) {
1077                 ret += static_cast<char>(c);
1078                 c = fgetc(inf);
1079         }
1080
1081 #if defined (HAVE_PCLOSE)
1082         int const pret = pclose(inf);
1083 #elif defined (HAVE__PCLOSE)
1084         int const pret = _pclose(inf);
1085 #else
1086 #error No pclose() function.
1087 #endif
1088
1089         if (pret == -1)
1090                 perror("RunCommand:: could not terminate child process");
1091
1092         return make_pair(pret, ret);
1093 }
1094
1095
1096 FileName const findtexfile(string const & fil, string const & /*format*/)
1097 {
1098         /* There is no problem to extend this function too use other
1099            methods to look for files. It could be setup to look
1100            in environment paths and also if wanted as a last resort
1101            to a recursive find. One of the easier extensions would
1102            perhaps be to use the LyX file lookup methods. But! I am
1103            going to implement this until I see some demand for it.
1104            Lgb
1105         */
1106
1107         // If the file can be found directly, we just return a
1108         // absolute path version of it.
1109         FileName const absfile(makeAbsPath(fil));
1110         if (fs::exists(absfile.toFilesystemEncoding()))
1111                 return absfile;
1112
1113         // No we try to find it using kpsewhich.
1114         // It seems from the kpsewhich manual page that it is safe to use
1115         // kpsewhich without --format: "When the --format option is not
1116         // given, the search path used when looking for a file is inferred
1117         // from the name given, by looking for a known extension. If no
1118         // known extension is found, the search path for TeX source files
1119         // is used."
1120         // However, we want to take advantage of the format sine almost all
1121         // the different formats has environment variables that can be used
1122         // to controll which paths to search. f.ex. bib looks in
1123         // BIBINPUTS and TEXBIB. Small list follows:
1124         // bib - BIBINPUTS, TEXBIB
1125         // bst - BSTINPUTS
1126         // graphic/figure - TEXPICTS, TEXINPUTS
1127         // ist - TEXINDEXSTYLE, INDEXSTYLE
1128         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1129         // tex - TEXINPUTS
1130         // tfm - TFMFONTS, TEXFONTS
1131         // This means that to use kpsewhich in the best possible way we
1132         // should help it by setting additional path in the approp. envir.var.
1133         string const kpsecmd = "kpsewhich " + fil;
1134
1135         cmd_ret const c = runCommand(kpsecmd);
1136
1137         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1138                  << "kpse result = `" << rtrim(c.second, "\n\r")
1139                  << '\'' << endl;
1140         if (c.first != -1)
1141                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1142                                                         "\n\r")));
1143         else
1144                 return FileName();
1145 }
1146
1147
1148 void removeAutosaveFile(string const & filename)
1149 {
1150         string a = onlyPath(filename);
1151         a += '#';
1152         a += onlyFilename(filename);
1153         a += '#';
1154         FileName const autosave(a);
1155         if (fs::exists(autosave.toFilesystemEncoding()))
1156                 unlink(autosave);
1157 }
1158
1159
1160 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1161         string const & message)
1162 {
1163         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1164                 << message << std::endl;
1165 #ifdef WITH_WARNINGS
1166 #warning Why is this func deleting a file? (Lgb)
1167 #endif
1168         if (zipped)
1169                 unlink(file);
1170 }
1171
1172
1173 string const readBB_from_PSFile(FileName const & file)
1174 {
1175         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1176         // It seems that every command in the header has an own line,
1177         // getline() should work for all files.
1178         // On the other hand some plot programs write the bb at the
1179         // end of the file. Than we have in the header:
1180         // %%BoundingBox: (atend)
1181         // In this case we must check the end.
1182         bool zipped = zippedFile(file);
1183         FileName const file_ = zipped ? unzipFile(file) : file;
1184         string const format = getFormatFromContents(file_);
1185
1186         if (format != "eps" && format != "ps") {
1187                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1188                 return string();
1189         }
1190
1191         static boost::regex bbox_re(
1192                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1193         std::ifstream is(file_.toFilesystemEncoding().c_str());
1194         while (is) {
1195                 string s;
1196                 getline(is,s);
1197                 boost::smatch what;
1198                 if (regex_match(s, what, bbox_re)) {
1199                         // Our callers expect the tokens in the string
1200                         // separated by single spaces.
1201                         // FIXME: change return type from string to something
1202                         // sensible
1203                         ostringstream os;
1204                         os << what.str(1) << ' ' << what.str(2) << ' '
1205                            << what.str(3) << ' ' << what.str(4);
1206                         string const bb = os.str();
1207                         readBB_lyxerrMessage(file_, zipped, bb);
1208                         return bb;
1209                 }
1210         }
1211         readBB_lyxerrMessage(file_, zipped, "no bb found");
1212         return string();
1213 }
1214
1215
1216 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1217 {
1218         // If the original is newer than the copy, then copy the original
1219         // to the new directory.
1220
1221         string const file1 = filename1.toFilesystemEncoding();
1222         string const file2 = filename2.toFilesystemEncoding();
1223         int cmp = 0;
1224         if (fs::exists(file1) && fs::exists(file2)) {
1225                 double const tmp = difftime(fs::last_write_time(file1),
1226                                             fs::last_write_time(file2));
1227                 if (tmp != 0)
1228                         cmp = tmp > 0 ? 1 : -1;
1229
1230         } else if (fs::exists(file1)) {
1231                 cmp = 1;
1232         } else if (fs::exists(file2)) {
1233                 cmp = -1;
1234         }
1235
1236         return cmp;
1237 }
1238
1239 } //namespace support
1240 } // namespace lyx