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 #define ENABLE_CURLX_PRINTF
27 /* use our own printf() functions */
28 #include "curlx.h"
29
30 #include "tool_cfgable.h"
31 #include "tool_getparam.h"
32 #include "tool_helpers.h"
33 #include "tool_findfile.h"
34 #include "tool_msgs.h"
35 #include "tool_parsecfg.h"
36 #include "dynbuf.h"
37
38 #include "memdebug.h" /* keep this as LAST include */
39
40 /* only acknowledge colon or equals as separators if the option was not
41 specified with an initial dash! */
42 #define ISSEP(x,dash) (!dash && (((x) == '=') || ((x) == ':')))
43
44 static const char *unslashquote(const char *line, char *param);
45
46 #define MAX_CONFIG_LINE_LENGTH (10*1024*1024)
47 static bool my_get_line(FILE *fp, struct curlx_dynbuf *, bool *error);
48
49 #ifdef _WIN32
execpath(const char * filename,char ** pathp)50 static FILE *execpath(const char *filename, char **pathp)
51 {
52 static char filebuffer[512];
53 /* Get the filename of our executable. GetModuleFileName is already declared
54 * via inclusions done in setup header file. We assume that we are using
55 * the ASCII version here.
56 */
57 unsigned long len = GetModuleFileNameA(0, filebuffer, sizeof(filebuffer));
58 if(len > 0 && len < sizeof(filebuffer)) {
59 /* We got a valid filename - get the directory part */
60 char *lastdirchar = strrchr(filebuffer, '\\');
61 if(lastdirchar) {
62 size_t remaining;
63 *lastdirchar = 0;
64 /* If we have enough space, build the RC filename */
65 remaining = sizeof(filebuffer) - strlen(filebuffer);
66 if(strlen(filename) < remaining - 1) {
67 FILE *f;
68 msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename);
69 *pathp = filebuffer;
70 f = fopen(filebuffer, FOPEN_READTEXT);
71 return f;
72 }
73 }
74 }
75
76 return NULL;
77 }
78 #endif
79
80
81 /* return 0 on everything-is-fine, and non-zero otherwise */
parseconfig(const char * filename,struct GlobalConfig * global)82 int parseconfig(const char *filename, struct GlobalConfig *global)
83 {
84 FILE *file = NULL;
85 bool usedarg = FALSE;
86 int rc = 0;
87 struct OperationConfig *operation = global->last;
88 char *pathalloc = NULL;
89
90 if(!filename) {
91 /* NULL means load .curlrc from homedir! */
92 char *curlrc = findfile(".curlrc", CURLRC_DOTSCORE);
93 if(curlrc) {
94 file = fopen(curlrc, FOPEN_READTEXT);
95 if(!file) {
96 free(curlrc);
97 return 1;
98 }
99 filename = pathalloc = curlrc;
100 }
101 #ifdef _WIN32 /* Windows */
102 else {
103 char *fullp;
104 /* check for .curlrc then _curlrc in the dir of the executable */
105 file = execpath(".curlrc", &fullp);
106 if(!file)
107 file = execpath("_curlrc", &fullp);
108 if(file)
109 /* this is the filename we read from */
110 filename = fullp;
111 }
112 #endif
113 }
114 else {
115 if(strcmp(filename, "-"))
116 file = fopen(filename, FOPEN_READTEXT);
117 else
118 file = stdin;
119 }
120
121 if(file) {
122 char *line;
123 char *option;
124 char *param;
125 int lineno = 0;
126 bool dashed_option;
127 struct curlx_dynbuf buf;
128 bool fileerror;
129 curlx_dyn_init(&buf, MAX_CONFIG_LINE_LENGTH);
130 DEBUGASSERT(filename);
131
132 while(my_get_line(file, &buf, &fileerror)) {
133 int res;
134 bool alloced_param = FALSE;
135 lineno++;
136 line = curlx_dyn_ptr(&buf);
137 if(!line) {
138 rc = 1; /* out of memory */
139 break;
140 }
141
142 /* line with # in the first non-blank column is a comment! */
143 while(*line && ISSPACE(*line))
144 line++;
145
146 switch(*line) {
147 case '#':
148 case '/':
149 case '\r':
150 case '\n':
151 case '*':
152 case '\0':
153 curlx_dyn_reset(&buf);
154 continue;
155 }
156
157 /* the option keywords starts here */
158 option = line;
159
160 /* the option starts with a dash? */
161 dashed_option = option[0]=='-'?TRUE:FALSE;
162
163 while(*line && !ISSPACE(*line) && !ISSEP(*line, dashed_option))
164 line++;
165 /* ... and has ended here */
166
167 if(*line)
168 *line++ = '\0'; /* null-terminate, we have a local copy of the data */
169
170 #ifdef DEBUG_CONFIG
171 fprintf(tool_stderr, "GOT: %s\n", option);
172 #endif
173
174 /* pass spaces and separator(s) */
175 while(*line && (ISSPACE(*line) || ISSEP(*line, dashed_option)))
176 line++;
177
178 /* the parameter starts here (unless quoted) */
179 if(*line == '\"') {
180 /* quoted parameter, do the quote dance */
181 line++;
182 param = malloc(strlen(line) + 1); /* parameter */
183 if(!param) {
184 /* out of memory */
185 rc = 1;
186 break;
187 }
188 alloced_param = TRUE;
189 (void)unslashquote(line, param);
190 }
191 else {
192 param = line; /* parameter starts here */
193 while(*line && !ISSPACE(*line))
194 line++;
195
196 if(*line) {
197 *line = '\0'; /* null-terminate */
198
199 /* to detect mistakes better, see if there's data following */
200 line++;
201 /* pass all spaces */
202 while(*line && ISSPACE(*line))
203 line++;
204
205 switch(*line) {
206 case '\0':
207 case '\r':
208 case '\n':
209 case '#': /* comment */
210 break;
211 default:
212 warnf(operation->global, "%s:%d: warning: '%s' uses unquoted "
213 "whitespace", filename, lineno, option);
214 warnf(operation->global, "This may cause side-effects. "
215 "Consider using double quotes?");
216 }
217 }
218 if(!*param)
219 /* do this so getparameter can check for required parameters.
220 Otherwise it always thinks there's a parameter. */
221 param = NULL;
222 }
223
224 #ifdef DEBUG_CONFIG
225 fprintf(tool_stderr, "PARAM: \"%s\"\n",(param ? param : "(null)"));
226 #endif
227 res = getparameter(option, param, NULL, &usedarg, global, operation);
228 operation = global->last;
229
230 if(!res && param && *param && !usedarg)
231 /* we passed in a parameter that wasn't used! */
232 res = PARAM_GOT_EXTRA_PARAMETER;
233
234 if(res == PARAM_NEXT_OPERATION) {
235 if(operation->url_list && operation->url_list->url) {
236 /* Allocate the next config */
237 operation->next = malloc(sizeof(struct OperationConfig));
238 if(operation->next) {
239 /* Initialise the newly created config */
240 config_init(operation->next);
241
242 /* Set the global config pointer */
243 operation->next->global = global;
244
245 /* Update the last operation pointer */
246 global->last = operation->next;
247
248 /* Move onto the new config */
249 operation->next->prev = operation;
250 operation = operation->next;
251 }
252 else
253 res = PARAM_NO_MEM;
254 }
255 }
256
257 if(res != PARAM_OK && res != PARAM_NEXT_OPERATION) {
258 /* the help request isn't really an error */
259 if(!strcmp(filename, "-")) {
260 filename = "<stdin>";
261 }
262 if(res != PARAM_HELP_REQUESTED &&
263 res != PARAM_MANUAL_REQUESTED &&
264 res != PARAM_VERSION_INFO_REQUESTED &&
265 res != PARAM_ENGINES_REQUESTED) {
266 const char *reason = param2text(res);
267 warnf(operation->global, "%s:%d: warning: '%s' %s",
268 filename, lineno, option, reason);
269 }
270 }
271
272 if(alloced_param)
273 Curl_safefree(param);
274
275 curlx_dyn_reset(&buf);
276 }
277 curlx_dyn_free(&buf);
278 if(file != stdin)
279 fclose(file);
280 if(fileerror)
281 rc = 1;
282 }
283 else
284 rc = 1; /* couldn't open the file */
285
286 free(pathalloc);
287 return rc;
288 }
289
290 /*
291 * Copies the string from line to the buffer at param, unquoting
292 * backslash-quoted characters and NUL-terminating the output string.
293 * Stops at the first non-backslash-quoted double quote character or the
294 * end of the input string. param must be at least as long as the input
295 * string. Returns the pointer after the last handled input character.
296 */
unslashquote(const char * line,char * param)297 static const char *unslashquote(const char *line, char *param)
298 {
299 while(*line && (*line != '\"')) {
300 if(*line == '\\') {
301 char out;
302 line++;
303
304 /* default is to output the letter after the backslash */
305 switch(out = *line) {
306 case '\0':
307 continue; /* this'll break out of the loop */
308 case 't':
309 out = '\t';
310 break;
311 case 'n':
312 out = '\n';
313 break;
314 case 'r':
315 out = '\r';
316 break;
317 case 'v':
318 out = '\v';
319 break;
320 }
321 *param++ = out;
322 line++;
323 }
324 else
325 *param++ = *line++;
326 }
327 *param = '\0'; /* always null-terminate */
328 return line;
329 }
330
331 /*
332 * Reads a line from the given file, ensuring is NUL terminated.
333 */
my_get_line(FILE * fp,struct curlx_dynbuf * db,bool * error)334 static bool my_get_line(FILE *fp, struct curlx_dynbuf *db,
335 bool *error)
336 {
337 char buf[4096];
338 *error = FALSE;
339 do {
340 /* fgets() returns s on success, and NULL on error or when end of file
341 occurs while no characters have been read. */
342 if(!fgets(buf, sizeof(buf), fp))
343 /* only if there's data in the line, return TRUE */
344 return curlx_dyn_len(db) ? TRUE : FALSE;
345 if(curlx_dyn_add(db, buf)) {
346 *error = TRUE; /* error */
347 return FALSE; /* stop reading */
348 }
349 } while(!strchr(buf, '\n'));
350
351 return TRUE; /* continue */
352 }
353