Advertisement
Guest User

Mike Boers

a guest
Feb 15th, 2009
325
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 14.95 KB | None | 0 0
  1. <?php
  2. /**
  3. * File for N_Uri class.
  4. * @package NitrogenPHP
  5. * @see N_Uri
  6. **/
  7. /**
  8. * A convenient description of "uniform resource identifiers" or "data source names".
  9. *
  10. * It roughly follows the uri specifications linked to by the given wikipedia
  11. * page, but a few things are not accounted for yet.
  12. *   - may encode more characters than nessesary in certain parts
  13. *
  14. * User info (keyed by "userinfo") is represented by a numerically keyed array
  15. * of segments. A zero-length array implies there is no user info section in the
  16. * URI.
  17. *
  18. * Paths are represented by a numerically keyed array of path segments.
  19. * A non-empty URI will always have at least one section (even if empty).
  20. * Absolute paths have a zero-length (ie '') first element (at index 0).
  21. *
  22. * URI components are access with array syntax. Note that getting the value of
  23. * the userinfo, path, or query paths will return an object that must either be
  24. * cast to a string, or accessed by array access as well.
  25. *
  26. * Examples:
  27. * <code>
  28. * $uri = new N_Uri('http://johndoe:password@example.com/path/to/example?key=value');
  29. *
  30. * echo $uri . "\n";
  31. * // http://johndoe:password@example.com/path/to/example?key=value
  32. *
  33. * echo $uri['userinfo'] . "\n";
  34. * // johndoe:password
  35. *
  36. * $uri['host'] = 'whatever.org';
  37. * $uri['userinfo'][0] = 'jim';
  38. * echo $uri . "\n";
  39. * // http://jim:password@whatever.org/path/to/example?key=value
  40. * </code>
  41. *
  42. * Non-standard parts will be assumed to be from the query, and will be
  43. * get or set appropriately. Note that non-existant parts will always return
  44. * null WITHOUT throwing an error.
  45. * <code>
  46. * $uri = 'sqlite://path/to/database';
  47. * $uri['table'] = 'some_table';
  48. * echo $uri . "\n";
  49. * // sqlite://path/to/database?table=some_table
  50. * </code>
  51. *
  52. * @see http://en.wikipedia.org/wiki/Uniform_Resource_Identifier
  53. * @see http://tools.ietf.org/html/rfc3986
  54. * @package NitrogenPHP
  55. **/
  56. class N_Uri implements ArrayAccess, Serializable, IteratorAggregate
  57. {  
  58.     /**
  59.     * @var string The delimiters between major components in a URI string.
  60.     **/
  61.     const GEN_DELIMS = ":/?#[]@";
  62.    
  63.     /**
  64.     * @var string The delimiters within components of a URI string.
  65.     **/
  66.     // I had to make this with single quotes, because the VPS choked and thought I was refering to a $& variable or something.
  67.     const SUB_DELIMS = '!$&\'()*+,;=';
  68.    
  69.     /**
  70.     * @var string All of the fully URI safe (never need encoding) characters.
  71.     **/
  72.     const SAFE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
  73.    
  74.     /**
  75.     * Encode non-safe characters.
  76.     * @param string $string The string to encode unsafe characters in.
  77.     * @param string $safe Other characters to consider safe in addition to N_Uri::SAFE
  78.     * @return string
  79.     **/
  80.     public static function encode($string, $safe = '')
  81.     {
  82.         // prepare an array of the safe characters
  83.         $safe .= self::SAFE;
  84.         $safe  = array_unique(str_split($safe));
  85.  
  86.         // prepare a character mapping with the safe characters mapping to themselves
  87.         $char_map = Array();
  88.         foreach ($safe as $char)
  89.         {
  90.             $char_map[$char] = $char;
  91.         }
  92.        
  93.         // iterate through the string, mapping the characters
  94.         $return = '';
  95.         foreach (str_split($string) as $char)
  96.         {
  97.             // add escaped characters to the mapping if they are not found
  98.             if (!isset($char_map[$char]))
  99.             {
  100.                 $char_map[$char] = rawurlencode($char);
  101.             }
  102.  
  103.             $return .= $char_map[$char];
  104.         }
  105.         return $return;
  106.     }
  107.  
  108.     /**
  109.     * Decode all URI encoded characters in a given string.
  110.     * @param string
  111.     * @return string
  112.     **/
  113.     public static function decode($string)
  114.     {
  115.         return rawurldecode($string);
  116.     }
  117.    
  118.     /**
  119.     * Parse a URI string into its major components.
  120.     *
  121.     * A non-existant scheme, host, port or fragment will be null, and a
  122.     * non-existant userinfo, path, or query will be a zero-length array.
  123.     *
  124.     * @param string $uri
  125.     * @return array
  126.     **/
  127.     public static function parseUriString($input_string)
  128.     {  
  129.         // parse out 5 major parts
  130.         preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/', $input_string, $m);
  131.         //            12             3    4           5       6  7        8 9
  132.  
  133.         $scheme    = isset($m[1]) && $m[1] != '' ? $m[2] : null;
  134.         $authority = isset($m[3])                ? $m[4] : null;
  135.         $path      = $m[5];
  136.         $query     = isset($m[6])                ? $m[7] : null;
  137.         $fragment  = isset($m[8])                ? $m[9] : null;
  138.        
  139.         // parse the authority
  140.         // NOTE: port should only be an int, but I am matching everything
  141.         if ($authority)
  142.         {
  143.             preg_match('/^(([^@]*)@)?([^:]*)(:(.*))?$/', $authority, $m);
  144.             //            12          3      4 5
  145.        
  146.             $userinfo = isset($m[1]) && $m[1] != '' ? $m[2] : null;
  147.             $host     = $m[3];
  148.             $port     = isset($m[4])                ? $m[5] : null;
  149.         }
  150.         else
  151.         {
  152.             $userinfo = $host = $port = null;
  153.         }
  154.        
  155.         return array(
  156.             'scheme'   => $scheme   === null ? null : rawurldecode($scheme),
  157.             'userinfo' => self::parseUserinfo($userinfo),
  158.             'host'     => $host     === null ? null : rawurldecode($host),
  159.             'port'     => $port     === null ? null : (ctype_digit($port) ? (int)$port : rawurldecode($port)),
  160.             'path'     => self::parsePath($path),
  161.             'query'    => self::parseQuery($query),
  162.             'fragment' => $fragment === null ? null : rawurldecode($fragment),
  163.         );
  164.     }
  165.    
  166.     /**
  167.     * Parse the user info part of a URI into an array, decoding each element.
  168.     * @param string $input_string
  169.     * @return array
  170.     **/
  171.     public static function parseUserinfo($input_string)
  172.     {
  173.         $input_string = (string)$input_string;
  174.         if ($input_string == '')
  175.         {
  176.             return array();
  177.         }
  178.         return array_map('rawurldecode', explode(':', $input_string));
  179.     }
  180.    
  181.     /**
  182.     * Build a userinfo string from an array, encoding as nessesary
  183.     * @param array $input_array
  184.     * @return string
  185.     **/
  186.     protected static function buildUserinfo($input_array)
  187.     {
  188.         $encoded = Array();
  189.         foreach ($input_array as $segment)
  190.         {
  191.             $encoded[] = self::encode($segment, self::SUB_DELIMS);
  192.         }
  193.         return implode(':', $encoded);
  194.     }
  195.    
  196.     /**
  197.     * Parse the path part of a URI into an array.
  198.     * A zero-length string at index 0 indicates an absolute path.
  199.     * @param string $input_string
  200.     * @return array
  201.     **/
  202.     protected static function parsePath($input_string)
  203.     {
  204.         $input_string = (string)$input_string;
  205.         if ('' == $input_string)
  206.         {
  207.             return array();
  208.         }
  209.         return array_map('rawurldecode', explode('/', $input_string));
  210.     }
  211.    
  212.     /**
  213.     * Build a path string from an array, encoding as nessesary.
  214.     * @param array $input_array
  215.     * @return string
  216.     **/
  217.     protected static function buildPath($input_array)
  218.     {
  219.         $encoded = Array();
  220.         foreach ($input_array as $segment)
  221.         {
  222.             $encoded[] = self::encode($segment, self::SUB_DELIMS . '@:');
  223.         }
  224.         return implode('/', $encoded);
  225.     }
  226.    
  227.     /**
  228.     * Parse the query part of a URI into an associative array.
  229.     * @param string $input_string
  230.     * @return array
  231.     **/
  232.     protected static function parseQuery($input_string)
  233.     {
  234.         parse_str($input_string, $query_data_array);
  235.         return $query_data_array;
  236.     }
  237.    
  238.     protected static function buildQuery($input_array)
  239.     {
  240.         return http_build_query($input_array);
  241.     }
  242.    
  243.     /**
  244.     * @var array All of the URI data goes in this array.
  245.     **/
  246.     protected $_data;
  247.    
  248.     /**
  249.     * Construct a uri from a string or associative array.
  250.     *
  251.     * <code>
  252.     * $uri1 = new N_Uri('http://mike:boers@website.com/path/to/thing');
  253.     * $uri2 = new N_Uri(array(
  254.     *   'scheme' => 'http',
  255.     *   'userinfo' => array('mike', 'boers'),
  256.     *   'host' => 'website.com',
  257.     *   'path' => array('', 'path', 'to', 'thing'),
  258.     * );
  259.     * $uri3 = new N_Uri(array(
  260.     *   'scheme' => 'http',
  261.     *   'userinfo' => 'mike:boers',
  262.     *   'host' => 'website.com',
  263.     *   'path' => '/path/to/thing',
  264.     * );
  265.     * </code>
  266.     *
  267.     * @param string|array $input
  268.     **/
  269.     public function __construct($input = '')
  270.     {
  271.         if ($input instanceof self)
  272.         {
  273.             $this->_data = $input->_data;
  274.         }
  275.         elseif (is_array($input) || ($input instanceof Traversable))
  276.         {
  277.             $this->_data = self::parseUriString(''); // setup the defaults
  278.             foreach ($input as $key => $value)
  279.             {
  280.                 $this[$key] = $value; // note that this uses the ArrayAccess, and so handles types properly
  281.             }
  282.         }
  283.         else
  284.         {
  285.             $this->_data = self::parseUriString((string)$input);
  286.         }
  287.     }
  288.    
  289.     /**
  290.     * Parse a string, input an array, or return a N_Uri
  291.     * @param string|array|N_Uri $input
  292.     * @return N_Uri
  293.     **/
  294.     public static function factory($uri)
  295.     {
  296.         if ($uri instanceof self)
  297.         {
  298.             return $uri;
  299.         }
  300.         return new self($uri);
  301.     }
  302.    
  303.     /**
  304.     * Get a string representation of the URI.
  305.     * @return string
  306.     **/
  307.     public function __toString()
  308.     {
  309.         $uri = '';
  310.        
  311.         // append the scheme
  312.         if (null !== $this->_data['scheme'])
  313.         {
  314.             $uri .= self::encode($this->_data['scheme'], '+') . ':';
  315.         }
  316.        
  317.         // append the authority
  318.         $has_authority = $this->_data['userinfo'] || null !== $this->_data['host'] || null !== $this->_data['port'];
  319.         if ($has_authority)
  320.         {
  321.             $uri .= '//';
  322.             $uri .= $this->_data['userinfo'] ? self::buildUserinfo($this->_data['userinfo']) . '@' : '';
  323.             $uri .= self::encode($this->_data['host'], self::SUB_DELIMS);
  324.             $uri .= (null !== $this->_data['port'] && '' !== $this->_data['port'] ? ':' . self::encode($this->_data['port']) : '');
  325.         }
  326.        
  327.         // append the path
  328.         // TODO: conform this to section 3.3 on the reference
  329.         if (Array('') != $this->_data['path'])
  330.         {
  331.             // encode most of it
  332.             $path = Array();
  333.             foreach ($this->_data['path'] as $row)
  334.             {
  335.                 $path[] = self::encode($row, self::SUB_DELIMS . '@:');
  336.             }
  337.            
  338.             // no scheme -> no colon in first chunk
  339.             if (null === $this->_data['scheme'])
  340.             {
  341.                 $path = $this->_data['path'];
  342.                 $path[0] = str_replace(':', rawurlencode(':'), $path[0]);
  343.             }
  344.            
  345.            
  346.             // no authority -> cannot have empty segments at beggining
  347.             if (!$has_authority)
  348.             {
  349.                 while ($path && $path[0] == '')
  350.                 {
  351.                     array_shift($path);
  352.                 }
  353.             }
  354.             // authority -> must either be empty or have a preceding slash
  355.             elseif ($path && $path[0]) // there is a path and the first is not blank
  356.             {
  357.                 array_unshift($path, '');
  358.             }  
  359.            
  360.             $uri .= implode('/', $path);
  361.         }
  362.        
  363.         // append the query
  364.         if ($this->_data['query'])
  365.         {
  366.             $uri .= '?' . http_build_query($this->_data['query']);
  367.         }
  368.        
  369.         // append the fragment
  370.         if (null !== $this->_data['fragment'])
  371.         {
  372.             $uri .= '#' . self::encode($this->_data['fragment'], '/?');
  373.         }
  374.        
  375.         return $uri;
  376.     }
  377.    
  378.    
  379.    
  380.  
  381.     public function getSchemeParent()
  382.     {
  383.         switch ($this->_data['scheme'])
  384.         {
  385.             case 'mysql':
  386.             case 'sqlite':
  387.                 return 'database';
  388.                
  389.             case 'file':
  390.                 return 'file';
  391.                
  392.             case 'udp':
  393.             case 'tcp':
  394.             case 'unix':
  395.                 return 'socket';
  396.         }
  397.         return $this->_data['scheme'];
  398.     }
  399.    
  400.     public function offsetExists($offset)
  401.     {
  402.         switch ($offset)
  403.         {
  404.             case 'scheme':
  405.             case 'host':
  406.             case 'port':
  407.             case 'fragment':
  408.                 return is_string($this->_data[$offset]);
  409.                
  410.             case 'userinfo':
  411.             case 'path':
  412.             case 'query':
  413.                 return (bool) count($this->_data[$offset]);
  414.                
  415.             default:
  416.                 return isset($this->_data['query'][$offset]);
  417.         }
  418.     }
  419.    
  420.     public function offsetUnset($offset)
  421.     {
  422.         switch ($offset)
  423.         {
  424.             case 'scheme':
  425.             case 'host':
  426.             case 'port':
  427.             case 'fragment':
  428.                 $this->_data[$offset] = null;
  429.                 break;
  430.                
  431.             case 'userinfo':
  432.             case 'path':
  433.             case 'query':
  434.                 $this->_data[$offset] = array();
  435.                 break;
  436.            
  437.             default:
  438.                 unset($this->_data['query'][$offset]);
  439.                 break;
  440.         }
  441.     }
  442.    
  443.     public function offsetGet($offset)
  444.     {
  445.         switch ($offset)
  446.         {
  447.             case 'scheme':
  448.             case 'host':
  449.             case 'port':
  450.             case 'fragment':
  451.                 return $this->_data[$offset];
  452.                 break;
  453.  
  454.             case 'userinfo':
  455.             case 'path':
  456.                 return new N_Uri_SequenceAccessor($this, $offset);
  457.                
  458.             case 'query':
  459.                 return new N_Uri_QueryAccessor($this, $offset);
  460.  
  461.             default:
  462.                 return isset($this->_data['query'][$offset]) ? $this->_data['query'][$offset] : null;
  463.         }
  464.     }
  465.    
  466.     public function offsetSet($offset, $value)
  467.     {
  468.         switch ($offset)
  469.         {
  470.             case 'scheme':
  471.             case 'host':
  472.             case 'fragment':
  473.                 $this->_data[$offset] = (string)$value;
  474.                 break;
  475.            
  476.             case 'port':
  477.                 $this->_data[$offset] = is_int($value) || ctype_digit($value) ? (int)$value : $value;
  478.                 break;
  479.            
  480.             case 'userinfo':   
  481.             case 'path':
  482.             case 'query':
  483.                 $callback = array(__CLASS__, 'parse' . ucfirst($offset));
  484.                 $this->_data[$offset] = is_array($value) ? $value : call_user_func($callback, $value);
  485.                 break;
  486.            
  487.             default:
  488.                 $this->_data['query'][$offset] = $value;
  489.                 break;
  490.         }
  491.     }
  492.    
  493.     public function serialize()
  494.     {
  495.         return $this->__toString();
  496.     }
  497.    
  498.     public function unserialize($serialized)
  499.     {
  500.         $this->__construct($serialized);
  501.     }
  502.    
  503.     public function getIterator()
  504.     {
  505.         return new ArrayIterator($this->_data);
  506.     }
  507. }
  508.  
  509.  
  510. class N_Uri_SequenceAccessor extends N_Uri implements ArrayAccess
  511. {
  512.     protected $_uri;
  513.     protected $_type;
  514.    
  515.     public function __construct(N_Uri $uri, $type)
  516.     {
  517.         $this->_uri  = $uri;
  518.         $this->_type = $type;
  519.         switch ($type)
  520.         {
  521.             case 'userinfo':
  522.             case 'path':
  523.                 break;
  524.                
  525.             default:
  526.                 throw new Exception('Type must be valid.');
  527.         }
  528.     }
  529.    
  530.     public function __toString()
  531.     {
  532.         switch ($this->_type)
  533.         {
  534.             case 'path':
  535.                 return N_Uri::buildPath($this->_uri->_data[$this->_type]);
  536.            
  537.             case 'userinfo':
  538.                 return N_Uri::buildUserinfo($this->_uri->_data[$this->_type]);
  539.         }      
  540.     }
  541.    
  542.     protected function _normalizeOffset($offset)
  543.     {  
  544.         $count   = count($this->_uri->_data[$this->_type]);
  545.         $offset  = is_int($offset) ? min($offset, $count) : $count;
  546.         return $offset + ($offset < 0 ? count($this->_uri->_data[$this->_type]) : 0);
  547.     }
  548.    
  549.     public function offsetExists($offset)
  550.     {
  551.         $offset = $this->_normalizeOffset($offset);
  552.         return isset($this->_uri->_data[$this->_type][$offset]);
  553.     }
  554.    
  555.     public function offsetUnset($offset)
  556.     {
  557.         $offset = $this->_normalizeOffset($offset);
  558.         unset($this->_uri->_data[$this->_type][$offset]);
  559.         $this->_uri->_data[$this->_type] = array_values($this->_uri->_data[$this->_type]);
  560.     }
  561.    
  562.     public function offsetGet($offset)
  563.     {
  564.         $offset = $this->_normalizeOffset($offset);
  565.         return $this->_uri->_data[$this->_type][$offset];
  566.     }
  567.    
  568.     public function offsetSet($offset, $value)
  569.     {
  570.         if (!is_int($offset) && !is_null($offset))
  571.         {
  572.             throw new Exception('Offset must be an integer.');
  573.         }
  574.         else
  575.         {
  576.             $offset = $this->_normalizeOffset($offset);
  577.             $this->_uri->_data[$this->_type][$offset] = (string)$value;
  578.         }
  579.     }
  580. }
  581.  
  582. class N_Uri_QueryAccessor extends N_Uri implements ArrayAccess
  583. {
  584.     protected $_uri;
  585.     protected $_key = 'query';
  586.    
  587.     public function __construct(N_Uri $uri)
  588.     {
  589.         $this->_uri = $uri;
  590.     }
  591.    
  592.     public function __toString()
  593.     {
  594.         return self::buildQuery($this->_uri->_data[$this->_key]);
  595.     }
  596.    
  597.     public function offsetExists($offset)
  598.     {
  599.         return isset($this->_uri->_data[$this->_key][$offset]);
  600.     }
  601.    
  602.     public function offsetUnset($offset)
  603.     {
  604.         unset($this->_uri->_data[$this->_key][$offset]);
  605.     }
  606.    
  607.     public function offsetGet($offset)
  608.     {
  609.         return $this->_uri->_data[$this->_key][$offset];
  610.     }
  611.    
  612.     public function offsetSet($offset, $value)
  613.     {
  614.         $this->_uri->_data[$this->_key][$offset] = $value;
  615.     }
  616. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement