1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Pan - A Newsreader for Gtk+
4 * Copyright (C) 2002-2006 Charles Kerr <charles@rebelbase.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
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, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include <config.h>
21 #include <cassert>
22 #include <cerrno>
23 #include <cctype>
24
25 #include <unistd.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #ifndef G_OS_WIN32
31 #include <pwd.h>
32 #endif
33
34 #include "debug.h"
35 #include "log.h"
36 #include "messages.h"
37 #include "file-util.h"
38 #include "e-util.h"
39 #include "utf8-utils.h"
40 #include <algorithm>
41
42
43
44 using namespace pan;
45
46 #define is_nonempty_string(a) ((a) && (*a))
47 #define NL std::endl
48
49 std::ostream&
print_file_info(std::ostream & os,const char * file)50 file :: print_file_info (std::ostream& os, const char* file)
51 {
52 EvolutionDateMaker dm;
53 struct stat sb;
54 int ret = stat(file,&sb);
55
56 os << "File information for file "<<file<<NL;
57 if (ret)
58 {
59 os << "File not found / accessible!"<<NL;
60 return os;
61 }
62 os << "Umask : "<<sb.st_mode<<NL;
63 os << "User ID : "<< sb.st_uid<<NL;
64 os << "Group ID : "<< sb.st_gid<<NL;
65 os << "Size (Bytes) : "<<sb.st_size<<NL;
66 os << "Last accessed : "<<dm.get_date_string(sb.st_atime)<<NL;
67 os << "Last modified : "<<dm.get_date_string(sb.st_mtime)<<NL;
68 os << "Last status change : "<<dm.get_date_string(sb.st_ctime)<<NL;
69
70 return os;
71 }
72
73 /***
74 ****
75 ***/
76
77 const std::string &
get_pan_home()78 file :: get_pan_home ()
79 {
80 static std::string pan_home;
81 if (pan_home.empty())
82 {
83 const char * env_str = g_getenv ("PAN_HOME");
84 if (env_str && *env_str)
85 pan_home = env_str;
86 else {
87 char * pch = g_build_filename (g_get_home_dir(), ".pan2", NULL);
88 pan_home = pch;
89 g_free (pch);
90 }
91 }
92
93 ensure_dir_exists (pan_home);
94 return pan_home;
95 }
96
97 std::string
absolute_fn(const std::string & dir,const std::string & base)98 file :: absolute_fn(const std::string &dir, const std::string &base)
99 {
100 const char *fn = base.c_str();
101 if (g_path_is_absolute(fn))
102 return base;
103 const char *ph = file::get_pan_home().c_str();
104 char *temp = g_build_filename(ph, dir.empty() ? "" : dir.c_str(), fn, NULL);
105 std::string out(temp);
106 g_free(temp);
107 return out;
108 }
109
110 const char*
pan_strerror(int error_number)111 file :: pan_strerror (int error_number)
112 {
113 const char * pch (g_strerror (error_number));
114 return pch && *pch ? pch : "";
115 }
116
117 namespace
118 {
119
120 enum EX_ERRORS
121 {
122 EX_NOFILE, EX_BIT, EX_SUCCESS
123 };
124
check_executable_bit(const char * d)125 EX_ERRORS check_executable_bit(const char* d)
126 {
127 #ifndef G_OS_WIN32
128 struct stat sb;
129 if (stat (d, &sb) == -1) return EX_NOFILE;
130 const char* user(g_get_user_name());
131 struct passwd* pw(getpwnam(user));
132 if ((sb.st_mode & S_IXUSR) || ((sb.st_mode & S_IXGRP ) && pw->pw_gid == sb.st_gid))
133 return EX_SUCCESS;
134 return EX_BIT;
135 #else
136 return EX_SUCCESS;
137 #endif
138 }
139 }
140
141 bool
ensure_dir_exists(const StringView & dirname_sv)142 file :: ensure_dir_exists (const StringView& dirname_sv)
143 {
144 assert (!dirname_sv.empty());
145
146 pan_return_val_if_fail (!dirname_sv.empty(), true);
147 bool retval (true);
148 const std::string dirname (dirname_sv.to_string());
149 EX_ERRORS cmd (check_executable_bit(dirname.c_str()));
150 if (cmd == EX_BIT) goto _set_bit;
151
152 if (!g_file_test (dirname.c_str(), G_FILE_TEST_IS_DIR))
153 retval = !g_mkdir_with_parents (dirname.c_str(), 0740); // changed from 755
154
155 if (!retval)
156 {
157 // check for executable bit
158 Log::add_err_va("Error creating directory '%s' : %s", dirname.c_str(),
159 cmd == EX_NOFILE ? "error accessing file." : "executable bit not set.");
160 // set it manually
161 _set_bit:
162 if (cmd == EX_BIT)
163 if (chmod(dirname.c_str(), 0740))
164 {
165 Log::add_urgent_va("Error setting executable bit for directory '%s' : "
166 "Please check your permissions.", dirname.c_str());
167 }
168 }
169 return retval;
170 }
171
172 bool
file_exists(const char * filename)173 file :: file_exists (const char * filename)
174 {
175 return filename && *filename && g_file_test (filename, G_FILE_TEST_EXISTS);
176 }
177
178
179 /**
180 *** Attempt to make a filename safe for use.
181 *** This is done by replacing illegal characters with '_'.
182 *** This function assumes the input is UTF8 since gmime uses UTF8 interface.
183 *** return value must be g_free'd.
184 **/
185 std::string
sanitize(const StringView & fname)186 file :: sanitize (const StringView& fname)
187 {
188 std::string ret;
189
190 // sanity checks
191 pan_return_val_if_fail (!fname.empty(), ret);
192
193 ret = content_to_utf8(fname);
194
195 // strip illegal characters
196 # ifdef G_OS_WIN32
197 static const char* illegal_chars = "/\\:?*\"\'<>|";
198 # else
199 static const char* illegal_chars = "/\\";
200 # endif
201 for (const char *pch(illegal_chars); *pch; ++pch)
202 std::replace (ret.begin(), ret.end(), *pch, '_');
203
204 return ret;
205 }
206
207 char*
normalize_inplace(char * filename)208 file :: normalize_inplace (char * filename)
209 {
210 register char *in, *out;
211 pan_return_val_if_fail (filename && *filename, filename);
212
213 for (in=out=filename; *in; )
214 if (in[0]==G_DIR_SEPARATOR && in[1]==G_DIR_SEPARATOR)
215 ++in;
216 else
217 *out++ = *in++;
218 *out = '\0';
219
220 return filename;
221 }
222
223 /**
224 *** Makes a unique filename given an optional path and a starting file name.
225 *** The filename is sanitized before looking for uniqueness.
226 **/
227 char*
get_unique_fname(const gchar * path,const gchar * fname)228 file :: get_unique_fname ( const gchar *path, const gchar *fname)
229 {
230 // sanity checks
231 pan_return_val_if_fail (is_nonempty_string (fname), NULL);
232
233 // sanitize filename
234 std::string tmp = sanitize (fname);
235 GString * filename = g_string_new_len (tmp.c_str(), tmp.size());
236
237 // add the directory & look for uniqueness
238 const char * front = filename->str;
239 const char * lastdot = strrchr (front, '.');
240 char * lead;
241 char * tail;
242 if (lastdot == NULL) {
243 lead = g_strdup (front);
244 tail = g_strdup ("");
245 } else {
246 lead = g_strndup (front, lastdot-front);
247 tail = g_strdup (lastdot);
248 }
249
250 for (int i=1;; ++i)
251 {
252 char * unique;
253
254 if (i==1 && is_nonempty_string(path))
255 {
256 unique = g_strdup_printf ("%s%c%s%s",
257 path, G_DIR_SEPARATOR,
258 lead, tail);
259 }
260 else if (i==1)
261 {
262 unique = g_strdup_printf ("%s%s", lead, tail);
263 }
264 else if (is_nonempty_string(path))
265 {
266 unique = g_strdup_printf ("%s%c%s_copy_%d%s",
267 path, G_DIR_SEPARATOR,
268 lead, i, tail);
269 }
270 else
271 {
272 unique = g_strdup_printf ("%s_copy_%d%s", lead, i, tail);
273 }
274
275 if (!file_exists (unique)) {
276 g_string_assign (filename, unique);
277 g_free (unique);
278 break;
279 }
280
281 g_free (unique);
282 }
283
284 /* cleanup */
285 g_free (lead);
286 g_free (tail);
287
288 return normalize_inplace (g_string_free (filename, FALSE));
289 }
290
291 /***
292 ****
293 ***/
294
295 bool
get_text_file_contents(const StringView & filename,std::string & setme,const char * fallback_charset_1,const char * fallback_charset_2)296 file :: get_text_file_contents (const StringView & filename,
297 std::string & setme,
298 const char * fallback_charset_1,
299 const char * fallback_charset_2)
300 {
301 // read in the file...
302 char * body (0);
303 gsize body_len (0);
304 GError * err (0);
305 const std::string fname (filename.str, filename.len);
306 g_file_get_contents (fname.c_str(), &body, &body_len, &err);
307 if (err) {
308 Log::add_err_va (_("Error reading file “%s”: %s"), err->message, g_strerror(errno));
309 g_clear_error (&err);
310 return false;
311 }
312
313 // CRLF => LF
314 body_len = std::remove (body, body+body_len, '\r') - body;
315
316 // utf8
317 setme = content_to_utf8 (StringView (body, body_len),
318 fallback_charset_1,
319 fallback_charset_2);
320
321 g_free (body);
322 return true;
323 }
324