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.