vendor/nategood/httpful/src/Httpful/Request.php line 1048

Open in your IDE?
  1. <?php
  2. namespace Httpful;
  3. use Httpful\Exception\ConnectionErrorException;
  4. /**
  5.  * Clean, simple class for sending HTTP requests
  6.  * in PHP.
  7.  *
  8.  * There is an emphasis of readability without loosing concise
  9.  * syntax.  As such, you will notice that the library lends
  10.  * itself very nicely to "chaining".  You will see several "alias"
  11.  * methods: more readable method definitions that wrap
  12.  * their more concise counterparts.  You will also notice
  13.  * no public constructor.  This two adds to the readability
  14.  * and "chainabilty" of the library.
  15.  *
  16.  * @author Nate Good <me@nategood.com>
  17.  */
  18. class Request
  19. {
  20.     // Option constants
  21.     const SERIALIZE_PAYLOAD_NEVER   0;
  22.     const SERIALIZE_PAYLOAD_ALWAYS  1;
  23.     const SERIALIZE_PAYLOAD_SMART   2;
  24.     const MAX_REDIRECTS_DEFAULT     25;
  25.     public $uri,
  26.            $method                  Http::GET,
  27.            $headers                 = array(),
  28.            $raw_headers             '',
  29.            $strict_ssl              false,
  30.            $content_type,
  31.            $expected_type,
  32.            $additional_curl_opts    = array(),
  33.            $auto_parse              true,
  34.            $serialize_payload_method self::SERIALIZE_PAYLOAD_SMART,
  35.            $username,
  36.            $password,
  37.            $serialized_payload,
  38.            $payload,
  39.            $parse_callback,
  40.            $error_callback,
  41.            $send_callback,
  42.            $follow_redirects        false,
  43.            $max_redirects           self::MAX_REDIRECTS_DEFAULT,
  44.            $payload_serializers     = array();
  45.     // Options
  46.     // private $_options = array(
  47.     //     'serialize_payload_method' => self::SERIALIZE_PAYLOAD_SMART
  48.     //     'auto_parse' => true
  49.     // );
  50.     // Curl Handle
  51.     public $_ch,
  52.            $_debug;
  53.     // Template Request object
  54.     private static $_template;
  55.     /**
  56.      * We made the constructor private to force the factory style.  This was
  57.      * done to keep the syntax cleaner and better the support the idea of
  58.      * "default templates".  Very basic and flexible as it is only intended
  59.      * for internal use.
  60.      * @param array $attrs hash of initial attribute values
  61.      */
  62.     private function __construct($attrs null)
  63.     {
  64.         if (!is_array($attrs)) return;
  65.         foreach ($attrs as $attr => $value) {
  66.             $this->$attr $value;
  67.         }
  68.     }
  69.     // Defaults Management
  70.     /**
  71.      * Let's you configure default settings for this
  72.      * class from a template Request object.  Simply construct a
  73.      * Request object as much as you want to and then pass it to
  74.      * this method.  It will then lock in those settings from
  75.      * that template object.
  76.      * The most common of which may be default mime
  77.      * settings or strict ssl settings.
  78.      * Again some slight memory overhead incurred here but in the grand
  79.      * scheme of things as it typically only occurs once
  80.      * @param Request $template
  81.      */
  82.     public static function ini(Request $template)
  83.     {
  84.         self::$_template = clone $template;
  85.     }
  86.     /**
  87.      * Reset the default template back to the
  88.      * library defaults.
  89.      */
  90.     public static function resetIni()
  91.     {
  92.         self::_initializeDefaults();
  93.     }
  94.     /**
  95.      * Get default for a value based on the template object
  96.      * @param string|null $attr Name of attribute (e.g. mime, headers)
  97.      *    if null just return the whole template object;
  98.      * @return mixed default value
  99.      */
  100.     public static function d($attr)
  101.     {
  102.         return isset($attr) ? self::$_template->$attr self::$_template;
  103.     }
  104.     // Accessors
  105.     /**
  106.      * @return bool does the request have a timeout?
  107.      */
  108.     public function hasTimeout()
  109.     {
  110.         return isset($this->timeout);
  111.     }
  112.     /**
  113.      * @return bool has the internal curl request been initialized?
  114.      */
  115.     public function hasBeenInitialized()
  116.     {
  117.         return isset($this->_ch);
  118.     }
  119.     /**
  120.      * @return bool Is this request setup for basic auth?
  121.      */
  122.     public function hasBasicAuth()
  123.     {
  124.         return isset($this->password) && isset($this->username);
  125.     }
  126.     /**
  127.      * @return bool Is this request setup for digest auth?
  128.      */
  129.     public function hasDigestAuth()
  130.     {
  131.         return isset($this->password) && isset($this->username) && $this->additional_curl_opts[CURLOPT_HTTPAUTH] == CURLAUTH_DIGEST;
  132.     }
  133.     /**
  134.      * Specify a HTTP timeout
  135.      * @param float|int $timeout seconds to timeout the HTTP call
  136.      * @return Request
  137.      */
  138.     public function timeout($timeout)
  139.     {
  140.         $this->timeout $timeout;
  141.         return $this;
  142.     }
  143.     // alias timeout
  144.     public function timeoutIn($seconds)
  145.     {
  146.         return $this->timeout($seconds);
  147.     }
  148.     /**
  149.      * If the response is a 301 or 302 redirect, automatically
  150.      * send off another request to that location
  151.      * @param bool|int $follow follow or not to follow or maximal number of redirects
  152.      * @return Request
  153.      */
  154.     public function followRedirects($follow true)
  155.     {
  156.         $this->max_redirects $follow === true self::MAX_REDIRECTS_DEFAULT max(0$follow);
  157.         $this->follow_redirects = (bool) $follow;
  158.         return $this;
  159.     }
  160.     /**
  161.      * @see Request::followRedirects()
  162.      * @return Request
  163.      */
  164.     public function doNotFollowRedirects()
  165.     {
  166.         return $this->followRedirects(false);
  167.     }
  168.     /**
  169.      * Actually send off the request, and parse the response
  170.      * @return Response with parsed results
  171.      * @throws ConnectionErrorException when unable to parse or communicate w server
  172.      */
  173.     public function send()
  174.     {
  175.         if (!$this->hasBeenInitialized())
  176.             $this->_curlPrep();
  177.         $result curl_exec($this->_ch);
  178.         $response $this->buildResponse($result);
  179.         curl_close($this->_ch);
  180.         return $response;
  181.     }
  182.     public function sendIt()
  183.     {
  184.         return $this->send();
  185.     }
  186.     // Setters
  187.     /**
  188.      * @param string $uri
  189.      * @return Request
  190.      */
  191.     public function uri($uri)
  192.     {
  193.         $this->uri $uri;
  194.         return $this;
  195.     }
  196.     /**
  197.      * User Basic Auth.
  198.      * Only use when over SSL/TSL/HTTPS.
  199.      * @param string $username
  200.      * @param string $password
  201.      * @return Request
  202.      */
  203.     public function basicAuth($username$password)
  204.     {
  205.         $this->username $username;
  206.         $this->password $password;
  207.         return $this;
  208.     }
  209.     // @alias of basicAuth
  210.     public function authenticateWith($username$password)
  211.     {
  212.         return $this->basicAuth($username$password);
  213.     }
  214.     // @alias of basicAuth
  215.     public function authenticateWithBasic($username$password)
  216.     {
  217.         return $this->basicAuth($username$password);
  218.     }
  219.     // @alias of ntlmAuth
  220.     public function authenticateWithNTLM($username$password)
  221.     {
  222.         return $this->ntlmAuth($username$password);
  223.     }
  224.     public function ntlmAuth($username$password)
  225.     {
  226.         $this->addOnCurlOption(CURLOPT_HTTPAUTHCURLAUTH_NTLM);
  227.         return $this->basicAuth($username$password);
  228.     }
  229.     /**
  230.      * User Digest Auth.
  231.      * @param string $username
  232.      * @param string $password
  233.      * @return Request
  234.      */
  235.     public function digestAuth($username$password)
  236.     {
  237.         $this->addOnCurlOption(CURLOPT_HTTPAUTHCURLAUTH_DIGEST);
  238.         return $this->basicAuth($username$password);
  239.     }
  240.     // @alias of digestAuth
  241.     public function authenticateWithDigest($username$password)
  242.     {
  243.         return $this->digestAuth($username$password);
  244.     }
  245.     /**
  246.      * @return bool is this request setup for client side cert?
  247.      */
  248.     public function hasClientSideCert()
  249.     {
  250.         return isset($this->client_cert) && isset($this->client_key);
  251.     }
  252.     /**
  253.      * Use Client Side Cert Authentication
  254.      * @param string $key file path to client key
  255.      * @param string $cert file path to client cert
  256.      * @param string $passphrase for client key
  257.      * @param string $encoding default PEM
  258.      * @return Request
  259.      */
  260.     public function clientSideCert($cert$key$passphrase null$encoding 'PEM')
  261.     {
  262.         $this->client_cert          $cert;
  263.         $this->client_key           $key;
  264.         $this->client_passphrase    $passphrase;
  265.         $this->client_encoding      $encoding;
  266.         return $this;
  267.     }
  268.     // @alias of basicAuth
  269.     public function authenticateWithCert($cert$key$passphrase null$encoding 'PEM')
  270.     {
  271.         return $this->clientSideCert($cert$key$passphrase$encoding);
  272.     }
  273.     /**
  274.      * Set the body of the request
  275.      * @param mixed $payload
  276.      * @param string $mimeType currently, sets the sends AND expects mime type although this
  277.      *    behavior may change in the next minor release (as it is a potential breaking change).
  278.      * @return Request
  279.      */
  280.     public function body($payload$mimeType null)
  281.     {
  282.         $this->mime($mimeType);
  283.         $this->payload $payload;
  284.         // Iserntentially don't call _serializePayload yet.  Wait until
  285.         // we actually send off the request to convert payload to string.
  286.         // At that time, the `serialized_payload` is set accordingly.
  287.         return $this;
  288.     }
  289.     /**
  290.      * Helper function to set the Content type and Expected as same in
  291.      * one swoop
  292.      * @param string $mime mime type to use for content type and expected return type
  293.      * @return Request
  294.      */
  295.     public function mime($mime)
  296.     {
  297.         if (empty($mime)) return $this;
  298.         $this->content_type $this->expected_type Mime::getFullMime($mime);
  299.         if ($this->isUpload()) {
  300.             $this->neverSerializePayload();
  301.         }
  302.         return $this;
  303.     }
  304.     // @alias of mime
  305.     public function sendsAndExpectsType($mime)
  306.     {
  307.         return $this->mime($mime);
  308.     }
  309.     // @alias of mime
  310.     public function sendsAndExpects($mime)
  311.     {
  312.         return $this->mime($mime);
  313.     }
  314.     /**
  315.      * Set the method.  Shouldn't be called often as the preferred syntax
  316.      * for instantiation is the method specific factory methods.
  317.      * @param string $method
  318.      * @return Request
  319.      */
  320.     public function method($method)
  321.     {
  322.         if (empty($method)) return $this;
  323.         $this->method $method;
  324.         return $this;
  325.     }
  326.     /**
  327.      * @param string $mime
  328.      * @return Request
  329.      */
  330.     public function expects($mime)
  331.     {
  332.         if (empty($mime)) return $this;
  333.         $this->expected_type Mime::getFullMime($mime);
  334.         return $this;
  335.     }
  336.     // @alias of expects
  337.     public function expectsType($mime)
  338.     {
  339.         return $this->expects($mime);
  340.     }
  341.     public function attach($files)
  342.     {
  343.         $finfo finfo_open(FILEINFO_MIME_TYPE);
  344.         foreach ($files as $key => $file) {
  345.             $mimeType finfo_file($finfo$file);
  346.             if (function_exists('curl_file_create')) {
  347.                 $this->payload[$key] = curl_file_create($file$mimeType);
  348.             } else {
  349.                 $this->payload[$key] = '@' $file;
  350.                 if ($mimeType) {
  351.                     $this->payload[$key] .= ';type=' $mimeType;
  352.                 }
  353.             }
  354.         }
  355.         $this->sendsType(Mime::UPLOAD);
  356.         return $this;
  357.     }
  358.     /**
  359.      * @param string $mime
  360.      * @return Request
  361.      */
  362.     public function contentType($mime)
  363.     {
  364.         if (empty($mime)) return $this;
  365.         $this->content_type  Mime::getFullMime($mime);
  366.         if ($this->isUpload()) {
  367.             $this->neverSerializePayload();
  368.         }
  369.         return $this;
  370.     }
  371.     // @alias of contentType
  372.     public function sends($mime)
  373.     {
  374.         return $this->contentType($mime);
  375.     }
  376.     // @alias of contentType
  377.     public function sendsType($mime)
  378.     {
  379.         return $this->contentType($mime);
  380.     }
  381.     /**
  382.      * Do we strictly enforce SSL verification?
  383.      * @param bool $strict
  384.      * @return Request
  385.      */
  386.     public function strictSSL($strict)
  387.     {
  388.         $this->strict_ssl $strict;
  389.         return $this;
  390.     }
  391.     public function withoutStrictSSL()
  392.     {
  393.         return $this->strictSSL(false);
  394.     }
  395.     public function withStrictSSL()
  396.     {
  397.         return $this->strictSSL(true);
  398.     }
  399.     /**
  400.      * Use proxy configuration
  401.      * @param string $proxy_host Hostname or address of the proxy
  402.      * @param int $proxy_port Port of the proxy. Default 80
  403.      * @param string $auth_type Authentication type or null. Accepted values are CURLAUTH_BASIC, CURLAUTH_NTLM. Default null, no authentication
  404.      * @param string $auth_username Authentication username. Default null
  405.      * @param string $auth_password Authentication password. Default null
  406.      * @return Request
  407.      */
  408.     public function useProxy($proxy_host$proxy_port 80$auth_type null$auth_username null$auth_password null$proxy_type Proxy::HTTP)
  409.     {
  410.         $this->addOnCurlOption(CURLOPT_PROXY"{$proxy_host}:{$proxy_port}");
  411.         $this->addOnCurlOption(CURLOPT_PROXYTYPE$proxy_type);
  412.         if (in_array($auth_type, array(CURLAUTH_BASIC,CURLAUTH_NTLM))) {
  413.             $this->addOnCurlOption(CURLOPT_PROXYAUTH$auth_type)
  414.                 ->addOnCurlOption(CURLOPT_PROXYUSERPWD"{$auth_username}:{$auth_password}");
  415.         }
  416.         return $this;
  417.     }
  418.     /**
  419.      * Shortcut for useProxy to configure SOCKS 4 proxy
  420.      * @see Request::useProxy
  421.      * @return Request
  422.      */
  423.     public function useSocks4Proxy($proxy_host$proxy_port 80$auth_type null$auth_username null$auth_password null)
  424.     {
  425.         return $this->useProxy($proxy_host$proxy_port$auth_type$auth_username$auth_passwordProxy::SOCKS4);
  426.     }
  427.     /**
  428.      * Shortcut for useProxy to configure SOCKS 5 proxy
  429.      * @see Request::useProxy
  430.      * @return Request
  431.      */
  432.     public function useSocks5Proxy($proxy_host$proxy_port 80$auth_type null$auth_username null$auth_password null)
  433.     {
  434.         return $this->useProxy($proxy_host$proxy_port$auth_type$auth_username$auth_passwordProxy::SOCKS5);
  435.     }
  436.     /**
  437.      * @return bool is this request setup for using proxy?
  438.      */
  439.     public function hasProxy()
  440.     {
  441.         return isset($this->additional_curl_opts[CURLOPT_PROXY]) && is_string($this->additional_curl_opts[CURLOPT_PROXY]);
  442.     }
  443.     /**
  444.      * Determine how/if we use the built in serialization by
  445.      * setting the serialize_payload_method
  446.      * The default (SERIALIZE_PAYLOAD_SMART) is...
  447.      *  - if payload is not a scalar (object/array)
  448.      *    use the appropriate serialize method according to
  449.      *    the Content-Type of this request.
  450.      *  - if the payload IS a scalar (int, float, string, bool)
  451.      *    than just return it as is.
  452.      * When this option is set SERIALIZE_PAYLOAD_ALWAYS,
  453.      * it will always use the appropriate
  454.      * serialize option regardless of whether payload is scalar or not
  455.      * When this option is set SERIALIZE_PAYLOAD_NEVER,
  456.      * it will never use any of the serialization methods.
  457.      * Really the only use for this is if you want the serialize methods
  458.      * to handle strings or not (e.g. Blah is not valid JSON, but "Blah"
  459.      * is).  Forcing the serialization helps prevent that kind of error from
  460.      * happening.
  461.      * @param int $mode
  462.      * @return Request
  463.      */
  464.     public function serializePayload($mode)
  465.     {
  466.         $this->serialize_payload_method $mode;
  467.         return $this;
  468.     }
  469.     /**
  470.      * @see Request::serializePayload()
  471.      * @return Request
  472.      */
  473.     public function neverSerializePayload()
  474.     {
  475.         return $this->serializePayload(self::SERIALIZE_PAYLOAD_NEVER);
  476.     }
  477.     /**
  478.      * This method is the default behavior
  479.      * @see Request::serializePayload()
  480.      * @return Request
  481.      */
  482.     public function smartSerializePayload()
  483.     {
  484.         return $this->serializePayload(self::SERIALIZE_PAYLOAD_SMART);
  485.     }
  486.     /**
  487.      * @see Request::serializePayload()
  488.      * @return Request
  489.      */
  490.     public function alwaysSerializePayload()
  491.     {
  492.         return $this->serializePayload(self::SERIALIZE_PAYLOAD_ALWAYS);
  493.     }
  494.     /**
  495.      * Add an additional header to the request
  496.      * Can also use the cleaner syntax of
  497.      * $Request->withMyHeaderName($my_value);
  498.      * @see Request::__call()
  499.      *
  500.      * @param string $header_name
  501.      * @param string $value
  502.      * @return Request
  503.      */
  504.     public function addHeader($header_name$value)
  505.     {
  506.         $this->headers[$header_name] = $value;
  507.         return $this;
  508.     }
  509.     /**
  510.      * Add group of headers all at once.  Note: This is
  511.      * here just as a convenience in very specific cases.
  512.      * The preferred "readable" way would be to leverage
  513.      * the support for custom header methods.
  514.      * @param array $headers
  515.      * @return Request
  516.      */
  517.     public function addHeaders(array $headers)
  518.     {
  519.         foreach ($headers as $header => $value) {
  520.             $this->addHeader($header$value);
  521.         }
  522.         return $this;
  523.     }
  524.     /**
  525.      * @param bool $auto_parse perform automatic "smart"
  526.      *    parsing based on Content-Type or "expectedType"
  527.      *    If not auto parsing, Response->body returns the body
  528.      *    as a string.
  529.      * @return Request
  530.      */
  531.     public function autoParse($auto_parse true)
  532.     {
  533.         $this->auto_parse $auto_parse;
  534.         return $this;
  535.     }
  536.     /**
  537.      * @see Request::autoParse()
  538.      * @return Request
  539.      */
  540.     public function withoutAutoParsing()
  541.     {
  542.         return $this->autoParse(false);
  543.     }
  544.     /**
  545.      * @see Request::autoParse()
  546.      * @return Request
  547.      */
  548.     public function withAutoParsing()
  549.     {
  550.         return $this->autoParse(true);
  551.     }
  552.     /**
  553.      * Use a custom function to parse the response.
  554.      * @param \Closure $callback Takes the raw body of
  555.      *    the http response and returns a mixed
  556.      * @return Request
  557.      */
  558.     public function parseWith(\Closure $callback)
  559.     {
  560.         $this->parse_callback $callback;
  561.         return $this;
  562.     }
  563.     /**
  564.      * @see Request::parseResponsesWith()
  565.      * @param \Closure $callback
  566.      * @return Request
  567.      */
  568.     public function parseResponsesWith(\Closure $callback)
  569.     {
  570.         return $this->parseWith($callback);
  571.     }
  572.     /**
  573.      * Callback called to handle HTTP errors. When nothing is set, defaults
  574.      * to logging via `error_log`
  575.      * @param \Closure $callback (string $error)
  576.      * @return Request
  577.      */
  578.     public function whenError(\Closure $callback)
  579.     {
  580.         $this->error_callback $callback;
  581.         return $this;
  582.     }
  583.     /**
  584.      * Callback invoked after payload has been serialized but before
  585.      * the request has been built.
  586.      * @param \Closure $callback (Request $request)
  587.      * @return Request
  588.      */
  589.     public function beforeSend(\Closure $callback)
  590.     {
  591.         $this->send_callback $callback;
  592.         return $this;
  593.     }
  594.     /**
  595.      * Register a callback that will be used to serialize the payload
  596.      * for a particular mime type.  When using "*" for the mime
  597.      * type, it will use that parser for all responses regardless of the mime
  598.      * type.  If a custom '*' and 'application/json' exist, the custom
  599.      * 'application/json' would take precedence over the '*' callback.
  600.      *
  601.      * @param string $mime mime type we're registering
  602.      * @param \Closure $callback takes one argument, $payload,
  603.      *    which is the payload that we'll be
  604.      * @return Request
  605.      */
  606.     public function registerPayloadSerializer($mime\Closure $callback)
  607.     {
  608.         $this->payload_serializers[Mime::getFullMime($mime)] = $callback;
  609.         return $this;
  610.     }
  611.     /**
  612.      * @see Request::registerPayloadSerializer()
  613.      * @param \Closure $callback
  614.      * @return Request
  615.      */
  616.     public function serializePayloadWith(\Closure $callback)
  617.     {
  618.         return $this->registerPayloadSerializer('*'$callback);
  619.     }
  620.     /**
  621.      * Magic method allows for neatly setting other headers in a
  622.      * similar syntax as the other setters.  This method also allows
  623.      * for the sends* syntax.
  624.      * @param string $method "missing" method name called
  625.      *    the method name called should be the name of the header that you
  626.      *    are trying to set in camel case without dashes e.g. to set a
  627.      *    header for Content-Type you would use contentType() or more commonly
  628.      *    to add a custom header like X-My-Header, you would use xMyHeader().
  629.      *    To promote readability, you can optionally prefix these methods with
  630.      *    "with"  (e.g. withXMyHeader("blah") instead of xMyHeader("blah")).
  631.      * @param array $args in this case, there should only ever be 1 argument provided
  632.      *    and that argument should be a string value of the header we're setting
  633.      * @return Request
  634.      */
  635.     public function __call($method$args)
  636.     {
  637.         // This method supports the sends* methods
  638.         // like sendsJSON, sendsForm
  639.         //!method_exists($this, $method) &&
  640.         if (substr($method05) === 'sends') {
  641.             $mime strtolower(substr($method5));
  642.             if (Mime::supportsMimeType($mime)) {
  643.                 $this->sends(Mime::getFullMime($mime));
  644.                 return $this;
  645.             }
  646.             // else {
  647.             //     throw new \Exception("Unsupported Content-Type $mime");
  648.             // }
  649.         }
  650.         if (substr($method07) === 'expects') {
  651.             $mime strtolower(substr($method7));
  652.             if (Mime::supportsMimeType($mime)) {
  653.                 $this->expects(Mime::getFullMime($mime));
  654.                 return $this;
  655.             }
  656.             // else {
  657.             //     throw new \Exception("Unsupported Content-Type $mime");
  658.             // }
  659.         }
  660.         // This method also adds the custom header support as described in the
  661.         // method comments
  662.         if (count($args) === 0)
  663.             return;
  664.         // Strip the sugar.  If it leads with "with", strip.
  665.         // This is okay because: No defined HTTP headers begin with with,
  666.         // and if you are defining a custom header, the standard is to prefix it
  667.         // with an "X-", so that should take care of any collisions.
  668.         if (substr($method04) === 'with')
  669.             $method substr($method4);
  670.         // Precede upper case letters with dashes, uppercase the first letter of method
  671.         $header ucwords(implode('-'preg_split('/([A-Z][^A-Z]*)/'$method, -1PREG_SPLIT_DELIM_CAPTURE PREG_SPLIT_NO_EMPTY)));
  672.         $this->addHeader($header$args[0]);
  673.         return $this;
  674.     }
  675.     // Internal Functions
  676.     /**
  677.      * This is the default template to use if no
  678.      * template has been provided.  The template
  679.      * tells the class which default values to use.
  680.      * While there is a slight overhead for object
  681.      * creation once per execution (not once per
  682.      * Request instantiation), it promotes readability
  683.      * and flexibility within the class.
  684.      */
  685.     private static function _initializeDefaults()
  686.     {
  687.         // This is the only place you will
  688.         // see this constructor syntax.  It
  689.         // is only done here to prevent infinite
  690.         // recusion.  Do not use this syntax elsewhere.
  691.         // It goes against the whole readability
  692.         // and transparency idea.
  693.         self::$_template = new Request(array('method' => Http::GET));
  694.         // This is more like it...
  695.         self::$_template
  696.             ->withoutStrictSSL();
  697.     }
  698.     /**
  699.      * Set the defaults on a newly instantiated object
  700.      * Doesn't copy variables prefixed with _
  701.      * @return Request
  702.      */
  703.     private function _setDefaults()
  704.     {
  705.         if (!isset(self::$_template))
  706.             self::_initializeDefaults();
  707.         foreach (self::$_template as $k=>$v) {
  708.             if ($k[0] != '_')
  709.                 $this->$k $v;
  710.         }
  711.         return $this;
  712.     }
  713.     private function _error($error)
  714.     {
  715.         // TODO add in support for various Loggers that follow
  716.         // PSR 3 https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
  717.         if (isset($this->error_callback)) {
  718.             $this->error_callback->__invoke($error);
  719.         } else {
  720.             error_log($error);
  721.         }
  722.     }
  723.     /**
  724.      * Factory style constructor works nicer for chaining.  This
  725.      * should also really only be used internally.  The Request::get,
  726.      * Request::post syntax is preferred as it is more readable.
  727.      * @param string $method Http Method
  728.      * @param string $mime Mime Type to Use
  729.      * @return Request
  730.      */
  731.     public static function init($method null$mime null)
  732.     {
  733.         // Setup our handlers, can call it here as it's idempotent
  734.         Bootstrap::init();
  735.         // Setup the default template if need be
  736.         if (!isset(self::$_template))
  737.             self::_initializeDefaults();
  738.         $request = new Request();
  739.         return $request
  740.                ->_setDefaults()
  741.                ->method($method)
  742.                ->sendsType($mime)
  743.                ->expectsType($mime);
  744.     }
  745.     /**
  746.      * Does the heavy lifting.  Uses de facto HTTP
  747.      * library cURL to set up the HTTP request.
  748.      * Note: It does NOT actually send the request
  749.      * @return Request
  750.      * @throws \Exception
  751.      */
  752.     public function _curlPrep()
  753.     {
  754.         // Check for required stuff
  755.         if (!isset($this->uri))
  756.             throw new \Exception('Attempting to send a request before defining a URI endpoint.');
  757.         if (isset($this->payload)) {
  758.             $this->serialized_payload $this->_serializePayload($this->payload);
  759.         }
  760.         if (isset($this->send_callback)) {
  761.             call_user_func($this->send_callback$this);
  762.         }
  763.         $ch curl_init($this->uri);
  764.         curl_setopt($chCURLOPT_CUSTOMREQUEST$this->method);
  765.         if ($this->method === Http::HEAD) {
  766.             curl_setopt($chCURLOPT_NOBODYtrue);
  767.         }
  768.         if ($this->hasBasicAuth()) {
  769.             curl_setopt($chCURLOPT_USERPWD$this->username ':' $this->password);
  770.         }
  771.         if ($this->hasClientSideCert()) {
  772.             if (!file_exists($this->client_key))
  773.                 throw new \Exception('Could not read Client Key');
  774.             if (!file_exists($this->client_cert))
  775.                 throw new \Exception('Could not read Client Certificate');
  776.             curl_setopt($chCURLOPT_SSLCERTTYPE,   $this->client_encoding);
  777.             curl_setopt($chCURLOPT_SSLKEYTYPE,    $this->client_encoding);
  778.             curl_setopt($chCURLOPT_SSLCERT,       $this->client_cert);
  779.             curl_setopt($chCURLOPT_SSLKEY,        $this->client_key);
  780.             curl_setopt($chCURLOPT_SSLKEYPASSWD,  $this->client_passphrase);
  781.             // curl_setopt($ch, CURLOPT_SSLCERTPASSWD,  $this->client_cert_passphrase);
  782.         }
  783.         if ($this->hasTimeout()) {
  784.             if (defined('CURLOPT_TIMEOUT_MS')) {
  785.                 curl_setopt($chCURLOPT_TIMEOUT_MS$this->timeout 1000);
  786.             } else {
  787.                 curl_setopt($chCURLOPT_TIMEOUT$this->timeout);
  788.             }
  789.         }
  790.         if ($this->follow_redirects) {
  791.             curl_setopt($chCURLOPT_FOLLOWLOCATIONtrue);
  792.             curl_setopt($chCURLOPT_MAXREDIRS$this->max_redirects);
  793.         }
  794.         curl_setopt($chCURLOPT_SSL_VERIFYPEER$this->strict_ssl);
  795.         // zero is safe for all curl versions
  796.         $verifyValue $this->strict_ssl 0;
  797.         //Support for value 1 removed in cURL 7.28.1 value 2 valid in all versions
  798.         if ($verifyValue 0$verifyValue++;
  799.         curl_setopt($chCURLOPT_SSL_VERIFYHOST$verifyValue);
  800.         curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
  801.         // https://github.com/nategood/httpful/issues/84
  802.         // set Content-Length to the size of the payload if present
  803.         if (isset($this->payload)) {
  804.             curl_setopt($chCURLOPT_POSTFIELDS$this->serialized_payload);
  805.             if (!$this->isUpload()) {
  806.                 $this->headers['Content-Length'] =
  807.                     $this->_determineLength($this->serialized_payload);
  808.             }
  809.         }
  810.         $headers = array();
  811.         // https://github.com/nategood/httpful/issues/37
  812.         // Except header removes any HTTP 1.1 Continue from response headers
  813.         $headers[] = 'Expect:';
  814.         if (!isset($this->headers['User-Agent'])) {
  815.             $headers[] = $this->buildUserAgent();
  816.         }
  817.         $headers[] = "Content-Type: {$this->content_type}";
  818.         // allow custom Accept header if set
  819.         if (!isset($this->headers['Accept'])) {
  820.             // http://pretty-rfc.herokuapp.com/RFC2616#header.accept
  821.             $accept 'Accept: */*; q=0.5, text/plain; q=0.8, text/html;level=3;';
  822.             if (!empty($this->expected_type)) {
  823.                 $accept .= "q=0.9, {$this->expected_type}";
  824.             }
  825.             $headers[] = $accept;
  826.         }
  827.         // Solve a bug on squid proxy, NONE/411 when miss content length
  828.         if (!isset($this->headers['Content-Length']) && !$this->isUpload()) {
  829.             $this->headers['Content-Length'] = 0;
  830.         }
  831.         foreach ($this->headers as $header => $value) {
  832.             $headers[] = "$header$value";
  833.         }
  834.         $url \parse_url($this->uri);
  835.         $path = (isset($url['path']) ? $url['path'] : '/').(isset($url['query']) ? '?'.$url['query'] : '');
  836.         $this->raw_headers "{$this->method} $path HTTP/1.1\r\n";
  837.         $host = (isset($url['host']) ? $url['host'] : 'localhost').(isset($url['port']) ? ':'.$url['port'] : '');
  838.         $this->raw_headers .= "Host: $host\r\n";
  839.         $this->raw_headers .= \implode("\r\n"$headers);
  840.         $this->raw_headers .= "\r\n";
  841.         curl_setopt($chCURLOPT_HTTPHEADER$headers);
  842.         if ($this->_debug) {
  843.             curl_setopt($chCURLOPT_VERBOSEtrue);
  844.         }
  845.         curl_setopt($chCURLOPT_HEADER1);
  846.         // If there are some additional curl opts that the user wants
  847.         // to set, we can tack them in here
  848.         foreach ($this->additional_curl_opts as $curlopt => $curlval) {
  849.             curl_setopt($ch$curlopt$curlval);
  850.         }
  851.         $this->_ch $ch;
  852.         return $this;
  853.     }
  854.     /**
  855.      * @param string $str payload
  856.      * @return int length of payload in bytes
  857.      */
  858.     public function _determineLength($str)
  859.     {
  860.         if (function_exists('mb_strlen')) {
  861.             return mb_strlen($str'8bit');
  862.         } else {
  863.             return strlen($str);
  864.         }
  865.     }
  866.     /**
  867.      * @return bool
  868.      */
  869.     public function isUpload()
  870.     {
  871.         return Mime::UPLOAD == $this->content_type;
  872.     }
  873.     /**
  874.      * @return string
  875.      */
  876.     public function buildUserAgent()
  877.     {
  878.         $user_agent 'User-Agent: Httpful/' Httpful::VERSION ' (cURL/';
  879.         $curl \curl_version();
  880.         if (isset($curl['version'])) {
  881.             $user_agent .= $curl['version'];
  882.         } else {
  883.             $user_agent .= '?.?.?';
  884.         }
  885.         $user_agent .= ' PHP/'PHP_VERSION ' (' PHP_OS ')';
  886.         if (isset($_SERVER['SERVER_SOFTWARE'])) {
  887.             $user_agent .= ' ' \preg_replace('~PHP/[\d\.]+~U''',
  888.                 $_SERVER['SERVER_SOFTWARE']);
  889.         } else {
  890.             if (isset($_SERVER['TERM_PROGRAM'])) {
  891.                 $user_agent .= {$_SERVER['TERM_PROGRAM']}";
  892.             }
  893.             if (isset($_SERVER['TERM_PROGRAM_VERSION'])) {
  894.                 $user_agent .= "/{$_SERVER['TERM_PROGRAM_VERSION']}";
  895.             }
  896.         }
  897.         if (isset($_SERVER['HTTP_USER_AGENT'])) {
  898.             $user_agent .= {$_SERVER['HTTP_USER_AGENT']}";
  899.         }
  900.         $user_agent .= ')';
  901.         return $user_agent;
  902.     }
  903.     /**
  904.      * Takes a curl result and generates a Response from it
  905.      * @return Response
  906.      */
  907.     public function buildResponse($result) {
  908.         if ($result === false) {
  909.             if ($curlErrorNumber curl_errno($this->_ch)) {
  910.                 $curlErrorString curl_error($this->_ch);
  911.                 $this->_error($curlErrorString);
  912.                 throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'": ' $curlErrorNumber ' ' $curlErrorString);
  913.             }
  914.             $this->_error('Unable to connect to "'.$this->uri.'".');
  915.             throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'".');
  916.         }
  917.         $info curl_getinfo($this->_ch);
  918.         // Remove the "HTTP/1.x 200 Connection established" string and any other headers added by proxy
  919.         $proxy_regex "/HTTP\/1\.[01] 200 Connection established.*?\r\n\r\n/si";
  920.         if ($this->hasProxy() && preg_match($proxy_regex$result)) {
  921.             $result preg_replace($proxy_regex''$result);
  922.         }
  923.         $response explode("\r\n\r\n"$result$info['redirect_count']);
  924.         $body array_pop($response);
  925.         $headers array_pop($response);
  926.         return new Response($body$headers$this$info);
  927.     }
  928.     /**
  929.      * Semi-reluctantly added this as a way to add in curl opts
  930.      * that are not otherwise accessible from the rest of the API.
  931.      * @param string $curlopt
  932.      * @param mixed $curloptval
  933.      * @return Request
  934.      */
  935.     public function addOnCurlOption($curlopt$curloptval)
  936.     {
  937.         $this->additional_curl_opts[$curlopt] = $curloptval;
  938.         return $this;
  939.     }
  940.     /**
  941.      * Turn payload from structured data into
  942.      * a string based on the current Mime type.
  943.      * This uses the auto_serialize option to determine
  944.      * it's course of action.  See serialize method for more.
  945.      * Renamed from _detectPayload to _serializePayload as of
  946.      * 2012-02-15.
  947.      *
  948.      * Added in support for custom payload serializers.
  949.      * The serialize_payload_method stuff still holds true though.
  950.      * @see Request::registerPayloadSerializer()
  951.      *
  952.      * @param mixed $payload
  953.      * @return string
  954.      */
  955.     private function _serializePayload($payload)
  956.     {
  957.         if (empty($payload) || $this->serialize_payload_method === self::SERIALIZE_PAYLOAD_NEVER)
  958.             return $payload;
  959.         // When we are in "smart" mode, don't serialize strings/scalars, assume they are already serialized
  960.         if ($this->serialize_payload_method === self::SERIALIZE_PAYLOAD_SMART && is_scalar($payload))
  961.             return $payload;
  962.         // Use a custom serializer if one is registered for this mime type
  963.         if (isset($this->payload_serializers['*']) || isset($this->payload_serializers[$this->content_type])) {
  964.             $key = isset($this->payload_serializers[$this->content_type]) ? $this->content_type '*';
  965.             return call_user_func($this->payload_serializers[$key], $payload);
  966.         }
  967.         return Httpful::get($this->content_type)->serialize($payload);
  968.     }
  969.     /**
  970.      * HTTP Method Get
  971.      * @param string $uri optional uri to use
  972.      * @param string $mime expected
  973.      * @return Request
  974.      */
  975.     public static function get($uri$mime null)
  976.     {
  977.         return self::init(Http::GET)->uri($uri)->mime($mime);
  978.     }
  979.     /**
  980.      * Like Request:::get, except that it sends off the request as well
  981.      * returning a response
  982.      * @param string $uri optional uri to use
  983.      * @param string $mime expected
  984.      * @return Response
  985.      */
  986.     public static function getQuick($uri$mime null)
  987.     {
  988.         return self::get($uri$mime)->send();
  989.     }
  990.     /**
  991.      * HTTP Method Post
  992.      * @param string $uri optional uri to use
  993.      * @param string $payload data to send in body of request
  994.      * @param string $mime MIME to use for Content-Type
  995.      * @return Request
  996.      */
  997.     public static function post($uri$payload null$mime null)
  998.     {
  999.         return self::init(Http::POST)->uri($uri)->body($payload$mime);
  1000.     }
  1001.     /**
  1002.      * HTTP Method Put
  1003.      * @param string $uri optional uri to use
  1004.      * @param string $payload data to send in body of request
  1005.      * @param string $mime MIME to use for Content-Type
  1006.      * @return Request
  1007.      */
  1008.     public static function put($uri$payload null$mime null)
  1009.     {
  1010.         return self::init(Http::PUT)->uri($uri)->body($payload$mime);
  1011.     }
  1012.     /**
  1013.      * HTTP Method Patch
  1014.      * @param string $uri optional uri to use
  1015.      * @param string $payload data to send in body of request
  1016.      * @param string $mime MIME to use for Content-Type
  1017.      * @return Request
  1018.      */
  1019.     public static function patch($uri$payload null$mime null)
  1020.     {
  1021.         return self::init(Http::PATCH)->uri($uri)->body($payload$mime);
  1022.     }
  1023.     /**
  1024.      * HTTP Method Delete
  1025.      * @param string $uri optional uri to use
  1026.      * @return Request
  1027.      */
  1028.     public static function delete($uri$mime null)
  1029.     {
  1030.         return self::init(Http::DELETE)->uri($uri)->mime($mime);
  1031.     }
  1032.     /**
  1033.      * HTTP Method Head
  1034.      * @param string $uri optional uri to use
  1035.      * @return Request
  1036.      */
  1037.     public static function head($uri)
  1038.     {
  1039.         return self::init(Http::HEAD)->uri($uri);
  1040.     }
  1041.     /**
  1042.      * HTTP Method Options
  1043.      * @param string $uri optional uri to use
  1044.      * @return Request
  1045.      */
  1046.     public static function options($uri)
  1047.     {
  1048.         return self::init(Http::OPTIONS)->uri($uri);
  1049.     }
  1050. }