mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 04:30:05 +00:00 
			
		
		
		
	Verify downloaded file against content-md5 response header
This commit is contained in:
		
							parent
							
								
									2f397106f9
								
							
						
					
					
						commit
						3ab05a1654
					
				
					 5 changed files with 171 additions and 12 deletions
				
			
		|  | @ -1,4 +1,5 @@ | |||
| #include <scwx/qt/manager/download_manager.hpp> | ||||
| #include <scwx/util/digest.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <fstream> | ||||
|  | @ -128,15 +129,9 @@ void DownloadManager::Download( | |||
|          bool ofsGood = ofs.good(); | ||||
|          ofs.close(); | ||||
| 
 | ||||
|          // Handle response
 | ||||
|          if (response.error.code == cpr::ErrorCode::OK && | ||||
|              !request->IsCanceled() && ofsGood) | ||||
|          { | ||||
|             logger_->info("Download complete: \"{}\"", request->url()); | ||||
|             Q_EMIT request->RequestComplete( | ||||
|                request::DownloadRequest::CompleteReason::OK); | ||||
|          } | ||||
|          else | ||||
|          // Handle error response
 | ||||
|          if (response.error.code != cpr::ErrorCode::OK || | ||||
|              request->IsCanceled() || !ofsGood) | ||||
|          { | ||||
|             request::DownloadRequest::CompleteReason reason = | ||||
|                request::DownloadRequest::CompleteReason::IOError; | ||||
|  | @ -173,7 +168,68 @@ void DownloadManager::Download( | |||
|             } | ||||
| 
 | ||||
|             Q_EMIT request->RequestComplete(reason); | ||||
| 
 | ||||
|             return; | ||||
|          } | ||||
| 
 | ||||
|          // Handle response
 | ||||
|          const auto contentMd5 = response.header.find("content-md5"); | ||||
|          if (contentMd5 != response.header.cend() && | ||||
|              !contentMd5->second.empty()) | ||||
|          { | ||||
|             // Open file for reading
 | ||||
|             std::ifstream is {destinationPath, | ||||
|                               std::ios_base::in | std::ios_base::binary}; | ||||
|             if (!is.is_open() || !is.good()) | ||||
|             { | ||||
|                logger_->error( | ||||
|                   "Unable to open destination file for reading: \"{}\"", | ||||
|                   destinationPath.string()); | ||||
| 
 | ||||
|                Q_EMIT request->RequestComplete( | ||||
|                   request::DownloadRequest::CompleteReason::IOError); | ||||
| 
 | ||||
|                return; | ||||
|             } | ||||
| 
 | ||||
|             // Compute MD5
 | ||||
|             std::vector<std::uint8_t> digest {}; | ||||
|             if (!util::ComputeDigest(EVP_md5(), is, digest)) | ||||
|             { | ||||
|                logger_->error("Failed to compute MD5: \"{}\"", | ||||
|                               destinationPath.string()); | ||||
| 
 | ||||
|                Q_EMIT request->RequestComplete( | ||||
|                   request::DownloadRequest::CompleteReason::IOError); | ||||
| 
 | ||||
|                return; | ||||
|             } | ||||
| 
 | ||||
|             // Compare calculated MD5 with digest in response header
 | ||||
|             QByteArray expectedDigestArray = | ||||
|                QByteArray::fromBase64(contentMd5->second.c_str()); | ||||
|             std::vector<std::uint8_t> expectedDigest( | ||||
|                expectedDigestArray.cbegin(), expectedDigestArray.cend()); | ||||
| 
 | ||||
|             if (digest != expectedDigest) | ||||
|             { | ||||
|                QByteArray calculatedDigest( | ||||
|                   reinterpret_cast<char*>(digest.data()), digest.size()); | ||||
| 
 | ||||
|                logger_->error("Digest mismatch: {} != {}", | ||||
|                               calculatedDigest.toBase64().toStdString(), | ||||
|                               contentMd5->second); | ||||
| 
 | ||||
|                Q_EMIT request->RequestComplete( | ||||
|                   request::DownloadRequest::CompleteReason::DigestError); | ||||
| 
 | ||||
|                return; | ||||
|             } | ||||
|          } | ||||
| 
 | ||||
|          logger_->info("Download complete: \"{}\"", request->url()); | ||||
|          Q_EMIT request->RequestComplete( | ||||
|             request::DownloadRequest::CompleteReason::OK); | ||||
|       }); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,7 +22,8 @@ public: | |||
|       OK, | ||||
|       Canceled, | ||||
|       IOError, | ||||
|       RemoteError | ||||
|       RemoteError, | ||||
|       DigestError | ||||
|    }; | ||||
| 
 | ||||
|    explicit DownloadRequest(const std::string&           url, | ||||
|  |  | |||
							
								
								
									
										18
									
								
								wxdata/include/scwx/util/digest.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								wxdata/include/scwx/util/digest.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <istream> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <openssl/evp.h> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace util | ||||
| { | ||||
| 
 | ||||
| bool ComputeDigest(const EVP_MD*              mdtype, | ||||
|                    std::istream&              is, | ||||
|                    std::vector<std::uint8_t>& digest); | ||||
| 
 | ||||
| } // namespace util
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										82
									
								
								wxdata/source/scwx/util/digest.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								wxdata/source/scwx/util/digest.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,82 @@ | |||
| #include <scwx/util/digest.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace util | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = "scwx::util::digest"; | ||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||
| 
 | ||||
| bool ComputeDigest(const EVP_MD*              mdtype, | ||||
|                    std::istream&              is, | ||||
|                    std::vector<std::uint8_t>& digest) | ||||
| { | ||||
|    int         mdsize; | ||||
|    EVP_MD_CTX* mdctx = nullptr; | ||||
| 
 | ||||
|    digest.clear(); | ||||
| 
 | ||||
|    if ((mdsize = EVP_MD_get_size(mdtype)) < 1) | ||||
|    { | ||||
|       logger_->error("Invalid digest"); | ||||
|       return false; | ||||
|    } | ||||
| 
 | ||||
|    if ((mdctx = EVP_MD_CTX_new()) == nullptr) | ||||
|    { | ||||
|       logger_->error("Error allocating a digest context"); | ||||
|       return false; | ||||
|    } | ||||
| 
 | ||||
|    if (!EVP_DigestInit_ex(mdctx, mdtype, nullptr)) | ||||
|    { | ||||
|       logger_->error("Message digest initialization failed"); | ||||
|       EVP_MD_CTX_free(mdctx); | ||||
|       return false; | ||||
|    } | ||||
| 
 | ||||
|    is.seekg(0, std::ios_base::end); | ||||
|    const std::size_t streamSize = is.tellg(); | ||||
|    is.seekg(0, std::ios_base::beg); | ||||
| 
 | ||||
|    std::size_t bytesRead = 0; | ||||
|    std::size_t chunkSize = 4096; | ||||
|    std::string fileData; | ||||
|    fileData.resize(chunkSize); | ||||
| 
 | ||||
|    while (bytesRead < streamSize) | ||||
|    { | ||||
|       const std::size_t bytesRemaining = streamSize - bytesRead; | ||||
|       const std::size_t readSize       = std::min(chunkSize, bytesRemaining); | ||||
| 
 | ||||
|       is.read(fileData.data(), readSize); | ||||
| 
 | ||||
|       if (!is.good() || !EVP_DigestUpdate(mdctx, fileData.data(), readSize)) | ||||
|       { | ||||
|          logger_->error("Message digest update failed"); | ||||
|          EVP_MD_CTX_free(mdctx); | ||||
|          return false; | ||||
|       } | ||||
| 
 | ||||
|       bytesRead += readSize; | ||||
|    } | ||||
| 
 | ||||
|    digest.resize(mdsize); | ||||
| 
 | ||||
|    if (!EVP_DigestFinal_ex(mdctx, digest.data(), nullptr)) | ||||
|    { | ||||
|       logger_->error("Message digest finalization failed"); | ||||
|       EVP_MD_CTX_free(mdctx); | ||||
|       digest.clear(); | ||||
|       return false; | ||||
|    } | ||||
| 
 | ||||
|    EVP_MD_CTX_free(mdctx); | ||||
| 
 | ||||
|    return true; | ||||
| } | ||||
| 
 | ||||
| } // namespace util
 | ||||
| } // namespace scwx
 | ||||
|  | @ -67,7 +67,8 @@ set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp | |||
|                  source/scwx/provider/nexrad_data_provider.cpp | ||||
|                  source/scwx/provider/nexrad_data_provider_factory.cpp | ||||
|                  source/scwx/provider/warnings_provider.cpp) | ||||
| set(HDR_UTIL include/scwx/util/enum.hpp | ||||
| set(HDR_UTIL include/scwx/util/digest.hpp | ||||
|              include/scwx/util/enum.hpp | ||||
|              include/scwx/util/environment.hpp | ||||
|              include/scwx/util/float.hpp | ||||
|              include/scwx/util/hash.hpp | ||||
|  | @ -80,7 +81,8 @@ set(HDR_UTIL include/scwx/util/enum.hpp | |||
|              include/scwx/util/threads.hpp | ||||
|              include/scwx/util/time.hpp | ||||
|              include/scwx/util/vectorbuf.hpp) | ||||
| set(SRC_UTIL source/scwx/util/environment.cpp | ||||
| set(SRC_UTIL source/scwx/util/digest.cpp | ||||
|              source/scwx/util/environment.cpp | ||||
|              source/scwx/util/float.cpp | ||||
|              source/scwx/util/hash.cpp | ||||
|              source/scwx/util/logger.cpp | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat