packages feed

validity-network-uri (empty) → 0.0.0.0

raw patch · 2 files changed

+406/−0 lines, 2 filesdep +basedep +network-uridep +validity

Dependencies added: base, network-uri, validity

Files

+ src/Data/Validity/URI.hs view
@@ -0,0 +1,372 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -Wno-orphans -Wno-duplicate-exports #-}++-- | 'Validity' instances for 'URI' and 'URIAuth'+--+-- The main API of this module is in the orphan instances @Validity URI@ and @Validity URIAuth@.+--+-- [RFC 3986, section 3](https://datatracker.ietf.org/doc/html/rfc3986#section-3)+module Data.Validity.URI+  ( -- ** Helper functions+    dangerousURIToString,+    dangerousDetailedURIToString,++    -- * Export everything for testing, **You probably do not want to use any of the functions below**.+    module Data.Validity.URI,+  )+where++import Data.Char as Char+import Data.List (intercalate)+import Data.Validity+import Network.URI++instance Validity URIAuth where+  validate ua@URIAuth {..} =+    mconcat+      [ genericValidate ua,+        validateUserInfo uriUserInfo,+        validateHost uriRegName,+        validatePort uriPort+      ]++instance Validity URI where+  validate u@URI {..} =+    mconcat+      [ genericValidate u,+        let rendered = dangerousURIToString u+            parsed = parseURIReference rendered+            explanation =+              unlines+                [ "Roundtrips through a parse",+                  "rendered:",+                  rendered,+                  "parsed:",+                  show $ dangerousURIToString <$> parsed+                ]+         in declare explanation $+              case parsed of+                Nothing -> False+                Just u' -> u' == u,+        validateScheme uriScheme,+        validatePath uriPath,+        validateQuery uriQuery,+        validateFragment uriFragment+      ]++-- | Validate the 'uriScheme' part of an URI+--+-- NOTE: Watch out with using this validation separately, it may not reject enough.+--+-- [RFC 3986, section 3.1](https://datatracker.ietf.org/doc/html/rfc3986#section-3.1)+-- @+-- scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )+-- @+validateScheme :: String -> Validation+validateScheme uriScheme =+  mconcat+    [ declare (unwords ["The scheme", show uriScheme, "is empty or ends in ':'"]) $+        -- Laziness prevents the partial 'last' from blowing up.+        null uriScheme || last uriScheme == ':',+      case uriScheme of+        [] -> valid+        [c] -> declare "The first character is ALPHA" (charIsALPHA c)+        (c : rest) ->+          mconcat+            [ declare "The first character is ALPHA" (charIsALPHA c),+              decorateString+                -- 'init' is safe because of case match above+                (init rest)+                validateSchemeChar+            ]+    ]++-- [RFC 3986, section 3.1](https://datatracker.ietf.org/doc/html/rfc3986#section-3.1)+-- @+-- scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )+-- @+validateSchemeChar :: Char -> Validation+validateSchemeChar c =+  declare "The character is alphanumeric, '+', '-' or '.'." $+    case c of+      '+' -> True+      '-' -> True+      '.' -> True+      _ -> charIsALPHA c || charIsDIGIT c++-- | Validate the 'uriUserInfo' part of an URI+--+-- NOTE: Watch out with using this validation separately, it may not reject enough.+--+-- [RFC 3986, section 3.2.1](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1)+--+-- @+-- userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )+-- @+validateUserInfo :: String -> Validation+validateUserInfo uriUserInfo =+  mconcat+    [ declare "The user info is empty or ends in @" $+        -- Laziness prevents the partial 'last' from blowing up.+        null uriUserInfo || last uriUserInfo == '@',+      case uriUserInfo of+        [] -> valid+        _ ->+          decorateString+            -- init is safe because of the case above+            (init uriUserInfo)+            validateUserInfoChar+    ]++-- [RFC 3986, section 3.2.1](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1)+--+-- @+-- userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )+-- @+validateUserInfoChar :: Char -> Validation+validateUserInfoChar c =+  declare "The character is unreserved, part of a percent-encoding, a sub-delimiter, or ':'" $+    case c of+      ':' -> True+      _ ->+        charIsUnreserved c+          ||+          -- NOTE:+          -- Technically this is not good enough, because incorrectly-percent-encoded values should be disallowed.+          -- However, this is good enough because we do the extra parsing elsewhere+          charIsPossiblyPartOfPercentEncoding c+          || charIsSubDelim c++-- | Validate the 'uriRegName' part of an URI+--+-- NOTE: Watch out with using this validation separately, it may not reject enough.+--+-- [RFC 3986, section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2)+-- @+-- host        = IP-literal / IPv4address / reg-name+-- @+--+-- @+-- IP-literal = "[" ( IPv6address / IPvFuture  ) "]"+--+-- IPvFuture  = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )+-- @+--+-- @+-- reg-name    = *( unreserved / pct-encoded / sub-delims )+-- @+validateHost :: String -> Validation+validateHost s =+  declare "The host looks like an IP literal, an IPv4 Address, or a reg-name" $+    stringIsIPLiteral s+      || isIPv4address s+      || stringIsRegName s++stringIsIPLiteral :: String -> Bool+stringIsIPLiteral =+  -- NOTE this is technically not good enough but it is made up for in other parts of the validation.+  const True++stringIsRegName :: String -> Bool+stringIsRegName = all isRegNameChar++isRegNameChar :: Char -> Bool+isRegNameChar c =+  charIsUnreserved c+    ||+    -- NOTE:+    -- Technically this is not good enough, because incorrectly-percent-encoded values should be disallowed.+    -- However, this is good enough because we do the extra parsing elsewhere+    charIsPossiblyPartOfPercentEncoding c+    || charIsSubDelim c++-- | Validate the 'uriPort' part of an URI+--+-- NOTE: Watch out with using this validation separately, it may not reject enough.+--+-- [RFC 3986, section 3.2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3)+-- @+-- port        = *DIGIT+-- @+--+-- NOTE: The spec does not specify a maximum length of ports so we do not validate this either.+validatePort :: String -> Validation+validatePort uriPort =+  mconcat+    [ case uriPort of+        [] -> valid+        [':'] -> invalid "The port must not just be a ':'"+        (':' : rest) -> decorateString rest validatePortChar+        _ -> invalid "The port is empty or starts with ':'"+    ]++-- [RFC 3986, section 3.2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3)+-- @+-- port        = *DIGIT+-- @+validatePortChar :: Char -> Validation+validatePortChar c = declare "The character is a digit" $ charIsDIGIT c++-- [RFC 3986, section 2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2)+-- @+-- unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"+-- @+charIsUnreserved :: Char -> Bool+charIsUnreserved = \case+  '+' -> True+  '-' -> True+  '.' -> True+  '~' -> True+  c -> charIsALPHA c || charIsDIGIT c++-- [RFC 3986, section 2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2)+-- @+-- reserved      = gen-delims / sub-delims+-- @+charIsReserved :: Char -> Bool+charIsReserved c = charIsGenDelim c || charIsSubDelim c++-- [RFC 3986, section 2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2)+-- @+-- gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"+-- @+charIsGenDelim :: Char -> Bool+charIsGenDelim = \case+  ':' -> True+  '/' -> True+  '?' -> True+  '#' -> True+  '[' -> True+  ']' -> True+  '@' -> True+  _ -> False++-- [RFC 3986, section 2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2)+-- @+-- sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"+--             / "*" / "+" / "," / ";" / "="+-- @+charIsSubDelim :: Char -> Bool+charIsSubDelim = \case+  '!' -> True+  '$' -> True+  '&' -> True+  '\'' -> True+  '(' -> True+  ')' -> True+  '*' -> True+  '+' -> True+  ',' -> True+  ';' -> True+  '=' -> True+  _ -> False++-- [RFC 3986, section 2.1](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)+-- @+-- pct-encoded = "%" HEXDIG HEXDIG+-- @+charIsPossiblyPartOfPercentEncoding :: Char -> Bool+charIsPossiblyPartOfPercentEncoding = \case+  '%' -> True+  c -> charIsHEXDIG c++-- [RFC 3986, section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3)+-- @+-- ALPHA (%41-%5A and %61-%7A)+-- @+charIsALPHA :: Char -> Bool+charIsALPHA c =+  let o = Char.ord c+   in (0x41 <= o && o <= 0x5A)+        || (0x61 <= o && o <= 0x7A)++-- [RFC 3986, section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3)+-- @+-- The uppercase hexadecimal digits 'A' through 'F' are equivalent to+-- the lowercase digits 'a' through 'f', respectively.  If two URIs+-- differ only in the case of hexadecimal digits used in percent-encoded+-- octets, they are equivalent.+-- @+charIsHEXDIG :: Char -> Bool+charIsHEXDIG c =+  charIsDIGIT c+    || ('A' <= c && c <= 'F')+    || ('a' <= c && c <= 'f')++--  [RFC 3986, section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3)+-- @+-- DIGIT (%30-%39)+-- @+charIsDIGIT :: Char -> Bool+charIsDIGIT c =+  let o = Char.ord c+   in (0x30 <= o && o <= 0x39)++-- | Validate the 'uriPath' part of an URI+--+-- NOTE: Watch out with using this validation separately, it may not reject enough.+validatePath :: String -> Validation+validatePath _uriPath =+  mconcat+    [ -- We could check the following, but it actually does not hold, see also:+      -- https://github.com/haskell/network-uri/issues/76+      --+      -- declare (unwords ["The path", show uriPath, "is empty or starts with '/'"]) $+      --   -- Laziness prevents the partial 'head' from blowing up.+      --   null uriPath || head uriPath == '/',++      valid+    ]++-- | Validate the 'uriQuery' part of an URI+--+-- NOTE: Watch out with using this validation separately, it may not reject enough.+validateQuery :: String -> Validation+validateQuery uriQuery =+  declare (unwords ["The query", show uriQuery, "is empty or starts with '?'"]) $+    -- Laziness prevents the partial 'head' from blowing up.+    null uriQuery || head uriQuery == '?'++validateFragment :: String -> Validation+validateFragment uriFragment =+  declare (unwords ["The fragment", show uriFragment, "is empty or starts with '#'"]) $+    -- Laziness prevents the partial 'head' from blowing up.+    null uriFragment || head uriFragment == '#'++-- | Render a URI to a 'String', for use in testing+--+-- This uses @uriToString id@ as the docs specify.+-- It potentially exposes passwords, so only use it if you know what you're+-- doing.+dangerousURIToString :: URI -> String+dangerousURIToString u = uriToString id u ""++-- | Render a URI to a 'String', for use in debugging.+--+-- This uses custom string juggling.+-- It's what happened when you don't derive show but instead write your own+-- 'show' function. :(+-- It potentially exposes passwords, so only use it if you know what you're+-- doing.+dangerousDetailedURIToString :: URI -> String+dangerousDetailedURIToString URI {..} =+  unlines+    [ "URI {",+      concat ["  uriScheme = ", show uriScheme, ","],+      case uriAuthority of+        Nothing -> "  uriAuthority = Nothing,"+        Just URIAuth {..} ->+          intercalate+            "\n"+            [ "  uriAuthority = Just URIAuth {",+              concat ["    uriUserInfo = ", show uriUserInfo],+              concat ["    uriRegName = ", show uriRegName],+              concat ["    uriPort = ", show uriPort],+              "  }"+            ],+      concat ["  uriPath = ", show uriPath, ","],+      concat ["  uriQuery = ", show uriQuery, ","],+      concat ["  uriFragment = ", show uriFragment],+      "}"+    ]
+ validity-network-uri.cabal view
@@ -0,0 +1,34 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.7.+--+-- see: https://github.com/sol/hpack++name:           validity-network-uri+version:        0.0.0.0+synopsis:       Validity instances for URI+category:       Validity+homepage:       https://github.com/NorfairKing/validity#readme+bug-reports:    https://github.com/NorfairKing/validity/issues+author:         Tom Sydney Kerckhove+maintainer:     syd@cs-syd.eu+copyright:      Copyright: (c) 2022 Tom Sydney Kerckhove+license:        MIT+build-type:     Simple++source-repository head+  type: git+  location: https://github.com/NorfairKing/validity++library+  exposed-modules:+      Data.Validity.URI+  other-modules:+      Paths_validity_network_uri+  hs-source-dirs:+      src+  build-depends:+      base >=4.7 && <5+    , network-uri+    , validity >=0.12+  default-language: Haskell2010