@@ -19,6 +19,7 @@ module Network.Minio.API
1919 S3ReqInfo (..),
2020 runMinio,
2121 executeRequest,
22+ buildRequest,
2223 mkStreamRequest,
2324 getLocation,
2425 isValidBucketName,
@@ -40,6 +41,7 @@ import qualified Data.HashMap.Strict as H
4041import qualified Data.Text as T
4142import qualified Data.Time.Clock as Time
4243import Lib.Prelude
44+import qualified Network.HTTP.Client as NClient
4345import Network.HTTP.Conduit (Response)
4446import qualified Network.HTTP.Conduit as NC
4547import qualified Network.HTTP.Types as HT
@@ -78,6 +80,7 @@ discoverRegion ri = runMaybeT $ do
7880 return
7981 regionMay
8082
83+-- | Returns the region to be used for the request.
8184getRegion :: S3ReqInfo -> Minio (Maybe Region)
8285getRegion ri = do
8386 ci <- asks mcConnInfo
@@ -104,6 +107,42 @@ getRegionHost r = do
104107 (H.lookup r awsRegionMap)
105108 else return $ connectHost ci
106109
110+-- | Computes the appropriate host, path and region for the request.
111+--
112+-- For AWS, always use virtual bucket style, unless bucket has periods. For
113+-- MinIO and other non-AWS, default to path style.
114+getHostPathRegion :: S3ReqInfo -> Minio (Text, ByteString, Maybe Region)
115+getHostPathRegion ri = do
116+ ci <- asks mcConnInfo
117+ regionMay <- getRegion ri
118+ case riBucket ri of
119+ Nothing ->
120+ -- Implies a ListBuckets request.
121+ return (connectHost ci, "/", regionMay)
122+ Just bucket -> do
123+ regionHost <- case regionMay of
124+ Nothing -> return $ connectHost ci
125+ Just "" -> return $ connectHost ci
126+ Just r -> getRegionHost r
127+ let pathStyle =
128+ ( regionHost,
129+ getS3Path (riBucket ri) (riObject ri),
130+ regionMay
131+ )
132+ virtualStyle =
133+ ( ( bucket <> "." <> regionHost,
134+ encodeUtf8 $ "/" <> fromMaybe "" (riObject ri),
135+ regionMay
136+ )
137+ )
138+ if
139+ | isAWSConnectInfo ci ->
140+ return $
141+ if bucketHasPeriods bucket
142+ then pathStyle
143+ else virtualStyle
144+ | otherwise -> return pathStyle
145+
107146buildRequest :: S3ReqInfo -> Minio NC.Request
108147buildRequest ri = do
109148 maybe (return ()) checkBucketNameValidity $ riBucket ri
@@ -111,17 +150,15 @@ buildRequest ri = do
111150
112151 ci <- asks mcConnInfo
113152
114− regionMay <- getRegion ri
115−
116− regionHost <- maybe (return $ connectHost ci) getRegionHost regionMay
153+ (host, path, regionMay) <- getHostPathRegion ri
117154
118− let ri' =
155+ let ci' = ci {connectHost = host}
156+ hostHeader = (hHost, getHostAddr ci')
157+ ri' =
119158 ri
120159 { riHeaders = hostHeader : riHeaders ri,
121160 riRegion = regionMay
122161 }
123− ci' = ci {connectHost = regionHost}
124− hostHeader = (hHost, getHostAddr ci')
125162 -- Does not contain body and auth info.
126163 baseRequest =
127164 NC.defaultRequest
@@ -129,7 +166,7 @@ buildRequest ri = do
129166 NC.secure = connectIsSecure ci',
130167 NC.host = encodeUtf8 $ connectHost ci',
131168 NC.port = connectPort ci',
132− NC.path = getS3Path (riBucket ri') (riObject ri'),
169+ NC.path = path,
133170 NC.requestHeaders = riHeaders ri',
134171 NC.queryString = HT.renderQuery False $ riQueryParams ri'
135172 }
@@ -142,11 +179,13 @@ buildRequest ri = do
142179 (connectSecretKey ci')
143180 timeStamp
144181 (riRegion ri')
145− Nothing
182+ (riPresignExpirySecs ri')
146183 Nothing
147184
148185 -- Cases to handle:
149186 --
187+ -- 0. Handle presign URL case.
188+ --
150189 -- 1. Connection is secure: use unsigned payload
151190 --
152191 -- 2. Insecure connection, streaming signature is enabled via use of
@@ -155,33 +194,44 @@ buildRequest ri = do
155194 -- 3. Insecure connection, non-conduit payload: compute payload
156195 -- sha256hash, buffer request in memory and perform request.
157196
158− -- case 2 from above.
159197 if
160− | isStreamingPayload (riPayload ri')
161− && (not $ connectIsSecure ci') -> do
162− (pLen, pSrc) <- case riPayload ri of
163− PayloadC l src -> return (l, src)
164− _ -> throwIO MErrVUnexpectedPayload
165− let reqFn = signV4Stream pLen sp baseRequest
166− return $ reqFn pSrc
167− | otherwise -> do
168− -- case 1 described above.
169− sp' <-
170− if
171− | connectIsSecure ci' -> return sp
172− -- case 3 described above.
173− | otherwise -> do
174− pHash <- getPayloadSHA256Hash $ riPayload ri'
175− return $ sp {spPayloadHash = Just pHash}
198+ | isJust (riPresignExpirySecs ri') ->
199+ -- case 0 from above.
200+ do
201+ let signPairs = signV4 sp baseRequest
202+ qpToAdd = (fmap . fmap) Just signPairs
203+ existingQueryParams = HT.parseQuery (NC.queryString baseRequest)
204+ updatedQueryParams = existingQueryParams ++ qpToAdd
205+ return $ NClient.setQueryString updatedQueryParams baseRequest
206+ | isStreamingPayload (riPayload ri') && (not $ connectIsSecure ci') ->
207+ -- case 2 from above.
208+ do
209+ (pLen, pSrc) <- case riPayload ri of
210+ PayloadC l src -> return (l, src)
211+ _ -> throwIO MErrVUnexpectedPayload
212+ let reqFn = signV4Stream pLen sp baseRequest
213+ return $ reqFn pSrc
214+ | otherwise ->
215+ do
216+ sp' <-
217+ if
218+ | connectIsSecure ci' ->
219+ -- case 1 described above.
220+ return sp
221+ | otherwise ->
222+ -- case 3 described above.
223+ do
224+ pHash <- getPayloadSHA256Hash $ riPayload ri'
225+ return $ sp {spPayloadHash = Just pHash}
176226
177− let signHeaders = signV4 sp' baseRequest
178− return $
179− baseRequest
180− { NC.requestHeaders =
181− NC.requestHeaders baseRequest
182− ++ mkHeaderFromPairs signHeaders,
183− NC.requestBody = getRequestBody (riPayload ri')
184− }
227+ let signHeaders = signV4 sp' baseRequest
228+ return $
229+ baseRequest
230+ { NC.requestHeaders =
231+ NC.requestHeaders baseRequest
232+ ++ mkHeaderFromPairs signHeaders,
233+ NC.requestBody = getRequestBody (riPayload ri')
234+ }
185235
186236retryAPIRequest :: Minio a -> Minio a
187237retryAPIRequest apiCall = do