For Development HEAD DRAFTSearch (procedure/syntax/module):

12.55 rfc.uri - URIの解析と作成

Module: rfc.uri

RFC 2396 (https://www.ietf.org/rfc/rfc2396.txt)で定義されている Uniform Resource Identifiers、 またRFC 2397で定義されているData URI Schemeをパーズおよび構築する 手続き群を提供します。

はじめに、URIの構造をざっとみておきましょう。次のグラフはURIがどのように構築されるかを 示しています。

URI-+-scheme
    |
    +-specific--+--authority-+--userinfo
                |            +--host
                |            +--port
                +--path
                +--query
                +--fragment

全てのURIがここに示される完全な木構造を備えているわけではありません。 例えばmailto:admin@example.comscheme (mailto) と specific (admin@example.com) だけから構成されます。

しかし、良く使われるURIのほとんどはリソースを木構造で管理するので、 authority (通常はサーバ)と階層的なpathを持ちます。 http://example.com:8080/search?q=key#resultsでは、 authorityはexample.com:8080、pathは/search、 queryはkeyで、fragmentがresultsです。 userinfoはあればホスト名の前に付加されます。例えば ftp://anonymous@example.com/pub/におけるanonymousです。

URIをこれらの部分に分解する手続きと、 部分からURIを組み立てる手続きが提供されます。

URIのパーズ

Function: uri-ref uri parts

{rfc.uri} 与えられたURIから、指定部分を抜き出します。 このモジュールはURIを完全に分解する手続きも提供していますが、 現実のアプリケーションでは特定部分のみを取り出したい場合も多く、 この手続きはそういう場合に便利に使えます。

parts引数はシンボルもしくはシンボルのリストで、 抜き出したい部分を指定します。指定可能なシンボルは以下の通りです。

scheme

schemeパートを文字列で抜き出します。

authority

authorityパートを文字列で抜き出します。 URIにauthorityパートが無ければ#fが返されます。

userinfo

userinfoパートを文字列で抜き出します。 URIにuserinfoパートが無ければ#fが返されます。

host

hostパートを文字列で抜き出します。 URIにhostパートが無ければ#fが返されます。

port

portパートを文字列で抜き出します。 URIにportパートが無ければ#fが返されます。

path

pathパートを文字列で抜き出します。 URIが階層的でない場合は、specificパートが返されます。

query

queryパートを文字列で抜き出します。 URIにqueryパートが無ければ#fが返されます。

fragment

fragmentパートを文字列で抜き出します。 URIにfragmentパートが無ければ#fが返されます。

scheme+authority

schemeパートとauthorityパートの部分を文字列で。

host+port

hostパートとportパートの部分を文字列で。

userinfo+host+port

usertinfoパート、hostパート、portパートの部分を文字列で。

path+query

pathパートとqueryパートの部分を文字列で。

path+query+fragment

pathパートとqueryパートとfragmentパートの部分を文字列で。

(define uri "http://foo:bar@example.com:8080/search?q=word#results")

(uri-ref uri 'scheme)             ⇒ "http"
(uri-ref uri 'authority)          ⇒ "//foo:bar@example.com:8080/"
(uri-ref uri 'userinfo)           ⇒ "foo:bar"
(uri-ref uri 'host)               ⇒ "example.com"
(uri-ref uri 'port)               ⇒ 8080
(uri-ref uri 'path)               ⇒ "/search"
(uri-ref uri 'query)              ⇒ "q=word"
(uri-ref uri 'fragment)           ⇒ "results"
(uri-ref uri 'scheme+authority)   ⇒ "http://foo:bar@example.com:8080/"
(uri-ref uri 'host+port)          ⇒ "example.com:8080"
(uri-ref uri 'userinfo+host+port) ⇒ "foo:bar@example.com:8080"
(uri-ref uri 'path+query)         ⇒ "/search?q=word"
(uri-ref uri 'path+query+fragment)⇒ "/search?q=word#results"

partsに抜き出したい部分を指定するリストを渡すと、 それぞの部分のリストが返されます。

(uri-ref uri '(host+port path+query))
  ⇒ ("example.com:8080" "/search?q=word")
Function: uri-parse uri
Function: uri-scheme&specific uri
Function: uri-decompose-hierarchical specific
Function: uri-decompose-authority authority

{rfc.uri} URIの一般的なパーザです。これらの関数はURIエンコーディングを デコードしません。URIスキームによってどの部分をデコードすべきかが 異なるからです。パージングを行った後に、後述のuri-decode等を 使ってデコードを行ってください。

uri-parseは最も手軽な手続きで、uriを以下に示す部分に 分割し、多値で返します。 もし該当する部分がuriに無かった場合は、その部分には#fが返ります。

  • URIスキームを文字列で。 (例: "mailto:foo@example.com""mailto")。
  • authorityパートのuser-infoを文字列で。 (例: ftp://anonymous@ftp.example.com/pub/foo"anonymous")。
  • authorityパートのhostnameを文字列で。 (例: ftp://anonymous@ftp.example.com/pub/foo"ftp.example.com")。
  • authorityパートのport番号を整数で。 (例: http://www.example.com:8080/8080)。
  • pathパート。 (例: http://www.example.com/index.html"/index.html")。
  • queryパート。 (例: http://www.example.com/search?key=xyz&lang=en"key=xyz&lang=en")。
  • fragmentパート。 (例: http://www.example.com/document.html#section4"section4")。

以下の手続きはより詳細に、段階をふんでuriを分割してゆくものです。

uri-scheme&specific は URI uri を引数に取り、 スキーム部分と、そのスキーム特有の部分を表す2つの値を返します。 uri がスキーム部分を持たない場合、#f を返します。

(uri-scheme&specific "mailto:sclaus@north.pole")
  ⇒ "mailto" and "sclaus@north.pole"
(uri-scheme&specific "/icons/new.gif")
  ⇒ #f and "/icons/new.gif"

URI が階層的な記法を用いている場合、すなわち、 “//authority/path?query#fragment” のような場合、スキーム特有の部分を uri-decompose-hierarchical に渡すと、authoritypathqueryfragment の4つの値が返ります。

(uri-decompose-hierarchical "//www.foo.com/about/company.html")
  ⇒ "www.foo.com", "/about/company.html", #f and #f
(uri-decompose-hierarchical "//zzz.org/search?key=%3fhelp")
  ⇒ "zzz.org", "/search", "key=%3fhelp" and #f
(uri-decompose-hierarchical "//jjj.jp/index.html#whatsnew")
  ⇒ "jjj.jp", "/index.html", #f and "whatsnew"
(uri-decompose-hierarchical "my@address")
  ⇒ #f, #f, #f and #f

さらに、階層的 URI の authority の部分を uri-decompose-authority に渡すと、userinfohostport が返ります。

(uri-decompose-authority "yyy.jp:8080")
  ⇒ #f, "yyy.jp" and "8080"
(uri-decompose-authority "[::1]:8080")  ;(IPv6 host address)
  ⇒ #f, "::1" and "8080"
(uri-decompose-authority "mylogin@yyy.jp")
  ⇒ "mylogin", "yyy.jp" and #f
Function: uri-decompose-query query-string :key separators

{rfc.uri} クエリ文字列(例: "foo=abc&bar") を、 パラメータのリスト(例: (("foo" ""abc") ("bar" #t))へと分解します。 各パラメータは名前(文字列)と値(文字列または#t)のリストで表されます。

CGIスクリプトを書いているなら、www.cgiモジュールの cgi-parse-parametersの方が便利でしょう。この手続きの上に、 フォームパラメータやクッキーの取扱いなどを統合的に扱えるからです (www.cgi - CGIユーティリティ参照)。

  • Each parameter name and value is urldecoded.
  • If there are multiple parameters with the same name, they are not merged.
    (uri-decompose-query "a=b&a=c") ⇒ (("a" "b") ("a" "c"))
    
  • If the parameter isn’t given a value, its value in the output is #t.
    (uri-decompose-query "a&b") ⇒ (("a" #t) ("b" #t))
    
  • The order of input parameters is preserved.
  • 各パラメータ名と値はそれぞれurldecodeされます。
  • 同名のパラメータが複数ある場合、それらは統合されず、別々の要素として出力に現れます。
    (uri-decompose-query "a=b&a=c") ⇒ (("a" "b") ("a" "c"))
    
  • 値を持たないパラメータについては、出力の値は#tとなります。
    (uri-decompose-query "a&b") ⇒ (("a" #t) ("b" #t))
    
  • パラメータの順序は保存されます。

省略可能引数separatorsには、パラメータの区切りに使われる文字集合を指定します。 デフォルトは#[&;]です。歴史的に&;の両方が区切りに使われてきたので。 ただ、アプリケーションによっては&しか受け付けない場合があります。

この手続きの逆関数については下のurl-compose-queryを見てください。

Function: uri-decompose-data uri

{rfc.uri} Data scheme形式のuriをパーズします。data:スキーム部分は有っても無くても 構いません。渡されたuriがdata uriとして無効な文字列であればエラーが投げられます。

二つの値、パーズされたContent-Typeおよびデコードされたデータを返します。 Content-Typeがtext/*であればデコードされたデータは文字列で、 そうでなければu8vectorで返されます。

Content-Typeはmime-parse-content-typeでパーズされます (rfc.mime - MIMEメッセージ処理参照)。結果のデータ形式は次のようなリストです。

(type subtype (attribute . value) ...).

いくつか例を示します。

(uri-decompose-data
 "data:text/plain;charset=utf-8;base64,KGhlbGxvIHdvcmxkKQ==")
  ⇒ ("text" "plain" ("charset" . "utf-8")) and "(hello world)"

(uri-decompose-data
 "application/octet-stream;base64,AAECAw==")
  ⇒ ("application" "octet-stream") and #u8(0 1 2 3)

URIの構築

Function: uri-compose :key scheme userinfo host port authority path path* query fragment specific

{rfc.uri} 与えられたコンポーネントから URI を構成します。 妥当な URI を作成するためのコンポーネントの組み合わせはたくさんあります。 以下のダイアグラムは、考え得る組み合わせの方法を示しています。

        /-----------------specific-------------------\
        |                                            |
 scheme-+------authority-----+-+-------path*---------+-
        |                    | |                     |
        \-userinfo-host-port-/ \-path-query-fragment-/

キーワード引数に #f が与えられた場合、それはキーワード引数が 指定されないことと等価です。これは URI をパーズした結果を渡す場合に 特に有用です。

コンポーネントに適切でない文字が含まれている場合は、 url-compose に渡す前に正しくエスケープされなければなりません。

いくつかの例を示します。

(uri-compose :scheme "http" :host "foo.com" :port 80
             :path "/index.html" :fragment "top")
  ⇒ "http://foo.com:80/index.html#top"

(uri-compose :scheme "http" :host "foo.net"
             :path* "/cgi-bin/query.cgi?keyword=foo")
  ⇒ "http://foo.net/cgi-bin/query.cgi?keyword=foo"

(uri-compose :scheme "mailto" :specific "a@foo.org")
  ⇒ "mailto:a@foo.org"

(receive (authority path query fragment)
   (uri-decompose-hierarchical "//foo.jp/index.html#whatsnew")
 (uri-compose :authority authority :path path
              :query query :fragment fragment))
  ⇒ "//foo.jp/index.html#whatsnew"
Function: uri-merge base-uri relative-uri relative-uri2 …

{rfc.uri} 引数は、完全な、あるいは部分的なURIを表す文字列です。 この手続きは、RFC3986 Section 5.2. “Relative Resolution” に 示されるアルゴリズムに従い、relative-uribase-uriからの相対 として解決します。

relative-uri2 … が与えられた場合は、まずrelative-uribase-uriを基準に解決され、その結果を新たな基準として次の relative-uri2を解決し、以下同様に続けます。

(uri-merge "http://example.com/foo/index.html" "a/b/c")
 ⇒ "http://example.com/foo/a/b/c"

(uri-merge "http://example.com/foo/search?q=abc" "../about#me")
 ⇒ "http://example.com/about#me"

(uri-merge "http://example.com/foo" "http://example.net/bar")
 ⇒ "http://example.net/bar"

(uri-merge "http://example.com/foo/" "q" "?xyz")
 ⇒ "http://example.com/foo/q?xyz"
Function: uri-compose-query params :optional encoding

{rfc.uri} paramsはパラーメータ指定のリストです。各パラメータ指定は (name value)の形式で、nameは文字列、 valueは文字列もしくは#tです (上のurl-decompose-queryも参照)。

各パラメータの名前と値はurlencodeされてから、URLのクエリパラメータとして結合されます。 パラメータの値が#tの場合は、名前だけのパラメータとなります。

(uri-compose-query '(("foo" "abc") ("bar" #t)))
  ⇒ "foo=abc&bar"

省略可能なencoding引数はパラメータの文字エンコーディングを指定します。 デフォルトはutf-8です。それ以外の場合は、各文字列はその文字エンコーディングに 変換された後でurlencodeされます。

rfc.httphttp-compose-queryはこの手続きの上に作られた ユーティリティです(HTTPクライアントユーティリティ参照)。

Function: uri-compose-data data :key content-type encoding

{rfc.uri} 与えられたdataからData URIを構築して文字列で返します。

data引数は文字列かu8vectorでなければなりません。

content-typeキーワード引数は、#f (デフォルト)、 content typeを表現する文字列 (例: "text/plain;charset=utf-8")、 もしくはパーズされたcontent type (例: ("application" "octet-stream"))です。 #fである場合は、dataが完全な文字列であれば text/plainにGaucheのネイティブ文字エンコーディングに基づくcharsetを つけたもの、dataがそれ以外であればapplication/octet-streamが 使われます。

encodingキーワード引数は#f (デフォルト)、 もしくはシンボルuriまたはbase64です。これは文字エンコーディングではなく トランスファーエンコーディングであることに注意。 #fの場合は、テキストデータならuriが、バイナリデータならbase64が 使われます。

(uri-compose-data "(hello world)")
 ⇒ "data:text/plain;charset=utf-8,%28hello%20world%29"

(uri-compose-data "(hello world)" :encoding 'base64)
 ⇒ "data:text/plain;charset=utf-8;base64,KGhlbGxvIHdvcmxkKQ=="

(uri-compose-data '#u8(0 1 2 3))
 ⇒ "data:application/octet-stream;base64,AAECAw=="

URIのエンコードとデコード

Function: uri-decode :key :cgi-decode
Function: uri-decode-string string :key :cgi-decode :encoding

{rfc.uri} URI エンコーディング、すなわち、%でエスケープされた URI 文字列を デコードします。uri-decode は現在の入力ポートから入力を受け取り、 デコードした結果を現在の出力ポートに書き出します。 uri-decode-stringstring を入力とし、デコードした 文字列を返します。

cgi-decode が真の場合は、+ がスペース文字に置換されます。

uri-decode-stringには、外部の文字エンコーディングを指定する encodingキーワード引数を与えることができます。この引数が与えれた 場合、デコードされたオクテットの列を指定された文字エンコーディングであると してGaucheの内部文字エンコーディングへと変換したものが返されます。

Function: uri-encode :key :noescape
Function: uri-encode-string string :key :noescape :encoding

{rfc.uri} 安全でない文字を、%によるエスケープでエンコードします。 uri-encode は現在の入力ポートから入力を受け取り、 結果を現在の出力ポートに書き出します。 uri-encode-stringstring を入力とし、エンコードした 文字列を返します。

デフォルトでは、RFC3986 で"非予約文字"として規定されていない文字は エスケープされます。noescape 引数に異なる文字集合を渡すことで、 それらがエンコードされるのを抑止することができます。 例えば古いRFC2396では"非予約文字"がいくつか多かったのですが、 *rfc2396-unreserved-char-set* (下記参照) を渡すことで それらの文字がエスケープされるのを防ぐことができます。

マルチバイト文字は、デフォルトではGauche のネイティブなマルチバイト表現の オクテット・ストリームとしてエンコードされます。ただし uri-encode-stringにはencodingキーワード引数を渡すことができて、 その場合はまずstringが指定された文字エンコーディングへと変換されます。

Constant: *rfc2396-unreserved-char-set*
Constant: *rfc3986-unreserved-char-set*

{rfc.uri} これらの定数はそれぞれ、RFC2396とRFC3986で定義されている 「非予約文字」の文字集合に束縛されています。 (文字集合の操作については、文字集合およびscheme.charset - R7RS文字集合 を参照して下さい。)



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT