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 in the line that may cause side-effects",
214 filename, lineno, option);
215 }
216 }
217 if(!*param)
218 /* do this so getparameter can check for required parameters.
219 Otherwise it always thinks there's a parameter. */
220 param = NULL;
221 }
222
223 #ifdef DEBUG_CONFIG
224 fprintf(tool_stderr, "PARAM: \"%s\"\n",(param ? param : "(null)"));
225 #endif
226 res = getparameter(option, param, NULL, &usedarg, global, operation);
227 operation = global->last;
228
229 if(!res && param && *param && !usedarg)
230 /* we passed in a parameter that wasn't used! */
231 res = PARAM_GOT_EXTRA_PARAMETER;
232
233 if(res == PARAM_NEXT_OPERATION) {
234 if(operation->url_list && operation->url_list->url) {
235 /* Allocate the next config */
236 operation->next = malloc(sizeof(struct OperationConfig));
237 if(operation->next) {
238 /* Initialise the newly created config */
239 config_init(operation->next);
240
241 /* Set the global config pointer */
242 operation->next->global = global;
243
244 /* Update the last operation pointer */
245 global->last = operation->next;
246
247 /* Move onto the new config */
248 operation->next->prev = operation;
249 operation = operation->next;
250 }
251 else
252 res = PARAM_NO_MEM;
253 }
254 }
255
256 if(res != PARAM_OK && res != PARAM_NEXT_OPERATION) {
257 /* the help request isn't really an error */
258 if(!strcmp(filename, "-")) {
259 filename = "<stdin>";
260 }
261 if(res != PARAM_HELP_REQUESTED &&
262 res != PARAM_MANUAL_REQUESTED &&
263 res != PARAM_VERSION_INFO_REQUESTED &&
264 res != PARAM_ENGINES_REQUESTED) {
265 const char *reason = param2text(res);
266 warnf(operation->global, "%s:%d: warning: '%s' %s",
267 filename, lineno, option, reason);
268 }
269 }
270
271 if(alloced_param)
272 Curl_safefree(param);
273
274 curlx_dyn_reset(&buf);
275 }
276 curlx_dyn_free(&buf);
277 if(file != stdin)
278 fclose(file);
279 if(fileerror)
280 rc = 1;
281 }
282 else
283 rc = 1; /* couldn't open the file */
284
285 free(pathalloc);
286 return rc;
287 }
288
289 /*
290 * Copies the string from line to the buffer at param, unquoting
291 * backslash-quoted characters and NUL-terminating the output string.
292 * Stops at the first non-backslash-quoted double quote character or the
293 * end of the input string. param must be at least as long as the input
294 * string. Returns the pointer after the last handled input character.
295 */
unslashquote(const char * line,char * param)296 static const char *unslashquote(const char *line, char *param)
297 {
298 while(*line && (*line != '\"')) {
299 if(*line == '\\') {
300 char out;
301 line++;
302
303 /* default is to output the letter after the backslash */
304 switch(out = *line) {
305 case '\0':
306 continue; /* this'll break out of the loop */
307 case 't':
308 out = '\t';
309 break;
310 case 'n':
311 out = '\n';
312 break;
313 case 'r':
314 out = '\r';
315 break;
316 case 'v':
317 out = '\v';
318 break;
319 }
320 *param++ = out;
321 line++;
322 }
323 else
324 *param++ = *line++;
325 }
326 *param = '\0'; /* always null-terminate */
327 return line;
328 }
329
330 /*
331 * Reads a line from the given file, ensuring is NUL terminated.
332 */
my_get_line(FILE * fp,struct curlx_dynbuf * db,bool * error)333 static bool my_get_line(FILE *fp, struct curlx_dynbuf *db,
334 bool *error)
335 {
336 char buf[4096];
337 *error = FALSE;
338 do {
339 /* fgets() returns s on success, and NULL on error or when end of file
340 occurs while no characters have been read. */
341 if(!fgets(buf, sizeof(buf), fp))
342 /* only if there's data in the line, return TRUE */
343 return curlx_dyn_len(db) ? TRUE : FALSE;
344 if(curlx_dyn_add(db, buf)) {
345 *error = TRUE; /* error */
346 return FALSE; /* stop reading */
347 }
348 } while(!strchr(buf, '\n'));
349
350 return TRUE; /* continue */
351 }
352