Autor Zpráva
XolyCZ
Profil
Ahoj, potřebuju to zkontrolovat, jestli to dělám správě. Potřebuju nějak pracovat s databází a tak se to nějak učím, protože jsem kdysi používal nějakou knihovnu, přes kterou to bylo jednodušší, ale řekl jsem si, že to zkusím takhle. Dám sem malý úsek kódu.
$email = mysqli_real_escape_string($spojeni, $_POST['email']);
$sql_email = "SELECT count(player_id) AS emailCheck FROM players WHERE email=" . $email;
$email_check = mysqli_query($spojeni, $sql_email);
while ($email_checked = mysqli_fetch_row($email_check)){
    $emailCheck = $email_checked;
}
    

Mám to takhle správně ošetřené proti SQL Injection? Jinak nemám to zatím ověřené, jestli to funguje. Nevím jestli se tam v tomto případě musí psát $emailCheck[] nebo stačí to co tam je, protože by mi to mělo vrátit jenom jednu hodnotu. Takže teoreticky by tam nemusel být ani ten while, ale to je takové moje tušení, které není podložené. Tak jestli je tam chyba, díky za upozornění. :)
Joker
Profil
Na tuto otázku:
Mám to takhle správně ošetřené proti SQL Injection?
je odpověď ano, ta funkce mysqli_real_escape_string na to stačí.

Nicméně ten kód fungovat nebude, nestačí vzít mysql_ funkce a dopsat na konec „i“.
Rozšíření mysqli nepoužívá tu metodiku z rozšíření mysql, že provedení dotazu vrátí resource a ten se pak předává do fetch_row.
Funkce mysql_query vrací objekt, který má přímo metodu pro získání záznamu.

Dále funkce mysqli_query může vrátit různé druhy výsledků a bylo by fajn ošetřit alespoň variantu, kdy nastane nějaká chyba a to volání vrátí false.

Jinak jestli se to učíte, já osobně bych možná doporučil se naučit spíš PDO (kde je výhoda, že to je jednotné rozhraní pro různé druhy databází) a prepared statements.
Keeehi
Profil
Plus ještě jelikož jde o řetězec, tak v tom SQL dotazu by měl být obalen apostrofy.

XolyCZ:
protože by mi to mělo vrátit jenom jednu hodnotu
Metoda fetch_row vždy vrací pole. Nezáleží na tom zda dotaz je na více sloupců nebo na jeden. I když je na jeden, tak se prostě vrátí jednoprvkové pole. Může se zdát, že to je zbytečné ale je to kvůli konzistentnosti. Pokud by funkce vracela občas pole, občas řetězec, muselo by se pak vždy detekovat, co se vlastně obdrželo. Takhle víš, že to vždy bude pole a můžeš se podle toho zachovat.*

* Reálně to není vždy pole, může to být ještě null. Ale to je celkem v pořádku vzhledem k tomu co tato hodnota znamená a ke způsobu jakým se metoda běžně používá.
XolyCZ
Profil
Joker:
No tak ale co tam mám tím pádem dopsat? Když se podívám na tyto odkazy, tak tam nic není potřeba doplňovat.

php.net/manual/en/mysqli.query.php
php.net/manual/en/mysqli.store-result.php

Mi se v tomto případě jedná jenom o to, jestli ten dotaz něco obsahuje nebo ne, jestli něco našel nebo ne. Mohl bych použít mysqli_fetch_array a ukládat to třeba do associativního pole, ale to je mi k ničemu.
Kajman
Profil
A funkci mysqli_fetch_row v php manuálu najdete?
XolyCZ
Profil
Kajman:
Aha hodil jsem tu blbý odkaz. php.net/manual/en/mysqli-result.fetch-row.php

Jinak já bych asi mohl použít i mysqli_num_rows ne?
Kajman
Profil
XolyCZ:
Jinak já bych asi mohl použít i mysqli_num_rows ne?

V kombinaci s count() v dotazu ne. I když řádek nenalezne, vrátí se jeden řádek s počtem nula.

Pro num_rows by musel být dotaz nějak takto...
$sql_email = "SELECT 1 AS emailCheck FROM players WHERE email='" . $email "' LIMIT 1";
XolyCZ
Profil
Kajman:
Takže výsledek by v tomto případě byl takový?
$email = mysqli_real_escape_string($spojeni, $_POST['email']);
$sql_email = "SELECT 1 FROM players WHERE email='" . $email "' LIMIT 1";
$email_check = mysqli_query($spojeni, $sql_email);
$emails_founded = mysqli_num_rows($email_check );
Keeehi
Profil
XolyCZ:
V podstatě ano. Jediné co tam je trochu zavádějící je jméno proměnné $emails_founded. Z něj se zdá, že by mohlo být nalezeno i více emailů než jeden. Což vzhledem k LIMIT 1 nikdy nemůže nastat. Pokud ve sloupci může být vícekrát stejný email, asi bych odstranil LIMIT 1 a proměnná by pak obsahovala to co názvem naznačuje - počet výskytů daného emailu ve sloupci. Pokud může být email ve sloupci maximálně jednou (nad sloupcem je nastaven unikátní index), pak tam ten limit zůstat může (i když je asi zbytečný) a poslední řádek bych přepsal na
$email_was_found = mysqli_num_rows($email_check ) !== 0;
V $email_was_found je pak true nebo false podle toho zda email byl nebo nebyl nalezen.
XolyCZ
Profil
Keeehi:
Dobře, opravím to. Ještě mě napadá jedna věc. Když zapisuju do databáze heslo, které už je v hashi, je taky potřeba ho protáhnout přes mysqli_real_escape_string?
Methez
Profil *
Prosím použij raději třídu PDO. V rámci bezpečnosti to děláš hlavně kvůli sobě :)
XolyCZ
Profil
Methez:
No tak teď by mě zajímalo jestli je to dobře nebo ne.. :D

$email_check_prepare = $db_connection->prepare("SELECT 1 FROM players WHERE email=? LIMIT 1");
$email_check = $email_check_prepare->execute(array($_POST['email']));
$email_was_found = ($email_check->num_rows) !== 0;
Kajman
Profil
XolyCZ:
Jestli je $db_connectin instance mysqli, tak chybí bind_params.

Methez:
I v PDO můžu splácat nebezpečný dotaz, když si ho bude sestavovat sám.
XolyCZ
Profil
Kajman:
Nevím co znamená instance, ikdyž jsem si to hledal. To spojení jsem přehodil na tohle:
$db_connection = new mysqli("...", "...", "...", "...");

Já se dívám na více stránek a na každé je to zapsané trošku jinak, takže se začínám ztrácet v tom co tam vlastně proč musí být. Ty bind_params, jestli jsem to dobře pochopil, doplní ty otazníky s tím, že ví co je to za typ proměnné. No ale proč je to vlastně nutné?

EDIT: Tam by ale asi mělo být tohle co...$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
Nebo se to používá ještě při jinačím zápisu..
Keeehi
Profil
XolyCZ:
Když zapisuju do databáze heslo, které už je v hashi, je taky potřeba ho protáhnout přes mysqli_real_escape_string?
Teoreticky by jsi nemusel. U hašovacích funkcí máš přesně definovanou znakovou sadu a nenapadá mě jediný který by v ní měl nějaké nebezpečné znaky. Ovšem když tam to escapování bude, ničemu to vadit nebude. A je prostě dobrou praxí escapovat vždy a vše. Odpadá pak nutnost přemýšlet zda v každém konkrétním případě je escapování potřeba nebo ne a tím se i snižuje pravděpodobnost, že by jsi na něco zapomněl.
XolyCZ
Profil
Keeehi:
No jestli se vydám teda tou cestou PDO, tak to teď asi nebudu potřebovat. Musím akorát pochopit co k čemu patří a teď vidím, že jsem napsal [#12] ve verzi PDO a to připojení k databázi v MySQLi. Prakticky nevím co pro mě bude ve výsledku lepší, ale musím to nějak poskládat no.

A vlastně jsem to zase řekl špatně, mám v tom guláš :D No zkusím si ještě něco přečíst.
XolyCZ
Profil
No tak jsem se teda ještě podíval a vytvořil tohle.. Funguje mi to teda nakonec všechno :D

$dsn = "mysql:host=uvdb36.active24.cz;dbname=forthewicz;charset=utf8";
$options = [
  PDO::ATTR_EMULATE_PREPARES   => false, // turn off emulation mode for "real" prepared statements
  PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, //turn on errors in the form of exceptions
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //make the default fetch be an associative array
];
try {
  $pdo = new PDO($dsn, "...", "...", $options);
} catch (Exception $e) {
  error_log($e->getMessage());
  exit('Something weird happened'); //something a user can understand
}


function registration($pdo, &$message){
    if (empty($_POST['email'])) {
        $message = 'Nevyplnil jsi email.<br>';
    }
    
     if (empty($_POST['nickname'])) {
        $message .= 'Nevyplnil jsi nick.<br>';
    }
    
    if (empty($_POST['password'])) {
        $message .= 'Nevyplnil jsi heslo.<br>';
    }
    
    if($_POST['password'] != $_POST['repeated_password']){
        $message .= 'Zadané hesla se neshodují';
    }
    
    if (!empty($message)) {
        return false;
    }
    
    
    $email_checking = $pdo->prepare("SELECT COUNT(*) FROM players WHERE email=? LIMIT 1");
    $email_checking->execute([$_POST['email']]);
    if($email_checking->fetchColumn() > 0){
        $email_checking = null;
        $message = 'Tento email již někdo používá.<br>';
        return false;
    }
    $email_checking = null;
    
    $nickname_checking = $pdo->prepare("SELECT COUNT(*) FROM players WHERE nickname=? LIMIT 1");
    $nickname_checking->execute([$_POST['nickname']]);
    if($nickname_checking->fetchColumn() > 0){
        $nickname_checking= null;
        $message = 'Tento nick již někdo používá.<br>';
        return false;
    }
    $nickname_checking = null;
    
    
    $password = password_hash($_POST['password'], PASSWORD_DEFAULT);
    
    
    $player_insert = $pdo->prepare("INSERT INTO players (nickname, password, email) VALUES(?,?,?)");
    $player_insert->execute([$_POST['nickname'], $password, $_POST['email']]);
    if($player_insert->rowCount() > 0){
        $player_insert = null; 
        $message="Byl/a jsi úspěšně zaregistrován/a, nyní se <a href='login.php?str=user'>přihlaš</a>.";
        return true;
    }else{
        $player_insert = null; 
        $message="Nepovedlo se vytvořit nový účet.";
        return false;
    }    
}


Dočetl jsem se, že rowCount() nefunguje při SELECTu nebo aspoň ne ve všech databázích, tak jsem použil jinačí variantu, při zápisu už ne. Ještě mě napadá, jestli mám správně ukončené to spojení nebo jestli je vůbec třeba to tam psát.
Keeehi:
Zkoušel jsem to v totom případě ještě s tím tvým $email_was_founded, ale nechtělo mi to fungovat, nevím proč. Jinak, možná že ta podmínka co je tam teď vyjde nastejno.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0