8
8
#include " nix/util/callback.hh"
9
9
#include " nix/util/signals.hh"
10
10
#include " nix/store/store-reference.hh"
11
+ #include " nix/util/url.hh"
11
12
12
13
#include " store-config-private.hh"
13
14
#if NIX_WITH_S3_SUPPORT
@@ -147,7 +148,8 @@ struct curlFileTransfer : public FileTransfer
147
148
// Handle S3 URLs with curl-based AWS SigV4 authentication
148
149
if (hasPrefix (request.uri , " s3://" )) {
149
150
try {
150
- auto [httpsUri, params] = fileTransfer.convertS3ToHttpsUri (request.uri );
151
+ auto s3Url = fileTransfer.parseS3Url (request.uri );
152
+ auto httpsUri = s3Url.toHttpsUrl ().to_string ();
151
153
152
154
// Update the request URI to use HTTPS
153
155
const_cast <FileTransferRequest &>(request).uri = httpsUri;
@@ -157,7 +159,7 @@ struct curlFileTransfer : public FileTransfer
157
159
isS3Request = true ;
158
160
159
161
// Get credentials
160
- std::string profile = getOr (params, " profile " , " " );
162
+ std::string profile = s3Url. getProfile ( );
161
163
auto credProvider = profile.empty () ? AwsCredentialProvider::createDefault ()
162
164
: AwsCredentialProvider::createProfile (profile);
163
165
@@ -166,7 +168,7 @@ struct curlFileTransfer : public FileTransfer
166
168
if (creds) {
167
169
awsCredentials = creds->accessKeyId + " :" + creds->secretAccessKey ;
168
170
169
- std::string region = getOr (params, " region " , " us-east-1 " );
171
+ std::string region = s3Url. getRegion ( );
170
172
std::string service = " s3" ;
171
173
awsSigV4Provider = " aws:amz:" + region + " :" + service;
172
174
@@ -176,7 +178,7 @@ struct curlFileTransfer : public FileTransfer
176
178
requestHeaders, (" x-amz-security-token: " + *creds->sessionToken ).c_str ());
177
179
}
178
180
179
- debug (" Using AWS SigV4 authentication for S3 request to %s" , httpsUri);
181
+ debug (" Using AWS SigV4 authentication for S3 request to %s" , httpsUri. c_str () );
180
182
} else {
181
183
warn (" Failed to obtain AWS credentials for S3 request %s" , request.uri );
182
184
}
@@ -865,41 +867,90 @@ struct curlFileTransfer : public FileTransfer
865
867
}
866
868
867
869
#if NIX_WITH_S3_SUPPORT
868
- std::tuple<std::string, std::string, Store::Config::Params> parseS3Uri (std::string uri)
870
+ /* *
871
+ * Parsed S3 URL with convenience methods for parameter access and HTTPS conversion
872
+ */
873
+ struct S3Url
869
874
{
870
- auto [path, params] = splitUriAndParams (uri);
875
+ std::string bucket;
876
+ std::string key;
877
+ StringMap params;
871
878
872
- auto slash = path.find (' /' , 5 ); // 5 is the length of "s3://" prefix
873
- if (slash == std::string::npos)
874
- throw nix::Error (" bad S3 URI '%s'" , path);
879
+ std::string getProfile () const
880
+ {
881
+ return getOr (params, " profile" , " " );
882
+ }
875
883
876
- std::string bucketName (path, 5 , slash - 5 );
877
- std::string key (path, slash + 1 );
884
+ std::string getRegion () const
885
+ {
886
+ return getOr (params, " region" , " us-east-1" );
887
+ }
878
888
879
- return {bucketName, key, params};
880
- }
889
+ std::string getScheme () const
890
+ {
891
+ return getOr (params, " scheme" , " https" );
892
+ }
893
+
894
+ std::string getEndpoint () const
895
+ {
896
+ return getOr (params, " endpoint" , " " );
897
+ }
898
+
899
+ /* *
900
+ * Convert S3 URL to HTTPS URL for use with curl's AWS SigV4 authentication
901
+ */
902
+ ParsedURL toHttpsUrl () const
903
+ {
904
+ std::string region = getRegion ();
905
+ std::string endpoint = getEndpoint ();
906
+ std::string scheme = getScheme ();
907
+
908
+ ParsedURL httpsUrl;
909
+ httpsUrl.scheme = scheme;
910
+
911
+ if (!endpoint.empty ()) {
912
+ // Custom endpoint (e.g., MinIO, custom S3-compatible service)
913
+ httpsUrl.authority = ParsedURL::Authority{.host = endpoint};
914
+ httpsUrl.path = " /" + bucket + " /" + key;
915
+ } else {
916
+ // Standard AWS S3 endpoint
917
+ httpsUrl.authority = ParsedURL::Authority{.host = " s3." + region + " .amazonaws.com" };
918
+ httpsUrl.path = " /" + bucket + " /" + key;
919
+ }
920
+
921
+ return httpsUrl;
922
+ }
923
+ };
881
924
882
925
/* *
883
- * Convert S3 URI to HTTPS URI for use with curl's AWS SigV4 authentication
926
+ * Parse S3 URI into structured S3Url object
884
927
*/
885
- std::pair<std::string, Store::Config::Params> convertS3ToHttpsUri (const std::string & s3Uri )
928
+ S3Url parseS3Url (const std::string & uri )
886
929
{
887
- auto [bucketName, key, params] = parseS3Uri (s3Uri);
888
-
889
- std::string region = getOr (params, " region" , " us-east-1" );
890
- std::string endpoint = getOr (params, " endpoint" , " " );
891
- std::string scheme = getOr (params, " scheme" , " https" );
892
-
893
- std::string httpsUri;
894
- if (!endpoint.empty ()) {
895
- // Custom endpoint (e.g., MinIO, custom S3-compatible service)
896
- httpsUri = scheme + " ://" + endpoint + " /" + bucketName + " /" + key;
897
- } else {
898
- // Standard AWS S3 endpoint
899
- httpsUri = scheme + " ://s3." + region + " .amazonaws.com/" + bucketName + " /" + key;
900
- }
930
+ try {
931
+ auto parsed = parseURL (uri);
901
932
902
- return {httpsUri, params};
933
+ if (parsed.scheme != " s3" )
934
+ throw nix::Error (" URI scheme '%s' is not 's3'" , parsed.scheme );
935
+
936
+ if (!parsed.authority || parsed.authority ->host .empty ())
937
+ throw nix::Error (" S3 URI missing bucket name" );
938
+
939
+ std::string bucket = parsed.authority ->host ;
940
+ std::string key = parsed.path ;
941
+
942
+ // Remove leading slash from key if present
943
+ if (!key.empty () && key[0 ] == ' /' ) {
944
+ key = key.substr (1 );
945
+ }
946
+
947
+ if (key.empty ())
948
+ throw nix::Error (" S3 URI missing object key" );
949
+
950
+ return S3Url{.bucket = bucket, .key = key, .params = parsed.query };
951
+ } catch (BadURL & e) {
952
+ throw nix::Error (" invalid S3 URI '%s': %s" , uri, e.what ());
953
+ }
903
954
}
904
955
#endif
905
956
@@ -914,17 +965,17 @@ struct curlFileTransfer : public FileTransfer
914
965
// Fall back to legacy S3Helper approach
915
966
// FIXME: do this on a worker thread
916
967
try {
917
- auto [bucketName, key, params] = parseS3Uri (request.uri );
968
+ auto s3Parsed = this -> parseS3Url (request.uri );
918
969
919
- std::string profile = getOr (params, " profile" , " " );
920
- std::string region = getOr (params, " region" , Aws::Region::US_EAST_1);
921
- std::string scheme = getOr (params, " scheme" , " " );
922
- std::string endpoint = getOr (params, " endpoint" , " " );
970
+ std::string profile = getOr (s3Parsed. params , " profile" , " " );
971
+ std::string region = getOr (s3Parsed. params , " region" , Aws::Region::US_EAST_1);
972
+ std::string scheme = getOr (s3Parsed. params , " scheme" , " " );
973
+ std::string endpoint = getOr (s3Parsed. params , " endpoint" , " " );
923
974
924
975
S3Helper s3Helper (profile, region, scheme, endpoint);
925
976
926
977
// FIXME: implement ETag
927
- auto s3Res = s3Helper.getObject (bucketName, key);
978
+ auto s3Res = s3Helper.getObject (s3Parsed. bucket , s3Parsed. key );
928
979
FileTransferResult res;
929
980
if (!s3Res.data )
930
981
throw FileTransferError (NotFound, {}, " S3 object '%s' does not exist" , request.uri );
0 commit comments