Mercurial
comparison seobeo/s_http_client.c @ 221:ce7f4400c2de hg-web
[hg-web] Harden forge and add changeset UI
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 02 Aug 2026 09:01:24 -0700 |
| parents | 71ad34a8bc9a |
| children |
comparison
equal
deleted
inserted
replaced
| 219:8c9bb0b0759e | 221:ce7f4400c2de |
|---|---|
| 1 #include "seobeo/seobeo.h" | 1 #include "seobeo/seobeo.h" |
| 2 #include <ctype.h> | 2 #include <ctype.h> |
| 3 #include <time.h> | |
| 4 | |
| 5 static int64_t Seobeo_Client_Monotonic_Milliseconds(void) | |
| 6 { | |
| 7 struct timespec now; | |
| 8 clock_gettime(CLOCK_MONOTONIC, &now); | |
| 9 return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; | |
| 10 } | |
| 11 | |
| 12 static boolean Seobeo_Client_Read_Timed_Out(int32 timeout_ms, int64_t last_progress_ms) | |
| 13 { | |
| 14 return timeout_ms > 0 && | |
| 15 Seobeo_Client_Monotonic_Milliseconds() - last_progress_ms >= timeout_ms; | |
| 16 } | |
| 17 | |
| 18 static size_t Seobeo_Client_Find_Header_Length(const uint8 *buffer, size_t length) | |
| 19 { | |
| 20 if (!buffer || length < 4) | |
| 21 return 0; | |
| 22 for (size_t i = 0; i + 3 < length; i++) | |
| 23 { | |
| 24 if (buffer[i] == '\r' && buffer[i + 1] == '\n' && | |
| 25 buffer[i + 2] == '\r' && buffer[i + 3] == '\n') | |
| 26 return i + 4; | |
| 27 } | |
| 28 return 0; | |
| 29 } | |
| 30 | |
| 31 static const char *Seobeo_Client_Header_Value( | |
| 32 Seobeo_Request_Entry *headers, | |
| 33 const char *name) | |
| 34 { | |
| 35 if (!headers || !name) | |
| 36 return NULL; | |
| 37 for (size_t i = 0; i < Dowa_Array_Length(headers); i++) | |
| 38 { | |
| 39 if (headers[i].key && strcasecmp(headers[i].key, name) == 0) | |
| 40 return headers[i].value; | |
| 41 } | |
| 42 return NULL; | |
| 43 } | |
| 3 | 44 |
| 4 static void Seobeo_Client_Parse_Url(const char *url, char **p_host, | 45 static void Seobeo_Client_Parse_Url(const char *url, char **p_host, |
| 5 char **p_port, char **p_path, boolean *p_use_tls, Dowa_Arena *p_arena) | 46 char **p_port, char **p_path, boolean *p_use_tls, Dowa_Arena *p_arena) |
| 6 { | 47 { |
| 7 if (!url) | 48 if (!url) |
| 85 p_req->method = Dowa_Arena_Allocate(p_req->p_arena, 4); | 126 p_req->method = Dowa_Arena_Allocate(p_req->p_arena, 4); |
| 86 strcpy(p_req->method, "GET"); | 127 strcpy(p_req->method, "GET"); |
| 87 | 128 |
| 88 p_req->follow_redirects = FALSE; | 129 p_req->follow_redirects = FALSE; |
| 89 p_req->max_redirects = 10; | 130 p_req->max_redirects = 10; |
| 131 p_req->timeout_ms = 0; | |
| 90 | 132 |
| 91 return p_req; | 133 return p_req; |
| 92 } | 134 } |
| 93 | 135 |
| 94 void Seobeo_Client_Request_Set_Method(Seobeo_Client_Request *p_req, const char *method) | 136 void Seobeo_Client_Request_Set_Method(Seobeo_Client_Request *p_req, const char *method) |
| 147 | 189 |
| 148 p_req->follow_redirects = follow; | 190 p_req->follow_redirects = follow; |
| 149 p_req->max_redirects = max_redirects > 0 ? max_redirects : 10; | 191 p_req->max_redirects = max_redirects > 0 ? max_redirects : 10; |
| 150 } | 192 } |
| 151 | 193 |
| 194 void Seobeo_Client_Request_Set_Timeout_Milliseconds(Seobeo_Client_Request *p_req, int32 timeout_ms) | |
| 195 { | |
| 196 if (!p_req) | |
| 197 return; | |
| 198 p_req->timeout_ms = timeout_ms > 0 ? timeout_ms : 0; | |
| 199 } | |
| 200 | |
| 152 void Seobeo_Client_Request_Set_Download_Path(Seobeo_Client_Request *p_req, const char *path) | 201 void Seobeo_Client_Request_Set_Download_Path(Seobeo_Client_Request *p_req, const char *path) |
| 153 { | 202 { |
| 154 if (!p_req || !path) | 203 if (!p_req || !path) |
| 155 return; | 204 return; |
| 156 | 205 |
| 221 offset += snprintf(buffer + offset, buffer_size - offset, "\r\n"); | 270 offset += snprintf(buffer + offset, buffer_size - offset, "\r\n"); |
| 222 | 271 |
| 223 return offset; | 272 return offset; |
| 224 } | 273 } |
| 225 | 274 |
| 226 static Seobeo_Client_Response *Seobeo_Client_Parse_Response(Seobeo_Handle *p_handle, const char *download_path) | 275 static Seobeo_Client_Response *Seobeo_Client_Parse_Response( |
| 276 Seobeo_Handle *p_handle, | |
| 277 const char *download_path, | |
| 278 int32 timeout_ms) | |
| 227 { | 279 { |
| 228 Seobeo_Client_Response *p_resp = malloc(sizeof(Seobeo_Client_Response)); | 280 Seobeo_Client_Response *p_resp = malloc(sizeof(Seobeo_Client_Response)); |
| 229 if (!p_resp) | 281 if (!p_resp) |
| 230 return NULL; | 282 return NULL; |
| 231 | 283 |
| 236 { | 288 { |
| 237 free(p_resp); | 289 free(p_resp); |
| 238 return NULL; | 290 return NULL; |
| 239 } | 291 } |
| 240 | 292 |
| 293 int64_t last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); | |
| 241 while (TRUE) | 294 while (TRUE) |
| 242 { | 295 { |
| 243 int r = Seobeo_Handle_Read(p_handle); | 296 int r = Seobeo_Handle_Read(p_handle); |
| 244 if (r < 0) | |
| 245 return p_resp; | |
| 246 if (r == -2) | 297 if (r == -2) |
| 247 break; | 298 break; |
| 248 | 299 if (r < 0) |
| 249 if (p_handle->read_buffer_len >= 4 && strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) | 300 { |
| 301 Seobeo_Client_Response_Destroy(p_resp); | |
| 302 return NULL; | |
| 303 } | |
| 304 if (r > 0) | |
| 305 last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); | |
| 306 | |
| 307 if (Seobeo_Client_Find_Header_Length( | |
| 308 p_handle->read_buffer, p_handle->read_buffer_len) > 0) | |
| 250 break; | 309 break; |
| 251 | 310 |
| 252 if (r == 0) | 311 if (r == 0) |
| 312 { | |
| 313 if (Seobeo_Client_Read_Timed_Out(timeout_ms, last_progress_ms)) | |
| 314 { | |
| 315 Seobeo_Log(SEOBEO_ERROR, "HTTP response header timed out\n"); | |
| 316 Seobeo_Client_Response_Destroy(p_resp); | |
| 317 return NULL; | |
| 318 } | |
| 319 usleep(1000); | |
| 253 continue; | 320 continue; |
| 254 } | 321 } |
| 255 | 322 } |
| 256 char *buf = (char*)p_handle->read_buffer; | 323 |
| 257 char *hdr_end = strstr(buf, "\r\n\r\n"); | 324 size_t hdr_len = Seobeo_Client_Find_Header_Length( |
| 258 if (!hdr_end) | 325 p_handle->read_buffer, p_handle->read_buffer_len); |
| 259 return p_resp; | 326 if (hdr_len == 0) |
| 260 | 327 { |
| 261 size_t hdr_len = hdr_end - buf + 4; | 328 Seobeo_Client_Response_Destroy(p_resp); |
| 329 return NULL; | |
| 330 } | |
| 331 | |
| 332 char *buf = Dowa_Arena_Allocate(p_resp->p_arena, hdr_len + 1); | |
| 333 memcpy(buf, p_handle->read_buffer, hdr_len); | |
| 334 buf[hdr_len] = '\0'; | |
| 335 char *hdr_end = buf + hdr_len - 4; | |
| 262 | 336 |
| 263 char version[16]; | 337 char version[16]; |
| 264 int status_code; | 338 int status_code; |
| 265 char status_text[256]; | 339 char status_text[256]; |
| 266 int scan_result = sscanf(buf, "%15s %d %255[^\r\n]", version, &status_code, status_text); | 340 int scan_result = sscanf(buf, "%15s %d %255[^\r\n]", version, &status_code, status_text); |
| 326 line = next + 2; | 400 line = next + 2; |
| 327 } | 401 } |
| 328 | 402 |
| 329 Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); | 403 Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); |
| 330 | 404 |
| 331 void *p_cl_kv = Dowa_HashMap_Get_Ptr(p_resp->headers, "Content-Length"); | |
| 332 size_t body_len = 0; | 405 size_t body_len = 0; |
| 333 if (p_cl_kv) | 406 const char *content_length = Seobeo_Client_Header_Value( |
| 334 { | 407 p_resp->headers, "Content-Length"); |
| 335 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; | 408 if (content_length) |
| 336 body_len = atoi(content_length_str); | 409 { |
| 410 body_len = (size_t)strtoull(content_length, NULL, 10); | |
| 337 } | 411 } |
| 338 | 412 |
| 339 FILE *p_file = NULL; | 413 FILE *p_file = NULL; |
| 340 if (download_path) | 414 if (download_path) |
| 341 { | 415 { |
| 364 else | 438 else |
| 365 memcpy(body + total_read, p_handle->read_buffer, to_copy); | 439 memcpy(body + total_read, p_handle->read_buffer, to_copy); |
| 366 | 440 |
| 367 total_read += to_copy; | 441 total_read += to_copy; |
| 368 Seobeo_Handle_Consume(p_handle, (uint32)to_copy); | 442 Seobeo_Handle_Consume(p_handle, (uint32)to_copy); |
| 443 last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); | |
| 369 } | 444 } |
| 370 | 445 |
| 371 if (total_read < body_len) | 446 if (total_read < body_len) |
| 372 { | 447 { |
| 373 int r = Seobeo_Handle_Read(p_handle); | 448 int r = Seobeo_Handle_Read(p_handle); |
| 374 if (r < 0 || r == -2) | 449 if (r == -2 || r < 0) |
| 375 break; | 450 { |
| 451 if (p_file) fclose(p_file); | |
| 452 Seobeo_Client_Response_Destroy(p_resp); | |
| 453 return NULL; | |
| 454 } | |
| 455 if (r > 0) | |
| 456 last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); | |
| 376 if (r == 0) | 457 if (r == 0) |
| 458 { | |
| 459 if (Seobeo_Client_Read_Timed_Out(timeout_ms, last_progress_ms)) | |
| 460 { | |
| 461 Seobeo_Log(SEOBEO_ERROR, "HTTP response body timed out\n"); | |
| 462 if (p_file) fclose(p_file); | |
| 463 Seobeo_Client_Response_Destroy(p_resp); | |
| 464 return NULL; | |
| 465 } | |
| 466 usleep(1000); | |
| 377 continue; | 467 continue; |
| 468 } | |
| 378 } | 469 } |
| 379 } | 470 } |
| 380 | 471 |
| 381 if (!download_path) | 472 if (!download_path) |
| 382 { | 473 { |
| 395 size_t used = 0; | 486 size_t used = 0; |
| 396 char *body = download_path ? NULL : Dowa_Arena_Allocate(p_resp->p_arena, cap); | 487 char *body = download_path ? NULL : Dowa_Arena_Allocate(p_resp->p_arena, cap); |
| 397 | 488 |
| 398 while (1) | 489 while (1) |
| 399 { | 490 { |
| 400 int n = Seobeo_Handle_Read(p_handle); | 491 int n = p_handle->read_buffer_len > 0 |
| 492 ? (int)p_handle->read_buffer_len | |
| 493 : Seobeo_Handle_Read(p_handle); | |
| 401 if (n > 0) | 494 if (n > 0) |
| 402 { | 495 { |
| 496 last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); | |
| 403 if (download_path) | 497 if (download_path) |
| 404 { | 498 { |
| 405 fwrite(p_handle->read_buffer, 1, p_handle->read_buffer_len, p_file); | 499 fwrite(p_handle->read_buffer, 1, p_handle->read_buffer_len, p_file); |
| 406 used += p_handle->read_buffer_len; | 500 used += p_handle->read_buffer_len; |
| 407 } | 501 } |
| 418 Seobeo_Handle_Consume(p_handle, (uint32)p_handle->read_buffer_len); | 512 Seobeo_Handle_Consume(p_handle, (uint32)p_handle->read_buffer_len); |
| 419 } | 513 } |
| 420 else if (n == -2) | 514 else if (n == -2) |
| 421 break; | 515 break; |
| 422 else if (n == 0) | 516 else if (n == 0) |
| 517 { | |
| 518 if (Seobeo_Client_Read_Timed_Out(timeout_ms, last_progress_ms)) | |
| 519 { | |
| 520 Seobeo_Log(SEOBEO_ERROR, "HTTP response body timed out\n"); | |
| 521 if (p_file) fclose(p_file); | |
| 522 Seobeo_Client_Response_Destroy(p_resp); | |
| 523 return NULL; | |
| 524 } | |
| 525 usleep(1000); | |
| 423 continue; | 526 continue; |
| 527 } | |
| 424 else | 528 else |
| 425 break; | 529 { |
| 530 if (p_file) fclose(p_file); | |
| 531 Seobeo_Client_Response_Destroy(p_resp); | |
| 532 return NULL; | |
| 533 } | |
| 426 } | 534 } |
| 427 | 535 |
| 428 if (!download_path) | 536 if (!download_path) |
| 429 { | 537 { |
| 430 p_resp->body = body; | 538 p_resp->body = body; |
| 466 { | 574 { |
| 467 Seobeo_Handle_Destroy(p_handle); | 575 Seobeo_Handle_Destroy(p_handle); |
| 468 return NULL; | 576 return NULL; |
| 469 } | 577 } |
| 470 | 578 |
| 471 Seobeo_Client_Response *p_resp = Seobeo_Client_Parse_Response(p_handle, p_req->download_path); | 579 Seobeo_Client_Response *p_resp = |
| 580 Seobeo_Client_Parse_Response(p_handle, p_req->download_path, p_req->timeout_ms); | |
| 472 | 581 |
| 473 Seobeo_Handle_Destroy(p_handle); | 582 Seobeo_Handle_Destroy(p_handle); |
| 474 | 583 |
| 475 return p_resp; | 584 return p_resp; |
| 476 } | 585 } |