Alan Yeung 4 zile în urmă
părinte
comite
04df522654
5 a modificat fișierele cu 142 adăugiri și 42 ștergeri
  1. 2 0
      aws/.gitignore
  2. BIN
      aws/data/bolt.db
  3. 43 17
      aws/internal/handler/public_view.go
  4. 56 14
      aws/internal/handler/s3.go
  5. 41 11
      aws/pkg/sigv4/sigv4.go

+ 2 - 0
aws/.gitignore

@@ -33,3 +33,5 @@ Thumbs.db
 # Environment
 .env
 .env.local
+data/
+upload/

BIN
aws/data/bolt.db


+ 43 - 17
aws/internal/handler/public_view.go

@@ -28,20 +28,34 @@ func NewPublicViewHandler(storage *storage.UserAwareStorage, db kvdb.KVDB) *Publ
 }
 
 // Handle processes public view requests
-// URL format: /{bucketName}/{key...}
+// Supports both:
+// - Virtual-hosted-style: bucketname.s3.domain.com/{key}
+// - Path-style: s3.domain.com/{bucketName}/{key}
 func (h *PublicViewHandler) Handle(w http.ResponseWriter, r *http.Request) {
-	// Parse path: /{bucketName}/{key...}
-	path := strings.TrimPrefix(r.URL.Path, "/")
-	if path == "" {
-		http.Error(w, "Invalid path format. Expected: /{bucketName}/{key}", http.StatusBadRequest)
-		return
-	}
+	var bucketName, key string
+
+	// Determine if this is virtual-hosted-style or path-style
+	if isVirtualHostedStyle(r.Host) {
+		// Virtual-hosted-style: bucket from Host header, key from path
+		bucketName = extractBucketFromHost(r.Host)
+		key = strings.TrimPrefix(r.URL.Path, "/")
+
+		log.Printf("Public view (virtual-hosted): bucket=%s, key=%s", bucketName, key)
+	} else {
+		// Path-style: bucket and key from path
+		path := strings.TrimPrefix(r.URL.Path, "/")
+		if path == "" {
+			http.Error(w, "Invalid path format. Expected: /{bucketName}/{key}", http.StatusBadRequest)
+			return
+		}
 
-	parts := strings.SplitN(path, "/", 2)
-	bucketName := parts[0]
-	key := ""
-	if len(parts) > 1 {
-		key = parts[1]
+		parts := strings.SplitN(path, "/", 2)
+		bucketName = parts[0]
+		if len(parts) > 1 {
+			key = parts[1]
+		}
+
+		log.Printf("Public view (path-style): bucket=%s, key=%s", bucketName, key)
 	}
 
 	// Resolve bucket name to accountID and bucketID
@@ -85,7 +99,7 @@ func (h *PublicViewHandler) handleBucketListing(w http.ResponseWriter, r *http.R
 	}
 
 	// Generate HTML page
-	htmlContent := h.generateBucketListingHTML(config.BucketName, objects)
+	htmlContent := h.generateBucketListingHTML(config.BucketName, objects, r)
 
 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 	w.WriteHeader(http.StatusOK)
@@ -126,9 +140,19 @@ func (h *PublicViewHandler) handleFileDownload(w http.ResponseWriter, r *http.Re
 	http.ServeContent(w, r, filepath.Base(key), info.LastModified, file.(io.ReadSeeker))
 }
 
-func (h *PublicViewHandler) generateBucketListingHTML(bucketName string, objects []storage.ObjectInfo) string {
+func (h *PublicViewHandler) generateBucketListingHTML(bucketName string, objects []storage.ObjectInfo, r *http.Request) string {
 	var sb strings.Builder
 
+	// Determine URL prefix based on request style
+	var urlPrefix string
+	if isVirtualHostedStyle(r.Host) {
+		// Virtual-hosted-style: just use the path
+		urlPrefix = ""
+	} else {
+		// Path-style: include bucket name in URL
+		urlPrefix = "/" + bucketName
+	}
+
 	sb.WriteString(`<!DOCTYPE html>
 <html lang="en">
 <head>
@@ -299,16 +323,18 @@ func (h *PublicViewHandler) generateBucketListingHTML(bucketName string, objects
 			ext := strings.ToLower(filepath.Ext(obj.Key))
 			icon := getFileIcon(ext)
 
+			// Build the correct URL based on style
+			fileURL := urlPrefix + "/" + obj.Key
+
 			sb.WriteString(fmt.Sprintf(`
-            <a href="/%s/%s" class="file-item">
+            <a href="%s" class="file-item">
                 <div class="file-icon">%s</div>
                 <div class="file-info">
                     <div class="file-name">%s</div>
                     <div class="file-meta">%s • %s</div>
                 </div>
             </a>`,
-				bucketName,
-				obj.Key,
+				fileURL,
 				icon,
 				html.EscapeString(obj.Key),
 				formatBytes(obj.Size),

+ 56 - 14
aws/internal/handler/s3.go

@@ -28,25 +28,67 @@ func NewS3Handler(service *service.S3Service) *S3Handler {
 	}
 }
 
+// Handle routes S3 requests to appropriate handlers
+// Helper functions - add these at package level (before S3Handler struct)
+func isVirtualHostedStyle(host string) bool {
+	// Virtual-hosted-style: bucketname.s3.domain.com
+	// Path-style: s3.domain.com
+	parts := strings.Split(host, ".")
+	// If host starts with a bucket name (has more than 2 parts before domain)
+	// and contains "s3", it's virtual-hosted-style
+	return len(parts) > 2 && strings.Contains(host, "s3")
+}
+
+func extractBucketFromHost(host string) string {
+	// Extract bucket name from virtual-hosted-style host
+	// photosbucket.s3.alanyeung.co -> photosbucket
+	// Remove port if present
+	if idx := strings.Index(host, ":"); idx != -1 {
+		host = host[:idx]
+	}
+	parts := strings.Split(host, ".")
+	if len(parts) > 0 {
+		return parts[0]
+	}
+	return ""
+}
+
 // Handle routes S3 requests to appropriate handlers
 func (h *S3Handler) Handle(w http.ResponseWriter, r *http.Request) {
-	path := strings.TrimPrefix(r.URL.Path, "/")
-	parts := strings.SplitN(path, "/", 2)
+	log.Printf("S3 Request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
+	log.Printf("Host header: %s", r.Host)
+	log.Printf("RequestURI: %s", r.RequestURI)
+
+	var bucketName, objectKey string
+
+	// Determine if this is virtual-hosted-style or path-style
+	if isVirtualHostedStyle(r.Host) {
+		// Virtual-hosted-style: bucket from Host header, object from path
+		bucketName = extractBucketFromHost(r.Host)
+		objectKey = strings.TrimPrefix(r.URL.Path, "/")
+
+		log.Printf("Virtual-hosted-style request: bucket=%s, key=%s", bucketName, objectKey)
+	} else {
+		// Path-style: bucket and object from path
+		path := strings.TrimPrefix(r.URL.Path, "/")
+		parts := strings.SplitN(path, "/", 2)
+
+		// Root path - list buckets
+		if path == "" {
+			if r.Method == "GET" {
+				h.handleListBuckets(w, r)
+			} else {
+				h.writeError(w, "MethodNotAllowed", "Method not allowed", "", http.StatusMethodNotAllowed)
+			}
+			return
+		}
 
-	// Root path - list buckets
-	if path == "" {
-		if r.Method == "GET" {
-			h.handleListBuckets(w, r)
-		} else {
-			h.writeError(w, "MethodNotAllowed", "Method not allowed", "", http.StatusMethodNotAllowed)
+		bucketName = parts[0]
+		if len(parts) > 1 {
+			objectKey = parts[1]
 		}
-		return
-	}
 
-	bucketName := parts[0]
-	objectKey := ""
-	if len(parts) > 1 {
-		objectKey = parts[1]
+		log.Printf("Path-style request: bucket=%s, key=%s", bucketName, objectKey)
 	}
 
 	// Bucket operations (no object key)

+ 41 - 11
aws/pkg/sigv4/sigv4.go

@@ -210,40 +210,68 @@ func calculateSignature(r *http.Request, body []byte, creds AWSCredentials, sigV
 	return finalSig, nil
 }
 
+// Add this helper function to detect virtual-hosted-style requests
+func isVirtualHostedStyle(host string) bool {
+	// Virtual-hosted-style: bucketname.s3.domain.com
+	// Path-style: s3.domain.com
+	parts := strings.Split(host, ".")
+	// If host starts with a bucket name (has more than 2 parts before domain)
+	// and contains "s3", it's virtual-hosted-style
+	return len(parts) > 2 && strings.Contains(host, "s3")
+}
+
+// Add this helper to extract bucket from host
+func extractBucketFromHost(host string) string {
+	// Extract bucket name from virtual-hosted-style host
+	// photosbucket.s3.alanyeung.co -> photosbucket
+	parts := strings.Split(host, ".")
+	if len(parts) > 0 {
+		return parts[0]
+	}
+	return ""
+}
+
 func buildCanonicalRequest(r *http.Request, body []byte, signedHeaders []string) string {
 	// Method
 	method := r.Method
 
-	// Canonical URI - Use RequestURI which preserves URL encoding
-	// Split RequestURI to get just the path (before the query string)
+	// Canonical URI - Handle both virtual-hosted and path-style
 	canonicalURI := r.RequestURI
 	if idx := strings.Index(canonicalURI, "?"); idx != -1 {
 		canonicalURI = canonicalURI[:idx]
 	}
+
+	// NEW: Check if this is a virtual-hosted-style request
+	if isVirtualHostedStyle(r.Host) {
+		// For virtual-hosted-style, strip the bucket name from the URI
+		bucketName := extractBucketFromHost(r.Host)
+		if bucketName != "" {
+			// Remove /bucketname/ prefix from the URI
+			prefix := "/" + bucketName + "/"
+			if strings.HasPrefix(canonicalURI, prefix) {
+				canonicalURI = "/" + strings.TrimPrefix(canonicalURI, prefix)
+			} else if canonicalURI == "/"+bucketName {
+				canonicalURI = "/"
+			}
+		}
+	}
+
 	if canonicalURI == "" {
 		canonicalURI = "/"
 	}
 
-	// Canonical query string - MUST BE SORTED
+	// ... rest of the function remains the same
 	canonicalQueryString := buildCanonicalQueryString(r)
-
-	// Canonical headers (already includes trailing newlines)
 	canonicalHeaders := buildCanonicalHeaders(r, signedHeaders)
-
-	// Signed headers
 	signedHeadersStr := strings.Join(signedHeaders, ";")
 
-	// Payload hash - Check if client sent UNSIGNED-PAYLOAD
 	var payloadHash string
 	amzContentSha256 := r.Header.Get("X-Amz-Content-SHA256")
 	if amzContentSha256 == "UNSIGNED-PAYLOAD" {
-		// Use the literal string for streaming/multipart uploads
 		payloadHash = "UNSIGNED-PAYLOAD"
 	} else if amzContentSha256 != "" {
-		// Use the hash provided by the client
 		payloadHash = amzContentSha256
 	} else {
-		// Calculate hash from the actual body
 		payloadHash = sha256Hash(body)
 	}
 
@@ -258,6 +286,8 @@ func buildCanonicalRequest(r *http.Request, body []byte, signedHeaders []string)
 
 	log.Printf("=== Canonical Request ===")
 	log.Printf("Method: %s", method)
+	log.Printf("Host: %s (Virtual-hosted: %v)", r.Host, isVirtualHostedStyle(r.Host))
+	log.Printf("Original RequestURI: %s", r.RequestURI)
 	log.Printf("Canonical URI: %s", canonicalURI)
 	log.Printf("Canonical Query String: %s", canonicalQueryString)
 	log.Printf("Canonical Headers:\n%s", canonicalHeaders)