32

I'm trying to get the response & the response headers from CURL using PHP, specifically for Content-Disposition: attachment; so I can return the filename passed within the header. This doesn't seem to get returned within curl_getinfo.

I've tried using the HeaderFunction to call a function to read the additional headers, however, I am unable to add the contents to an array.

Does anyone have any ideas please?


Below is part of my code which is a Curl wrapper class:

 ...
 curl_setopt($this->_ch, CURLOPT_URL, $this->_url);
 curl_setopt($this->_ch, CURLOPT_HEADER, false);
 curl_setopt($this->_ch, CURLOPT_POST, 1);
 curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $this->_postData);
 curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, 1);
 curl_setopt($this->_ch, CURLOPT_USERAGENT, $this->_userAgent);
 curl_setopt($this->_ch, CURLOPT_HEADERFUNCTION, 'readHeader');

 $this->_response = curl_exec($this->_ch);
 $info = curl_getinfo($this->_ch);
 ...


 function readHeader($ch, $header)
 {
      array_push($this->_headers, $header);
 }
StuffandBlah
  • 1,047
  • 4
  • 13
  • 22
  • I should add that readHeader function is part of the curl wrapper class. Using '$this->readHeader' doesn't work. – StuffandBlah May 14 '12 at 19:58
  • 2
    According to the docs, your `readHeader` function must return the number of bytes written. Adding `return strlen($header)` should make this work – marcfrodi Aug 06 '14 at 14:34

9 Answers9

80

Here, this should do it:

curl_setopt($this->_ch, CURLOPT_URL, $this->_url);
curl_setopt($this->_ch, CURLOPT_HEADER, 1);
curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($this->_ch);
$info = curl_getinfo($this->_ch);

$headers = get_headers_from_curl_response($response);

function get_headers_from_curl_response($response)
{
    $headers = array();

    $header_text = substr($response, 0, strpos($response, "\r\n\r\n"));

    foreach (explode("\r\n", $header_text) as $i => $line)
        if ($i === 0)
            $headers['http_code'] = $line;
        else
        {
            list ($key, $value) = explode(': ', $line);

            $headers[$key] = $value;
        }

    return $headers;
}
c.hill
  • 3,127
  • 25
  • 31
  • 4
    header names are case insensitive, so it's probably better to lower case $key. Also, multiply headers can have the same name/ – ruz Jun 09 '15 at 14:00
  • 2
    `explode(': ', $line);` Header value may contain ": ", so third parameter "2" is necessary. `explode(': ', $line, 2);` Headers can be multiline... – elixon May 29 '19 at 11:12
  • 1
    I forgot: Headers can be multiline with next folded line starting with white-space. There can be duplicate headers like 'set-cookie' - current code will overwrite previous with later... – elixon May 29 '19 at 11:17
35

The anwser from c.hill is great but the code will not handle if the first response is a 301 or 302 - in that case only the first header will be added to the array returned by get_header_from_curl_response().

I've updated the function to return an array with each of the headers.

First I use this lines to create a variable with only the header content

$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($a, 0, $header_size);

Than I pass $header in to the new get_headers_from_curl_response()-function:

static function get_headers_from_curl_response($headerContent)
{

    $headers = array();

    // Split the string on every "double" new line.
    $arrRequests = explode("\r\n\r\n", $headerContent);

    // Loop of response headers. The "count() -1" is to 
    //avoid an empty row for the extra line break before the body of the response.
    for ($index = 0; $index < count($arrRequests) -1; $index++) {

        foreach (explode("\r\n", $arrRequests[$index]) as $i => $line)
        {
            if ($i === 0)
                $headers[$index]['http_code'] = $line;
            else
            {
                list ($key, $value) = explode(': ', $line);
                $headers[$index][$key] = $value;
            }
        }
    }

    return $headers;
}

This function will take header like this:

HTTP/1.1 302 Found
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Expires: -1
Location: http://www.website.com/
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
Date: Sun, 08 Sep 2013 10:51:39 GMT
Connection: close
Content-Length: 16313

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
Date: Sun, 08 Sep 2013 10:51:39 GMT
Connection: close
Content-Length: 15519

And return an array like this:

(
    [0] => Array
        (
            [http_code] => HTTP/1.1 302 Found
            [Cache-Control] => no-cache
            [Pragma] => no-cache
            [Content-Type] => text/html; charset=utf-8
            [Expires] => -1
            [Location] => http://www.website.com/
            [Server] => Microsoft-IIS/7.5
            [X-AspNet-Version] => 4.0.30319
            [Date] => Sun, 08 Sep 2013 10:51:39 GMT
            [Connection] => close
            [Content-Length] => 16313
        )

    [1] => Array
        (
            [http_code] => HTTP/1.1 200 OK
            [Cache-Control] => private
            [Content-Type] => text/html; charset=utf-8
            [Server] => Microsoft-IIS/7.5
            [X-AspNet-Version] => 4.0.30319
            [Date] => Sun, 08 Sep 2013 10:51:39 GMT
            [Connection] => close
            [Content-Length] => 15519
        )

)
2

Simple and straightforward

$headers = [];
// Get the response body as string
$response = curl_exec($curl);
// Get the response headers as string
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
// Get the substring of the headers and explode as an array by \r\n
// Each element of the array will be a string `Header-Key: Header-Value`
// Retrieve this two parts with a simple regex `/(.*?): (.*)/`
foreach(explode("\r\n", trim(substr($response, 0, $headerSize))) as $row) {
    if(preg_match('/(.*?): (.*)/', $row, $matches)) {
        $headers[$matches[1]] = $matches[2];
    }
}
1

Using the array() form for method callbacks should make the original example work:

curl_setopt($this->_ch, CURLOPT_HEADERFUNCTION, array($this, 'readHeader'));

Pieter Ennes
  • 2,301
  • 19
  • 21
1

Another my implementation:

function getHeaders($response){

    if (!preg_match_all('/([A-Za-z\-]{1,})\:(.*)\\r/', $response, $matches) 
            || !isset($matches[1], $matches[2])){
        return false;
    }

    $headers = [];

    foreach ($matches[1] as $index => $key){
        $headers[$key] = $matches[2][$index];
    }

    return $headers;
}

Used in case, which request format is:

Host: *
Accept: *
Content-Length: *
and etc ...

1

Fixing issues:

  • Error when content of the header contained ': '(split string)
  • Multiline-headers were not supported
  • Duplicate headers (Set-Cookie) were not supported

This is my take on the topic ;-)

list($head, $body)=explode("\r\n\r\n", $content, 2);
$headers=parseHeaders($head); 

function parseHeaders($text) {
    $headers=array();

    foreach (explode("\r\n", $text) as $i => $line) {
        // Special HTTP first line
        if (!$i && preg_match('@^HTTP/(?<protocol>[0-9.]+)\s+(?<code>\d+)(?:\s+(?<message>.*))?$@', $line, $match)) {
            $headers['@status']=$line;
            $headers['@code']=$match['code'];
            $headers['@protocol']=$match['protocol'];
            $headers['@message']=$match['message'];
            continue;
        }

        // Multiline header - join with previous
        if ($key && preg_match('/^\s/', $line)) {
            $headers[$key].=' '.trim($line);
            continue;
        }

        list ($key, $value) = explode(': ', $line, 2);
        $key=strtolower($key);
        // Append duplicate headers - namely Set-Cookie header
        $headers[$key]=isset($headers[$key]) ? $headers[$key].' ' : $value;
    }

    return $headers;
}
elixon
  • 1,123
  • 12
  • 15
0

You can use http_parse_headers function.

It comes from PECL but you will find fallbacks in this SO thread.

  • 1
    Perhaps a bit misleading to say that this is "standard" - it's not a standard extension, e.g. doesn't ship with PHP and doesn't have an official Windows build. It's also still `0.x` which means it's unstable. I wouldn't depend on this until it's at least tagged as stable, and preferably officially bundled as a standard PHP extension. – mindplay.dk Dec 23 '15 at 13:56
  • @mindplay.dk you're right: coming from PECL does not make it a standard. Anyway, pecl_http is tagged as stable since version 1.0.0 in 2006, latest stable version is version 3.1.0. I reworded my answer to make it clearer. – Pierre-Alexis de Solminihac Jun 16 '17 at 15:07
0

C.hill's answer is great but breaks when retrieving multiple cookies. Made the change here

public function get_headers_from_curl_response($response) { 
    $headers = array(); 
    $header_text = substr($response, 0, strpos($response, "\r\n\r\n")); 
    foreach (explode("\r\n", $header_text) as $i => $line) 
         if ($i === 0) $headers['http_code'] = $line; 
         else { 
              list ($key, $value) = explode(': ', $line); $headers[$key][] = $value; 
         } 
    return $headers; 
}
Jeff
  • 6,895
  • 1
  • 15
  • 33
Anthony Harley
  • 1,327
  • 1
  • 8
  • 16
-3

you can do 2 ways

  1. by set curl_setopt($this->_ch, CURLOPT_HEADER, true); header will come out with response message from curl_exec(); you must search keyword 'Content-Disposition:' from response message.

  2. by use this function get_headers($url) right after you call curl_exec(). $url is url called in curl. the return is array of headers. search for 'Content-Disposition' in array to get what you want.

sun
  • 55
  • 1
  • 1
  • 3
    Using get_headers() would load the server unnecessarily with a second HTTP request, and may very well give an entirely different set of headers. – Pieter Ennes Jun 12 '13 at 12:23