Category: Device

リンク先の情報がほぼすべてであるが、私の担当しているサービスでトラブルが発生したので備忘録として。

SoftBankケータイはリクエストのリダイレクト回数が3回(C型は2回)と仕様書に明記されている。

DoCoMo / KDDIにもリダイレクト上限が存在するが、SoftBankに比べて制限がゆるいこと、仕様書に明確な記述が(おそらく)ないことから、あまり認知されていないようだ。

回数上限:

  • DoCoMo(SH905i)
     >5回
  • KDDI(W44K)
     >7回

情報いただきっぱなしでは申し訳ないのでKDDIの上限は調べたところ、7回ループでは200、8回ループでは403が返ってきた。

iモード、EZweb のリダイレクト回数制限 – nomblog
http://blog.izanagi-izanami.net/2008/02/iezweb.html

巷(の一部)で大騒ぎのiPhone 3G。値段やら販売方法やらいろいろ批判はあるけど、とりあえず新しいものは触ってみたいじゃないか。でも自分で持つのはコストかかりすぎなので会社に「検証用に欲しいなー」などと言っていたら、意外とすんなり買ってきてくれた。

いろいろいじるのは楽しいのだが、まずはリモートホストとユーザエージェントを調べてみる。

リモートホスト
pw************.14.tik.panda-world.ne.jp
ユーザーエージェント
Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_0 like Mac OS X; ja-jp) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5A345 Safari/525.20

(さらに…)

大変いまさらながら、ディズニーモバイル端末が届いたのでテストしてみた。通信網も端末も販売もSoftbank Mobileが行う。変化があるのはユーザエージェントの一部とメールのドメイン名。

ユーザエージェント:
http://developers.softbankmobile.co.jp/dp/tool_dl/web/useragent.php

メール:
http://developers.softbankmobile.co.jp/dp/tech_svc/mail/

Kwappaの端末情報取得クラスにも放り込んでみた。

array(6) {
  ["type"]=>
  int(3)
  ["name"]=>
  string(7) "DM001SH"
  ["icc"]=>
  string(15) "3580*******6824"
  ["ser"]=>
  string(0) ""
  ["ua"]=>
  string(106) "SoftBank/1.0/DM001SH/SHJ001/SN3580*******6824 Browser/NetFront/3.4 Profile/MIDP-2.0 Configuration/CLDC-1.1"
  ["guid"]=>
  string(0) ""
}

キャリア種別はSoftbankと判定されている(当たり前)。機種名が違う(長くなってる)ので、機種名を使って端末の対応 / 非対応などを見ている場合は注意が必要だろう。幸い私のプロジェクトではメールのドメインだけ対応すれば完了だった。ああよかった。

See also:

2月末に発表されたとおり、3月末日よりDoCoMo端末のリクエストから「iモードID」というのが利用できるようになった。リクエストラインに「guid=ON」というクエリを付与するとリクエストヘッダに7桁の英数字(FOMAカードごとにユニーク / 再利用されない)が付与されてくる。

いろいろ不具合はあるがとりあえずユーザ側にダイアログを出すことなく端末識別ができるようになったのは大きな進歩。早速(…というほど早くないが。風邪でダウンしてました)いろいろ試してみる。

ユーザエージェントからキャリア種別と端末名を知る」で紹介した端末情報取得クラスをちょっと改修。

<?php
class MobileUtil
{
    /******** 前略 ********/
    private $guid = "" ;        // iモードID / DoCoMoのみ
    /******** 中略 ********/
            // 先頭が DoCoMo = DoCoMo ==========================================
            if ($uaSlash[0] == "DoCoMo")
            {
                /******** 中略 ********/
                // HTTPヘッダからiモードIDを取得
                $this->guid = getenv("HTTP_X_DCMGUID") ;
            }
    /******** 後略 ********/
}
?>

手近に転がっていた端末でテストしてみた。テストに使ったフォームはこんな感じ。

<form action="?guid=ON" method="post" utn="utn">
    <input type="text"   name="text" />
    <br />
    <input type="submit" name="__exec__" value="OK" />
</form>

icc / ser / guidは適当に伏せてある。

--------------------------------------------------------------------------------
SH905i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(6) "SH905i"
  ["icc"]=>
  string(20) "8981************039f"
  ["ser"]=>
  string(15) "353*********044"
  ["ua"]=>
  string(76) "DoCoMo/2.0 SH905i(c100;TB;W30H20;ser353*********044;icc8981************039f)"
  ["guid"]=>
  string(7) "NW****x3"
}
--------------------------------------------------------------------------------
SO903i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(6) "SO903i"
  ["icc"]=>
  string(20) "8981************948f"
  ["ser"]=>
  string(15) "351*********564"
  ["ua"]=>
  string(76) "DoCoMo/2.0 SO903i(c100;TB;W30H23;ser351*********564;icc8981************948f)"
  ["guid"]=>
  string(7) "7t***Ob"
}
--------------------------------------------------------------------------------
P703imyu
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(8) "P703imyu"
  ["icc"]=>
  string(20) "8981************208F"
  ["ser"]=>
  string(15) "359*********705"
  ["ua"]=>
  string(78) "DoCoMo/2.0 P703imyu(c100;TB;W24H12;ser359*********705;icc8981************208F)"
  ["guid"]=>
  string(7) "2d***hz"
}
--------------------------------------------------------------------------------
D703i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(5) "D703i"
  ["icc"]=>
  string(20) "8981************628f"
  ["ser"]=>
  string(15) "359*********518"
  ["ua"]=>
  string(75) "DoCoMo/2.0 D703i(c100;TB;W28H15;ser359*********518;icc8981************628f)"
  ["guid"]=>
  string(7) "04***EI"
}
--------------------------------------------------------------------------------
F703i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(5) "F703i"
  ["icc"]=>
  string(20) "8981************636f"
  ["ser"]=>
  string(15) "359*********503"
  ["ua"]=>
  string(75) "DoCoMo/2.0 F703i(c100;TB;W23H12;ser359*********503;icc8981100010579573************36f)"
  ["guid"]=>
  string(7) "Fc***4T"
}

D703iとF703iのFOMAカードを交換してみる。

--------------------------------------------------------------------------------
D703i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(5) "D703i"
  ["icc"]=>
  string(20) "8981************636f"
  ["ser"]=>
  string(15) "359*********518"
  ["ua"]=>
  string(75) "DoCoMo/2.0 D703i(c100;TB;W28H15;ser359*********518;icc8981************636f)"
  ["guid"]=>
  string(7) "Fc***4T"
}
--------------------------------------------------------------------------------
D703i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(5) "F703i"
  ["icc"]=>
  string(20) "8981************628f"
  ["ser"]=>
  string(15) "359*********503"
  ["ua"]=>
  string(75) "DoCoMo/2.0 F703i(c100;TB;W23H12;ser359*********503;icc8981************628f)"
  ["guid"]=>
  string(7) "04***EI"
}

…ということで、serは端末固有、iccとiモードIDはFOMAカード固有【追記参照】、ということが確認できた。

「POSTのbody部については対象外」も念のため検証してみた。

<form action="" method="post" utn="utn">
    <input type="text"   name="guid" value="ON" />
    <br />
    <input type="submit" name="__exec__" value="OK" />
</form>
--------------------------------------------------------------------------------
SH905i
--------------------------------------------------------------------------------
array(6) {
  ["type"]=>
  int(1)
  ["name"]=>
  string(6) "SH905i"
  ["icc"]=>
  string(20) "8981************039f"
  ["ser"]=>
  string(15) "353*********044"
  ["ua"]=>
  string(76) "DoCoMo/2.0 SH905i(c100;TB;W30H20;ser353*********044;icc8981************039f)"
  ["guid"]=>
  bool(false)
}

仕様どおり。

クエリに「guid」というキー名が使えなくなるということなので、運が悪いと今頃改修で大変な方もいるのではないだろうか。私は幸いにして無関係だが。

仕様ページにちっちゃくしれっと注意点が書いてあるので引用しておく。

利用時の注意点

  • ユーザのiモードID利用設定がOFFの場合にはiモードIDの拡張ヘッダは付与されません。
  • ユーザの名義変更、改番、iモード契約の解約によりiモードIDは変更となります。
  • 一度、付与したiモードIDは再利用いたしません。
  • SSL通信時は、iモードIDは付与できません。
  • 2in1契約の場合、利用中のモードによらず、AナンバーのiモードIDを付与します。
  • 交換機などの工事・ネットワークの負荷状況によりiモードIDが送出できない場合があります。

SSL通信時は使えないというのはこちらでも困ってるし、私のプロジェクトでもそのうち困ることになるだろうなぁ。公式サイトではuidという端末識別IDを送出させるクエリがあるのだが、そちらもSSL通信時は使えなくて大変苦労した過去がある。


2008.07.09追記:

OST勉強会に参加してきたら、講演資料にちゃんとした検証結果があった。検証の甘さに恥じ入りつつ、成果はあつかましく取り入れさせていただく。

formメソッド action hidden
get ×
post ×
  • [action]…<form action="?guid=ON">
  • [hidden]…<input type="hidden" name="guid" value="ON" />


2009.01.22追記:

大変いまさらだが、微妙な(しかし大きな)誤記があったのでお詫びして訂正する。

  • icc
     >FOMAカードの製造番号。
  • iモードID(guid)
     >電話番号に固有の番号。名義変更 / 番号変更 / iモード契約解約で変更される。

●その他の留意点 | サービス・機能 | NTTドコモ
http://www.nttdocomo.co.jp/service/imode/make/content/html/notice/other/#p14

●重要なお知らせ : 『iモードID』の提供開始について | お知らせ | NTTドコモ
http://www.nttdocomo.co.jp/info/notice/page/080228_00.html

つまりiモードIDを変えずにiccを変える方法も、逆にiccを変えずにiモードIDを変える方法も存在する、ということ。認証やユーザ識別に両方を使用している場合注意が必要となる。

●紛失・盗難などによる利用中断・再開 | お客様サポート | NTTドコモ
http://www.nttdocomo.co.jp/support/procedure/change_release/trouble/index.html

ブラウザが吐き出すユーザエージェントがアクセスしてきた端末を特定する(たぶん唯一の)手がかりである。各キャリアのユーザエージェントに関する仕様は以下のとおり。

ユーザエージェント | サービス・機能 | NTTドコモ

KDDI au: そのほかの技術情報 > ユーザーエージェント

ソフトバンク – 技術資料 – ユーザーエージェントについて

アクセスしてきたユーザを特定する情報として「個体識別番号」がある。取得方法は以下のとおり。

◆DoCoMo:
aタグ / formタグに「utn="utn"」を付与する必要があり、ユーザがクリックすると送信を知らせるダイアログが表示される。

◆au:
リクエストヘッダ[HTTP_X_UP_SUBNO]として垂れ流し。

◆SoftBank:
端末側で送出する設定にしていればリクエストヘッダ[HTTP_X_JPHONE_UID]およびユーザエージェントに乗ってくる。

ということでちょっと長いがKwappaで使っている機種情報取得クラス。

<?php
/**
*  端末情報クラス
*/
class MobileUtil
{
    private $type = "" ;        // キャリア種別("dcm"とか)
    private $name = "" ;        // 端末名("J-SH02"とか)
    private $icc  = "" ;        // icc : au / SB / FOMA
    private $ser  = "" ;        // ser : DoCoMoのみ
    private $ua   = "" ;        // ユーザエージェント
    public function __construct()
    {
        // ユーザエージェントを控える
        $this->ua = getenv("HTTP_USER_AGENT") ;
        $uaSlash = explode("/", $this->ua) ;
        //======================================================================
        // UAがSBM関係の文字列で開始 = sb
        //======================================================================
        if (preg_match("/^(J-PHONE)|(Vodafone)|(SoftBank)|(MOT-[VC]980)/", $uaSlash[0]))
        {
            $this->type = KWAPPA_TERM_SB ;
            // HTTP_X_JPHONE_MSNAMEに値があればそのまま取得
            if (($this->name = getenv("HTTP_X_JPHONE_MSNAME")) != "")
            {
                $this->uid  = getenv("HTTP_X_JPHONE_UID") ;
            }
            // HTTP_X_JPHONE_MSNAMEが未設定ならUAから切り出し
            else
            {
                // モトローラ2端末
                if ($uaSlash[0] == "MOT-V980")
                {
                    $this->name = "V702MO" ;
                }
                else if ($uaSlash[0] == "MOT-C980")
                {
                    $this->name = "V702sMO" ;
                }
                // 一般端末
                else
                {
                    $this->name = $uaSlash[2] ;
                }
            }
            // iccの取り出し
            if (preg_match("/^SN\d+/", $uaSlash[4], $result))
            {
                $this->icc = substr($result[0], 2) ;
            }
        }
        //======================================================================
        // その他端末
        //======================================================================
        else
        {
            //==================================================================
            // 先頭が DoCoMo = DoCoMo
            //==================================================================
            if ($uaSlash[0] == "DoCoMo")
            {
                $this->type = KWAPPA_TERM_DCM ;
                // PDC -----------------------------------------------------
                if ($uaSlash[1] == "1.0")
                {
                    // 端末名
                    $this->name = $uaSlash[2] ;
                    // ser(製造番号)
                    if (preg_match("/ser(.{11})/", $this->ua, $result))
                    {
                        $this->ser = $result[1] ;
                    }
                }
                // FOMA ----------------------------------------------------
                else
                {
                    // 端末名
                    $t = substr($uaSlash[1], 4) ;
                    $this->name = substr($t, 0, strpos($t, "(")) ;
                    // ser(製造番号) / icc(FOMAカードシリアル)
                    if (preg_match("/ser(.{15}).*icc(.{20})/", $this->ua, $result))
                    {
                        $this->ser = $result[1] ;
                        $this->icc = $result[2] ;
                    }
                }
            }
            //==================================================================
            // "UP.Browser"文字列を発見 = au
            //==================================================================
            else if (($upbPos = strpos($this->ua, "UP.Browser")) !== false)
            {
                $this->type = KWAPPA_TERM_AU ;
                // UAを" "で分割、最初のブロックのハイフン以降を取得
                $uaBlank = explode(" ", $this->ua, 2) ;
                $this->name = substr($uaBlank[0], strrpos($uaBlank[0], "-") + 1) ;
                // icc
                $this->icc = getenv("HTTP_X_UP_SUBNO") ;
            }
            //==================================================================
            // それ以外はPCからのアクセス
            //==================================================================
            else
            {
                $this->type = KWAPPA_TERM_PC ;
            }
        }
    }
    // アクセサ略
}
?>

なお、DoCoMoは近日「iモードID」として端末識別情報の送出をSoftBankに近い仕様にする予定。

重要なお知らせ : 『iモードID』の提供開始について | お知らせ | NTTドコモ

送出される文字列はおそらくいままでの個体識別番号と違うものになるだろう。個体識別による認証が随時行えるのは大変ありがたいのだが、仕事が増えるのが確定しているので今からちょっと憂鬱である。

まずは3キャリアに向けてXHTMLを出力する準備。

ポイントはDoCoMo端末にXHTMを出力する際、default_mimetypeを出力してやること。

ini_set関数の前に少しでも出力があるとtext/htmlが送られてしまうので注意。コントローラ部分で「echo “hoge=[{$hoge}]\n” ;」とかデバッグプリントしちゃうことがよくあると思うけど、それがあるとXHTMLとして解釈してくれなくなる。

au端末はヘッダに不備があると「このページは表示できません」というダイアログが出てレンダリングを中止してしまうため、開発中のプレビューには向いていない。

ついでにキャッシュの生存期間を出力している。kwappaは更新頻度の高いコンテンツで使われているので、キャッシュされることにはデメリットのほうが大きくなってしまうので。

ということでsmartyのヘッダ出力関数と、キャリア別に用意したヘッダ部分のテンプレートを掲載する。

<?php
/**
*  Smarty plugin {kwappa_header}
*
*  usage: {kwappa_header [title=$title]}
*
*  キャリア別ヘッダの出力
*
*/
// 実際はもっとcommonな場所でdefineしておくが…。
define("KWAPPA_TERM_PC"0) ;  // PCで見た場合(テスト用)
define("KWAPPA_TERM_DCM", 1) ;  // DoCoMo
define("KWAPPA_TERM_AU"2) ;  // au
define("KWAPPA_TERM_SB"3) ;  // SoftBank
function smarty_function_kwappa_header($value, &$smarty)
{
    // あらかじめコントローラでキャリア種別を取得しておく
    $term_type = $smarty->get_template_vars('term_type') ;
   
    // キャリアごとにヘッダを出力
    switch ($term_type)
    {
        case KWAPPA_TERM_DCM :
        {
            ini_set("default_mimetype", "application/xhtml+xml") ;
            $header_tpl = "inc/header_dcm.tpl" ;
            break ;
        }
        case KWAPPA_TERM_AU :
        {
            header("Cache-Control: no-cache") ;
            header('Expires: Sun, 10 Jan 1990 01:01:01 GMT');
            header('Pragma: no-cache');
            $header_tpl = "inc/header_au.tpl" ;
            break ;
        }
        case KWAPPA_TERM_SB :
        {
            header("Cache-Control: no-cache") ;
            $header_tpl = "inc/header_sb.tpl" ;
            break ;
        }
        default :
        {
            $header_tpl = "inc/header_dcm.tpl" ;
            break ;
        }
    }
    // タイトルをassign
    if (isset($value['title']))
    {
        $smarty->assign("kwappa_page_title", $value['title']) ;
    }
    // ヘッダを表示
    $smarty->display($header_tpl) ;
}
?>

header_dcm.tpl

<?xml version="1.0" encoding="Shift_JIS" ?>
<!DOCTYPE html PUBLIC "-//i-mode group (ja)//DTD XHTML i-XHTML(Locale/Ver.=ja/1.1) 1.0//EN" "i-xhtml_4ja_10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type"       content="application/xhtml+xml; charset=Shift_JIS" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<title>{$kwappa_page_title}</title>
</head>
<body>

header_au.tpl

<?xml version="1.0" encoding="Shift_JIS" ?>
<!DOCTYPE html PUBLIC "-//OPENWAVE//DTD XHTML 1.0//EN" "http://www.openwave.com/DTD/xhtml-basic.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type"       content="application/xhtml+xml; charset=Shift_JIS" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<title>{$kwappa_page_title}</title>
</head>
<body>
header_sb.tpl 
<?xml version="1.0" encoding="Shift_JIS" ?>
<!DOCTYPE html PUBLIC "-//J-PHONE//DTD XHTML Basic 1.0 Plus//EN" "xhtml-basic10-plus.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type"       content="application/xhtml+xml; charset=Shift_JIS" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<title>{$kwappa_page_title}</title>
</head>
<body>