Net_WebFinger
[ class tree: Net_WebFinger ] [ index: Net_WebFinger ] [ all elements ]

Source for file WebFinger.php

Documentation is available at WebFinger.php

  1. <?php
  2. /**
  3.  * Part of Net_WebFinger
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * @category Networking
  8.  * @package  Net_WebFinger
  9.  * @author   Christian Weiske <cweiske@php.net>
  10.  * @license  http://www.gnu.org/copyleft/lesser.html LGPL
  11.  * @link     http://pear.php.net/package/Net_WebFinger
  12.  */
  13.  
  14. require_once 'Net/WebFinger/Error.php';
  15. require_once 'Net/WebFinger/Reaction.php';
  16.  
  17. /**
  18.  * PHP WebFinger client. Performs discovery and returns a result.
  19.  *
  20.  * Fetches the well-known WebFinger URI
  21.  * https://example.org/.well-known/webfinger?resource=acct:user@example.org
  22.  *
  23.  * If that fails, the account's host's .well-known/host-meta file is fetched,
  24.  * then the file indicated by the "lrdd" type, as specificed by RFC 6415.
  25.  *
  26.  * <code>
  27.  * require_once 'Net/WebFinger.php';
  28.  * $wf = new Net_WebFinger();
  29.  * $react = $wf->finger('user@example.org');
  30.  * echo 'OpenID: ' . $react->openid . "\n";
  31.  * </code>
  32.  *
  33.  * @category Networking
  34.  * @package  Net_WebFinger
  35.  * @author   Christian Weiske <cweiske@php.net>
  36.  * @license  http://www.gnu.org/copyleft/lesser.html LGPL
  37.  * @link     http://pear.php.net/package/Net_WebFinger
  38.  */
  39. {
  40.     /**
  41.      * Retry with HTTP if the HTTPS webfinger request fails.
  42.      * This is not allowed by the webfinger specification, but may be
  43.      * helpful during development.
  44.      *
  45.      * @var boolean 
  46.      */
  47.     public $fallbackToHttp = false;
  48.  
  49.     /**
  50.      * HTTP client to use.
  51.      *
  52.      * @var HTTP_Request2 
  53.      */
  54.     protected $httpClient;
  55.  
  56.     /**
  57.      * Cache object to use (PEAR Cache package).
  58.      *
  59.      * @var Cache 
  60.      */
  61.     protected $cache;
  62.  
  63.     /**
  64.      * Set a HTTP client object that's used to fetch URLs.
  65.      *
  66.      * Useful to set an own user agent.
  67.      *
  68.      * @param object $httpClient HTTP_Request2 instance
  69.      *
  70.      * @return void 
  71.      */
  72.     public function setHttpClient(HTTP_Request2 $httpClient)
  73.     {
  74.         $this->httpClient = $httpClient;
  75.     }
  76.  
  77.     /**
  78.      * Set a cache object that's used to buffer XRD files.
  79.      *
  80.      * @param Cache $cache PEAR cache object
  81.      *
  82.      * @return void 
  83.      */
  84.     public function setCache(Cache $cache)
  85.     {
  86.         $this->cache = $cache;
  87.     }
  88.  
  89.     /**
  90.      * Finger a email address like identifier - get information about it.
  91.      *
  92.      * If an error occurs, you find it in the reaction's $error property.
  93.      *
  94.      * @param string $identifier E-mail address like identifier ("user@host")
  95.      *
  96.      * @return Net_WebFinger_Reaction Reaction object
  97.      *
  98.      * @see Net_WebFinger_Reaction::$error
  99.      */
  100.     public function finger($identifier)
  101.     {
  102.         $identifier strtolower($identifier);
  103.         $host       substr($identifierstrpos($identifier'@'+ 1);
  104.  
  105.         $react $this->loadWebfinger($identifier$host);
  106.  
  107.         if ($react->error === null{
  108.             //FIXME: only fallback if URL does not exist, not on general error
  109.             // like broken XML/JSON
  110.             return $react;
  111.         }
  112.  
  113.         //fall back to host-meta and LRDD file if webfinger URL does not exist
  114.         $hostMeta $this->loadHostMeta($host);
  115.         if ($hostMeta->error{
  116.             return $hostMeta;
  117.         }
  118.  
  119.         $react $this->loadLrdd($identifier$host$hostMeta);
  120.         if ($react->error
  121.             && $react->error->getCode(== Net_WebFinger_Error::NO_LRDD
  122.         {
  123.             $react->error = new Net_WebFinger_Error(
  124.                 'No webfinger data found',
  125.                 Net_WebFinger_Error::NOTHING,
  126.                 $react->error
  127.             );
  128.         }
  129.         return $react;
  130.     }
  131.  
  132.     /**
  133.      * Loads the webfinger JRD file for a given identifier
  134.      *
  135.      * @param string $identifier E-mail address like identifier ("user@host")
  136.      * @param string $host       Hostname of $identifier
  137.      *
  138.      * @return Net_WebFinger_Reaction Reaction object
  139.      *
  140.      * @see Net_WebFinger_Reaction::$error
  141.      */
  142.     protected function loadWebfinger($identifier$host)
  143.     {
  144.         $account 'acct:' $identifier;
  145.         $userUrl 'https://' $host '/.well-known/webfinger?resource='
  146.             . urlencode($account);
  147.  
  148.         $react $this->loadXrdCached($userUrl);
  149.  
  150.         if ($this->fallbackToHttp && $react->error !== null
  151.             && $this->isHttps($userUrl)
  152.         {
  153.             //fall back to HTTP
  154.             $userUrl 'http://' substr($userUrl8);
  155.             $react $this->loadXrdCached($userUrl);
  156.             $react->secure = false;
  157.         }
  158.         if ($react->error !== null{
  159.             return $react;
  160.         }
  161.  
  162.         $this->verifyDescribes($react$account);
  163.  
  164.         return $react;
  165.     }
  166.  
  167.     /**
  168.      * Load the host's .well-known/host-meta XRD file.
  169.      *
  170.      * The XRD is stored in the reaction object's $source['host-meta'] property,
  171.      * and any error that is encountered in its $error property.
  172.      *
  173.      * When the XRD file cannot be loaded, this method returns false.
  174.      *
  175.      * @param string $host Hostname to fetch host-meta file from
  176.      *
  177.      * @return Net_WebFinger_Reaction Reaction object
  178.      *
  179.      * @see Net_WebFinger_Reaction::$error
  180.      */
  181.     protected function loadHostMeta($host)
  182.     {
  183.         /**
  184.          * HTTPS is secure.
  185.          * xrd->describes() may not be used because the host-meta should not
  186.          * have a subject at all: http://tools.ietf.org/html/rfc6415#section-3.1
  187.          * > The document SHOULD NOT include a "Subject" element, as at this
  188.          * > time no URI is available to identify hosts.
  189.          * > The use of the "Alias" element in host-meta is undefined and
  190.          * > NOT RECOMMENDED.
  191.          */
  192.         $react = new Net_WebFinger_Reaction();
  193.         $react->secure = true;
  194.  
  195.         $react $this->loadXrdCached('https://' $host '/.well-known/host-meta');
  196.         if (!$react->error{
  197.             return $react;
  198.         }
  199.  
  200.         $react $this->loadXrdCached(
  201.             'http://' $host '/.well-known/host-meta'
  202.         );
  203.         //no https, so not secure
  204.         $react->secure = false;
  205.  
  206.         if (!$react->error{
  207.             return $react;
  208.         }
  209.  
  210.         $react->error = new Net_WebFinger_Error(
  211.             'No .well-known/host-meta file found on ' $host,
  212.             Net_WebFinger_Error::NO_HOSTMETA,
  213.             $react->error
  214.         );
  215.         return $react;
  216.     }
  217.  
  218.     /**
  219.      * Loads the user XRD file for a given identifier
  220.      *
  221.      * The XRD is stored in the reaction object's $userXrd property,
  222.      * any error is stored in its $error property.
  223.      *
  224.      * @param string $identifier E-mail address like identifier ("user@host")
  225.      * @param string $host       Hostname of $identifier
  226.      * @param object $hostMeta   host-meta XRD object
  227.      *
  228.      * @return Net_WebFinger_Reaction Reaction object
  229.      *
  230.      * @see Net_WebFinger_Reaction::$error
  231.      */
  232.     protected function loadLrdd($identifier$hostXML_XRD $hostMeta)
  233.     {
  234.         $link $hostMeta->get('lrdd''application/xrd+xml');
  235.         if ($link === null || !$link->template{
  236.             $react = new Net_WebFinger_Reaction();
  237.             $react->error = new Net_WebFinger_Error(
  238.                 'No lrdd link in host-meta for ' $host,
  239.                 Net_WebFinger_Error::NO_LRDD_LINK
  240.             );
  241.             $this->mergeHostMeta($react$hostMeta);
  242.             return $react;
  243.         }
  244.  
  245.         $account 'acct:' $identifier;
  246.         $userUrl str_replace('{uri}'urlencode($account)$link->template);
  247.  
  248.         $react $this->loadXrdCached($userUrl);
  249.         if ($react->error && $this->isHttps($userUrl)) {
  250.             //fall back to HTTP
  251.             $userUrl 'http://' substr($userUrl8);
  252.             $react $this->loadXrdCached($userUrl);
  253.         }
  254.         if ($react->error{
  255.             $react->error = new Net_WebFinger_Error(
  256.                 'LRDD file not found',
  257.                 Net_WebFinger_Error::NO_LRDD,
  258.                 $react->error
  259.             );
  260.             $this->mergeHostMeta($react$hostMeta);
  261.             return $react;
  262.         }
  263.  
  264.         if (!$this->isHttps($userUrl)) {
  265.             $react->secure = false;
  266.         }
  267.         $this->verifyDescribes($react$account);
  268.  
  269.         $this->mergeHostMeta($react$hostMeta);
  270.  
  271.         return $react;
  272.     }
  273.  
  274.     /**
  275.      * Merges some properties from the hostMeta file into the reaction object
  276.      *
  277.      * @param object $react    Target reaction object
  278.      * @param object $hostMeta Source hostMeta object
  279.      *
  280.      * @return void 
  281.      */
  282.     protected function mergeHostMeta(
  283.         Net_WebFinger_Reaction $reactNet_WebFinger_Reaction $hostMeta
  284.     {
  285.         foreach ($hostMeta->links as $link{
  286.             if ($link->rel == 'http://specs.openid.net/auth/2.0/provider'{
  287.                 $react->links[$link;
  288.             }
  289.         }
  290.         $react->secure = $react->secure && $hostMeta->secure;
  291.     }
  292.  
  293.     /**
  294.      * Verifies that the reaction object is about the given account URL.
  295.      * Sets the error property in the reaction object.
  296.      *
  297.      * @param object $react   Reaction object to check
  298.      * @param string $account acct: URL that the reaction should be about
  299.      *
  300.      * @return void 
  301.      */
  302.     protected function verifyDescribes(Net_WebFinger_Reaction $react$account)
  303.     {
  304.         if (!$react->describes($account)) {
  305.             $react->error = new Net_WebFinger_Error(
  306.                 'Webfinger file is not about "' $account '"'
  307.                 . ' but "' $react->subject . '"',
  308.                 Net_WebFinger_Error::DESCRIBE
  309.             );
  310.             //additional hint that something is wrong
  311.             $react->secure = false;
  312.         }
  313.     }
  314.  
  315.     /**
  316.      * Check whether the URL is an HTTPS URL.
  317.      *
  318.      * @param string $url URL to check
  319.      *
  320.      * @return boolean True if it's a HTTPS url
  321.      */
  322.     protected function isHttps($url)
  323.     {
  324.         return substr($url08== 'https://';
  325.     }
  326.  
  327.     /**
  328.      * Load an XRD file and caches it.
  329.      *
  330.      * @param string $url URL to fetch
  331.      *
  332.      * @return Net_WebFinger_Reaction Reaction object with XRD data
  333.      *
  334.      * @see loadXrd()
  335.      */
  336.     protected function loadXrdCached($url)
  337.     {
  338.         if (!$this->cache{
  339.             return $this->loadXrd($url);
  340.         }
  341.  
  342.         //FIXME: make $host secure, remove / and so
  343.         $cacheId 'webfinger-cache-' str_replace(
  344.             array('/'':')'-.-'$url
  345.         );
  346.         $cacheRetval $this->cache->get($cacheId);
  347.         if ($cacheRetval !== null{
  348.             //load from cache
  349.             return $cacheRetval;
  350.         }
  351.  
  352.         //no cache yet
  353.         $retval $this->loadXrd($url);
  354.  
  355.         //we do not implement http caching headers yet, so use default
  356.         // cache lifetime settings
  357.         $this->cache->save($cacheId$retval);
  358.  
  359.         return $retval;
  360.     }
  361.  
  362.     /**
  363.      * Loads the XRD file from the given URL.
  364.      * Sets $react->error when loading fails
  365.      *
  366.      * @param string $url URL to fetch
  367.      *
  368.      * @return boolean True if loading data succeeded, false if not
  369.      */
  370.     protected function loadXrd($url)
  371.     {
  372.         try {
  373.             $react = new Net_WebFinger_Reaction();
  374.             $react->url = $url;
  375.             $react->error = null;
  376.             if ($this->httpClient !== null{
  377.                 $this->httpClient->setUrl($url);
  378.                 $this->httpClient->setHeader(
  379.                     'user-agent''PEAR Net_WebFinger'true
  380.                 );
  381.                 $this->httpClient->setHeader(
  382.                     'accept',
  383.                     'application/jrd+json, application/xrd+xml;q=0.9',
  384.                     true
  385.                 );
  386.                 $res $this->httpClient->send();
  387.                 $code $res->getStatus();
  388.                 if (intval($code / 100!== 2{
  389.                     throw new Net_WebFinger_Error(
  390.                         'Error loading XRD file: ' $res->getStatus()
  391.                         . ' ' $res->getReasonPhrase(),
  392.                         Net_WebFinger_Error::NOT_FOUND
  393.                     );
  394.                 }
  395.                 $react->loadString($res->getBody());
  396.             else {
  397.                 $context stream_context_create(
  398.                     array(
  399.                         'http' => array(
  400.                             'user-agent' => 'PEAR Net_WebFinger',
  401.                             'header' => 'accept: application/jrd+json, application/xrd+xml;q=0.9',
  402.                             'follow_location' => true,
  403.                             'max_redirects' => 20
  404.                         )
  405.                     )
  406.                 );
  407.                 $content @file_get_contents($urlfalse$context);
  408.                 if ($content === false{
  409.                     $msg 'Error loading XRD file';
  410.                     if (isset($http_response_header)) {
  411.                         $status = null;
  412.                         //we need this because there will be several HTTP/..
  413.                         // status lines when redirection is going on.
  414.                         foreach ($http_response_header as $header{
  415.                             if (substr($header05== 'HTTP/'{
  416.                                 $status $header;
  417.                             }
  418.                         }
  419.                         $msg .= ': ' $status;
  420.                     };
  421.                     throw new Net_WebFinger_Error(
  422.                         $msgNet_WebFinger_Error::NOT_FOUND,
  423.                         new Net_WebFinger_Error(
  424.                             'file_get_contents on ' $url
  425.                         )
  426.                     );
  427.                 }
  428.                 $react->loadString($content);
  429.             }
  430.         catch (Exception $e{
  431.             $react->error = $e;
  432.         }
  433.  
  434.         return $react;
  435.     }
  436.  
  437. }
  438.  
  439. ?>

Documentation generated on Mon, 04 Nov 2013 14:00:04 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.