21.2. "cgi" --- CGI (ゲートウェイインタフェース規格) のサポート
***************************************************************

**ソースコード:** Lib/cgi.py

======================================================================

ゲートウェイインタフェース規格 (CGI) に準拠したスクリプトをサポートす
るためのモジュールです。

このモジュールでは、Python で CGI スクリプトを書く際に使える様々なユー
ティリティを定義しています。


21.2.1. はじめに
================

CGI スクリプトは、HTTP サーバによって起動され、通常は HTML の "<FORM>"
または "<ISINDEX>" エレメントを通じてユーザが入力した内容を処理します
。

ほとんどの場合、CGI スクリプトはサーバ上の特殊なディレクトリ "cgi-bin"
の下に置きます。HTTP サーバは、まずスクリプトを駆動するためのシェルの
環境変数に、リクエストの全ての情報 (クライアントのホスト名、リクエスト
されている URL、クエリ文字列、その他諸々) を設定し、スクリプトを実行し
た後、スクリプトの出力をクライアントに送信します。

スクリプトの入力端もクライアントに接続されていて、この経路を通じてフォ
ームデータを読み込むこともあります。それ以外の場合には、フォームデータ
は URL の一部分である「クエリ文字列」を介して渡されます。このモジュー
ルでは、上記のケースの違いに注意しつつ、Python スクリプトに対しては単
純なインタフェースを提供しています。このモジュールではまた、スクリプト
をデバッグするためのユーティリティも多数提供しています。また、最近はフ
ォームを経由したファイルのアップロードをサポートしています (ブラウザ側
がサポートしていればです)。

CGI スクリプトの出力は 2 つのセクションからなり、空行で分割されていま
す。最初のセクションは複数のヘッダからなり、後続するデータがどのような
ものかをクライアントに通知します。最小のヘッダセクションを生成するため
の Python のコードは以下のようなものです:

   print("Content-Type: text/html")    # HTML is following
   print()                             # blank line, end of headers

二つ目のセクションは通常、ヘッダやインラインイメージ等の付属したテキス
トをうまくフォーマットして表示できるようにした HTML です。以下に単純な
HTML を出力する Python コードを示します:

   print("<TITLE>CGI script output</TITLE>")
   print("<H1>This is my first CGI script</H1>")
   print("Hello, world!")


21.2.2. cgi モジュールを使う
============================

"import cgi" と記述して開始します。

新たにスクリプトを書く際には、以下の行を付加するかどうか検討してくださ
い:

   import cgitb
   cgitb.enable()

これによって、特別な例外処理が有効にされ、エラーが発生した際にブラウザ
上に詳細なレポートを出力するようになります。ユーザにスクリプトの内部を
見せたくないのなら、以下のようにしてレポートをファイルに保存できます:

   import cgitb
   cgitb.enable(display=0, logdir="/path/to/logdir")

スクリプトを開発する際には、この機能はとても役に立ちます。 "cgitb" が
生成する報告はバグを追跡するためにかかる時間を大幅に減らせるような情報
を提供してくれます。スクリプトをテストし終わり、正確に動作することを確
認したら、いつでも "cgitb" の行を削除できます。

入力されたフォームデータを取得するには、 "FieldStorage" クラスを使いま
す。フォームが非 ASCII 文字を含んでいる場合は、 *encoding* キーワード
パラメータを使用してドキュメントに対して定義されたエンコーディングの値
を設定してください。それは、通常 HTML ドキュメントの HEAD セクション中
の META タグ、あるいは  *Content-Type* ヘッダーに含まれています。これ
は、標準入力または環境変数からフォームの内容を読み出します (どちらから
読み出すかは、複数の環境変数の値が CGI 標準に従ってどのように設定され
ているかで決まります)。インスタンスが標準入力を使うかもしれないので、
インスタンス生成を行うのは一度だけにしなければなりません。

"FieldStorage" のインスタンスは Python の辞書型のように添え字アクセス
が可能です。 "in" を使用することによって要素が含まれているかの判定も出
来ますし、標準の辞書メソッド "keys()" 及び組み込み関数 "len()" もサポ
ートしています。空の文字列を含むフォーム要素は無視され、辞書には現れま
せん。そのような値を保持するには、"FieldStorage" のインスタンス作成の
際にオプションのキーワードパラメータ *keep_blank_values* に true を指
定してください。

例えば、以下のコード (*Content-Type* ヘッダと空行はすでに出力された後
とします) は "name" および "addr" フィールドが両方とも空の文字列に設定
されていないか調べます:

   form = cgi.FieldStorage()
   if "name" not in form or "addr" not in form:
       print("<H1>Error</H1>")
       print("Please fill in the name and addr fields.")
       return
   print("<p>name:", form["name"].value)
   print("<p>addr:", form["addr"].value)
   ...further form processing here...

ここで、 "form[key]" で参照される各フィールドはそれ自体が
"FieldStorage" (または "MiniFieldStorage" 。フォームのエンコードによっ
て変わります) のインスタンスです。インスタンスの属性 "value" の内容は
対応するフィールドの値で、文字列になります。 "getvalue()" メソッドはこ
の文字列値を直接返します。 "getvalue()" の 2 つめの引数にオプションの
値を与えると、リクエストされたキーが存在しない場合に返すデフォルトの値
になります。

入力されたフォームデータに同じ名前のフィールドが二つ以上あれば、
"form[key]" で得られるオブジェクトは "FieldStorage" や
"MiniFieldStorage" のインスタンスではなく、そうしたインスタンスのリス
トになります。この場合、 "form.getvalue(key)" も同様に、文字列からなる
リストを返します。もしこうした状況が起きうると思うなら (HTML のフォー
ムに同じ名前をもったフィールドが複数含まれているのなら) 、 "getlist()"
メソッドを使ってください。これは常に値のリストを返します (単一要素のケ
ースを特別扱いする必要はありません)。例えば、以下のコードは任意の数の
ユーザ名フィールドを結合し、コンマで分割された文字列にします:

   value = form.getlist("username")
   usernames = ",".join(value)

フィールドがアップロードされたファイルを表している場合、 "value" 属性
や "getvalue()" メソッドを使ってフィールドの値にアクセスすると、ファイ
ルの内容をすべてメモリ上にバイト列として読み込みます。これは場合によっ
ては望ましい動作ではないかもしれません。アップロードされたファイルがあ
るかどうかは "filename" 属性および "file" 属性のいずれかで調べられます
。そして、 "FieldStorage" インスタンスのガベージコレクションの一部とし
て自動的に閉じられるまでの間に、 "file" 属性から以下のようにデータを読
み込むことができます ("read()" および "readline()" メソッドはバイト列
を返します):

   fileitem = form["userfile"]
   if fileitem.file:
       # It's an uploaded file; count lines
       linecount = 0
       while True:
           line = fileitem.file.readline()
           if not line: break
           linecount = linecount + 1

"FieldStorage" オブジェクトは "with" 文での使用にも対応しています。
with 文を使用した場合、オブジェクトは終了時に自動的に閉じられます。

アップロードされたファイルの内容を取得している間にエラーが発生した場合
(例えば、ユーザーが戻るボタンやキャンセルボタンで submit を中断した場
合)、そのフィールドのオブジェクトの "done" 属性には -1 が設定されます
。

現在ドラフトとなっているファイルアップロードの標準仕様では、一つのフィ
ールドから (再帰的な *multipart/** エンコーディングを使って) 複数のフ
ァイルがアップロードされる可能性を受け入れています。この場合、アイテム
は辞書形式の "FieldStorage" アイテムとなります。複数ファイルかどうかは
"type" 属性が *multipart/form-data* (または *multipart/** にマッチする
他の MIME 型) になっているかどうかを調べれば判別できます。この場合、ト
ップレベルのフォームオブジェクトと同様にして再帰的に個別処理できます。

フォームが「古い」形式で入力された場合 (クエリ文字列または単一の
*application/x-www-form-urlencoded* データで入力された場合)、データ要
素の実体は "MiniFieldStorage" クラスのインスタンスになります。この場合
、 "list" 、 "file" 、および "filename" 属性は常に "None" になります。

フォームがPOSTによって送信され、クエリー文字列も持っていた場合、
"FieldStorage" と "MiniFieldStorage" の両方が含まれます。

バージョン 3.4 で変更: "file" 属性は、それを作成した "FieldStorage" イ
ンスタンスのガベージコレクションによって自動的に閉じられます。

バージョン 3.5 で変更: "FieldStorage" クラスにコンテキスト管理プロトコ
ルのサポートが追加されました。


21.2.3. 高水準インタフェース
============================

前節では CGI フォームデータを "FieldStorage" クラスを使って読み出す方
法について解説しました。この節では、フォームデータを分かりやすく直感的
な方法で読み出せるようにするために追加された、より高水準のインタフェー
スについて記述します。このインタフェースは前節で説明した技術を撤廃する
ものではありません --- 例えば、前節の技術は依然としてファイルのアップ
ロードを効率的に行う上で便利です。

このインタフェースは 2 つの単純なメソッドからなります。このメソッドを
使えば、一般的な方法でフォームデータを処理でき、ある名前のフィールドに
入力された値が一つなのかそれ以上なのかを心配する必要がなくなります。

前節では、一つのフィールド名に対して二つ以上の値が入力されるかもしれな
い場合には、常に以下のようなコードを書くよう学びました:

   item = form.getvalue("item")
   if isinstance(item, list):
       # The user is requesting more than one item.
   else:
       # The user is requesting only one item.

こういった状況は、例えば以下のように、同じ名前を持った複数のチェックボ
ックスからなるグループがフォームに入っているような場合によく起きます:

   <input type="checkbox" name="item" value="1" />
   <input type="checkbox" name="item" value="2" />

しかしながら、ほとんどの場合、あるフォーム中で特定の名前を持ったコント
ロールはただ一つしかないので、その名前に関連付けられた値はただ一つしか
ないはずだと考えるでしょう。そこで、スクリプトには例えば以下のようなコ
ードを書くでしょう:

   user = form.getvalue("user").upper()

このコードの問題点は、クライアントがスクリプトにとって常に有効な入力を
提供するとは期待できないところにあります。例えば、もし好奇心旺盛なユー
ザがもう一つの "user=foo" ペアをクエリ文字列に追加したら、
"getvalue("user")" メソッドは文字列ではなくリストを返すため、このスク
リプトはクラッシュするでしょう。リストに対して "upper()" メソッドを呼
び出すと、引数が有効でない (リスト型はその名前のメソッドを持っていない
) ため、例外 "AttributeError" を送出します。

従って、フォームデータの値を読み出しには、得られた値が単一の値なのか値
のリストなのかを常に調べるコードを使うのが適切でした。これでは煩わしく
、より読みにくいスクリプトになってしまいます。

ここで述べる高水準のインタフェースで提供している "getfirst()" や
"getlist()" メソッドを使うと、もっと便利にアプローチできます。

FieldStorage.getfirst(name, default=None)

   フォームフィールド *name* に関連付けられた値をつねに一つだけ返す軽
   量メソッドです。同じ名前で 1 つ以上の値がポストされている場合、この
   メソッドは最初の値だけを返します。フォームから値を受信する際の値の
   並び順はブラウザ間で異なる可能性があり、特定の順番であるとは期待で
   きないので注意してください。[1] 指定したフォームフィールドや値がな
   い場合、このメソッドはオプションの引数 *default* を返します。このパ
   ラメタを指定しない場合、標準の値は "None" に設定されます。

FieldStorage.getlist(name)

   このメソッドはフォームフィールド *name* に関連付けられた値を常にリ
   ストにして返します。*name* に指定したフォームフィールドや値が存在し
   ない場合、このメソッドは空のリストを返します。値が一つだけ存在する
   場合、要素を一つだけ含むリストを返します。

これらのメソッドを使うことで、以下のようにナイスでコンパクトにコードを
書けます:

   import cgi
   form = cgi.FieldStorage()
   user = form.getfirst("user", "").upper()    # This way it's safe.
   for item in form.getlist("item"):
       do_something(item)


21.2.4. 関数
============

より細かく CGI をコントロールしたり、このモジュールで実装されているア
ルゴリズムを他の状況で利用したい場合には、以下の関数が便利です。

cgi.parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False)

   環境変数、またはファイルからからクエリを解釈します (ファイルは標準
   で "sys.stdin" になります) *keep_blank_values* および
   *strict_parsing* パラメタはそのまま "urllib.parse.parse_qs()" に渡
   されます。

cgi.parse_qs(qs, keep_blank_values=False, strict_parsing=False)

   この関数はこのモジュールでは廃止予定です。 代わりに
   "urllib.parse.parse_qs()" を利用してください。 この関数は後方互換性
   のためだけに残されています。

cgi.parse_qsl(qs, keep_blank_values=False, strict_parsing=False)

   この関数はこのモジュールでは廃止予定です。 代わりに
   "urllib.parse.parse_qsl()" を利用してください。 この関数は後方互換
   性のためだけに残されています。

cgi.parse_multipart(fp, pdict)

   (ファイル入力のための) *multipart/form-data* 型の入力を解釈します。
   引数は入力ファイルを示す *fp* と *Content-Type* ヘッダ内の他のパラ
   メタを含む辞書 *pdict* です。

   "urllib.parse.parse_qs()" と同じく辞書を返します。辞書のキーはフィ
   ールド名で、対応する値は各フィールドの値でできたリストです。この関
   数は簡単に使えますが、数メガバイトのデータがアップロードされると考
   えられる場合にはあまり適していません --- その場合、より柔軟性のある
   "FieldStorage" を代りに使ってください。

   マルチパートデータがネストしている場合、各パートを解釈できないので
   注意してください --- 代りに "FieldStorage" を使ってください。

cgi.parse_header(string)

   (*Content-Type* のような) MIME ヘッダを解釈し、ヘッダの主要値と各パ
   ラメタからなる辞書にします。

cgi.test()

   メインプログラムから利用できる堅牢性テストを行う CGI スクリプトです
   。最小の HTTP ヘッダと、HTML フォームからスクリプトに供給された全て
   の情報を書式化して出力します。

cgi.print_environ()

   シェル変数を HTML に書式化して出力します。

cgi.print_form(form)

   フォームを HTML に初期化して出力します。

cgi.print_directory()

   現在のディレクトリを HTML に書式化して出力します。

cgi.print_environ_usage()

   意味のある (CGI の使う) 環境変数を HTML で出力します。

cgi.escape(s, quote=False)

   文字列 *s* 中の文字 "'&'"、"'<'"、および  "'>'" を HTML で正しく表
   示できる文字列に変換します。それらの文字が中に入っているかもしれな
   いようなテキストを出力する必要があるときに使ってください。オプショ
   ンの引数 *quote* の値が真であれば、二重引用符文字 (""") も変換しま
   す; この機能は "<a href="...">" のように二重引用符で区切られた HTML
   の属性値を出力に含めるのに役立ちます。単引用符は変換されないことに
   注意して下さい。

   バージョン 3.2 で非推奨: この関数は *quote* がデフォルトで false な
   ので安全ではありません。そのため deprecated になりました。代わりに
   "html.escape()" を使ってください。


21.2.5. セキュリティへの配慮
============================

重要なルールが一つあります: (関数 "os.system()"  または "os.popen()"
、またはその他の同様の機能によって) 外部プログラムを呼び出すなら、クラ
イアントから受信した任意の文字列をシェルに渡していないことをよく確かめ
てください。これはよく知られているセキュリティホールであり、これによっ
て Web  のどこかにいる悪賢いハッカーが、だまされやすい CGI スクリプト
に任意のシェルコマンドを実行させてしまえます。URL の一部やフィールド名
でさえも信用してはいけません。CGI へのリクエストはあなたの作ったフォー
ムから送信されるとは限らないからです！

安全な方法をとるために、フォームから入力された文字をシェルに渡す場合、
文字列に入っているのが英数文字、ダッシュ、アンダースコア、およびピリオ
ドだけかどうかを確認してください。


21.2.6. CGI スクリプトを Unix システムにインストールする
========================================================

あなたの使っている HTTP サーバのドキュメントを読んでください。そしてロ
ーカルシステムの管理者と一緒にどのディレクトリに CGI スクリプトをイン
ストールすべきかを調べてください; 通常これはサーバのファイルシステムツ
リー内の "cgi-bin" ディレクトリです。

あなたのスクリプトが "others" によって読み取り可能および実行可能である
ことを確認してください; Unix ファイルモードは 8 進表記で "0o755" です
("chmod 0755 filename" を使ってください)。スクリプトの最初の行の 1 カ
ラム目が、"#!" で開始し、その後に Python インタプリタへのパス名が続い
ていることを確認してください。例えば:

   #!/usr/local/bin/python

Python インタプリタが存在し、"others" によって実行可能であることを確か
めてください。

あなたのスクリプトが読み書きしなければならないファイルが全て "others"
によって読み出しや書き込み可能であることを確かめてください --- 読み出
し可能のファイルモードは "0o644" で、書き込み可能のファイルモードは
"0o666" になるはずです。これは、セキュリティ上の理由から、 HTTP サーバ
があなたのスクリプトを特権を全く持たないユーザ "nobody" の権限で実行す
るからです。この権限下では、誰でもが読める (書ける、実行できる) ファイ
ルしか読み出し (書き込み、実行) できません。スクリプト実行時のディレク
トリや環境変数のセットもあなたがログインしたときの設定と異なります。特
に、実行ファイルに対するシェルの検索パス ("PATH") や Python のモジュー
ル検索パス ("PYTHONPATH")が何らかの値に設定されていると期待してはいけ
ません。

モジュールを Python の標準設定におけるモジュール検索パス上にないディレ
クトリからロードする必要がある場合、他のモジュールを取り込む前にスクリ
プト内で検索パスを変更できます。例えば:

   import sys
   sys.path.insert(0, "/usr/home/joe/lib/python")
   sys.path.insert(0, "/usr/local/lib/python")

(この方法では、最後に挿入されたディレクトリが最初に検索されます！)

非 Unix システムにおける説明は変わるでしょう; あなたの使っている HTTP
サーバのドキュメントを調べてください (普通は CGI スクリプトに関する節
があります)。


21.2.7. CGI スクリプトをテストする
==================================

残念ながら、CGI スクリプトは普通、コマンドラインから起動しようとしても
動きません。また、コマンドラインから起動した場合には完璧に動作するスク
リプトが、不思議なことにサーバからの起動では失敗することがあります。し
かし、スクリプトをコマンドラインから実行してみなければならない理由が一
つあります: もしスクリプトが文法エラーを含んでいれば、Python インタプ
リタはそのプログラムを全く実行しないため、HTTP サーバはほとんどの場合
クライアントに謎めいたエラーを送信するからです。

スクリプトが構文エラーを含まないのにうまく動作しないなら、次の節に読み
進むしかありません。


21.2.8. CGI スクリプトをデバッグする
====================================

何よりもまず、些細なインストール関連のエラーでないか確認してください
--- 上の CGI スクリプトのインストールに関する節を注意深く読めば時間を
大いに節約できます。もしインストールの手続きを正しく理解しているか不安
なら、このモジュールのファイル ("cgi.py") をコピーして、CGI スクリプト
としてインストールしてみてください。このファイルはスクリプトとして呼び
出すと、スクリプトの実行環境とフォームの内容を HTML 形式で出力します。
ファイルに正しいモードを設定するなどして、リクエストを送ってみてくださ
い。標準的な "cgi-bin" ディレクトリにインストールされていれば、以下の
ような URL をブラウザに入力してリクエストを送信できるはずです:

   http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home

もしタイプ 404 のエラーになるなら、サーバはスクリプトを発見できないで
います -- おそらくあなたはスクリプトを別のディレクトリに入れる必要があ
るのでしょう。他のエラーになるなら、先に進む前に解決しなければならない
インストール上の問題があります。もし実行環境の情報とフォーム内容 (この
例では、各フィールドはフィールド名 "addr" に対して値 "At Home"、および
フィールド名 "name" に対して "Joe Blow") が綺麗にフォーマットされて表
示されるなら、 "cgi.py" スクリプトは正しくインストールされています。同
じ操作をあなたの自作スクリプトに対して行えば、スクリプトをデバッグでき
るようになるはずです。

次のステップでは "cgi" モジュールの "test()" 関数を呼び出すことになり
ます: メインプログラムコードを以下の 1 文と置き換えてください

   cgi.test()

この操作で "cgi.py" ファイル自体をインストールした時と同じ結果を出力す
るはずです。

通常の Python スクリプトが例外を処理しきれずに送出した場合 (様々な理由
: モジュール名のタイプミス、ファイルが開けなかった、など)、Python イン
タプリタはナイスなトレースバックを出力して終了します。Python インタプ
リタはあなたの CGI スクリプトが例外を送出した場合にも同様に振舞うので
、トレースバックは大抵HTTP サーバのいずれかのログファイルに残るかまっ
たく無視されるかです。

幸運なことに、あなたが自作のスクリプトで *何らかの* コードを実行できる
ようになったら、 "cgitb" モジュールを使って簡単にトレースバックをブラ
ウザに送信できます。まだそうでないなら、以下の2行:

   import cgitb
   cgitb.enable()

をスクリプトの先頭に追加してください。そしてスクリプトを再度走らせます
; 問題が発生すれば、クラッシュの原因を見出せるような詳細な報告を読めま
す。

"cgitb" モジュールのインポートに問題がありそうだと思うなら、(組み込み
モジュールだけを使った) もっと堅牢なアプローチを取れます:

   import sys
   sys.stderr = sys.stdout
   print("Content-Type: text/plain")
   print()
   ...your code here...

このコードは Python インタプリタがトレースバックを出力することに依存し
ています。出力のコンテント型はプレーンテキストに設定されており、全ての
HTML 処理を無効にしています。スクリプトがうまく動作する場合、生の HTML
コードがクライアントに表示されます。スクリプトが例外を送出する場合、最
初の 2 行が出力された後、トレースバックが表示されます。HTML の解釈は行
われないので、トレースバックを読めるはずです。


21.2.9. よくある問題と解決法
============================

* ほとんどの HTTP サーバはスクリプトの実行が完了するまで CGI からの出
  力をバッファします。このことは、スクリプトの実行中にクライアントが進
  捗状況報告を表示できないことを意味します。

* 上のインストールに関する説明を調べましょう。

* HTTP サーバのログファイルを調べましょう。(別のウィンドウで  "tail -f
  logfile" を実行すると便利かもしれません！)

* 常に "python script.py" などとして、スクリプトが構文エラーでないか調
  べましょう。

* スクリプトに構文エラーがないなら、"import cgitb; cgitb.enable()" を
  スクリプトの先頭に追加してみましょう。

* 外部プログラムを起動するときには、スクリプトがそのプログラムを見つけ
  られるようにしましょう。これは通常、絶対パス名を使うことを意味します
  --- "PATH" は普通、あまり CGI スクリプトにとって便利でない値に設定さ
  れています。

* 外部のファイルを読み書きする際には、CGI スクリプトを動作させるときに
  使われる userid でファイルを読み書きできるようになっているか確認しま
  しょう: userid は通常、Web サーバを動作させている userid か、Web サ
  ーバの "suexec" 機能で明示的に指定している userid になります。

* CGI スクリプトを set-uid モードにしてはいけません。これはほとんどの
  システムで動作せず、セキュリティ上の信頼性もありません。

-[ 脚注 ]-

[1] 最近のバージョンの HTML 仕様ではフィールドの値を供給する順番を取り
    決めてはいますが、ある HTTP リクエストがその取り決めに準拠したブラ
    ウザから受信したものかどうか、そもそもブラウザから送信されたものか
    どうかの判別は退屈で間違いやすいので注意してください。
