| 488 | } |
| 489 | |
| 490 | void CrossPointWebServer::handleDownload() const { |
| 491 | if (!server->hasArg("path")) { |
| 492 | server->send(400, "text/plain", "Missing path"); |
| 493 | return; |
| 494 | } |
| 495 | |
| 496 | String itemPath = server->arg("path"); |
| 497 | if (itemPath.isEmpty() || itemPath == "/") { |
| 498 | server->send(400, "text/plain", "Invalid path"); |
| 499 | return; |
| 500 | } |
| 501 | if (!itemPath.startsWith("/")) { |
| 502 | itemPath = "/" + itemPath; |
| 503 | } |
| 504 | |
| 505 | const String itemName = itemPath.substring(itemPath.lastIndexOf('/') + 1); |
| 506 | if (itemName.startsWith(".")) { |
| 507 | server->send(403, "text/plain", "Cannot access system files"); |
| 508 | return; |
| 509 | } |
| 510 | for (const auto* item : HIDDEN_ITEMS) { |
| 511 | if (itemName.equals(item)) { |
| 512 | server->send(403, "text/plain", "Cannot access protected items"); |
| 513 | return; |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | if (!Storage.exists(itemPath.c_str())) { |
| 518 | server->send(404, "text/plain", "Item not found"); |
| 519 | return; |
| 520 | } |
| 521 | |
| 522 | HalFile file = Storage.open(itemPath.c_str()); |
| 523 | if (!file) { |
| 524 | server->send(500, "text/plain", "Failed to open file"); |
| 525 | return; |
| 526 | } |
| 527 | if (file.isDirectory()) { |
| 528 | file.close(); |
| 529 | server->send(400, "text/plain", "Path is a directory"); |
| 530 | return; |
| 531 | } |
| 532 | |
| 533 | String contentType = "application/octet-stream"; |
| 534 | if (isEpubFile(itemPath)) { |
| 535 | contentType = "application/epub+zip"; |
| 536 | } |
| 537 | |
| 538 | char nameBuf[128] = {0}; |
| 539 | String filename = "download"; |
| 540 | if (file.getName(nameBuf, sizeof(nameBuf))) { |
| 541 | filename = nameBuf; |
| 542 | } |
| 543 | |
| 544 | server->setContentLength(file.size()); |
| 545 | server->sendHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); |
| 546 | server->send(200, contentType.c_str(), ""); |
| 547 | |