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