@@ -43,8 +43,8 @@ import Data.HashSet qualified as HashSet
43
43
import Data.Text (Text )
44
44
import Data.Text qualified as Text
45
45
import Data.Text.Encoding qualified as Text
46
- import Network.HTTP.Types.URI (Query , parseQuery , parseQueryText )
47
- import Network.URI (URI (.. ), parseRelativeReference )
46
+ import Network.HTTP.Types.URI (Query , parseQuery , parseQueryText , renderQuery )
47
+ import Network.URI (URI (.. ), parseRelativeReference , parseURI )
48
48
import Network.Wai
49
49
( pathInfo ,
50
50
rawQueryString ,
@@ -549,7 +549,7 @@ matchFlatfile ::
549
549
FlatfileRule ->
550
550
Request ->
551
551
m (Maybe RedirectOrProxy )
552
- matchFlatfile FlatfileRule {.. } request@ Request {requestPath, requestQueryString} = do
552
+ matchFlatfile FlatfileRule {.. } request@ Request {requestWai, requestPath, requestQueryString} = do
553
553
let -- If the public facing template captures query parameters we include the query string
554
554
-- in the path to match, else we just check on the path.
555
555
pathToMatch =
@@ -563,7 +563,8 @@ matchFlatfile FlatfileRule {..} request@Request {requestPath, requestQueryString
563
563
let extracted = HashMap. fromList xs,
564
564
HashSet. isSubsetOf ruleExpectedVariables (HashMap. keysSet extracted),
565
565
Just ! expanded <- URLTemplate. expand (`HashMap.lookup` extracted) ruleBackendTemplate -> do
566
- let ! absoluteUrl = Text. encodeUtf8 expanded
566
+ -- Rewrite the query string, if necessary
567
+ let ! absoluteUrl = rewriteQueryString expanded requestWai
567
568
pure
568
569
( Just
569
570
( RedirectTo
@@ -600,7 +601,8 @@ matchFlatfile FlatfileRule {..} request@Request {requestPath, requestQueryString
600
601
Just ! ruleBackendExpanded <- URLTemplate. expand (\ _ -> Nothing ) ruleBackendTemplate,
601
602
-- check that the publicPathRule matches our request including queryStrings if they are present
602
603
pathAndQueryValidation rulePublicTemplate request -> do
603
- let ! absoluteUrl = Text. encodeUtf8 ruleBackendExpanded
604
+ -- Rewrite the query string, if necessary
605
+ let ! absoluteUrl = rewriteQueryString ruleBackendExpanded requestWai
604
606
pure
605
607
( Just
606
608
( RedirectTo
@@ -630,15 +632,65 @@ matchFlatfile FlatfileRule {..} request@Request {requestPath, requestQueryString
630
632
_ ->
631
633
pure Nothing
632
634
635
+ -- | It's a requirement to forward all query parameters to the redirect location.
636
+ -- This function merges the query strings from the incoming request and the rendered
637
+ -- redirect location.
638
+ rewriteQueryString ::
639
+ -- | Absolute URL to redirect to
640
+ Text ->
641
+ -- | Incoming request
642
+ Wai. Request ->
643
+ ByteString
644
+ rewriteQueryString redirectTarget request
645
+ -- Fast path: In case there are no query parameters on the request
646
+ -- there is no need for rewriting.
647
+ | [] <- Wai. queryString request =
648
+ Text. encodeUtf8 redirectTarget
649
+ -- Parse the redirect target as a URI to extract, combine and replace
650
+ -- the query part of it.
651
+ -- TODO: there are an aweful lot of string conversions going on,
652
+ -- maybe there's a more direct way.
653
+ | Just uri@ URI {uriQuery} <- parseURI (Text. unpack redirectTarget) =
654
+ let redirectTargetQuery =
655
+ parseQuery (Text. encodeUtf8 (Text. pack uriQuery))
656
+
657
+ requestQuery =
658
+ Wai. queryString request
659
+
660
+ -- This is tricky: In case the redirect target already has query
661
+ -- parameters there is a chance that when combining the queries that
662
+ -- we have duplicate query parameters. Duplicates means they will
663
+ -- be counted twice in our main application. But if we nub them, we might
664
+ -- lose duplicate query parametes that were expected to occurr multiple
665
+ -- times -- after all, query strings may contain more than one occurrence of
666
+ -- a variable.
667
+ combinedQuery =
668
+ redirectTargetQuery <> requestQuery
669
+
670
+ renderedQuery =
671
+ Text. decodeUtf8
672
+ ( renderQuery
673
+ True -- add a leading '?'
674
+ combinedQuery
675
+ )
676
+ in Text. encodeUtf8 $
677
+ Text. pack $
678
+ show (uri {uriQuery = Text. unpack renderedQuery})
679
+ -- In case the redirectTarget didn't parse as a URI we are not doing anything
680
+ -- and ideally shouldn't happen.
681
+ | otherwise =
682
+ Text. encodeUtf8 redirectTarget
683
+
633
684
matchFileRuleV2 :: (MonadMatch m ) => FileRuleV2 -> Request -> m (Maybe RedirectOrProxy )
634
- matchFileRuleV2 FileRuleV2 {.. } Request {requestPath} =
685
+ matchFileRuleV2 FileRuleV2 {.. } Request {requestWai, requestPath} =
635
686
case Regex. match ruleIncomingPathRegex requestPath of
636
687
Just xs
637
688
| let extracted = HashMap. fromList xs,
638
689
HashSet. isSubsetOf ruleExpectedVariables (HashMap. keysSet extracted),
639
690
Just ruleBackendTemplate <- ruleBackendTemplate,
640
691
Just ! expanded <- URLTemplate. expand (`HashMap.lookup` extracted) ruleBackendTemplate -> do
641
- let ! absoluteUrl = Text. encodeUtf8 expanded
692
+ -- Rewrite the query string, if necessary
693
+ let ! absoluteUrl = rewriteQueryString expanded requestWai
642
694
pure
643
695
( Just
644
696
( RedirectTo
@@ -658,7 +710,8 @@ matchFileRuleV2 FileRuleV2 {..} Request {requestPath} =
658
710
| HashSet. null ruleExpectedVariables,
659
711
Just ruleBackendTemplate <- ruleBackendTemplate,
660
712
Just ! ruleBackendExpanded <- URLTemplate. expand (\ _ -> Nothing ) ruleBackendTemplate -> do
661
- let ! absoluteUrl = Text. encodeUtf8 ruleBackendExpanded
713
+ -- Rewrite the query string, if necessary
714
+ let ! absoluteUrl = rewriteQueryString ruleBackendExpanded requestWai
662
715
pure
663
716
( Just
664
717
( RedirectTo
0 commit comments