@@ -329,6 +329,134 @@ std::expected<void, Error> S3Client::DeleteBucket(const std::string& bucket, con
329329 return std::unexpected<Error>(deserializeError (XMLBody));
330330}
331331
332+ std::expected<HeadBucketResult, Error> S3Client::HeadBucket (const std::string& bucket, const HeadBucketInput& options) {
333+ std::string url = buildURL (bucket);
334+
335+ HttpRequest req = Client.head (url).header (" Host" , getHostHeader (bucket));
336+
337+ // opt headers
338+ if (options.ExpectedBucketOwner .has_value ())
339+ req.header (" x-amz-expected-bucket-owner" , std::move (options.ExpectedBucketOwner .value ()));
340+
341+ Signer.sign (req);
342+ HttpResponse res = req.execute ();
343+
344+ if (res.status () == 200 ) {
345+ return deserializeHeadBucketResult (res.headers ());
346+ }
347+
348+ // HEAD requests dont return error bodies, parse it from headers
349+ Error error;
350+ const auto & headers = res.headers ();
351+ if (headers.contains (" X-Minio-Error-Code" )) {
352+ error.Code = headers.at (" X-Minio-Error-Code" );
353+ if (headers.contains (" X-Minio-Error-Desc" )) {
354+ error.Message = headers.at (" X-Minio-Error-Desc" );
355+ }
356+ } else if (headers.contains (" x-amz-error-code" )) {
357+ error.Code = headers.at (" x-amz-error-code" );
358+ if (headers.contains (" x-amz-error-message" )) {
359+ error.Message = headers.at (" x-amz-error-message" );
360+ }
361+ } else {
362+ error.Code = " UnknownError" ;
363+ error.Message = std::format (" HTTP {}" , res.status ());
364+ }
365+
366+ return std::unexpected<Error>(error);
367+ }
368+
369+ std::expected<HeadObjectResult, Error> S3Client::HeadObject (const std::string& bucket, const std::string& key, const HeadObjectInput& options) {
370+ std::string url = buildURL (bucket) + std::format (" /{}" , key);
371+
372+ // Query params
373+ bool firstParam = true ;
374+ if (options.partNumber .has_value ()) {
375+ url += std::format (" {}part-number={}" , firstParam ? " ?" : " &" , options.partNumber .value ());
376+ firstParam = false ;
377+ }
378+ if (options.versionId .has_value ()) {
379+ url += std::format (" {}versionId={}" , firstParam ? " ?" : " &" , options.versionId .value ());
380+ firstParam = false ;
381+ }
382+ if (options.response_cache_control .has_value ()) {
383+ url += std::format (" {}response-cache-control={}" , firstParam ? " ?" : " &" , options.response_cache_control .value ());
384+ firstParam = false ;
385+ }
386+ if (options.response_content_disposition .has_value ()) {
387+ url += std::format (" {}response-content-disposition={}" , firstParam ? " ?" : " &" , options.response_content_disposition .value ());
388+ firstParam = false ;
389+ }
390+ if (options.response_content_encoding .has_value ()) {
391+ url += std::format (" {}response-content-encoding={}" , firstParam ? " ?" : " &" , options.response_content_encoding .value ());
392+ firstParam = false ;
393+ }
394+ if (options.response_content_language .has_value ()) {
395+ url += std::format (" {}response-content-language={}" , firstParam ? " ?" : " &" , options.response_content_language .value ());
396+ firstParam = false ;
397+ }
398+ if (options.response_content_type .has_value ()) {
399+ url += std::format (" {}response-content-type={}" , firstParam ? " ?" : " &" , options.response_content_type .value ());
400+ firstParam = false ;
401+ }
402+ if (options.response_expires .has_value ()) {
403+ url += std::format (" {}response-expires={}" , firstParam ? " ?" : " &" , options.response_expires .value ());
404+ firstParam = false ;
405+ }
406+
407+ HttpRequest req = Client.head (url).header (" Host" , getHostHeader (bucket));
408+
409+ // opt headers
410+ if (options.If_Match .has_value ())
411+ req.header (" If-Match" , options.If_Match .value ());
412+ if (options.If_Modified_Since .has_value ())
413+ req.header (" If-Modified-Since" , options.If_Modified_Since .value ());
414+ if (options.If_None_Match .has_value ())
415+ req.header (" If-None-Match" , options.If_None_Match .value ());
416+ if (options.If_Unmodified_Since .has_value ())
417+ req.header (" If-Unmodified-Since" , options.If_Unmodified_Since .value ());
418+ if (options.Range .has_value ())
419+ req.header (" Range" , options.Range .value ());
420+ if (options.CheckSumMode .has_value ())
421+ req.header (" x-amz-checksum-mode" , options.CheckSumMode .value ());
422+ if (options.ExpectedBucketOwner .has_value ())
423+ req.header (" x-amz-expected-bucket-owner" , options.ExpectedBucketOwner .value ());
424+ if (options.RequestPayer .has_value ())
425+ req.header (" x-amz-request-payer" , options.RequestPayer .value ());
426+ if (options.SideEncryptionCustomerAlgorithm .has_value ())
427+ req.header (" x-amz-server-side-encryption-customer-algorithm" , options.SideEncryptionCustomerAlgorithm .value ());
428+ if (options.SideEncryptionCustomerKey .has_value ())
429+ req.header (" x-amz-server-side-encryption-customer-key" , options.SideEncryptionCustomerKey .value ());
430+ if (options.SideEncryptionCustomerKeyMD5 .has_value ())
431+ req.header (" x-amz-server-side-encryption-customer-key-MD5" , options.SideEncryptionCustomerKeyMD5 .value ());
432+
433+ Signer.sign (req);
434+ HttpResponse res = req.execute ();
435+
436+ if (res.status () == 200 ) {
437+ return deserializeHeadObjectResult (res.headers ());
438+ }
439+
440+ // HEAD requests dont return error bodies, parse it from headers
441+ Error error;
442+ const auto & headers = res.headers ();
443+ if (headers.contains (" X-Minio-Error-Code" )) {
444+ error.Code = headers.at (" X-Minio-Error-Code" );
445+ if (headers.contains (" X-Minio-Error-Desc" )) {
446+ error.Message = headers.at (" X-Minio-Error-Desc" );
447+ }
448+ } else if (headers.contains (" x-amz-error-code" )) {
449+ error.Code = headers.at (" x-amz-error-code" );
450+ if (headers.contains (" x-amz-error-message" )) {
451+ error.Message = headers.at (" x-amz-error-message" );
452+ }
453+ } else {
454+ error.Code = " UnknownError" ;
455+ error.Message = std::format (" HTTP {}" , res.status ());
456+ }
457+ return std::unexpected<Error>(error);
458+ }
459+
332460Error S3Client::deserializeError (const std::vector<XMLNode>& nodes) {
333461 Error error;
334462
@@ -429,3 +557,107 @@ std::expected<CreateBucketResult, Error> S3Client::deserializeCreateBucketResult
429557 }
430558 return result;
431559}
560+
561+ std::expected<HeadBucketResult, Error> S3Client::deserializeHeadBucketResult (const std::map<std::string, std::string, LowerCaseCompare>& headers) {
562+ HeadBucketResult result;
563+ for (const auto & [header, value] : headers) {
564+ if (header == " x-amz-bucket-arn" )
565+ result.BucketARN = std::move (value);
566+ else if (header == " x-amz-bucket-location-type" )
567+ result.BucketLocationType = std::move (value);
568+ else if (header == " x-amz-bucket-location-name" )
569+ result.BucketLocationName = std::move (value);
570+ else if (header == " x-amz-bucket-region" )
571+ result.BucketRegion = std::move (value);
572+ else if (header == " x-amz-access-point-alias" )
573+ result.AccessPointAlias = std::move (value);
574+ else {
575+ continue ;
576+ }
577+ }
578+ return result;
579+ }
580+
581+ std::expected<HeadObjectResult, Error> S3Client::deserializeHeadObjectResult (const std::map<std::string, std::string, LowerCaseCompare>& headers) {
582+ HeadObjectResult result;
583+ for (const auto & [header, value] : headers) {
584+ if (header == " x-amz-delete-marker" )
585+ result.DeleteMarker = Parser.parseBool (value);
586+ else if (header == " accept-ranges" )
587+ result.AcceptRanges = std::move (value);
588+ else if (header == " x-amz-expiration" )
589+ result.Expiration = std::move (value);
590+ else if (header == " x-amz-restore" )
591+ result.Restore = std::move (value);
592+ else if (header == " x-amz-archive-status" )
593+ result.ArchiveStatus = std::move (value);
594+ else if (header == " Last-Modified" )
595+ result.LastModified = std::move (value);
596+ else if (header == " Content-Length" )
597+ result.ContentLength = Parser.parseNumber <int64_t >(value);
598+ else if (header == " x-amz-checksum-crc32" )
599+ result.ChecksumCRC32 = std::move (value);
600+ else if (header == " x-amz-checksum-crc32c" )
601+ result.ChecksumCRC32C = std::move (value);
602+ else if (header == " x-amz-checksum-crc64nvme" )
603+ result.ChecksumCRC64NVME = std::move (value);
604+ else if (header == " x-amz-checksum-sha1" )
605+ result.ChecksumSHA1 = std::move (value);
606+ else if (header == " x-amz-checksum-sha256" )
607+ result.ChecksumSHA256 = std::move (value);
608+ else if (header == " x-amz-checksum-type" )
609+ result.ChecksumType = std::move (value);
610+ else if (header == " ETag" )
611+ result.ETag = std::move (value);
612+ else if (header == " x-amz-missing-meta" )
613+ result.MissingMeta = Parser.parseNumber <int >(value);
614+ else if (header == " x-amz-version-id" )
615+ result.VersionId = std::move (value);
616+ else if (header == " Cache-Control" )
617+ result.CacheControl = std::move (value);
618+ else if (header == " Content-Disposition" )
619+ result.ContentDisposition = std::move (value);
620+ else if (header == " Content-Encoding" )
621+ result.ContentEncoding = std::move (value);
622+ else if (header == " Content-Language" )
623+ result.ContentLanguage = std::move (value);
624+ else if (header == " Content-Type" )
625+ result.ContentType = std::move (value);
626+ else if (header == " Content-Range" )
627+ result.ContentRange = std::move (value);
628+ else if (header == " Expires" )
629+ result.Expires = std::move (value);
630+ else if (header == " x-amz-website-redirect-location" )
631+ result.WebsiteRedirectLocation = std::move (value);
632+ else if (header == " x-amz-server-side-encryption" )
633+ result.ServerSideEncryption = std::move (value);
634+ else if (header == " x-amz-server-side-encryption-customer-algorithm" )
635+ result.SSECustomerAlgorithm = std::move (value);
636+ else if (header == " x-amz-server-side-encryption-customer-key-MD5" )
637+ result.SSECustomerKeyMD5 = std::move (value);
638+ else if (header == " x-amz-server-side-encryption-aws-kms-key-id" )
639+ result.SSEKMSKeyId = std::move (value);
640+ else if (header == " x-amz-server-side-encryption-bucket-key-enabled" )
641+ result.BucketKeyEnabled = Parser.parseBool (value);
642+ else if (header == " x-amz-storage-class" )
643+ result.StorageClass = std::move (value);
644+ else if (header == " x-amz-request-charged" )
645+ result.RequestCharged = std::move (value);
646+ else if (header == " x-amz-replication-status" )
647+ result.ReplicationStatus = std::move (value);
648+ else if (header == " x-amz-mp-parts-count" )
649+ result.PartsCount = Parser.parseNumber <int >(value);
650+ else if (header == " x-amz-tagging-count" )
651+ result.TagCount = Parser.parseNumber <int >(value);
652+ else if (header == " x-amz-object-lock-mode" )
653+ result.ObjectLockMode = std::move (value);
654+ else if (header == " x-amz-object-lock-retain-until-date" )
655+ result.ObjectLockRetainUntilDate = std::move (value);
656+ else if (header == " x-amz-object-lock-legal-hold" )
657+ result.ObjectLockLegalHoldStatus = std::move (value);
658+ else {
659+ continue ;
660+ }
661+ }
662+ return result;
663+ }
0 commit comments