]> gitweb.factorcode.org Git - factor.git/blob - basis/urls/urls.factor
Merge branch 'master' of git://github.com/slavapestov/factor
[factor.git] / basis / urls / urls.factor
1 ! Copyright (C) 2008, 2011 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: kernel ascii combinators combinators.short-circuit
4 sequences splitting fry namespaces make assocs arrays strings
5 io.sockets io.encodings.string io.encodings.utf8 math
6 math.parser accessors parser strings.parser lexer
7 hashtables present peg.ebnf urls.encoding ;
8 IN: urls
9
10 TUPLE: url protocol username password host port path query anchor ;
11
12 : <url> ( -- url ) url new ;
13
14 : query-param ( url key -- value )
15     swap query>> at ;
16
17 : delete-query-param ( url key -- url )
18     over query>> delete-at ;
19
20 : set-query-param ( url value key -- url )
21     over [
22         '[ [ _ _ ] dip ?set-at ] change-query
23     ] [
24         nip delete-query-param
25     ] if ;
26
27 ERROR: malformed-port ;
28
29 : parse-host ( string -- host/f port/f )
30     [
31         ":" split1-last [ url-decode ]
32         [ dup [ string>number [ malformed-port ] unless* ] when ] bi*
33     ] [ f f ] if* ;
34
35 GENERIC: >url ( obj -- url )
36
37 M: f >url drop <url> ;
38
39 M: url >url ;
40
41 <PRIVATE
42
43 EBNF: parse-url
44
45 protocol = [a-z]+                   => [[ url-decode ]]
46 username = [^/:@#?]+                => [[ url-decode ]]
47 password = [^/:@#?]+                => [[ url-decode ]]
48 pathname = [^#?]+                   => [[ url-decode ]]
49 query    = [^#]+                    => [[ query>assoc ]]
50 anchor   = .+                       => [[ url-decode ]]
51
52 hostname = [^/#?]+                  => [[ url-decode ]]
53
54 hostname-spec = hostname ("/"|!(.)) => [[ first ]]
55
56 auth     = (username (":" password  => [[ second ]])? "@"
57                                     => [[ first2 2array ]])?
58
59 url      = ((protocol "://")        => [[ first ]] auth hostname)?
60            (pathname)?
61            ("?" query               => [[ second ]])?
62            ("#" anchor              => [[ second ]])?
63
64 ;EBNF
65
66 PRIVATE>
67
68 M: string >url
69     [ <url> ] dip
70     parse-url {
71         [
72             first [
73                 [ first >>protocol ]
74                 [
75                     second
76                     [ first [ first2 [ >>username ] [ >>password ] bi* ] when* ]
77                     [ second parse-host [ >>host ] [ >>port ] bi* ] bi
78                 ] bi
79             ] when*
80         ]
81         [ second >>path ]
82         [ third >>query ]
83         [ fourth >>anchor ]
84     } cleave
85     dup host>> [ [ "/" or ] change-path ] when ;
86
87 : protocol-port ( protocol -- port )
88     {
89         { "http" [ 80 ] }
90         { "https" [ 443 ] }
91         { "ftp" [ 21 ] }
92         [ drop f ]
93     } case ;
94
95 : relative-url ( url -- url' )
96     clone
97         f >>protocol
98         f >>host
99         f >>port ;
100
101 : relative-url? ( url -- ? ) protocol>> not ;
102
103 <PRIVATE
104
105 : unparse-username-password ( url -- )
106     dup username>> dup [
107         % password>> [ ":" % % ] when* "@" %
108     ] [ 2drop ] if ;
109
110 : url-port ( url -- port/f )
111     [ port>> ] [ port>> ] [ protocol>> protocol-port ] tri =
112     [ drop f ] when ;
113
114 : unparse-host-part ( url protocol -- )
115     %
116     "://" %
117     {
118         [ unparse-username-password ]
119         [ host>> url-encode % ]
120         [ url-port [ ":" % # ] when* ]
121         [ path>> "/" head? [ "/" % ] unless ]
122     } cleave ;
123
124 M: url present
125     [
126         {
127             [ dup protocol>> dup [ unparse-host-part ] [ 2drop ] if ]
128             [ path>> url-encode % ]
129             [ query>> dup assoc-empty? [ drop ] [ "?" % assoc>query % ] if ]
130             [ anchor>> [ "#" % present url-encode % ] when* ]
131         } cleave
132     ] "" make ;
133
134 PRIVATE>
135
136 : url-append-path ( path1 path2 -- path )
137     {
138         { [ dup "/" head? ] [ nip ] }
139         { [ dup empty? ] [ drop ] }
140         { [ over "/" tail? ] [ append ] }
141         { [ "/" pick start not ] [ nip ] }
142         [ [ "/" split1-last drop "/" ] dip 3append ]
143     } cond ;
144
145 <PRIVATE
146
147 : derive-port ( url base -- url' )
148     over relative-url? [ [ port>> ] either? ] [ drop port>> ] if ;
149
150 : derive-path ( url base -- url' )
151     [ path>> ] bi@ swap url-append-path ;
152
153 PRIVATE>
154
155 : derive-url ( base url -- url' )
156     [ clone ] dip over {
157         [ [ protocol>>  ] either? >>protocol ]
158         [ [ username>>  ] either? >>username ]
159         [ [ password>>  ] either? >>password ]
160         [ [ host>>      ] either? >>host ]
161         [ derive-port             >>port ]
162         [ derive-path             >>path ]
163         [ [ query>>     ] either? >>query ]
164         [ [ anchor>>    ] either? >>anchor ]
165     } 2cleave ;
166
167 ! Half-baked stuff follows
168 : secure-protocol? ( protocol -- ? )
169     "https" = ;
170
171 <PRIVATE
172
173 GENERIC: >secure-addr ( addrspec -- addrspec' )
174
175 PRIVATE>
176
177 : url-addr ( url -- addr )
178     [
179         [ host>> ]
180         [ port>> ]
181         [ protocol>> protocol-port ]
182         tri or <inet>
183     ] [ protocol>> ] bi
184     secure-protocol? [ >secure-addr ] when ;
185
186 : set-url-addr ( url addr -- url )
187     [ host>> >>host ] [ port>> >>port ] bi ;
188
189 : ensure-port ( url -- url' )
190     clone dup protocol>> '[ _ protocol-port or ] change-port ;
191
192 ! Literal syntax
193 SYNTAX: URL" lexer get skip-blank parse-string >url suffix! ;
194
195 USE: vocabs.loader
196
197 { "urls" "prettyprint" } "urls.prettyprint" require-when
198 { "urls" "io.sockets.secure" } "urls.secure" require-when