1 -- This file is part of Diohsc
2 -- Copyright (C) 2020 Martin Bays <mbays@sdf.org>
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of version 3 of the GNU General Public License as
6 -- published by the Free Software Foundation, or any later version.
8 -- You should have received a copy of the GNU General Public License
9 -- along with this program. If not, see http://www.gnu.org/licenses/.
11 {-# LANGUAGE OverloadedStrings #-}
41 import Control
.Monad
(mplus
, (<=<))
42 import Data
.Char (toLower)
43 import Data
.List
(dropWhileEnd
)
44 import Data
.Maybe (isNothing)
47 import qualified Network
.URI
as NU
49 defaultScheme
:: String
50 defaultScheme
= "gemini"
55 -- | Represents a normalised absolute URI with scheme and port defaults as above.
56 -- We use "Uri" rather than "URI" in camelcase,
57 -- because I prefer to think of it as a word rather than an acronym.
58 -- Still use "URI" if it's the first/only word of the identifier.
59 newtype URI
= URI
{uriUri
:: NU
.URI
}
61 instance Show URI
where
62 show (URI uri
) = show uri
64 uriPath
, uriQuery
, uriFragment
:: URI
-> String
65 uriPath
= NU
.uriPath
. uriUri
66 uriQuery
= NU
.uriQuery
. uriUri
67 uriFragment
= NU
.uriFragment
. uriUri
69 -- | strips trailing ':'
70 uriScheme
:: URI
-> String
71 uriScheme
= init . NU
.uriScheme
. uriUri
73 setSchemeDefault
:: URI
-> URI
74 setSchemeDefault
= URI
. (\nuri
-> nuri
{ NU
.uriScheme
= defaultScheme
<> ":" }) . uriUri
76 pathSegments
:: URI
-> [String]
77 pathSegments
(URI uri
) = NU
.pathSegments uri
80 nullUri
= URI NU
.nullURI
82 -- | URI reference. May be absolute. Not normalised.
83 newtype URIRef
= URIRef NU
.URI
85 instance Show URIRef
where
86 show (URIRef uri
) = show uri
88 normaliseUri
:: NU
.URI
-> URI
89 normaliseUri uri
= URI
$ uri
90 { NU
.uriPath
= (\p
-> if null p
then "/" else p
) .
91 NU
.normalizePathSegments
. NU
.normalizeEscape
$ NU
.uriPath uri
92 , NU
.uriScheme
= if null $ NU
.uriScheme uri
93 then defaultScheme
else toLower <$> NU
.uriScheme uri
94 , NU
.uriAuthority
= (\auth
-> auth
96 if NU
.uriPort auth
== ':' : show defaultPort
97 then "" else NU
.uriPort auth
98 , NU
.uriRegName
= toLower <$> NU
.uriRegName auth
100 <$> NU
.uriAuthority uri
101 , NU
.uriQuery
= NU
.normalizeEscape
$ NU
.uriQuery uri
104 stripUriForGemini
:: URI
-> URI
105 stripUriForGemini
(URI uri
) = URI
$ uri
106 { NU
.uriAuthority
= (\auth
-> auth
{NU
.uriUserInfo
= ""}) <$> NU
.uriAuthority uri
107 , NU
.uriFragment
= ""
110 parseAbsoluteUri
:: String -> Maybe URI
111 parseAbsoluteUri
= (normaliseUri
<$>) . NU
.parseURI
113 parseUriAsAbsolute
:: String -> Maybe URI
114 parseUriAsAbsolute s
= parseAbsoluteUri s `mplus` parseAbsoluteUri
(defaultScheme
++ "://" ++ s
)
116 parseUriReference
:: String -> Maybe URIRef
117 parseUriReference
= (URIRef
<$>) . NU
.parseURIReference
120 setQuery
:: String -> URI
-> URI
121 setQuery q
(URI uri
) = URI
$ uri
{ NU
.uriQuery
= q
}
123 stripUri
:: URI
-> URI
124 stripUri
(URI uri
) = URI
$ uri
{ NU
.uriPath
= dropWhileEnd
(== '/') $ NU
.uriPath uri
, NU
.uriQuery
= "" }
126 relativeTo
:: URIRef
-> URI
-> URI
127 relativeTo
(URIRef ref
) (URI uri
) = normaliseUri
$ NU
.relativeTo ref uri
129 -- | lift NU.relativeFrom, but set scheme when the result is absolute,
130 -- and avoid initial slash where possible, and prefer "." to "" and ".." to "../"
131 relativeFrom
:: URI
-> URI
-> URIRef
132 relativeFrom
(URI uri1
) (URI uri2
) =
133 URIRef
. fixDots
. stripSlash
. setScheme
$ NU
.relativeFrom uri1 uri2
where
134 setScheme ref |
isNothing (NU
.uriAuthority ref
) = ref
135 |
otherwise = ref
{ NU
.uriScheme
= NU
.uriScheme uri1
}
136 stripSlash ref |
'/':path
' <- NU
.uriPath ref
138 , ref
' <- ref
{ NU
.uriPath
= path
' }
139 , NU
.relativeTo ref
' uri2
== uri1
= ref
'
141 fixDots ref
= case NU
.uriPath ref
of
142 "" | ref
' <- ref
{ NU
.uriPath
= "." }
143 , NU
.relativeTo ref
' uri2
== uri1
-> ref
'
144 "../" -> ref
{ NU
.uriPath
= ".." }
147 uriRegName
:: URI
-> Maybe String
148 uriRegName
= (NU
.uriRegName
<$>) . NU
.uriAuthority
. uriUri
150 uriPort
:: URI
-> Maybe Int
151 uriPort
= (readPort
. NU
.uriPort
) <=< (NU
.uriAuthority
. uriUri
)
153 readPort
(':':n
) = readMay n
156 escapePathString
:: String -> String
157 escapePathString
= NU
.escapeURIString
(\c
-> NU
.isUnreserved c || c
== '/')
159 unescapeUriString
:: String -> String
160 unescapeUriString
= NU
.unEscapeString
162 -- | unreserved / sub-delims / ":" / "@" / "/" / "?"
163 isUnescapedInQuery
:: Char -> Bool
164 isUnescapedInQuery c
= NU
.isUnescapedInURI c
&& c `
notElem`
("#[]"::String)
166 escapeQuery
:: String -> String
167 escapeQuery
= NU
.escapeURIString isUnescapedInQuery
. withEscapes
170 withEscapes
('\\':'x
':h1
:h2
:s
) | Just c
<- readMay
$ "'\\x" <> [h1
,h2
,'\''] = c
:withEscapes s
171 withEscapes
('\\':'e
':s
) = '\ESC
':withEscapes s
172 withEscapes
('\\':'r
':s
) = '\r':withEscapes s
173 withEscapes
('\\':'n
':s
) = '\n':withEscapes s
174 withEscapes
('\\':'t
':s
) = '\t':withEscapes s
175 withEscapes
('\\':c
:s
) = c
:withEscapes s
176 withEscapes
(c
:s
) = c
:withEscapes s
178 -- |escape the query part of an unparsed uri string
179 escapeQueryPart
:: String -> String
181 |
(s
','?
':q
) <- break (== '?
') s
= s
' ++ '?
' : escapeQuery q
184 -- |conversion of IRI to URI according to Step 2 in Section 3.1 in RFC3987
185 -- (for now at least, we apply this also to the regname rather than
187 escapeIRI
:: String -> String
188 escapeIRI
= NU
.escapeURIString
(not . escape
)
190 -- |ucschar or iprivate in RFC3987
191 escape
:: Char -> Bool
192 escape c
= let i
= fromEnum c
in
193 i
>= 0xA0 && i
<= 0xD7FF ||
194 i
>= 0xE000 && i
<= 0xF8FF ||
195 i
>= 0xF900 && i
<= 0xFDCF ||
196 i
>= 0xFDF0 && i
<= 0xFFEF ||
197 i
>= 0x10000 && i
<= 0x1FFFD ||
198 i
>= 0x20000 && i
<= 0x2FFFD ||
199 i
>= 0x30000 && i
<= 0x3FFFD ||
200 i
>= 0x40000 && i
<= 0x4FFFD ||
201 i
>= 0x50000 && i
<= 0x5FFFD ||
202 i
>= 0x60000 && i
<= 0x6FFFD ||
203 i
>= 0x70000 && i
<= 0x7FFFD ||
204 i
>= 0x80000 && i
<= 0x8FFFD ||
205 i
>= 0x90000 && i
<= 0x9FFFD ||
206 i
>= 0xA0000 && i
<= 0xAFFFD ||
207 i
>= 0xB0000 && i
<= 0xBFFFD ||
208 i
>= 0xC0000 && i
<= 0xCFFFD ||
209 i
>= 0xD0000 && i
<= 0xDFFFD ||
210 i
>= 0xE1000 && i
<= 0xEFFFD ||
211 i
>= 0xF0000 && i
<= 0xFFFFD ||
212 i
>= 0x100000 && i
<= 0x10FFFD