CVE-2024-6874: macidn punycode buffer overread
What the report claims
A stack-buffer over-read in lib/idn.c:75 leads to an out-of-bounds read when punycode-encoding a URL host, causing potential information leaks.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2024-6874 macidn punycode buffer overread
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The code at lib/idn.c:68 ignores the return value of uidna_nameToASCII_UTF8, which indicates the number of bytes written.
- The 256-byte stack buffer at line 67 has no guaranteed null terminator if the ICU function writes exactly 256 bytes.
- strdup at line 75 reads past the buffer boundary in that case, leaking adjacent stack memory.
- The trigger path via curl_url_get with CURLU_PUNYCODE is confirmed in lib/urlapi.c.
- The fix proposed (check return value, ensure n < sizeof(buffer), explicitly null-terminate) matches PHP's mitigation and corrects the flaw.
How this analysis ran 5 claims · 11 citations verified
Evidence check: judge quoted an external source that does not say that (https://github.com/php/php-src/blob/master/ext/intl/idn/idn.c#L69)
Checked against curl permalink 58772b0e082e (58772b0e082e). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for the detailed report and patch. The flaw is confirmed: mac_idn_to_ascii ignores the return value of uidna_nameToASCII_UTF8, so a 256-byte punycode output leaves the stack buffer unterminated, causing strdup to overread. The code path via curl_url_get with CURLU_PUNYCODE is valid. Your proposed fix (capture return value, verify n < sizeof(buffer), explicitly null-terminate) is correct and matches PHP's approach. We will apply this fix.
Inspect all claims and source code 5 claims · 9 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
uidna_nameToASCII_UTF8() leaves the output buffer unterminated when the encoded length equals the buffer capacity, causing strdup() to read beyond the buffer
Read the reasoning
The code ignores the return value of uidna_nameToASCII_UTF8 (line 68) and passes the fixed-size stack buffer directly to strdup (line 75). If the ICU function writes exactly 256 bytes (filling the buffer), no null terminator remains, causing strdup to read past the buffer boundary.
in lib/idn.c at line 75 in the mac_idn_to_ascii() function
Read the reasoning
The function mac_idn_to_ascii is defined at line 57 and the strdup call is at line 75, matching the report's location claim.
when a URL host is converted to punycode via curl_url_get() with CURLU_PUNYCODE
Read the reasoning
The code path exists: curl_url_get with CURLU_PUNYCODE triggers punycode conversion for the host, which calls Curl_idn_decode -> idn_decode -> mac_idn_to_ascii.
leaks pointers in the stackframe of mac_idn_to_ascii() leading to information disclosure
Read the reasoning
An unterminated buffer passed to strdup causes a stack buffer overread, potentially leaking stack frame pointers, return addresses, or other adjacent stack data, which can defeat ASLR/PIE.
provide a URL with a long host name that punycode-encodes to exactly 256 bytes, causing the unterminated buffer to be passed to strdup()
Read the reasoning
An attacker-controlled hostname that punycode-encodes to exactly 256 bytes will fill the buffer completely, leaving no null terminator. The PoC demonstrates this with a long internationalized domain name.
What the report says about other projects
not addressed uidna_nameToASCII_UTF8() leaves the output buffer unterminated when the encoded length equals the buffer capacity
confirmed PHP's handling of uidna_nameToASCII_UTF8() checks the return value and ensures null-termination
“int n = uidna_nameToASCII_UTF8(idna, in, -1, buffer, sizeof(buffer), &info, &err); ... if (U_FAILURE(err) || n < 0 || n >= sizeof(buffer)) { ... } buffer[n] = 0;” source
Read the original report (excerpt)
Source code
| 50 | #include "curl_memory.h" |
| 51 | #include "memdebug.h" |
| 52 | |
| 53 | /* for macOS and iOS targets */ |
| 54 | #if defined(USE_APPLE_IDN) |
| 55 | #include <unicode/uidna.h> |
| 56 | |
| 57 | static CURLcode mac_idn_to_ascii(const char *in, char **out) |
| C2: mac_idn_to_ascii function definition starts here | |
| 58 | { |
| 59 | UErrorCode err = U_ZERO_ERROR; |
| 60 | UIDNA* idna = uidna_openUTS46( |
| 61 | UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_ASCII, &err); |
| 62 | if(U_FAILURE(err)) { |
| 63 | return CURLE_OUT_OF_MEMORY; |
| 64 | } |
| 65 | else { |
| 66 | UIDNAInfo info = UIDNA_INFO_INITIALIZER; |
| 67 | char buffer[256] = {0}; |
| C1: buffer is 256 bytes, initialized to zero | |
| C4: buffer is a stack-allocated array of 256 bytes | |
| C5: buffer size is 256 bytes | |
| 68 | (void)uidna_nameToASCII_UTF8(idna, in, -1, buffer, |
| C1: uidna_nameToASCII_UTF8 return value cast to void, ignoring the number of bytes written | |
| C5: uidna_nameToASCII_UTF8 called with sizeof(buffer) as capacity | |
| 69 | sizeof(buffer), &info, &err); |
| 70 | uidna_close(idna); |
| 71 | if(U_FAILURE(err)) { |
| 72 | return CURLE_URL_MALFORMAT; |
| 73 | } |
| 74 | else { |
| 75 | *out = strdup(buffer); |
| C1: strdup(buffer) called without verifying null-termination; if uidna_nameToASCII_UTF8 wrote exactly 256 bytes, buffer has no NUL terminator | |
| C2: strdup(buffer) call at line 75 as cited | |
| C4: strdup reads past buffer if not null-terminated, leaking adjacent stack contents | |
| 76 | if(*out) |
| 77 | return CURLE_OK; |
| 78 | else |
| 79 | return CURLE_OUT_OF_MEMORY; |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | static CURLcode mac_ascii_to_idn(const char *in, char **out) |
| 85 | { |
| 86 | UErrorCode err = U_ZERO_ERROR; |
| 87 | UIDNA* idna = uidna_openUTS46( |
| 88 | UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_UNICODE, &err); |
| 89 | if(U_FAILURE(err)) { |
| 90 | return CURLE_OUT_OF_MEMORY; |
| 91 | } |
| 92 | else { |
| 93 | UIDNAInfo info = UIDNA_INFO_INITIALIZER; |
| 94 | char buffer[256] = {0}; |
| 95 | (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer, |
| 96 | sizeof(buffer), &info, &err); |
| 97 | uidna_close(idna); |
| 98 | if(U_FAILURE(err)) { |
| 99 | return CURLE_URL_MALFORMAT; |
| 100 | } |
| 1419 | CURLUcode curl_url_get(const CURLU *u, CURLUPart what, |
| 1420 | char **part, unsigned int flags) |
| 1421 | { |
| 1422 | const char *ptr; |
| 1423 | CURLUcode ifmissing = CURLUE_UNKNOWN_PART; |
| 1424 | char portbuf[7]; |
| 1425 | bool urldecode = (flags & CURLU_URLDECODE)?1:0; |
| 1426 | bool urlencode = (flags & CURLU_URLENCODE)?1:0; |
| 1427 | bool punycode = FALSE; |
| 1428 | bool depunyfy = FALSE; |
| 1429 | bool plusdecode = FALSE; |
| 1430 | (void)flags; |
| 1431 | if(!u) |
| 1432 | return CURLUE_BAD_HANDLE; |
| 1433 | if(!part) |
| 1434 | return CURLUE_BAD_PARTPOINTER; |
| 1435 | *part = NULL; |
| 1436 | |
| 1437 | switch(what) { |
| 1438 | case CURLUPART_SCHEME: |
| 1439 | ptr = u->scheme; |
| 1440 | ifmissing = CURLUE_NO_SCHEME; |
| 1441 | urldecode = FALSE; /* never for schemes */ |
| 1442 | if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme) |
| 1443 | return CURLUE_NO_SCHEME; |
| 1444 | break; |
| 1445 | case CURLUPART_USER: |
| 1446 | ptr = u->user; |
| 1447 | ifmissing = CURLUE_NO_USER; |
| 1448 | break; |
| 1449 | case CURLUPART_PASSWORD: |
| 1450 | ptr = u->password; |
| 1451 | ifmissing = CURLUE_NO_PASSWORD; |
| 1452 | break; |
| 1453 | case CURLUPART_OPTIONS: |
| 1454 | ptr = u->options; |
| 1455 | ifmissing = CURLUE_NO_OPTIONS; |
| 1456 | break; |
| 1457 | case CURLUPART_HOST: |
| C3: punycode flag set from CURLU_PUNYCODE when getting host part | |
| 1458 | ptr = u->host; |
| 1459 | ifmissing = CURLUE_NO_HOST; |
| 1460 | punycode = (flags & CURLU_PUNYCODE)?1:0; |
| 1461 | depunyfy = (flags & CURLU_PUNY2IDN)?1:0; |
| 1462 | break; |
| 1463 | case CURLUPART_ZONEID: |
| 1464 | ptr = u->zoneid; |
| 1465 | ifmissing = CURLUE_NO_ZONEID; |
| 1466 | break; |
| 1467 | case CURLUPART_PORT: |
| 1468 | ptr = u->port; |
| 1469 | ifmissing = CURLUE_NO_PORT; |
| 1470 | urldecode = FALSE; /* never for port */ |
| 1471 | if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { |
| 1472 | /* there is no stored port number, but asked to deliver |
| 1473 | a default one for the scheme */ |
| 1474 | const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); |
| 1475 | if(h) { |
| 1476 | msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); |
| 1477 | ptr = portbuf; |
| 1478 | } |
| 1479 | } |
| 1480 | else if(ptr && u->scheme) { |
| 1481 | /* there is a stored port number, but ask to inhibit if |
| 1482 | it matches the default one for the scheme */ |
| 1483 | const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); |
| 1484 | if(h && (h->defport == u->portnum) && |
| 1485 | (flags & CURLU_NO_DEFAULT_PORT)) |
| 1486 | ptr = NULL; |
| 1487 | } |
| 1488 | break; |
| 1489 | case CURLUPART_PATH: |
| 1490 | ptr = u->path; |
| 1491 | if(!ptr) |
| 1492 | ptr = "/"; |
| 1493 | break; |
| 1494 | case CURLUPART_QUERY: |
| 1495 | ptr = u->query; |
| 1496 | ifmissing = CURLUE_NO_QUERY; |
| 1497 | plusdecode = urldecode; |
| 1498 | if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) |
| 1499 | /* there was a blank query and the user do not ask for it */ |
| 1500 | ptr = NULL; |
| 1501 | break; |
| 1502 | case CURLUPART_FRAGMENT: |
| 1503 | ptr = u->fragment; |
| 1504 | ifmissing = CURLUE_NO_FRAGMENT; |
| 1505 | if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY) |
| 1506 | /* there was a blank fragment and the user asks for it */ |
| 1507 | ptr = ""; |
| 1508 | break; |
| 1509 | case CURLUPART_URL: { |
| 1510 | char *url; |
| 1511 | char *scheme; |
| 1512 | char *options = u->options; |
| 1513 | char *port = u->port; |
| 1514 | char *allochost = NULL; |
| 1515 | bool show_fragment = |
| 1516 | u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY); |
| 1517 | bool show_query = |
| 1518 | (u->query && u->query[0]) || |
| 1519 | (u->query_present && flags & CURLU_GET_EMPTY); |
| 1520 | punycode = (flags & CURLU_PUNYCODE)?1:0; |
| 1521 | depunyfy = (flags & CURLU_PUNY2IDN)?1:0; |
| 1522 | if(u->scheme && strcasecompare("file", u->scheme)) { |
| 1523 | url = aprintf("file://%s%s%s", |
| 1524 | u->path, |
| 1525 | show_fragment ? "#": "", |
| 1526 | u->fragment ? u->fragment : ""); |
| 1527 | } |
| 1528 | else if(!u->host) |
| 1529 | return CURLUE_NO_HOST; |
| 1530 | else { |
| 1531 | const struct Curl_handler *h = NULL; |
| 1532 | char schemebuf[MAX_SCHEME_LEN + 5]; |
| 1533 | if(u->scheme) |
| 1534 | scheme = u->scheme; |
| 1535 | else if(flags & CURLU_DEFAULT_SCHEME) |
| 1536 | scheme = (char *) DEFAULT_SCHEME; |
| 1537 | else |
| 1538 | return CURLUE_NO_SCHEME; |
| 1539 | |
| 1540 | h = Curl_get_scheme_handler(scheme); |
| 1541 | if(!port && (flags & CURLU_DEFAULT_PORT)) { |
| 1542 | /* there is no stored port number, but asked to deliver |
| 1543 | a default one for the scheme */ |
| 1544 | if(h) { |
| 1545 | msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); |
| 1546 | port = portbuf; |
| 1547 | } |
| 1548 | } |
| 1549 | else if(port) { |
| 1550 | /* there is a stored port number, but asked to inhibit if it matches |
| 1551 | the default one for the scheme */ |
| 1552 | if(h && (h->defport == u->portnum) && |
| 1553 | (flags & CURLU_NO_DEFAULT_PORT)) |
| 1554 | port = NULL; |
| 1555 | } |
| 1556 | |
| 1557 | if(h && !(h->flags & PROTOPT_URLOPTIONS)) |
| 1558 | options = NULL; |
| 1559 | |
| 1560 | if(u->host[0] == '[') { |
| 1561 | if(u->zoneid) { |
| 1562 | /* make it '[ host %25 zoneid ]' */ |
| 1563 | struct dynbuf enc; |
| 1564 | size_t hostlen = strlen(u->host); |
| 1565 | Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); |
| 1566 | if(Curl_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, |
| 1567 | u->zoneid)) |
| 1568 | return CURLUE_OUT_OF_MEMORY; |
| 1569 | allochost = Curl_dyn_ptr(&enc); |
| 1570 | } |
| 1571 | } |
| 1572 | else if(urlencode) { |
| 1573 | allochost = curl_easy_escape(NULL, u->host, 0); |
| 1574 | if(!allochost) |
| 1575 | return CURLUE_OUT_OF_MEMORY; |
| 1576 | } |
| 1577 | else if(punycode) { |
| C3: Curl_idn_decode called when punycode flag is set and host is not ASCII | |
| 1578 | if(!Curl_is_ASCII_name(u->host)) { |
| 1579 | #ifndef USE_IDN |
Show 1 lines (not cited by the judge)
| 153 | #define strdup(ptr) Curl_cstrdup(ptr) |
Show 1 lines (not cited by the judge)
| 100 | #define CURLU_PUNYCODE (1<<12) /* get the hostname in punycode */ |
Show 1 lines (not cited by the judge)
| 100 | #define CURLU_PUNYCODE (1<<12) /* get the host name in punycode */ |
Show 25 lines (not cited by the judge)
| 57 | static CURLcode mac_idn_to_ascii(const char *in, char **out) |
| 58 | { |
| 59 | UErrorCode err = U_ZERO_ERROR; |
| 60 | UIDNA* idna = uidna_openUTS46(UIDNA_CHECK_BIDI, &err); |
| 61 | if(U_FAILURE(err)) { |
| 62 | return CURLE_OUT_OF_MEMORY; |
| 63 | } |
| 64 | else { |
| 65 | UIDNAInfo info = UIDNA_INFO_INITIALIZER; |
| 66 | char buffer[256] = {0}; |
| 67 | (void)uidna_nameToASCII_UTF8(idna, in, -1, buffer, |
| 68 | sizeof(buffer), &info, &err); |
| 69 | uidna_close(idna); |
| 70 | if(U_FAILURE(err)) { |
| 71 | return CURLE_URL_MALFORMAT; |
| 72 | } |
| 73 | else { |
| 74 | *out = strdup(buffer); |
| 75 | if(*out) |
| 76 | return CURLE_OK; |
| 77 | else |
| 78 | return CURLE_OUT_OF_MEMORY; |
| 79 | } |
| 80 | } |
| 81 | } |
Show 161 lines (not cited by the judge)
| 1417 | CURLUcode curl_url_get(const CURLU *u, CURLUPart what, |
| 1418 | char **part, unsigned int flags) |
| 1419 | { |
| 1420 | const char *ptr; |
| 1421 | CURLUcode ifmissing = CURLUE_UNKNOWN_PART; |
| 1422 | char portbuf[7]; |
| 1423 | bool urldecode = (flags & CURLU_URLDECODE)?1:0; |
| 1424 | bool urlencode = (flags & CURLU_URLENCODE)?1:0; |
| 1425 | bool punycode = FALSE; |
| 1426 | bool depunyfy = FALSE; |
| 1427 | bool plusdecode = FALSE; |
| 1428 | (void)flags; |
| 1429 | if(!u) |
| 1430 | return CURLUE_BAD_HANDLE; |
| 1431 | if(!part) |
| 1432 | return CURLUE_BAD_PARTPOINTER; |
| 1433 | *part = NULL; |
| 1434 | |
| 1435 | switch(what) { |
| 1436 | case CURLUPART_SCHEME: |
| 1437 | ptr = u->scheme; |
| 1438 | ifmissing = CURLUE_NO_SCHEME; |
| 1439 | urldecode = FALSE; /* never for schemes */ |
| 1440 | break; |
| 1441 | case CURLUPART_USER: |
| 1442 | ptr = u->user; |
| 1443 | ifmissing = CURLUE_NO_USER; |
| 1444 | break; |
| 1445 | case CURLUPART_PASSWORD: |
| 1446 | ptr = u->password; |
| 1447 | ifmissing = CURLUE_NO_PASSWORD; |
| 1448 | break; |
| 1449 | case CURLUPART_OPTIONS: |
| 1450 | ptr = u->options; |
| 1451 | ifmissing = CURLUE_NO_OPTIONS; |
| 1452 | break; |
| 1453 | case CURLUPART_HOST: |
| 1454 | ptr = u->host; |
| 1455 | ifmissing = CURLUE_NO_HOST; |
| 1456 | punycode = (flags & CURLU_PUNYCODE)?1:0; |
| 1457 | depunyfy = (flags & CURLU_PUNY2IDN)?1:0; |
| 1458 | break; |
| 1459 | case CURLUPART_ZONEID: |
| 1460 | ptr = u->zoneid; |
| 1461 | ifmissing = CURLUE_NO_ZONEID; |
| 1462 | break; |
| 1463 | case CURLUPART_PORT: |
| 1464 | ptr = u->port; |
| 1465 | ifmissing = CURLUE_NO_PORT; |
| 1466 | urldecode = FALSE; /* never for port */ |
| 1467 | if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { |
| 1468 | /* there's no stored port number, but asked to deliver |
| 1469 | a default one for the scheme */ |
| 1470 | const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); |
| 1471 | if(h) { |
| 1472 | msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); |
| 1473 | ptr = portbuf; |
| 1474 | } |
| 1475 | } |
| 1476 | else if(ptr && u->scheme) { |
| 1477 | /* there is a stored port number, but ask to inhibit if |
| 1478 | it matches the default one for the scheme */ |
| 1479 | const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); |
| 1480 | if(h && (h->defport == u->portnum) && |
| 1481 | (flags & CURLU_NO_DEFAULT_PORT)) |
| 1482 | ptr = NULL; |
| 1483 | } |
| 1484 | break; |
| 1485 | case CURLUPART_PATH: |
| 1486 | ptr = u->path; |
| 1487 | if(!ptr) |
| 1488 | ptr = "/"; |
| 1489 | break; |
| 1490 | case CURLUPART_QUERY: |
| 1491 | ptr = u->query; |
| 1492 | ifmissing = CURLUE_NO_QUERY; |
| 1493 | plusdecode = urldecode; |
| 1494 | if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) |
| 1495 | /* there was a blank query and the user do not ask for it */ |
| 1496 | ptr = NULL; |
| 1497 | break; |
| 1498 | case CURLUPART_FRAGMENT: |
| 1499 | ptr = u->fragment; |
| 1500 | ifmissing = CURLUE_NO_FRAGMENT; |
| 1501 | if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY) |
| 1502 | /* there was a blank fragment and the user asks for it */ |
| 1503 | ptr = ""; |
| 1504 | break; |
| 1505 | case CURLUPART_URL: { |
| 1506 | char *url; |
| 1507 | char *scheme; |
| 1508 | char *options = u->options; |
| 1509 | char *port = u->port; |
| 1510 | char *allochost = NULL; |
| 1511 | bool show_fragment = |
| 1512 | u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY); |
| 1513 | bool show_query = |
| 1514 | (u->query && u->query[0]) || |
| 1515 | (u->query_present && flags & CURLU_GET_EMPTY); |
| 1516 | punycode = (flags & CURLU_PUNYCODE)?1:0; |
| 1517 | depunyfy = (flags & CURLU_PUNY2IDN)?1:0; |
| 1518 | if(u->scheme && strcasecompare("file", u->scheme)) { |
| 1519 | url = aprintf("file://%s%s%s", |
| 1520 | u->path, |
| 1521 | show_fragment ? "#": "", |
| 1522 | u->fragment ? u->fragment : ""); |
| 1523 | } |
| 1524 | else if(!u->host) |
| 1525 | return CURLUE_NO_HOST; |
| 1526 | else { |
| 1527 | const struct Curl_handler *h = NULL; |
| 1528 | if(u->scheme) |
| 1529 | scheme = u->scheme; |
| 1530 | else if(flags & CURLU_DEFAULT_SCHEME) |
| 1531 | scheme = (char *) DEFAULT_SCHEME; |
| 1532 | else |
| 1533 | return CURLUE_NO_SCHEME; |
| 1534 | |
| 1535 | h = Curl_get_scheme_handler(scheme); |
| 1536 | if(!port && (flags & CURLU_DEFAULT_PORT)) { |
| 1537 | /* there's no stored port number, but asked to deliver |
| 1538 | a default one for the scheme */ |
| 1539 | if(h) { |
| 1540 | msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); |
| 1541 | port = portbuf; |
| 1542 | } |
| 1543 | } |
| 1544 | else if(port) { |
| 1545 | /* there is a stored port number, but asked to inhibit if it matches |
| 1546 | the default one for the scheme */ |
| 1547 | if(h && (h->defport == u->portnum) && |
| 1548 | (flags & CURLU_NO_DEFAULT_PORT)) |
| 1549 | port = NULL; |
| 1550 | } |
| 1551 | |
| 1552 | if(h && !(h->flags & PROTOPT_URLOPTIONS)) |
| 1553 | options = NULL; |
| 1554 | |
| 1555 | if(u->host[0] == '[') { |
| 1556 | if(u->zoneid) { |
| 1557 | /* make it '[ host %25 zoneid ]' */ |
| 1558 | struct dynbuf enc; |
| 1559 | size_t hostlen = strlen(u->host); |
| 1560 | Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); |
| 1561 | if(Curl_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, |
| 1562 | u->zoneid)) |
| 1563 | return CURLUE_OUT_OF_MEMORY; |
| 1564 | allochost = Curl_dyn_ptr(&enc); |
| 1565 | } |
| 1566 | } |
| 1567 | else if(urlencode) { |
| 1568 | allochost = curl_easy_escape(NULL, u->host, 0); |
| 1569 | if(!allochost) |
| 1570 | return CURLUE_OUT_OF_MEMORY; |
| 1571 | } |
| 1572 | else if(punycode) { |
| 1573 | if(!Curl_is_ASCII_name(u->host)) { |
| 1574 | #ifndef USE_IDN |
| 1575 | return CURLUE_LACKS_IDN; |
| 1576 | #else |
| 1577 | CURLcode result = Curl_idn_decode(u->host, &allochost); |
Show 161 lines (not cited by the judge)
| 1710 | CURLUcode curl_url_set(CURLU *u, CURLUPart what, |
| 1711 | const char *part, unsigned int flags) |
| 1712 | { |
| 1713 | char **storep = NULL; |
| 1714 | bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0; |
| 1715 | bool plusencode = FALSE; |
| 1716 | bool urlskipslash = FALSE; |
| 1717 | bool leadingslash = FALSE; |
| 1718 | bool appendquery = FALSE; |
| 1719 | bool equalsencode = FALSE; |
| 1720 | size_t nalloc; |
| 1721 | |
| 1722 | if(!u) |
| 1723 | return CURLUE_BAD_HANDLE; |
| 1724 | if(!part) { |
| 1725 | /* setting a part to NULL clears it */ |
| 1726 | switch(what) { |
| 1727 | case CURLUPART_URL: |
| 1728 | break; |
| 1729 | case CURLUPART_SCHEME: |
| 1730 | storep = &u->scheme; |
| 1731 | u->guessed_scheme = FALSE; |
| 1732 | break; |
| 1733 | case CURLUPART_USER: |
| 1734 | storep = &u->user; |
| 1735 | break; |
| 1736 | case CURLUPART_PASSWORD: |
| 1737 | storep = &u->password; |
| 1738 | break; |
| 1739 | case CURLUPART_OPTIONS: |
| 1740 | storep = &u->options; |
| 1741 | break; |
| 1742 | case CURLUPART_HOST: |
| 1743 | storep = &u->host; |
| 1744 | break; |
| 1745 | case CURLUPART_ZONEID: |
| 1746 | storep = &u->zoneid; |
| 1747 | break; |
| 1748 | case CURLUPART_PORT: |
| 1749 | u->portnum = 0; |
| 1750 | storep = &u->port; |
| 1751 | break; |
| 1752 | case CURLUPART_PATH: |
| 1753 | storep = &u->path; |
| 1754 | break; |
| 1755 | case CURLUPART_QUERY: |
| 1756 | storep = &u->query; |
| 1757 | u->query_present = FALSE; |
| 1758 | break; |
| 1759 | case CURLUPART_FRAGMENT: |
| 1760 | storep = &u->fragment; |
| 1761 | u->fragment_present = FALSE; |
| 1762 | break; |
| 1763 | default: |
| 1764 | return CURLUE_UNKNOWN_PART; |
| 1765 | } |
| 1766 | if(storep && *storep) { |
| 1767 | Curl_safefree(*storep); |
| 1768 | } |
| 1769 | else if(!storep) { |
| 1770 | free_urlhandle(u); |
| 1771 | memset(u, 0, sizeof(struct Curl_URL)); |
| 1772 | } |
| 1773 | return CURLUE_OK; |
| 1774 | } |
| 1775 | |
| 1776 | nalloc = strlen(part); |
| 1777 | if(nalloc > CURL_MAX_INPUT_LENGTH) |
| 1778 | /* excessive input length */ |
| 1779 | return CURLUE_MALFORMED_INPUT; |
| 1780 | |
| 1781 | switch(what) { |
| 1782 | case CURLUPART_SCHEME: { |
| 1783 | size_t plen = strlen(part); |
| 1784 | const char *s = part; |
| 1785 | if((plen > MAX_SCHEME_LEN) || (plen < 1)) |
| 1786 | /* too long or too short */ |
| 1787 | return CURLUE_BAD_SCHEME; |
| 1788 | /* verify that it is a fine scheme */ |
| 1789 | if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(part)) |
| 1790 | return CURLUE_UNSUPPORTED_SCHEME; |
| 1791 | storep = &u->scheme; |
| 1792 | urlencode = FALSE; /* never */ |
| 1793 | if(ISALPHA(*s)) { |
| 1794 | /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ |
| 1795 | while(--plen) { |
| 1796 | if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) |
| 1797 | s++; /* fine */ |
| 1798 | else |
| 1799 | return CURLUE_BAD_SCHEME; |
| 1800 | } |
| 1801 | } |
| 1802 | else |
| 1803 | return CURLUE_BAD_SCHEME; |
| 1804 | u->guessed_scheme = FALSE; |
| 1805 | break; |
| 1806 | } |
| 1807 | case CURLUPART_USER: |
| 1808 | storep = &u->user; |
| 1809 | break; |
| 1810 | case CURLUPART_PASSWORD: |
| 1811 | storep = &u->password; |
| 1812 | break; |
| 1813 | case CURLUPART_OPTIONS: |
| 1814 | storep = &u->options; |
| 1815 | break; |
| 1816 | case CURLUPART_HOST: |
| 1817 | storep = &u->host; |
| 1818 | Curl_safefree(u->zoneid); |
| 1819 | break; |
| 1820 | case CURLUPART_ZONEID: |
| 1821 | storep = &u->zoneid; |
| 1822 | break; |
| 1823 | case CURLUPART_PORT: |
| 1824 | if(!ISDIGIT(part[0])) |
| 1825 | /* not a number */ |
| 1826 | return CURLUE_BAD_PORT_NUMBER; |
| 1827 | else { |
| 1828 | char *tmp; |
| 1829 | char *endp; |
| 1830 | unsigned long port; |
| 1831 | errno = 0; |
| 1832 | port = strtoul(part, &endp, 10); /* must be decimal */ |
| 1833 | if(errno || (port > 0xffff) || *endp) |
| 1834 | /* weirdly provided number, not good! */ |
| 1835 | return CURLUE_BAD_PORT_NUMBER; |
| 1836 | tmp = strdup(part); |
| 1837 | if(!tmp) |
| 1838 | return CURLUE_OUT_OF_MEMORY; |
| 1839 | free(u->port); |
| 1840 | u->port = tmp; |
| 1841 | u->portnum = (unsigned short)port; |
| 1842 | return CURLUE_OK; |
| 1843 | } |
| 1844 | case CURLUPART_PATH: |
| 1845 | urlskipslash = TRUE; |
| 1846 | leadingslash = TRUE; /* enforce */ |
| 1847 | storep = &u->path; |
| 1848 | break; |
| 1849 | case CURLUPART_QUERY: |
| 1850 | plusencode = urlencode; |
| 1851 | appendquery = (flags & CURLU_APPENDQUERY)?1:0; |
| 1852 | equalsencode = appendquery; |
| 1853 | storep = &u->query; |
| 1854 | u->query_present = TRUE; |
| 1855 | break; |
| 1856 | case CURLUPART_FRAGMENT: |
| 1857 | storep = &u->fragment; |
| 1858 | u->fragment_present = TRUE; |
| 1859 | break; |
| 1860 | case CURLUPART_URL: { |
| 1861 | /* |
| 1862 | * Allow a new URL to replace the existing (if any) contents. |
| 1863 | * |
| 1864 | * If the existing contents is enough for a URL, allow a relative URL to |
| 1865 | * replace it. |
| 1866 | */ |
| 1867 | CURLcode result; |
| 1868 | CURLUcode uc; |
| 1869 | char *oldurl; |
| 1870 | char *redired_url; |
Show 161 lines (not cited by the judge)
| 1700 | CURLUcode curl_url_set(CURLU *u, CURLUPart what, |
| 1701 | const char *part, unsigned int flags) |
| 1702 | { |
| 1703 | char **storep = NULL; |
| 1704 | bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0; |
| 1705 | bool plusencode = FALSE; |
| 1706 | bool urlskipslash = FALSE; |
| 1707 | bool leadingslash = FALSE; |
| 1708 | bool appendquery = FALSE; |
| 1709 | bool equalsencode = FALSE; |
| 1710 | size_t nalloc; |
| 1711 | |
| 1712 | if(!u) |
| 1713 | return CURLUE_BAD_HANDLE; |
| 1714 | if(!part) { |
| 1715 | /* setting a part to NULL clears it */ |
| 1716 | switch(what) { |
| 1717 | case CURLUPART_URL: |
| 1718 | break; |
| 1719 | case CURLUPART_SCHEME: |
| 1720 | storep = &u->scheme; |
| 1721 | break; |
| 1722 | case CURLUPART_USER: |
| 1723 | storep = &u->user; |
| 1724 | break; |
| 1725 | case CURLUPART_PASSWORD: |
| 1726 | storep = &u->password; |
| 1727 | break; |
| 1728 | case CURLUPART_OPTIONS: |
| 1729 | storep = &u->options; |
| 1730 | break; |
| 1731 | case CURLUPART_HOST: |
| 1732 | storep = &u->host; |
| 1733 | break; |
| 1734 | case CURLUPART_ZONEID: |
| 1735 | storep = &u->zoneid; |
| 1736 | break; |
| 1737 | case CURLUPART_PORT: |
| 1738 | u->portnum = 0; |
| 1739 | storep = &u->port; |
| 1740 | break; |
| 1741 | case CURLUPART_PATH: |
| 1742 | storep = &u->path; |
| 1743 | break; |
| 1744 | case CURLUPART_QUERY: |
| 1745 | storep = &u->query; |
| 1746 | u->query_present = FALSE; |
| 1747 | break; |
| 1748 | case CURLUPART_FRAGMENT: |
| 1749 | storep = &u->fragment; |
| 1750 | u->fragment_present = FALSE; |
| 1751 | break; |
| 1752 | default: |
| 1753 | return CURLUE_UNKNOWN_PART; |
| 1754 | } |
| 1755 | if(storep && *storep) { |
| 1756 | Curl_safefree(*storep); |
| 1757 | } |
| 1758 | else if(!storep) { |
| 1759 | free_urlhandle(u); |
| 1760 | memset(u, 0, sizeof(struct Curl_URL)); |
| 1761 | } |
| 1762 | return CURLUE_OK; |
| 1763 | } |
| 1764 | |
| 1765 | nalloc = strlen(part); |
| 1766 | if(nalloc > CURL_MAX_INPUT_LENGTH) |
| 1767 | /* excessive input length */ |
| 1768 | return CURLUE_MALFORMED_INPUT; |
| 1769 | |
| 1770 | switch(what) { |
| 1771 | case CURLUPART_SCHEME: { |
| 1772 | size_t plen = strlen(part); |
| 1773 | const char *s = part; |
| 1774 | if((plen > MAX_SCHEME_LEN) || (plen < 1)) |
| 1775 | /* too long or too short */ |
| 1776 | return CURLUE_BAD_SCHEME; |
| 1777 | /* verify that it is a fine scheme */ |
| 1778 | if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(part)) |
| 1779 | return CURLUE_UNSUPPORTED_SCHEME; |
| 1780 | storep = &u->scheme; |
| 1781 | urlencode = FALSE; /* never */ |
| 1782 | if(ISALPHA(*s)) { |
| 1783 | /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ |
| 1784 | while(--plen) { |
| 1785 | if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) |
| 1786 | s++; /* fine */ |
| 1787 | else |
| 1788 | return CURLUE_BAD_SCHEME; |
| 1789 | } |
| 1790 | } |
| 1791 | else |
| 1792 | return CURLUE_BAD_SCHEME; |
| 1793 | break; |
| 1794 | } |
| 1795 | case CURLUPART_USER: |
| 1796 | storep = &u->user; |
| 1797 | break; |
| 1798 | case CURLUPART_PASSWORD: |
| 1799 | storep = &u->password; |
| 1800 | break; |
| 1801 | case CURLUPART_OPTIONS: |
| 1802 | storep = &u->options; |
| 1803 | break; |
| 1804 | case CURLUPART_HOST: |
| 1805 | storep = &u->host; |
| 1806 | Curl_safefree(u->zoneid); |
| 1807 | break; |
| 1808 | case CURLUPART_ZONEID: |
| 1809 | storep = &u->zoneid; |
| 1810 | break; |
| 1811 | case CURLUPART_PORT: |
| 1812 | if(!ISDIGIT(part[0])) |
| 1813 | /* not a number */ |
| 1814 | return CURLUE_BAD_PORT_NUMBER; |
| 1815 | else { |
| 1816 | char *tmp; |
| 1817 | char *endp; |
| 1818 | unsigned long port; |
| 1819 | errno = 0; |
| 1820 | port = strtoul(part, &endp, 10); /* must be decimal */ |
| 1821 | if(errno || (port > 0xffff) || *endp) |
| 1822 | /* weirdly provided number, not good! */ |
| 1823 | return CURLUE_BAD_PORT_NUMBER; |
| 1824 | tmp = strdup(part); |
| 1825 | if(!tmp) |
| 1826 | return CURLUE_OUT_OF_MEMORY; |
| 1827 | free(u->port); |
| 1828 | u->port = tmp; |
| 1829 | u->portnum = (unsigned short)port; |
| 1830 | return CURLUE_OK; |
| 1831 | } |
| 1832 | case CURLUPART_PATH: |
| 1833 | urlskipslash = TRUE; |
| 1834 | leadingslash = TRUE; /* enforce */ |
| 1835 | storep = &u->path; |
| 1836 | break; |
| 1837 | case CURLUPART_QUERY: |
| 1838 | plusencode = urlencode; |
| 1839 | appendquery = (flags & CURLU_APPENDQUERY)?1:0; |
| 1840 | equalsencode = appendquery; |
| 1841 | storep = &u->query; |
| 1842 | u->query_present = TRUE; |
| 1843 | break; |
| 1844 | case CURLUPART_FRAGMENT: |
| 1845 | storep = &u->fragment; |
| 1846 | u->fragment_present = TRUE; |
| 1847 | break; |
| 1848 | case CURLUPART_URL: { |
| 1849 | /* |
| 1850 | * Allow a new URL to replace the existing (if any) contents. |
| 1851 | * |
| 1852 | * If the existing contents is enough for a URL, allow a relative URL to |
| 1853 | * replace it. |
| 1854 | */ |
| 1855 | CURLcode result; |
| 1856 | CURLUcode uc; |
| 1857 | char *oldurl; |
| 1858 | char *redired_url; |
| 1859 | |
| 1860 | if(!nalloc) |