2

Dear Stackoverflow users, I am running into a problem. It is as follows: Currently i am programming a management tool for pfsense, which needs to send a multipart form that the server needs to validate and process. It should enable the voucher based acces control on the interface. However, i am getting the error that my headers are already sent. I did not sent them.

my code is as follows:

protected function doCurl($resourceID=null, $post=null)
{
    //volledige url
    $url = Yii::app()->params->pfsense['host'].$resourceID;

    $ch = curl_init();
    if($post != null)
    {
        $post_string = "";
        foreach($post as $key=>$value) 
        { 
            if($key != 'enctype')
            {
                $post_string .= $key.'='.$value.'&'; 
            }
            else
            {
                curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                                        'Content-Type: multipart/form-data'
                                        ));
            }
        }
        rtrim($post_string, '&');
        //var_dump($post);
        /**/
        curl_setopt($ch,CURLOPT_POST, count($post));
        curl_setopt($ch,CURLOPT_POSTFIELDS, $post_string);
        //var_dump($post_string);
    }
    else
    {
        curl_setopt($ch, CURLOPT_HEADER, true);
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    //omdat het certificaat niet klopt zetten we de verificatie uit.
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //we setten de useragent en de timeout. Useragent omdat sommige websites iets anders voorschotelen per browser. 
    //timeout voor als er iets gebeurd wat niet moet
    curl_setopt($ch,CURLOPT_USERAGENT,Yii::app()->params->pfsense['useragent']);
    curl_setopt($ch,CURLOPT_COOKIEJAR, Yii::app()->params->pfsense['cookiepath']);
    curl_setopt($ch,CURLOPT_COOKIEFILE, Yii::app()->params->pfsense['cookiepath']);
    curl_setopt($ch, CURLOPT_AUTOREFERER, true );
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,10);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $response = curl_exec($ch);
    $result = array( 'header' => '', 
                     'body' => '', 
                     'http_code' => '',
                     'last_url' => '');

    $header_size = curl_getinfo($ch,CURLINFO_HEADER_SIZE);
    $result['header'] = substr($response, 0, $header_size);
    $result['body'] = substr( $response, $header_size );
    $result['http_code'] = curl_getinfo($ch,CURLINFO_HTTP_CODE);
    $result['last_url'] = curl_getinfo($ch,CURLINFO_EFFECTIVE_URL);
    //curl_close($ch);
    return $result;        
}

public function curl($resourceID=null, $post=null)
{
    $result = $this->doCurl($resourceID, $post);
    if(strpos($result['body'], 'Login') == false && $result['http_code'] != 403)
    {
        //echo $result['body'];
        return $result;
    }
    else 
    {
        $loginpost = array(
                        '__csrf_magic' => substr($result['body'], strpos($result['body'],'sid:') , 55),
                        'login' => urlencode('Login'),
                        'usernamefld' => urlencode(Yii::app()->params->pfsense['pfuser']),
                        'passwordfld' => urlencode(Yii::app()->params->pfsense['pfpass'])
                    );
        $result = $this->doCurl('',$loginpost);
        $result = $this->doCurl($resourceID, $post);
        return $result;
    }
}

This is the code that allows a curl request to be sent to the server. If the page that is returned is the login page, the login info needs to be sent and the original post request needs to be sent again.

the code that follows is the code to insert a zone:

 public function insertZone($post)
{
    $description = $post['description'];
    $interface = $post['interfaces'];
    $name = $post['name'];

    $post=null;
    $post['zone'] = $name;
    $post['descr'] = $description;
    $post['Submit'] = 'Continue';
    $result = $this->curl(Yii::app()->params->pfsense['pfpathtoinsertzone']);
    $post['__csrf_magic'] = substr($result['body'], strpos($result['body'],'sid:') , 55);
    var_dump($post);
    $result = $this->curl(Yii::app()->params->pfsense['pfpathtoinsertzone'], $post);
    var_dump($result['body']);
    //exit;
    if(strpos($result['body'], 'The following input errors were detected') == false)
    {
        $post = null;
        $post['enable'] = 'yes';
        $post['interfaces'] = $interface;
        $post['Submit'] = 'Save';
        $post['name'] = $name;

        $result = $this->editZone($post);
        if($result != false)
        {
            $post = null;
            $post['zone'] = $name;
            $post['enable'] = 'yes';
            $post['Submit'] = 'Save';

            $result = $this->curl(Yii::app()->params->pfsense['pfpathtovoucherroll'].$name);
            $post['__csrf_magic'] = substr($result['body'], strpos($result['body'],'sid:') , 55);

            $doc = new DOMDocument();
            $doc->loadHTML($result['body']);
            $doc->preserveWhiteSpace = false;
            if($childs = $doc->getElementsByTagName("textarea"))
            {
                foreach($childs as $child)
                {
                    if($child->nodeType == XML_TEXT_NODE)
                    {
                        continue;
                    }
                    if(strpos(trim($child->nodeValue),'BEGIN RSA PRIVATE KEY'))
                    {
                        $post['privatekey'] = trim($child->nodeValue);
                    }
                    elseif(strpos(trim($child->nodeValue),'BEGIN PUBLIC KEY'))
                    {
                        $post['publickey'] = trim($child->nodeValue);
                    }
                }
            }
            $post['charset'] = $doc->getElementById('charset')->attributes->getNamedItem('value')->nodeValue;
            $post['rollbits'] = $doc->getElementById('rollbits')->attributes->getNamedItem('value')->nodeValue;
            $post['ticketbits'] = $doc->getElementById('ticketbits')->attributes->getNamedItem('value')->nodeValue;
            $post['checksumbits'] = $doc->getElementById('checksumbits')->attributes->getNamedItem('value')->nodeValue;
            $post['magic'] = $doc->getElementById('magic')->attributes->getNamedItem('value')->nodeValue;
            $result = $this->curl(Yii::app()->params->pfsense['pfpathtovoucherroll'].$name, $post);
            if($result['http_code'] >= 100 && $result['http_code'] <= 299)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }
    else 
    {
        return false;
    }
}

public function editZone($post)
{
    $zone = $post['name'];
    $interfaces = $post['interfaces'];
    $post = null;
    //$post['localauth_priv'] = 'yes';
    //$post['radiussrcip_attribute'] = strtolower($interfaces);
    if(is_array($interfaces))
    {
        $post['cinterface[]'] = array_map('strtolower', $interfaces);
    }
    else
    {
        $post['cinterface[]'] = strtolower($interfaces);
    }

    $post['auth_method'] = 'local';
    $post['radiussrcip_attribute'] = 'wan';
    $post['radiusvendor'] = 'default';
    $post['radmac_format'] = 'default';
    $post['enable'] = 'yes';
    $post['Submit'] = 'Save';
    $post["maxprocperip"] = '';
    $post["idletimeout"] = '';
    $post["timeout"] = '';
    $post["freelogins_count"] = '';
    $post["freelogins_resettimeout"] = '';
    $post["preauthurl"] = '';
    $post["redirurl"] = '';
    $post["blockedmacsurl"] = '';
    $post["bwdefaultdn"] = '';
    $post["bwdefaultup"] = '';
    $post["radiusip"] = '';
    $post["radiusport"] = '';
    $post["radiuskey"] = '';
    $post["radiusip2"] = '';
    $post["radiusport2"] = '';
    $post["radiuskey2"] = '';
    $post["radiusip3"] = '';
    $post["radiusport3"] = '';
    $post["radiuskey3"] = '';
    $post["radiusip4"] = '';
    $post["radiusport4"] = '';
    $post["reauthenticateacct"] = '';
    $post["radmac_secret"] = '';
    $post["radiusvendor"] = 'default';
    $post["radiusnasid"] = '';
    $post["radmac_format"] = 'default';
    $post["httpsname"] = '';
    $post['certref'] = '';
    $post['enctype'] = true;

    $post['zone'] = $zone;
    $post['enable'] = 'yes';
    $post['Submit'] = 'Save';

    $result = $this->curl(Yii::app()->params->pfsense['pfpathtoupdatezone'].$zone);
    //echo $result['last_url'];
    $post['__csrf_magic'] = substr($result['body'], strpos($result['body'],'sid:') , 55);
    //var_dump($post);
    $result = $this->curl(Yii::app()->params->pfsense['pfpathtoupdatezone'].$zone, $post);
    ini_set('xdebug.var_display_max_depth', -1);
    ini_set('xdebug.var_display_max_children', -1);
    ini_set('xdebug.var_display_max_data', -1);
    var_dump($result['body']);
    exit;
    if($result['http_code'] >= 100 && $result['http_code'] <= 299)
    {
        return true;
    }
    else
    {
        //var_dump($result);
        ///exit;
        return $result;
    }
}

This code works by first inserting a zone with the name and description and then updating it to set the interface active and enabling the captive portal page to be displayed. However, if i sent the page without the multipart form(it seems to be that this is the issue) then the authentication is not set correctly. It is set, but it does not work. If i then manually change the authentication setting (it is a radio button, if i choose another radio button and then choose my original radio button it suddenly works)

has anyone have a clue about what i am doing wrong? because with the following code i get the result that my headers are already sent:

    $result = $this->curl(Yii::app()->params->pfsense['pfpathtoupdatezone'].$zone, $post);
    ini_set('xdebug.var_display_max_depth', -1);
    ini_set('xdebug.var_display_max_children', -1);
    ini_set('xdebug.var_display_max_data', -1);
    var_dump($result['body']);
    exit;

i would appreciate all the help i can get. thanks in advance!

Michel Kok
  • 81
  • 9

2 Answers2

1

What got my request to work: it turned out that there was no enctype needed in the request. It was however, needed that the update request was sent a 3th time. Do'nt ask me why.

Michel Kok
  • 81
  • 9
0

If anything, anything at all, is output, e.g. echo, var_dump, you will get this error.

curl sets the headers to application/x-www-form-urlencoded. If the post data is sent as a string.

If it is sent as an array, it uses a Content-Type: multipart/form-data If that is not it, add this to see the Request header:

Now I am not sure exactly how you fixed it, but you may have a problem.

It appears your data is in an array and then sent as a sting for some unknown reason.

It should have stayed in the array. This code is off.

The else will be executed for every foreach loop. Probably will not hurt anything it is just a mistake

   foreach($post as $key=>$value) 
    { 
        if($key != 'enctype')
        {
            $post_string .= $key.'='.$value.'&'; 
        }
        else
        {
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: multipart/form-data'));
        }
    }

Should have been:

    if($key == 'enctype'){
       curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: multipart/form-data'));  

     }
     else{
        foreach($post as $key=>$value){ 
          $post_string .= $key.'='.$value.'&';
        } 
    }

I think you sent the data as a string, or not at all.

This is the big question: if($key != 'enctype') Why? Is this open source?

The above loop would be used only if the post data had to be sent encoded.

And this part:

if($key == 'enctype'){
   curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: multipart/form-data'));  
}

Should just be:

if($key == 'enctype'){
  $post_string = $post;
}

This way, because the post data is in an array curl will automatically use Content-Type: multipart/form-data

The problem is if it is sent as a string curl will use application/x-www-form-urlencoded, then you have to add this after the loop:

$post_string = urlencode($post_string);

Like this:

     else{
        foreach($post as $key=>$value){ 
          $post_string .= $key.'='.$value.'&';
        } 
        $post_string = urlencode($post_string);
      }
Misunderstood
  • 5,534
  • 1
  • 18
  • 25