git-annex-10.20260901: Git/Config/Url.hs
{- git-config http.<url>.* handling
-
- Copyright 2026 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
{-# LANGUAGE OverloadedStrings #-}
module Git.Config.Url (
getHttpConfig,
httpConfigKeys,
prop_httpConfigKeys_sane,
) where
import qualified Data.Map as M
import Network.URI
import Data.Function
import Common
import Git
import Git.Types
import Utility.Glob
{- Gets any per-url settings from the git config for a http.foo ConfigKey.
-
- If there are non, falls back to the non-url-spcecific config, if any,
- or the provided fallback value.
-
- See git-config(1)'s documentation of http.<url>.* for the details.
-}
getHttpConfig :: ConfigKey -> ConfigValue -> URI -> Repo -> ConfigValue
getHttpConfig key fallback url repo = fromMaybe fallback $
case httpConfigKeys key url (config repo) of
[] -> Nothing
(k:_) -> M.lookup k (config repo)
{- Gets any per-url config keys for a non-url-specific input
- http.foo ConfigKey that match the provided url.
-
- The list is ordered by decreasing precedance and includes the input
- ConfigKey at the end when it's part of the RepoConfig.
-}
httpConfigKeys :: ConfigKey -> URI -> RepoConfig -> [ConfigKey]
httpConfigKeys nonurlspecifickey@(ConfigKey key) urltomatch c =
let l = map fst
$ reverse $ sortBy precedence
$ mapMaybe matching (M.keys c)
in if M.member nonurlspecifickey c
then l ++ [nonurlspecifickey]
else l
where
httpprefix = "http."
keysuffix = case decodeBS key of
('h':'t':'t':'p':'.':rest) -> '.' : rest
v -> v
httpprefixlen = length httpprefix
keysuffixlen = length keysuffix
extracturlfromkey = parseURI
. reverse . drop keysuffixlen . reverse
. drop httpprefixlen
precedence (_k1, u1) (_k2, u2) =
(compare `on` (length . uriPath)) u1 u2
<> (compare `on` (uriUserInfo <$$> uriAuthority)) u1 u2
matching k@(ConfigKey ck) =
let sk = decodeBS ck
in if httpprefix `isPrefixOf` sk && keysuffix `isSuffixOf` sk && k /= nonurlspecifickey
then do
u <- extracturlfromkey sk
let same f = f u == f urltomatch
if same uriScheme
&& (same (uriRegName <$$> uriAuthority)
|| subdomainwildcardmatch u)
&& same getportordefault
&& (same uriPath
|| pathslashprefix u)
&& (same getusername
|| getusername u == Nothing)
then Just (k, u)
else Nothing
else Nothing
getportordefault u = do
a <- uriAuthority u
if null (uriPort a)
then case uriScheme u of
"http:" -> return ":80"
"https:" -> return ":443"
_ -> Nothing
else return (uriPort a)
getusername u = do
a <- uriAuthority u
let (user, _pass) = break (== ':') (uriUserInfo a)
let username = fst (break (== '@') user)
if null username
then Nothing
else return username
pathslashprefix u =
let p = if "/" `isSuffixOf` uriPath u
then uriPath u
else uriPath u ++ "/"
in p `isPrefixOf` uriPath urltomatch
subdomainwildcardmatch u =
subdomainwildcardmatch' (uridomains u) (uridomains urltomatch)
subdomainwildcardmatch' [] [] = True
subdomainwildcardmatch' [] _ = False
subdomainwildcardmatch' _ [] = False
subdomainwildcardmatch' (a:as) (b:bs)
| a == b = subdomainwildcardmatch' as bs
| otherwise =
let g = compileGlob a CaseInsensitive (GlobFilePath False)
in if matchGlob g b
then subdomainwildcardmatch' as bs
else False
uridomains u = case uriRegName <$> uriAuthority u of
Nothing -> []
Just d -> splitc '.' d
prop_httpConfigKeys_sane :: Bool
prop_httpConfigKeys_sane = and prop_httpConfigKeys_tests
prop_httpConfigKeys_tests :: [Bool]
prop_httpConfigKeys_tests =
[ httpConfigKeys (ConfigKey "http.foo") u c ==
[ "http.http://user@example.com/foo/bar.foo"
, "http.http://example.com/foo/bar.foo"
, "http.http://example.com/foo.foo"
, "http.http://example.com.foo"
, "http.foo"
]
, httpConfigKeys (ConfigKey "http.bar") u c ==
[ "http.http://*.com.bar" ]
, httpConfigKeys (ConfigKey "http.baz") u c ==
[ "http.http://example.com:80.baz" ]
, httpConfigKeys (ConfigKey "http.baz") uwithport c ==
[ "http.http://example.com:8080.baz" ]
]
where
u = fromMaybe (error "internal") $
parseURI "http://user:password@example.com/foo/bar/"
uwithport = fromMaybe (error "internal") $
parseURI "http://user:password@example.com:8080/foo/bar/"
c = M.fromList $ map (\k -> (ConfigKey k, ConfigValue "dummy value"))
[ "http.foo"
, "http.http://example.co.foo"
, "http.http://example.com.foo"
, "https.http://example.com.foo"
, "http.http://example.com/fo.foo"
, "http.http://example.com/foo.foo"
, "http.http://example.com/foo/ba.foo"
, "http.http://example.com/foo/bar.foo"
, "http.http://user@example.com/foo/bar.foo"
, "http.http://nonmatchingexample.com.foo"
, "http.http://*.bar"
, "http.http://*.com.bar"
, "http.http://example.com:80.baz"
, "http.http://example.com:443.baz"
, "http.http://example.com:8080.baz"
]