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
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.
This is the accepted HTTP standard and the PHP Core upholds this. http://php.net/manual/en/function.header.php
If you set headers in PHP header("TESTHeader: Test1"); header("TESTHeader: Test2") only one will come through and this is correct to specification RFC2616 Section 4.2 Page 31&32
If you wish to send multiple values your header should construct as header("TESTHeader: Test1, Test2");. while it is possible to send multiple same name headers through PHP it is not recommended as browsers & servers receiving 2 sets of the same header should convert them to the above style this could cause problems as you will not know for certain what format they are in. header("TESTHeader: Test1", false); header("TESTHeader: Test2", false). depending on the server or clients adherence to the RFC or HTTP Version.
So this answer is the reason as to why you are not allowed to send the same header multiple times in ZF3, it can't identify when to use the overwrite or not to based on you setting the header. to get around this and use multi-valued headers you can use Jim's answer
make your own multipleheader class, add the function you need (MultipleHeaderInterface) then add your header in a multistering and finally call it in your
$headers = $controller->getResponse()->getHeaders();
(call the new function with the new fromStringHeaders)
Related
I'm trying to filter returned header location after POST using below PHP code. The returned header location is required for further processing of the payment status and saving the status in the db. The API provider seems not supportive since they don't reply on time or fail to reply at all.
$output = curl_exec($curl);
$lines = explode("\n",$output);
$out = array();
$headers = true;
foreach ($lines as $l){
$l = trim($l);
if ($headers && !empty($l)){
if (strpos($l,'location') !== false){
$p = explode(' ',$l);
$out['Headers']['location'] = trim($p[1]);
$url = json_encode($out['Headers']['location']);
echo json_encode($out['Headers']['location']);
}
}
}
The echo output is as below:- "https:\/\/sandbox.kopokopo.com\/api\/v1\/payments\/c122c1d2-8e07-48d3-8c9d-597829447fda"
How do I make the output to be a valid url without "\" ? I'll really appreciate your valuable assistance.
Your json_encode call is causing the problem, by escaping the / values.
Demo: https://3v4l.org/S3VWD
There's no need to encode a single string like this. JSON is mainly useful when you have a more complex set of information (e.g. multiple separate data items) that you want to output in a structured way.
echo $out['Headers']['location'];
is all you need in this case.
I've written an API using a simple but fairly long query string request.
The requester can designate an XML or HTML response.
The query is received and the data is processed. The HTML simply echos back a response. In order to avoid redundant code, I use a JavaScript redirect to open and process the XML. That looks like this:
$QString = '';
foreach($XMLItems as $key => $value)
{
$QString .= $key.'^'.$value.'|';
}
$QString = substr($QString,0,-1);
Redirect('XMLProcess.php?Q='.$QString);
Redirect() is a simple function that runs javascript:
function Redirect($n)
{
die("<script type=\"text/javascript\">window.location='$n';</script>");
}
The XML construction looks like this:
<?php
$dom = new DOMDocument("1.0");
header ("Content-Type:text/xml");
$QArray = explode('|',$_REQUEST[Q]);
foreach($QArray as $value)
{
$x = explode('^',$value);
$XMLItems[$x[0]] = $x[1];
}
$root = $dom->createElement("Data");
$dom->appendChild($root);
foreach($XMLItems as $key => $value)
{
$key = $dom->createElement($key);
$root->appendChild($key);
$variable = $dom->createTextNode($value);
$key->appendChild($variable);
}
echo $dom->saveXML();
?>
I'm pretty ignorant as far as API's and what someone may be using on the receiving end. I have a client who is asking for a 302 redirect and location header. I am assuming my Redirect() function may be throwing his software off, but I don't really know. Of course, I cannot redirect to the XML file immediately, as the incoming data needs to be processed first. So I am trying to wrap my mind around what the client needs without duplicating the processing in a second file. And, since I am in the dark, the Redirect may not be the problem, anyway.
To emit a 302 Redirect (or indeed any status code), you need to use header. Per the docs:
The second special case is the "Location:" header. Not only does it send this header back to the browser, but it also returns a REDIRECT (302) status code to the browser unless the 201 or a 3xx status code has already been set.
which would be implemented like:
function Redirect($n)
{
header("Location: $n");
exit(0);
}
As noted in the comments, since your script is emitting output prior to the invocation of Redirect, you'll need to use ob_start (and friends) to capture that output and emit it after the header has been sent:
ob_start();
// ...
$QString = '';
foreach($XMLItems as $key => $value)
{
$QString .= $key.'^'.$value.'|';
}
$QString = substr($QString,0,-1);
Redirect('XMLProcess.php?Q='.$QString);
function Redirect($n)
{
header("Location: $n");
$contents = ob_get_clean();
// echo $contents; // if you want or need to
// you might also consider leaving this, but clients will
// honor the 302 before executing any Javascript
die("<script type=\"text/javascript\">window.location='$n';</script>");
}
All of this said, though, the code is feeling pretty janky. If at all possible, consider refactoring the HTML and XML versions so that the main work of the code is done in an included helper, and the HTML and XML "front-ends" just focus on rendering the output in the desired format.
PHP function \http_parse_headers() parses HTTP headers into an associative array.
But is there some reverse function? Which parses associative array into HTTP headers?
Cannot find anything :(
(it's for saveing email into textual .eml file)
There isn't a function that turns associative array into text-representation of headers. The reason: this function is extremely trivial to create.
Headers are defined as key: value delimiter is \r\n.
There is another \r\n delimiter between headers and body.
Lets take an example array:
$headers = [
'Content-Length': 50,
'Content-Encoding': 'gzip'
];
The goal: provide a string that represents HTTP headers
function parse_array_to_headers(array $headers)
{
$result = [];
$delimiter = "\r\n";
foreach($headers as $name => $value)
{
$result[] = sprintf("%s: %s", $name, $value);
}
return implode($delimiter, $result);
}
Note: this function will not check the validity of array and it won't return the string with two repetitions of \r\n at the end. This example serves to show how easy it should be to add missing function. Adjust according to your needs. Also, I didn't test this so don't copy paste it! :)
I'm refactoring some code and found something I've never seen. the function is used for user to set cookie when user logs in:
function setUserCookie($name, $value) {
$date = date("D, d M Y H:i:s",strtotime('1 January 2015')) . 'GMT';
header("Set-Cookie: {$name}={$value}; EXPIRES{$date};");
}
now that I've been assigned to refactor code I'm planning to use setcookie function which essentially does same thing according to php.net.
My question is: is there any difference between two and which one should I use?
NOTE: this code was written long time ago so I'm assuming that at that time setcookie didnt exist?
There's no good reason not to use setcookie. The above code doesn't properly encode names and values, so that's at least one major benefit to refactoring.
The difference between the two functions is that header() is the general function for setting HTTP headers while setcookie() is specifically meant to set the Set-Cookie header.
header() therefore takes a string containing the complete header, while setcookie() takes several cookie-specific arguments and then creates the Set-Cookie header from them.
Here's a use case in which you can't use setcookie
you run a website on PHP<7.3
you have to set 'SameSite' cookie attribute
You can achieve that by exploiting a bug in setcookie, but I wouldn't rely on a bug as it gets fixed over time: setcookie('samesite-test', '1', 0, '/; samesite=strict');
Or you can use PHP header function: header("Set-Cookie: samesite-test=1; expires=0; path=/; samesite=Strict");
Note that secure option is required when setting samesite attribute
One big difference is, that setcookie always sets host_only=false and there is nothing you can do about it.
So if you have to set host_only=true for whatever reasons you have to use the header method. As far as I know.
I replicated what I believe to be the exact behavior of setCookie programmatically. Here is my implementation, if it can be useful for anyone else.
function setUserCookie($name, $value, $expires = 0, $path = "", $domain = "", $secure = false, $http_only = false) {
$value = rawurlencode($value);
date_default_timezone_set('UTC');
$date = date("D, d-M-Y H:i:s",$expires) . ' GMT';
$header = "Set-Cookie: {$name}={$value}";
if($expires != 0) {
$header .= "; expires={$date}; Max-Age=".($expires - time());
}
if($path != "") {
$header .= "; path=".$path;
}
if($domain != "") {
$header .= "; domain=".$domain;
}
if($secure) {
$header .= "; secure";
}
if($http_only) {
$header .= "; HttpOnly";
}
header($header, false);
}
The difference with your function are exactly the difference with setCookie (more arguments like custom expires, path, domain, secure and httpOnly). Especially, note the second argument to "header" (false) so that it becomes possible to place multiple cookies with different calls to the function.
of all the languages i know im the weakest in php...
I have a script... that takes a csv file and does some stuff with it... fairly simple.
the issue i am having:
in_array('username', $headers) ... returns null...
while...
print_r ($headers); shows username being the first entry in the csv.
thoughts? mistakes i may have made?
TIA
code here
/// Turn the string into an array
$rows = explode("\n", $this->raw_data);
/// First row includes headers
$headers = $rows[0];
$headers = explode($this->delimiter, $headers);
/// Trim spaces from $headers
$headers = array_map('trim', $headers);
/// Check that there are no empty headers. This can happen if there are delimiters at the end of the file
foreach($headers as $header){
if(!empty($header)){
$headers2[] = $header;
}
}
$headers = $headers2;
if(! in_array('password', $headers)){
/// Add password column for generated passwords
$headers[] = 'password';
}
/// Add status column to the headers
$headers[] = 'status';
$this->headers = $headers;
/// Check that at least username, name and email are provided in the headers
if(!in_array('username', $headers) ||
!in_array('name', $headers) ||
!in_array('email', $headers)){
echo "error\n";
return false;
}
You can use the built in str_getcsv() function. Try replacing the $headers variable assignment with
$headers = str_getcsv($rows[0], $this->delimiter);
Then find the value(column) you want and loop through the rest of the $rows using the same str_getcsv() function to get the matches you need.
You may want to use the file() function to grab the file in an array delimited by newlines to begin with, as well.
Check the first three functions in this list . Your problem can arise from several causes. Start by elimination of unnecessary parsing by using the built in CSV function.
I don't see code that sets $headers2 to be an array. Is the first assignment to that variable getting lost once the second assignment happens which turns it into an array?