rcfile.c 36 KB
Newer Older
1
2
/* $Id$ */
/**************************************************************************
3
 *   rcfile.c                                                             *
4
 *                                                                        *
5
6
 *   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,  *
 *   2010, 2011, 2013, 2014 Free Software Foundation, Inc.                *
7
8
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
9
 *   the Free Software Foundation; either version 3, or (at your option)  *
10
11
 *   any later version.                                                   *
 *                                                                        *
12
13
14
15
 *   This program is distributed in the hope that it will be useful, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 *   General Public License for more details.                             *
16
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
19
20
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
21
22
23
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

26
#include <glob.h>
27
#include <stdarg.h>
28
29
30
#include <string.h>
#include <stdio.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
#include <unistd.h>
32
#include <ctype.h>
33

34
#ifndef DISABLE_NANORC
35

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
36
static const rcoption rcopts[] = {
37
    {"boldtext", BOLD_TEXT},
38
39
#ifndef DISABLE_JUSTIFY
    {"brackets", 0},
40
#endif
41
42
    {"const", CONST_UPDATE},  /* deprecated form, remove in 2018 */
    {"constantshow", CONST_UPDATE},
Chris Allegretta's avatar
Chris Allegretta committed
43
#ifndef DISABLE_WRAPJUSTIFY
44
    {"fill", 0},
Chris Allegretta's avatar
Chris Allegretta committed
45
#endif
46
47
#ifndef DISABLE_HISTORIES
    {"historylog", HISTORYLOG},
48
#endif
49
    {"morespace", MORE_SPACE},
50
#ifndef DISABLE_MOUSE
51
    {"mouse", USE_MOUSE},
52
#endif
53
#ifndef DISABLE_MULTIBUFFER
54
55
    {"multibuffer", MULTIBUFFER},
#endif
56
    {"nofollow", NOFOLLOW_SYMLINKS},
57
    {"nohelp", NO_HELP},
58
    {"nonewlines", NO_NEWLINES},
Chris Allegretta's avatar
Chris Allegretta committed
59
#ifndef DISABLE_WRAPPING
60
    {"nowrap", NO_WRAP},
Chris Allegretta's avatar
Chris Allegretta committed
61
#endif
62
#ifndef DISABLE_OPERATINGDIR
63
    {"operatingdir", 0},
64
65
#endif
#ifndef DISABLE_HISTORIES
66
67
    {"poslog", POS_HISTORY},  /* deprecated form, remove in 2018 */
    {"positionlog", POS_HISTORY},
68
#endif
Chris Allegretta's avatar
Chris Allegretta committed
69
    {"preserve", PRESERVE},
70
#ifndef DISABLE_JUSTIFY
71
    {"punct", 0},
72
73
    {"quotestr", 0},
#endif
74
    {"rebinddelete", REBIND_DELETE},
75
    {"rebindkeypad", REBIND_KEYPAD},
76
77
78
#ifdef HAVE_REGEX_H
    {"regexp", USE_REGEXP},
#endif
79
#ifndef DISABLE_SPELLER
80
    {"speller", 0},
81
82
83
#endif
    {"suspend", SUSPEND},
    {"tabsize", 0},
84
    {"tempfile", TEMP_FILE},
85
    {"view", VIEW_MODE},
86
#ifndef NANO_TINY
87
    {"allow_insecure_backup", INSECURE_BACKUP},
88
89
90
91
92
93
    {"autoindent", AUTOINDENT},
    {"backup", BACKUP_FILE},
    {"backupdir", 0},
    {"backwards", BACKWARDS_SEARCH},
    {"casesensitive", CASE_SENSITIVE},
    {"cut", CUT_TO_END},
94
    {"locking", LOCKING},
95
    {"matchbrackets", 0},
96
    {"noconvert", NO_CONVERT},
97
    {"quickblank", QUICK_BLANK},
98
    {"quiet", QUIET},
99
100
    {"smarthome", SMART_HOME},
    {"smooth", SMOOTH_SCROLL},
101
    {"softwrap", SOFTWRAP},
102
    {"tabstospaces", TABS_TO_SPACES},
103
    {"whitespace", 0},
104
    {"wordbounds", WORD_BOUNDS},
105
106
107
108
109
110
#endif
#ifndef DISABLE_COLOR
    {"titlecolor", 0},
    {"statuscolor", 0},
    {"keycolor", 0},
    {"functioncolor", 0},
111
#endif
112
    {NULL, 0}
113
};
114

115
static bool errors = FALSE;
116
	/* Whether we got any errors while parsing an rcfile. */
117
static size_t lineno = 0;
118
	/* If we did, the line number where the last error occurred. */
119
static char *nanorc = NULL;
120
	/* The path to the rcfile we're parsing. */
121
#ifndef DISABLE_COLOR
122
123
124
125
126
static syntaxtype *endsyntax = NULL;
	/* The end of the list of syntaxes. */
static colortype *endcolor = NULL;
	/* The end of the color list for the current syntax. */
#endif
127

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
128
129
130
/* We have an error in some part of the rcfile.  Print the error message
 * on stderr, and then make the user hit Enter to continue starting
 * nano. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
131
void rcfile_error(const char *msg, ...)
132
133
134
{
    va_list ap;

135
136
137
    if (ISSET(QUIET))
	return;

138
    fprintf(stderr, "\n");
139
140
    if (lineno > 0) {
	errors = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
141
	fprintf(stderr, _("Error in %s on line %lu: "), nanorc, (unsigned long)lineno);
142
    }
Chris Allegretta's avatar
Chris Allegretta committed
143

144
    va_start(ap, msg);
145
    vfprintf(stderr, _(msg), ap);
146
    va_end(ap);
147
148

    fprintf(stderr, "\n");
149
}
150
#endif /* !DISABLE_NANORC */
151

152
#if !defined(DISABLE_NANORC) || !defined(DISABLE_HISTORIES)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
153
154
155
/* Parse the next word from the string, null-terminate it, and return
 * a pointer to the first character after the null terminator.  The
 * returned pointer will point to '\0' if we hit the end of the line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
156
char *parse_next_word(char *ptr)
157
{
158
    while (!isblank(*ptr) && *ptr != '\0')
159
160
161
	ptr++;

    if (*ptr == '\0')
162
	return ptr;
163

164
165
    /* Null-terminate and advance ptr. */
    *ptr++ = '\0';
166

167
    while (isblank(*ptr))
168
169
170
171
	ptr++;

    return ptr;
}
172
#endif /* !DISABLE_NANORC || !DISABLE_HISTORIES */
173

174
#ifndef DISABLE_NANORC
175
176
177
178
/* Parse an argument, with optional quotes, after a keyword that takes
 * one.  If the next word starts with a ", we say that it ends with the
 * last " of the line.  Otherwise, we interpret it as usual, so that the
 * arguments can contain "'s too. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
179
180
char *parse_argument(char *ptr)
{
181
    const char *ptr_save = ptr;
Chris Allegretta's avatar
Chris Allegretta committed
182
183
184
185
186
187
188
189
190
191
192
    char *last_quote = NULL;

    assert(ptr != NULL);

    if (*ptr != '"')
	return parse_next_word(ptr);

    do {
	ptr++;
	if (*ptr == '"')
	    last_quote = ptr;
193
    } while (*ptr != '\0');
Chris Allegretta's avatar
Chris Allegretta committed
194
195
196
197
198
199

    if (last_quote == NULL) {
	if (*ptr == '\0')
	    ptr = NULL;
	else
	    *ptr++ = '\0';
200
	rcfile_error(N_("Argument '%s' has an unterminated \""), ptr_save);
Chris Allegretta's avatar
Chris Allegretta committed
201
202
203
204
205
    } else {
	*last_quote = '\0';
	ptr = last_quote + 1;
    }
    if (ptr != NULL)
206
	while (isblank(*ptr))
Chris Allegretta's avatar
Chris Allegretta committed
207
208
209
210
	    ptr++;
    return ptr;
}

211
#ifndef DISABLE_COLOR
212
/* Parse the next regex string from the line at ptr, and return it. */
213
214
char *parse_next_regex(char *ptr)
{
215
216
217
218
    assert(ptr != NULL);

    /* Continue until the end of the line, or a " followed by a space, a
     * blank character, or \0. */
219
    while ((*ptr != '"' || (!isblank(*(ptr + 1)) &&
220
	*(ptr + 1) != '\0')) && *ptr != '\0')
221
222
	ptr++;

223
224
225
    assert(*ptr == '"' || *ptr == '\0');

    if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
226
227
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
228
	return NULL;
229
    }
230

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
231
    /* Null-terminate and advance ptr. */
232
233
    *ptr++ = '\0';

234
    while (isblank(*ptr))
235
236
237
238
239
	ptr++;

    return ptr;
}

240
241
/* Compile the regular expression regex to see if it's valid.  Return
 * TRUE if it is, or FALSE otherwise. */
242
bool nregcomp(const char *regex, int eflags)
243
{
244
    regex_t preg;
245
    const char *r = fixbounds(regex);
246
    int rc = regcomp(&preg, r, REG_EXTENDED | eflags);
247
248

    if (rc != 0) {
249
	size_t len = regerror(rc, &preg, NULL, 0);
250
251
	char *str = charalloc(len);

252
	regerror(rc, &preg, str, len);
253
	rcfile_error(N_("Bad regex \"%s\": %s"), r, str);
254
255
	free(str);
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
256

257
    regfree(&preg);
258
    return (rc == 0);
259
260
}

261
262
/* Parse the next syntax string from the line at ptr, and add it to the
 * global list of color syntaxes. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
263
void parse_syntax(char *ptr)
264
{
Chris Allegretta's avatar
Chris Allegretta committed
265
    const char *fileregptr = NULL, *nameptr = NULL;
266
    syntaxtype *tmpsyntax, *prev_syntax;
267
    regexlisttype *endext = NULL;
268
	/* The end of the extensions list for this syntax. */
269

270
    assert(ptr != NULL);
271

272
273
    if (*ptr == '\0') {
	rcfile_error(N_("Missing syntax name"));
274
	return;
275
    }
276
277

    if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
278
279
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
280
	return;
281
    }
282

283
284
285
286
287
    ptr++;

    nameptr = ptr;
    ptr = parse_next_regex(ptr);

288
    if (ptr == NULL)
289
	return;
290

291
292
    /* Search for a duplicate syntax name.  If we find one, free it, so
     * that we always use the last syntax with a given name. */
293
    prev_syntax = NULL;
294
295
    for (tmpsyntax = syntaxes; tmpsyntax != NULL;
	tmpsyntax = tmpsyntax->next) {
296
	if (strcmp(nameptr, tmpsyntax->desc) == 0) {
297
298
299
300
	    syntaxtype *old_syntax = tmpsyntax;

	    if (endsyntax == tmpsyntax)
		endsyntax = prev_syntax;
301
302

	    tmpsyntax = tmpsyntax->next;
303
304
305
306
307
308
309
	    if (prev_syntax != NULL)
		prev_syntax->next = tmpsyntax;
	    else
		syntaxes = tmpsyntax;

	    free(old_syntax->desc);
	    free(old_syntax);
310
	    break;
311
	}
312
	prev_syntax = tmpsyntax;
313
314
    }

Chris Allegretta's avatar
Chris Allegretta committed
315
316
    if (syntaxes == NULL) {
	syntaxes = (syntaxtype *)nmalloc(sizeof(syntaxtype));
317
	endsyntax = syntaxes;
Chris Allegretta's avatar
Chris Allegretta committed
318
    } else {
319
320
	endsyntax->next = (syntaxtype *)nmalloc(sizeof(syntaxtype));
	endsyntax = endsyntax->next;
321
#ifdef DEBUG
322
	fprintf(stderr, "Adding new syntax after first one\n");
323
#endif
Chris Allegretta's avatar
Chris Allegretta committed
324
    }
325

326
327
328
329
    endsyntax->desc = mallocstrcpy(NULL, nameptr);
    endsyntax->color = NULL;
    endcolor = NULL;
    endsyntax->extensions = NULL;
330
    endsyntax->headers = NULL;
331
    endsyntax->magics = NULL;
332
    endsyntax->next = NULL;
333
    endsyntax->nmultis = 0;
334
    endsyntax->linter = NULL;
335
    endsyntax->formatter = NULL;
336

337
#ifdef DEBUG
338
    fprintf(stderr, "Starting a new syntax type: \"%s\"\n", nameptr);
339
340
#endif

341
342
343
344
345
346
347
    /* The "none" syntax is the same as not having a syntax at all, so
     * we can't assign any extensions or colors to it. */
    if (strcmp(endsyntax->desc, "none") == 0) {
	rcfile_error(N_("The \"none\" syntax is reserved"));
	return;
    }

348
    /* The default syntax should have no associated extensions. */
349
    if (strcmp(endsyntax->desc, "default") == 0 && *ptr != '\0') {
350
351
	rcfile_error(
		N_("The \"default\" syntax must take no extensions"));
352
353
354
	return;
    }

355
    /* Now load the extension regexes into their part of the struct. */
356
    while (*ptr != '\0') {
357
	regexlisttype *newext;
358

359
	while (*ptr != '"' && *ptr != '\0')
360
361
	    ptr++;

362
	if (*ptr == '\0')
363
	    return;
364

365
366
367
368
	ptr++;

	fileregptr = ptr;
	ptr = parse_next_regex(ptr);
369
370
	if (ptr == NULL)
	    break;
371

372
	newext = (regexlisttype *)nmalloc(sizeof(regexlisttype));
373
374
375
376
377
378

	/* Save the extension regex if it's valid. */
	if (nregcomp(fileregptr, REG_NOSUB)) {
	    newext->ext_regex = mallocstrcpy(NULL, fileregptr);
	    newext->ext = NULL;

379
	    if (endext == NULL)
380
		endsyntax->extensions = newext;
381
382
383
384
	    else
		endext->next = newext;
	    endext = newext;
	    endext->next = NULL;
385
386
	} else
	    free(newext);
387
    }
388
}
389
#endif /* !DISABLE_COLOR */
390

391
392
393
int check_bad_binding(sc *s)
{
#define BADLISTLEN 1
394
    key_type badtypes[BADLISTLEN] = {META};
395
396
397
398
    int badseqs[BADLISTLEN] = { 91 };
    int i;

    for (i = 0; i < BADLISTLEN; i++)
399
	if (s->type == badtypes[i] && s->seq == badseqs[i])
400
401
402
403
404
	    return 1;

    return 0;
}

405
406
407
408
409
410
411
/* Check whether the given executable function is "universal" (meaning
 * any horizontal movement or deletion) and thus is present in almost
 * all menus. */
bool is_universal(void (*func))
{
    if (func == do_left || func == do_right ||
	func == do_home || func == do_end ||
412
#ifndef NANO_TINY
413
	func == do_prev_word_void || func == do_next_word_void ||
414
#endif
415
416
417
418
419
420
421
422
	func == do_verbatim_input || func == do_cut_text_void ||
	func == do_delete || func == do_backspace ||
	func == do_tab || func == do_enter)
	return TRUE;
    else
	return FALSE;
}

423
/* Bind or unbind a key combo, to or from a function. */
424
void parse_binding(char *ptr, bool dobind)
425
426
{
    char *keyptr = NULL, *keycopy = NULL, *funcptr = NULL, *menuptr = NULL;
427
    sc *s, *newsc = NULL;
428
    int menu;
429
430
431

    assert(ptr != NULL);

432
433
434
435
#ifdef DEBUG
    fprintf(stderr, "Starting the rebinding code...\n");
#endif

436
437
438
439
440
441
442
443
    if (*ptr == '\0') {
	rcfile_error(N_("Missing key name"));
	return;
    }

    keyptr = ptr;
    ptr = parse_next_word(ptr);
    keycopy = mallocstrcpy(NULL, keyptr);
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460

    if (strlen(keycopy) < 2) {
	rcfile_error(N_("Key name is too short"));
	return;
    }

    /* Uppercase only the first two or three characters of the key name. */
    keycopy[0] = toupper(keycopy[0]);
    keycopy[1] = toupper(keycopy[1]);
    if (keycopy[0] == 'M' && keycopy[1] == '-') {
	if (strlen(keycopy) > 2)
	    keycopy[2] = toupper(keycopy[2]);
	else {
	    rcfile_error(N_("Key name is too short"));
	    return;
	}
    }
461

462
463
464
465
466
    /* Allow the codes for Insert and Delete to be rebound, but apart
     * from those two only Control, Meta and Function sequences. */
    if (!strcasecmp(keycopy, "Ins") || !strcasecmp(keycopy, "Del"))
	keycopy[1] = tolower(keycopy[1]);
    else if (keycopy[0] != '^' && keycopy[0] != 'M' && keycopy[0] != 'F') {
467
	rcfile_error(N_("Key name must begin with \"^\", \"M\", or \"F\""));
468
469
470
	return;
    }

471
472
473
    if (dobind) {
	funcptr = ptr;
	ptr = parse_next_word(ptr);
474

475
	if (funcptr[0] == '\0') {
476
	    rcfile_error(N_("Must specify a function to bind the key to"));
477
478
	    return;
	}
479
480
481
482
483
    }

    menuptr = ptr;
    ptr = parse_next_word(ptr);

484
    if (menuptr[0] == '\0') {
485
	/* TRANSLATORS: Do not translate the word "all". */
486
	rcfile_error(N_("Must specify a menu (or \"all\") in which to bind/unbind the key"));
487
488
	return;
    }
489
490

    if (dobind) {
491
	newsc = strtosc(funcptr);
492
	if (newsc == NULL) {
493
	    rcfile_error(N_("Cannot map name \"%s\" to a function"), funcptr);
494
495
	    return;
	}
496
497
    }

498
499
500
501
502
503
    menu = strtomenu(menuptr);
    if (menu < 1) {
	rcfile_error(N_("Cannot map name \"%s\" to a menu"), menuptr);
	return;
    }

504
#ifdef DEBUG
505
    if (dobind)
506
507
	fprintf(stderr, "newsc address is now %ld, assigned func = %ld, menu = %x\n",
	    (long)&newsc, (long)newsc->scfunc, menu);
508
    else
509
	fprintf(stderr, "unbinding \"%s\" from menu %x\n", keycopy, menu);
510
511
#endif

512
    if (dobind) {
513
514
515
516
517
518
519
520
	subnfunc *f;
	int mask = 0;

	/* Tally up the menus where the function exists. */
	for (f = allfuncs; f != NULL; f = f->next)
	    if (f->scfunc == newsc->scfunc)
		mask = mask | f->menus;

521
522
523
524
	/* Handle the special case of the toggles. */
	if (newsc->scfunc == do_toggle_void)
	    mask = MMAIN;

525
526
527
528
529
530
531
532
533
534
535
536
	/* Now limit the given menu to those where the function exists. */
	if (is_universal(newsc->scfunc))
	    menu = menu & MMOST;
	else
	    menu = menu & mask;

	if (!menu) {
	    rcfile_error(N_("Function '%s' does not exist in menu '%s'"), funcptr, menuptr);
	    free(newsc);
	    return;
	}

537
	newsc->keystr = keycopy;
538
	newsc->menus = menu;
539
540
	newsc->type = strtokeytype(newsc->keystr);
	assign_keyinfo(newsc);
541
#ifdef DEBUG
542
543
	fprintf(stderr, "s->keystr = \"%s\"\n", newsc->keystr);
	fprintf(stderr, "s->seq = \"%d\"\n", newsc->seq);
544
545
#endif

546
	if (check_bad_binding(newsc)) {
547
548
	    rcfile_error(N_("Sorry, keystroke \"%s\" may not be rebound"), newsc->keystr);
	    free(newsc);
549
	    return;
550
551
	}
    }
552

553
    /* Now find and delete any existing same shortcut in the menu(s). */
554
    for (s = sclist; s != NULL; s = s->next) {
555
	if ((s->menus & menu) && !strcmp(s->keystr, keycopy)) {
556
#ifdef DEBUG
557
	    fprintf(stderr, "deleting entry from among menus %x\n", s->menus);
558
#endif
559
	    s->menus &= ~menu;
560
561
	}
    }
562
563

    if (dobind) {
564
565
566
567
568
569
570
	/* If this is a toggle, copy its sequence number. */
	if (newsc->scfunc == do_toggle_void) {
	    for (s = sclist; s != NULL; s = s->next)
		if (newsc->toggle == s->toggle)
		    newsc->ordinal = s->ordinal;
	} else
	    newsc->ordinal = 0;
571
572
573
574
	/* Add the new shortcut at the start of the list. */
	newsc->next = sclist;
	sclist = newsc;
    }
575
576
}

577

578
#ifndef DISABLE_COLOR
579
/* Read and parse additional syntax files. */
580
static void _parse_include(char *file)
581
582
583
584
{
    struct stat rcinfo;
    FILE *rcstream;

585
586
587
    /* Can't get the specified file's full path because it may screw up
     * our cwd depending on the parent directories' permissions (see
     * Savannah bug #25297). */
588
589

    /* Don't open directories, character files, or block files. */
590
    if (stat(file, &rcinfo) != -1) {
591
592
593
594
	if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode)) {
	    rcfile_error(S_ISDIR(rcinfo.st_mode) ?
		_("\"%s\" is a directory") :
595
		_("\"%s\" is a device file"), file);
596
597
598
599
	}
    }

    /* Open the new syntax file. */
600
601
    if ((rcstream = fopen(file, "rb")) == NULL) {
	rcfile_error(_("Error reading %s: %s"), file,
602
		strerror(errno));
603
	return;
604
605
606
607
    }

    /* Use the name and line number position of the new syntax file
     * while parsing it, so we can know where any errors in it are. */
608
    nanorc = file;
609
610
    lineno = 0;

611
#ifdef DEBUG
612
    fprintf(stderr, "Parsing file \"%s\"\n", file);
613
614
#endif

615
    parse_rcfile(rcstream, TRUE);
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
}

void parse_include(char *ptr)
{
    char *option, *nanorc_save = nanorc, *expanded;
    size_t lineno_save = lineno, i;
    glob_t files;

    option = ptr;
    if (*option == '"')
	option++;
    ptr = parse_argument(ptr);

    /* Expand tildes first, then the globs. */
    expanded = real_dir_from_tilde(option);

    if (glob(expanded, GLOB_ERR|GLOB_NOSORT, NULL, &files) == 0) {
	for (i = 0; i < files.gl_pathc; ++i)
	    _parse_include(files.gl_pathv[i]);
    } else {
	rcfile_error(_("Error expanding %s: %s"), option,
		strerror(errno));
    }
639

640
641
642
    globfree(&files);
    free(expanded);

643
644
645
646
647
648
    /* We're done with the new syntax file.  Restore the original
     * filename and line number position. */
    nanorc = nanorc_save;
    lineno = lineno_save;
}

649
650
/* Return the short value corresponding to the color named in colorname,
 * and set bright to TRUE if that color is bright. */
651
short color_to_short(const char *colorname, bool *bright)
652
{
653
    short mcolor = -1;
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687

    assert(colorname != NULL && bright != NULL);

    if (strncasecmp(colorname, "bright", 6) == 0) {
	*bright = TRUE;
	colorname += 6;
    }

    if (strcasecmp(colorname, "green") == 0)
	mcolor = COLOR_GREEN;
    else if (strcasecmp(colorname, "red") == 0)
	mcolor = COLOR_RED;
    else if (strcasecmp(colorname, "blue") == 0)
	mcolor = COLOR_BLUE;
    else if (strcasecmp(colorname, "white") == 0)
	mcolor = COLOR_WHITE;
    else if (strcasecmp(colorname, "yellow") == 0)
	mcolor = COLOR_YELLOW;
    else if (strcasecmp(colorname, "cyan") == 0)
	mcolor = COLOR_CYAN;
    else if (strcasecmp(colorname, "magenta") == 0)
	mcolor = COLOR_MAGENTA;
    else if (strcasecmp(colorname, "black") == 0)
	mcolor = COLOR_BLACK;
    else
	rcfile_error(N_("Color \"%s\" not understood.\n"
		"Valid colors are \"green\", \"red\", \"blue\",\n"
		"\"white\", \"yellow\", \"cyan\", \"magenta\" and\n"
		"\"black\", with the optional prefix \"bright\"\n"
		"for foreground colors."), colorname);

    return mcolor;
}

688
689
690
/* Parse the color string in the line at ptr, and add it to the current
 * file's associated colors.  If icase is TRUE, treat the color string
 * as case insensitive. */
691
void parse_colors(char *ptr, bool icase)
692
{
693
    short fg, bg;
694
    bool bright = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
695
    char *fgstr;
696

697
    assert(ptr != NULL);
698

699
700
    if (syntaxes == NULL) {
	rcfile_error(
701
		N_("Cannot add a color command without a syntax command"));
702
703
704
	return;
    }

705
    if (*ptr == '\0') {
706
	rcfile_error(N_("Missing color name"));
707
	return;
708
709
    }

710
711
    fgstr = ptr;
    ptr = parse_next_word(ptr);
712
713
    if (!parse_color_names(fgstr, &fg, &bg, &bright))
	return;
714

715
    if (*ptr == '\0') {
716
	rcfile_error(N_("Missing regex string"));
717
718
719
	return;
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
720
    /* Now for the fun part.  Start adding regexes to individual strings
721
722
     * in the colorstrings array, woo! */
    while (ptr != NULL && *ptr != '\0') {
723
	colortype *newcolor;
724
	    /* The container for a color plus its regexes. */
725
	bool cancelled = FALSE;
726
	    /* The start expression was bad. */
727
728
	bool expectend = FALSE;
	    /* Do we expect an end= line? */
729

730
	if (strncasecmp(ptr, "start=", 6) == 0) {
731
	    ptr += 6;
732
	    expectend = TRUE;
733
734
735
	}

	if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
736
737
	    rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
738
	    ptr = parse_next_regex(ptr);
739
740
	    continue;
	}
741

742
	ptr++;
743

744
745
	fgstr = ptr;
	ptr = parse_next_regex(ptr);
746
747
748
	if (ptr == NULL)
	    break;

749
	newcolor = (colortype *)nmalloc(sizeof(colortype));
750

751
752
753
	/* Save the starting regex string if it's valid, and set up the
	 * color information. */
	if (nregcomp(fgstr, icase ? REG_ICASE : 0)) {
754
755
756
	    newcolor->fg = fg;
	    newcolor->bg = bg;
	    newcolor->bright = bright;
757
	    newcolor->icase = icase;
758
759
760
761

	    newcolor->start_regex = mallocstrcpy(NULL, fgstr);
	    newcolor->start = NULL;

762
	    newcolor->end_regex = NULL;
763
	    newcolor->end = NULL;
764

765
	    newcolor->next = NULL;
766

767
768
	    if (endcolor == NULL) {
		endsyntax->color = newcolor;
769
#ifdef DEBUG
770
		fprintf(stderr, "Starting a new colorstring for fg %hd, bg %hd\n", fg, bg);
771
#endif
772
	    } else {
Chris Allegretta's avatar
Chris Allegretta committed
773
#ifdef DEBUG
774
		fprintf(stderr, "Adding new entry for fg %hd, bg %hd\n", fg, bg);
Chris Allegretta's avatar
Chris Allegretta committed
775
#endif
776
777
		/* Need to recompute endcolor now so we can extend
		 * colors to syntaxes. */
778
		for (endcolor = endsyntax->color; endcolor->next != NULL; endcolor = endcolor->next)
779
		    ;
780
		endcolor->next = newcolor;
781
	    }
782

783
	    endcolor = newcolor;
784
785
786
	} else {
	    free(newcolor);
	    cancelled = TRUE;
787
	}
Chris Allegretta's avatar
Chris Allegretta committed
788

789
	if (expectend) {
790
	    if (ptr == NULL || strncasecmp(ptr, "end=", 4) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
791
792
		rcfile_error(
			N_("\"start=\" requires a corresponding \"end=\""));
793
794
795
796
		return;
	    }
	    ptr += 4;
	    if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
797
798
		rcfile_error(
			N_("Regex strings must begin and end with a \" character"));
799
		continue;
800
	    }
801

802
803
	    ptr++;

804
	    fgstr = ptr;
805
	    ptr = parse_next_regex(ptr);
806
807
	    if (ptr == NULL)
		break;
808

809
810
	    /* If the start regex was invalid, skip past the end regex
	     * to stay in sync. */
811
812
	    if (cancelled)
		continue;
813

814
815
816
	    /* Save the ending regex string if it's valid. */
	    newcolor->end_regex = (nregcomp(fgstr, icase ? REG_ICASE :
		0)) ? mallocstrcpy(NULL, fgstr) : NULL;
817

818
	    /* Lame way to skip another static counter. */
819
820
	    newcolor->id = endsyntax->nmultis;
	    endsyntax->nmultis++;
Chris Allegretta's avatar
Chris Allegretta committed
821
	}
822
    }
823
}
824

825
826
827
828
829
830
/* Parse the color name, or pair of color names, in combostr. */
bool parse_color_names(char *combostr, short *fg, short *bg, bool *bright)
{
    bool no_fgcolor = FALSE;

    if (combostr == NULL)
831
	return FALSE;
832
833
834
835
836
837
838
839
840
841
842
843
844

    if (strchr(combostr, ',') != NULL) {
	char *bgcolorname;
	strtok(combostr, ",");
	bgcolorname = strtok(NULL, ",");
	if (bgcolorname == NULL) {
	    /* If we have a background color without a foreground color,
	     * parse it properly. */
	    bgcolorname = combostr + 1;
	    no_fgcolor = TRUE;
	}
	if (strncasecmp(bgcolorname, "bright", 6) == 0) {
	    rcfile_error(N_("Background color \"%s\" cannot be bright"), bgcolorname);
845
	    return FALSE;
846
847
848
849
850
851
852
853
854
855
	}
	*bg = color_to_short(bgcolorname, bright);
    } else
	*bg = -1;

    if (!no_fgcolor) {
	*fg = color_to_short(combostr, bright);

	/* Don't try to parse screwed-up foreground colors. */
	if (*fg == -1)
856
	    return FALSE;
857
858
859
    } else
	*fg = -1;

860
    return TRUE;
861
862
}

863
/* Parse the header-line regexes that may influence the choice of syntax. */
864
void parse_header_exp(char *ptr)
865
{
866
    regexlisttype *endheader = NULL;
867
868
869
870
871
872
873
874
875
876
877
878
879
880

    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add a header regex without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing regex string"));
	return;
    }

881
882
    while (*ptr != '\0') {
	const char *regexstring;
883
	regexlisttype *newheader;
884
885
886
887
888
889
890
891
892
893

	if (*ptr != '"') {
	    rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
	    ptr = parse_next_regex(ptr);
	    continue;
	}

	ptr++;

894
	regexstring = ptr;
895
896
897
898
	ptr = parse_next_regex(ptr);
	if (ptr == NULL)
	    break;

899
	newheader = (regexlisttype *)nmalloc(sizeof(regexlisttype));
900

901
	/* Save the regex string if it's valid. */
902
903
	if (nregcomp(regexstring, 0)) {
	    newheader->ext_regex = mallocstrcpy(NULL, regexstring);
904
905
	    newheader->ext = NULL;

906
	    if (endheader == NULL)
907
		endsyntax->headers = newheader;
908
	    else
909
910
		endheader->next = newheader;
	    endheader = newheader;
911
	    endheader->next = NULL;
912
913
914
915
	} else
	    free(newheader);
    }
}
916

917
#ifdef HAVE_LIBMAGIC
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
/* Parse the magic regexes that may influence the choice of syntax. */
void parse_magic_exp(char *ptr)
{
    regexlisttype *endmagic = NULL;

    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add a magic string regex without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing magic string name"));
	return;
    }

    if (*ptr != '"') {
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
	return;
    }

#ifdef DEBUG
    fprintf(stderr, "Starting a magic type: \"%s\"\n", ptr);
#endif

    /* Now load the magic regexes into their part of the struct. */
    while (*ptr != '\0') {
	const char *regexstring;
	regexlisttype *newmagic;

	while (*ptr != '"' && *ptr != '\0')
	    ptr++;

	if (*ptr == '\0')
	    return;

	ptr++;

	regexstring = ptr;
	ptr = parse_next_regex(ptr);
	if (ptr == NULL)
	    break;

	newmagic = (regexlisttype *)nmalloc(sizeof(regexlisttype));

	/* Save the regex string if it's valid. */
	if (nregcomp(regexstring, REG_NOSUB)) {
	    newmagic->ext_regex = mallocstrcpy(NULL, regexstring);
	    newmagic->ext = NULL;

	    if (endmagic == NULL)
		endsyntax->magics = newmagic;
	    else
		endmagic->next = newmagic;
	    endmagic = newmagic;
	    endmagic->next = NULL;
	} else
	    free(newmagic);
    }
}
981
#endif /* HAVE_LIBMAGIC */
982

983
/* Parse the linter requested for this syntax. */
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
void parse_linter(char *ptr)
{
    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add a linter without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing linter command"));
	return;
    }

999
    free(endsyntax->linter);
1000

Benno Schulenberg's avatar
Benno Schulenberg committed
1001
    /* Let them unset the linter by using "". */
1002
1003
1004
    if (!strcmp(ptr, "\"\""))
	endsyntax->linter = NULL;
    else
1005
	endsyntax->linter = mallocstrcpy(NULL, ptr);
1006
}
1007

1008
#ifndef DISABLE_SPELLER
1009
/* Parse the formatter requested for this syntax. */
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
void parse_formatter(char *ptr)
{
    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add formatter without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing formatter command"));
	return;
    }

1025
    free(endsyntax->formatter);
1026
1027
1028
1029
1030

    /* Let them unset the formatter by using "". */
    if (!strcmp(ptr, "\"\""))
	endsyntax->formatter = NULL;
    else
1031
	endsyntax->formatter = mallocstrcpy(NULL, ptr);
1032
}
1033
#endif /* !DISABLE_SPELLER */
1034
#endif /* !DISABLE_COLOR */
1035

1036
/* Check whether the user has unmapped every shortcut for a
1037
 * sequence we consider 'vital', like the exit function. */
1038
1039
1040
1041
1042
static void check_vitals_mapped(void)
{
    subnfunc *f;
    int v;
#define VITALS 5
1043
    void (*vitals[VITALS])(void) = { do_exit, do_exit, do_cancel, do_cancel, do_cancel };
1044
1045
1046
    int inmenus[VITALS] = { MMAIN, MHELP, MWHEREIS, MREPLACE, MGOTOLINE };

    for  (v = 0; v < VITALS; v++) {
1047
1048
1049
1050
1051
	for (f = allfuncs; f != NULL; f = f->next) {
	    if (f->scfunc == vitals[v] && f->menus & inmenus[v]) {
		const sc *s = first_sc_for(inmenus[v], f->scfunc);
		if (!s) {
		    fprintf(stderr, _("Fatal error: no keys mapped for function "
1052
				     "\"%s\".  Exiting.\n"), f->desc);
1053
		    fprintf(stderr, _("If needed, use nano with the -I option "
1054
				     "to adjust your nanorc settings.\n"));
1055
1056
1057
1058
1059
		     exit(1);
		}
		break;
	    }
	}
1060
1061
1062
    }
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1063
1064
1065
/* Parse the rcfile, once it has been opened successfully at rcstream,
 * and close it afterwards.  If syntax_only is TRUE, only allow the file
 * to contain color syntax commands: syntax, color, and icolor. */
1066
void parse_rcfile(FILE *rcstream
1067
#ifndef DISABLE_COLOR
1068
1069
1070
	, bool syntax_only
#endif
	)
1071
{
1072
1073
    char *buf = NULL;
    ssize_t len;
1074
    size_t n = 0;
1075
#ifndef DISABLE_COLOR
1076
1077
    syntaxtype *end_syn_save = NULL;
#endif
1078
1079
1080

    while ((len = getline(&buf, &n, rcstream)) > 0) {
	char *ptr, *keyword, *option;
1081
1082
	int set = 0;
	size_t i;
1083

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1084
	/* Ignore the newline. */
1085
1086
	if (buf[len - 1] == '\n')
	    buf[len - 1] = '\0';
1087
1088
1089

	lineno++;
	ptr = buf;
1090
	while (isblank(*ptr))
1091
1092
	    ptr++;

1093
1094
1095
	/* If we have a blank line or a comment, skip to the next
	 * line. */
	if (*ptr == '\0' || *ptr == '#')
1096
1097
	    continue;

1098
	/* Otherwise, skip to the next space. */
1099
1100
1101
	keyword = ptr;
	ptr = parse_next_word(ptr);

1102
#ifndef DISABLE_COLOR
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
	/* Handle extending first... */
	if (strcasecmp(keyword, "extendsyntax") == 0) {
	    char *syntaxname = ptr;
	    syntaxtype *ts = NULL;

	    ptr = parse_next_word(ptr);
	    for (ts = syntaxes; ts != NULL; ts = ts->next)
		if (!strcmp(ts->desc, syntaxname))
		    break;

	    if (ts == NULL) {
		rcfile_error(N_("Could not find syntax \"%s\" to extend"), syntaxname);
		continue;
	    } else {
		end_syn_save = endsyntax;
		endsyntax = ts;
		keyword = ptr;
		ptr = parse_next_word(ptr);
	    }
	}
1123
#endif
1124

1125
	/* Try to parse the keyword. */
1126
	if (strcasecmp(keyword, "set") == 0) {
1127
#ifndef DISABLE_COLOR
1128
1129
	    if (syntax_only)
		rcfile_error(
1130
			N_("Command \"%s\" not allowed in included file"),
1131
1132
1133
1134
1135
			keyword);
	    else
#endif
		set = 1;
	} else if (strcasecmp(keyword, "unset") == 0) {
1136
#ifndef DISABLE_COLOR
1137
1138
	    if (syntax_only)
		rcfile_error(
1139
			N_("Command \"%s\" not allowed in included file"),
1140
1141
1142
1143
1144
			keyword);
	    else
#endif
		set = -1;
	}
1145
#ifndef DISABLE_COLOR
1146
1147
1148
	else if (strcasecmp(keyword, "include") == 0) {
	    if (syntax_only)
		rcfile_error(
1149
			N_("Command \"%s\" not allowed in included file"),
1150
1151
1152
			keyword);
	    else
		parse_include(ptr);
1153
1154
	} else if (strcasecmp(keyword, "syntax") == 0) {
	    if (endsyntax != NULL && endcolor == NULL)
1155
		rcfile_error(N_("Syntax \"%s\" has no color commands"),
1156
			endsyntax->desc);
Chris Allegretta's avatar
Chris Allegretta committed
1157
	    parse_syntax(ptr);
1158
	}
1159
	else if (strcasecmp(keyword, "magic") == 0)
1160
#ifdef HAVE_LIBMAGIC
1161
	    parse_magic_exp(ptr);
1162
1163
1164
#else
	    ;
#endif
1165
	else if (strcasecmp(keyword, "header") == 0)
1166
	    parse_header_exp(ptr);
1167
	else if (strcasecmp(keyword, "color") == 0)
1168
1169
1170
	    parse_colors(ptr, FALSE);
	else if (strcasecmp(keyword, "icolor") == 0)
	    parse_colors(ptr, TRUE);
1171
1172
	else if (strcasecmp(keyword, "linter") == 0)
	    parse_linter(ptr);
1173
	else if (strcasecmp(keyword, "formatter") == 0)
1174
#ifndef DISABLE_SPELLER
1175
	    parse_formatter(ptr);
1176
1177
1178
#else
	    ;
#endif
1179
#endif /* !DISABLE_COLOR */
1180
	else if (strcasecmp(keyword, "bind") == 0)
1181
	    parse_binding(ptr, TRUE);
1182
	else if (strcasecmp(keyword, "unbind") == 0)
1183
	    parse_binding(ptr, FALSE);
1184
	else
1185
	    rcfile_error(N_("Command \"%s\" not understood"), keyword);
1186

1187
1188
1189
#ifndef DISABLE_COLOR
	/* If we temporarily reset endsyntax to allow extending,
	 * restore the value here. */
1190
1191
1192
1193
1194
1195
	if (end_syn_save != NULL) {
	    endsyntax = end_syn_save;
	    end_syn_save = NULL;
	}
#endif

1196
1197
1198
1199
	if (set == 0)
	    continue;

	if (*ptr == '\0') {
1200
	    rcfile_error(N_("Missing option"));
1201
1202
1203
1204
1205
1206
	    continue;
	}

	option = ptr;
	ptr = parse_next_word(ptr);

1207
1208
	for (i = 0; rcopts[i].name != NULL; i++) {
	    if (strcasecmp(option, rcopts[i].name) == 0) {
1209
#ifdef DEBUG
1210
1211
1212
1213
1214
1215
1216
1217
1218
		fprintf(stderr, "parse_rcfile(): name = \"%s\"\n", rcopts[i].name);
#endif
		if (set == 1) {
		    if (rcopts[i].flag != 0)
			/* This option has a flag, so it doesn't take an
			 * argument. */
			SET(rcopts[i].flag);
		    else {
			/* This option doesn't have a flag, so it takes
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1219
			 * an argument. */
1220
			if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1221
			    rcfile_error(
1222
				N_("Option \"%s\" requires an argument"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1223
				rcopts[i].name);
1224
1225
1226
1227
1228
1229
			    break;
			}
			option = ptr;
			if (*option == '"')
			    option++;
			ptr = parse_argument(ptr);
1230

1231
			option = mallocstrcpy(NULL, option);
Chris Allegretta's avatar
Chris Allegretta committed
1232
#ifdef DEBUG
1233
			fprintf(stderr, "option = \"%s\"\n", option);
Chris Allegretta's avatar
Chris Allegretta committed
1234
#endif
1235
1236
1237
1238
1239
1240
1241
1242
1243

			/* Make sure option is a valid multibyte
			 * string. */
			if (!is_valid_mbstring(option)) {
			    rcfile_error(
				N_("Option is not a valid multibyte string"));
			    break;
			}

1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
#ifndef DISABLE_COLOR
			if (strcasecmp(rcopts[i].name, "titlecolor") == 0)
			    specified_color_combo[TITLE_BAR] = option;
			else if (strcasecmp(rcopts[i].name, "statuscolor") == 0)
			    specified_color_combo[STATUS_BAR] = option;
			else if (strcasecmp(rcopts[i].name, "keycolor") == 0)
			    specified_color_combo[KEY_COMBO] = option;
			else if (strcasecmp(rcopts[i].name, "functioncolor") == 0)
			    specified_color_combo[FUNCTION_TAG] = option;
			else
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1255
#ifndef DISABLE_OPERATINGDIR
1256
			if (strcasecmp(rcopts[i].name, "operatingdir") == 0)
1257
			    operating_dir = option;
1258
			else
Chris Allegretta's avatar
Chris Allegretta committed
1259
#endif
1260
#ifndef DISABLE_WRAPJUSTIFY
1261
1262
			if (strcasecmp(rcopts[i].name, "fill") == 0) {
			    if (!parse_num(option, &wrap_at)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1263
				rcfile_error(
1264
					N_("Requested fill size \"%s\" is invalid"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1265
					option);
1266
				wrap_at = -CHARS_FROM_EOL;
1267
1268
			    } else
				free(option);
1269
			} else
Chris Allegretta's avatar
Chris Allegretta committed
1270
#endif
1271
#ifndef NANO_TINY
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
			if (strcasecmp(rcopts[i].name,
				"matchbrackets") == 0) {
			    matchbrackets = option;
			    if (has_blank_mbchars(matchbrackets)) {
				rcfile_error(
					N_("Non-blank characters required"));
				free(matchbrackets);
				matchbrackets = NULL;
			    }
			} else if (strcasecmp(rcopts[i].name,
				"whitespace") == 0) {
1283
			    whitespace = option;
1284
1285
			    if (mbstrlen(whitespace) != 2 ||
				strlenpt(whitespace) != 2) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1286
1287
				rcfile_error(
					N_("Two single-column characters required"));
1288
1289
				free(whitespace);
				whitespace = NULL;
1290
1291
1292
			    } else {
				whitespace_len[0] =
					parse_mbchar(whitespace, NULL,
1293
					NULL);
1294
1295
				whitespace_len[1] =
					parse_mbchar(whitespace +
1296
					whitespace_len[0], NULL, NULL);
1297
1298
			    }
			} else
1299
#endif
1300
#ifndef DISABLE_JUSTIFY
1301
			if (strcasecmp(rcopts[i].name, "punct") == 0) {
1302
			    punct = option;
1303
			    if (has_blank_mbchars(punct)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1304
				rcfile_error(
1305
					N_("Non-blank characters required"));
1306
1307
1308
				free(punct);
				punct = NULL;
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1309
1310
			} else if (strcasecmp(rcopts[i].name,
				"brackets") == 0) {
1311
			    brackets = option;
1312
			    if (has_blank_mbchars(brackets)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1313
				rcfile_error(
1314
					N_("Non-blank characters required"));
1315
1316
1317
				free(brackets);
				brackets = NULL;
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1318
1319
			} else if (strcasecmp(rcopts[i].name,
				"quotestr") == 0)
1320
			    quotestr = option;
1321
			else
1322
#endif
1323
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1324
1325
			if (strcasecmp(rcopts[i].name,
				"backupdir") == 0)
1326
			    backup_dir = option;
1327
			else
1328
#endif
1329
#ifndef DISABLE_SPELLER
1330
			if (strcasecmp(rcopts[i].name, "speller") == 0)
1331
			    alt_speller = option;
1332
1333
			else
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1334
1335
1336
1337
1338
			if (strcasecmp(rcopts[i].name,
				"tabsize") == 0) {
			    if (!parse_num(option, &tabsize) ||
				tabsize <= 0) {
				rcfile_error(
1339
					N_("Requested tab size \"%s\" is invalid"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1340
					option);
1341
				tabsize = -1;
1342
1343
			    } else
				free(option);
1344
			} else
1345
1346
			    assert(FALSE);
		    }
1347
#ifdef DEBUG
1348
		    fprintf(stderr, "flag = %ld\n", rcopts[i].flag);
1349
#endif
1350
1351
1352
		} else if (rcopts[i].flag != 0)
		    UNSET(rcopts[i].flag);
		else
1353
		    rcfile_error(N_("Cannot unset option \"%s\""),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1354
			rcopts[i].name);
1355
		break;
1356
1357
	    }
	}
1358
	if (rcopts[i].name == NULL)
1359
	    rcfile_error(N_("Unknown option \"%s\""), option);
1360
    }
1361

1362
#ifndef DISABLE_COLOR
1363
    if (endsyntax != NULL && endcolor == NULL)
1364
	rcfile_error(N_("Syntax \"%s\" has no color commands"),
1365
		endsyntax->desc);
1366
#endif
1367

Chris Allegretta's avatar
Chris Allegretta committed
1368
    free(buf);
1369
1370
    fclose(rcstream);
    lineno = 0;
1371

Chris Allegretta's avatar
Chris Allegretta committed
1372
    check_vitals_mapped();
1373
1374
1375
    return;
}

1376
/* The main rcfile function.  It tries to open the system-wide rcfile,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1377
 * followed by the current user's rcfile. */
1378
1379
void do_rcfile(void)
{
1380
    struct stat rcinfo;
1381
1382
    FILE *rcstream;

1383
    nanorc = mallocstrcpy(nanorc, SYSCONFDIR "/nanorc");
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393

    /* Don't open directories, character files, or block files. */
    if (stat(nanorc, &rcinfo) != -1) {
	if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode))
	    rcfile_error(S_ISDIR(rcinfo.st_mode) ?
		_("\"%s\" is a directory") :
		_("\"%s\" is a device file"), nanorc);
    }

1394
1395
1396
1397
#ifdef DEBUG
    fprintf(stderr, "Parsing file \"%s\"\n", nanorc);
#endif

1398
    /* Try to open the system-wide nanorc. */
1399
    rcstream = fopen(nanorc, "rb");
1400
    if (rcstream != NULL)
1401
	parse_rcfile(rcstream
1402
#ifndef DISABLE_COLOR
1403
1404
1405
		, FALSE
#endif
		);
1406

1407
#ifdef DISABLE_ROOTWRAPPING
1408
    /* We've already read SYSCONFDIR/nanorc, if it's there.  If we're
1409
1410
     * root, and --disable-wrapping-as-root is used, turn wrapping off
     * now. */
1411
1412
1413
    if (geteuid() == NANO_ROOT_UID)
	SET(NO_WRAP);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1414

1415
    get_homedir();
1416

1417
    if (homedir == NULL)
1418
	rcfile_error(N_("I can't find my home directory!  Wah!"));
1419
    else {
1420
1421
1422
1423
1424
#ifndef RCFILE_NAME
#define RCFILE_NAME ".nanorc"
#endif
	nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2);
	sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME);
1425

1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
	/* Don't open directories, character files, or block files. */
	if (stat(nanorc, &rcinfo) != -1) {
	    if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode))
		rcfile_error(S_ISDIR(rcinfo.st_mode) ?
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), nanorc);
	}

	/* Try to open the current user's nanorc. */
	rcstream = fopen(nanorc, "rb");
1437
	if (rcstream == NULL) {
1438
1439
	    /* Don't complain about the file's not existing. */
	    if (errno != ENOENT)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1440
1441
		rcfile_error(N_("Error reading %s: %s"), nanorc,
			strerror(errno));
1442
	} else
1443
	    parse_rcfile(rcstream
1444
#ifndef DISABLE_COLOR
1445
1446
1447
		, FALSE
#endif
		);
1448
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1449

1450
1451
    free(nanorc);
    nanorc = NULL;
1452

1453
1454
1455
1456
1457
1458
1459
    if (errors && !ISSET(QUIET)) {
	errors = FALSE;
	fprintf(stderr,
		_("\nPress Enter to continue starting nano.\n"));
	while (getchar() != '\n')
	    ;
    }
1460
1461
}

1462
#endif /* !DISABLE_NANORC */