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