PHP » MyEgo.cz - Radek Hulán webzine

MyEgo.cz

home foto blogy mywindows.cz kontakt

bCompiler - kompilace a ochrana PHP kódu zdarma

PHP 16.03.2007

PHP kód je ve standardní konfiguraci v jedné věci poměrně nepříjemný pro vývojářské firmy - kdokoliv má přístup k serveru, má přístup i ke zdrojovým kódům, navíc se při každém spuštění PHP skriptu všechno musí znovu a znovu kompilovat, tisíckrát denně, což drasticky snižuje výkon aplikace a zbytečně zatěžuje server.

Kompilace PHP do bytecode (podobně jako v Javě) může být kromě zrychlení aplikace použita i jako určitý (samozřejmě nikoliv stoprocentní) způsob ochrany.

Pokud si například klient chce vyzkoušet redakční systém na své síti, nahrávat mu tam desítky MB vašich zdrojových PHP kódů není zcela určitě to pravé. Kompilace do bytecode tuto situaci v praxi uspokojivě řeší, pokud tedy neexistuje pro daný kompiler volně dostupný decode proces (dekompiler).

bCompiler - zdarma dostupný kompiler PHP

Pro komerčně dostupný Zend Encoder dekompiler bohužel existuje, pro Ioncube rovněž, pro bCompiler, dodávaný v PHP licenci (tedy zdarma, navíc je zde možnost modifikace bez nutnosti zveřejnění zdrojových kódů a tím kompromitace případného uživatelského šifrování) a zdokumentovaný na webu PHP ovšem zřejmě nikoliv (Google hledání: (bcompiler decompiler, bcompiler decode).

Začal jsem bCompiler používat a jsem velice spokojen.

Výsledek je 100% kompatibilní s originálem, soubor je menší, a celá aplikace je (i při použití BZ2) podstatně rychlejší, zvláště pokud používáte rozsáhlé třídy.

Na Windows si můžete bCompiler.dll stáhnout pro různé verze PHP na pecl4win.php.net. Následně již jen povolíte v php.ini bCompiler a případně i bz2 a můžete kompilovat do bytecode:

extension=php_bcompiler.dll
extension=php_bz2.dll

Základy SQL, část 2.

PHP 12.03.2007

Tento článek navazuje na Základy SQL, část 1., kde jsem psal především o SELECTech a JOINech na MySQL databázi.

V této části se budu věnovat tvorbě VIEW a TRIGGER. Na uvedenou logiku je potřeba minimálně MySQL 5.0 a vyšší (popřípadě libovolná 15 roků stará Oracle databáze).

CREATE VIEW

Představte si, že pracujete na projektu, který má za úkol sjednotit dva velice rozdílné projekty pod jedním uživatelským účtem a společným přihlášením. Třeba BLOG:CMS pro publikaci článků a IP.Board jako fórum.

BLOG:CMS používá pro autorizaci tabulku members, IP.Board pro stejný účel používá hned 3 tabulky, a to ipb_members (přihlašovací jméno), ipb_member_extra (email) a ipb_member_converge (hash hesla). Naštěstí jsou veškeré údaje, které BLOG:CMS potřebuje, obsažené i v IP.Board tabulkách. Toto celé svádí k tomu zjednodušit si práci tím, že tabulku members v BLOG:CMS dropnu a místo ní udělám pohled (VIEW) na 3 tabulky IP.Boardu.

Například takto:

DROP TABLE `member`;

CREATE VIEW `member` AS 
SELECT 
	`m`.`id` AS `mnumber`,
	`m`.`name` AS `mname`,
	`m`.`members_display_name` AS `mrealname`,
	`m`.`email` AS `memail`,
	`e`.`website` AS `murl`,
	`e`.`notes` AS `mnotes`,
	`c`.`converge_pass_hash` AS `mpassword`,
	`c`.`converge_pass_salt` AS `mcookiehash`,
	`m`.`member_login_key` AS `mcookiekey`,
	`e`.`mcanlogin` AS `mcanlogin`,
	`e`.`deflang` AS `deflang`,
	`e`.`madmin` AS `madmin` 
FROM (
	(`ipb_members` `m` 
	JOIN `ipb_member_extra` `e` ON ((`m`.`id` = `e`.`id`))) 
	JOIN `ipb_members_converge` `c` ON 
		((`m`.`id` = `c`.`converge_id`)));

Pro PHP kód bude celá operace zcela transparentní, nepozná, zda se autorizuje proti TABLE member či nově proti VIEW member. Tímto jednoduchým způsobem je možné docílit toho, že dvě zcela různé aplikace sdílejí během pár sekund jednu databázi uživatelů (a protože IP.Board má skvělé prostředky pro správu uživatelů, byla zvolena jeho databáze).

Podobně je VIEW možné používat kdekoliv, kde jinde použijete tabulku s WHERE podmímkou. V redakčním systému je dobré si vytvořit VIEW, které bude filtrovat zatím nepublikované články (drafts) a články, které jsou určeny k publikaci k pozdějšímu datu. Tvorbou tohoto jednoho VIEW ušetříte programátorský čas a zpřehledníte aplikaci (nehovoře o snazším uzpůsobení), nebudete totiž muset na desítkách míst opakovat ty samé WHERE podmínky. Také je to dobrá prevence chyb, jedno VIEW se ladí lépe než desítky WHERE podmínek v PHP kódu.

Používat VIEW místo natvrdo psaných WHERE podmínek je základ dobrého programátorského stylu. Bohužel, mimořádně primitivní MySQL databáze (nazývám ji spíše filesystém) tuto triviální funkčnost umí až od verze 5.0.

Dostupnost x64 verzí Apache, MySQL a PHP pro Windows

1 příspěvek PHP 15.02.2007

S Windows Vista 64b přišla i možnost provozovat oblíbené open-source produkty pro webdesign / webhosting, tedy Apache, MySQL a PHP, v 64-bitových edicích. Tedy, alespoň teoreticky. Pokud se podíváte na stránky vývojářů, tak pouze MySQL je zatím dostupné ke stažení i v x64 verzi, zatímco PHP a Apache nikoliv.

Řešením je ApacheLounge.com a neoficiální buildy Apache a PHP.

Zatím je testuji pod Windows Vista Ultimate 64b, a vypadají stabilně, nicméně do produkce bych je rozhodně nenasazoval a počkal si na oficiální build.

Odkazy:

Mohu potrvrdit, že uvedená kombinace na Vista x64 funguje, a v průběhu příštího týdne přinesu první testy a srovnání rychlosti XP 32b versus Vista 64b pro tuto platformu.

Jak funguje mkdir / chmod v PHP

Řešil jsem dnes problém na ŠTĚRBA-KOLA.cz, kdy klient mohl nahrát soubory na web pomocí WYSIWYG editoru / PHP, nicméně dále je nemohl smazat přes FTP. Nechápal jsem proč, adresáře se vytvářejí pomocí mkdir($dir,0777), na soubory se nastavuje chmod($fp,0777), a žádná jiná prezentace s tím neměla dosud problém.

Vyřešilo se to až ve spolupráci s webhostingem (tojeono.cz). Problém je, že mkdir 0777 ve skutečnosti nedělá v PHP mkdir 0777. Bere totiž do úvahy i systémovou masku, která je typicky nastavená pro *nixové uživatele na 0022, takže udělá pouhé 0755. A v php.ini ani v httpd.conf se to bohužel nedá změnit. Pokud tuto triviální informaci víte, vše je jasné, já jsem ji do dnešního dne nevěděl. Je tedy nutné před mkdir() použít ještě umask(0000), tedy vynulovat masku, a vše bude fungovat dle očekávání.

Druhou možností je samozřejmě nastavit defaultní umask v *nixovém systému na 0000. Poté i PHP bude fungovat tak, jak očekáváte, a nebude problém v syncu práv mezi PHP a FTP. Je to nakonec nejlepší řešení, protože spousta skriptů umask nepoužívá, a dopisovat to do skriptů je poněkud méně pohodlné. Navíc umask funguje jen pro nejbližší další příkaz, poté se opět resetuje do původního stavu, takže se nedá vložit jen do nějakého config.php.

Bezvadné by bylo, kdyby PHP podporovalo v php.ini cosi jako set_default_umask, pár requestů na to již (pár let) je.

Jak na download PDF souboru v PHP?

Když jsem včera řešil generování PDF z HTML a následný download tohoto souboru, setkal jsem se s řadou problémů. Není to v praxi až tak triviální věc, jak to vypadá. Některé zasílané header() hlavičky nefungují v MSIE, některé v Opeře, a jiné zase ve Firefoxu. Dospěl jsem nakonec k řešení, které funguje, dle testů, pod MSIE 6.0, Firefoxem 1.5 i Operou 9, a nabídne uživateli stažení vytvořeného PDF souboru.

Takto vypadá skutečně funkční download vyvořeného PDF v PHP:

function download_file($pdfdata, filename){
 if (ereg('Opera(/| )([0-9].[0-9]{1,2})', $_SERVER['HTTP_USER_AGENT']))
  $UserBrowser = "Opera";
 elseif (ereg('MSIE ([0-9].[0-9]{1,2})', $_SERVER['HTTP_USER_AGENT']))
  $UserBrowser = "IE";
 else
  $UserBrowser = '';
 $mime_type = ($UserBrowser == 'IE' || $UserBrowser == 'Opera') ? 
  'application/octetstream' : 'application/octet-stream';
 header('Content-Type: ' . $mime_type);
 header('Content-Disposition: attachment; filename="'.$filename.'"');
 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 header('Accept-Ranges: bytes');
 header("Cache-control: private");
 header('Pragma: private');				
 header("Content-Length: ".strlen($pdfdata));
 echo $pdfdata;
}

Otestovat si jej můžete přímo pod tímto článkem kliknutím na [export do PDF].

Základy SQL, část 1.

V této sérii článků budu popisovat poněkud opomíjenou věc - SQL a PL/SQL syntax pro současné databáze, a to konkrétně pro opensource produkt MySQL 5.x (tedy včetně VIEWS, TRIGGERS, atd.). Řada lidí se nějak naučí programovat v PHP, ale kvalitní použití SQL pro ně zůstává záhadou. A to je velice špatně.

Osobně veškeré rozsáhlé aplikace píšu zásadně nad databází (typicky Oracle 8 až 10g). Proč? Řada činností se dá rozdělit na události, a tyto události, pokud je napíšete jako triggery a uložené procedury, není nutné explicitně volat. Ať už přistupujete do databáze z webové aplikace, z Delphi či z .NET aplikace, máte záruku, že daná akce se vždy provede. Navíc je zpracování dat nad databází mnohem efektivnější a rychlejší než tahat všechna data na klienta, tam to aplikačně zpracovat, a poté data posílat zpět k uložení na server.

Ubohá znalost SQL je dána i zcela nechutným rozšířením jedné extrémně špatné databáze, tedy MySQL 3.x a 4.x. Mírně lepší je až MySQL 5.x, nicméně, její nasazení na hostingu není zatím moc reálné, řada českých webhostingů se sotva dopracovala k MySQL 4.1.

Tolik k úvodu, a následuje první, pochopitelně nejsnazší, část tohoto seriálu.

SELECT a LEFT/RIGHT/INNER JOIN

Představte si, že máte nějaké fórum, ve kterém ja tabulka příspěvků (TOPICS) a tabulka práv uživatelů (PERMS). Pokud administrátor nic nenastaví, je tabulka PERMS prázdná a platí defaultní práva systému, pokud chce defaultní práva změnit, zapíše přes administrační rozhraní práva pro jednotlivé skupiny uživatelů do tabulky PERMS.

Známe Vaše heslo!

Mám hrůzu ze služeb a programátorů, kteří ukládají hesla klientů jako plain-text. Tedy v lidsky čitelné podobě. Když jsem včera žádal o obnovu hesla na Cybex.cz, kde jsem se kdysi dávno registroval, přišlo mi toto:

Známe tvoje heslo

Jinak řečeno, jakýkoliv administrátor podobného webu či hacker získající přístup do databáze zná Vaše skutečné heslo. A to není moc příjemné. Místo například sha1 hashe hesla v podobě 59af7f2e fa5fb2d7 c2081a87 04e0a003 43e5ff6e je uloženo přímo heslo samotné, tedy znamevaseheslo. Nyní si představte, že stejný účet a heslo používáte do dalších deseti obchodů. Začínáte se děsit?

Pokud klient zapomene heslo, existuje jediná relativně bezpečná cesta, jak mu sdělit jeho heslo, a to vygenerovat heslo zcela nové, jeho sha1 či md5 hash uložit do databáze, a nové heslo poslat na jeho registrační email.

Nicméně, protože nemůžete nikdy vědět jak „prasácky“ je naprogramovaná ta která webová aplikace, tak nezbývá než pro každou Vaši registraci používat jiné heslo a minimalizovat tím jeho potencionální zneužití.

PHP neobsahuje, obsahuje, neobsahuje SQLite

Když jsem před půl rokem s radostí přivítal, že PHP 5.0 pro Windows obsahovalo standardně v sobě SQLite extension (a také třeba iconv), tak musím konstatovat, že v PHP 5.1 je zase všechno jinak. Chlapci pracující na PHP mají zjevně rádi náhodné změny, a tak se rozhodli, že PHP 5.1 už zase SQLite obsahovat zakompilované nebude, ale je nutné je explicitně načíst jako extension. A aby to nebylo tak úplně jednoduché, je nutné v php.ini načíst nejenom extension=php_sqlite.dll, ale rovnou takto:

extension=php_pdo.dll
extension=php_sqlite.dll

Chlapcům z PHP děkuji, jen více a více neustálých úžasných změn! Tedy, já tuším, že to je kvůli zahrnutí PDO, ale stejně by to chtělo trošku více konzistence a konzervativního postoje. Není důvod, aby non-PDO php_sqlite.dll potřebovala ke své funkci načtené PDO, taková mysql a mysqli extension to samozřejmě nepotřebuje. Je to bordel. Takto na novější verze nepřejde skoro nikdo, protože kompatibilita neexistuje.

Převod dat z MySQL 4.0 do MySQL 4.1

MySQL 4.0 do 4.1

Upravoval jsem dnes prezentaci Města Český Brod (do redakčního systému byla doplněna například automatická expirace článků či zobrazení počtu článků v dané sekci, což jsou velice podstatné funkce pro funkci Úřední deska) a při té příležitosti jsem chtěl i přesunout data z MySQL 4.0 databáze (mysql) do MySQL 4.1 (mysqli).

Požádal jsem tedy webhosting tojeono.cz o vytvoření nové databáze v MySQL 4.1, a zkopírování datových souborů. Problém ovšem byl, že pouhé zkopírování dat nestačí. Tabulky v MySQL 4.0 jsou po převodu do MySQL 4.1 v latin2 kódování (což je hlavní důvod pro úpravu, celý redakční systém jede v UTF-8), a v MySQL 4.1 jsem potřeboval UTF-8. Výsledkem ovšem byla změť znaků, přestože binárně se o unicode jednalo. Nepomohla ani změna typu tabulky z latin2 na binary a poté z binary na UTF-8. PHPMyAdmin zobrazoval pořád nesmyslné znaky.

Jediná cesta, která fungovala, bylo vytvořit novou databázi v MySQL 4.1 s UTF-8 kódováním, exportovat danou tabulku, starou dropnout, vytvořit ji následně znovu, a následně importovat dříve exportovaná data. Za pomoci PHPMyAdmina práce na 10 minut, a web je poté plně fukční, a řadí hezky česky, nicméně, stejně nechápu, proč běžné postupy jako je nastavení COLLATE a CHARACTER SET nefungují, a tabulku je potřeba fyzicky DROPnout a udělat pomocí CREATE novou.

PHP knihovna pro Captcha a kontaktní formulář

Vzhledem k množícímu se spamu, který přichází přes standardní kontaktní formulář, jsem si napsal cca 1.000 řádkovou knihovnu v PHP, která tomuto jednou provždy zamezí. Ono lze kontaktní formulář obecně zneužít k rozesílání spamu, a to velice snadno, aniž o tom autor původního skriptíku tuší.

Základem knihovny je třída CAPTCHA, a následný potomek CONTACT, které implementují veškeré akce nutné pro Captcha, tedy vygenerování Captcha klíče, jeho autorizaci, a následného poslání emailu.

Vlastní třída je velice dobře dokumentovaná přímo ve zdrojovém kódu, který si můžete stáhnout v souboru captcha.zip (36 KB), podívat se na něj online, a zde je vidět v praxi. Pokud tuto knihovnu někde použijete, budu rád, když mi pošlete email.

Vlastní použití třídy CONTACT je velice snadné, nejlépe je ilustruje následující příklad:

<form method="post" action="index.php#captcha" id="captcha">
<?php
 require('captcha.php');
 $email = new CONTACT('your@email.cz');
 $email->DefaultMessage = '[zprava]';
 $email->doFormExtras();
 $email->doMail();
?>
 <fieldset><legend>Kontakt</legend>
  <p><label for='idname'>Jmeno:</label> 
   <input type="text" name="name" id='idname' tabindex="1" size="40" /></p>
  <p><label for='idemail'>E-mail:</label> 
   <input type="text" name="email" id='idemail' tabindex="2" size="40" /></p>
  <p><label for='idtelephone'>Telefon:</label> 
   <input type="text" id='idtelephone' name="telephone" tabindex="3" size="40" /></p>
  <p><textarea name="message" cols="50" rows="10" tabindex="4"><?php if (isset($_POST['message'])) echo htmlspecialchars($_POST['message']); else echo $email->DefaultMessage; ?></textarea></p>
  <p><input type="submit" value="Poslat!" tabindex="5" /></p>
 </fieldset>
</form>

Uvedený skript vyžaduje pro svoji práci PHP verze 5.X, včetně podpory pro SQLite. Skripty pro PHP 4.x již nepíšu.

Vlastní CAPTCHA třídu samozřejmě můžete použít i pro jiné účely než kontaktní formulář, třída CONTACT je jen jedním z mnoha možných příkladů její implementace.

Banan.cz