1 /* diritor.cc: Iterator through entries in a directory.
2 *
3 * Copyright (C) 2007,2008,2010,2014 Olly Betts
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <config.h>
21
22 #include "diritor.h"
23
24 #include "safeunistd.h"
25 #include <sys/types.h>
26 #include "safeerrno.h"
27
28 #include <cstring>
29
30 using namespace std;
31
32 #if defined O_NOATIME && O_NOATIME != 0
33 uid_t DirectoryIterator::euid = geteuid();
34 #endif
35
36 #ifdef HAVE_MAGIC_H
37 magic_t DirectoryIterator::magic_cookie = NULL;
38 #endif
39
40 void
call_stat()41 DirectoryIterator::call_stat()
42 {
43 build_path();
44 int retval;
45 #ifdef HAVE_LSTAT
46 if (follow_symlinks) {
47 #endif
48 retval = stat(path.c_str(), &statbuf);
49 #ifdef HAVE_LSTAT
50 } else {
51 retval = lstat(path.c_str(), &statbuf);
52 }
53 #endif
54 if (retval == -1) {
55 string error = "Can't stat \"";
56 error += path;
57 error += "\" (";
58 error += strerror(errno);
59 error += ')';
60 throw error;
61 }
62 }
63
64 void
build_path()65 DirectoryIterator::build_path()
66 {
67 if (path.length() == path_len) {
68 path += '/';
69 path += leafname();
70 }
71 }
72
73 void
start(const std::string & path_)74 DirectoryIterator::start(const std::string & path_)
75 {
76 if (dir) closedir(dir);
77 path = path_;
78 path_len = path.length();
79 dir = opendir(path.c_str());
80 if (dir == NULL) {
81 string error = "Can't open directory \"";
82 error += path;
83 error += "\" (";
84 error += strerror(errno);
85 error += ')';
86 throw error;
87 }
88 }
89
90 void
next_failed() const91 DirectoryIterator::next_failed() const
92 {
93 string error = "Can't read next entry from directory \"";
94 error += path;
95 error += "\" (";
96 error += strerror(errno);
97 error += ')';
98 throw error;
99 }
100
101 #ifdef HAVE_MAGIC_H
102 string
get_magic_mimetype()103 DirectoryIterator::get_magic_mimetype()
104 {
105 if (rare(magic_cookie == NULL)) {
106 #ifdef MAGIC_MIME_TYPE
107 magic_cookie = magic_open(MAGIC_SYMLINK|MAGIC_MIME_TYPE|MAGIC_ERROR);
108 #else
109 // MAGIC_MIME_TYPE was added in 4.22, released 2007-12-27. If we don't
110 // have it then use MAGIC_MIME instead and trim any encoding off below.
111 magic_cookie = magic_open(MAGIC_SYMLINK|MAGIC_MIME|MAGIC_ERROR);
112 #endif
113 if (magic_cookie == NULL) {
114 string m("Failed to initialise the file magic library: ");
115 m += strerror(errno);
116 throw m;
117 }
118 if (magic_load(magic_cookie, NULL) == -1) {
119 string m("Failed to load the file magic database");
120 const char * err = magic_error(magic_cookie);
121 if (err) {
122 m += ": ";
123 m += err;
124 }
125 throw m;
126 }
127 }
128
129 // FIXME: handle NOATIME here and share the fd with load_file().
130 build_path();
131 const char * res = magic_file(magic_cookie, path.c_str());
132 if (!res) {
133 const char * err = magic_error(magic_cookie);
134 if (rare(err)) {
135 string m("Failed to use magic on file: ");
136 m += err;
137 throw m;
138 }
139 return string();
140 }
141
142 // Sometimes libmagic returns this string instead of a mime-type for some
143 // Microsoft documents, so pick a suitable MIME content-type based on the
144 // extension. Newer versions seem to return "application/CDFV2-corrupt"
145 // instead for this case (on Debian, file 5.11 gives the former and file
146 // 5.18 the latter).
147 #define COMPOSITE_DOC "Composite Document File V2 Document"
148 if (strncmp(res, COMPOSITE_DOC, sizeof(COMPOSITE_DOC) - 1) == 0 ||
149 strcmp(res, "application/CDFV2-corrupt") == 0) {
150 // Default to something self-explanatory.
151 res = "application/x-compound-document-file";
152 const char * leaf = leafname();
153 const char * ext = strrchr(leaf, '.');
154 if (ext && strlen(++ext) == 3) {
155 char e[3];
156 for (int i = 0; i != 3; ++i) {
157 if (ext[i] <= 'Z' && ext[i] >= 'A')
158 e[i] = ext[i] + ('a' - 'A');
159 else
160 e[i] = ext[i];
161 }
162 switch (e[0]) {
163 case 'd':
164 if (e[1] == 'o')
165 res = "application/msword";
166 break;
167 case 'm':
168 if (e[1] == 's' && e[2] == 'g')
169 res = "application/vnd.ms-outlook";
170 break;
171 case 'p':
172 if (e[1] == 'p' || e[1] == 'o')
173 res = "application/vnd.ms-powerpoint";
174 else if (e[1] == 'u' && e[2] == 'b')
175 res = "application/x-mspublisher";
176 break;
177 case 'x':
178 if (e[1] == 'l')
179 res = "application/vnd.ms-excel";
180 break;
181 case 'w':
182 if (e[1] == 'p' && e[2] != 'd')
183 res = "application/vnd.ms-works";
184 break;
185 }
186 }
187 } else {
188 #ifndef MAGIC_MIME_TYPE
189 // Discard any encoding returned.
190 char * spc = strchr(res, ' ');
191 if (spc)
192 *spc = '\0';
193 #endif
194 }
195
196 return res;
197 }
198 #endif
199