1

Here's the code i'm trying to use to connect to the C F E website. Can someone help me with this. I can't find whats wrong with the code.

Even if i changed the codes , i get a (object move to here)

$ckfile = tempnam ("/tmp", "CURLCOOKIE");


$url = 


$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_COOKIEJAR, $ckfile); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);

$fields = array(
'ctl00$PHContenidoPag$UCLogin2$LoginUsuario$UserName' => 'xxxxxxxxxxxx',
'ctl00$PHContenidoPag$UCLogin2$LoginUsuario$Password' => 'xxxxxxxxxxxx',
);

$fields_string = '';
foreach($fields as $key=>$value) {
$fields_string .= $key . '=' . $value . '&';
}
rtrim($fields_string, '&');


$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
curl_setopt($ch, CURLOPT_COOKIEFILE, $ckfile); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 
$output = curl_exec($ch);

$url = 
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_COOKIEFILE, $ckfile); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
echo $output;
user3140580
  • 63
  • 1
  • 5
  • 1
    you have two major issues, `CURLOPT_POST` is a 1 or 0, not `$fields`. And you don't pass `$fields_string` to `CURLOPT_POSTFIELDS`, you just pass `$fields`, and passing stuff in the URL `Get` and passing a `POST` isn't the wisest – Forbs Nov 30 '17 at 02:01
  • Even with changing the CURLOPT_POST to 1 and CURLOPT_POSTFIELDS to $fields , i still get . – user3140580 Nov 30 '17 at 02:11

2 Answers2

2

you're doing several things wrong. first thing, your $fields_string is incorrectly encoded. in application/x-www-urlencoded, as you're trying to encode, $ must be encoded as %24, but you just send the $ directly. but if you stop to think about it, it should be obvious that your encoding method is wrong, because, what do you think would happen if the username or password contains & ? if there is an & in the username or password, it must be encoded as %26, spaces must be encoded as %20, and so on, use urlencode() to encode them, correcting the encoding loop, it looks like:

foreach($fields as $key=>$value) {
     $fields_string .= urlencode($key) . '=' . urlencode($value) . '&';
}
rtrim($fields_string, '&');

but luckily, php has a dedicated function for encoding to application/x-www-urlencoded called http_build_query, that entire loop & trim can (and should be) replaced by this single line:

$fields_string=http_build_query($fields);

second, you make a curl handle, set the CURLOPT_COOKIEJAR, and fetch the login page, i guess you do this to get a cookie session for your login request, which you indeed need to do, but you don't close the first curl handle before you create a brand new curl handle to do the login request. CURLOPT_COOKIEJAR is first flushed when the curl handle is closed, meaning that your first curl handle has not saved the cookies yet becuase you didn't do curl_close, so your second curl handle has no way of loading the first handle's cookies, meaning that it tries to login without a cookie session, which is required to login here.

third, your code completely ignores any setopt errors. curl_setopt returns bool(false) if there was a problem setting your option, which should not be ignored. to make sure there's no problems setting your curl options, i suggest you use this function instead:

function ecurl_setopt ( /*resource*/$ch , int $option , /*mixed*/ $value ):bool{
    $ret=curl_setopt($ch,$option,$value);
    if($ret!==true){
        //option should be obvious by stack trace
        throw new RuntimeException ( 'curl_setopt() failed. curl_errno: ' .  curl_errno ($ch) .' curl_error: '.curl_error($ch) );
    }
    return true;
}

fourth, this page appears to employ a CSRF-token-like scheme called __VIEWSTATE and __EVENTVALIDATION , given in the html of the login page load, which is required when logging in, your code completely ignores them, you must parse them out of the html and add them to your login request. i highly recommend DOMDocument/DOMXPath for this (... but the most common (and flawed) method of doing this is regexes...)

fifth, this line is nonsensical, and works by mistake: curl_setopt($ch, CURLOPT_POST, count($fields)); it's supposed to be bool true, not the number of post fields (luckily it works anyway because any int above zero is true-ish, aka close enough, but its still weird and suggest the author don't know what he's doing)

lastly, protip, you can reuse the same curl session as many times as you'd like, there's no reason for you to create 2 curl sessions in this php code. also, when debugging curl code, enable CURLOPT_VERBOSE, it prints lots of useful debugging info.

here's an example code, using hhb_curl as a curl wrapper (taking care of error detection and reporting, cookie handling, etc), doing none of your mistakes, which i think would work with a correct username & password on line 3 and 4:

<?php
declare(strict_types = 1);
const USERNAME = '???';
const PASSWORD = '???';
header ( "content-type: text/plain;charset=utf8" );
require_once ('hhb_.inc.php');
$hc = new hhb_curl ( '', true );
$html = $hc->exec ( 'https://app.cfe.gob.mx/Aplicaciones/CCFE/Recibos/Consulta/login.aspx' )->getStdOut ();
$domd = @DOMDocument::loadHTML ( $html );
$inputsRaw = getDOMDocumentFormInputs ( $domd, true ) ['aspnetForm'];
$inputs = array ();
foreach ( $inputsRaw as $tmp ) {
    $inputs [$tmp->getAttribute ( "name" )] = $tmp->getAttribute ( "value" );
}
assert ( isset ( $inputs ['__VIEWSTATE'], $inputs ['__EVENTVALIDATION'] ) );
$inputs ['ctl00$PHContenidoPag$UCLogin2$LoginUsuario$UserName'] = USERNAME;
$inputs ['ctl00$PHContenidoPag$UCLogin2$LoginUsuario$Password'] = PASSWORD;
hhb_var_dump ( $inputs );
$html = $hc->setopt_array ( array (
        CURLOPT_URL => 'https://app.cfe.gob.mx/Aplicaciones/CCFE/Recibos/Consulta/login.aspx',
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query ( $inputs ) 
) )->exec ()->getStdOut ();
// hhb_var_dump($html) & die();
$domd = @DOMDocument::loadHTML ( $html );
$xp = new DOMXPath ( $domd );
$loginErrors = $xp->query ( '//*[(contains(@style,"color:Red") or contains(@color,"Red")) and not(contains(@style,"hidden"))]' );
foreach ( $loginErrors as $tmp ) {
    echo "login error!! ";
    var_dump ( $tmp->textContent );
}
if (0 === $loginErrors->length) {
    echo "login success!";
}

function getDOMDocumentFormInputs(\DOMDocument $domd, bool $getOnlyFirstMatches = false): array {
    // :DOMNodeList?
    $forms = $domd->getElementsByTagName ( 'form' );
    $parsedForms = array ();
    $isDescendantOf = function (\DOMNode $decendant, \DOMNode $ele): bool {
        $parent = $decendant;
        while ( NULL !== ($parent = $parent->parentNode) ) {
            if ($parent === $ele) {
                return true;
            }
        }
        return false;
    };
    // i can't use array_merge on DOMNodeLists :(
    $merged = function () use (&$domd): array {
        $ret = array ();
        foreach ( $domd->getElementsByTagName ( "input" ) as $input ) {
            $ret [] = $input;
        }
        foreach ( $domd->getElementsByTagName ( "textarea" ) as $textarea ) {
            $ret [] = $textarea;
        }
        foreach ( $domd->getElementsByTagName ( "button" ) as $button ) {
            $ret [] = $button;
        }
        return $ret;
    };
    $merged = $merged ();
    foreach ( $forms as $form ) {
        $inputs = function () use (&$domd, &$form, &$isDescendantOf, &$merged): array {
            $ret = array ();
            foreach ( $merged as $input ) {
                // hhb_var_dump ( $input->getAttribute ( "name" ), $input->getAttribute ( "id" ) );
                if ($input->hasAttribute ( "disabled" )) {
                    // ignore disabled elements?
                    continue;
                }
                $name = $input->getAttribute ( "name" );
                if ($name === '') {
                    // echo "inputs with no name are ignored when submitted by mainstream browsers (presumably because of specs)... follow suite?", PHP_EOL;
                    continue;
                }
                if (! $isDescendantOf ( $input, $form ) && $form->getAttribute ( "id" ) !== '' && $input->getAttribute ( "form" ) !== $form->getAttribute ( "id" )) {
                    // echo "this input does not belong to this form.", PHP_EOL;
                    continue;
                }
                if (! array_key_exists ( $name, $ret )) {
                    $ret [$name] = array (
                            $input 
                    );
                } else {
                    $ret [$name] [] = $input;
                }
            }
            return $ret;
        };
        $inputs = $inputs (); // sorry about that, Eclipse gets unstable on IIFE syntax.
        $hasName = true;
        $name = $form->getAttribute ( "id" );
        if ($name === '') {
            $name = $form->getAttribute ( "name" );
            if ($name === '') {
                $hasName = false;
            }
        }
        if (! $hasName) {
            $parsedForms [] = array (
                    $inputs 
            );
        } else {
            if (! array_key_exists ( $name, $parsedForms )) {
                $parsedForms [$name] = array (
                        $inputs 
                );
            } else {
                $parsedForms [$name] [] = $tmp;
            }
        }
    }
    unset ( $form, $tmp, $hasName, $name, $i, $input );
    if ($getOnlyFirstMatches) {
        foreach ( $parsedForms as $key => $val ) {
            $parsedForms [$key] = $val [0];
        }
        unset ( $key, $val );
        foreach ( $parsedForms as $key1 => $val1 ) {
            foreach ( $val1 as $key2 => $val2 ) {
                $parsedForms [$key1] [$key2] = $val2 [0];
            }
        }
    }
    return $parsedForms;
}

it currently outputs:

login error!! string(35) "Usuario No Existente en Aplicacion."

saying that ??? is not a valid username.

hanshenrik
  • 19,904
  • 4
  • 43
  • 89
  • Thanks for your help on this one. The only problem is that i get a (HTTP ERROR 500) page – user3140580 Nov 30 '17 at 23:17
  • Update: I manage to do it on my local server and i get that response (HHB_VAR_DUMP_END login success!) . The problem is that it does not redirect me to 'https://app.cfe.gob.mx/Aplicaciones/CCFE/Recibos/Consulta/Default.aspx' . I see in your code that you didnt put the followed link after login. Where can i put that link ? Thanks again – user3140580 Nov 30 '17 at 23:33
  • @user3140580 maybe try to remove line 5, and make a new line after line 34, and write: `echo $hc->exec ( 'https://app.cfe.gob.mx/Aplicaciones/CCFE/Recibos/Consulta/Default.aspx' )->getStdOut ();` ? you can serve the browser the html curl recieves, but you cant transfer your curl's login session/cookies to the browser (at least not without target website's support for such an operation), the best you can do is probably to have curl act as a proxy. (unless you get support from the app.cfe.gob.mx website devs) – hanshenrik Dec 01 '17 at 00:10
  • The login works now ! [link](http://www.forumjeep.ca/connect.php) . The only last problem i see is that it shows the code instead of the website. Any idea ? – user3140580 Dec 01 '17 at 01:34
0

This is what I get when I follow your recommendations. At least I don't get the 'object move to here'. I'm able to get the login page, but that's it. It's not logging in. Try the code and at least you will see what it does. Thanks everyone for your help with this.

<?php


$ckfile = tempnam ("/tmp", "CURLCOOKIE");




$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_COOKIEJAR, $ckfile); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$output = curl_exec($ch);

$fields = array(
'ctl00$PHContenidoPag$UCLogin2$LoginUsuario$UserName' => 'xxx',
'ctl00$PHContenidoPag$UCLogin2$LoginUsuario$Password' => 'xxx',
);

$fields_string = http_build_query($fields);
foreach($fields as $key=>$value) {
$fields_string .= $key . '=' . $value . '&';
}
rtrim($fields_string, '&');


$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, count(1));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch, CURLOPT_COOKIEFILE, $ckfile); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$output = curl_exec($ch);

$url = 
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_COOKIEFILE, $ckfile); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$output = curl_exec($ch);
echo $output;

?>
user3140580
  • 63
  • 1
  • 5
  • $fields_string is now getting build twice because it should only be http_build_query. You don't need the foreach anymore. And after looking at their form, you're missing a lot of form fields like __VIEWSTATE and other things that will be required. You need to scrape the fields after first retrieving the login page. Request 1: GET login page. Scrape form inputs. Request 2: POST to login. Request 3: Confirm login. All from one curl handle with one curl_init, not multiple. – drew010 Nov 30 '17 at 03:45
  • I'm abit lost with what you are saying. I tried so many things that i have a hard time with it. Or i get to the login page OR i get a 500 error page OR i get a blank page – user3140580 Nov 30 '17 at 23:25