EpicWEB.pl

webdesign, programowanie, phat lewt!

Ostatni projekt

ddrpl.com

Ostatnie wiadomości

include() i require() a open_basedir

Ostatnio spędziłem parę godzin szukając, dlaczego nie mogę includwać plików w swoich serwisach - okazało się, że winny jest open_basedir, który ustawiony jest dla konkretnej ścieżki, i include() (oraz inne funkcja otwierające pliki) tam właśnie zaczynają szukać plików, które mają wczytać - i nie przeszukują podkatalogów.

Popularne, i wszędzie sugerowane rozwiązanie:

<?
#a.php
include('podkatalog/b.php');

#podkatalog/b.php
include('plik.php');

Gdzie plik.php znajduje się w tym samym katalogu co a.php mimo, że wydaje się logiczne i w większości wypadków działa, może okazać się, że dzięki open_basedir przestało.

Rozwiązania?

a) wyłączyć dyrektywę open_basedir w php.ini (i masie podrzędnych, jeżeli masz dużo hostów virtualnych)
b) podawać ścieżki względne lub bezwzględne do dołączanych plików, np. tak:

<?
include('../plik.php');

# albo lepiej

include(dirname(__FILE__)  . '/../plik.php');

Wycinanie znaków regionalnych oraz specjalnych ze zmiennej

Czas temu napisałem skrypt, który konwertował tytuły postów na linki SEO-friendly, czyli wywalał pliterki i zamieniał na bez-ogonkowe odpowiedniki, wywalał znaki specjalne etc. Niestety, poległ na czymś wklejonym z Worda. Nowa wersja jest nieco krótsza i wygląda tak:

It’s How Everyone Builds It zażółć gęślą jaźń !@#$%^&*()_+=-[]{}\\|;:\'<>?,./
<?
$string = iconv('UTF-8', 'UTF-8//IGNORE', $string);
$string = iconv('UTF-8', 'ASCII//TRANSLIT', $string);
$string = str_replace(' ', '-', trim(preg_replace('/([^a-zA-Z\s])/Us','', $string)));
echo $string;

W wyniku da nam ładne

Its-How-Everyone-Builds-It-zazolc-gesla-jazn-zazolc

TRIGGERy w MySQL na przykładzie systemu ankiet

Celem kolejnego ze zleceń jakie otrzymaliśmy jest napisanie rozbudowanego systemu ankiet z możliwością powrotu i zmiany odpowiedzi a co za tym idzie śledzeniem zmian.

Zanim talen wspomniał coś przy innej okazji nt. TRIGGERów w MySQL zapewne zrobił bym to używając kilku SELECT połączonych z UPDATE i INSERT. Jednak dzięki mechanizmowi TRIGGERów możemy to spłycić do jednego zapytania. Jak? Za chwilkę, najpierw o mechanizmie.

TRIGGERy to coś na kształt EVENTów - odpalają się w bazie kiedy zajdzie opisana przez programistę podczas ich tworzenia sytuacja - np, jakiś wiersz zostanie zmodyfikowany, usunięty lub dodany - pozawala nam to na jednokrotne zaprogramowanie takiego zdarzenia w MySQL i nie zawracanie sobie już tym głowy w warstwie aplikacji.

Przejdźmy do zadania - tworzymy sobie prostą tabelę:

CREATE TABLE IF NOT EXISTS `answers` (
        `question_id` int(11) NOT NULL,
        `user_id` int(11) NOT NULL,
        `answer_id` int(11) NOT NULL,
        UNIQUE KEY `question_id` (`question_id`,`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Jak widać, założyłem klucz UNIQUE na pola question_id i user_id - zapobiega to dodaniu wielu odpowiedzi na jedno pytanie przez tego samego użytkownika. Następnym krokiem jest stworzenie tabeli, która przechowywać będzie archiwalne odpowiedzi - kod jest prawie identyczny:

 CREATE TABLE IF NOT EXISTS `answers_old` (
        `question_id` int(11) NOT NULL,
        `user_id` int(11) NOT NULL,
        `answer_id` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Tabela nie posiada żadnych indexów, ponieważ każda odpowiedź mogła być zmieniona więcej niż raz, więc będziemy mieli więcej niż jedno archiwum.

Następnym krokiem jest stworzenie samego TRIGGERa:

DELIMITER //
        CREATE TRIGGER `test`.`archiwizuj` BEFORE UPDATE ON `test`.`answers`
                FOR EACH ROW BEGIN
                        INSERT INTO answers_old SET question_id = OLD.question_id, answer_id = OLD.answer_id, user_id = OLD.user_id;
                END
        //
DELIMITER ;

Przed utworzeniem samego TRIGERRA zmieniamy znak końca funkcji na coś innego niż standardowy średnik, by średniki w treści zapytania nie wykrzaczyły nam operacji. Jeżeli robicie to przez phpMyAdmin i po wydaniu polecenia zwrócony zostanie błąd, a macie pewność, że wasza baza obsługuje TRIGERRy sprawdźcie czy mimo wszystko nie został on pomyślnie utworzony - u mnie tak właśnie było, a ja jak głupi szukałem błędu. Cóż.

Teraz demonstracja:

 INSERT INTO `answers` SET `question_id` = 1, `user_id` = 1, `answer_id` = 1 ON DUPLICATE KEY UPDATE `answer_id` = 1;

Proste zapytanie, ustawia odpowiedź “1″ dla użytkownika “1″ i pytania “1″ - jeżeli istnieje już wpis użytkownika dla tego pytania, zaktualizuj odpowiedź na 1.

Po tym wywołaniu mamy jedynki w tabeli. Następne podejście:

INSERT INTO `answers` SET `question_id` = 1, `user_id` = 1, `answer_id` = 2 ON DUPLICATE KEY UPDATE `answer_id` = 2;

Użytkownik aktualizuje odpowiedź - pole `answer_id` zmieniło się na 2. Jak dotąd - nic nowego. Jednak jeżeli zajrzymy do tabeli `answers_old` zobaczymy tam wpis, który widzieliśmy poprzednio w `answers`. Jeszcze jedno zapytanie:

INSERT INTO `answers` SET `question_id` = 1, `user_id` = 1, `answer_id` = 3 ON DUPLICATE KEY UPDATE `answer_id` = 3;

W tabeli `answers` mamy odpowiedź numer 3, zaś w `answers_old` są już dwa wpisy z odpowiedziami 1 i 2.

Jak widać całość działa świetnie i zostało nam już tylko napisanie mechanizmu obsługującego informowanie administratora o zmianie odpowiedzi i wyświetlanie historii. Oczywiście ten przykład odnosi się nie tylko do ankiet, ale do wszelkich rodzaju danych, których stare wersje chcemy zachowywać w bazie danych - np. na potrzeby archiwizowania starej wersji strony w celu jej późniejszego odtworzenia etc etc.

Fałszowanie mime-type przy wgrywaniu plików na serwer

Większość z programistów zdaje sobie sprawę z tego, jakim zagrożeniem może być błędnie napisany skrypt pozwalający na upload plików na serwer. Dlatego, większość ogranicza możliwość wgrywania plików przez odpowiednie filtrowanie na podstawie mime-type np. w taki sposób:

<?php
if($_FILES["type"] == "image/jpeg") {
// logika wgrywania dla plików JPEG
}
else {
echo "Nie można wgrać pliku takiego typu - tylko JPEG!";
}

Wszystko ładnie - wiemy, że wgrywany plik, jest de-facto plikiem jpeg.

Na pewno?

Otóż nie - dane, które znajdujemy w tablicy _FILES (name, size, type) są danymi, które podała nam przeglądarka podczas wysyłania pliku! Nie daje to nam w żaden sposób pewności, że złośliwy użytkownik nie zmienił swojej przeglądarki / nie użył innego programu który wysłał nam plik.php przy okazji dodając, że jest to format image/jpeg !

Jeżeli nie zabezpieczymy się przed tego typu zagraniami otwieramy serwer na wszelkiego rodzaju ataki. Zostają dwa rozwiązania, które najlepiej stosować jednocześnie

  1. sprawdź rozszerzenie pliku - rozszerzenie wyciągamy w następujący sposób:
    <?
    $ext = substr($file, strrpos($file,".") + 1);
    
  2. upewnij się, że plik rzeczywiście jest typu image/jpeg - np. przy użyciu funkcji getimagesize która jako wynik zwraca tablicę asocjacyjną - pod kluczem mime znajduje się typ pliku odczytany już na naszym serwerze