Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"homepage": "http://github.com/nategood/httpful",
"license": "MIT",
"keywords": ["http", "curl", "rest", "restful", "api", "requests"],
"version": "0.2.20",
"version": "0.3.0",
"authors": [
{
"name": "Nate Good",
Expand All @@ -13,15 +13,17 @@
}
],
"require": {
"php": ">=5.3",
"ext-curl": "*"
"php": ">=7.2",
"ext-curl": "*",
"ext-json": "*",
"ext-simplexml": "*"
},
"autoload": {
"psr-0": {
"Httpful": "src/"
}
},
"require-dev": {
"phpunit/phpunit": "*"
"phpunit/phpunit": "^8.0 || ^9.0"
}
}
17 changes: 15 additions & 2 deletions src/Httpful/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Httpful;

/**
* Bootstrap class that facilitates autoloading. A naive
* Bootstrap class that facilitates autoloading. A naive
* PSR-0 autoloader.
*
* @author Nate Good <me@nategood.com>
Expand All @@ -21,6 +21,11 @@ class Bootstrap
*/
public static function init()
{
// FIX: Prevent double registration of autoloader (Issue #9)
if (self::$registered === true) {
return;
}

spl_autoload_register(array('\Httpful\Bootstrap', 'autoload'));
self::registerHandlers();
}
Expand All @@ -40,6 +45,11 @@ public static function autoload($classname)
*/
public static function pharInit()
{
// FIX: Prevent double registration in Phar mode too
if (self::$registered === true) {
return;
}

spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload'));
self::registerHandlers();
}
Expand Down Expand Up @@ -68,7 +78,7 @@ private static function _autoload($base, $classname)
}
}
/**
* Register default mime handlers. Is idempotent.
* Register default mime handlers. Is idempotent.
*/
public static function registerHandlers()
{
Expand All @@ -80,6 +90,9 @@ public static function registerHandlers()
// hardcoding into the library?
$handlers = array(
\Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(),
// FIX: Register handlers for new JSON types (Issue #8)
\Httpful\Mime::JSON_API => new \Httpful\Handlers\JsonHandler(),
\Httpful\Mime::PROBLEM_JSON => new \Httpful\Handlers\JsonHandler(),
\Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(),
\Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(),
\Httpful\Mime::CSV => new \Httpful\Handlers\CsvHandler(),
Expand Down
11 changes: 11 additions & 0 deletions src/Httpful/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Httpful;

/**
* Base Exception for the Httpful library.
* All other exceptions in this library should extend this one.
*/
class Exception extends \Exception
{
}
50 changes: 46 additions & 4 deletions src/Httpful/Exception/ConnectionErrorException.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
<?php
<?php

namespace Httpful\Exception;

class ConnectionErrorException extends \Exception
{
}
// FIX: Extend the library-specific Exception (Issue #14)
class ConnectionErrorException extends \Httpful\Exception {

/**
* @var int|string
*/
private $curlErrorNumber;

/**
* @var string
*/
private $curlErrorString;

/**
* @return int|string
*/
public function getCurlErrorNumber() {
return $this->curlErrorNumber;
}

/**
* @param int|string $curlErrorNumber
* @return $this
*/
public function setCurlErrorNumber($curlErrorNumber) {
$this->curlErrorNumber = $curlErrorNumber;
return $this;
}

/**
* @return string
*/
public function getCurlErrorString() {
return $this->curlErrorString;
}

/**
* @param string $curlErrorString
* @return $this
*/
public function setCurlErrorString($curlErrorString) {
$this->curlErrorString = $curlErrorString;
return $this;
}
}
6 changes: 5 additions & 1 deletion src/Httpful/Handlers/FormHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class FormHandler extends MimeHandlerAdapter
*/
public function parse($body)
{
// FIX: Strip Byte Order Mark (BOM) before parsing
// This prevents the first key in the array from getting corrupted by invisible characters.
$body = $this->stripBom($body);

$parsed = array();
parse_str($body, $parsed);
return $parsed;
Expand All @@ -27,4 +31,4 @@ public function serialize($payload)
{
return http_build_query($payload, null, '&');
}
}
}
7 changes: 6 additions & 1 deletion src/Httpful/Handlers/JsonHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ public function init(array $args)
public function parse($body)
{
$body = $this->stripBom($body);
if (empty($body))

// FIX: Added check for whitespace-only strings (trim)
// This prevents crashing on "200 OK" responses that contain only a newline.
if (empty($body) || trim($body) === '')
return null;

$parsed = json_decode($body, $this->decode_as_array);
if (is_null($parsed) && 'null' !== strtolower($body))
throw new JsonParseException('Unable to parse response as JSON: ' . json_last_error_msg());

return $parsed;
}

Expand Down
19 changes: 18 additions & 1 deletion src/Httpful/Handlers/XmlHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,26 @@ public function parse($body)
$body = $this->stripBom($body);
if (empty($body))
return null;

// FIX: Prevent XXE attacks (Issue #4)
// Disable external entities for PHP versions < 8.0.
// PHP 8.0+ disables this by default and deprecates the function.
$shouldDisable = (\PHP_VERSION_ID < 80000);
$backup = false;

if ($shouldDisable) {
$backup = \libxml_disable_entity_loader(true);
}

$parsed = simplexml_load_string($body, null, $this->libxml_opts, $this->namespace);

if ($shouldDisable) {
\libxml_disable_entity_loader($backup);
}

if ($parsed === false)
throw new \Exception("Unable to parse response as XML");

return $parsed;
}

Expand Down Expand Up @@ -149,4 +166,4 @@ private function _future_serializeObjectAsXml($value, &$parent, &$dom)
}
return array($parent, $dom);
}
}
}
9 changes: 7 additions & 2 deletions src/Httpful/Httpful.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Httpful;

class Httpful {
const VERSION = '0.2.20';
const VERSION = '0.3.0'; // Bumped version to reflect modern patches

private static $mimeRegistrar = array();
private static $default = null;
Expand All @@ -14,6 +14,10 @@ class Httpful {
*/
public static function register($mimeType, \Httpful\Handlers\MimeHandlerAdapter $handler)
{
// FIX: Validate input to prevent obscure errors later
if (empty($mimeType) || !is_string($mimeType)) {
throw new \InvalidArgumentException('Mime type must be a non-empty string');
}
self::$mimeRegistrar[$mimeType] = $handler;
}

Expand All @@ -23,7 +27,8 @@ public static function register($mimeType, \Httpful\Handlers\MimeHandlerAdapter
*/
public static function get($mimeType = null)
{
if (isset(self::$mimeRegistrar[$mimeType])) {
// FIX: Check if registrar has the type (and handle null gracefully)
if ($mimeType !== null && isset(self::$mimeRegistrar[$mimeType])) {
return self::$mimeRegistrar[$mimeType];
}

Expand Down
37 changes: 26 additions & 11 deletions src/Httpful/Mime.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@
*/
class Mime
{
const JSON = 'application/json';
const XML = 'application/xml';
const XHTML = 'application/html+xml';
const FORM = 'application/x-www-form-urlencoded';
const UPLOAD = 'multipart/form-data';
const PLAIN = 'text/plain';
const JS = 'text/javascript';
const HTML = 'text/html';
const YAML = 'application/x-yaml';
const CSV = 'text/csv';
const JSON = 'application/json';
const XML = 'application/xml';
const XHTML = 'application/html+xml';
const FORM = 'application/x-www-form-urlencoded';
const UPLOAD = 'multipart/form-data';
const PLAIN = 'text/plain';
const JS = 'text/javascript';
const HTML = 'text/html';
const YAML = 'application/x-yaml';
const CSV = 'text/csv';

// FIX: Add modern JSON content types (Issue #8)
const JSON_API = 'application/vnd.api+json';
const PROBLEM_JSON = 'application/problem+json';

/**
* Map short name for a mime type
Expand All @@ -29,13 +33,16 @@ class Mime
'form' => self::FORM,
'plain' => self::PLAIN,
'text' => self::PLAIN,
'upload' => self::UPLOAD,
'upload' => self::UPLOAD,
'html' => self::HTML,
'xhtml' => self::XHTML,
'js' => self::JS,
'javascript'=> self::JS,
'yaml' => self::YAML,
'csv' => self::CSV,
// FIX: Map new short names
'json_api' => self::JSON_API,
'problem_json' => self::PROBLEM_JSON,
);

/**
Expand All @@ -46,6 +53,10 @@ class Mime
*/
public static function getFullMime($short_name)
{
// FIX: Prevent "Deprecated: Passing null..." in PHP 8.1+ (Issue #7)
if ($short_name === null) {
return null;
}
return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name;
}

Expand All @@ -55,6 +66,10 @@ public static function getFullMime($short_name)
*/
public static function supportsMimeType($short_name)
{
// FIX: Prevent null check crash
if ($short_name === null) {
return false;
}
return array_key_exists($short_name, self::$mimes);
}
}
Loading