1 ! Copyright (C) 2008 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.sockets.secure io.encodings.string
6 io.encodings.utf8 math math.parser accessors parser
7 strings.parser lexer prettyprint.backend hashtables present ;
10 : url-quotable? ( ch -- ? )
11 #! In a URL, can this character be used without
24 [ CHAR: % , >hex 2 CHAR: 0 pad-left % ] each ;
28 : url-encode ( str -- str )
30 [ dup url-quotable? [ , ] [ push-utf8 ] if ] each
35 : url-decode-hex ( index str -- )
39 [ 1+ dup 2 + ] dip subseq hex> [ , ] when*
42 : url-decode-% ( index str -- index str )
43 2dup url-decode-hex [ 3 + ] dip ;
45 : url-decode-+-or-other ( index str ch -- index str )
46 dup CHAR: + = [ drop CHAR: \s ] when , [ 1+ ] dip ;
48 : url-decode-iter ( index str -- )
52 2dup nth dup CHAR: % = [
61 : url-decode ( str -- str )
62 [ 0 swap url-decode-iter ] "" make utf8 decode ;
66 : add-query-param ( value key assoc -- )
70 { [ dup string? ] [ swap 2array ] }
71 { [ dup array? ] [ swap suffix ] }
72 { [ dup not ] [ drop ] }
79 : query>assoc ( query -- assoc )
81 "&" split H{ } clone [
83 [ "=" split1 [ dup [ url-decode ] when ] bi@ swap ] dip
89 : assoc>query ( hash -- str )
91 dup array? [ [ present ] map ] [ present 1array ] if
96 [ url-encode "=" swap 3append , ] with each
100 TUPLE: url protocol username password host port path query anchor ;
102 : <url> ( -- url ) url new ;
104 : query-param ( url key -- value )
107 : set-query-param ( url value key -- url )
108 '[ [ _ _ ] dip ?set-at ] change-query ;
110 : parse-host ( string -- host port )
111 ":" split1 [ url-decode ] [
114 dup [ "Invalid port" throw ] unless
120 : parse-host-part ( url protocol rest -- url string' )
122 "//" ?head [ "Invalid URL" throw ] unless
125 ":" split1 [ >>username ] [ >>password ] bi*
129 parse-host [ >>host ] [ >>port ] bi*
130 ] [ "/" prepend ] bi*
135 GENERIC: >url ( obj -- url )
137 M: f >url drop <url> ;
143 ":" split1 [ parse-host-part ] when*
146 [ url-decode >>path ]
147 [ [ query>assoc >>query ] when* ] bi*
149 [ url-decode >>anchor ] bi* ;
153 : unparse-username-password ( url -- )
155 % password>> [ ":" % % ] when* "@" %
158 : unparse-host-part ( url protocol -- )
162 [ unparse-username-password ]
163 [ host>> url-encode % ]
164 [ port>> [ ":" % # ] when* ]
165 [ path>> "/" head? [ "/" % ] unless ]
171 [ dup protocol>> dup [ unparse-host-part ] [ 2drop ] if ]
172 [ path>> url-encode % ]
173 [ query>> dup assoc-empty? [ drop ] [ "?" % assoc>query % ] if ]
174 [ anchor>> [ "#" % present url-encode % ] when* ]
178 : url-append-path ( path1 path2 -- path )
180 { [ dup "/" head? ] [ nip ] }
181 { [ dup empty? ] [ drop ] }
182 { [ over "/" tail? ] [ append ] }
183 { [ "/" pick start not ] [ nip ] }
184 [ [ "/" last-split1 drop "/" ] dip 3append ]
189 : derive-url ( base url -- url' )
191 [ [ protocol>> ] either? >>protocol ]
192 [ [ username>> ] either? >>username ]
193 [ [ password>> ] either? >>password ]
194 [ [ host>> ] either? >>host ]
195 [ [ port>> ] either? >>port ]
196 [ [ path>> ] bi@ swap url-append-path >>path ]
197 [ [ query>> ] either? >>query ]
198 [ [ anchor>> ] either? >>anchor ]
201 : relative-url ( url -- url' )
207 ! Half-baked stuff follows
208 : secure-protocol? ( protocol -- ? )
211 : url-addr ( url -- addr )
212 [ [ host>> ] [ port>> ] bi <inet> ] [ protocol>> ] bi
213 secure-protocol? [ <secure> ] when ;
215 : protocol-port ( protocol -- port )
222 : ensure-port ( url -- url' )
223 dup protocol>> '[ _ protocol-port or ] change-port ;
226 : URL" lexer get skip-blank parse-string >url parsed ; parsing
228 M: url pprint* dup present "URL\" " "\"" pprint-string ;