1 /*
2 * Copyright (C) 1984-2012 Mark Nudelman
3 * Modified for use with illumos by Garrett D'Amore.
4 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
5 *
6 * You may distribute under the terms of either the GNU General Public
7 * License or the Less License, as specified in the README file.
8 *
9 * For more information, see the README file.
10 */
11
12 /*
13 * Routines to mess around with filenames (and files).
14 * Much of this is very OS dependent.
15 *
16 * Modified for illumos/POSIX -- it uses native glob(3C) rather than
17 * popen to a shell to perform the expansion.
18 */
19
20 #include <sys/stat.h>
21
22 #include <glob.h>
23 #include <stdarg.h>
24
25 #include "less.h"
26
27 extern int force_open;
28 extern int secure;
29 extern int ctldisp;
30 extern int utf_mode;
31 extern IFILE curr_ifile;
32 extern IFILE old_ifile;
33 extern char openquote;
34 extern char closequote;
35
36 /*
37 * Remove quotes around a filename.
38 */
39 char *
shell_unquote(char * str)40 shell_unquote(char *str)
41 {
42 char *name;
43 char *p;
44
45 name = p = ecalloc(strlen(str)+1, sizeof (char));
46 if (*str == openquote) {
47 str++;
48 while (*str != '\0') {
49 if (*str == closequote) {
50 if (str[1] != closequote)
51 break;
52 str++;
53 }
54 *p++ = *str++;
55 }
56 } else {
57 char *esc = get_meta_escape();
58 int esclen = strlen(esc);
59 while (*str != '\0') {
60 if (esclen > 0 && strncmp(str, esc, esclen) == 0)
61 str += esclen;
62 *p++ = *str++;
63 }
64 }
65 *p = '\0';
66 return (name);
67 }
68
69 /*
70 * Get the shell's escape character.
71 */
72 char *
get_meta_escape(void)73 get_meta_escape(void)
74 {
75 char *s;
76
77 s = lgetenv("LESSMETAESCAPE");
78 if (s == NULL)
79 s = "\\";
80 return (s);
81 }
82
83 /*
84 * Get the characters which the shell considers to be "metacharacters".
85 */
86 static char *
metachars(void)87 metachars(void)
88 {
89 static char *mchars = NULL;
90
91 if (mchars == NULL) {
92 mchars = lgetenv("LESSMETACHARS");
93 if (mchars == NULL)
94 mchars = DEF_METACHARS;
95 }
96 return (mchars);
97 }
98
99 /*
100 * Is this a shell metacharacter?
101 */
102 static int
metachar(char c)103 metachar(char c)
104 {
105 return (strchr(metachars(), c) != NULL);
106 }
107
108 /*
109 * Must use quotes rather than escape characters for this meta character.
110 */
111 static int
must_quote(char c)112 must_quote(char c)
113 {
114 return (c == '\n');
115 }
116
117 /*
118 * Insert a backslash before each metacharacter in a string.
119 */
120 char *
shell_quote(const char * s)121 shell_quote(const char *s)
122 {
123 const char *p;
124 char *r;
125 char *newstr;
126 int len;
127 char *esc = get_meta_escape();
128 int esclen = strlen(esc);
129 int use_quotes = 0;
130 int have_quotes = 0;
131
132 /*
133 * Determine how big a string we need to allocate.
134 */
135 len = 1; /* Trailing null byte */
136 for (p = s; *p != '\0'; p++) {
137 len++;
138 if (*p == openquote || *p == closequote)
139 have_quotes = 1;
140 if (metachar(*p)) {
141 if (esclen == 0) {
142 /*
143 * We've got a metachar, but this shell
144 * doesn't support escape chars. Use quotes.
145 */
146 use_quotes = 1;
147 } else if (must_quote(*p)) {
148 /* Opening quote + character + closing quote. */
149 len += 3;
150 } else {
151 /*
152 * Allow space for the escape char.
153 */
154 len += esclen;
155 }
156 }
157 }
158 /*
159 * Allocate and construct the new string.
160 */
161 if (use_quotes) {
162 /* We can't quote a string that contains quotes. */
163 if (have_quotes)
164 return (NULL);
165 newstr = easprintf("%c%s%c", openquote, s, closequote);
166 } else {
167 newstr = r = ecalloc(len, sizeof (char));
168 while (*s != '\0') {
169 if (!metachar(*s)) {
170 *r++ = *s++;
171 } else if (must_quote(*s)) {
172 /* Surround the character with quotes. */
173 *r++ = openquote;
174 *r++ = *s++;
175 *r++ = closequote;
176 } else {
177 /* Escape the character. */
178 (void) strlcpy(r, esc, newstr + len - p);
179 r += esclen;
180 *r++ = *s++;
181 }
182 }
183 *r = '\0';
184 }
185 return (newstr);
186 }
187
188 /*
189 * Return a pathname that points to a specified file in a specified directory.
190 * Return NULL if the file does not exist in the directory.
191 */
192 static char *
dirfile(const char * dirname,const char * filename)193 dirfile(const char *dirname, const char *filename)
194 {
195 char *pathname;
196 char *qpathname;
197 int f;
198
199 if (dirname == NULL || *dirname == '\0')
200 return (NULL);
201 /*
202 * Construct the full pathname.
203 */
204 pathname = easprintf("%s/%s", dirname, filename);
205 /*
206 * Make sure the file exists.
207 */
208 qpathname = shell_unquote(pathname);
209 f = open(qpathname, O_RDONLY);
210 if (f == -1) {
211 free(pathname);
212 pathname = NULL;
213 } else {
214 (void) close(f);
215 }
216 free(qpathname);
217 return (pathname);
218 }
219
220 /*
221 * Return the full pathname of the given file in the "home directory".
222 */
223 char *
homefile(char * filename)224 homefile(char *filename)
225 {
226 return (dirfile(lgetenv("HOME"), filename));
227 }
228
229 /*
230 * Expand a string, substituting any "%" with the current filename,
231 * and any "#" with the previous filename.
232 * But a string of N "%"s is just replaced with N-1 "%"s.
233 * Likewise for a string of N "#"s.
234 * {{ This is a lot of work just to support % and #. }}
235 */
236 char *
fexpand(char * s)237 fexpand(char *s)
238 {
239 char *fr, *to;
240 int n;
241 char *e;
242 IFILE ifile;
243
244 #define fchar_ifile(c) \
245 ((c) == '%' ? curr_ifile : (c) == '#' ? old_ifile : NULL)
246
247 /*
248 * Make one pass to see how big a buffer we
249 * need to allocate for the expanded string.
250 */
251 n = 0;
252 for (fr = s; *fr != '\0'; fr++) {
253 switch (*fr) {
254 case '%':
255 case '#':
256 if (fr > s && fr[-1] == *fr) {
257 /*
258 * Second (or later) char in a string
259 * of identical chars. Treat as normal.
260 */
261 n++;
262 } else if (fr[1] != *fr) {
263 /*
264 * Single char (not repeated). Treat specially.
265 */
266 ifile = fchar_ifile(*fr);
267 if (ifile == NULL)
268 n++;
269 else
270 n += strlen(get_filename(ifile));
271 }
272 /*
273 * Else it is the first char in a string of
274 * identical chars. Just discard it.
275 */
276 break;
277 default:
278 n++;
279 break;
280 }
281 }
282
283 e = ecalloc(n+1, sizeof (char));
284
285 /*
286 * Now copy the string, expanding any "%" or "#".
287 */
288 to = e;
289 for (fr = s; *fr != '\0'; fr++) {
290 switch (*fr) {
291 case '%':
292 case '#':
293 if (fr > s && fr[-1] == *fr) {
294 *to++ = *fr;
295 } else if (fr[1] != *fr) {
296 ifile = fchar_ifile(*fr);
297 if (ifile == NULL) {
298 *to++ = *fr;
299 } else {
300 (void) strlcpy(to, get_filename(ifile),
301 e + n + 1 - to);
302 to += strlen(to);
303 }
304 }
305 break;
306 default:
307 *to++ = *fr;
308 break;
309 }
310 }
311 *to = '\0';
312 return (e);
313 }
314
315 /*
316 * Return a blank-separated list of filenames which "complete"
317 * the given string.
318 */
319 char *
fcomplete(char * s)320 fcomplete(char *s)
321 {
322 char *fpat;
323 char *qs;
324
325 if (secure)
326 return (NULL);
327 /*
328 * Complete the filename "s" by globbing "s*".
329 */
330 fpat = easprintf("%s*", s);
331
332 qs = lglob(fpat);
333 s = shell_unquote(qs);
334 if (strcmp(s, fpat) == 0) {
335 /*
336 * The filename didn't expand.
337 */
338 free(qs);
339 qs = NULL;
340 }
341 free(s);
342 free(fpat);
343 return (qs);
344 }
345
346 /*
347 * Try to determine if a file is "binary".
348 * This is just a guess, and we need not try too hard to make it accurate.
349 */
350 int
bin_file(int f)351 bin_file(int f)
352 {
353 char data[256];
354 ssize_t i, n;
355 wchar_t ch;
356 int bin_count, len;
357
358 if (!seekable(f))
359 return (0);
360 if (lseek(f, (off_t)0, SEEK_SET) == (off_t)-1)
361 return (0);
362 n = read(f, data, sizeof (data));
363 bin_count = 0;
364 for (i = 0; i < n; i += len) {
365 len = mbtowc(&ch, data + i, n - i);
366 if (len <= 0) {
367 bin_count++;
368 len = 1;
369 } else if (iswprint(ch) == 0 && iswspace(ch) == 0 &&
370 data[i] != '\b' &&
371 (ctldisp != OPT_ONPLUS || data[i] != ESC))
372 bin_count++;
373 }
374 /*
375 * Call it a binary file if there are more than 5 binary characters
376 * in the first 256 bytes of the file.
377 */
378 return (bin_count > 5);
379 }
380
381 /*
382 * Expand a filename, doing any system-specific metacharacter substitutions.
383 */
384 char *
lglob(char * filename)385 lglob(char *filename)
386 {
387 char *gfilename;
388 char *ofilename;
389 glob_t list;
390 int i;
391 int length;
392 char *p;
393 char *qfilename;
394
395 ofilename = fexpand(filename);
396 if (secure)
397 return (ofilename);
398 filename = shell_unquote(ofilename);
399
400 /*
401 * The globbing function returns a list of names.
402 */
403
404 #ifndef GLOB_TILDE
405 #define GLOB_TILDE 0
406 #endif
407 #ifndef GLOB_LIMIT
408 #define GLOB_LIMIT 0
409 #endif
410 if (glob(filename, GLOB_TILDE | GLOB_LIMIT, NULL, &list) != 0) {
411 free(filename);
412 return (ofilename);
413 }
414 length = 1; /* Room for trailing null byte */
415 for (i = 0; i < list.gl_pathc; i++) {
416 p = list.gl_pathv[i];
417 qfilename = shell_quote(p);
418 if (qfilename != NULL) {
419 length += strlen(qfilename) + 1;
420 free(qfilename);
421 }
422 }
423 gfilename = ecalloc(length, sizeof (char));
424 for (i = 0; i < list.gl_pathc; i++) {
425 p = list.gl_pathv[i];
426 qfilename = shell_quote(p);
427 if (qfilename != NULL) {
428 if (i != 0) {
429 (void) strlcat(gfilename, " ", length);
430 }
431 (void) strlcat(gfilename, qfilename, length);
432 free(qfilename);
433 }
434 }
435 globfree(&list);
436 free(filename);
437 free(ofilename);
438 return (gfilename);
439 }
440
441 /*
442 * Is the specified file a directory?
443 */
444 int
is_dir(char * filename)445 is_dir(char *filename)
446 {
447 int isdir = 0;
448 int r;
449 struct stat statbuf;
450
451 filename = shell_unquote(filename);
452
453 r = stat(filename, &statbuf);
454 isdir = (r >= 0 && S_ISDIR(statbuf.st_mode));
455 free(filename);
456 return (isdir);
457 }
458
459 /*
460 * Returns NULL if the file can be opened and
461 * is an ordinary file, otherwise an error message
462 * (if it cannot be opened or is a directory, etc.)
463 */
464 char *
bad_file(char * filename)465 bad_file(char *filename)
466 {
467 char *m = NULL;
468
469 filename = shell_unquote(filename);
470 if (!force_open && is_dir(filename)) {
471 m = easprintf("%s is a directory", filename);
472 } else {
473 int r;
474 struct stat statbuf;
475
476 r = stat(filename, &statbuf);
477 if (r == -1) {
478 m = errno_message(filename);
479 } else if (force_open) {
480 m = NULL;
481 } else if (!S_ISREG(statbuf.st_mode)) {
482 m = easprintf("%s is not a regular file (use -f to "
483 "see it)", filename);
484 }
485 }
486 free(filename);
487 return (m);
488 }
489
490 /*
491 * Return the size of a file, as cheaply as possible.
492 */
493 off_t
filesize(int f)494 filesize(int f)
495 {
496 struct stat statbuf;
497
498 if (fstat(f, &statbuf) >= 0)
499 return (statbuf.st_size);
500 return (-1);
501 }
502
503 /*
504 * Return last component of a pathname.
505 */
506 char *
last_component(char * name)507 last_component(char *name)
508 {
509 char *slash;
510
511 for (slash = name + strlen(name); slash > name; ) {
512 --slash;
513 if (*slash == '/')
514 return (slash + 1);
515 }
516 return (name);
517 }
518