|
@@ -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),
|