Search code examples
phpcontent-security-policyzend-framework3

How to convince Zend Framework to send duplicate headers?


With Content-Security-Policy headers there is often a need to send more than one such header or to union merge these headers before sending them. This arises from the fact that each module/package of an application may define its own CSP.

Right now ZF3 doesn't seem to have a way to handle such a scenario. If I try to add multple CSP headers, they keep overwriting each other so that only the last added header is sent.

Code to reproduce the issue

$headers = $controller->getResponse()->getHeaders();
$headers->addHeader(new ContentSecurityPolicy($someDirectives));
$headers->addHeader(new ContentSecurityPolicy($someOtherDirectives));

Expected results

The expected result is a response with two CSP headers (OR a union merged CSP).

Actual results

The second addition overwrites the first, the response only contains that one CSP.

Question

How can I make ZF3 send multple headers with the same fieldname?


For more information about this problem, also see my own issue on github https://github.com/zendframework/zend-http/issues/159


Solution

  • You should be able to create a simple workaround using GenericMultipleHeader as a reference (and changing comma delimiter to semicolon):

    class MultiContentSecurityPolicy extends ContentSecurityPolicy implements MultipleHeaderInterface {
    
        public static function fromString($headerLine)
        {
            list($fieldName, $fieldValue) = GenericHeader::splitHeaderLine($headerLine);
            if (strpos($fieldValue, ';')) {
                $headers = [];
                foreach (explode(';', $fieldValue) as $multiValue) {
                    $headers[] = new static($fieldName, $multiValue);
                }
                return $headers;
            } else {
                $header = new static($fieldName, $fieldValue);
                return $header;
            }
        }
    
        public function toStringMultipleHeaders(array $headers)
        {
            $name  = $this->getFieldName();
            $values = [$this->getFieldValue()];
            foreach ($headers as $header) {
                if (! $header instanceof static) {
                    throw new Exception\InvalidArgumentException(
                        'This method toStringMultipleHeaders was expecting an array of headers of the same type'
                    );
                }
                $values[] = $header->getFieldValue();
            }
            return $name . ': ' . implode(';', $values) . "\r\n";
        }
    
    }
    

    Then use that class instead of ContentSecurityPolicy:

    $headers = $controller->getResponse()->getHeaders();
    $headers->addHeader(new MultiContentSecurityPolicy($someDirectives));
    $headers->addHeader(new MultiContentSecurityPolicy($someOtherDirectives));
    

    Since Zend checks the interface rather than the class, should work fine.