public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox 1/2] s3-client: fix non-optional date header for api response parsing
Date: Fri,  8 Aug 2025 13:41:16 +0200	[thread overview]
Message-ID: <20250808114117.393536-2-c.ebner@proxmox.com> (raw)
In-Reply-To: <20250808114117.393536-1-c.ebner@proxmox.com>

Fixes an implementation oversight for the http date header response
parsing.

This header is most commonly present for all responses, but it is
documented as optional in the AWS reference documentation [0]. While
introduced during development to possibly detect time shifts between
the local s3 client and the api for e.g. last modified time checks,
it is currently not actively used by any codepath, and can therefore
be adapted without further modifications.

[0] https://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

Fixes: https://forum.proxmox.com/threads/169395/
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
 proxmox-s3-client/src/response_reader.rs | 33 +++++++++++++++++-------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/proxmox-s3-client/src/response_reader.rs b/proxmox-s3-client/src/response_reader.rs
index bfd71205..f200e15a 100644
--- a/proxmox-s3-client/src/response_reader.rs
+++ b/proxmox-s3-client/src/response_reader.rs
@@ -9,8 +9,7 @@ use hyper::http::StatusCode;
 use hyper::{HeaderMap, Response};
 use serde::Deserialize;
 
-use crate::S3ObjectKey;
-use crate::{HttpDate, LastModifiedTimestamp};
+use crate::{HttpDate, LastModifiedTimestamp, S3ObjectKey};
 
 /// Response reader to check S3 api response status codes and parse response body, if any.
 pub(crate) struct ResponseReader {
@@ -21,7 +20,7 @@ pub(crate) struct ResponseReader {
 /// Response contents of list objects v2 api calls.
 pub struct ListObjectsV2Response {
     /// Parsed http date header from response.
-    pub date: HttpDate,
+    pub date: Option<HttpDate>,
     /// Bucket name.
     pub name: String,
     /// Requested key prefix.
@@ -64,7 +63,7 @@ struct ListObjectsV2ResponseBody {
 }
 
 impl ListObjectsV2ResponseBody {
-    fn with_date(self, date: HttpDate) -> ListObjectsV2Response {
+    fn with_optional_date(self, date: Option<HttpDate>) -> ListObjectsV2Response {
         ListObjectsV2Response {
             date,
             name: self.name,
@@ -105,7 +104,7 @@ pub struct HeadObjectResponse {
     /// Content type header.
     pub content_type: String,
     /// Http date header.
-    pub date: HttpDate,
+    pub date: Option<HttpDate>,
     /// Entity tag header.
     pub e_tag: String,
     /// Last modified http header.
@@ -120,7 +119,7 @@ pub struct GetObjectResponse {
     /// Content type header.
     pub content_type: String,
     /// Http date header.
-    pub date: HttpDate,
+    pub date: Option<HttpDate>,
     /// Entity tag header.
     pub e_tag: String,
     /// Last modified http header.
@@ -272,12 +271,12 @@ impl ResponseReader {
 
         let body = String::from_utf8(body.to_vec())?;
 
-        let date: HttpDate = Self::parse_header(header::DATE, &parts.headers)?;
+        let date = Self::parse_optional_date_header(&parts.headers)?;
 
         let response: ListObjectsV2ResponseBody =
             serde_xml_rs::from_str(&body).context("failed to parse response body")?;
 
-        Ok(response.with_date(date))
+        Ok(response.with_optional_date(date))
     }
 
     /// Read and parse the head object response.
@@ -303,7 +302,7 @@ impl ResponseReader {
         let content_length: u64 = Self::parse_header(header::CONTENT_LENGTH, &parts.headers)?;
         let content_type = Self::parse_header(header::CONTENT_TYPE, &parts.headers)?;
         let e_tag = Self::parse_header(header::ETAG, &parts.headers)?;
-        let date = Self::parse_header(header::DATE, &parts.headers)?;
+        let date = Self::parse_optional_date_header(&parts.headers)?;
         let last_modified = Self::parse_header(header::LAST_MODIFIED, &parts.headers)?;
 
         Ok(Some(HeadObjectResponse {
@@ -335,7 +334,7 @@ impl ResponseReader {
         let content_length: u64 = Self::parse_header(header::CONTENT_LENGTH, &parts.headers)?;
         let content_type = Self::parse_header(header::CONTENT_TYPE, &parts.headers)?;
         let e_tag = Self::parse_header(header::ETAG, &parts.headers)?;
-        let date = Self::parse_header(header::DATE, &parts.headers)?;
+        let date = Self::parse_optional_date_header(&parts.headers)?;
         let last_modified = Self::parse_header(header::LAST_MODIFIED, &parts.headers)?;
 
         Ok(Some(GetObjectResponse {
@@ -509,6 +508,20 @@ impl ResponseReader {
             .with_context(|| format!("failed to parse header '{name}'"))?;
         Ok(value)
     }
+
+    fn parse_optional_date_header(headers: &HeaderMap) -> Result<Option<HttpDate>, Error> {
+        let header_value = match headers.get(header::DATE) {
+            Some(value) => value,
+            None => return Ok(None),
+        };
+        let header_str = header_value
+            .to_str()
+            .with_context(|| format!("non UTF-8 header '{}'", header::DATE))?;
+        let date: HttpDate = header_str
+            .parse()
+            .with_context(|| format!("failed to parse header '{}'", header::DATE))?;
+        Ok(Some(date))
+    }
 }
 
 #[test]
-- 
2.47.2



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel


  reply	other threads:[~2025-08-08 11:39 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-08 11:41 [pbs-devel] [PATCH proxmox 0/2] fix non-optional date header for S3 " Christian Ebner
2025-08-08 11:41 ` Christian Ebner [this message]
2025-08-08 11:41 ` [pbs-devel] [PATCH proxmox 2/2] s3-client: add regression tests for http date header parsing Christian Ebner
2025-08-11 12:30 ` [pbs-devel] applied-series: [PATCH proxmox 0/2] fix non-optional date header for S3 api response parsing Fabian Grünbichler

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250808114117.393536-2-c.ebner@proxmox.com \
    --to=c.ebner@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal