おひっこしします。

色々と使いづらかったので、レンタルサーバでWordPress使うことにしました。
てっく★ゆきろぐ Rev2

|

mb_eregの罠(なぜか一致しない)

先日の記事で、eregのひとはmb_eregに移行するといいよ、って記事を書きました。

ところがその後。
tweetAngelで、設定した文字列が一致しないということがありまして。
(実は、mb_eregを使用してます(参考))

気になって、こんなコードを書いてみました。

if ( mb_ereg( "メモ" , "メモ。あとで(以下略" ) === FALSE ) {
print "no match.\n" ;
} else {
print "match.\n" ;
}

そして実行したら・・・
no match.

(;;;・3・)あるぇぇぇぇ?
おかしいですね。どうみても一致するはずなのに、なぜか一致しません。
ちなみに、同じようにやってる「^予定」と「見てるぅ」と「^ToDo」はちゃんと動いてたので、なおさら頭を抱えてました。

もしかして・・・と思って、ちょっと調べたら・・・やっぱりありました!
mb_eregで日本語検索すると・・ : アシアルブログ
mb_ereg系関数を使う際の文字エンコーディング設定がちゃんとなってなかったようです。
どうやら同様の件は、結構はまるポイントのようです。

詳細は上記ブログにて解説がありますのでそちらに譲るといたしまして。
結論言うと、mb_ereg()系関数を使う場合、 mb_regex_encoding() で使用する文字エンコーディングをきちんと指定してあげる必要があります。

そんなわけであっさり解決。

| | コメント (0) | トラックバック (0)

Googleカレンダーに関するまとめ

twitterで書いたのをそのまま持ち込み。


○Googleカレンダーに関するドキュメント
メモ: Google カレンダーの API とツール - Google Code - http://code.google.com/intl/ja/apis/calendar/
メモ GoogleカレンダーのAPIについて書いてあるサイトを教えてください。 特にPHPとの連携があるとうれしいです。個人のブログや掲示板のトピックなどでも結構です。 具体的に.. - 人力検索はてな - http://q.hatena.ne.jp/1182325546
メモ(+見てる) Data API Developer's Guide: The Protocol - Google Calendar APIs and Tools - Google Code: http://bit.ly/44JTzz
見てるぅ> AuthSub for Web Applications - Accounts APIs - Google Code - http://code.google.com/intl/ja/apis/accounts/docs/AuthSub.html
メモ: Googleカレンダーの要素定義はここにある。>Common Elements: "Kinds" - Google Data Protocol - Google Code - http://bit.ly/yNAKW

このへんを参考にすればいいよというまとめ。
認証はAuthSubを使います。

|

ereg使っちゃいけません(ダメダメ♪ダメーッ)

まぁ、いままでereg多用しとったわけです。
ereg関数はPHP6.0でコアから消える予定【PHP】 - Programming Magic
そんなわけでereg厨の俺涙目状態、なわけです。

というか、実はeregにはさらにヤバい問題がありまして。
23. 関数とバイナリセーフ - PHP TIPS:ITpro
こちらの説明がわかりやすいので読んでいただければ、どういうことかは理解できると思います。
Cから入ってる人はひっかかりやすい部分だと思います。

とりあえず、実際にコードかいてみました。

<?php
header( "Content-type: text/html ; charset=utf-8" ) ;
?>
<HTML>
<TITLE>TEST</TITLE>
<BODY>
<P>テスト。
<P>\0 を途中に入れるとどうなる?
<P>\0 あり
<?php
$u = "1234567890" . "\x00" . "HOGEHOGE" ;
print "<P>PRINT raw = " . $u . "<br />\n" ;
print "htmlspecialchars = " . htmlspecialchars( $u ) . "<br />\n" ;
print "strlen = " . strlen( $u ) . "<br />\n" ;
print "is_numeric = " . var_export( is_numeric( $u ) , TRUE ). "<br />\n" ;
print "ctype_digit = " . var_export( ctype_digit( $u ) , TRUE ) . "<br />\n" ;
print "ereg(\"[^0-9]+\") = " . var_export( ereg( "[^0-9]+" , $u ) , TRUE ) . "<br />\n" ;
print "mb_ereg(\"[^0-9]+\")=" . var_export( mb_ereg( "[^0-9]+" , $u ) , TRUE ) . "<br />\n" ;
print "preg_match(\"/[^0-9]+/\")=" . var_export( preg_match( "/[^0-9]+/" , $u ) , TRUE ) . "<br />\n" ;
?>
<P>\0 なし
$u = "1234567890HOGEHOGE";
print "<P>PRINT raw = " . $u . "<br />\n" ;
print "htmlspecialchars = " . htmlspecialchars( $u ) . "<br />\n" ;
print "strlen = " . strlen( $u ) . "<br />\n" ;
print "is_numeric = " . var_export( is_numeric( $u ) , TRUE ). "<br />\n" ;
print "ctype_digit = " . var_export( ctype_digit( $u ) , TRUE ) . "<br />\n" ;
print "ereg(\"[^0-9]+\") = " . var_export( ereg( "[^0-9]+" , $u ) , TRUE ) . "<br />\n" ;
print "mb_ereg(\"[^0-9]+\")=" . var_export( mb_ereg( "[^0-9]+" , $u ) , TRUE ) . "<br />\n" ;
print "preg_match(\"/[^0-9]+/\")=" . var_export( preg_match( "/[^0-9]+/" , $u ) , TRUE ) . "<br />\n" ;
?>
<P>\0 なし、数字のみ
$u = "1234567890";
print "<P>PRINT raw = " . $u . "<br />\n" ;
print "htmlspecialchars = " . htmlspecialchars( $u ) . "<br />\n" ;
print "strlen = " . strlen( $u ) . "<br />\n" ;
print "is_numeric = " . var_export( is_numeric( $u ) , TRUE ). "<br />\n" ;
print "ctype_digit = " . var_export( ctype_digit( $u ) , TRUE ) . "<br />\n" ;
print "ereg(\"[^0-9]+\") = " . var_export( ereg( "[^0-9]+" , $u ) , TRUE ) . "<br />\n" ;
print "mb_ereg(\"[^0-9]+\")=" . var_export( mb_ereg( "[^0-9]+" , $u ) , TRUE ) . "<br />\n" ;
print "preg_match(\"/[^0-9]+/\")=" . var_export( preg_match( "/[^0-9]+/" , $u ) , TRUE ) . "<br />\n" ;
?>
</BODY>
</HTML>

¥x00(¥0)の文字を含むものを間に入れてみると、入れてないとで、動作が変わります。

テスト。

\0 を途中に入れるとどうなる?

\0 あり
PRINT raw = 1234567890HOGEHOGE
htmlspecialchars = 1234567890HOGEHOGE
strlen = 19
is_numeric = false
ctype_digit = false
ereg("[^0-9]+") = false
mb_ereg("[^0-9]+")=1
preg_match("/[^0-9]+/")=1

\0 なし
PRINT raw = 1234567890HOGEHOGE
htmlspecialchars = 1234567890HOGEHOGE
strlen = 18
is_numeric = false
ctype_digit = false
ereg("[^0-9]+") = 1
mb_ereg("[^0-9]+")=1
preg_match("/[^0-9]+/")=1

\0 なし、数字のみ
PRINT raw = 1234567890
htmlspecialchars = 1234567890
strlen = 10
is_numeric = true
ctype_digit = true
ereg("[^0-9]+") = false
mb_ereg("[^0-9]+")=false
preg_match("/[^0-9]+/")=0


本来は「¥0あり」「¥0なし」で同じ値を返さないとなりませんが、eregだけ違う値が返ってきてますね。
たとえばrequest.phpで引数pにid(INT)を渡すとします。こういうコード書きますよね。
<?php
$u = $_GET['p'] ;
if ( ereg( "[^0-9]" , $u ) ) ) {
// error !
} else {
// ok ! go !
$query = "SELECT * FROM table01 WHERE id=$u" ;
mysql_query( $query , $connection ) ;
(以下略)

で、リクエストを投げるときにこんなものを投げます。
http://example.com/request.php?p=12345678%00%20union%20(以下略)

すると、$u には、 "1234567890¥0union (以下略)" という文字列が渡ります。
ereg( "[^0-9]+" , $u ) は(さっきの結果をごらんのとおり) FALSE がかえりますので、SQL文を作るところに飛んできます。
$queryで展開されるのは、"SELECT * FROM table01 WHERE id=1234567890¥0 union (以下略)" ・・・。
はい・・・オワタ\(^o^)/、ですね。
そういうことです。

ちなみにこの系列で同様の影響を受けるのが以下の関数。これらはPHP6.0でereg()同様に廃止となります。
・ereg_replace()
・eregi()
・eregi_replace()
・split()
・spliti()
ちなみに正規表現を使わない場合、split()ではなくexplode()を使うといいです(そのぶん高速、かつバイナリセーフ)

この場面の・・・いや、今後の対策としては、
・mb_ereg() にする (いちばん簡単かつ確実)
・preg_match() をはじめとした PCRE関数に転向 書き換える( /〜/ を入れる必要があるので面倒)
・数字なら、is_numeric() とか ctype_digit() とかを使うようにする
あたりでしょうか。

参考:PHPネタ関連メモとか

|

見やすいPHPコードの書き方(但し我流)

あちらのほうで書いたとおり、昨日参加してきました、わんくま東京勉強会#38
そこでのライトニングトークの2本目で話した内容についてです。

ちなみに当日使ったスライド(PDF)
まぁ、ソースがわりとひどかったりします(苦笑)。
設定読むのにglobal使ってたりとか、SQLを複数行に分けてかくときの処理が割とひどいとか・・・ねー。
実は当日会場で作ったスライドで、5分という限られた時間のなかでおさえておきたいところだけを言ったんで、細かいところがいろいろとおかしかったりもしますが、そこんとこはご容赦をー。

最近はPHPでもいいフレームワーク(CakePHPとかsymfonyとか)があるのでそちらに頼ればいいんですが、頼らなくても十分なケースは多々あったりするもので、そのときに注意したいことをいろいろと話してみました。

要約するとこう。
(1)入力から処理されるまでのロジック(コード)と、表示ロジック(HTML)は分けよう
 コードは前半、表示は後半にはっきりわける。
(2)コード処理では以下の順番で書く
 ・共通で読み込む処理
 ・GET/POST/COOKIEからの取得、ならびに値のvalidation check。
  あらかじめ、ここで使用する全部の引数に対して、まとめて行っておく。
  そのときそのときでやると、ミスする可能性が大きいからです。
  ※validation checkしてから、実際に使用する変数に代入するとなおよし
  (私はこの方法でやってます。チェックずみかどうかを意識したいので)
   ↑これ、言い忘れてましたw
 ・入出力データの処理
  GET/POST/COOKIEのデータ、ファイル、データベース・・・
(3)表示部分は表示処理(とそれに必要かつ表示にしか使用されない処理)に徹する。
 ・HTMLタグに使われる文字のエスケープは必須(一部例外を除く)
  ・ちなみにエスケープ関数は自前で書いてます。
   以前htmlspecialcharsを使ったら動作がキモかったので・・・。
(4)データ入出力で特にデータベースを使う場合は、別に関数、出来ればクラスを定義してそこで集中して処理させる
 ・・・はい、メイン部分でSQLを意識しちゃダメです(笑)。
 個人的にはクラス作ってそこで処理させるのが非常に望ましいと思ってます。データの代入・取得とDBへの登録/DBからの取得を1つのインスタンスで出来ちゃうので。
 ただ、PHPのクラスは「あとづけ」的かんじがして結構醜い(笑)部分がありますが、そこさえ我慢すれば十分使えると思います。
 SQLの特別文字のエスケープはこのクラス/関数内で。SQLを生成する段階でエスケープさせます。
(5)変数名のつけかたに(極力)ルールを持たせる
 ・私自身の場合、実体データ、配列、表示用データ、クラスのインスタンス、クラス内変数、・・・・といろいろありますが、先頭1〜3文字でわかるようにしています。

話した内容+αは、ここまでー。


あと言いわすれていましたが、これは言っておきたいんで。
・入ってくるデータは信用してはいけない。
 特にid等に関しては「○○は弾く」ではなく「○○しか認めない」方式にする。
・「magic_quotes_gpc」はオフにすべき。
 正直、入力チェックを徹底している場合、「¥」を勝手に挿入される→取り除くための処理で、かえって邪魔で余計なバグを増やすだけなので(笑)。

今回の件で読んでおいてほしいページとか
@IT:クロスサイトスクリプティング対策の基本
今夜分かるSQLインジェクション対策 - @IT
クッキーに隠されたSQLインジェクション、対策は? - @IT
または、このへんに関連した各サイト。たまに読み返すといいと思います。


参考になりそうなサイト。というかクラスの話が出たので、
@IT:オブジェクト指向の世界(10)
 このへんは、技術というよりは哲学の話ですねー。

|

今日のお題:「祝日判定」

はじめに断っておきますが、万年カレンダーを作る、ってはなしではありません。

お仕事で祝日判定のルーチンを書くことになりまして。
国民の祝日について
…こんなふうに書きました。

function isholiday( $f_date ) {
    // 毎年 mmdd が変わらない日
    //                     元旦   建国   昭和   憲法   みど   こど 
    $fday_common = array( "0101","0211","0429","0503","0504","0505", 
    //                     文化   勤労   天皇   
                          "1103","1123","1223");
    // mmdd が変わる日+国民の休日
    $fday_alter  = array(
        // 2009               成人   春分   振替   海     敬老   国休 
            "2009" => array( "0112","0320","0506","0720","0921","0922",
        //                    秋分   体育
                             "0923","1012") ,   
        // 2010               成人   春分   振替   海     敬老   秋分 
            "2010" => array( "0111","0321","0322","0719","0920","0923",
        //                    体育
                            "1011") ,
        // 2011               成人   春分   海     敬老   秋分   体育
            "2011" => array( "0110","0322","0718","0919","0923","1010")
    ) ;
    $yy = substr( $f_date , 0 , 4 ) ;
    $mm = substr( $f_date , 5 , 2 ) ;
    $dd = substr( $f_date , 8 , 2 ) ;
    $mmdd = $mm . $dd ;
    if ( in_array( $mmdd , $fday_common       ) ) return TRUE ;
    if ( in_array( $mmdd , $fday_alter{ $yy } ) ) return TRUE ;
    // 以下省略

まぁ、はっきり言って

  • なんで(振替休日込みで)全部配列に突っ込んでるんだよー
  • 7月の第3週って設定くらいプログラムで書けよー
  • 春分日・秋分日くらい計算できるだろー

とかいろいろツッコミどころが多いと思います(苦笑)

まぁ、私としては、

  • 毎年1回更新するものだから、そのとき直すだけでいーじゃん
  • 休みはプログラムじゃなくて法律(人間の手)で決まるからどうせまた変わるしー

とかいうヌルい考えで組んだわけです(笑)

これだけじゃ、あんまりなんで、ちょっと遊んでみたいと思います。
twitterでこんなこと書いてみました。

そんなわけで、たとえば「1月の第2月曜日は何日になるかを求めるプログラムを書きなさい」とかいう課題は面白そうだとふと思った。

ぱっと思いついたのは、

1月の1日から数えて何回月曜日がくるかを数えて、2回目だったら祝日

という方法。
●年●月●日が何曜日かというのを求めるには、一番有名な方法としては、Zellerの公式、というのがありますが、今回は手抜きしてmktimeとdateでやっちゃいます。

$yy = 2009 ; // 年
$mm = 1 ;   // 月
$wnum = 2 ; // 第何週?
$wday=date("w",mktime(0,0,0,$mm,1,$yy));
$target_day = ( $wday > 1? $wnum : $wnum-1 ) * 7 + 2 -$wday ;

こっちのほうがわかりやすいかな。

$target_day = $wnum * 7 + 2 - ( $wday + ( $wday > 1 ? 0 : 7 ) ) ;

$wday>1 なのは、日曜が0、月曜が1、火曜が2、となっているためです。
日曜始まりの暦を見ると、月曜日は2番目に位置しています。だから +2 。
第何週なので、そのぶん*7します。
そこから、$wday (1日の曜日) をひきます。曜日が土曜日に向かうほど、月曜日が近くなりますから。

ざっとですが、コードと解説、かいてみました。

|

PEARのXML_Parserを使ったXMLパーサのクラス化(というか、移行方法)

PHPのXMLパーサをクラスで使おうとすると、難しいというか、簡単にはうまくいきません。
すでにxml_parseとか使って書いちゃったよ!って場合、泣かされてしまうところです。
けど、PEARのXML_Parserを使うと、移行がわりと簡単にできます。

まず、以前のようにXMLパーサ関数を使った処理の流れは大体こんなかんじ。
・要素の開始・終了時の処理、cdata(文字データ)の処理を行うハンドラ関数を作成する
・xml_parser_create() でXMLパーサを作成
・xml_set_element_handler() で、要素の開始・終了のハンドラを定義する
・xml_set_characer_data_handler() で、cdataハンドラを定義する
・xml_parse() で、XMLドキュメントの処理
・xml_parser_free() でXMLパーサの解放

手抜きしてるけど、ソースコードを例にするとこんなかんじ。
まず、xmlパース処理を呼び出すところをこんなかんじで作っております。

  $xmlparser = xml_parser_create() ;
xml_set_element_handler( $xmlparser ,
"xmlparse_h_element_start" ,
"xmlparse_h_element_end" ) ;
xml_set_character_data_handler( $xmlparser ,
"xmlparse_h_data" ) ;
$res = xml_parse( $xmlparser , $xmldata ) ;
xml_parser_free( $xmlparser ) ;

あとは、ハンドラの定義がこんなかんじにしてあるとして。
function xmlparse_h_element_start ( $parser , $name , $attrs )
{
/* 中略。ここで element の開始(開きタグ)処理 */
}
function xmlparse_h_element_end( $parser , $name )
{
/* 中略。ここで element 終了(閉じタグ)処理 */
}
function xmlparse_h_data( $parser , $data )
{
/* 中略。ここで 文字データ処理 */
}

これを、移行する方法は、こんなかんじ。
・XML_Parserの派生クラスを作る
・コンストラクタ(__construct)で、最後に「parent::XML_Parser」を呼ぶとき(必ず呼ぶこと!)、2番目の引数を 'event' にするのを忘れない。
・xml_set_element_handler()で定義した要素開始のハンドラ関数の中身を、 startHandler メンバ関数に持ってくる
・xml_set_element_handler()で定義した要素終了のハンドラ関数の中身を、 endHandler メンバ関数に持ってくる
・xml_set_characer_data_handler() で定義したcdataハンドラ関数の中身を、cdataHandler メンバ関数に持ってくる
・必要に応じて、グローバル変数とかを使ってた部分をクラス変数に変更するなど、修正する
・他から呼ぶときは、普段のクラスの呼び出しと同じ。
 ・初期化:$xmlp = new MyXmlPaser( 'UTF-8' , 'UTF-8' ) ;
 ・入力データのセット:$xmlp->setInputString( $xmldata ) ;
 ・XML処理実行:$xmlp->parse() ;

クラス部分はこんなかんじ。

require_once 'XML/Parser.php' ;

class MyXmlPaser extends XML_Parser
{
function __construct( $ca_srcenc , $ca_dstenc ) {
parent::XML_Parser( $ca_srcenc , 'event' , $ca_dstenc ) ;
}
function startHandler ( $ca_parser , $ca_name , $ca_attrs )
{ /* element start 処理 */
}
function endHandler( $ca_parser , $ca_name )
{
/* element end 処理 */
}
function cdataHandler( $ca_parser , $ca_data )
{
if ( $ca_data == FALSE ) {
} else {
/* cdata 処理 */
}
}
}

呼び出す本体をまとめるとこう。
$xmlp = new MyXmlPaser( 'UTF-8' , 'UTF-8' ) ;
$xmlp->setInputString( $xmldata ) ;
$xmlp->parse() ;

なお、基底クラスの XML_Parser でどんなことしてるかは、PEARディレクトリの XML/Parser.php にすべて書いてあるので、わからなくなったら目を通すといいと思います。

この移行によって、大幅に処理の追加や変更をする場所は(基本的に)ありません。
クラス化することによって、全部クラスに集約できるので、グローバル変数で扱うことがなくて、楽になりました。

| | コメント (0) | トラックバック (0)

さくらインターネットサーバでpear (厄介なバージョンに当たった人向け)

クラス内でxml_paserを使って・・・と思ったら、どう足掻いても使えないことがわかったので(参考:PHP-users 23513)、pearからXML_Parserを突っ込もうとしたら・・・

pear install XML_Parser
pear.php.net is using a unsupported protocal - This should never happen.
なんということでしょう〜('A`)

というわけで調べたら、
pear.php.net is using a unsupported protocal - This should never happen. - Do You PHP はてな
5.2.9と5.2.10がだめくさい。
というわけで、調べたら、

% php -v
PHP 5.2.10 (cli) (built: Jul 1 2009 18:42:15)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies
きたよ・・・きたよ・・・あたりのバージョンだよ・・・オワタ\(^o^)/
しかも、「 pear update-channels」を実行するにも、パーミッションの関係で不可。

・・・というわけで。自前でPEAR入れる以外の解決策が(当面は)ありませんでしたので、やることにしました。
(※サポートに言えば多分やってくれるとは思うけど)
さくらでpearインストール覚書
を参考にちょちょいとやってみました。
ちょっと古いのか、一部読み替える必要があります。
1. go-pear.php はここから落とす。→http://pear.php.net/go-pear (参考:Manual :: マネージャの取得 )
2.〜9. まではこのままでOK。ちなみに私は.cshrcにこう書いた。

setenv PATH $HOME/php/bin:$PATH

10.いきなりpearを実行ではなく、
which pear
としたときに、
/home/〜/bin/pear
と出ればOKです。
11.の前に、update-channelをするのですが、そのままだとエラーになるんで、config変えます。
pear config-set bin_dir ~/php/bin
pear config-set php_dir ~/php
pear config-set doc_dir ~/php/PEAR/docs
pear config-set test_dir ~/php/PEAR/test
pear config-set data_dir ~/php/PEAR/data

このあとに、
pear update-channel

そして、
pear install XML_Parser

これで問題なくいくようです。

| | コメント (1) | トラックバック (1)

twitterでOAuthを使う方法(その2:前回の続き〜APIにアクセスしてみる)

前回のおはなし。
twitterでOAuthを使う方法(その1:認証まで)
認証までの手順を説明しました。
・前準備:コンシューマの登録
・ユーザからのリクエスト→サービスプロバイダへリクエストトークンを要求
・サービスプロバイダの認証画面へリダイレクト→サービスプロバイダの承認
・サービスプロバイダへアクセストークンを要求

前回の説明で、署名に関して説明不足の点がありました。

リクエストに絡む情報すべて(※1)ですが、
・リクエストヘッダ
・POSTメソッドによる本体
・GETメソッドのquery_string
すべてを、「&」で結んだあとに、一括してurlencodeします。

これら3つに関して、『全部を』アルファベット順にソートしてから、&でつなげます。
三者間に優先順位・区別はなく、すべてを通してのソートです。
たとえば、
・リクエストヘッダ「a: 60」「c: 30」「g: 40」
・POSTメソッド「b=11」「e=50」
・GETのquery_string「d=24&f=33&h=66」
こういうふうに与えられてる場合、
a=60&b=11&c=30&d=24&e=50&f=33&g=40&h=66
というふうになります。

面倒なんで、コードで書くとこんなかんじです。

  // make signature --> See 9.2
private function MakeSignature( $ca_method , $ca_request_uri , $ca_header ,$ca_data ,
$ca_consumer_secret , $ca_token_secret ) {
$cl_dataarray = array() ;
// もとになるデータを生成
// Header
if ( $ca_header != NULL ) {
foreach ( $ca_header as $cl_key => $cl_val ) {
$cl_data = $cl_key . "=" . $cl_val ;
array_push( $cl_dataarray , $cl_data ) ;
}
}
// POST body
if ( $ca_data != NULL ) {
foreach ( $ca_data as $cl_key => $cl_val ) {
$cl_data = $cl_key . "=" . $cl_val ;
array_push( $cl_dataarray , $cl_data ) ;
}
}
// query_string for GET
if ( ereg( "\?" , $ca_request_uri ) ) {
$cl_uri_spl = explode( "?" , $ca_request_uri , 2 ) ; // split -> explode
$cl_request_uri = $cl_uri_spl[0] ;
if ( $cl_uri_spl[1] != "" ) {
$cl_req_qry = explode( "&" , $cl_uri_spl[1] ) ;
foreach( $cl_req_qry as $cl_data ) {
array_push( $cl_dataarray , $cl_data ) ;
}
}
} else {
$cl_request_uri = $ca_request_uri ;
}

$cl_count = 0 ;
$cl_dataall = "" ;
sort( $cl_dataarray ) ;
foreach ( $cl_dataarray as $cl_key => $cl_val ) {
if ( $cl_count > 0 ) {
$cl_dataall .= "&" ;
}
$cl_dataall .= $cl_val ;
$cl_count ++ ;
}

$cl_base = $ca_method . "&" . urlencode( $cl_request_uri ) ."&" . urlencode( $cl_dataall ) ;
$cl_base = ereg_replace( "\+" , "%20" , $cl_base ) ; // +ではなく%20で。
$cl_key = $ca_consumer_secret . "&" . $ca_token_secret ;

// HMAC-SHA1 でhash値生成
$cl_hash = hash_hmac( 'sha1' , $cl_base , $cl_key , TRUE ) ;

// 結果の処理
$cl_enc = base64_encode( $cl_hash ) ; // まずはbase64エンコードして
$cl_enc = ereg_replace( '\n' , '' , $cl_enc ) ;
$cl_enc2 = urlencode( $cl_enc ) ; // 続いてurlencodeする( 5.1 )

return $cl_enc2 ;
}


●APIへのアクセス (7)
OAuthを使う場合、APIへのアクセス方法は、
・リクエストヘッダにoAuth関連のパラメータを埋める
・GETのquery stringに埋める
・POSTの送信データに埋める
の3つの方法があります。
twitterの場合、残念ながら(リバースプロキシの関係?)、リクエストヘッダに埋める方法には対応しておりません(503エラーがかえってきます。ついでに、くじらさんが拝めます)。
従って、GET/POSTの(フォーム)データに埋めるしかありません。

与えるパラメータは以下の通りです。
・oauth_consumer_key ・・・ コンシューマのキー
・oauth_token ・・・ステップ4で(正式発行された) アクセストークン
あと、必要なもの
・OAuthのバージョン (oauth_version )
・タイムスタンプ ( oauth_timestamp )
・当該アクセスに対して、一意性を表す文字列( oauth_nonce )
・署名のプロトコル( oauth_signature_method )
・署名( oauth_signature )
そして、
・APIに与えるパラメータ

署名の作成ですが、例によって、署名以外のデータを対象にします。
署名に使うキーですが、 oauth_consumer_secret と oauth_token_secret (ステップ4で発行されたもの) を "&" でつないだものを使います。

リクエストを投げる先は、これまで通りのAPIのURIです。


○あと、気がついたこと。
認証の許可と解除を繰り返したせいかはわかりませんが。
気のせいか、updateのAPIがやたら不安定です。
よく『401 Not Authorized』を返してきます。署名が一致しないようで。

| | コメント (0) | トラックバック (0)

twitterでOAuthを使う方法(その1:認証まで)

OAuthって結構難しいと思われてるようですが、難しいというよりは、『ややこしい』です(苦笑)


そんなわけで。
手順毎に順番に説明をしようと思います。
※2009/09/23 説明の図(手書きでごめんなさい)追加しました。

●語句の説明
・サービスプロバイダ(service provider)・・・サービスを提供しているところ。この場合、twitter。
・ユーザ(user)・・・サービスプロバイダに登録していて、そのサービスを利用している人。
・コンシューマ(consumer)・・・サービスを提供しているところに、ユーザにかわって、そのサービスに対してアクセスする第三者。サードパーティ、とでも言うべきでしょうか。要は、この記事を見て「何か作ってみたい」という、あなたです。
2009092311

●ステップ0:まずは登録→Cosumer Token/Secretの取得
まずは、アプリケーションの登録作業です。ある程度かたちになってからのほうが望ましいですが、ぶっちゃけ適当でかまいません(笑)
OAuthの仕様書では、この取得に関するプロトコルは定めておりません。
サービスプロバイダとコンシューマの間で直接やりとりしてね、ってことです。
2009092312

ここから登録できます→http://twitter.com/apps/new
1.「設定」
2.「Connections」
3.右側の「Developers can edit the registration settings for their applications here.」
4.一番下に「Register a new application »」とあるので、こいつをクリック!
と行けば、登録画面に行けます。
たまにくじらさんが出ますが、そのときはリロードしてください。

登録に必要な項目は以下の通りです。
・Application Icon ・・・アプリケーションのアイコンです。アイコンはわりと重要です。
・Application Name ・・・アプリケーション名称です。
・Description ・・・アプリケーションについての説明。あまり長く書けないので簡潔に。
・Application Website ・・・ アプリケーションのサイト。説明してるサイト、もしくはホームを書きます。
・Organization ・・・ 作ってる組織。自分の名前(ニックネーム)でいいでしょう。
・Website ・・・ 作ってる組織のウェブサイト。twitterやってるなら、プロフィールのweb欄と同じで大丈夫。
・Application Type ・・・アプリケーションの種類です。
  ・Cient・・・クライアントアプリケーション。
  ・Browser・・・ブラウザベースで使用する場合。webアプリの場合はこちらでOKです。
・Callback URL・・・認証に成功したときにtwitterが戻すURI
・Use Twitter for login・・・twitterのidを認証に使用する場合はチェック
  ※twitterのidを使って自分のサービスで認証しないときは、チェックする必要はありません。

登録すると、以下の情報がもらえます。
・Consumer key ・・・コンシューマを識別するキー
・Consumer secret ・・・コンシューマの秘密鍵
・Request token URL ・・・ Request token の処理に用いる URI
・Access token URL ・・・ Access token の処理に用いる URI
・Authorize URL ・・・ 認証に用いる URI (今回は使用しない )

登録は以上です。
Consumer key , Consumer secret は、今後の処理で必要なので、忘れずに控えておいて下さい(といっても、自分の作ったアプリ一覧画面で出るから大丈夫だけど )

これからが、実際の「ユーザによる認証」のプロセスとなります。

●ステップ1:ユーザから、サービスプロバイダへのアクセスを「許可したい」と、コンシューマにリクエストが来る
あなたが提供しているサービスに対して、twitterのOAuthを使ってアクセス許可を出したい、というリクエストがきます。
2009092313

●ステップ2:サービスプロバイダへ利用許可を求める (6.1)
はい、ここからが本番です。
コンシューマ(要は、あなた)は、サービスプロバイダへ「request token」を投げます。
必要な、固有の情報は以下のとおりです。
・コンシューマキー ( oauth_consumer_key )
・ユーザが承認したときに(最終的にコンシューマの使用を許可させるために)差し戻すURI ( oauth_callback )
あと、必要なもの
・OAuthのバージョン (oauth_version )
・タイムスタンプ ( oauth_timestamp )
・当該アクセスに対して、一意性を表す文字列( oauth_nonce )
・署名のプロトコル( oauth_signature_method )
・署名( oauth_signature )
※署名に関してはややこしいので、あとで説明します。

以上の情報を添えて、サービスプロバイダへrequest tokenを投げます。
コンシューマキーと署名の情報が合致した場合、サービスプロバイダは以下の情報を返します。
・oauth_token ・・・ユーザのトークン(仮)
・oauth_token_secret ・・・ ユーザの秘密鍵
・oauth_callback_confirmed ・・・ OKだった場合これが「true」になる
 なお、そもそも失敗した場合、HTTPプロトコルの「401」がかえってきますので、成功の可否はこちらで見るべきです。
2009092314

無事に情報がかえってきた場合、コンシューマ(つまり、あなた)は、ユーザに対して、サービスプロバイダの認証用URIに行くように指示(リダイレクト)してください。
2009092315

twitterなら、 http://twitter.com/authorize
引数には、さきほど返してきた「oauth_token」を入れます。
oauth_tokenに「hogehoge1234」とついていた場合、
 http://twitter.com/authorize?oauth_token=hogehoge1234
というURI、となります。
これにより、サービスプロバイダ(この場合、twitter)の認証画面へ飛びます。

●ステップ3:サービスプロバイダによる許可 (6.2)
認証画面に行くと、「このアプリケーションへの使用許可を出しますか?」という画面になります。
ユーザはここで「Accept(許可する)」を選択します。
すると、サービスプロバイダはユーザに対して、ステップ2でサービスプロバイダに与えた「oauth_callback」へ、行くように指示します(リダイレクト)。
2009092316

このとき、以下のパラメータを付与します。
・oauth_token・・・ステップ2で戻ってきた oauth_token
・oauth_verifier・・・ユーザがOKしたということを示す(verify) キー

●ステップ4:コールバック→アクセストークンの取得 (6.3)
callbackされたリソースは、oauth_token/oauth_verifierを使って、ステップ2のユーザが「本当にリクエストしてきた」ということを証明するため、もう一度、サービスプロバイダへアクセスします。
・oauth_consumer_key ・・・ コンシューマのキー
・oauth_token ・・・ステップ2で戻ってきた oauth_token(ユーザごとの、ね)
あと、必要なもの
・OAuthのバージョン (oauth_version )
・タイムスタンプ ( oauth_timestamp )
・当該アクセスに対して、一意性を表す文字列( oauth_nonce )
・署名のプロトコル( oauth_signature_method )
・署名( oauth_signature )
※署名に関してはややこしいので、あとで説明します。
※この時点で、oauth_token_secret (ユーザごとの秘密鍵) が存在することを忘れないでください。

このリクエストを投げると、ユーザからのリクエストが正式なものとして扱われ、以下の情報がかえってきます。
・oauth_token ・・・ 『正式の』アクセストークン
・oauth_token_secret ・・・『正式の』アクセストークン秘密鍵

コンシューマは、この2つの情報を保持して、当該ユーザの情報とリンクさせて、サービスプロバイダへのアクセスに使用します。
2009092317

これで、すべての認証プロセスは終了です。


●最大の問題:署名
一番厄介な部分がここだと思います。

■signature base string ... 署名の対象となる文字列
とりあえず、単刀直入にこんなかんじになります。
・リクエストメソッド(GET/POST等)
・リクエストを投げるURI (?以降は含まない)
・リクエストに絡む情報すべて(※1)
この3つを「&」で結びます。
こんなかんじ。

POST&http%3A%2F%2Fexample.net%2Fapi.php&oauth_consumer_key=dpf43f3p2l4k3l03%26oauth_nonce=kllo9940pd9333jh%26oauth_signature_method=HMAC-SHA1%26oauth_timestamp=1191242096%26oauth_token=nnch734d00sl2jdk%26oauth_version=1.0

リクエストに絡む情報すべて(※1)ですが、
・リクエストヘッダ
・POSTメソッドによる本体
・GETメソッドのquery_string
すべてを、「&」で結んだあとに、一括してurlencodeします。
つまり、3つ目の情報においては
・「&」「=」はurlencodeの対象になる
ので、signature_base_stringの中には「&」が2個だけ存在します。

このsignature base stringに対して、指定したアルゴリズム(twitterなら、HMAC-SHA1 )で、処理したあと、base64エンコーディングします(改行コード等は取り除く)

これで、署名は完成です。


もし、何か間違いがありましたら、気軽にご指摘いただければな、と思います。

参考文献
OAuth core 1.0 ・・・オフィシャル
Yahoo!デベロッパーネットワーク - OAuth - フロー
APIアクセス権を委譲するプロトコル、OAuthを知る − @IT

| | コメント (0) | トラックバック (3)

«MacOS X Snow Leopard 入・・・った!