Dziś bardzo krótko, bez zbędnych dywagacji, czyli tylko i wyłącznie o tablicy $_SERVER. Dbając o bezpieczeństwo aplikacji webowych zwraca się uwagę na wiele czynników, jakimi są SQL injections, przechwytywanie nieprawidłowych parametrów, uogólniające zapytania przepuszczające maskę % w LIKE zapytaniu do baz, XSS‘y w $_POST, $_GET.
I finalnie… wiele osób zapomina (a jeszcze więcej nie jest tego świadom) o możliwości wstrzyknięcia szkodliwych danych w $_SERVER['HTTP_X_FORWARDED_FOR'];. Konsekwencje są oczywiście katastrofalne.
O ile sama walidacja jest rzeczą wtórną, diabeł tkwi w trzech szczegółach:
X-Forwarded-For).document.cookie i przesłać je sobie na serwer w dowolny sposób, zatem atakowi nie ulegną osoby trzecie.ip2long() i zapis w zupełności nieprzydatnych nam później danych do bazy.Przykład tampingu danych, żeby spreparować niepożądane efekty.
Mamy bardzo prosty, niebezpieczny kod funkcji, która pobiera pierwszy adres na liście adresów oddzielonych przecinkami z $_SERVER['HTTP_X_FORWARDED_FOR'] o ile istnieje, natomiast w przeciwnym wypadku $_SERVER['REMOTE_ADDR']:
< ?php function getUserIp() { if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) return trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']))); return $_SERVER['REMOTE_ADDR']; } $sUserIP = getUserIp(); echo 'Hi "' . $sUserIP . '"!'; // first bug while display. var_dump(ip2long($sUserIP)); // second bug while transforming data. ?>
Pora na przykład manipulacji takich danych.
$_POST, $_GET, $_COOKIE, nagłówki, “w locie żądania” etc.X_FORWARDED_FOR=<script>alert('Test.')</script>Naszym oczom ukazują się co najmniej dwa błędy. Pierwszy to błąd prezentacji danych, który wykorzystuje <script>. O ile nie musimy się tym przejmować, bo naturalnie takie żądania nie są tak łatwo wysyłane, użytkownik nie może paść ofiarą ataku przez kliknięcie w link, który np. ukradnie mu ciasteczka. Dane nagłówkowe nie są w stanie być zmodyfikowane poprzez kliknięcie w link, podobnie jak z danymi $_POST (oczywiście mówimy o przypadkach trywialnych, bez javascript’owych wymuszanych submitów targetowanych do np. ramek).
Znacznie poważniejszym błędem jest konsekwencja wadliwego formatu danych, które nasza funkcja bagatelizuje. Po pierwsze mamy fałszywe dane zwracane przez ip2long(), po drugie kto powiedział, że właśnie z tej funkcji korzystamy, a nie zapisujemy danych plain’em i nie bindujemy pofiltrowanych danych lub instrukcji warunkowych zapytania przez np. sprawdzony ORM.
Rozwiązanie problemu.
Edit: Jak słusznie zauważył Zyx, zapomniałem o tym wspomnieć, że skoro mogą znaleźć się tam dowolne dane przesłane od użytkownika, nie należy tego pola traktować jako wyznacznik, że jest to numer jego IP, jest ono bezużyteczne i powoduje potencjalną lukę. Poza zabezpieczeniami to podstawowy argument, żeby o polu zapomnieć i używać $_SERVER['REMOTE_ADDR'].
Po pierwsze funkcja powinna sprawdzać dane wejściowe chociażby preg_match() lub konwersją do ip2long() i (jeżeli jest taka potrzeba) spowrotem do long2ip(). Dwa, pamiętajmy, że w X_FORWARDED_FOR znajdują się śmieci, adresy lokalne sieci, itd., które należy pominąć przy wyborze adresu z listy po przecinku.
We wpisie Chmura tagów w PHP, w którym został przedstawiony problem budowy chmury tagów zapisałem przykładowe zapytanie prezentujące przykładowe dane dla klasy, które dosłownie zabija bazę danych zliczając za każdym razem ilość występowań tagów. Dostając feedbacki, zauważyłem, że problem ten jest bagatelizowany przez wiele osób. Spróbujmy zbudować bardziej optymalne rozwiązanie zarządzania strukturą danych w taki sposób, aby dane wyciągać bardzo bezboleśnie.
Zbudujmy przykładową strukturę bazy danych tagów, do której będziemy przypinać różne rzeczy – newsy, artykuły, galerie zdjęć, zdjęcia, cokolwiek.
Najprostsza tabela db_tags o polach:
CREATE TABLE db_tags ( tag_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , tag_name VARCHAR(255) NOT NULL , tag_count INT UNSIGNED NOT NULL ) ENGINE = INNODB;
Zastanówmy się, po czym będziemy sortować tagi. Warto założyć klucz na pole tag_count, znacznie przyspieszy późniejsze sortowanie wyników po najpopularniejszych tagach. Jeżeli chcemy sortować po liczbie występowań tagu oraz nazwie (aby chmura była alfabetycznie), warto założyć wspólny klucz na tag_name oraz tag_count. Osobiście sortowanie alfabetyczne zostawiam implementacji klasie tagów dla ksort(), bowiem zapytanie wyciągające tagi jest obarczone limitem, zatem wspólny klucz w bazie danych nie jest mi potrzebny – mniej danych w indeksach.
ALTER TABLE db_tags ADD INDEX (tag_count);
Tworzymy dowolną strukturę danych, która będzie podpinała się do naszych tagów. Pamiętajmy, że do tagów może podpinać się (a przynajmniej powinno, zależy od założeń początkowych projektu) wiele struktur jednocześnie. Wybrałem najbardziej pospolite – newsy w tabeli db_news.
CREATE TABLE db_news ( news_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, news_title TEXT NOT NULL, news_content TEXT NOT NULL ) ENGINE = INNODB;
Pozostało nam stworzyć tabelę wiążącą nasze newsy z tagami (nie tagi z newsami). Tabelę nazwałem db_news_tags. Zawierać ona będzie tylko dwa pola przechowujące identyfikator newsa oraz przypisanego do niego tagu, zachowując typ danych wiążących, czyli INT UNSIGNED. Zakładam wspólny primary key dla obu pól.
CREATE TABLE db_news_tags ( handler_item INT UNSIGNED NOT NULL, handler_node INT UNSIGNED NOT NULL, PRIMARY KEY (handler_item, handler_node) ) ENGINE = INNODB;
Buduję relacyjną bazę danych. Gdy jakiś tag zostanie usunięty, bądź gdy jakiś news zostanie usunięty, automatycznie powinien zniknąć wpis z tabeli db_news_tags, zatem używamy kluczy obcych:
ALTER TABLE db_news_tags ADD FOREIGN KEY (handler_item) REFERENCES db_news (news_id) ON DELETE CASCADE; ALTER TABLE db_news_tags ADD FOREIGN KEY (handler_node) REFERENCES db_tags (tag_id) ON DELETE CASCADE;
Tak zaprojektowaną strukturę danych mogę spokojnie używać do przechowywania danych. Pozostaje kwestia obliczania ilości występowań tagów. Istnieją co najmniej dwie szkoły.
Luźny komentarz techniczny (problems, tips & tricks): Aby ominąć problemy wynikłe z założenia w punkcie pierwszym, równie dobrze możemy napisać procedury, które inkrementują/dekrementują liczbę tagów w zależności od warunków (np. tylko wtedy, kiedy tag jest aktywny i widoczny w serwisie). Nikt nie powiedział, że procedury muszą liczyć wszystko od początku możemy się na takie rozwiązanie zgodzić, rezygnujemy natomiast z synchronizacji licznika podczas zmiany warunków, wówczas podczas każdej zmiany warunków, trzeba przekręcić licznik od początku, zliczając wszystkie rekordy wg. ustalonych warunków ręcznie. Triggera należałoby również umieścić w UPDATE (zmiana stanu tagu, np. z niewidocznego na widoczny, z aktywnego na nieaktywny). I to jest najrozsądniejsze rozwiązanie.
W naszym przypadku ograniczymy się do dwóch triggerów, które będą trzymały rękę na pulsie w momencie przypisania tagu do struktury INSERT oraz zerwaniu przypisania DELETE. Zatem:
CREATE TRIGGER NewsTagsCountInsert AFTER INSERT ON db_news_tags FOR EACH ROW BEGIN UPDATE db_tags SET tag_count = tag_count + 1 WHERE tag_id = NEW.handler_node; END CREATE TRIGGER NewsTagsCountDelete AFTER DELETE ON db_news_tags FOR EACH ROW BEGIN UPDATE db_tags SET tag_count = tag_count - 1 WHERE tag_id = OLD.handler_node; END
Komentarz: bardziej eleganckim w większej strukturze danych byłoby wywołanie procedur inkrementujących i dekrementujących licznik – wówczas wykonywalibyśmy procedury (nie zapytania) w wielu strukturach wiązanych (nie tylko newsy, a video, ankiety, etc). Zmiana implementacji liczenia tagów byłaby wówczas wiele prostsza – zmienialibyśmy tylko procedurę, a nie każdy TRIGGER z osobna, zatem:
CREATE PROCEDURE TagsCountIncrement(IN iTagID INT) BEGIN UPDATE db_tags SET tag_count = tag_count + 1 WHERE tag_id = iTagID; END CREATE PROCEDURE TagsCountDecrement(IN iTagID INT) BEGIN UPDATE db_tags SET tag_count = tag_count - 1 WHERE tag_id = iTagID; END CREATE TRIGGER NewsTagsCountInsert AFTER INSERT ON db_news_tags FOR EACH ROW BEGIN CALL TagsCountIncrement(NEW.handler_node); END CREATE TRIGGER NewsTagsCountDelete AFTER DELETE ON db_news_tags FOR EACH ROW BEGIN CALL TagsCountDecrement(OLD.handler_node); END
Finalnie, z czystym sumieniem:
SELECT tag_name, tag_count FROM db_tags ORDER BY tag_count LIMIT 0, 50
Ostatnimi czasy tworząc projekty coraz częściej zwracam uwagę na usability, poprawiając przy tym grafików. Dziś krótko, bo o tym, jak dostosować nagłówki <meta>, aby jak najlepiej były przeklejane do okienka udostępniania linków na Facebook’u.
Czasem cennymi danymi są te, które widzi Google oraz użytkownik (meta description i bezpośrednio w pasku przeglądarki title), czasem zależy nam na dostosowaniu prezentacji danych agregatorów, w naszym przypadku Facebook’a, aby użytkownik wklejając linka nie musiał się dodatkowo w nic angażować.
if(preg_match('/^facebookexternalhit/', $_SERVER['HTTP_USER_AGENT']))
<link rel="image_src" href=" ... " />Efekt? Sprawdź sam na Nowiny365.pl.
Przeczytaj również o OpenGraph.
Odzwyczajony od blogowania, zaabsorbowany przez niekorzystnego dla planet i blogosfery stanu rzeczy Social Media, postanowiłem wrzucić kolejny wpis, w podzięce za porady uzyskane z blogów kolegów z branży. Ostatnio moim zadaniem było zaprojektowanie struktury tabel bazy danych, która wraz z drzewem z zagłębieniem kategorii, ma przechowywać dane na temat cen za ogłoszenia publikowane w tych kategoriach.
Założenia wstępne wyglądały następująco:
Potrzebne mi było zapytanie, które zwróci mi zwyczajnie listę wszystkich kategorii wraz z cenami, uwzględniając zagłębienia, w jakich dana kategoria się znajduje. Podejścia składowania cen są dwa:
Sposób drugi wydaje się być bardziej rozsądny w przypadku większej ilości zapytań podejmujących dane z bazy. Pierwszy odwrotnie – częściej wpisujemy coś do bazy. Z początku wybrałem pierwszy sposób, przy założeniu, że ceny będą generowane tylko w panelu administracyjnym, zatem zapytanie nie będzie wykonywane często. Niestety, moje myślenie jest bardziej abstrakcyjne, nastawione na elastyczność rozwiązań, w przyszłości cel istnienia bazy może się zmienić.
Tak czy siak, dla obu rozwiązań, trzeba stworzyć procedurę sprawdzającą ceny po rodzicach. Wygląda mniej więcej tak:
CREATE FUNCTION AnnouncementsCategoriesGetPrice(iCategoryId INT(11)) RETURNS FLOAT(6,2) BEGIN DECLARE iResult FLOAT(6,2); DECLARE iPointer INT(11); SET iPointer = iCategoryId; SET iResult = (SELECT category_data_price FROM cms_announcements_categories WHERE category_id = iPointer); WHILE (iResult IS NULL AND iPointer IS NOT NULL) DO SET iResult = (SELECT category_data_price FROM cms_announcements_categories WHERE category_id = iPointer); SET iPointer = (SELECT category_parent FROM cms_announcements_categories WHERE category_id = iPointer); END WHILE; RETURN iResult; END
Stosować ją można dla SELECT’u kategorii lub do pozyskania wartości pola tymczasowego przy zapisie do bazy.
Mam nadzieję, że komuś się przyda.
Nie można kwestionować faktu, że jQuery.animate() jest jednym z najbardziej potężnych narzędzi jQuery. Służy on do animowania atrybutów CSS (czyli zmiany ich wartości w czasie od obecnego stanu A do definiowanego stanu B). Najprostszą implementacją jQuery Animate jest podanie zbioru atrybutów CSS, które mają ulec zmianie oraz czasu, w jakim ta zmiana ma nastąpić. Nie będę się zagłębiał w najprostsze przykłady użycia, są one dostępne w oficjalnej dokumentacji jQuery.
Należy pamiętać, że dzięki jQuery jesteśmy w stanie nie tylko płynnie zmieniać kolory, wielkość czcionki, obramowanie, ale także pozycje elementów, nadając stronie dynamicznego kształtu. Domyślnym sposobem animowania (easing) jest płynne przechodzenie. Istnieje natomiast sposób na zmianę adaptera animowania. Robert Penner – autor pluginu jQuery Easing dostarczył nam niewiarygodnie efektowne i proste w implementacji narzędzie. Na oficjalnej stronie pluginu można znaleźć wiele przykładów animacji, które dostarcza nam dodatek. Efekty widoczne są zwłaszcza przy animowaniu pozycji i wymiarów obiektu, ale następują także w przypadku zmiany koloru – czyli są aplikowane do zmiany stanu każdego z atrybutów CSS.
Dziś postaram się pokazać efekty, jakie można uzyskać za pomocą jQuery Animate rozszerzonego o jQuery Interface oraz Easing.
Pierwszym krokiem jest wygenerowanie własnej biblioteki jQuery Interface. Dzięki generatorowi, jesteśmy w stanie ściągnąć tylko te części Interface, które są nam de facto potrzebne, zmniejszając jednocześnie ilość kodu. Klikamy ‘deselect all components’, a w sekcji Effects wybieramy efekty, których będziemy używać. Mnie w tej chwili interesuje Bounce i Slide. W paczce otrzymamy wersję deweloperską (z wcięciami) oraz minified, gotową do publikacji na serwerze.
Do wykorzystania efektu slideowania a’la iPhone (elastyczne odbicie od krawędzi ściany ekranu) sprowadza się drobny kawałek kodu, w którym istotnym jest parametr easing:
$('#example').animate({ left: 500 }, { duration: 1000, easing: 'easeOutElastic' })
Na pewno komuś się przyda.
Ostatnio byłem zobligowany napisać nową klasę tagów do mojego projektu Sypacz.pl, która de facto zachowała stare API, lecz rozszerzyła swoje funkcjonalności, więc w kodzie projektu nie było wielu zmian. Wena spowodowała to, że zacząłem pisać kod od zera.
Cały problem polega na tym, aby napisać na tyle elastyczną klasę tagów, która przyjmie nam zestaw danych, a następnie zaprezentować ją w formie chmury, czym zaopiekuje się arkusz stylów CSS:
Nazwa tagu => Ilość występowań
Parę osób mnie pytało, jak wyciągnąć takie informacje z bazy danych:
SELECT tag_name, COUNT(tag_name) AS tag_times FROM tags GROUP BY tag_name ORDER BY tag_times LIMIT 1, 50
Uwaga! Zaprezentowane wyżej zapytanie jest przykładowe, nieoptymalne, a jedynie służące do testowania chmur tagów na małych, testowych bazach danych. Optymalna implementacja struktur tagów w bazie danych dla większych projektów została opisana we wpisie MySQL tags.
Wykorzystałem obiekt Vframe_Attribute, aby ustandaryzować komponent względem pozostałych w moim frameworku. Jeżeli ktoś nie chce używać obiektu Attribute, może w prosty sposób przekształcić klasę tagów, otrzymując ten sam efekt, deklarując tylko atrybut chroniony protected $_aAttributes = array();. Temat chmury tagów wydaje mi się na tyle trywialny, że nie ma się co nad nim zbyt wiele rozwodzić, zamieszczę tylko klasę i opiszę krótko w przykładach jej możliwości.
Aby stworzyć nowy obiekt tagów, po prostu wywołujemy konstruktor:
$oCloud = new Vframe_Tagcloud();
Konstruktor nie przyjmuje żadnych argumentów, wiec możemy od razu przejść do podawania obiektowi tagów. W tym miejscu warto nadmienić, że każdy znak jest rozróżniany (ze względów elastycznych), więc jeżeli chcesz, aby Nazwatagu oraz nazwatagu były rozpoznawane jako jeden klucz, wypadałoby użyć funkcji strtolower lub mb_strtolower (dla Multibyte Strings):
foreach($aDataTags as $iKey => $aRow) $oCloud->add(strtolower($aRow['tag_name']), $aRow['tag_times']);
Najistotniejszą częścią klasy jest sposób renderowania chmury, które może działać w dwóch trybach:
level – waga tagu po przeliczeniu,count – ilość występowań, taka jaką podaliśmy,count_percentage – informacja, w jakiej procentowej części ilości występowań znajduje się tag, przyjmując za 100% tag, który występuje najczęściej.Aby w prosty sposób wyrenderować chmurę tagów, używamy poniższego przykładu:
$aDataTagsRender = $oCloud->render();
Najczęściej używa się trybu prostego. Oba tryby są dalej rozbudowane, bowiem mamy możliwość zdefiniowania zakresu i dokładności wag tagów. Domyślnie wagi tagów zawierają się pomiędzy 1, a 10. Możemy na przykład przyjąć, że najmniejszą wagą jest liczba 3, największą 5, a precyzja wag tagów to 2 miejsca po przecinku:
$aDataTagsRender = $oCloud->render(3, 5, 2);
W celu uniknięcia precyzji po przecinku (chcemy otrzymać liczby całkowite), ustawimy precyzję na 0.
Aby wywołać tryb zaawansowany, musimy podać 4 argument dla metody render() i ustawić go na true.
$aDataTagsRender = $oCloud->render(1, 5, 0, true);
Warto w tym miejscu nadmienić, że tagi mogą nie być posortowane alfabetycznie (co ma miejsce podczas tworzenia chmury tagów). Wystarczy wywołać funkcję ksort (key sort).
Finalny przykład używania klasy tagów, celem wywołania klas CSS level_X, gdzie X to liczba całkowita z zakresu od 1 do 10, resztę robi CSS (kolorowanie, nakładanie rozmiaru):
if(count($aDataTags)) { $oCloud = new Vframe_Tagcloud(); foreach($aDataTags as $iKey => $aRow) $oCloud->add(strtolower($aRow['tag_name']), $aRow['tag_times']); $aDataTags = $oCloud->render(1, 10, 0); ksort($aDataTags); foreach($aDataTags as $sTag => $iTag) echo '<a class="level_' . $iTag . '" href="' . $this->route('tag', $sTag) . '">' . $sTag . '</a>'; }