1 //========================================================================
2 //
3 // gfile.cc
4 //
5 // Miscellaneous file and directory name manipulation.
6 //
7 // Copyright 1996-2003 Glyph & Cog, LLC
8 //
9 //========================================================================
10 
11 #include <aconf.h>
12 
13 #ifdef _WIN32
14 #  undef WIN32_LEAN_AND_MEAN
15 #  include <windows.h>
16 #  include <time.h>
17 #  include <direct.h>
18 #  include <shobjidl.h>
19 #  include <shlguid.h>
20 #else
21 #  if !defined(ACORN)
22 #    include <sys/types.h>
23 #    include <sys/stat.h>
24 #    include <fcntl.h>
25 #  endif
26 #  include <time.h>
27 #  include <limits.h>
28 #  include <string.h>
29 #  if !defined(VMS) && !defined(ACORN)
30 #    include <pwd.h>
31 #  endif
32 #  if defined(VMS) && (__DECCXX_VER < 50200000)
33 #    include <unixlib.h>
34 #  endif
35 #endif // _WIN32
36 #include "gmem.h"
37 #include "gmempp.h"
38 #include "GString.h"
39 #include "gfile.h"
40 
41 // Some systems don't define this, so just make it something reasonably
42 // large.
43 #ifndef PATH_MAX
44 #define PATH_MAX 1024
45 #endif
46 
47 //------------------------------------------------------------------------
48 
getHomeDir()49 GString *getHomeDir() {
50 #ifdef VMS
51   //---------- VMS ----------
52   return new GString("SYS$LOGIN:");
53 
54 #elif defined(__EMX__) || defined(_WIN32)
55   //---------- OS/2+EMX and Win32 ----------
56   char *s;
57   GString *ret;
58 
59   if ((s = getenv("HOME")))
60     ret = new GString(s);
61   else
62     ret = new GString(".");
63   return ret;
64 
65 #elif defined(ACORN)
66   //---------- RISCOS ----------
67   return new GString("@");
68 
69 #else
70   //---------- Unix ----------
71   char *s;
72   struct passwd *pw;
73   GString *ret;
74 
75   if ((s = getenv("HOME"))) {
76     ret = new GString(s);
77   } else {
78     if ((s = getenv("USER")))
79       pw = getpwnam(s);
80     else
81       pw = getpwuid(getuid());
82     if (pw)
83       ret = new GString(pw->pw_dir);
84     else
85       ret = new GString(".");
86   }
87   return ret;
88 #endif
89 }
90 
getCurrentDir()91 GString *getCurrentDir() {
92   char buf[PATH_MAX+1];
93 
94 #if defined(__EMX__)
95   if (_getcwd2(buf, sizeof(buf)))
96 #elif defined(_WIN32)
97   if (GetCurrentDirectoryA(sizeof(buf), buf))
98 #elif defined(ACORN)
99   if (strcpy(buf, "@"))
100 #else
101   if (getcwd(buf, sizeof(buf)))
102 #endif
103     return new GString(buf);
104   return new GString();
105 }
106 
appendToPath(GString * path,const char * fileName)107 GString *appendToPath(GString *path, const char *fileName) {
108 #if defined(VMS)
109   //---------- VMS ----------
110   //~ this should handle everything necessary for file
111   //~ requesters, but it's certainly not complete
112   char *p0, *p1, *p2;
113   char *q1;
114 
115   p0 = path->getCString();
116   p1 = p0 + path->getLength() - 1;
117   if (!strcmp(fileName, "-")) {
118     if (*p1 == ']') {
119       for (p2 = p1; p2 > p0 && *p2 != '.' && *p2 != '['; --p2) ;
120       if (*p2 == '[')
121 	++p2;
122       path->del(p2 - p0, p1 - p2);
123     } else if (*p1 == ':') {
124       path->append("[-]");
125     } else {
126       path->clear();
127       path->append("[-]");
128     }
129   } else if ((q1 = strrchr(fileName, '.')) && !strncmp(q1, ".DIR;", 5)) {
130     if (*p1 == ']') {
131       path->insert(p1 - p0, '.');
132       path->insert(p1 - p0 + 1, fileName, q1 - fileName);
133     } else if (*p1 == ':') {
134       path->append('[');
135       path->append(']');
136       path->append(fileName, q1 - fileName);
137     } else {
138       path->clear();
139       path->append(fileName, q1 - fileName);
140     }
141   } else {
142     if (*p1 != ']' && *p1 != ':')
143       path->clear();
144     path->append(fileName);
145   }
146   return path;
147 
148 #elif defined(_WIN32)
149   //---------- Win32 ----------
150   GString *tmp;
151   char buf[256];
152   char *fp;
153 
154   tmp = new GString(path);
155   tmp->append('/');
156   tmp->append(fileName);
157   GetFullPathNameA(tmp->getCString(), sizeof(buf), buf, &fp);
158   delete tmp;
159   path->clear();
160   path->append(buf);
161   return path;
162 
163 #elif defined(ACORN)
164   //---------- RISCOS ----------
165   char *p;
166   int i;
167 
168   path->append(".");
169   i = path->getLength();
170   path->append(fileName);
171   for (p = path->getCString() + i; *p; ++p) {
172     if (*p == '/') {
173       *p = '.';
174     } else if (*p == '.') {
175       *p = '/';
176     }
177   }
178   return path;
179 
180 #elif defined(__EMX__)
181   //---------- OS/2+EMX ----------
182   int i;
183 
184   // appending "." does nothing
185   if (!strcmp(fileName, "."))
186     return path;
187 
188   // appending ".." goes up one directory
189   if (!strcmp(fileName, "..")) {
190     for (i = path->getLength() - 2; i >= 0; --i) {
191       if (path->getChar(i) == '/' || path->getChar(i) == '\\' ||
192 	  path->getChar(i) == ':')
193 	break;
194     }
195     if (i <= 0) {
196       if (path->getChar(0) == '/' || path->getChar(0) == '\\') {
197 	path->del(1, path->getLength() - 1);
198       } else if (path->getLength() >= 2 && path->getChar(1) == ':') {
199 	path->del(2, path->getLength() - 2);
200       } else {
201 	path->clear();
202 	path->append("..");
203       }
204     } else {
205       if (path->getChar(i-1) == ':')
206 	++i;
207       path->del(i, path->getLength() - i);
208     }
209     return path;
210   }
211 
212   // otherwise, append "/" and new path component
213   if (path->getLength() > 0 &&
214       path->getChar(path->getLength() - 1) != '/' &&
215       path->getChar(path->getLength() - 1) != '\\')
216     path->append('/');
217   path->append(fileName);
218   return path;
219 
220 #else
221   //---------- Unix ----------
222   int i;
223 
224   // appending "." does nothing
225   if (!strcmp(fileName, "."))
226     return path;
227 
228   // appending ".." goes up one directory
229   if (!strcmp(fileName, "..")) {
230     for (i = path->getLength() - 2; i >= 0; --i) {
231       if (path->getChar(i) == '/')
232 	break;
233     }
234     if (i <= 0) {
235       if (path->getChar(0) == '/') {
236 	path->del(1, path->getLength() - 1);
237       } else {
238 	path->clear();
239 	path->append("..");
240       }
241     } else {
242       path->del(i, path->getLength() - i);
243     }
244     return path;
245   }
246 
247   // otherwise, append "/" and new path component
248   if (path->getLength() > 0 &&
249       path->getChar(path->getLength() - 1) != '/')
250     path->append('/');
251   path->append(fileName);
252   return path;
253 #endif
254 }
255 
grabPath(char * fileName)256 GString *grabPath(char *fileName) {
257 #ifdef VMS
258   //---------- VMS ----------
259   char *p;
260 
261   if ((p = strrchr(fileName, ']')))
262     return new GString(fileName, p + 1 - fileName);
263   if ((p = strrchr(fileName, ':')))
264     return new GString(fileName, p + 1 - fileName);
265   return new GString();
266 
267 #elif defined(__EMX__) || defined(_WIN32)
268   //---------- OS/2+EMX and Win32 ----------
269   char *p;
270 
271   if ((p = strrchr(fileName, '/')))
272     return new GString(fileName, (int)(p - fileName));
273   if ((p = strrchr(fileName, '\\')))
274     return new GString(fileName, (int)(p - fileName));
275   if ((p = strrchr(fileName, ':')))
276     return new GString(fileName, (int)(p + 1 - fileName));
277   return new GString();
278 
279 #elif defined(ACORN)
280   //---------- RISCOS ----------
281   char *p;
282 
283   if ((p = strrchr(fileName, '.')))
284     return new GString(fileName, p - fileName);
285   return new GString();
286 
287 #else
288   //---------- Unix ----------
289   char *p;
290 
291   if ((p = strrchr(fileName, '/')))
292     return new GString(fileName, (int)(p - fileName));
293   return new GString();
294 #endif
295 }
296 
isAbsolutePath(char * path)297 GBool isAbsolutePath(char *path) {
298 #ifdef VMS
299   //---------- VMS ----------
300   return strchr(path, ':') ||
301 	 (path[0] == '[' && path[1] != '.' && path[1] != '-');
302 
303 #elif defined(__EMX__) || defined(_WIN32)
304   //---------- OS/2+EMX and Win32 ----------
305   return path[0] == '/' || path[0] == '\\' || path[1] == ':';
306 
307 #elif defined(ACORN)
308   //---------- RISCOS ----------
309   return path[0] == '$';
310 
311 #else
312   //---------- Unix ----------
313   return path[0] == '/';
314 #endif
315 }
316 
makePathAbsolute(GString * path)317 GString *makePathAbsolute(GString *path) {
318 #ifdef VMS
319   //---------- VMS ----------
320   char buf[PATH_MAX+1];
321 
322   if (!isAbsolutePath(path->getCString())) {
323     if (getcwd(buf, sizeof(buf))) {
324       path->insert(0, buf);
325     }
326   }
327   return path;
328 
329 #elif defined(_WIN32)
330   //---------- Win32 ----------
331   char buf[_MAX_PATH];
332   char *fp;
333 
334   buf[0] = '\0';
335   if (!GetFullPathNameA(path->getCString(), _MAX_PATH, buf, &fp)) {
336     path->clear();
337     return path;
338   }
339   path->clear();
340   path->append(buf);
341   return path;
342 
343 #elif defined(ACORN)
344   //---------- RISCOS ----------
345   path->insert(0, '@');
346   return path;
347 
348 #else
349   //---------- Unix and OS/2+EMX ----------
350   struct passwd *pw;
351   char buf[PATH_MAX+1];
352   GString *s;
353   char *p1, *p2;
354   int n;
355 
356   if (path->getChar(0) == '~') {
357     if (path->getChar(1) == '/' ||
358 #ifdef __EMX__
359 	path->getChar(1) == '\\' ||
360 #endif
361 	path->getLength() == 1) {
362       path->del(0, 1);
363       s = getHomeDir();
364       path->insert(0, s);
365       delete s;
366     } else {
367       p1 = path->getCString() + 1;
368 #ifdef __EMX__
369       for (p2 = p1; *p2 && *p2 != '/' && *p2 != '\\'; ++p2) ;
370 #else
371       for (p2 = p1; *p2 && *p2 != '/'; ++p2) ;
372 #endif
373       if ((n = (int)(p2 - p1)) > PATH_MAX)
374 	n = PATH_MAX;
375       strncpy(buf, p1, n);
376       buf[n] = '\0';
377       if ((pw = getpwnam(buf))) {
378 	path->del(0, (int)(p2 - p1 + 1));
379 	path->insert(0, pw->pw_dir);
380       }
381     }
382   } else if (!isAbsolutePath(path->getCString())) {
383     if (getcwd(buf, sizeof(buf))) {
384 #ifndef __EMX__
385       path->insert(0, '/');
386 #endif
387       path->insert(0, buf);
388     }
389   }
390   return path;
391 #endif
392 }
393 
getModTime(char * fileName)394 time_t getModTime(char *fileName) {
395 #ifdef _WIN32
396   //~ should implement this, but it's (currently) only used in xpdf
397   return 0;
398 #else
399   struct stat statBuf;
400 
401   if (stat(fileName, &statBuf)) {
402     return 0;
403   }
404   return statBuf.st_mtime;
405 #endif
406 }
407 
openTempFile(GString ** name,FILE ** f,const char * mode,const char * ext)408 GBool openTempFile(GString **name, FILE **f,
409 		   const char *mode, const char *ext) {
410 #if defined(_WIN32)
411   //---------- Win32 ----------
412   char tempPath[MAX_PATH + 1];
413   GString *s, *s2;
414   FILE *f2;
415   DWORD n;
416   int t, i;
417 
418   // this has the standard race condition problem, but I haven't found
419   // a better way to generate temp file names with extensions on
420   // Windows
421   n = GetTempPathA(sizeof(tempPath), tempPath);
422   if (n > 0 && n <= sizeof(tempPath)) {
423     s = new GString(tempPath);
424     if (tempPath[n-1] != '\\') {
425       s->append('\\');
426     }
427   } else {
428     s = new GString(".\\");
429   }
430   s->appendf("xpdf_{0:d}_{1:d}_",
431 	     (int)GetCurrentProcessId(), (int)GetCurrentThreadId());
432   t = (int)time(NULL);
433   for (i = 0; i < 1000; ++i) {
434     s2 = GString::format("{0:t}{1:d}", s, t + i);
435     if (ext) {
436       s2->append(ext);
437     }
438     if (!(f2 = fopen(s2->getCString(), "r"))) {
439       if (!(f2 = fopen(s2->getCString(), mode))) {
440 	delete s2;
441 	delete s;
442 	return gFalse;
443       }
444       *name = s2;
445       *f = f2;
446       delete s;
447       return gTrue;
448     }
449     fclose(f2);
450     delete s2;
451   }
452   delete s;
453   return gFalse;
454 #elif defined(VMS) || defined(__EMX__) || defined(ACORN)
455   //---------- non-Unix ----------
456   char *s;
457 
458   // There is a security hole here: an attacker can create a symlink
459   // with this file name after the tmpnam call and before the fopen
460   // call.  I will happily accept fixes to this function for non-Unix
461   // OSs.
462   if (!(s = tmpnam(NULL))) {
463     return gFalse;
464   }
465   *name = new GString(s);
466   if (ext) {
467     (*name)->append(ext);
468   }
469   if (!(*f = fopen((*name)->getCString(), mode))) {
470     delete (*name);
471     *name = NULL;
472     return gFalse;
473   }
474   return gTrue;
475 #else
476   //---------- Unix ----------
477   char *s;
478   int fd;
479 
480   if (ext) {
481 #if HAVE_MKSTEMPS
482     if ((s = getenv("TMPDIR"))) {
483       *name = new GString(s);
484     } else {
485       *name = new GString("/tmp");
486     }
487     (*name)->append("/XXXXXX")->append(ext);
488     fd = mkstemps((*name)->getCString(), (int)strlen(ext));
489 #else
490     if (!(s = tmpnam(NULL))) {
491       return gFalse;
492     }
493     *name = new GString(s);
494     (*name)->append(ext);
495     fd = open((*name)->getCString(), O_WRONLY | O_CREAT | O_EXCL, 0600);
496 #endif
497   } else {
498 #if HAVE_MKSTEMP
499     if ((s = getenv("TMPDIR"))) {
500       *name = new GString(s);
501     } else {
502       *name = new GString("/tmp");
503     }
504     (*name)->append("/XXXXXX");
505     fd = mkstemp((*name)->getCString());
506 #else // HAVE_MKSTEMP
507     if (!(s = tmpnam(NULL))) {
508       return gFalse;
509     }
510     *name = new GString(s);
511     fd = open((*name)->getCString(), O_WRONLY | O_CREAT | O_EXCL, 0600);
512 #endif // HAVE_MKSTEMP
513   }
514   if (fd < 0 || !(*f = fdopen(fd, mode))) {
515     delete *name;
516     *name = NULL;
517     return gFalse;
518   }
519   return gTrue;
520 #endif
521 }
522 
createDir(char * path,int mode)523 GBool createDir(char *path, int mode) {
524 #ifdef _WIN32
525   return !mkdir(path);
526 #else
527   return !mkdir(path, mode);
528 #endif
529 }
530 
executeCommand(char * cmd)531 GBool executeCommand(char *cmd) {
532 #ifdef VMS
533   return system(cmd) ? gTrue : gFalse;
534 #else
535   return system(cmd) ? gFalse : gTrue;
536 #endif
537 }
538 
539 #ifdef _WIN32
fileNameToUTF8(char * path)540 GString *fileNameToUTF8(char *path) {
541   GString *s;
542   char *p;
543 
544   s = new GString();
545   for (p = path; *p; ++p) {
546     if (*p & 0x80) {
547       s->append((char)(0xc0 | ((*p >> 6) & 0x03)));
548       s->append((char)(0x80 | (*p & 0x3f)));
549     } else {
550       s->append(*p);
551     }
552   }
553   return s;
554 }
555 
fileNameToUTF8(wchar_t * path)556 GString *fileNameToUTF8(wchar_t *path) {
557   GString *s;
558   wchar_t *p;
559 
560   s = new GString();
561   for (p = path; *p; ++p) {
562     if (*p < 0x80) {
563       s->append((char)*p);
564     } else if (*p < 0x800) {
565       s->append((char)(0xc0 | ((*p >> 6) & 0x1f)));
566       s->append((char)(0x80 | (*p & 0x3f)));
567     } else {
568       s->append((char)(0xe0 | ((*p >> 12) & 0x0f)));
569       s->append((char)(0x80 | ((*p >> 6) & 0x3f)));
570       s->append((char)(0x80 | (*p & 0x3f)));
571     }
572   }
573   return s;
574 }
575 
fileNameToUCS2(const char * path,wchar_t * out,size_t outSize)576 wchar_t *fileNameToUCS2(const char *path, wchar_t *out, size_t outSize) {
577   const char *p;
578   size_t i;
579 
580   for (p = path, i = 0; *p && i < outSize - 1; ++i) {
581     if ((p[0] & 0xe0) == 0xc0 &&
582 	p[1] && (p[1] & 0xc0) == 0x80) {
583       out[i] = (wchar_t)(((p[0] & 0x1f) << 6) |
584 			  (p[1] & 0x3f));
585       p += 2;
586     } else if ((p[0] & 0xf0) == 0xe0 &&
587 	       (p[1] & 0xc0) == 0x80 &&
588 	       (p[2] & 0xc0) == 0x80) {
589       out[i] = (wchar_t)(((p[0] & 0x0f) << 12) |
590 			 ((p[1] & 0x3f) << 6) |
591 			  (p[2] & 0x3f));
592       p += 3;
593     } else {
594       out[i] = (wchar_t)(p[0] & 0xff);
595       p += 1;
596     }
597   }
598   out[i] = (wchar_t)0;
599   return out;
600 }
601 #endif
602 
openFile(const char * path,const char * mode)603 FILE *openFile(const char *path, const char *mode) {
604 #if defined(_WIN32)
605   wchar_t wPath[_MAX_PATH + 1];
606   wchar_t wMode[8];
607   int i;
608 
609   fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t));
610   for (i = 0; mode[i] && i < sizeof(wMode)/sizeof(wchar_t) - 1; ++i) {
611     wMode[i] = (wchar_t)(mode[i] & 0xff);
612   }
613   wMode[i] = (wchar_t)0;
614   readWindowsShortcut(wPath, _MAX_PATH + 1);
615   return _wfopen(wPath, wMode);
616 #elif defined(VMS)
617   return fopen(path, mode, "ctx=stm");
618 #else
619   return fopen(path, mode);
620 #endif
621 }
622 
623 #ifdef _WIN32
readWindowsShortcut(wchar_t * wPath,size_t wPathSize)624 void readWindowsShortcut(wchar_t *wPath, size_t wPathSize) {
625   size_t n = wcslen(wPath);
626   if (n < 4 || wcscmp(wPath + n - 4, L".lnk")) {
627     return;
628   }
629   IShellLinkW *shellLink;
630   HRESULT hres;
631   hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
632 			  IID_IShellLinkW, (LPVOID *)&shellLink);
633   bool needCoUninit = false;
634   if (hres == CO_E_NOTINITIALIZED) {
635     CoInitialize(NULL);
636     needCoUninit = true;
637     hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
638 			    IID_IShellLinkW, (LPVOID *)&shellLink);
639   }
640   if (FAILED(hres)) {
641     return;
642   }
643   IPersistFile *persistFile;
644   hres = shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
645   if (FAILED(hres)) {
646     return;
647   }
648   hres = persistFile->Load(wPath, STGM_READ);
649   if (FAILED(hres)) {
650     fprintf(stderr, "IPersistFile.Load failed: 0x%08x\n", hres);
651     exit(1);
652   }
653   wchar_t target[_MAX_PATH + 1];
654   hres = shellLink->GetPath(target, _MAX_PATH + 1, NULL, 0);
655   if (FAILED(hres)) {
656     return;
657   }
658   shellLink->Release();
659   if (needCoUninit) {
660     CoUninitialize();
661   }
662   if (wcslen(target) > wPathSize - 1) {
663     return;
664   }
665   wcscpy(wPath, target);
666 }
667 #endif
668 
makeDir(const char * path,int mode)669 int makeDir(const char *path, int mode) {
670 #ifdef _WIN32
671   wchar_t wPath[_MAX_PATH + 1];
672   return _wmkdir(fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t)));
673 #else
674   return mkdir(path, (mode_t)mode);
675 #endif
676 }
677 
getLine(char * buf,int size,FILE * f)678 char *getLine(char *buf, int size, FILE *f) {
679   int c, i;
680 
681   i = 0;
682   while (i < size - 1) {
683     if ((c = fgetc(f)) == EOF) {
684       break;
685     }
686     buf[i++] = (char)c;
687     if (c == '\x0a') {
688       break;
689     }
690     if (c == '\x0d') {
691       c = fgetc(f);
692       if (c == '\x0a' && i < size - 1) {
693 	buf[i++] = (char)c;
694       } else if (c != EOF) {
695 	ungetc(c, f);
696       }
697       break;
698     }
699   }
700   buf[i] = '\0';
701   if (i == 0) {
702     return NULL;
703   }
704   return buf;
705 }
706 
gfseek(FILE * f,GFileOffset offset,int whence)707 int gfseek(FILE *f, GFileOffset offset, int whence) {
708 #if HAVE_FSEEKO
709   return fseeko(f, offset, whence);
710 #elif HAVE_FSEEK64
711   return fseek64(f, offset, whence);
712 #elif HAVE_FSEEKI64
713   return _fseeki64(f, offset, whence);
714 #else
715   return fseek(f, offset, whence);
716 #endif
717 }
718 
gftell(FILE * f)719 GFileOffset gftell(FILE *f) {
720 #if HAVE_FSEEKO
721   return ftello(f);
722 #elif HAVE_FSEEK64
723   return ftell64(f);
724 #elif HAVE_FSEEKI64
725   return _ftelli64(f);
726 #else
727   return ftell(f);
728 #endif
729 }
730 
fixCommandLine(int * argc,char ** argv[])731 void fixCommandLine(int *argc, char **argv[]) {
732 #ifdef _WIN32
733   int argcw;
734   wchar_t **argvw;
735   GString *arg;
736   int i;
737 
738   argvw = CommandLineToArgvW(GetCommandLineW(), &argcw);
739   if (!argvw || argcw < 0) {
740     return;
741   }
742 
743   *argc = argcw;
744 
745   *argv = (char **)gmallocn(argcw + 1, sizeof(char *));
746   for (i = 0; i < argcw; ++i) {
747     arg = fileNameToUTF8(argvw[i]);
748     (*argv)[i] = copyString(arg->getCString());
749     delete arg;
750   }
751   (*argv)[argcw] = NULL;
752 
753   LocalFree(argvw);
754 #endif
755 }
756