feat(webdav): move etag parser to own function, create required migration

This commit is contained in:
perf3ct 2025-06-23 19:39:39 +00:00
parent c747d0abc8
commit de45300c7a
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
3 changed files with 42 additions and 10 deletions

View File

@ -0,0 +1,11 @@
-- Normalize existing ETags in webdav_files table to match new normalization format
-- This migration ensures that existing ETag values are normalized to prevent
-- unnecessary re-downloads of unchanged files after the ETag normalization fix
-- Update ETags to remove quotes and W/ prefixes
UPDATE webdav_files
SET etag = TRIM(BOTH '"' FROM TRIM(LEADING 'W/' FROM etag))
WHERE etag LIKE '"%"' OR etag LIKE 'W/%';
-- Add a comment to document this normalization
COMMENT ON COLUMN webdav_files.etag IS 'Normalized ETag without quotes or W/ prefix (since migration 20250623000001)';

View File

@ -83,13 +83,7 @@ pub fn parse_propfind_response(xml_text: &str) -> Result<Vec<FileInfo>> {
resp.content_type = Some(text.trim().to_string()); resp.content_type = Some(text.trim().to_string());
} }
"getetag" => { "getetag" => {
// Normalize ETag by removing quotes and weak ETag prefix resp.etag = Some(normalize_etag(&text));
let etag = text.trim();
let normalized = etag
.trim_start_matches("W/")
.trim_matches('"')
.to_string();
resp.etag = Some(normalized);
} }
"status" if in_propstat => { "status" if in_propstat => {
// Check if status is 200 OK // Check if status is 200 OK
@ -206,6 +200,20 @@ fn parse_http_date(date_str: &str) -> Option<DateTime<Utc>> {
}) })
} }
/// Normalize ETag by removing quotes and weak ETag prefix
/// This ensures consistent ETag comparison across different WebDAV servers
///
/// Examples:
/// - `"abc123"` → `abc123`
/// - `W/"abc123"` → `abc123`
/// - `abc123` → `abc123`
fn normalize_etag(etag: &str) -> String {
etag.trim()
.trim_start_matches("W/")
.trim_matches('"')
.to_string()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -344,4 +352,17 @@ mod tests {
let files = parse_propfind_response(xml).unwrap(); let files = parse_propfind_response(xml).unwrap();
assert_eq!(files.len(), 0); assert_eq!(files.len(), 0);
} }
#[test]
fn test_normalize_etag() {
// Test various ETag formats that WebDAV servers might return
assert_eq!(normalize_etag("abc123"), "abc123");
assert_eq!(normalize_etag("\"abc123\""), "abc123");
assert_eq!(normalize_etag("W/\"abc123\""), "abc123");
assert_eq!(normalize_etag(" \"abc123\" "), "abc123");
assert_eq!(normalize_etag("W/\"abc-123-def\""), "abc-123-def");
assert_eq!(normalize_etag(""), "");
assert_eq!(normalize_etag("\"\""), "");
assert_eq!(normalize_etag("W/\"\""), "");
}
} }

View File

@ -201,21 +201,21 @@ fn test_webdav_response_parsing_comprehensive() {
let pdf_file = files.iter().find(|f| f.name == "report.pdf").unwrap(); let pdf_file = files.iter().find(|f| f.name == "report.pdf").unwrap();
assert_eq!(pdf_file.size, 2048000); assert_eq!(pdf_file.size, 2048000);
assert_eq!(pdf_file.mime_type, "application/pdf"); assert_eq!(pdf_file.mime_type, "application/pdf");
assert_eq!(pdf_file.etag, "\"pdf123\""); assert_eq!(pdf_file.etag, "pdf123"); // ETag should be normalized (quotes removed)
assert!(!pdf_file.is_directory); assert!(!pdf_file.is_directory);
// Verify second file (photo.png) // Verify second file (photo.png)
let png_file = files.iter().find(|f| f.name == "photo.png").unwrap(); let png_file = files.iter().find(|f| f.name == "photo.png").unwrap();
assert_eq!(png_file.size, 768000); assert_eq!(png_file.size, 768000);
assert_eq!(png_file.mime_type, "image/png"); assert_eq!(png_file.mime_type, "image/png");
assert_eq!(png_file.etag, "\"png456\""); assert_eq!(png_file.etag, "png456"); // ETag should be normalized (quotes removed)
assert!(!png_file.is_directory); assert!(!png_file.is_directory);
// Verify third file (unsupported.docx) // Verify third file (unsupported.docx)
let docx_file = files.iter().find(|f| f.name == "unsupported.docx").unwrap(); let docx_file = files.iter().find(|f| f.name == "unsupported.docx").unwrap();
assert_eq!(docx_file.size, 102400); assert_eq!(docx_file.size, 102400);
assert_eq!(docx_file.mime_type, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); assert_eq!(docx_file.mime_type, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
assert_eq!(docx_file.etag, "\"docx789\""); assert_eq!(docx_file.etag, "docx789"); // ETag should be normalized (quotes removed)
assert!(!docx_file.is_directory); assert!(!docx_file.is_directory);
} }