Alan Yeung 4 päivää sitten
vanhempi
commit
f2f3b93733

BIN
aws/data/bolt.db


+ 63 - 25
aws/internal/handler/public_view.go

@@ -16,11 +16,11 @@ import (
 // PublicViewHandler handles public viewing of S3 bucket contents
 type PublicViewHandler struct {
 	storage *storage.UserAwareStorage
-	db      *kvdb.BoltKVDB
+	db      kvdb.KVDB
 }
 
 // NewPublicViewHandler creates a new public view handler
-func NewPublicViewHandler(storage *storage.UserAwareStorage, db *kvdb.BoltKVDB) *PublicViewHandler {
+func NewPublicViewHandler(storage *storage.UserAwareStorage, db kvdb.KVDB) *PublicViewHandler {
 	return &PublicViewHandler{
 		storage: storage,
 		db:      db,
@@ -28,22 +28,28 @@ func NewPublicViewHandler(storage *storage.UserAwareStorage, db *kvdb.BoltKVDB)
 }
 
 // Handle processes public view requests
-// URL format: /s3/{accountID}/{bucketID}/{key...}
+// URL format: /{bucketName}/{key...}
 func (h *PublicViewHandler) Handle(w http.ResponseWriter, r *http.Request) {
-	// Parse path: /s3/{accountID}/{bucketID}/{key...}
-	path := strings.TrimPrefix(r.URL.Path, "/s3/")
-	parts := strings.SplitN(path, "/", 3)
-
-	if len(parts) < 2 {
-		http.Error(w, "Invalid path format. Expected: /s3/{accountID}/{bucketID}/{key}", http.StatusBadRequest)
+	// Parse path: /{bucketName}/{key...}
+	path := strings.TrimPrefix(r.URL.Path, "/")
+	if path == "" {
+		http.Error(w, "Invalid path format. Expected: /{bucketName}/{key}", http.StatusBadRequest)
 		return
 	}
 
-	accountID := parts[0]
-	bucketID := parts[1]
+	parts := strings.SplitN(path, "/", 2)
+	bucketName := parts[0]
 	key := ""
-	if len(parts) > 2 {
-		key = parts[2]
+	if len(parts) > 1 {
+		key = parts[1]
+	}
+
+	// Resolve bucket name to accountID and bucketID
+	accountID, bucketID, err := h.db.ResolveBucketName(bucketName)
+	if err != nil {
+		log.Printf("Failed to resolve bucket name %s: %v", bucketName, err)
+		http.Error(w, "Bucket not found", http.StatusNotFound)
+		return
 	}
 
 	// Check if bucket has public viewing enabled
@@ -66,7 +72,7 @@ func (h *PublicViewHandler) Handle(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// Serve the file
-	h.handleFileDownload(w, r, accountID, bucketID, key)
+	h.handleFileDownload(w, r, accountID, bucketID, bucketName, key)
 }
 
 func (h *PublicViewHandler) handleBucketListing(w http.ResponseWriter, r *http.Request, accountID, bucketID string, config *kvdb.BucketConfig) {
@@ -79,14 +85,14 @@ func (h *PublicViewHandler) handleBucketListing(w http.ResponseWriter, r *http.R
 	}
 
 	// Generate HTML page
-	html := h.generateBucketListingHTML(accountID, bucketID, config.BucketName, objects)
+	htmlContent := h.generateBucketListingHTML(config.BucketName, objects)
 
 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 	w.WriteHeader(http.StatusOK)
-	w.Write([]byte(html))
+	w.Write([]byte(htmlContent))
 }
 
-func (h *PublicViewHandler) handleFileDownload(w http.ResponseWriter, r *http.Request, accountID, bucketID, key string) {
+func (h *PublicViewHandler) handleFileDownload(w http.ResponseWriter, r *http.Request, accountID, bucketID, bucketName, key string) {
 	// Get the file
 	file, info, err := h.storage.GetObjectByBucketIDForUser(accountID, bucketID, key)
 	if err != nil {
@@ -120,7 +126,7 @@ 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(accountID, bucketID, bucketName string, objects []storage.ObjectInfo) string {
+func (h *PublicViewHandler) generateBucketListingHTML(bucketName string, objects []storage.ObjectInfo) string {
 	var sb strings.Builder
 
 	sb.WriteString(`<!DOCTYPE html>
@@ -294,15 +300,14 @@ func (h *PublicViewHandler) generateBucketListingHTML(accountID, bucketID, bucke
 			icon := getFileIcon(ext)
 
 			sb.WriteString(fmt.Sprintf(`
-            <a href="/s3/%s/%s/%s" class="file-item">
+            <a href="/%s/%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>`,
-				accountID,
-				bucketID,
+				bucketName,
 				obj.Key,
 				icon,
 				html.EscapeString(obj.Key),
@@ -315,12 +320,9 @@ func (h *PublicViewHandler) generateBucketListingHTML(accountID, bucketID, bucke
 	sb.WriteString(`
         </div>
         <div class="footer">
-            <p>Powered by AWS S3 Mock Server | Bucket ID: `)
-	sb.WriteString(bucketID)
-	sb.WriteString(`</p>
+            <p>Powered by AWS S3 Mock Server</p>
         </div>
     </div>
-</body>
 </html>`)
 
 	return sb.String()
@@ -364,6 +366,42 @@ func getFileIcon(ext string) string {
 	}
 }
 
+func getContentType(filename string) string {
+	ext := strings.ToLower(filepath.Ext(filename))
+	switch ext {
+	case ".jpg", ".jpeg":
+		return "image/jpeg"
+	case ".png":
+		return "image/png"
+	case ".gif":
+		return "image/gif"
+	case ".webp":
+		return "image/webp"
+	case ".svg":
+		return "image/svg+xml"
+	case ".pdf":
+		return "application/pdf"
+	case ".json":
+		return "application/json"
+	case ".xml":
+		return "application/xml"
+	case ".txt":
+		return "text/plain"
+	case ".html":
+		return "text/html"
+	case ".css":
+		return "text/css"
+	case ".js":
+		return "application/javascript"
+	case ".mp4":
+		return "video/mp4"
+	case ".mp3":
+		return "audio/mpeg"
+	default:
+		return "application/octet-stream"
+	}
+}
+
 func isImage(ext string) bool {
 	switch ext {
 	case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp":

+ 0 - 25
aws/internal/handler/utils.go

@@ -2,8 +2,6 @@ package handler
 
 import (
 	"fmt"
-	"path/filepath"
-	"strings"
 	"time"
 )
 
@@ -11,26 +9,3 @@ import (
 func generateRequestId() string {
 	return fmt.Sprintf("%d-%d", time.Now().Unix(), time.Now().Nanosecond())
 }
-
-// getContentType returns the MIME type for a file based on its extension
-func getContentType(filename string) string {
-	ext := strings.ToLower(filepath.Ext(filename))
-	switch ext {
-	case ".txt":
-		return "text/plain"
-	case ".html", ".htm":
-		return "text/html"
-	case ".json":
-		return "application/json"
-	case ".xml":
-		return "application/xml"
-	case ".jpg", ".jpeg":
-		return "image/jpeg"
-	case ".png":
-		return "image/png"
-	case ".pdf":
-		return "application/pdf"
-	default:
-		return "application/octet-stream"
-	}
-}

+ 11 - 10
aws/internal/kvdb/bolt.go

@@ -10,8 +10,8 @@ import (
 )
 
 var (
-	usersBucket        = []byte("users")
-	bucketConfigBucket = []byte("bucket_configs")
+	usersBucket   = []byte("users")
+	bucketsBucket = []byte("buckets")
 )
 
 // Ensure BoltKVDB implements KVDB interface
@@ -44,7 +44,7 @@ func NewBoltKVDB(path string) (*BoltKVDB, error) {
 		if err != nil {
 			return err
 		}
-		_, err = tx.CreateBucketIfNotExists(bucketConfigBucket)
+		_, err = tx.CreateBucketIfNotExists(bucketsBucket)
 		return err
 	})
 	if err != nil {
@@ -165,7 +165,7 @@ func (db *BoltKVDB) Close() error {
 // SetBucketConfig sets the configuration for a bucket
 func (db *BoltKVDB) SetBucketConfig(config *BucketConfig) error {
 	return db.db.Update(func(tx *bbolt.Tx) error {
-		b := tx.Bucket(bucketConfigBucket)
+		b := tx.Bucket(bucketsBucket)
 
 		// Key format: accountID:bucketID
 		key := config.AccountID + ":" + config.BucketID
@@ -184,7 +184,7 @@ func (db *BoltKVDB) GetBucketConfig(accountID, bucketID string) (*BucketConfig,
 	var config BucketConfig
 
 	err := db.db.View(func(tx *bbolt.Tx) error {
-		bucket := tx.Bucket([]byte("buckets"))
+		bucket := tx.Bucket(bucketsBucket)
 		if bucket == nil {
 			return fmt.Errorf("buckets bucket not found")
 		}
@@ -208,7 +208,7 @@ func (db *BoltKVDB) GetBucketConfig(accountID, bucketID string) (*BucketConfig,
 // DeleteBucketConfig deletes the configuration for a bucket
 func (db *BoltKVDB) DeleteBucketConfig(accountID, bucketID string) error {
 	return db.db.Update(func(tx *bbolt.Tx) error {
-		b := tx.Bucket(bucketConfigBucket)
+		b := tx.Bucket(bucketsBucket)
 		key := accountID + ":" + bucketID
 		return b.Delete([]byte(key))
 	})
@@ -220,7 +220,7 @@ func (db *BoltKVDB) ListBucketConfigs(accountID string) ([]*BucketConfig, error)
 	prefix := []byte(accountID + ":")
 
 	err := db.db.View(func(tx *bbolt.Tx) error {
-		b := tx.Bucket(bucketConfigBucket)
+		b := tx.Bucket(bucketsBucket)
 		c := b.Cursor()
 
 		for k, v := c.Seek(prefix); k != nil && len(k) >= len(prefix) && string(k[:len(prefix)]) == string(prefix); k, v = c.Next() {
@@ -236,9 +236,10 @@ func (db *BoltKVDB) ListBucketConfigs(accountID string) ([]*BucketConfig, error)
 	return configs, err
 }
 
-func (db *BoltKVDB) ResolveBucketName(bucketName string) (accountID string, bucketID string, errr error) {
-	err := db.db.View(func(tx *bbolt.Tx) error {
-		bucket := tx.Bucket([]byte("buckets"))
+// ResolveBucketName resolves a bucket name to accountID and bucketID
+func (db *BoltKVDB) ResolveBucketName(bucketName string) (accountID string, bucketID string, err error) {
+	err = db.db.View(func(tx *bbolt.Tx) error {
+		bucket := tx.Bucket(bucketsBucket)
 		if bucket == nil {
 			return fmt.Errorf("buckets bucket not found")
 		}

+ 183 - 12
aws/internal/kvdb/kvdb.go

@@ -3,6 +3,7 @@ package kvdb
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"os"
 	"path/filepath"
 	"sync"
@@ -10,14 +11,16 @@ import (
 
 // InMemoryKVDB is a simple in-memory key-value database
 type InMemoryKVDB struct {
-	users map[string]*User // key: AccessKeyID
-	mu    sync.RWMutex
+	users         map[string]*User         // key: AccessKeyID
+	bucketConfigs map[string]*BucketConfig // key: accountID:bucketID
+	mu            sync.RWMutex
 }
 
 // NewInMemoryKVDB creates a new in-memory key-value database
 func NewInMemoryKVDB() *InMemoryKVDB {
 	return &InMemoryKVDB{
-		users: make(map[string]*User),
+		users:         make(map[string]*User),
+		bucketConfigs: make(map[string]*BucketConfig),
 	}
 }
 
@@ -106,6 +109,78 @@ func (db *InMemoryKVDB) ValidateCredentials(accessKeyID, secretAccessKey string)
 	return user, nil
 }
 
+// SetBucketConfig sets the configuration for a bucket
+func (db *InMemoryKVDB) SetBucketConfig(config *BucketConfig) error {
+	db.mu.Lock()
+	defer db.mu.Unlock()
+
+	key := config.AccountID + ":" + config.BucketID
+	configCopy := *config
+	db.bucketConfigs[key] = &configCopy
+	return nil
+}
+
+// GetBucketConfig gets the configuration for a bucket
+func (db *InMemoryKVDB) GetBucketConfig(accountID, bucketID string) (*BucketConfig, error) {
+	db.mu.RLock()
+	defer db.mu.RUnlock()
+
+	key := accountID + ":" + bucketID
+	config, exists := db.bucketConfigs[key]
+	if !exists {
+		return nil, fmt.Errorf("bucket config not found")
+	}
+
+	configCopy := *config
+	return &configCopy, nil
+}
+
+// DeleteBucketConfig deletes the configuration for a bucket
+func (db *InMemoryKVDB) DeleteBucketConfig(accountID, bucketID string) error {
+	db.mu.Lock()
+	defer db.mu.Unlock()
+
+	key := accountID + ":" + bucketID
+	if _, exists := db.bucketConfigs[key]; !exists {
+		return fmt.Errorf("bucket config not found")
+	}
+
+	delete(db.bucketConfigs, key)
+	return nil
+}
+
+// ListBucketConfigs lists all bucket configurations for an account
+func (db *InMemoryKVDB) ListBucketConfigs(accountID string) ([]*BucketConfig, error) {
+	db.mu.RLock()
+	defer db.mu.RUnlock()
+
+	var configs []*BucketConfig
+	prefix := accountID + ":"
+
+	for key, config := range db.bucketConfigs {
+		if len(key) >= len(prefix) && key[:len(prefix)] == prefix {
+			configCopy := *config
+			configs = append(configs, &configCopy)
+		}
+	}
+
+	return configs, nil
+}
+
+// ResolveBucketName resolves a bucket name to accountID and bucketID
+func (db *InMemoryKVDB) ResolveBucketName(bucketName string) (string, string, error) {
+	db.mu.RLock()
+	defer db.mu.RUnlock()
+
+	for _, config := range db.bucketConfigs {
+		if config.BucketName == bucketName {
+			return config.AccountID, config.BucketID, nil
+		}
+	}
+
+	return "", "", fmt.Errorf("bucket not found: %s", bucketName)
+}
+
 // Close closes the database (no-op for in-memory)
 func (db *InMemoryKVDB) Close() error {
 	return nil
@@ -113,16 +188,18 @@ func (db *InMemoryKVDB) Close() error {
 
 // FileBasedKVDB is a file-based key-value database with persistence
 type FileBasedKVDB struct {
-	filePath string
-	users    map[string]*User
-	mu       sync.RWMutex
+	filePath      string
+	users         map[string]*User
+	bucketConfigs map[string]*BucketConfig
+	mu            sync.RWMutex
 }
 
 // NewFileBasedKVDB creates a new file-based key-value database
 func NewFileBasedKVDB(filePath string) (*FileBasedKVDB, error) {
 	db := &FileBasedKVDB{
-		filePath: filePath,
-		users:    make(map[string]*User),
+		filePath:      filePath,
+		users:         make(map[string]*User),
+		bucketConfigs: make(map[string]*BucketConfig),
 	}
 
 	// Create directory if it doesn't exist
@@ -139,6 +216,11 @@ func NewFileBasedKVDB(filePath string) (*FileBasedKVDB, error) {
 	return db, nil
 }
 
+type fileBasedData struct {
+	Users         []*User         `json:"users"`
+	BucketConfigs []*BucketConfig `json:"bucket_configs"`
+}
+
 // load reads the database from disk
 func (db *FileBasedKVDB) load() error {
 	data, err := os.ReadFile(db.filePath)
@@ -146,18 +228,23 @@ func (db *FileBasedKVDB) load() error {
 		return err
 	}
 
-	var users []*User
-	if err := json.Unmarshal(data, &users); err != nil {
+	var fileData fileBasedData
+	if err := json.Unmarshal(data, &fileData); err != nil {
 		return err
 	}
 
 	db.mu.Lock()
 	defer db.mu.Unlock()
 
-	for _, user := range users {
+	for _, user := range fileData.Users {
 		db.users[user.AccessKeyID] = user
 	}
 
+	for _, config := range fileData.BucketConfigs {
+		key := config.AccountID + ":" + config.BucketID
+		db.bucketConfigs[key] = config
+	}
+
 	return nil
 }
 
@@ -168,7 +255,17 @@ func (db *FileBasedKVDB) save() error {
 		users = append(users, user)
 	}
 
-	data, err := json.MarshalIndent(users, "", "  ")
+	configs := make([]*BucketConfig, 0, len(db.bucketConfigs))
+	for _, config := range db.bucketConfigs {
+		configs = append(configs, config)
+	}
+
+	fileData := fileBasedData{
+		Users:         users,
+		BucketConfigs: configs,
+	}
+
+	data, err := json.MarshalIndent(fileData, "", "  ")
 	if err != nil {
 		return err
 	}
@@ -262,6 +359,80 @@ func (db *FileBasedKVDB) ValidateCredentials(accessKeyID, secretAccessKey string
 	return user, nil
 }
 
+// SetBucketConfig sets the configuration for a bucket
+func (db *FileBasedKVDB) SetBucketConfig(config *BucketConfig) error {
+	db.mu.Lock()
+	defer db.mu.Unlock()
+
+	key := config.AccountID + ":" + config.BucketID
+	configCopy := *config
+	db.bucketConfigs[key] = &configCopy
+
+	return db.save()
+}
+
+// GetBucketConfig gets the configuration for a bucket
+func (db *FileBasedKVDB) GetBucketConfig(accountID, bucketID string) (*BucketConfig, error) {
+	db.mu.RLock()
+	defer db.mu.RUnlock()
+
+	key := accountID + ":" + bucketID
+	config, exists := db.bucketConfigs[key]
+	if !exists {
+		return nil, fmt.Errorf("bucket config not found")
+	}
+
+	configCopy := *config
+	return &configCopy, nil
+}
+
+// DeleteBucketConfig deletes the configuration for a bucket
+func (db *FileBasedKVDB) DeleteBucketConfig(accountID, bucketID string) error {
+	db.mu.Lock()
+	defer db.mu.Unlock()
+
+	key := accountID + ":" + bucketID
+	if _, exists := db.bucketConfigs[key]; !exists {
+		return fmt.Errorf("bucket config not found")
+	}
+
+	delete(db.bucketConfigs, key)
+
+	return db.save()
+}
+
+// ListBucketConfigs lists all bucket configurations for an account
+func (db *FileBasedKVDB) ListBucketConfigs(accountID string) ([]*BucketConfig, error) {
+	db.mu.RLock()
+	defer db.mu.RUnlock()
+
+	var configs []*BucketConfig
+	prefix := accountID + ":"
+
+	for key, config := range db.bucketConfigs {
+		if len(key) >= len(prefix) && key[:len(prefix)] == prefix {
+			configCopy := *config
+			configs = append(configs, &configCopy)
+		}
+	}
+
+	return configs, nil
+}
+
+// ResolveBucketName resolves a bucket name to accountID and bucketID
+func (db *FileBasedKVDB) ResolveBucketName(bucketName string) (string, string, error) {
+	db.mu.RLock()
+	defer db.mu.RUnlock()
+
+	for _, config := range db.bucketConfigs {
+		if config.BucketName == bucketName {
+			return config.AccountID, config.BucketID, nil
+		}
+	}
+
+	return "", "", fmt.Errorf("bucket not found: %s", bucketName)
+}
+
 // Close closes the database
 func (db *FileBasedKVDB) Close() error {
 	db.mu.Lock()

+ 4 - 0
aws/internal/kvdb/types.go

@@ -31,6 +31,10 @@ type KVDB interface {
 	// User validation
 	ValidateCredentials(accessKeyID, secretAccessKey string) (*User, error)
 
+	// Bucket operations
+	ResolveBucketName(bucketName string) (accountID string, bucketID string, err error)
+	GetBucketConfig(accountID, bucketID string) (*BucketConfig, error)
+
 	// Close the database
 	Close() error
 }

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 31 - 20
aws/output.txt


+ 32 - 23
aws/pkg/sigv4/sigv4.go

@@ -264,35 +264,44 @@ func buildCanonicalQueryString(r *http.Request) string {
 		return ""
 	}
 
-	// Parse the query parameters properly
-	query := r.URL.Query()
+	// Split into key-value pairs
+	params := strings.Split(r.URL.RawQuery, "&")
 
-	// Get all keys and sort them
-	keys := make([]string, 0, len(query))
-	for k := range query {
-		keys = append(keys, k)
+	// Parse each parameter to separate key and value
+	type param struct {
+		key   string
+		value string
 	}
-	sort.Strings(keys)
 
-	// Build the canonical query string
-	var params []string
-	for _, key := range keys {
-		values := query[key]
-		// Sort values for this key
-		sort.Strings(values)
-
-		for _, value := range values {
-			if value == "" {
-				// Empty value - add just key with equals sign
-				params = append(params, key+"=")
-			} else {
-				// Non-empty value
-				params = append(params, key+"="+value)
-			}
+	parsedParams := make([]param, 0, len(params))
+	for _, p := range params {
+		parts := strings.SplitN(p, "=", 2)
+		if len(parts) == 2 {
+			parsedParams = append(parsedParams, param{key: parts[0], value: parts[1]})
+		} else {
+			parsedParams = append(parsedParams, param{key: parts[0], value: ""})
+		}
+	}
+
+	// Sort by key, then by value
+	sort.Slice(parsedParams, func(i, j int) bool {
+		if parsedParams[i].key == parsedParams[j].key {
+			return parsedParams[i].value < parsedParams[j].value
+		}
+		return parsedParams[i].key < parsedParams[j].key
+	})
+
+	// Rebuild the query string
+	result := make([]string, len(parsedParams))
+	for i, p := range parsedParams {
+		if p.value == "" {
+			result[i] = p.key + "="
+		} else {
+			result[i] = p.key + "=" + p.value
 		}
 	}
 
-	return strings.Join(params, "&")
+	return strings.Join(result, "&")
 }
 
 func buildCanonicalHeaders(r *http.Request, signedHeaders []string) string {

+ 3 - 0
aws/test.bat

@@ -1,3 +1,4 @@
+aws sts get-caller-identity --endpoint-url http://localhost:8080
 aws s3 ls --endpoint-url http://localhost:8080
 aws s3 mb s3://my-test-bucket --endpoint-url http://localhost:8080
 echo "Hello, S3!" > test.txt
@@ -7,6 +8,8 @@ aws s3 cp s3://my-test-bucket/test.txt downloaded.txt --endpoint-url http://loca
 aws s3 ls --endpoint-url http://localhost:8080
 aws s3 ls s3://my-test-bucket/ --endpoint-url http://localhost:8080
 aws s3 rm s3://my-test-bucket/test.txt --endpoint-url http://localhost:8080
+aws s3 rm "s3://my-test-bucket/DSC01472 copy.jpg" --endpoint-url http://localhost:8080
 aws sts get-caller-identity --endpoint-url http://localhost:8080
 aws s3 rb s3://my-test-bucket --endpoint-url=http://localhost:8080
 aws s3 ls --endpoint-url http://localhost:8080
+

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä