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
	/* If this is a toggle, copy its sequence number. */
	if (newsc->scfunc == do_toggle_void) {
	    for (s = sclist; s != NULL; s = s->next)
567
		if (s->scfunc == do_toggle_void && s->toggle == newsc->toggle)
568
569
570
		    newsc->ordinal = s->ordinal;
	} else
	    newsc->ordinal = 0;
571
572
573
	/* Add the new shortcut at the start of the list. */
	newsc->next = sclist;
	sclist = newsc;
574
575
    } else
	free(keycopy);
576
577
}

578

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

586
587
588
    /* 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). */
589
590

    /* Don't open directories, character files, or block files. */
591
    if (stat(file, &rcinfo) != -1) {
592
593
594
595
	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") :
596
		_("\"%s\" is a device file"), file);
597
598
599
600
	}
    }

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

    /* 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. */
609
    nanorc = file;
610
611
    lineno = 0;

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

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

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));
    }
640

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

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

650
651
/* Return the short value corresponding to the color named in colorname,
 * and set bright to TRUE if that color is bright. */
652
short color_to_short(const char *colorname, bool *bright)
653
{
654
    short mcolor = -1;
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
688

    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;
}

689
690
691
/* 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. */
692
void parse_colors(char *ptr, bool icase)
693
{
694
    short fg, bg;
695
    bool bright = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
696
    char *fgstr;
697

698
    assert(ptr != NULL);
699

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

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

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

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

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

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

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

743
	ptr++;
744

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

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

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

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

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

766
	    newcolor->next = NULL;
767

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

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

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

803
804
	    ptr++;

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

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

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

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

826
827
828
829
830
831
/* 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)
832
	return FALSE;
833
834
835
836
837
838
839
840
841
842
843
844
845

    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);
846
	    return FALSE;
847
848
849
850
851
852
853
854
855
856
	}
	*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)
857
	    return FALSE;
858
859
860
    } else
	*fg = -1;

861
    return TRUE;
862
863
}

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

    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;
    }

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

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

	ptr++;

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

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

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

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

918
#ifdef HAVE_LIBMAGIC
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
981
/* 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);
    }
}
982
#endif /* HAVE_LIBMAGIC */
983

984
/* Parse the linter requested for this syntax. */
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
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;
    }

1000
    free(endsyntax->linter);
1001

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

1009
#ifndef DISABLE_SPELLER
1010
/* Parse the formatter requested for this syntax. */
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
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;
    }

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

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

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

    for  (v = 0; v < VITALS; v++) {
1048
1049
1050
1051
1052
	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 "
1053
				     "\"%s\".  Exiting.\n"), f->desc);
1054
		    fprintf(stderr, _("If needed, use nano with the -I option "
1055
				     "to adjust your nanorc settings.\n"));
1056
1057
1058
1059
1060
		     exit(1);
		}
		break;
	    }
	}
1061
1062
1063
    }
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1064
1065
1066
/* 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. */
1067
void parse_rcfile(FILE *rcstream
1068
#ifndef DISABLE_COLOR
1069
1070
1071
	, bool syntax_only
#endif
	)
1072
{
1073
1074
    char *buf = NULL;
    ssize_t len;
1075
    size_t n = 0;
1076
#ifndef DISABLE_COLOR
1077
1078
    syntaxtype *end_syn_save = NULL;
#endif
1079
1080
1081

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

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

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

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

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

1103
#ifndef DISABLE_COLOR
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
	/* 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);
	    }
	}
1124
#endif
1125

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

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

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

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

	option = ptr;
	ptr = parse_next_word(ptr);

1208
1209
	for (i = 0; rcopts[i].name != NULL; i++) {
	    if (strcasecmp(option, rcopts[i].name) == 0) {
1210
#ifdef DEBUG
1211
1212
1213
1214
1215
1216
1217
1218
1219
		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
1220
			 * an argument. */
1221
			if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1222
			    rcfile_error(
1223
				N_("Option \"%s\" requires an argument"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1224
				rcopts[i].name);
1225
1226
1227
1228
1229
1230
			    break;
			}
			option = ptr;
			if (*option == '"')
			    option++;
			ptr = parse_argument(ptr);
1231

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

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

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

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

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

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

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

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

    /* 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);
    }

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

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

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

1416
    get_homedir();
1417

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

1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
	/* 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");
1438
	if (rcstream == NULL) {
1439
1440
	    /* Don't complain about the file's not existing. */
	    if (errno != ENOENT)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1441
1442
		rcfile_error(N_("Error reading %s: %s"), nanorc,
			strerror(errno));
1443
	} else
1444
	    parse_rcfile(rcstream
1445
#ifndef DISABLE_COLOR
1446
1447
1448
		, FALSE
#endif
		);
1449
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1450

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

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

1463
#endif /* !DISABLE_NANORC */