1

I'm using file_get_contents() to get a PHP file which I use as a template to create a PDF.

I need to pass some POST values to it, in order to fill the template and get the produced HTML back into a PHP variable. Then use it with mPDF.

This works perfectly on MY server (a VPS using PHP 5.6.24)...
Now, at the point where I'm installing the fully tested script on the client's live site (PHP 5.6.29),
I get this error:

PHP Warning: file_get_contents(http://www.example.com/wp-content/calculator/pdf_page1.php): failed to open stream: HTTP request failed! HTTP/1.1 406 Not Acceptable

So I guess this can be fixed in php.ini or some config file.
I can ask (I WANT TO!!) my client to contact his host to fix it...

But since I know that hosters are generally not inclined to change server configs...
I would like to know exactly what to change in which file to allow the code below to work.

For my personnal knowledge... Obviously.
But also to make it look "easy" for the hoster (and my client!!) to change it efficiently. ;)

I'm pretty sure this is just one PHP config param with a strange name...

<?php
$baseAddr = "http://www.example.com/wp-content/calculator/";

// ====================================================
// CLEAR OLD PDFs

$now = date("U");
$delayToKeepPDFs = 60*60*2; // 2 hours in seconds.

if ($handle = opendir('.')) {

    while (false !== ($entry = readdir($handle))) {

      if(substr($entry,-4)==".pdf"){
        $fileTime = filemtime($entry);        // Returns unix timestamp;
        if($fileTime+$delayToKeepPDFs<$now){
          unlink($entry);                     // Delete file
        }
      }
    }
    closedir($handle);
}
// ====================================================



// Random file number
$random = rand(100, 999);

$page1 = $_POST['page1'];   // Here are the values, sent via ajax, to fill the template.
$page2 = $_POST['page2'];

// Instantiate mpdf
require_once __DIR__ . '/vendor/autoload.php';
$mpdf = new mPDF( __DIR__ . '/vendor/mpdf/mpdf/tmp');


// GET PDF templates from external PHP
// ==============================================================
// REF: http://stackoverflow.com/a/2445332/2159528
// ==============================================================
$postdata = http_build_query(
  array(
    "page1" => $page1,
    "page2" => $page2
  )
);
$opts = array('http' =>
  array(
    'method'  => 'POST',
    'header'  => 'Content-type: application/x-www-form-urlencoded',
    'content' => $postdata
  )
);
$context  = stream_context_create($opts);
// ==============================================================


$STYLE .=  file_get_contents("smolov.css", false, $context);
$PAGE_1 .=  file_get_contents($baseAddr . "pdf_page1.php", false, $context);
$PAGE_2 .=  file_get_contents($baseAddr . "pdf_page2.php", false, $context);

$mpdf->AddPage('P');
// Write style.
$mpdf->WriteHTML($STYLE,1);

// Write page 1.
$mpdf->WriteHTML($PAGE_1,2);


$mpdf->AddPage('P');
// Write page 1.
$mpdf->WriteHTML($PAGE_2,2);


// Create the pdf on server
$file =  "training-" . $random . ".pdf";

$mpdf->Output(__DIR__ . "/" . $file,"F");

// Send filename to ajax success.
echo $file;
?>

Just to avoid the "What have you tried so far?" comments:
I searched those keywords in many combinaisons, but didn't found the setting that would need to be changed:

  • php
  • php.ini
  • request
  • header
  • content-type
  • application
  • HTTP
  • file_get_contents
  • HTTP/1.1 406 Not Acceptable
Louys Patrice Bessette
  • 27,837
  • 5
  • 32
  • 57
  • Usually the host places a `php.ini` file into a folder (perhaps an outside-of-root folder, possibly in a `cgi-bin` folder or something) that you should be able to change at will. Sometimes it's a `user.ini` file, but usually a host allows the client to change the `ini` file to some degree. Most will overwrite important settings that allow shell access or whatever, but you should be able to set memory limits, max upload, allow url fopen, etc. – Rasclatt Aug 01 '17 at 19:10
  • 1
    Also noted on [this SO question](https://stackoverflow.com/questions/7794604/file-get-contents-not-working), there is a `cURL` option to try. – Rasclatt Aug 01 '17 at 19:13
  • Also try using `fopen()` / `fread()` / `fclose()`, see if there is an error there: http://php.net/manual/en/function.fread.php – Rasclatt Aug 01 '17 at 19:17
  • @Rasclatt: First test seems to work, using the answer you mentionned... But How do I pass the POST values? – Louys Patrice Bessette Aug 01 '17 at 19:37
  • Did you do `curl`? – Rasclatt Aug 01 '17 at 19:38
  • Yes... I used the code of the answer you mentionned. I just provided the URL. I have no error and have the template. But there is errors about undefined index, which are the POST values. So the template is just empty. – Louys Patrice Bessette Aug 01 '17 at 19:40
  • You can send the request as a POST, here is an example: https://davidwalsh.name/curl-post – Rasclatt Aug 01 '17 at 19:40
  • The only modification I might make there is to create the string from your array using `http_build_query()` in favor of a `foreach()` loop: http://php.net/manual/en/function.http-build-query.php – Rasclatt Aug 01 '17 at 19:43
  • @Rasclatt: I'm now getting `Not Acceptable! An appropriate representation of the requested resource could not be found on this server. This error was generated by Mod_Security.` **In the PDF**. Do you want me to edit the question to show what I just tried? – Louys Patrice Bessette Aug 01 '17 at 19:51
  • You can try to fool the server into thinking the connection is a browser. Send a user agent in the request: `curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56');` – Rasclatt Aug 01 '17 at 19:54
  • If that doesn't work, you may want to change the request to `GET` instead of `POST`...and beyond that, I am out of ideas! :D – Rasclatt Aug 01 '17 at 19:55
  • It seems to work... But I now get `urlencode() expects parameter 1 to be string, array given`. This is an array of arrays... Implode would mess it all. – Louys Patrice Bessette Aug 01 '17 at 20:02
  • How are you getting `$fields` and `$feilds2` into your function? You don't seem to be injecting those... – Rasclatt Aug 01 '17 at 20:08
  • `$fields` are the same for both URL (The pdf page uses the right one). Right now, I'm fassing it to `http_build_query()` then `fields2` is sent to curl... – Louys Patrice Bessette Aug 01 '17 at 20:10
  • 1
    I know, but your function is set as `curl_get_contents($url)` but you are not passing `$fields` and `$fields2`, should it not be `function curl_get_contents($url,$fields)` and then inside the function you compile the fields into the `curl`? – Rasclatt Aug 01 '17 at 20:12
  • That's IT !!!! hehehe I suddenly worked! – Louys Patrice Bessette Aug 01 '17 at 20:16
  • 1
    Cool man, glad it works! Cheers. – Rasclatt Aug 01 '17 at 20:18
  • I will post the working code... If you want to post it as an answer, I will be happy to accept it ;) – Louys Patrice Bessette Aug 01 '17 at 20:20
  • 1
    Na it's alright, you just take your working edit, and post it yourself as an answer then accept it so others know it's answered/close. If you find any of my other answers in my profile helpful, you can upvote one. Good enough! – Rasclatt Aug 01 '17 at 20:24

1 Answers1

2

Maaaaany thanks to @Rasclatt for the priceless help! Here is a working cURL code, as an alternative to file_get_contents() (I do not quite understand it yet... But proven functional!):

function curl_get_contents($url, $fields, $fields_url_enc){
    # Start curl
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_HEADER, 0);
    # Required to get data back
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    # Notes that request is sending a POST
    curl_setopt($ch,CURLOPT_POST, count($fields));
    # Send the post data
    curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_url_enc);
    # Send a fake user agent to simulate a browser hit
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56');
    # Set the endpoint
    curl_setopt($ch, CURLOPT_URL, $url);
    # Execute the call and get the data back from the hit
    $data = curl_exec($ch);
    # Close the connection
    curl_close($ch);
    # Send back data
    return $data;
}
# Store post data
$fields = array(
  'page1' => $_POST['page1'],
  'page2' => $_POST['page2']
);
# Create query string as noted in the curl manual
$fields_url_enc = http_build_query($fields);
# Request to page 1, sending post data
$PAGE_1 .=  curl_get_contents($baseAddr . "pdf_page1.php", $fields, $fields_url_enc);
# Request to page 2, sending post data
$PAGE_2 .=  curl_get_contents($baseAddr . "pdf_page2.php", $fields, $fields_url_enc);
Rasclatt
  • 12,249
  • 3
  • 21
  • 32
Louys Patrice Bessette
  • 27,837
  • 5
  • 32
  • 57
  • 1
    Not to be a pain, but you can actually check it off as correct/answered as well.... – Rasclatt Aug 01 '17 at 21:33
  • Also, in a response to your comment *(I do not quite understand it yet... But proven functionnal!)*, basically `curl` is a faceless request to a server, like a browser request but stripped of important identity markers. So you basically build a request (like the browser does) and you can customize what you send to the server. In this case, you are sending a `POST` request and you are sending a fake user agent to simulate a browser hit, then you are also wanting to get data back from that request (with `CURLOPT_RETURNTRANSFER`). – Rasclatt Aug 01 '17 at 21:38
  • The reason you have to send a user agent is that the server is probably not allowing traffic from faceless requests (possibly some sort of first-line security feature), so you have to give it a face...even though it's not a real one. It's obviously not that sophisticated since the workaround is sending a header. This is a way that one server can bruteforce another server by doing `cURL` hits in a loop in rapid succession (or in slow succession with the script sleeping after every attempt so it doesn't appear like a bruteforce). Anyway, hopefully that has *some* clarity. – Rasclatt Aug 01 '17 at 21:45
  • @Rasclatt: I will be able to mark it as accepted in two days... **Many thanks for the clarifications**, I will do some reading. ;) – Louys Patrice Bessette Aug 01 '17 at 23:48
  • Really? I thought if you had a certain rep it was sooner, maybe not! Anyway I updated your answer just to notate so people would know what it all does, hopefully you don't mind. – Rasclatt Aug 01 '17 at 23:50