Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.0] Introduce adapter interface and implementations #17

Merged
merged 9 commits into from
Jan 24, 2017
111 changes: 111 additions & 0 deletions src/HeaderBag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace Aidantwoods\SecureHeaders;

use Aidantwoods\SecureHeaders\Util\Types;

class HeaderBag
{
protected $headers = array();

public function __construct(array $headers = array())
{
// Send all headers through `add` to make sure they are properly lower-cased
foreach ($headers as $name => $value)
{
$this->add($name, $value);
}
}

public static function fromHeaderLines(array $lines)
{
$bag = new static;

foreach ($lines as $line)
{
preg_match('/^([^:]++)(?|(?:[:][ ]?+)(.*+)|())/', $line, $matches);
array_shift($matches);

list($name, $value) = $matches;

$bag->add($name, $value);
}

return $bag;
}

public function has($name)
{
Types::assert(array('string' => array($name)));

return array_key_exists(strtolower($name), $this->headers);
}

public function add($name, $value = '')
{
Types::assert(array('string' => array($name, $value)));

$key = strtolower($name);
if ( ! array_key_exists($key, $this->headers)) $this->headers[$key] = array();

$this->headers[$key][] = new Header($name, $value);
}

public function replace($name, $value = '')
{
Types::assert(array('string' => array($name, $value)));

$header = new Header($name, $value);
$this->headers[strtolower($name)] = array($header);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, I think I messed up the difference between add and replace here. add should still add the header if it already exists, just add another equally-named header...


public function remove($name)
{
Types::assert(array('string' => array($name)));

unset($this->headers[strtolower($name)]);
}

public function removeAll()
{
$this->headers = array();
}

public function get()
{
return array_reduce(
$this->headers,
function ($all, $item) {
return array_merge($all, $item);
},
array()
);
}
}

class Header
{
private $name;
private $value;

public function __construct($name, $value = '')
{
$this->name = $name;
$this->value = $value;
}

public function getName()
{
return $this->name;
}

public function getValue()
{
return $this->value;
}

public function __toString()
{
return $this->name . ':' .(empty($this->value) ? '' : ' ' . $this->value);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much like PHPs type hinting – that type assertion function will allow null to be passed (so you can see if the user left the default value in, without having to hard code check for the default). SecureHeaders defaults a header value to null if it isn't specified, so it's plausible that the value may be of null type (which should also set a header with no value).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both bugs fixed here were brought over from my code of course, just explaining the change here ;-)

}
}
38 changes: 38 additions & 0 deletions src/Http/GlobalHttpAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Aidantwoods\SecureHeaders\Http;

use Aidantwoods\SecureHeaders\HeaderBag;

class GlobalHttpAdapter implements HttpAdapter
{
/**
* Send the given headers, overwriting all previously send headers
*
* @param HeaderBag $headers
* @return void
*/
public function sendHeaders(HeaderBag $headers)
{
header_remove();

foreach ($headers->get() as $header)
{
header(
(string) $header
);
}
}

/**
* Retrieve the current list of already-sent (or planned-to-be-sent) headers
*
* @return HeaderBag
*/
public function getHeaders()
{
return HeaderBag::fromHeaderLines(
headers_list()
);
}
}
23 changes: 23 additions & 0 deletions src/Http/HttpAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Aidantwoods\SecureHeaders\Http;

use Aidantwoods\SecureHeaders\HeaderBag;

interface HttpAdapter
{
/**
* Send the given headers, overwriting all previously send headers
*
* @param HeaderBag $headers
* @return void
*/
public function sendHeaders(HeaderBag $headers);

/**
* Retrieve the current list of already-sent (or planned-to-be-sent) headers
*
* @return HeaderBag
*/
public function getHeaders();
}
43 changes: 43 additions & 0 deletions src/Http/StringHttpAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Aidantwoods\SecureHeaders\Http;

use Aidantwoods\SecureHeaders\HeaderBag;

class StringHttpAdapter implements HttpAdapter
{
private $headers = array();

/**
* Send the given headers, overwriting all previously send headers
*
* @param HeaderBag $headers
* @return void
*/
public function sendHeaders(HeaderBag $headers)
{
$this->headers = $headers->get();
}

/**
* Retrieve the current list of already-sent (or planned-to-be-sent) headers
*
* @return HeaderBag
*/
public function getHeaders()
{
return new HeaderBag();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know StringHttpAdapter isn't really meant to ever retrieve headers as an object, but just to keep this as a good implementation example for others, I guess this should be return new HeaderBag($this->headers);, rather than a blank list of headers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not really, as getHeaders() is called before $this->headers is ever populated.

}

public function getHeadersAsString()
{
$compiledHeaders = array();

foreach ($this->headers as $header)
{
$compiledHeaders[] = (string) $header;
}

return implode("\n", $compiledHeaders);
}
}
Loading