@@ -113,28 +113,36 @@ spec = do
113 it "does not recognize non-http schemes" $
113 it "does not recognize non-http schemes" $
114 parseUrlHttp "https://httpbin.org" `shouldSatisfy` isNothing
114 parseUrlHttp "https://httpbin.org" `shouldSatisfy` isNothing
115 it "parses correct URLs" $
115 it "parses correct URLs" $
116− property $ \host pieces queryParams ->
116+ property $ \host mport' pieces queryParams -> do
117− not (T.null host) && wellFormed queryParams ==> do
118 let (url', path, queryString) =
117 let (url', path, queryString) =
119− assembleUrl Http host pieces queryParams
118+ assembleUrl Http host mport' pieces queryParams
120 (url'', options) = fromJust (parseUrlHttp url')
119 (url'', options) = fromJust (parseUrlHttp url')
121 request <- req_ GET url'' NoReqBody options
120 request <- req_ GET url'' NoReqBody options
122− L.host request `shouldBe` urlEncode host
121+ L.host request `shouldBe` urlEncode (unHost host)
122+ L.port request `shouldBe` maybe 80 getNonNegative mport'
123 L.path request `shouldBe` path
123 L.path request `shouldBe` path
124 L.queryString request `shouldBe` queryString
124 L.queryString request `shouldBe` queryString
125+ it "rejects gibberish in port component" $ do
126+ parseUrlHttp "http://my-site.com:bob/far" `shouldSatisfy` isNothing
127+ parseUrlHttp "http://my-site.com:7001uh/api" `shouldSatisfy` isNothing
128+ parseUrlHttp "http://my-site.com:/bar" `shouldSatisfy` isNothing
125 describe "parseUrlHttps" $ do
129 describe "parseUrlHttps" $ do
126 it "does not recognize non-https schemes" $
130 it "does not recognize non-https schemes" $
127 parseUrlHttps "http://httpbin.org" `shouldSatisfy` isNothing
131 parseUrlHttps "http://httpbin.org" `shouldSatisfy` isNothing
128 it "parses correct URLs" $
132 it "parses correct URLs" $
129− property $ \host pieces queryParams ->
133+ property $ \host mport' pieces queryParams -> do
130− not (T.null host) && wellFormed queryParams ==> do
131 let (url', path, queryString) =
134 let (url', path, queryString) =
132− assembleUrl Https host pieces queryParams
135+ assembleUrl Https host mport' pieces queryParams
133 (url'', options) = fromJust (parseUrlHttps url')
136 (url'', options) = fromJust (parseUrlHttps url')
134 request <- req_ GET url'' NoReqBody options
137 request <- req_ GET url'' NoReqBody options
135− L.host request `shouldBe` urlEncode host
138+ L.host request `shouldBe` urlEncode (unHost host)
139+ L.port request `shouldBe` maybe 443 getNonNegative mport'
136 L.path request `shouldBe` path
140 L.path request `shouldBe` path
137 L.queryString request `shouldBe` queryString
141 L.queryString request `shouldBe` queryString
142+ it "rejects gibberish in port component" $ do
143+ parseUrlHttp "https://my-site.com:bob/far" `shouldSatisfy` isNothing
144+ parseUrlHttp "https://my-site.com:7001uh/api" `shouldSatisfy` isNothing
145+ parseUrlHttp "https://my-site.com:/bar" `shouldSatisfy` isNothing
139 describe "bodies" $ do
147 describe "bodies" $ do
140 describe "NoReqBody" $
148 describe "NoReqBody" $
@@ -359,6 +367,27 @@ instance Arbitrary Day where
359instance Arbitrary DiffTime where
367instance Arbitrary DiffTime where
360 arbitrary = secondsToDiffTime <$> arbitrary
368 arbitrary = secondsToDiffTime <$> arbitrary
370+----------------------------------------------------------------------------
373+-- | A wrapper to generate correct hosts.
375+newtype Host = Host { unHost :: Text }
378+instance Arbitrary Host where
379+ arbitrary = Host . T.pack <$> listOf1 (arbitrary `suchThat` (/= ':'))
381+-- | A wrapper to generate correct query parameters.
383+newtype QueryParams = QueryParams [(Text, Maybe Text)]
386+instance Arbitrary QueryParams where
387+ arbitrary = QueryParams <$> (arbitrary `suchThat` wellFormed)
389+ wellFormed = all (not . T.null . fst)
362----------------------------------------------------------------------------
391----------------------------------------------------------------------------
363-- Helpers
392-- Helpers
@@ -392,32 +421,32 @@ urlEncode = Y.urlEncode False . T.encodeUtf8
392encodePathPieces :: [Text] -> ByteString
421encodePathPieces :: [Text] -> ByteString
393encodePathPieces = BL.toStrict . BB.toLazyByteString . Y.encodePathSegments
422encodePathPieces = BL.toStrict . BB.toLazyByteString . Y.encodePathSegments
395−-- | Assemble entire URL.
424+-- | Assemble a URL.
397assembleUrl
426assembleUrl
398 :: Scheme -- ^ Scheme
427 :: Scheme -- ^ Scheme
399− -> Text -- ^ Host
428+ -> Host -- ^ Host
429+ -> Maybe (NonNegative Int) -- ^ Port
400 -> [Text] -- ^ Path pieces
430 -> [Text] -- ^ Path pieces
401− -> [(Text, Maybe Text)] -- ^ Query parameters
431+ -> QueryParams -- ^ Query parameters
402 -> (ByteString, ByteString, ByteString) -- ^ URL, path, query string
432 -> (ByteString, ByteString, ByteString) -- ^ URL, path, query string
403−assembleUrl scheme' host' pathPieces queryParams =
433+assembleUrl scheme' (Host host') mport' pathPieces (QueryParams queryParams) =
404− (scheme <> host <> path <> queryString, path, queryString)
434+ (scheme <> host <> port' <> path <> queryString, path, queryString)
406 scheme = case scheme' of
436 scheme = case scheme' of
407 Http -> "http://"
437 Http -> "http://"
408 Https -> "https://"
438 Https -> "https://"
409 host = urlEncode host'
439 host = urlEncode host'
443+ Just (NonNegative x) -> ":" <> B8.pack (show x)
410 path = encodePathPieces pathPieces
444 path = encodePathPieces pathPieces
411 queryString = Y.renderQuery True (Y.queryTextToQuery queryParams)
445 queryString = Y.renderQuery True (Y.queryTextToQuery queryParams)
413renderQuery :: [(Text, Maybe Text)] -> BL.ByteString
447renderQuery :: [(Text, Maybe Text)] -> BL.ByteString
414renderQuery = BL.fromStrict . Y.renderQuery False . Y.queryTextToQuery
448renderQuery = BL.fromStrict . Y.renderQuery False . Y.queryTextToQuery
416−-- | Check if collection of query params is well-formed.
418−wellFormed :: [(Text, Maybe Text)] -> Bool
419−wellFormed = all (not . T.null . fst)
421-- | Convert collection of query parameters to 'FormUrlEncodedParam' thing.
450-- | Convert collection of query parameters to 'FormUrlEncodedParam' thing.
423formUrlEnc :: [(Text, Maybe Text)] -> FormUrlEncodedParam
452formUrlEnc :: [(Text, Maybe Text)] -> FormUrlEncodedParam