CVE-2025-5399: WebSocket endless loop
What the report claims
curl_ws_send() contains an infinite loop that can be triggered by a malicious server when using curl_ws_recv() and curl_ws_send() with specific frame construction, causing 100% CPU usage and indefinite hang.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2025-5399 WebSocket endless loop
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code does what the report says, but curl's own policy may exclude it. A maintainer decides.
curl policy: Busy-loopsSee the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The code supports the existence of an infinite loop in curl_ws_send when ws_enc_write_payload fails to make progress and ws_flush becomes a no-op.
- However, the project's security policy explicitly classifies 'Busy-loops' as not security issues: 'Busy-loops that consume 100% CPU time but eventually end... are not considered security problems... while a prolonged such busy-loop is a nasty bug, we do not consider it a security problem.'
- The policy further notes malicious servers can already cause DoS by stalling or being slow, and applications must handle 100% CPU transfer loops.
- Therefore, although the bug appears real and reachable by a remote server, it falls under the project's 'Busy-loops' exclusion and is not treated as a security vulnerability.
How this analysis ran 5 claims · 12 citations verified
Evidence check: the behavior is real; the project's policy section 'Busy-loops' may exclude it
Checked against curl permalink 12d13b84fa40 (12d13b84fa40). Dev 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 PoC. The code does show a plausible infinite loop in curl_ws_send when a fragmented frame is interleaved with an auto-PONG response. However, per curl's security policy, 'Busy-loops' that consume 100% CPU (even indefinitely) are explicitly not considered security issues, as malicious servers can already cause equivalent DoS by stalling, and applications must handle transfer loops that consume full CPU. We will still track this as a functional bug to fix, but it does not qualify as a security vulnerability.
Inspect all claims and source code 5 claims · 8 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
curl_ws_send() contains an infinite loop when ws->sendbuf_payload is zero and buflen > ws->sendbuf_payload is always true
Read the reasoning
The code shows a plausible infinite loop: if ws_enc_write_payload repeatedly fails to add payload (returns 0/CURLE_AGAIN), ws_flush sends the frame header once then becomes a no-op returning success, ws->sendbuf_payload stays 0, buflen never decreases, and the loop condition buflen > 0 remains true forever.
in function curl_ws_send() on lines 1376-1419
Read the reasoning
The function and loop are at the cited locations in lib/ws.c at the target revision.
malicious server sends a PING message while constructing a frame via CURLWS_OFFSET causing ws_flush() to return success without modifying sent, buffer, or buflen
Read the reasoning
The auto-PONG response to PING frames is confirmed by the comment. The PoC sequence shows PING arrival queues a PONG in sendbuf, which is flushed at line 1365 before the loop starts, setting up the encoder state for the continuation frame.
program consumes 100% CPU and never terminates
Read the reasoning
An infinite loop in curl_ws_send consumes 100% CPU and never returns, matching the reported impact.
client repeatedly calls curl_ws_send() with specific payloads and then curl_ws_recv() to receive a PING, leading to infinite loop in curl_ws_send()
Read the reasoning
The PoC uses documented APIs (curl_ws_send, curl_ws_recv, CURLWS_OFFSET, CURLWS_CONT) in a sequence that the code logic shows can trigger the infinite loop when auto-PONG interleaves with a fragmented frame.
Read the original report (excerpt)
Source code
| 1290 | CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, |
| C2: curl_ws_send function definition | |
| C5: curl_ws_send API matches PoC usage | |
| 1291 | size_t buflen, size_t *sent, |
| 1292 | curl_off_t fragsize, |
| 1293 | unsigned int flags) |
| 1294 | { |
| 1295 | struct websocket *ws; |
| 1296 | const unsigned char *buffer = buffer_arg; |
| 1297 | ssize_t n; |
| 1298 | CURLcode result = CURLE_OK; |
| 1299 | struct Curl_easy *data = d; |
| 1300 | |
| 1301 | CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T |
| 1302 | ", flags=%x), raw=%d", |
| 1303 | buflen, fragsize, flags, data->set.ws_raw_mode); |
| 1304 | *sent = 0; |
| 1305 | if(!data->conn && data->set.connect_only) { |
| 1306 | result = Curl_connect_only_attach(data); |
| 1307 | if(result) |
| 1308 | goto out; |
| 1309 | } |
| 1310 | if(!data->conn) { |
| 1311 | failf(data, "No associated connection"); |
| 1312 | result = CURLE_SEND_ERROR; |
| 1313 | goto out; |
| 1314 | } |
| 1315 | ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); |
| 1316 | if(!ws) { |
| 1317 | failf(data, "Not a websocket transfer"); |
| 1318 | result = CURLE_SEND_ERROR; |
| 1319 | goto out; |
| 1320 | } |
| 1321 | |
| 1322 | if(data->set.ws_raw_mode) { |
| 1323 | /* In raw mode, we write directly to the connection */ |
| 1324 | /* try flushing any content still waiting to be sent. */ |
| 1325 | result = ws_flush(data, ws, FALSE); |
| 1326 | if(result) |
| 1327 | goto out; |
| 1328 | |
| 1329 | if(fragsize || flags) { |
| 1330 | failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero"); |
| 1331 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1332 | } |
| 1333 | result = ws_send_raw(data, buffer, buflen, sent); |
| 1334 | goto out; |
| 1335 | } |
| 1336 | |
| 1337 | /* Not RAW mode, buf we do the frame encoding */ |
| 1338 | |
| 1339 | if(ws->enc.payload_remain || !Curl_bufq_is_empty(&ws->sendbuf)) { |
| 1340 | /* a frame is ongoing with payload buffered or more payload |
| 1341 | * that needs to be encoded into the buffer */ |
| 1342 | if(buflen < ws->sendbuf_payload) { |
| 1343 | /* We have been called with LESS buffer data than before. This |
| 1344 | * is not how it's supposed too work. */ |
| 1345 | failf(data, "curl_ws_send() called with smaller 'buflen' than " |
| 1346 | "bytes already buffered in previous call, %zu vs %zu", |
| 1347 | buflen, ws->sendbuf_payload); |
| 1348 | result = CURLE_BAD_FUNCTION_ARGUMENT; |
| 1349 | goto out; |
| 1350 | } |
| 1351 | if((curl_off_t)buflen > |
| 1352 | (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) { |
| 1353 | /* too large buflen beyond payload length of frame */ |
| 1354 | infof(data, "WS: unaligned frame size (sending %zu instead of %" |
| 1355 | FMT_OFF_T ")", |
| 1356 | buflen, ws->enc.payload_remain + ws->sendbuf_payload); |
| 1357 | result = CURLE_BAD_FUNCTION_ARGUMENT; |
| 1358 | goto out; |
| 1359 | } |
| 1360 | } |
| 1361 | else { |
| 1362 | /* starting a new frame, we want a clean sendbuf */ |
| 1363 | curl_off_t payload_len = (flags & CURLWS_OFFSET) ? |
| 1364 | fragsize : (curl_off_t)buflen; |
| 1365 | result = ws_flush(data, ws, Curl_is_in_callback(data)); |
| C3: ws_flush called before loop to send any queued data (e.g., auto-PONG) | |
| 1366 | if(result) |
| 1367 | goto out; |
| 1368 | |
| 1369 | n = ws_enc_write_head(data, &ws->enc, flags, payload_len, |
| C3: ws_enc_write_head writes new frame header after flush | |
| 1370 | &ws->sendbuf, &result); |
| 1371 | if(n < 0) |
| 1372 | goto out; |
| 1373 | } |
| 1374 | |
| 1375 | /* While there is either sendbuf to flush OR more payload to encode... */ |
| 1376 | while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) { |
| C1: while loop condition: while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) | |
| C2: while loop at line 1376 | |
| C4: Infinite while loop with no progress when ws->sendbuf_payload==0 and buflen>0 | |
| 1377 | /* Try to add more payload to sendbuf */ |
| 1378 | if(buflen > ws->sendbuf_payload) { |
| C1: if(buflen > ws->sendbuf_payload) block calls ws_enc_write_payload which may not increase ws->sendbuf_payload if it returns 0/CURLE_AGAIN | |
| 1379 | size_t prev_len = Curl_bufq_len(&ws->sendbuf); |
| 1380 | n = ws_enc_write_payload(&ws->enc, data, |
| 1381 | buffer + ws->sendbuf_payload, |
| 1382 | buflen - ws->sendbuf_payload, |
| 1383 | &ws->sendbuf, &result); |
| 1384 | if(n < 0 && (result != CURLE_AGAIN)) |
| 1385 | goto out; |
| 1386 | ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len; |
| 1387 | } |
| 1388 | |
| 1389 | /* flush, blocking when in callback */ |
| 1390 | result = ws_flush(data, ws, Curl_is_in_callback(data)); |
| 1391 | if(!result) { |
| C1: if(!result) { *sent += ws->sendbuf_payload; buffer += ws->sendbuf_payload; buflen -= ws->sendbuf_payload; ws->sendbuf_payload = 0; } | |
| 1392 | *sent += ws->sendbuf_payload; |
| 1393 | buffer += ws->sendbuf_payload; |
| 1394 | buflen -= ws->sendbuf_payload; |
| 1395 | ws->sendbuf_payload = 0; |
| 1396 | } |
| 1397 | else if(result == CURLE_AGAIN) { |
| 1398 | if(ws->sendbuf_payload > Curl_bufq_len(&ws->sendbuf)) { |
| 1399 | /* blocked, part of payload bytes remain, report length |
| 1400 | * that we managed to send. */ |
| 1401 | size_t flushed = (ws->sendbuf_payload - Curl_bufq_len(&ws->sendbuf)); |
| 1402 | *sent += flushed; |
| 1403 | ws->sendbuf_payload -= flushed; |
| 1404 | result = CURLE_OK; |
| 1405 | goto out; |
| 1406 | } |
| 1407 | else { |
| 1408 | /* blocked before sending headers or 1st payload byte. We cannot report |
| 1409 | * OK on 0-length send (caller counts only payload) and EAGAIN */ |
| 1410 | CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu", |
| 1411 | ws->sendbuf_payload, buflen); |
| 1412 | DEBUGASSERT(*sent == 0); |
| 1413 | result = CURLE_AGAIN; |
| 1414 | goto out; |
| 1415 | } |
| 1416 | } |
| 1417 | else |
| 1418 | goto out; /* real error sending the data */ |
| 1419 | } |
| 1420 | |
| 1421 | out: |
| 1422 | CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T |
| 1423 | ", flags=%x, raw=%d) -> %d, %zu", |
| 1424 | buflen, fragsize, flags, data->set.ws_raw_mode, result, *sent); |
| 1425 | return result; |
| 1426 | } |
| 1427 | |
| 1428 | static CURLcode ws_setup_conn(struct Curl_easy *data, |
| 1429 | struct connectdata *conn) |
| 1430 | { |
| 1431 | /* WebSockets is 1.1 only (for now) */ |
| 1432 | data->state.http_neg.accept_09 = FALSE; |
| 1433 | data->state.http_neg.only_10 = FALSE; |
| 1434 | data->state.http_neg.wanted = CURL_HTTP_V1x; |
| 1435 | data->state.http_neg.allowed = CURL_HTTP_V1x; |
| 1436 | return Curl_http_setup_conn(data, conn); |
| 1437 | } |
| 1438 | |
| 1439 | |
| 1440 | CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *d) |
| 1441 | { |
| 1442 | /* we only return something for websocket, called from within the callback |
| 1443 | when not using raw mode */ |
| 1444 | struct Curl_easy *data = d; |
| 1153 | static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, |
| 1154 | bool blocking) |
| 1155 | { |
| 1156 | if(!Curl_bufq_is_empty(&ws->sendbuf)) { |
| C1: ws_flush returns CURLE_OK immediately if sendbuf empty, allowing loop to spin with ws->sendbuf_payload==0 | |
| 1157 | CURLcode result; |
| 1158 | const unsigned char *out; |
| 1159 | size_t outlen, n; |
| 1160 | #ifdef DEBUGBUILD |
| 1161 | /* Simulate a blocking send after this chunk has been sent */ |
| 1162 | bool eagain_next = FALSE; |
| 1163 | size_t chunk_egain = 0; |
| 1164 | const char *p = getenv("CURL_WS_CHUNK_EAGAIN"); |
| 1165 | if(p) { |
| 1166 | curl_off_t l; |
| 1167 | if(!curlx_str_number(&p, &l, 1*1024*1024)) |
| 1168 | chunk_egain = (size_t)l; |
| 1169 | } |
| 1170 | #endif |
| 1171 | |
| 1172 | while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) { |
| 1173 | #ifdef DEBUGBUILD |
| 1174 | if(eagain_next) |
| 1175 | return CURLE_AGAIN; |
| 1176 | if(chunk_egain && (outlen > chunk_egain)) { |
| 1177 | outlen = chunk_egain; |
| 1178 | eagain_next = TRUE; |
| 1179 | } |
| 1180 | #endif |
| 1181 | if(blocking) { |
| 1182 | result = ws_send_raw_blocking(data, ws, (const char *)out, outlen); |
| 1183 | n = result ? 0 : outlen; |
| 1184 | } |
| 1185 | else if(data->set.connect_only || Curl_is_in_callback(data)) |
| 1186 | result = Curl_senddata(data, out, outlen, &n); |
| 1187 | else { |
| 1188 | result = Curl_xfer_send(data, out, outlen, FALSE, &n); |
| 1189 | if(!result && !n && outlen) |
| 1190 | result = CURLE_AGAIN; |
| 1191 | } |
| 1192 | |
| 1193 | if(result == CURLE_AGAIN) { |
| 1194 | CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer", |
| 1195 | Curl_bufq_len(&ws->sendbuf)); |
| 1196 | return result; |
| 1197 | } |
| 1198 | else if(result) { |
| 1199 | failf(data, "WS: flush, write error %d", result); |
| 1200 | return result; |
| 1201 | } |
| 1202 | else { |
| 1203 | infof(data, "WS: flushed %zu bytes", n); |
| 1204 | Curl_bufq_skip(&ws->sendbuf, n); |
| 1205 | } |
| 1206 | } |
| 1207 | } |
| 1208 | return CURLE_OK; |
| 1209 | } |
| 1067 | CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer, |
| C5: curl_ws_recv API matches PoC usage | |
| 1068 | size_t buflen, size_t *nread, |
| 1069 | const struct curl_ws_frame **metap) |
| 1070 | { |
| 1071 | struct Curl_easy *data = d; |
| 1072 | struct connectdata *conn = data->conn; |
| 1073 | struct websocket *ws; |
| 1074 | struct ws_collect ctx; |
| 1075 | |
| 1076 | *nread = 0; |
| 1077 | *metap = NULL; |
| 1078 | |
| 1079 | if(!conn) { |
| 1080 | /* Unhappy hack with lifetimes of transfers and connection */ |
| 1081 | if(!data->set.connect_only) { |
| 1082 | failf(data, "CONNECT_ONLY is required"); |
| 1083 | return CURLE_UNSUPPORTED_PROTOCOL; |
| 1084 | } |
| 1085 | |
| 1086 | Curl_getconnectinfo(data, &conn); |
| 1087 | if(!conn) { |
| 1088 | failf(data, "connection not found"); |
| 1089 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1090 | } |
| 1091 | } |
| 1092 | ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); |
| 1093 | if(!ws) { |
| 1094 | failf(data, "connection is not setup for websocket"); |
| 1095 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1096 | } |
| 1097 | |
| 1098 | |
| 1099 | memset(&ctx, 0, sizeof(ctx)); |
| 1100 | ctx.data = data; |
| 1101 | ctx.buffer = buffer; |
| 1102 | ctx.buflen = buflen; |
| 1103 | |
| 1104 | while(1) { |
| 1105 | CURLcode result; |
| 1106 | |
| 1107 | /* receive more when our buffer is empty */ |
| 1108 | if(Curl_bufq_is_empty(&ws->recvbuf)) { |
| 1109 | ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result); |
| 1110 | if(n < 0) { |
| 1111 | return result; |
| 1112 | } |
| 1113 | else if(n == 0) { |
| 1114 | /* connection closed */ |
| 1115 | infof(data, "connection expectedly closed?"); |
| 1116 | return CURLE_GOT_NOTHING; |
| 1117 | } |
| 1118 | CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", |
| 1119 | Curl_bufq_len(&ws->recvbuf)); |
| 1120 | } |
| 1121 | |
| 1122 | result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, |
| 1123 | ws_client_collect, &ctx); |
| 1124 | if(result == CURLE_AGAIN) { |
| 1125 | if(!ctx.written) { |
| 1126 | ws_dec_info(&ws->dec, data, "need more input"); |
| 1127 | continue; /* nothing written, try more input */ |
| 1128 | } |
| 1129 | break; |
| 1130 | } |
| 1131 | else if(result) { |
| 1132 | return result; |
| 1133 | } |
| 1134 | else if(ctx.written) { |
| 1135 | /* The decoded frame is passed back to our caller. |
| C3: Comment in curl_ws_recv: 'There are frames like PING were we auto-respond to and that we do not return.' | |
| 1136 | * There are frames like PING were we auto-respond to and |
| 1137 | * that we do not return. For these `ctx.written` is not set. */ |
| 1138 | break; |
| 1139 | } |
| 1140 | } |
| 1141 | |
| 1142 | /* update frame information to be passed back */ |
| 1143 | update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset, |
| 1144 | ctx.payload_len, ctx.bufidx); |
| 1145 | *metap = &ws->frame; |
| 1146 | *nread = ws->frame.len; |
| 1147 | CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" |
| 1148 | FMT_OFF_T ", %" FMT_OFF_T " left)", |
| 1149 | buflen, *nread, ws->frame.offset, ws->frame.bytesleft); |
| 1150 | return CURLE_OK; |
| 1151 | } |
Show 94 lines (not cited by the judge)
| 257 | static int loop(const unsigned char *pattern, const unsigned char *string, |
| 258 | int maxstars) |
| 259 | { |
| 260 | const unsigned char *p = (const unsigned char *)pattern; |
| 261 | const unsigned char *s = (const unsigned char *)string; |
| 262 | unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; |
| 263 | |
| 264 | for(;;) { |
| 265 | const unsigned char *pp; |
| 266 | |
| 267 | switch(*p) { |
| 268 | case '*': |
| 269 | if(!maxstars) |
| 270 | return CURL_FNMATCH_NOMATCH; |
| 271 | /* Regroup consecutive stars and question marks. This can be done because |
| 272 | '*?*?*' can be expressed as '??*'. */ |
| 273 | for(;;) { |
| 274 | if(*++p == '\0') |
| 275 | return CURL_FNMATCH_MATCH; |
| 276 | if(*p == '?') { |
| 277 | if(!*s++) |
| 278 | return CURL_FNMATCH_NOMATCH; |
| 279 | } |
| 280 | else if(*p != '*') |
| 281 | break; |
| 282 | } |
| 283 | /* Skip string characters until we find a match with pattern suffix. */ |
| 284 | for(maxstars--; *s; s++) { |
| 285 | if(loop(p, s, maxstars) == CURL_FNMATCH_MATCH) |
| 286 | return CURL_FNMATCH_MATCH; |
| 287 | } |
| 288 | return CURL_FNMATCH_NOMATCH; |
| 289 | case '?': |
| 290 | if(!*s) |
| 291 | return CURL_FNMATCH_NOMATCH; |
| 292 | s++; |
| 293 | p++; |
| 294 | break; |
| 295 | case '\0': |
| 296 | return *s ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; |
| 297 | case '\\': |
| 298 | if(p[1]) |
| 299 | p++; |
| 300 | if(*s++ != *p++) |
| 301 | return CURL_FNMATCH_NOMATCH; |
| 302 | break; |
| 303 | case '[': |
| 304 | pp = p + 1; /* Copy in case of syntax error in set. */ |
| 305 | if(setcharset(&pp, charset)) { |
| 306 | bool found = FALSE; |
| 307 | if(!*s) |
| 308 | return CURL_FNMATCH_NOMATCH; |
| 309 | if(charset[(unsigned int)*s]) |
| 310 | found = TRUE; |
| 311 | else if(charset[CURLFNM_ALNUM]) |
| 312 | found = ISALNUM(*s); |
| 313 | else if(charset[CURLFNM_ALPHA]) |
| 314 | found = ISALPHA(*s); |
| 315 | else if(charset[CURLFNM_DIGIT]) |
| 316 | found = ISDIGIT(*s); |
| 317 | else if(charset[CURLFNM_XDIGIT]) |
| 318 | found = ISXDIGIT(*s); |
| 319 | else if(charset[CURLFNM_PRINT]) |
| 320 | found = ISPRINT(*s); |
| 321 | else if(charset[CURLFNM_SPACE]) |
| 322 | found = ISBLANK(*s); |
| 323 | else if(charset[CURLFNM_UPPER]) |
| 324 | found = ISUPPER(*s); |
| 325 | else if(charset[CURLFNM_LOWER]) |
| 326 | found = ISLOWER(*s); |
| 327 | else if(charset[CURLFNM_BLANK]) |
| 328 | found = ISBLANK(*s); |
| 329 | else if(charset[CURLFNM_GRAPH]) |
| 330 | found = ISGRAPH(*s); |
| 331 | |
| 332 | if(charset[CURLFNM_NEGATE]) |
| 333 | found = !found; |
| 334 | |
| 335 | if(!found) |
| 336 | return CURL_FNMATCH_NOMATCH; |
| 337 | p = pp + 1; |
| 338 | s++; |
| 339 | break; |
| 340 | } |
| 341 | /* Syntax error in set; mismatch! */ |
| 342 | return CURL_FNMATCH_NOMATCH; |
| 343 | |
| 344 | default: |
| 345 | if(*p++ != *s++) |
| 346 | return CURL_FNMATCH_NOMATCH; |
| 347 | break; |
| 348 | } |
| 349 | } |
| 350 | } |
Show 1 lines (not cited by the judge)
| 45 | #define CURLWS_OFFSET (1<<5) |
Show 4 lines (not cited by the judge)
| 285 | bool Curl_bufq_is_empty(const struct bufq *q) |
| 286 | { |
| 287 | return !q->head || chunk_is_empty(q->head); |
| 288 | } |
Show 4 lines (not cited by the judge)
| 3550 | bool Curl_is_in_callback(struct Curl_easy *data) |
| 3551 | { |
| 3552 | return data && data->multi && data->multi->in_callback; |
| 3553 | } |
Show 42 lines (not cited by the judge)
| 74 | while(<STDIN>) { |
| 75 | my $l = $_; |
| 76 | if($fl) { |
| 77 | # continued deprecation |
| 78 | if($l =~ /(.*)\),/) { |
| 79 | $fl .= $1; |
| 80 | |
| 81 | # the end |
| 82 | my @p=split(/, */, $fl); |
| 83 | add($p[0], $p[1], $p[2]); |
| 84 | undef $fl; |
| 85 | } |
| 86 | else { |
| 87 | # another line to append |
| 88 | chomp $l; |
| 89 | $fl .= $l; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | if(/^ *CURLOPTDEPRECATED\((.*)/) { |
| 94 | $fl = $1; |
| 95 | chomp $fl; |
| 96 | } |
| 97 | |
| 98 | if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) { |
| 99 | my($opt, $type, $num)=($1,$2,$3); |
| 100 | add($opt, $type, $num); |
| 101 | } |
| 102 | |
| 103 | # alias for an older option |
| 104 | # old = new |
| 105 | if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) { |
| 106 | my ($o, $n)=($1, $2); |
| 107 | # skip obsolete ones |
| 108 | if(($n !~ /OBSOLETE/) && ($o !~ /OBSOLETE/)) { |
| 109 | $o =~ s/^CURLOPT_//; |
| 110 | $n =~ s/^CURLOPT_//; |
| 111 | $alias{$o} = $n; |
| 112 | push @names, $o; |
| 113 | } |
| 114 | } |
| 115 | } |