Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00%
0 / 1
0.00%
0 / 8
CRAP
25.97%
40 / 154
Util
0.00%
0 / 1
0.00%
0 / 8
1148.88
25.97%
40 / 154
 urlEncode ($parms)
0.00%
0 / 1
5.02
60.00%
6 / 10
 mergeCurlOptions ($base, $additional)
0.00%
0 / 1
132
0.00%
0 / 21
 ensureCurlErrorConstants ()
0.00%
0 / 1
12
0.00%
0 / 25
 addQueryData ($url, $parms)
0.00%
0 / 1
5.07
85.71%
12 / 14
 assembleUrl ($parts)
0.00%
0 / 1
9.80
78.57%
22 / 28
 parseCookieHeader ($hdr)
0.00%
0 / 1
90
0.00%
0 / 23
 parseCookieElement ($elm)
0.00%
0 / 1
110
0.00%
0 / 32
 __construct ()
0.00%
0 / 1
2
0.00%
0 / 1
<?php
/**
 * @package Moar\Net\Http
 */
namespace Moar\Net\Http;
/**
 * HTTP utilities.
 *
 * @package Moar\Net\Http
 */
class Util {
  const COOKIE_NAME = 'cookie-name';
  const COOKIE_VALUE = 'cookie-value';
  /**
   * Make a URL-encoded string from a key=>value array
   * @param array $parms Parameter array
   * @return string URL-encoded message body
   */
  public static function urlEncode ($parms) {
    $payload = array();
    foreach ($parms as $key => $value) {
      if (is_array($value)) {
        foreach ($value as $item) {
          $payload[] = urlencode($key) . '=' . urlencode($item);
        }
      } else {
        $payload[] = urlencode($key) . '=' . urlencode($value);
      }
    }
    return implode('&', $payload);
  } //end urlEncode
  /**
   * Merge two arrays of curl options together into a new array.
   *
   * Values in the additional array will override values in the base array
   * except in the case of CURLOPT_HTTPHEADER where values from the additional
   * array will be merged with values from the base array if any exist.
   *
   * The additional array can be keyed with either ints which will be assumed
   * to be CURLOPT_* values or strings such as 'CURLOPT_USERPWD' which will be
   * turned into ints via constant(). This makes setting up options in a DI or
   * other non-php scripted senario easier to implement.
   *
   * @param array $base Base options
   * @param array $additional Additional options
   * @return array New array of base options with additional options overlayed
   * @throws \InvalidArgumentException If invalid string key is used
   */
  public static function mergeCurlOptions ($base, $additional) {
    if (null === $additional || empty($additional)) {
      return $base;
    }
    foreach ($additional as $key => $val) {
      if (!is_int($key)) {
        // treat non-numeric keys as CURLOPT_* constants to be resolved
        try {
          $curlkey = constant($key);
        } catch (Exception $ignored) {
          // no-op
        }
        if (null === $curlkey) {
          throw new \InvalidArgumentException("Invalid curl option [{$key}].");
        }
        $key = $curlkey;
      }
      if (CURLOPT_HTTPHEADER == $key &&
          array_key_exists(CURLOPT_HTTPHEADER, $base)) {
        // additional headers are cumlative
        if (is_array($val)) {
          // concatinate header collection
          foreach ($val as $header) {
            $base[CURLOPT_HTTPHEADER][] = $header;
          }
        } else {
          // add single header
          $base[CURLOPT_HTTPHEADER][] = $val;
        }
      } else {
        $base[$key] = $val;
      }
    } //end foreach
    return $base;
  } //end mergeCurlOptions
  /**
   * Ensure that constants are available for useful cURL error codes.
   *
   * @return void
   * @see http://curl.haxx.se/libcurl/c/libcurl-errors.html
   */
  public static function ensureCurlErrorConstants () {
    $defs = array(
      'CURLE_COULDNT_CONNECT' => 7,
      'CURLE_COULDNT_RESOLVE_HOST' => 6,
      'CURLE_HTTP_RETURNED_ERROR' => 22,
      'CURLE_OPERATION_TIMEDOUT' => 28,
      'CURLE_PEER_FAILED_VERIFICATION' => 51,
      'CURLE_SSL_CACERT' => 60,
      'CURLE_SSL_CACERT_BADFILE' => 77,
      'CURLE_SSL_CERTPROBLEM' => 58,
      'CURLE_SSL_CIPHER' => 59,
      'CURLE_SSL_CONNECT_ERROR' => 35,
      'CURLE_SSL_CRL_BADFILE' => 82,
      'CURLE_SSL_ENGINE_INITFAILED' => 66,
      'CURLE_SSL_ENGINE_NOTFOUND' => 53,
      'CURLE_SSL_ENGINE_SETFAILED' => 54,
      'CURLE_SSL_ISSUER_ERROR' => 83,
      'CURLE_SSL_SHUTDOWN_FAILED' => 80,
      'CURLE_UNSUPPORTED_PROTOCOL' => 1,
      'CURLE_URL_MALFORMAT' => 3,
      'CURLE_USE_SSL_FAILED' => 64,
    );
    foreach ($defs as $constName => $errCode) {
      if (!defined($constName)) {
        define($constName, $errCode);
      }
    }
  } //end ensureCurlConstants
  /**
   * Append a query string to the given URL.
   *
   * @param string $url URL to append to
   * @param string|array $parms Parameters to add as query string to url
   * @return string Composed URL
   */
  public static function addQueryData ($url, $parms) {
    if (is_array($parms)) {
      // construct GET data
      $payload = self::urlEncode($parms);
    } else if (null !== $parms) {
      $payload = (string) $parms;
    }
    if (!empty($payload)) {
      $parts = parse_url($url);
      if (isset($parts['query'])) {
        $parts['query'] .= "&{$payload}";
      } else {
        $parts['query'] = $payload;
      }
      $url = self::assembleUrl($parts);
    }
    return $url;
  } //end addQueryData
  /**
   * Assemble a URL from a array of components as would be returned by
   * parse_url().
   *
   * @param array $parts URL parts
   * @return string URL
   */
  public static function assembleUrl ($parts) {
    $url = '';
    if (isset($parts['scheme'])) {
      $url .= "{$parts['scheme']}:";
    }
    $url .= '//';
    if (isset($parts['user'])) {
      $url .= $parts['user'];
      if (isset($parts['password'])) {
        $url .= ":{$parts['password']}";
      }
      $url .= '@';
    }
    if (isset($parts['host'])) {
      $url .= $parts['host'];
    }
    if (isset($parts['port'])) {
      $url .= ":{$parts['port']}";
    }
    if (isset($parts['path'])) {
      $url .= $parts['path'];
    }
    if (isset($parts['query'])) {
      $url .= "?{$parts['query']}";
    }
    if (isset($parts['fragment'])) {
      $url .= "#{$parts['fragment']}";
    }
    return $url;
  } //end assembleUrl
  /**
   * Parse a "Set-Cookie" header to get the component cookie data.
   *
   * @param string $hdr Header data
   * @return array Collection of cookies specified by the header
   */
  public static function parseCookieHeader ($hdr) {
    $cookies = array();
    $i = 0;
    $from = 0;
    $len = mb_strlen($hdr, 'latin1');
    $quoted = false;
    while ($i < $len) {
      if ('"' === $hdr[$i] && '\\' !== $elm[$i - 1]) {
        $quoted = !$quoted;
      }
      $elm = null;
      if (!$quoted && ',' === $hdr[$i]) {
        $chunk = mb_substr($hdr, $from, $i - $from, 'latin1');
        $elm = self::parseCookieElement($chunk);
        $from = $i + 1;
      } else if ($i === $len - 1) {
        $chunk = mb_substr($hdr, $from, $len - $from, 'latin1');
        $elm = self::parseCookieElement($chunk);
      }
      if (null !== $elm && isset($elm[self::COOKIE_NAME])) {
        $cookies[] = $elm;
      }
      $i += 1;
    } //end while
    return $cookies;
  } //end parseCookieHeader
  /**
   * Parse a single cookie setting.
   * @param string $elm Cookie element
   * @return array Associative array of cookie data
   */
  protected static function parseCookieElement ($elm) {
    $cookie = array();
    // eat whitespace outside of quotes
    // each part ends with a semi-colon outside of quotes
    $i = 0;
    $from = 0;
    $len = mb_strlen($elm, 'latin1');
    $quoted = false;
    while ($i < $len) {
      if ('"' === $elm[$i] && '\\' !== $elm[$i - 1]) {
        $quoted = !$quoted;
      }
      $chunk = null;
      if (!$quoted && ';' === $elm[$i]) {
        $chunk = mb_substr($elm, $from, $i - $from, 'latin1');
        $from = $i + 1;
      } else if ($i === $len - 1) {
        $chunk = mb_substr($elm, $from, $len - $from, 'latin1');
      }
      if (null !== $chunk) {
        $parts = explode('=', $chunk, 2);
        if (count($parts) == 1) {
          // we found a flag of some sort
          $name = trim($parts[0]);
          $cookie[$name] = true;
        } else {
          $name = trim($parts[0]);
          $val = trim($parts[1]);
          // remove quotes
          $val = trim($val, '"');
          if (empty($cookie)) {
            // first key=value pair is the cookie name and value
            $cookie[self::COOKIE_NAME] = $name;
            $cookie[self::COOKIE_VALUE] = $val;
          } else {
            $cookie[$name] = $val;
          }
        } //end if/else
      } //end if
      $i += 1;
    } //end while
    return $cookie;
  } //end parseCookieElement
  /**
   * Construction disallowed.
   */
  private function __construct () {
    // no-op
  }
} //end Util