xref: /curl/src/tool_parsecfg.c (revision d8618f4d)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "tool_setup.h"
25 
26 #include "curlx.h"
27 
28 #include "tool_cfgable.h"
29 #include "tool_getparam.h"
30 #include "tool_helpers.h"
31 #include "tool_findfile.h"
32 #include "tool_msgs.h"
33 #include "tool_parsecfg.h"
34 #include "tool_util.h"
35 #include "dynbuf.h"
36 
37 #include "memdebug.h" /* keep this as LAST include */
38 
39 /* only acknowledge colon or equals as separators if the option was not
40    specified with an initial dash! */
41 #define ISSEP(x,dash) (!dash && (((x) == '=') || ((x) == ':')))
42 
43 static const char *unslashquote(const char *line, char *param);
44 
45 #define MAX_CONFIG_LINE_LENGTH (10*1024*1024)
46 
47 /* return 0 on everything-is-fine, and non-zero otherwise */
parseconfig(const char * filename,struct GlobalConfig * global)48 int parseconfig(const char *filename, struct GlobalConfig *global)
49 {
50   FILE *file = NULL;
51   bool usedarg = FALSE;
52   int rc = 0;
53   struct OperationConfig *operation = global->last;
54   char *pathalloc = NULL;
55 
56   if(!filename) {
57     /* NULL means load .curlrc from homedir! */
58     char *curlrc = findfile(".curlrc", CURLRC_DOTSCORE);
59     if(curlrc) {
60       file = fopen(curlrc, FOPEN_READTEXT);
61       if(!file) {
62         free(curlrc);
63         return 1;
64       }
65       filename = pathalloc = curlrc;
66     }
67 #ifdef _WIN32 /* Windows */
68     else {
69       char *fullp;
70       /* check for .curlrc then _curlrc in the dir of the executable */
71       file = Curl_execpath(".curlrc", &fullp);
72       if(!file)
73         file = Curl_execpath("_curlrc", &fullp);
74       if(file)
75         /* this is the filename we read from */
76         filename = fullp;
77     }
78 #endif
79   }
80   else {
81     if(strcmp(filename, "-"))
82       file = fopen(filename, FOPEN_READTEXT);
83     else
84       file = stdin;
85   }
86 
87   if(file) {
88     char *line;
89     char *option;
90     char *param;
91     int lineno = 0;
92     bool dashed_option;
93     struct curlx_dynbuf buf;
94     bool fileerror = FALSE;
95     curlx_dyn_init(&buf, MAX_CONFIG_LINE_LENGTH);
96     DEBUGASSERT(filename);
97 
98     while(!rc && my_get_line(file, &buf, &fileerror)) {
99       ParameterError res;
100       bool alloced_param = FALSE;
101       lineno++;
102       line = curlx_dyn_ptr(&buf);
103       if(!line) {
104         rc = 1; /* out of memory */
105         break;
106       }
107 
108       /* line with # in the first non-blank column is a comment! */
109       while(*line && ISSPACE(*line))
110         line++;
111 
112       switch(*line) {
113       case '#':
114       case '/':
115       case '\r':
116       case '\n':
117       case '*':
118       case '\0':
119         curlx_dyn_reset(&buf);
120         continue;
121       }
122 
123       /* the option keywords starts here */
124       option = line;
125 
126       /* the option starts with a dash? */
127       dashed_option = (option[0] == '-');
128 
129       while(*line && !ISSPACE(*line) && !ISSEP(*line, dashed_option))
130         line++;
131       /* ... and has ended here */
132 
133       if(*line)
134         *line++ = '\0'; /* null-terminate, we have a local copy of the data */
135 
136 #ifdef DEBUG_CONFIG
137       fprintf(tool_stderr, "GOT: %s\n", option);
138 #endif
139 
140       /* pass spaces and separator(s) */
141       while(*line && (ISSPACE(*line) || ISSEP(*line, dashed_option)))
142         line++;
143 
144       /* the parameter starts here (unless quoted) */
145       if(*line == '\"') {
146         /* quoted parameter, do the quote dance */
147         line++;
148         param = malloc(strlen(line) + 1); /* parameter */
149         if(!param) {
150           /* out of memory */
151           rc = 1;
152           break;
153         }
154         alloced_param = TRUE;
155         (void)unslashquote(line, param);
156       }
157       else {
158         param = line; /* parameter starts here */
159         while(*line && !ISSPACE(*line))
160           line++;
161 
162         if(*line) {
163           *line = '\0'; /* null-terminate */
164 
165           /* to detect mistakes better, see if there is data following */
166           line++;
167           /* pass all spaces */
168           while(*line && ISSPACE(*line))
169             line++;
170 
171           switch(*line) {
172           case '\0':
173           case '\r':
174           case '\n':
175           case '#': /* comment */
176             break;
177           default:
178             warnf(operation->global, "%s:%d: warning: '%s' uses unquoted "
179                   "whitespace", filename, lineno, option);
180             warnf(operation->global, "This may cause side-effects. "
181                   "Consider using double quotes?");
182           }
183         }
184         if(!*param)
185           /* do this so getparameter can check for required parameters.
186              Otherwise it always thinks there is a parameter. */
187           param = NULL;
188       }
189 
190 #ifdef DEBUG_CONFIG
191       fprintf(tool_stderr, "PARAM: \"%s\"\n",(param ? param : "(null)"));
192 #endif
193       res = getparameter(option, param, NULL, &usedarg, global, operation);
194       operation = global->last;
195 
196       if(!res && param && *param && !usedarg)
197         /* we passed in a parameter that was not used! */
198         res = PARAM_GOT_EXTRA_PARAMETER;
199 
200       if(res == PARAM_NEXT_OPERATION) {
201         if(operation->url_list && operation->url_list->url) {
202           /* Allocate the next config */
203           operation->next = malloc(sizeof(struct OperationConfig));
204           if(operation->next) {
205             /* Initialise the newly created config */
206             config_init(operation->next);
207 
208             /* Set the global config pointer */
209             operation->next->global = global;
210 
211             /* Update the last operation pointer */
212             global->last = operation->next;
213 
214             /* Move onto the new config */
215             operation->next->prev = operation;
216             operation = operation->next;
217           }
218           else
219             res = PARAM_NO_MEM;
220         }
221       }
222 
223       if(res != PARAM_OK && res != PARAM_NEXT_OPERATION) {
224         /* the help request is not really an error */
225         if(!strcmp(filename, "-")) {
226           filename = "<stdin>";
227         }
228         if(res != PARAM_HELP_REQUESTED &&
229            res != PARAM_MANUAL_REQUESTED &&
230            res != PARAM_VERSION_INFO_REQUESTED &&
231            res != PARAM_ENGINES_REQUESTED &&
232            res != PARAM_CA_EMBED_REQUESTED) {
233           const char *reason = param2text(res);
234           errorf(operation->global, "%s:%d: '%s' %s",
235                  filename, lineno, option, reason);
236           rc = (int)res;
237         }
238       }
239 
240       if(alloced_param)
241         Curl_safefree(param);
242     }
243     curlx_dyn_free(&buf);
244     if(file != stdin)
245       fclose(file);
246     if(fileerror)
247       rc = 1;
248   }
249   else
250     rc = 1; /* could not open the file */
251 
252   free(pathalloc);
253   return rc;
254 }
255 
256 /*
257  * Copies the string from line to the buffer at param, unquoting
258  * backslash-quoted characters and NUL-terminating the output string.
259  * Stops at the first non-backslash-quoted double quote character or the
260  * end of the input string. param must be at least as long as the input
261  * string. Returns the pointer after the last handled input character.
262  */
unslashquote(const char * line,char * param)263 static const char *unslashquote(const char *line, char *param)
264 {
265   while(*line && (*line != '\"')) {
266     if(*line == '\\') {
267       char out;
268       line++;
269 
270       /* default is to output the letter after the backslash */
271       switch(out = *line) {
272       case '\0':
273         continue; /* this'll break out of the loop */
274       case 't':
275         out = '\t';
276         break;
277       case 'n':
278         out = '\n';
279         break;
280       case 'r':
281         out = '\r';
282         break;
283       case 'v':
284         out = '\v';
285         break;
286       }
287       *param++ = out;
288       line++;
289     }
290     else
291       *param++ = *line++;
292   }
293   *param = '\0'; /* always null-terminate */
294   return line;
295 }
296 
297 /*
298  * Reads a line from the given file, ensuring is NUL terminated.
299  */
300 
my_get_line(FILE * input,struct dynbuf * buf,bool * error)301 bool my_get_line(FILE *input, struct dynbuf *buf, bool *error)
302 {
303   CURLcode result;
304   char buffer[128];
305   curlx_dyn_reset(buf);
306   while(1) {
307     char *b = fgets(buffer, sizeof(buffer), input);
308 
309     if(b) {
310       size_t rlen = strlen(b);
311 
312       if(!rlen)
313         break;
314 
315       result = curlx_dyn_addn(buf, b, rlen);
316       if(result) {
317         /* too long line or out of memory */
318         *error = TRUE;
319         return FALSE; /* error */
320       }
321 
322       else if(b[rlen-1] == '\n')
323         /* end of the line */
324         return TRUE; /* all good */
325 
326       else if(feof(input)) {
327         /* append a newline */
328         result = curlx_dyn_addn(buf, "\n", 1);
329         if(result) {
330           /* too long line or out of memory */
331           *error = TRUE;
332           return FALSE; /* error */
333         }
334         return TRUE; /* all good */
335       }
336     }
337     else
338       break;
339   }
340   return FALSE;
341 }
342