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