mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 04:40:06 +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/qt/manager/download_manager.hpp> | ||||||
|  | #include <scwx/util/digest.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <fstream> | #include <fstream> | ||||||
|  | @ -128,15 +129,9 @@ void DownloadManager::Download( | ||||||
|          bool ofsGood = ofs.good(); |          bool ofsGood = ofs.good(); | ||||||
|          ofs.close(); |          ofs.close(); | ||||||
| 
 | 
 | ||||||
|          // Handle response
 |          // Handle error response
 | ||||||
|          if (response.error.code == cpr::ErrorCode::OK && |          if (response.error.code != cpr::ErrorCode::OK || | ||||||
|              !request->IsCanceled() && ofsGood) |              request->IsCanceled() || !ofsGood) | ||||||
|          { |  | ||||||
|             logger_->info("Download complete: \"{}\"", request->url()); |  | ||||||
|             Q_EMIT request->RequestComplete( |  | ||||||
|                request::DownloadRequest::CompleteReason::OK); |  | ||||||
|          } |  | ||||||
|          else |  | ||||||
|          { |          { | ||||||
|             request::DownloadRequest::CompleteReason reason = |             request::DownloadRequest::CompleteReason reason = | ||||||
|                request::DownloadRequest::CompleteReason::IOError; |                request::DownloadRequest::CompleteReason::IOError; | ||||||
|  | @ -173,7 +168,68 @@ void DownloadManager::Download( | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             Q_EMIT request->RequestComplete(reason); |             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, |       OK, | ||||||
|       Canceled, |       Canceled, | ||||||
|       IOError, |       IOError, | ||||||
|       RemoteError |       RemoteError, | ||||||
|  |       DigestError | ||||||
|    }; |    }; | ||||||
| 
 | 
 | ||||||
|    explicit DownloadRequest(const std::string&           url, |    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.cpp | ||||||
|                  source/scwx/provider/nexrad_data_provider_factory.cpp |                  source/scwx/provider/nexrad_data_provider_factory.cpp | ||||||
|                  source/scwx/provider/warnings_provider.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/environment.hpp | ||||||
|              include/scwx/util/float.hpp |              include/scwx/util/float.hpp | ||||||
|              include/scwx/util/hash.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/threads.hpp | ||||||
|              include/scwx/util/time.hpp |              include/scwx/util/time.hpp | ||||||
|              include/scwx/util/vectorbuf.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/float.cpp | ||||||
|              source/scwx/util/hash.cpp |              source/scwx/util/hash.cpp | ||||||
|              source/scwx/util/logger.cpp |              source/scwx/util/logger.cpp | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat