1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ;; Bravais' Edito Princeps: EBook Tool Suite
3 ;; Copyright (C) 2007 Raymond Paul Racine
5 ;; This program is free software: you can redistribute it and/or modify
6 ;; it under the terms of the GNU General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;; GNU General Public License for more details.
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
17 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20 (rl3 web uri char-sets)
24 digit-char? hex-char? pchar? pct-encoded-char?
25 scheme-start-ch? scheme-tail-ch? sub-delim-char? unreserved-char?
31 char-ci>=? char-ci<=? char-downcase))
35 (string-append "%" (number->string (char->integer ch) 16))))
39 (or (unsafe-char? ch))))
47 (define alphabet-char?
51 (char-ci<=? ch #\z))))
57 (case (char-downcase ch)
58 ((#\a #\b #\c #\d #\e #\f) #t)
61 (define unreserved-char?
67 ((#\. #\_ #\~ #\\ #\-) #t)
73 (general-delim-char? ch)
74 (sub-delim-char? ch))))
80 ((#\{ #\} #\| #\\ #\^ #\~ #\[ #\] #\`)
84 (define general-delim-char?
87 ((#\: #\/ #\? #\# #\[ #\] #\@) #t)
90 (define sub-delim-char?
93 ((#\! #\$ #\& #\' #\( #\) #\* #\+ #\, #\; #\=) #t)
96 (define pct-encoded-char?
105 (unreserved-char? ch)
106 (pct-encoded-char? ch)
112 (define scheme-start-ch? alphabet-char?)
114 (define scheme-tail-ch?
117 (scheme-start-ch? ch)
131 parse-uri parse-authority uri->string
133 make-authority authority? authority->string authority-equal?
134 authority-username authority-host authority-port
137 uri-scheme uri-authority uri-path uri-query uri-fragment)
143 (only (rnrs io simple)
145 eof-object? peek-char read-char write-char)
146 (only (rnrs io ports)
148 open-string-input-port)
149 (primitives open-output-string get-output-string)
150 (err5rs records syntactic)
151 (only (larceny records printer)
154 (only (rl3 web uri char-sets)
155 hex-char? digit-char?
163 (define url-decode-string
165 (let ((op (open-output-string))
166 (ip (open-string-input-port str)))
167 (let loop ((ch (get-char ip)))
169 (get-output-string op)
171 (let ((ch1 (get-char ip)))
172 (if (and (not (eof-object? ch1))
174 (let ((ch2 (get-char ip)))
175 (if (and (not (eof-object? ch2))
178 (put-char op (integer->char (string->number (list->string (list ch1 ch2)) 16))) ;; use (let ((buff (make-string 2))) ???
179 (loop (get-char ip)))
180 (begin ;; got %d? so write '%' and digit and carryon
183 (unless (eof-object? ch2)
185 (loop (get-char ip)))))
186 (begin ;; got %? so write them and carryon
188 (unless (eof-object? ch1)
190 (loop (get-char ip)))))
192 (unless (eof-object? ch)
194 (loop (get-char ip)))))))))
197 (define-record-type authority #t #t username host port)
199 ;;all strings or #f, each string is a major piece of the uri.
200 (define-record-type uri ctor-uri #t scheme authority path query fragment)
205 (zero? (string-length s))
209 (lambda (scheme user host port path query fragment)
222 (let ((sport (if (number? port)
223 (number->string port)
225 (let ((authority (authority->string (make-authority (if (null-string? user)
227 host (if (null-string? sport) #f sport)))))
228 (if (null-string? scheme)
230 (ctor-uri scheme authority
231 (if (null-string? path)
234 (if (null-string? query)
237 (if (null-string? fragment)
242 (lambda (field prefix)
243 (if field (string-append prefix field) "")))
250 (let ((auth (uri-authority uri)))
252 (string-append "//" auth)
255 (maybe (uri-query uri) "?")
256 (maybe (uri-fragment uri) "#"))))
258 (define authority->string
261 (let ((user (authority-username authority)))
263 (string-append user "@")
265 (authority-host authority)
266 (let ((port (authority-port authority)))
268 (string-append ":" (number->string port))
271 ;; Two authororities are equal if they're record values are equal.
272 (define authority-equal?
273 (lambda (auth1 auth2)
274 (and (equal? (authority-username auth1)
275 (authority-username auth2))
276 (equal? (authority-host auth1)
277 (authority-host auth2))
278 (eqv? (authority-port auth1)
279 (authority-port auth2)))))
281 ;; Read chars while valid or eof-object?
282 ;; Place valid chars in output string port
283 ;; First invalid char is left on the input port
284 ;; returns: number of valid chars read from input port.
287 (lambda (ip valid? op)
288 (let loop ((ch (peek-char ip)) (cnt 0))
289 (if (or (eof-object? ch)
293 (write-char (read-char ip) op)
294 (loop (peek-char ip) (fx1+ cnt)))))))
296 ;; (input-port? output-port?) -> boolean?)
297 ;; parse the "tail" of a scheme
298 ;; i.e., the rest of the scheme string given that
299 ;; the start char of the scheme was valid.
300 ;; returns: # of chars read
301 (define parse-scheme-tail
303 (read-valid ip scheme-tail-ch? op)))
307 (let ((op (open-output-string)))
308 (let ((ch (peek-char ip)))
309 (if (not (scheme-start-ch? ch))
312 (write-char (read-char ip) op)
313 (parse-scheme-tail ip op)
314 (get-output-string op)))))))
317 ;; lex a character of value chtok
318 ;; returns: #f if the next character is not a chtok
319 (define (parse-char ip chtok)
320 (let ((ch (peek-char ip)))
329 (define parse-authority-opaque
331 (let ((op (open-output-string)))
332 (read-valid ip (lambda (ch)
337 (get-output-string op))))
339 (define parse-path-abempty
341 (let ((op (open-output-string)))
342 (let ((ch (peek-char ip)))
343 (if (or (eof-object? ch)
347 (if (not (eq? ch #\/))
348 (error "A URI with an authority can only have an absolute path.")
350 (write-char (read-char ip) op)
351 (let ((ch (peek-char ip)))
353 (error "Absolute path must have a none empty segment. i.e., // is illegal")
360 (get-output-string op)))))
362 (define parse-path-absolute
364 (let ((op (open-output-string)))
366 ;; first segment must not have a ':'
379 (get-output-string op))))
381 (define parse-path-rootless
383 (let ((op (open-output-string)))
390 (get-output-string op))))
393 ;; 1) authority or #f
397 (let ((ch (peek-char ip)))
403 (if (eq? (peek-char ip) #\/)
406 (let ((authority (parse-authority-opaque ip)))
407 (let ((path-abempty (parse-path-abempty ip)))
408 (values authority path-abempty))))
409 (values #f (parse-path-absolute ip))))
410 (values #f (parse-path-rootless ip)))))))
412 (define parse-query-or-fragment
413 (lambda (ip signal-char)
414 (let ((ch (peek-char ip)))
417 (if (eq? ch signal-char)
418 (let ((op (open-output-string)))
419 (read-char ip) ;; consume signal char
427 (get-output-string op))
432 (let ((ip (open-string-input-port uri-str)))
433 (let ((scheme (parse-scheme ip)))
435 (error "Invalid URI. manditory scheme is missing.")
436 (if (not (parse-char ip #\:))
437 (error "Invalid URI. scheme must be delimited by a ':'.")
438 (let-values (((authority path) (parse-hier ip)))
439 (let ((query (parse-query-or-fragment ip #\?)))
440 (let ((fragment (parse-query-or-fragment ip #\#)))
441 (ctor-uri scheme authority path query fragment))))))))))
443 ;; Parse out the port string.
444 ;; Assumes leading ':' has been consumed.
447 (let ((op (open-output-string)))
448 (if (or (eof-object? (peek-char ip))
449 (not (digit-char? (peek-char ip))))
450 (error "Missing port number or extraneous characters where port number was expected.")
456 (get-output-string op))))
457 (string->number port))))))
459 ;;; Parse the host and optional port from a given string
460 ;;; returns: (values host port)
461 (define parse-host-port
463 (let ((op (open-output-string)))
464 (if (eof-object? (peek-char ip))
465 (error "URI missing required host.")
470 (unreserved-char? ch)
471 (pct-encoded-char? ch)
472 (sub-delim-char? ch)))
474 (get-output-string op))))
475 (if (> (string-length host) 0)
476 (let ((ch (read-char ip)))
477 (if (eof-object? ch) ;; no port
479 (if (not (eq? ch #\:))
480 (error "Host must be optionally followed by a port. Something else found.")
481 (let ((port (parse-port ip)))
482 (values host port)))))
483 (error "Manditory host is missing.")))))))
485 (define authority-with-username?
487 (let ((ip (open-string-input-port auth)))
488 (let loop ((ch (read-char ip)))
490 ((eof-object? ch) #f)
493 (loop (read-char ip))))))))
495 (define parse-authority
497 (if (not (string? auth-str))
499 (let ((ip (open-string-input-port auth-str)))
500 (if (authority-with-username? auth-str)
501 (let ((op (open-output-string)))
505 (unreserved-char? ch)
506 (pct-encoded-char? ch)
510 (if (not (eq? (read-char ip) #\@))
511 (error "Invalid username.")
512 (let ((username (get-output-string op)))
513 (let-values (((host port) (parse-host-port ip)))
514 (make-authority username host port)))))
515 (let-values (((host port) (parse-host-port ip)))
516 (make-authority #f host port)))))))
519 (rtd-printer-set! uri (lambda (uri outp)
520 (display "#<uri \"" outp)
521 (display (uri->string uri) outp)
522 (display "\">" outp)))
524 (rtd-printer-set! authority (lambda (auth outp)
525 (display "#<authority \"" outp)
526 (display (authority->string auth) outp)
527 (display "\">" outp)))
531 (rl3 web uri url parms)
533 (export parse-parms encode-parm
538 (only (rnrs io simple)
540 (only (rnrs io ports)
541 open-string-input-port
542 get-char put-char put-string)
543 (only (rl3 web uri char-sets)
544 encode-char unsafe-char?)
545 (only (rl3 types chars)
548 (only (rl3 types strings)
550 (only (rl3 text text)
552 (primitives get-output-string open-output-string))
554 (define parm-reserved-char?
560 (define alist? list?)
561 (define query alist?)
563 (define encode-parm-string
565 (let ((op (open-output-string))
566 (ip (open-string-input-port str)))
567 (let loop ((ch (get-char ip)))
569 (get-output-string op)
571 (if (or (unsafe-char? ch)
572 (parm-reserved-char? ch))
573 (put-string op (encode-char ch))
575 (loop (get-char ip))))))))
579 (let ((key (car parm))
581 (cons (encode-parm-string key)
582 (encode-parm-string value)))))
586 (separate "&" (map (lambda (kv)
587 (string-append (car kv) "=" (cdr kv)))
590 (define parm-delim-char-set
591 (char-set-complement (string->char-set "=&")))
595 (let ((kvs (string-tokenize parm-str parm-delim-char-set)))
596 (let loop ((kvs kvs) (parms '()))
599 (let ((key (car kvs)))
600 (if (null? (cdr kvs))
601 parms ;; odd number of KVs which is wrong. Return what we got.
602 (loop (cddr kvs) (cons (cons key (cadr kvs)) parms)))))))))
609 ;; "ray@www.amazon.com"
610 ;; "www.amazon.com:80"
611 ;; "ray@www.amazon.com:80"))
613 ;; (define test-auth-bad
620 ;; (for-each (lambda (auth-str)
621 ;; (let ((auth (parse-authority auth-str)))
622 ;; (display "Username: ")
623 ;; (display (authority-username auth))
624 ;; (display " Host: ")
625 ;; (display (authority-host auth))
626 ;; (display " Port: ")
627 ;; (display (authority-port auth))
632 ;; http://ecs.amazonaws.com/onca/xml?
633 ;; Service=AWSECommerceService&
634 ;; Operation=ItemSearch&
635 ;; AWSAccessKeyId=[Access Key ID]&
636 ;; AssociateTag=[ID]&
637 ;; SearchIndex=Apparel&
640 ;; (uri->string (parse-uri (uri->string
642 ;; "" "ecs.amazonaws.com" #f
644 ;; "Service=AWSECommerceService&Operation=ItemSearch&AWSAccessKeyId=[Access Key ID]&AssociateTag=[ID]&SearchIndex=Apparel&Keywords=Shirt"
648 ;; (uri-authority (make-uri "http"
649 ;; "" "ecs.amazonaws.com" 8080
651 ;; "Service=AWSECommerceService&Operation=ItemSearch&AWSAccessKeyId=[Access Key ID]&AssociateTag=[ID]&SearchIndex=Apparel&Keywords=Shirt"