<?php
/**
 * DigestMd5.
 * @package magic.core
 * @subpackage tool.mail.auth
 */
/**
 * DIGEST-MD5認証で使用するクラスです.
 * <p>
 * このクラスは必要な時に{@link SmtpMailer}によってロードされます。
 * </p>
 * @package magic.core
 * @subpackage tool.mail.auth
 * @author T.Okumura
 * @version 1.0.0
 * @final
 */
final class DigestMd5 {
    /**
     * コンストラクタ.
     */
    private function __construct() {
    }
    /**
     * レスポンスを取得します.
     * @param string $user 接続ユーザー名
     * @param string $pass 接続パスワード
     * @param string $challenge チャレンジ
     * @param string $host SMTPホスト
     * @param string $service 'smtp'文字列
     * @return mixed レスポンス、パースに失敗した場合はFALSE
     * @static
     */
    public static function getResponse($user, $pass, $challenge, $host, $service) {
        $challenge = self::_parseChallenge($challenge);
        if (empty($challenge)) {
            return FALSE;
        }
        $cnonce = self::_getCnonce();
        $uri = $service . '/' . $host;
        $response = $this->_getResponseValue($user, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $uri);
        if ($challenge['realm']) {
            return 'username="' . $user . '",realm="' . $challenge['realm'] . '",nonce="' . $challenge['nonce']
                    . '",cnonce="' . $cnonce . '",nc=00000001,qop=auth,digest-uri="' . $uri . '",response=' . $response
                    . ',maxbuf=' . $challenge['maxbuf'];
        } else {
            return 'username="' . $user . '",nonce="' . $challenge['nonce'] . '",cnonce="' . $cnonce
                    . '",nc=00000001,qop=auth,digest-uri="' . $uri . '",response=' . $response . ',maxbuf='
                    . $challenge['maxbuf'];
        }
    }
    /**
     * チャレンジをパースします.
     *
     * @param string $challenge チャレンジ
     * @return array パース結果の配列
     * @static
     */
    private static function _parseChallenge($challenge) {
        $tokens = array();
        while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
            if ($matches[1] === 'opaque' || $matches[1] === 'domain') {
                $challenge = substr($challenge, strlen($matches[0]) + 1);
                continue;
            }
            if (!empty($tokens[$matches[1]]) && ($matches[1] === 'realm' OR $matches[1] === 'auth-param')) {
                if (is_array($tokens[$matches[1]])) {
                    $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
                } else {
                    $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
                }
            } elseif (!empty($tokens[$matches[1]])) {
                $tokens = array();
                break;
            } else {
                $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
            }
            $challenge = substr($challenge, strlen($matches[0]) + 1);
        }
        if (empty($tokens['realm'])) {
            $tokens['realm'] = '';
        }
        if (empty($tokens['maxbuf'])) {
            $tokens['maxbuf'] = 65536;
        }
        if (empty($tokens['nonce']) || empty($tokens['algorithm'])) {
            return array();
        }
        return $tokens;
    }
    /**
     * cnonceを取得します.
     * @return string cnonce
     * @static
     */
    private static function _getCnonce() {
        $str = '';
        for ($i = 0; $i < 32; $i++) {
            $str .= chr(mt_rand(0, 255));
        }
        return base64_encode($str);
    }
    /**
     * レスポンスバリューを取得します.
     * @param string $user 接続ユーザー名
     * @param string $pass 接続パスワード
     * @param string $realm チャレンジのrealm
     * @param string $nonce チャレンジのnonce
     * @param string $cnonce _getCnonce()の結果
     * @param string $uri 'smtp/[SMTPのホスト名]'文字列
     * @return string レスポンスバリュー
     * @static
     */
    private static function _getResponseValue($user, $pass, $realm, $nonce, $cnonce, $uri) {
        $pack = pack('H32', md5($user . ':' . $realm . ':' . $pass)) . ':' . $nonce . ':' . $cnonce;
        return md5(md5($pack) . ':' . $nonce . ':00000001:' . $cnonce . ':auth:' . md5('AUTHENTICATE:' . $uri));
    }
}
// EOF.