I already have a Server/Client application, clients connect to the server via a token, server verifies the token and only then server proceeds to send files to the client(s).
However, when I try to send and receive files, the checksum is different on client's end, BUT only for larger files. For smaller 39 byte text files is the checksum exactly the same, but as soon as I try to transfer something larger, the checksum differs.
I really need to use HTTP protocol & OpenSSL.Here are my functions:
std::size_t SendFiles(std::shared_ptr<Connection> connection, const std::vector<std::filesystem::path>& files){ // Basic validation if (!connection || connection->GetSocket() < 0) throw std::runtime_error("Invalid connection."); // Prepare boundary and initial response const std::string boundary = "----FILE_BOUNDARY"; std::string header ="HTTP/1.1 200 OK\r\n""Content-Type: multipart/mixed; boundary=" + boundary +"\r\n" +"expected_files=" + std::to_string(files.size()) +"\r\n""Connection: close\r\n\r\n"; if (SSL_write(connection->GetSSL(), header.data(), static_cast<int>(header.size())) <= 0) throw std::runtime_error("Failed to send initial response headers."); std::size_t sent_files = 0; for (auto &fp : files) { // Retrieve content from preloaded storage auto it = preloadedFiles.find(fp); if (it == preloadedFiles.end()) continue; // Skip if missing const std::string_view content = it->second; const std::string fname = fp.filename().string(); // Send MIME part headers std::string part ="--" + boundary +"\r\n""Content-Type: application/octet-stream\r\n""Content-Length: " + std::to_string(content.size()) +"\r\n""Content-Disposition: attachment; filename=\"" + fname +"\"\r\n""Content-Transfer-Encoding: binary\r\n\r\n"; if (SSL_write(connection->GetSSL(), part.data(), static_cast<int>(part.size())) <= 0) break; // Send file in chunks std::string_view chunk = content; while (!chunk.empty()) { const int to_send = std::min<int>(static_cast<int>(chunk.size()), 16384); const int written = SSL_write(connection->GetSSL(), chunk.data(), to_send); if (written <= 0) { const int err = SSL_get_error(connection->GetSSL(), written); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue; return sent_files; } chunk.remove_prefix(written); } // End of this file part static const std::string crlf = "\r\n"; if (SSL_write(connection->GetSSL(), crlf.data(), static_cast<int>(crlf.size())) <= 0) break; sent_files++; } // Final boundary const std::string closing = "--" + boundary +"--\r\n"; SSL_write(connection->GetSSL(), closing.data(), static_cast<int>(closing.size())); return sent_files;}std::size_t ReceiveFiles(std::shared_ptr<Connection> connection, const std::filesystem::path &destination_dir){ if (!connection || connection->GetSocket() < 0) throw std::runtime_error("Invalid connection."); std::string buffer; buffer.reserve(65536); const std::size_t chunkSize = 16384; char temp[chunkSize]; bool headersParsed = false; std::string boundary; int expectedFiles = 0, filesReceived = 0; bool readingFile = false; std::ofstream outFile; uint64_t contentLeft = 0; // Continuously read from SSL while (true) { // If we've met the file count, stop if (filesReceived == expectedFiles && expectedFiles > 0) break; const int r = SSL_read(connection->GetSSL(), temp, chunkSize); if (r <= 0) { const int err = SSL_get_error(connection->GetSSL(), r); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue; if (err == SSL_ERROR_ZERO_RETURN) break; return filesReceived; } buffer.append(temp, r); // Parse headers first if (!headersParsed) { auto pos = buffer.find("\r\n\r\n"); if (pos == std::string::npos) continue; const std::string head = buffer.substr(0, pos); buffer.erase(0, pos + 4); headersParsed = true; // Extract boundary const std::string btag = "boundary="; if (auto bpos = head.find(btag); bpos != std::string::npos) { auto start = bpos + btag.size(); auto end = head.find("\r\n", start); boundary = "--" + head.substr(start, end - start); } // Extract expected files const std::string etag = "expected_files="; if (auto epos = head.find(etag); epos != std::string::npos) { auto start = epos + etag.size(); auto end = head.find("\r\n", start); expectedFiles = std::stoi(head.substr(start, end - start)); } } // Process each file part while (true) { // If not currently reading a file, find the next boundary if (!readingFile) { auto bpos = buffer.find(boundary); if (bpos == std::string::npos) break; buffer.erase(0, bpos + boundary.size()); // After boundary, we expect headers auto headerEnd = buffer.find("\r\n\r\n"); if (headerEnd == std::string::npos) break; // Extract file headers std::string fileHead = buffer.substr(0, headerEnd); buffer.erase(0, headerEnd + 4); readingFile = true; // Filename std::string fname; if (auto fpos = fileHead.find("filename=\""); fpos != std::string::npos) { auto start = fpos + 10; auto end = fileHead.find("\"", start); fname = fileHead.substr(start, end - start); } outFile.open(destination_dir / fname, std::ios::binary); if (!outFile.is_open()) return filesReceived; // Content length contentLeft = 0; if (auto cpos = fileHead.find("Content-Length:"); cpos != std::string::npos) { auto start = fileHead.find_first_not_of(" \t", cpos + 15); auto end = fileHead.find("\r\n", start); contentLeft = std::stoull(fileHead.substr(start, end - start)); } } // Write file content if (readingFile) { const size_t canWrite = std::min<uint64_t>(buffer.size(), contentLeft); if (canWrite == 0) break; outFile.write(buffer.data(), canWrite); buffer.erase(0, canWrite); contentLeft -= canWrite; // If done, close file if (contentLeft == 0) { outFile.close(); filesReceived++; readingFile = false; } } else { break; } } } return filesReceived;}
As I've already mentioned, it works with small files (the checksum matches). It's really strange to me. Anyone has any idea? What am I doing wrong?