✓ProveIt
Examples / Report #4041053 Saved analysis

CVE-2026-94266: password leak in CURLINFO_EFFECTIVE_URL

curl · 2026-09-1822.5s analysis · $0.037 in model calls

What the report claims

A .netrc password injected at login persists in the handle's URL info and is leaked via CURLINFO_EFFECTIVE_URL after a relative Location redirect.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-94266 password leak in CURLINFO_EFFECTIVE_URL

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • The code confirms .netrc credentials are written into data->state.uh and never cleared (lib/url.c:1327-1332).
  • Redirect processing reuses the same handle (lib/http.c:1244).
  • For relative redirects, redirect_url copies the authority including userinfo into the new URL (lib/urlapi.c:1364,1392,1397).
  • The resulting URL is stored in data->state.url and exposed via CURLINFO_EFFECTIVE_URL, verbose output, and write-out variables (lib/http.c:1304,1306; lib/getinfo.c:84).
  • Absolute redirects do not leak because set_url calls parseurl_and_replace which fully replaces the handle (lib/urlapi.c:1855), but the relative path does not.
  • The project policy does not classify this confidentiality flaw as a non-security issue.
How this analysis ran 5 claims · 14 citations verified

Checked against curl permalink d1823ebc17e5 (d1823ebc17e5). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 43 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2857 tokens · $0.0003
3 · Evidence
17 code snippets
real curl source at d1823ebc17e5 · 5 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
27947 tokens · 19.6s · $0.0364
5 · Receipts
14 of 14 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed report and reproduction. The code confirms the flaw: url_set_data_creds_netrc injects credentials into data->state.uh (lib/url.c:1327-1332), the redirect logic reuses that handle (lib/http.c:1244), and for relative Location: headers, redirect_url copies the userinfo into the new URL (lib/urlapi.c:1364,1392,1397). The result reaches CURLINFO_EFFECTIVE_URL, verbose logs, and %{url_effective} (lib/http.c:1304,1306; lib/getinfo.c:84). This is a valid confidentiality issue. The fix should strip userinfo from the handle before computing the redirect URL, as the Referer builder does (lib/http.c:1219-1221) and the proxy target builder does (lib/http.c:2163-2172).

Inspect all claims and source code 5 claims · 17 code excerpts

Report claims

C1flaw supported

The function url_set_data_creds_netrc() writes the .netrc user and password directly into data->state.uh without clearing them afterwards.

Read the reasoning

The function url_set_data_creds_netrc explicitly writes the .netrc-sourced user and password into data->state.uh via curl_url_set at lines 1327-1332. There is no subsequent clearing of these fields before the function returns.

C2location supported

The handle data->state.uh is reused when computing the redirect follow‑URL, so the stale userinfo remains in the URL string.

Read the reasoning

Curl_http_follow reparses the redirect target URL into the same data->state.uh handle that already contains the netrc-injected userinfo. The handle is not cloned or cleared before this call.

C3trigger supported

A server that returns a relative Location: header causes the redirect logic to copy the base URL (including userinfo) into the new URL before appending the path.

Read the reasoning

For a relative Location: like '/newpath', redirect_url computes cutoff at the first '/' after the authority, making prelen span 'scheme://user:pass@host:port'. This prefix is copied verbatim into the new URL before the relative path is appended.

C4impact supported

The leaked userinfo ends up in data->state.url and is returned by CURLINFO_EFFECTIVE_URL, curl -w '%{url_effective}', and verbose output.

Read the reasoning

The follow_url containing the netrc credentials is stored in data->state.url and directly returned by CURLINFO_EFFECTIVE_URL. The same string is printed by infof (verbose/debug output) and is available via the %{url_effective} write-out variable.

C5reproduction supported

Using a .netrc with a password and a server that responds with a relative Location:, curl -v -L --netrc-file netrc_test --netrc-optional -w '\nEFFECTIVE_URL=%{url_effective}\n' http://127.0.0.1:25036/first reproduces the leak.

Read the reasoning

The reproduction uses documented options (--netrc-file, --netrc-optional, -L for follow location, -w for write-out) that map to the code paths verified in C1-C4. The server returning a relative Location: triggers the redirect_url path that copies the userinfo.

Read the original report (excerpt)

Full report on HackerOne ↗

A `.netrc`-sourced password, injected into the live URL handle at login time, survives an ordinary same-host relative `Location:` redirect and ends up verbatim in `data->state.url`, so it is exposed via `CURLINFO_EFFECTIVE_URL`, `curl -w '%{url_effective}'`, and verbose (`-v`) output. ## Affected code https://github.com/curl/curl/blob/d1823ebc17e5778c30f8a6b2221a057ec607a9b7/lib/url.c#L1325-L1332 ```c result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd, *pcreds, CREDS_NETRC, pcreds); if(result) goto out; /* for updated strings, we update them in the URL */ uc = curl_url_set(data->state.uh, CURLUPART_USER, Curl_creds_user(*pcreds), CURLU_URLENCODE); if(!uc) uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, Curl_creds_passwd(*pcreds), CURLU_URLENCODE); ``` https://github.com/curl/curl/blob/d1823ebc17e5778c30f8a6b2221a057ec607a9b7/lib/http.c#L1244 https://github.com/curl/curl/blob/d1823ebc17e5778c30f8a6b2221a057ec607a9b7/lib/http.c#L1298-L1306 https://github.com/curl/curl/blob/d1823ebc17e5778c30f8a6b2221a057ec607a9b7/lib/urlapi.c#L1334-L1401 https://github.com/curl/curl/blob/d1823ebc17e5778c30f8a6b2221a057ec607a9b7/lib/getinfo.c#L83-L86 ## Details 1. `url_set_data_creds_netrc()` (lib/url.c:1325-1332) writes the `.netrc` user/password directly into `data->state.uh`, the handle later reused to compute the effective URL. It is never cleared. 2. On a redirect, `Curl_http_follow()` calls `curl_url_set(data->state.uh, CURLUPART_URL, newurl, ...)` (http.c:1244), reparsing the same, already-dirty handle with the `Location:` value. 3. For a relative `Location: /path`, `redirect_url()` (urlapi.c:1334) computes `cutoff = strchr(protsep, '/')` and copies `base[0..cutoff)`, i.e. `scheme://user:pass@host:port`, verbatim into the new URL before appending the new path (urlapi.c:1355-1362, 1391-1397). `CURLU_URLENCODE` only percent-escapes non-unreserved bytes, so a plain alphanumeric password passes through unchanged. 4. The result is read back and stored: `Curl_bufref_set(&data->state.url, follow_url, ...)` (http.c:1298) and `infof(data, "Issue another request to this URL: '%s'", follow_url)` (http.c:1306). 5. `CURLINFO_EFFECTIVE_URL` returns `Curl_bufref_ptr(&data->state.url)` unsanitized (getinfo.c: …

Source code

lib/url.clines 1248–1357 · lines 1325-1332 cited by the report; definition of url_set_data_creds_netrc
1248static CURLcode url_set_data_creds_netrc(struct Curl_easy *data,
1249 struct Curl_creds **pcreds)
1250{
1251 struct Curl_creds *ncreds_out = NULL;
1252 CURLcode result = CURLE_OK;
1253
1254 if(data->set.use_netrc) { /* not CURL_NETRC_IGNORED */
C5: data->set.use_netrc enables netrc processing
1255 struct Curl_creds *ncreds_in = NULL;
1256 bool scan_netrc = TRUE;
1257 NETRCcode ret;
1258 CURLUcode uc;
1259
1260 if(*pcreds) {
1261 switch((*pcreds)->source) {
1262 case CREDS_OPTION:
1263 /* we never override credentials set via CURLOPT_*, leave. */
1264 scan_netrc = FALSE;
1265 break;
1266 case CREDS_URL: /* only apply when netrc is not required */
1267 if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
1268 /* We ignore password from URL */
1269 ncreds_in = *pcreds;
1270 }
1271 else if(!Curl_creds_has_user(*pcreds) ||
1272 !Curl_creds_has_passwd(*pcreds)) {
1273 /* We use netrc to complete what is missing */
1274 ncreds_in = *pcreds;
1275 }
1276 else
1277 scan_netrc = FALSE;
1278 break;
1279 default: /* ignore credentials from other sources */
1280 break;
1281 }
1282 }
1283
1284 if(!scan_netrc)
1285 goto out;
1286
1287 ret = Curl_netrc_scan(data, &data->state.netrc,
1288 data->state.origin->hostname,
1289 Curl_creds_user(ncreds_in),
1290 CURL_EASY_STR(data, STRING_NETRC_FILE),
1291 &ncreds_out);
1292 DEBUGASSERT(!ret || !ncreds_out);
1293 if(ret == NETRC_OUT_OF_MEMORY) {
1294 result = CURLE_OUT_OF_MEMORY;
1295 goto out;
1296 }
1297 else if(ret && ((ret == NETRC_NO_MATCH) ||
1298 (data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
1299 infof(data, "Could not find host %s in the %s file; using defaults",
1300 data->state.origin->hostname,
1301 (CURL_EASY_STR(data, STRING_NETRC_FILE) ?
1302 CURL_EASY_STR(data, STRING_NETRC_FILE) : ".netrc"));
1303 }
1304 else if(ret) {
1305 const char *m = Curl_netrc_strerror(ret);
1306 failf(data, ".netrc error: %s", m);
1307 result = CURLE_READ_ERROR;
1308 goto out;
1309 }
1310 else if(ncreds_out) {
1311 if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) &&
1312 /* if the protocol cannot handle control codes in credentials, make
1313 sure there are none */
1314 (str_has_ctrl(ncreds_out->user) ||
1315 str_has_ctrl(ncreds_out->passwd))) {
1316 failf(data, "control code detected in .netrc credentials");
1317 result = CURLE_READ_ERROR;
1318 goto out;
1319 }
1320 CURL_TRC_M(data, "netrc: using credentials for %s as %s",
1321 data->state.origin->hostname, ncreds_out->user);
1322 result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
1323 *pcreds, CREDS_NETRC, pcreds);
1324 if(result)
1325 goto out;
1326 /* for updated strings, we update them in the URL */
1327 uc = curl_url_set(data->state.uh, CURLUPART_USER,
C1: curl_url_set writes netrc user into data->state.uh
1328 Curl_creds_user(*pcreds), CURLU_URLENCODE);
1329 if(!uc)
1330 uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
C1: curl_url_set writes netrc password into data->state.uh
1331 Curl_creds_passwd(*pcreds),
1332 CURLU_URLENCODE);
1333 if(uc)
C1: No clearing of userinfo after writing credentials; function returns at line 1347
1334 result = Curl_uc_to_curlcode(uc);
1335 }
1336 else
1337 DEBUGASSERT(0);
1338 }
1339
1340#ifdef CURLVERBOSE
1341 Curl_creds_trace(data, data->state.creds, "transfer credentials");
1342#endif
1343
1344out:
1345 Curl_creds_unlink(&ncreds_out);
1346 return result;
1347}
1348#endif /* CURL_DISABLE_NETRC */
1349
1350static CURLcode url_set_data_creds(struct Curl_easy *data, CURLU *uh)
1351{
1352 struct Curl_creds *newcreds = NULL;
1353 CURLcode result = CURLE_OK;
1354
1355 if((CURL_EASY_STR(data, STRING_USERNAME) ||
1356 CURL_EASY_STR(data, STRING_PASSWORD) ||
1357 CURL_EASY_STR(data, STRING_BEARER) ||
lib/http.clines 1175–1335 · lines 1244-1244 cited by the report; lines 1219-1221 cited by the report; definition of Curl_http_follow (truncated)
1175CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl,
1176 followtype type)
1177{
1178 bool disallowport = FALSE;
1179 bool reachedmax = FALSE;
1180 char *follow_url = NULL;
1181 CURLUcode uc;
1182 CURLcode rewind_result;
1183 bool switch_to_get = FALSE;
1184
1185 DEBUGASSERT(type != FOLLOW_NONE);
1186
1187 if(type != FOLLOW_FAKE)
1188 data->state.requests++; /* count all real follows */
1189 if(type == FOLLOW_REDIR) {
1190 if((data->set.maxredirs != -1) &&
1191 (data->state.followlocation >= data->set.maxredirs)) {
1192 reachedmax = TRUE;
1193 type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected
1194 to URL */
1195 }
1196 else {
1197 data->state.followlocation++; /* count redirect-followings, including
1198 auth reloads */
1199
1200 if(data->set.http_auto_referer) {
1201 CURLU *u;
1202 char *referer = NULL;
1203
1204 /* We are asked to automatically set the previous URL as the referer
1205 when we get the next URL. We pick the ->url field, which may or may
1206 not be 100% correct */
1207 Curl_bufref_free(&data->state.referer);
1208
1209 /* Make a copy of the URL without credentials and fragment */
1210 u = curl_url();
1211 if(!u)
1212 return CURLE_OUT_OF_MEMORY;
1213
1214 uc = curl_url_set(u, CURLUPART_URL,
1215 Curl_bufref_ptr(&data->state.url), 0);
1216 if(!uc)
1217 uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0);
1218 if(!uc)
1219 uc = curl_url_set(u, CURLUPART_USER, NULL, 0);
1220 if(!uc)
1221 uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0);
1222 if(!uc)
1223 uc = curl_url_get(u, CURLUPART_URL, &referer, 0);
1224
1225 curl_url_cleanup(u);
1226
1227 if(uc || !referer)
1228 return CURLE_OUT_OF_MEMORY;
1229
1230 Curl_bufref_set(&data->state.referer, referer, 0, curl_free);
1231 }
1232 }
1233 }
1234
1235 if((type != FOLLOW_RETRY) &&
1236 (data->req.httpcode != 401) && (data->req.httpcode != 407) &&
1237 Curl_is_absolute_url(newurl, NULL, 0, FALSE)) {
1238 /* If this is not redirect due to a 401 or 407 response and an absolute
1239 URL: do not allow a custom port number */
1240 disallowport = TRUE;
1241 }
1242
1243 DEBUGASSERT(data->state.uh);
C2: DEBUGASSERT(data->state.uh) confirms the handle is reused
1244 uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int)
C2: curl_url_set called on data->state.uh with CURLUPART_URL for redirect target
1245 ((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
1246 ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) |
1247 CURLU_ALLOW_SPACE |
1248 (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)));
1249 if(uc) {
1250 if((uc == CURLUE_OUT_OF_MEMORY) || (type != FOLLOW_FAKE)) {
1251 failf(data, "The redirect target URL could not be parsed: %s",
1252 curl_url_strerror(uc));
1253 return Curl_uc_to_curlcode(uc);
1254 }
1255
1256 /* the URL could not be parsed for some reason, but since this is FAKE
1257 mode, duplicate the field as-is */
1258 follow_url = curlx_strdup(newurl);
1259 if(!follow_url)
1260 return CURLE_OUT_OF_MEMORY;
1261 }
1262 else {
1263 CURLU *u = curl_url();
1264 if(!u)
1265 return CURLE_OUT_OF_MEMORY;
1266 uc = curl_url_set(u, CURLUPART_URL,
1267 Curl_bufref_ptr(&data->state.url),
1268 CURLU_URLENCODE | CURLU_ALLOW_SPACE);
1269 if(!uc)
1270 uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0);
1271 if(uc) {
1272 curl_url_cleanup(u);
1273 return Curl_uc_to_curlcode(uc);
1274 }
1275
1276#ifndef CURL_DISABLE_DIGEST_AUTH
1277 {
1278 bool same_origin = Curl_url_same_origin(u, data->state.uh);
1279 curl_url_cleanup(u);
1280 if(!same_origin)
1281 Curl_auth_digest_cleanup(&data->state.digest);
1282 }
1283#else
1284 curl_url_cleanup(u);
1285#endif
1286 }
1287 DEBUGASSERT(follow_url);
1288
1289 if(type == FOLLOW_FAKE) {
1290 /* we are only figuring out the new URL if we would have followed locations
1291 but now we are done so we can get out! */
1292 data->info.wouldredirect = follow_url;
1293
1294 if(reachedmax) {
1295 failf(data, "Maximum (%d) redirects followed", data->set.maxredirs);
1296 return CURLE_TOO_MANY_REDIRECTS;
1297 }
1298 return CURLE_OK;
1299 }
1300
1301 if(disallowport)
1302 data->state.allow_port = FALSE;
1303
1304 Curl_bufref_set(&data->state.url, follow_url, 0, curl_free);
C4: Curl_bufref_set stores follow_url (with userinfo) into data->state.url
1305 rewind_result = Curl_req_soft_reset(&data->req, data);
1306 infof(data, "Issue another request to this URL: '%s'", follow_url);
C4: infof logs the follow_url with credentials in verbose output
1307 if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) &&
1308 !data->state.http_ignorecustom &&
1309 CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) {
1310 data->state.http_ignorecustom = TRUE;
1311 infof(data, "Drop custom request method for next request");
1312 }
1313
1314 /*
1315 * We get here when the HTTP code is 300-399 (and 401). We need to perform
1316 * differently based on exactly what return code there was.
1317 *
1318 * News from 7.10.6: we can also get here on a 401 or 407, in case we act on
1319 * an HTTP (proxy-) authentication scheme other than Basic.
1320 */
1321 switch(data->info.httpcode) {
1322 /* 401 - Act on a WWW-Authenticate, we keep on moving and do the
1323 Authorization: XXXX header in the HTTP request code snippet */
1324 /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
1325 Proxy-Authorization: XXXX header in the HTTP request code snippet */
1326 /* 300 - Multiple Choices */
1327 /* 306 - Not used */
1328 /* 307 - Temporary Redirect */
1329 default: /* for all above (and the unknown ones) */
1330 /* Some codes are explicitly mentioned since I have checked RFC2616 and
1331 * they seem to be OK to POST to.
1332 */
1333 break;
1334 case 301: /* Moved Permanently */
1335 /* (quote from RFC7231, section 6.4.2)
lib/http.clines 1273–1331 · lines 1298-1306 cited by the report
1273 return Curl_uc_to_curlcode(uc);
1274 }
1275
1276#ifndef CURL_DISABLE_DIGEST_AUTH
1277 {
1278 bool same_origin = Curl_url_same_origin(u, data->state.uh);
1279 curl_url_cleanup(u);
1280 if(!same_origin)
1281 Curl_auth_digest_cleanup(&data->state.digest);
1282 }
1283#else
1284 curl_url_cleanup(u);
1285#endif
1286 }
1287 DEBUGASSERT(follow_url);
1288
1289 if(type == FOLLOW_FAKE) {
1290 /* we are only figuring out the new URL if we would have followed locations
1291 but now we are done so we can get out! */
1292 data->info.wouldredirect = follow_url;
1293
1294 if(reachedmax) {
1295 failf(data, "Maximum (%d) redirects followed", data->set.maxredirs);
1296 return CURLE_TOO_MANY_REDIRECTS;
1297 }
1298 return CURLE_OK;
1299 }
1300
1301 if(disallowport)
1302 data->state.allow_port = FALSE;
1303
1304 Curl_bufref_set(&data->state.url, follow_url, 0, curl_free);
C4: Curl_bufref_set stores follow_url (with userinfo) into data->state.url
1305 rewind_result = Curl_req_soft_reset(&data->req, data);
1306 infof(data, "Issue another request to this URL: '%s'", follow_url);
C4: infof logs the follow_url with credentials in verbose output
1307 if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) &&
1308 !data->state.http_ignorecustom &&
1309 CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) {
1310 data->state.http_ignorecustom = TRUE;
1311 infof(data, "Drop custom request method for next request");
1312 }
1313
1314 /*
1315 * We get here when the HTTP code is 300-399 (and 401). We need to perform
1316 * differently based on exactly what return code there was.
1317 *
1318 * News from 7.10.6: we can also get here on a 401 or 407, in case we act on
1319 * an HTTP (proxy-) authentication scheme other than Basic.
1320 */
1321 switch(data->info.httpcode) {
1322 /* 401 - Act on a WWW-Authenticate, we keep on moving and do the
1323 Authorization: XXXX header in the HTTP request code snippet */
1324 /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
1325 Proxy-Authorization: XXXX header in the HTTP request code snippet */
1326 /* 300 - Multiple Choices */
1327 /* 306 - Not used */
1328 /* 307 - Temporary Redirect */
1329 default: /* for all above (and the unknown ones) */
1330 /* Some codes are explicitly mentioned since I have checked RFC2616 and
1331 * they seem to be OK to POST to.
lib/urlapi.clines 1309–1426 · lines 1334-1401 cited by the report
1309 curlx_dyn_free(&host);
1310 free_urlhandle(u);
1311 return ures;
1312}
1313
1314/*
1315 * Parse the URL and, if successful, replace everything in the Curl_URL struct.
1316 */
1317static CURLUcode parseurl_and_replace(const char *url, CURLU *u,
1318 unsigned int flags)
1319{
1320 CURLUcode ures;
1321 CURLU tmpurl;
1322 memset(&tmpurl, 0, sizeof(tmpurl));
1323 ures = parseurl(url, &tmpurl, flags);
1324 if(!ures) {
1325 free_urlhandle(u);
1326 *u = tmpurl;
1327 }
1328 return ures;
1329}
1330
1331/*
1332 * Concatenate a relative URL onto a base URL making it absolute.
1333 */
1334static CURLUcode redirect_url(const char *base, const char *relurl,
1335 CURLU *u, unsigned int flags)
1336{
1337 struct dynbuf urlbuf;
1338 bool host_changed = FALSE;
1339 const char *useurl = relurl;
1340 const char *cutoff = NULL;
1341 size_t prelen;
1342 CURLUcode uc;
1343 /* this can get here with a NULL u->scheme only if asked to use the default
1344 scheme, so allow fallback to that */
1345 const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1346
1347 /* protsep points to the start of the hostname, after [scheme]:// */
1348 const char *protsep = base + strlen(scheme) + 3;
1349 DEBUGASSERT(base && relurl && u); /* all set here */
1350 if(!base)
1351 return CURLUE_MALFORMED_INPUT; /* should never happen */
1352
1353 /* handle different relative URL types */
1354 switch(relurl[0]) {
1355 case '/':
1356 if(relurl[1] == '/') {
1357 /* protocol-relative URL: //example.com/path */
1358 cutoff = protsep;
1359 useurl = &relurl[2];
1360 host_changed = TRUE;
1361 }
1362 else
1363 /* absolute /path */
1364 cutoff = strchr(protsep, '/');
C3: cutoff = strchr(protsep, '/') finds first slash after host:port
1365 break;
1366
1367 case '#':
1368 /* fragment-only change */
1369 if(u->fragment_present)
1370 cutoff = strchr(protsep, '#');
1371 break;
1372
1373 default:
1374 /* path or query-only change */
1375 if(u->query_present)
1376 /* remove existing query */
1377 cutoff = strchr(protsep, '?');
1378 else if(u->fragment_present)
1379 /* Remove existing fragment */
1380 cutoff = strchr(protsep, '#');
1381
1382 if(relurl[0] != '?') {
1383 /* append a relative path after the last slash */
1384 cutoff = memrchr(protsep, '/',
1385 cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1386 if(cutoff)
1387 cutoff++; /* truncate after last slash */
1388 }
1389 break;
1390 }
1391
1392 prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
C3: prelen includes scheme://user:pass@host:port
1393
1394 /* build new URL */
1395 curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1396
1397 if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
C3: curlx_dyn_addn copies base (including userinfo) into new URL buffer
1398 !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1399 QUERY_NOT_YET)) {
1400 uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1401 flags & ~U_CURLU_PATH_AS_IS);
1402 }
1403 else
1404 uc = CURLUE_OUT_OF_MEMORY;
1405
1406 curlx_dyn_free(&urlbuf);
1407 return uc;
1408}
1409
1410/*
1411 */
1412CURLU *curl_url(void)
1413{
1414 return curlx_calloc(1, sizeof(struct Curl_URL));
1415}
1416
1417void curl_url_cleanup(CURLU *u)
1418{
1419 if(u) {
1420 free_urlhandle(u);
1421 curlx_free(u);
1422 }
1423}
1424
1425#define DUP(dest, src, name) \
1426 do { \
lib/getinfo.clines 58–111 · lines 83-86 cited by the report
58 info->proxyauthavail = 0;
59 info->httpauthavail = 0;
60 info->proxyauthpicked = 0;
61 info->httpauthpicked = 0;
62 info->numconnects = 0;
63
64 curlx_safefree(info->contenttype);
65 curlx_safefree(info->wouldredirect);
66
67 memset(&info->primary, 0, sizeof(info->primary));
68 info->retry_after = 0;
69
70 info->conn_scheme = 0;
71 info->conn_protocol = 0;
72 info->used_proxy = 0;
73
74#ifdef USE_SSL
75 Curl_ssl_free_certinfo(data);
76#endif
77}
78
79static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info,
80 const char **param_charp)
81{
82 switch(info) {
83 case CURLINFO_EFFECTIVE_URL: {
84 const char *s = Curl_bufref_ptr(&data->state.url);
C4: CURLINFO_EFFECTIVE_URL returns Curl_bufref_ptr(&data->state.url) unsanitized
85 *param_charp = s ? s : "";
86 }
87 break;
88 case CURLINFO_EFFECTIVE_METHOD: {
89 const char *m = CURL_EASY_STR(data, STRING_CUSTOMREQUEST);
90 if(!m) {
91 if(data->set.opt_no_body)
92 m = "HEAD";
93#ifndef CURL_DISABLE_HTTP
94 else {
95 switch(data->state.httpreq) {
96 case HTTPREQ_POST:
97 case HTTPREQ_POST_FORM:
98 case HTTPREQ_POST_MIME:
99 m = "POST";
100 break;
101 case HTTPREQ_PUT:
102 m = "PUT";
103 break;
104 default: /* this should never happen */
105 case HTTPREQ_GET:
106 m = "GET";
107 break;
108 case HTTPREQ_HEAD:
109 m = "HEAD";
110 break;
111 }
include/curl/curl.hlines 2383–2383 · definition of CURL_NETRC_OPTIONAL
2383#define CURL_NETRC_OPTIONAL 1L /* A user:password in the URL is preferred
C5: CURL_NETRC_OPTIONAL defined
lib/easy.clines 905–924 · definition of curl_easy_getinfo
905CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...)
906{
907 struct Curl_eapi_guard guard;
908 CURLcode result;
909
910 if(CURL_EAPI_ENTER(&guard, curl, easy_getinfo, &result)) {
911 struct Curl_easy *data = curl;
912 va_list arg;
913 void *paramp;
914
915 va_start(arg, info);
916 paramp = va_arg(arg, void *);
917
918 result = Curl_getinfo(data, info, paramp);
C5: curl_easy_getinfo calls Curl_getinfo for CURLINFO_EFFECTIVE_URL
919
920 va_end(arg);
921 }
922 CURL_EAPI_LEAVE(&guard);
923 return result;
924}
lib/http.clines 2136–2198 · lines 2161-2173 cited by the report
Show 63 lines (not cited by the judge)
2136 /* The path sent to the proxy is in fact the entire URL, but if the remote
2137 host is a IDN-name, we must make sure that the request we produce only
2138 uses the decoded hostname! */
2139
2140 /* and no fragment part */
2141 CURLUcode uc;
2142 char *url;
2143 CURLU *h = curl_url_dup(data->state.uh);
2144 if(!h)
2145 return CURLE_OUT_OF_MEMORY;
2146
2147 if(!data->state.origin->ipv6 &&
2148 (data->state.origin->user_hostname != data->state.origin->hostname)) {
2149 uc = curl_url_set(h, CURLUPART_HOST, data->state.origin->hostname, 0);
2150 if(uc) {
2151 curl_url_cleanup(h);
2152 return CURLE_OUT_OF_MEMORY;
2153 }
2154 }
2155 uc = curl_url_set(h, CURLUPART_FRAGMENT, NULL, 0);
2156 if(uc) {
2157 curl_url_cleanup(h);
2158 return CURLE_OUT_OF_MEMORY;
2159 }
2160
2161 if(data->state.origin->scheme == &Curl_scheme_http) {
2162 /* when getting HTTP, we do not want the userinfo the URL */
2163 uc = curl_url_set(h, CURLUPART_USER, NULL, 0);
2164 if(uc) {
2165 curl_url_cleanup(h);
2166 return CURLE_OUT_OF_MEMORY;
2167 }
2168 uc = curl_url_set(h, CURLUPART_PASSWORD, NULL, 0);
2169 if(uc) {
2170 curl_url_cleanup(h);
2171 return CURLE_OUT_OF_MEMORY;
2172 }
2173 }
2174 else if(data->state.creds && (data->state.creds->source != CREDS_URL)) {
2175 /* credentials not from the URL need to be set */
2176 uc = curl_url_set(h, CURLUPART_USER,
2177 data->state.creds->user, CURLU_URLENCODE);
2178 if(!uc)
2179 uc = curl_url_set(h, CURLUPART_PASSWORD,
2180 data->state.creds->passwd, CURLU_URLENCODE);
2181 if(uc) {
2182 curl_url_cleanup(h);
2183 return Curl_uc_to_curlcode(uc);
2184 }
2185 }
2186
2187 /* Extract the URL to use in the request. */
2188 uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT);
2189 if(uc) {
2190 curl_url_cleanup(h);
2191 return CURLE_OUT_OF_MEMORY;
2192 }
2193
2194 curl_url_cleanup(h);
2195
2196 /* target or URL */
2197 result = curlx_dyn_add(r, CURL_EASY_STR(data, STRING_TARGET) ?
2198 CURL_EASY_STR(data, STRING_TARGET) : url);
include/curl/urlapi.hlines 95–95 · definition of CURLU_URLENCODE
Show 1 lines (not cited by the judge)
95#define CURLU_URLENCODE (1 << 7) /* URL encode on set */
lib/bufref.clines 99–106 · definition of Curl_bufref_ptr
Show 8 lines (not cited by the judge)
99const char *Curl_bufref_ptr(const struct bufref *br)
100{
101 DEBUGASSERT(br);
102 DEBUGASSERT(br->signature == SIGNATURE);
103 DEBUGASSERT(br->ptr || !br->len);
104
105 return (const char *)br->ptr;
106}
lib/bufref.clines 72–82 · definition of Curl_bufref_set
Show 11 lines (not cited by the judge)
72void Curl_bufref_set(struct bufref *br, const void *ptr, size_t len,
73 void (*dtor)(void *))
74{
75 DEBUGASSERT(ptr || !len);
76 DEBUGASSERT(len <= CURL_MAX_INPUT_LENGTH);
77
78 Curl_bufref_free(br);
79 br->ptr = (const unsigned char *)ptr;
80 br->len = len;
81 br->dtor = dtor;
82}
lib/creds.clines 104–128 · definition of Curl_creds_merge
Show 25 lines (not cited by the judge)
104CURLcode Curl_creds_merge(const char *user,
105 const char *passwd,
106 struct Curl_creds *creds_in,
107 uint8_t source,
108 struct Curl_creds **pcreds_out)
109{
110 struct Curl_creds *creds_out = NULL;
111 CURLcode result;
112
113 if(!creds_in) {
114 result = Curl_creds_create(user, passwd, NULL, NULL, NULL,
115 source, &creds_out);
116 }
117 else {
118 result = Curl_creds_create(user ? user : Curl_creds_user(creds_in),
119 passwd ? passwd : Curl_creds_passwd(creds_in),
120 Curl_creds_oauth_bearer(creds_in),
121 Curl_creds_sasl_authzid(creds_in),
122 Curl_creds_sasl_service(creds_in),
123 source, &creds_out);
124 }
125 Curl_creds_link(pcreds_out, creds_out);
126 Curl_creds_unlink(&creds_out);
127 return result;
128}
lib/creds.hlines 81–82 · definition of Curl_creds_passwd; definition of Curl_creds_user
Show 2 lines (not cited by the judge)
81#define Curl_creds_user(c) ((c) ? (c)->user : "")
82#define Curl_creds_passwd(c) ((c) ? (c)->passwd : "")
lib/urlapi.clines 2070–2174 · definition of curl_url_set
Show 105 lines (not cited by the judge)
2070CURLUcode curl_url_set(CURLU *u, CURLUPart what,
2071 const char *part, unsigned int flags)
2072{
2073 char **storep = NULL;
2074 bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2075 bool plusencode = FALSE;
2076 bool pathmode = FALSE;
2077 bool leadingslash = FALSE;
2078 bool appendquery = FALSE;
2079 bool equalsencode = FALSE;
2080 size_t nalloc;
2081
2082 if(!u)
2083 return CURLUE_BAD_HANDLE;
2084 if(!part)
2085 /* setting a part to NULL clears it */
2086 return urlset_clear(u, what);
2087
2088 nalloc = strlen(part);
2089 if(nalloc > CURL_MAX_INPUT_LENGTH)
2090 /* excessive input length */
2091 return CURLUE_MALFORMED_INPUT;
2092
2093 switch(what) {
2094 case CURLUPART_SCHEME: {
2095 CURLUcode status = set_url_scheme(u, part, flags);
2096 if(status)
2097 return status;
2098 storep = &u->scheme;
2099 urlencode = FALSE; /* never */
2100 break;
2101 }
2102 case CURLUPART_USER:
2103 storep = &u->user;
2104 break;
2105 case CURLUPART_PASSWORD:
2106 storep = &u->password;
2107 break;
2108 case CURLUPART_OPTIONS:
2109 storep = &u->options;
2110 break;
2111 case CURLUPART_HOST:
2112 storep = &u->host;
2113 curlx_safefree(u->zoneid);
2114 break;
2115 case CURLUPART_ZONEID:
2116 storep = &u->zoneid;
2117 break;
2118 case CURLUPART_PORT:
2119 return set_url_port(u, part);
2120 case CURLUPART_PATH:
2121 pathmode = TRUE;
2122 leadingslash = TRUE; /* enforce */
2123 storep = &u->path;
2124 break;
2125 case CURLUPART_QUERY:
2126 plusencode = urlencode;
2127 appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0;
2128 equalsencode = appendquery;
2129 storep = &u->query;
2130 u->query_present = TRUE;
2131 break;
2132 case CURLUPART_FRAGMENT:
2133 storep = &u->fragment;
2134 u->fragment_present = TRUE;
2135 break;
2136 case CURLUPART_URL:
2137 return set_url(u, part, nalloc, flags);
2138 default:
2139 return CURLUE_UNKNOWN_PART;
2140 }
2141 DEBUGASSERT(storep);
2142 {
2143 const char *newp = NULL;
2144 struct dynbuf enc;
2145 CURLUcode status;
2146 curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash);
2147
2148 if(leadingslash && (part[0] != '/')) {
2149 CURLcode result = curlx_dyn_addn(&enc, "/", 1);
2150 if(result)
2151 return cc2cu(result);
2152 }
2153 if(urlencode)
2154 status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode);
2155 else
2156 status = url_uppercasehex_part(&enc, part);
2157 if(!status) {
2158 newp = curlx_dyn_ptr(&enc);
2159
2160 if(appendquery && newp)
2161 return url_append_query(u, &enc);
2162 else if(what == CURLUPART_HOST)
2163 status = url_sethost(u, &enc, urlencode, flags);
2164 }
2165 if(status)
2166 return status;
2167
2168 if(what == CURLUPART_PASSWORD)
2169 curlx_strzero(*storep);
2170 curlx_free(*storep);
2171 *storep = (char *)CURL_UNCONST(newp);
2172 }
2173 return CURLUE_OK;
2174}
lib/curl_trc.hlines 142–146 · definition of infof
Show 5 lines (not cited by the judge)
142#define infof(data, ...) \
143 do { \
144 if(Curl_trc_is_verbose(data)) \
145 Curl_infof(data, __VA_ARGS__); \
146 } while(0)
lib/urlapi.clines 1822–1874 · definition of set_url
Show 53 lines (not cited by the judge)
1822static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1823 unsigned int flags)
1824{
1825 /*
1826 * Allow a new URL to replace the existing (if any) contents.
1827 *
1828 * If the existing contents is enough for a URL, allow a relative URL to
1829 * replace it.
1830 */
1831 CURLUcode uc;
1832 char *oldurl = NULL;
1833
1834 if(!part_size) {
1835 /* a blank URL is not a valid URL unless we already have a complete one
1836 and this is a redirect */
1837 uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1838 if(!uc) {
1839 /* success, meaning the "" is a fine relative URL, and the new URL
1840 inherits scheme/authority/path/query, but not fragment, from the
1841 existing URL (RFC 3986 section 5.2.2) */
1842 curlx_safefree(u->fragment);
1843 u->fragment_present = FALSE;
1844 curlx_free(oldurl);
1845 return CURLUE_OK;
1846 }
1847 if(uc == CURLUE_OUT_OF_MEMORY)
1848 return uc;
1849 return CURLUE_MALFORMED_INPUT;
1850 }
1851
1852 /* if the new URL is absolute replace the existing with the new. */
1853 if(Curl_is_absolute_url(url, NULL, 0,
1854 flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1855 return parseurl_and_replace(url, u, flags);
1856
1857 /* if the old URL is incomplete (we cannot get an absolute URL in
1858 'oldurl'), replace the existing with the new.
1859 Always include "scheme://" to make the URL "complete" */
1860 /* Preserve empty query/fragment separators: they affect where relative
1861 references splice into the base URL. */
1862 uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1863 (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1864 if(uc == CURLUE_OUT_OF_MEMORY)
1865 return uc;
1866 else if(uc)
1867 return parseurl_and_replace(url, u, flags);
1868
1869 DEBUGASSERT(oldurl); /* it is set here */
1870 /* apply the relative part to create a new URL */
1871 uc = redirect_url(oldurl, url, u, flags);
1872 curlx_free(oldurl);
1873 return uc;
1874}
lib/url.c@curl-8_22_0lines 1322–1422 · definition of url_set_data_creds_netrc as in release curl-8_22_0; differs from master; commits that changed it since: ba666ecef6 clang-tidy: merge nested `if()`s
Show 101 lines (not cited by the judge)
1322static CURLcode url_set_data_creds_netrc(struct Curl_easy *data,
1323 struct Curl_creds **pcreds)
1324{
1325 struct Curl_creds *ncreds_out = NULL;
1326 CURLcode result = CURLE_OK;
1327
1328 if(data->set.use_netrc) { /* not CURL_NETRC_IGNORED */
1329 struct Curl_creds *ncreds_in = NULL;
1330 bool scan_netrc = TRUE;
1331 NETRCcode ret;
1332 CURLUcode uc;
1333
1334 if(*pcreds) {
1335 switch((*pcreds)->source) {
1336 case CREDS_OPTION:
1337 /* we never override credentials set via CURLOPT_*, leave. */
1338 scan_netrc = FALSE;
1339 break;
1340 case CREDS_URL: /* only apply when netrc is not required */
1341 if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
1342 /* We ignore password from URL */
1343 ncreds_in = *pcreds;
1344 }
1345 else if(!Curl_creds_has_user(*pcreds) ||
1346 !Curl_creds_has_passwd(*pcreds)) {
1347 /* We use netrc to complete what is missing */
1348 ncreds_in = *pcreds;
1349 }
1350 else
1351 scan_netrc = FALSE;
1352 break;
1353 default: /* ignore credentials from other sources */
1354 break;
1355 }
1356 }
1357
1358 if(!scan_netrc)
1359 goto out;
1360
1361 ret = Curl_netrc_scan(data, &data->state.netrc,
1362 data->state.origin->hostname,
1363 Curl_creds_user(ncreds_in),
1364 CURL_EASY_STR(data, STRING_NETRC_FILE),
1365 &ncreds_out);
1366 DEBUGASSERT(!ret || !ncreds_out);
1367 if(ret == NETRC_OUT_OF_MEMORY) {
1368 result = CURLE_OUT_OF_MEMORY;
1369 goto out;
1370 }
1371 else if(ret && ((ret == NETRC_NO_MATCH) ||
1372 (data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
1373 infof(data, "Could not find host %s in the %s file; using defaults",
1374 data->state.origin->hostname,
1375 (CURL_EASY_STR(data, STRING_NETRC_FILE) ?
1376 CURL_EASY_STR(data, STRING_NETRC_FILE) : ".netrc"));
1377 }
1378 else if(ret) {
1379 const char *m = Curl_netrc_strerror(ret);
1380 failf(data, ".netrc error: %s", m);
1381 result = CURLE_READ_ERROR;
1382 goto out;
1383 }
1384 else if(ncreds_out) {
1385 if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL)) {
1386 /* if the protocol cannot handle control codes in credentials, make
1387 sure there are none */
1388 if(str_has_ctrl(ncreds_out->user) ||
1389 str_has_ctrl(ncreds_out->passwd)) {
1390 failf(data, "control code detected in .netrc credentials");
1391 result = CURLE_READ_ERROR;
1392 goto out;
1393 }
1394 }
1395 CURL_TRC_M(data, "netrc: using credentials for %s as %s",
1396 data->state.origin->hostname, ncreds_out->user);
1397 result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
1398 *pcreds, CREDS_NETRC, pcreds);
1399 if(result)
1400 goto out;
1401 /* for updated strings, we update them in the URL */
1402 uc = curl_url_set(data->state.uh, CURLUPART_USER,
1403 Curl_creds_user(*pcreds), CURLU_URLENCODE);
1404 if(!uc)
1405 uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
1406 Curl_creds_passwd(*pcreds),
1407 CURLU_URLENCODE);
1408 if(uc)
1409 result = Curl_uc_to_curlcode(uc);
1410 }
1411 else
1412 DEBUGASSERT(0);
1413 }
1414
1415#ifdef CURLVERBOSE
1416 Curl_creds_trace(data, data->state.creds, "transfer credentials");
1417#endif
1418
1419out:
1420 Curl_creds_unlink(&ncreds_out);
1421 return result;
1422}