ssax/access-remote.rkt
#lang racket/base
(require net/url
         net/head
         racket/path
         srfi/13/string
         "errors-and-warnings.rkt")
(provide open-input-resource
         ar:resolve-uri-according-base
         ar:resource-type)

;; Uniform access to local and remote resources
;; Resolution for relative URIs in accordance with RFC 2396
;
; This software is in Public Domain.
; IT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
;
; Please send bug reports and comments to:
;   lizorkin@hotbox.ru    Dmitry Lizorkin

;=========================================================================
; Accessing (remote) resources

; Opens an input port for a resource
;  REQ-URI - a string representing a URI of the resource
; An input port is returned if there were no errors. In case of an error,
; the function returns #f and displays an error message as a side effect.
; Doesn't raise any exceptions.
;; ryanc: Why not?!
(define (open-input-resource req-uri)
  (with-handlers ([exn:fail?
                   (lambda (e)
                     (sxml:warn 'open-input-resource "~a: ~a"
                                req-uri (exn-message e))
                     #f)])
    (get-pure-port (string->url req-uri))))

;=========================================================================
; Determining resource type

; Determines the type of a resource
;  REQ-URI - a string representing a URI of the resource
; For a local resource, its type is determined by its file extension
; One of the following is returned:
;  #f - if the requested resource doesn't exist
;  'xml - for a resource that is an XML document
;  'html - for a resource that is an HTML document
;  'unknown - for any other resource type
(define (ar:resource-type req-uri)
  (cond [(string-prefix? "http://" req-uri)  ; HTTP scheme is used in REQ-URI
         (with-handlers ([exn:fail? (lambda (exn) #f)])
           (call/input-url (string->url req-uri) head-impure-port
             (lambda (port)
               (let* ([headers (purify-port port)]
                      [content-type (extract-field "content-type" headers)])
                 (cond [(not content-type) ;; no content-type specified
                        'unknown]
                       [(string-prefix? "text/xml" content-type)
                        'xml]
                       [(string-prefix? "text/html" content-type)
                        'html]
                       [(string-prefix? "text/plain" content-type)
                        'plain]
                       [else 'unknown])))))]
        [else  ; a local file
         (cond [(not (file-exists? req-uri))  ; file doesn't exist
                #f]
               [(assoc (filename-extension req-uri)
                       '((#"xml" . xml) (#"html" . html) (#"htm" . html)))
                => cdr]
               [else 'unknown])]))

;=========================================================================
; Working on absolute/relative URIs
; This section is based on RFC 2396

;-------------------------------------------------
; Resolves a relative URI with respect to the base URI

;  base-uri - base URI for the requiested one
; Returns the resolved URI
(define (ar:resolve-uri-according-base base-uri req-uri)
  (url->string (combine-url/relative (string->url base-uri) req-uri)))