44package ebpfcommon
55
66import (
7- "bytes"
87 "errors"
98 "net/http"
109 "strings"
@@ -38,7 +37,7 @@ func parseAWSS3(req *http.Request, resp *http.Response) (request.AWSS3, error) {
3837 if s3 .Meta .ExtendedRequestID == "" {
3938 return s3 , errors .New ("missing x-amz-id-2 header" )
4039 }
41- s3 .Bucket , s3 .Key = parseS3bucketKey (req . URL . Path )
40+ s3 .Bucket , s3 .Key = parseS3bucketKey (req )
4241 s3 .Method = inferS3Method (req )
4342 if s3 .Method == "" {
4443 return s3 , errors .New ("unable to parse s3 operation" )
@@ -47,71 +46,93 @@ func parseAWSS3(req *http.Request, resp *http.Response) (request.AWSS3, error) {
4746 return s3 , nil
4847}
4948
50- func parseS3bucketKey (path string ) (string , string ) {
51- // S3 paths are generally in the format 'PUT /bucket/key'
52- var bucket , key string
53- parts := bytes .SplitN ([]byte (path ), []byte ("/" ), 3 )
54- if len (parts ) >= 2 {
55- bucket = string (parts [1 ])
49+ // parseS3bucketKey extracts the S3 bucket name and object key from an HTTP request.
50+ // It supports both virtual-hosted-style (bucket.s3.region.amazonaws.com)
51+ // and path-style (s3.amazonaws.com/bucket/object) addressing.
52+ //
53+ // Examples:
54+ //
55+ // Host: my-bucket.s3.eu-west-1.amazonaws.com, Path: /foo/bar.txt
56+ // => ("my-bucket", "foo/bar.txt")
57+ //
58+ // Host: s3.amazonaws.com, Path: /my-bucket/foo/bar.txt
59+ // => ("my-bucket", "foo/bar.txt")
60+ //
61+ // Host: my-bucket.s3.amazonaws.com, Path: /
62+ // => ("my-bucket", "")
63+ func parseS3bucketKey (req * http.Request ) (string , string ) {
64+ path := strings .TrimPrefix (req .URL .Path , "/" )
65+
66+ // Case 1: Virtual-hosted–style — bucket in the hostname.
67+ // Example: my-bucket.s3.amazonaws.com /foo/bar.txt
68+ if strings .Contains (req .Host , ".s3." ) {
69+ bucket := strings .SplitN (req .Host , ".s3." , 2 )[0 ]
70+ return bucket , path
71+ }
72+
73+ // Case 2: Path-style — bucket in the first path segment.
74+ // Example: s3.amazonaws.com /my-bucket/foo/bar.txt
75+ parts := strings .SplitN (path , "/" , 2 )
76+ if len (parts ) == 0 || parts [0 ] == "" {
77+ return "" , ""
5678 }
57- if len (parts ) == 3 {
58- key = string (parts [2 ])
79+
80+ bucket := parts [0 ]
81+ key := ""
82+ if len (parts ) > 1 {
83+ key = parts [1 ]
5984 }
6085 return bucket , key
6186}
6287
6388// This is a naive inference of S3 operations based on HTTP method and URL path/query
6489func inferS3Method (req * http.Request ) string {
65- q := req .URL .Query ()
66- path := strings .Trim (strings .TrimPrefix (req .URL .Path , "/" ), "/" )
67- parts := strings .Split (path , "/" )
90+ path := strings .TrimPrefix (req .URL .Path , "/" )
6891
69- switch req .Method {
70- case http .MethodGet :
71- switch {
72- case path == "" :
73- return "ListBuckets"
74- case len (parts ) == 1 :
75- return "ListObjects"
76- case q .Has ("uploads" ):
77- return "ListMultipartUploads"
78- case q .Has ("uploadId" ):
79- return "ListParts"
80- default :
81- return "GetObject"
92+ var bucket , object string
93+ // --- Virtual-hosted–style URL ---
94+ // Example: PUT bucket.s3.eu-west-1.amazonaws.com /hello.txt
95+ if strings .Contains (req .Host , ".s3." ) {
96+ bucket = strings .SplitN (req .Host , ".s3." , 2 )[0 ]
97+ object = path // path may be empty or "object-key"
98+ } else {
99+ // --- Path-style URL ---
100+ // Example: PUT s3.amazonaws.com /bucket/hello.txt
101+ parts := strings .SplitN (path , "/" , 2 )
102+ if len (parts ) > 0 {
103+ bucket = parts [0 ]
82104 }
83- case http .MethodPut :
84- if q .Has ("uploadId" ) && q .Has ("partNumber" ) {
85- return "UploadPart"
86- }
87- if q .Has ("uploadId" ) {
88- return "CompleteMultipartUpload"
105+ if len (parts ) > 1 {
106+ object = parts [1 ]
89107 }
108+ }
90109
91- switch len (parts ) {
92- case 1 :
93- // PUT /my-bucket -> Create bucket
110+ hasBucket := bucket != ""
111+ hasObject := object != ""
112+
113+ switch req .Method {
114+ case http .MethodPut :
115+ if hasBucket && ! hasObject {
94116 return "CreateBucket"
95- default :
96- // PUT /my-bucket/object.txt
117+ }
118+ if hasBucket && hasObject {
97119 return "PutObject"
98120 }
99- case http .MethodPost :
100- if q . Has ( "uploads" ) {
101- return "CreateMultipartUpload "
121+ case http .MethodDelete :
122+ if hasBucket && ! hasObject {
123+ return "DeleteBucket "
102124 }
103- if q . Has ( "uploadId" ) {
104- return "CompleteMultipartUpload "
125+ if hasBucket && hasObject {
126+ return "DeleteObject "
105127 }
106- return "PutObject"
107- case http .MethodDelete :
108- if q .Has ("uploadId" ) {
109- return "AbortMultipartUpload"
128+ case http .MethodGet :
129+ if ! hasBucket {
130+ return "ListBuckets"
110131 }
111- if len ( parts ) == 1 {
112- return "DeleteBucket "
132+ if hasBucket && ! hasObject {
133+ return "ListObjects "
113134 }
114- return "DeleteObject "
135+ return "GetObject "
115136 }
116137
117138 return ""
0 commit comments