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