Flash + Unicode/UTF-8 + PHP = fail?
Ostatnio musiałem się zmierzyć z pewnym algorytmem, który wymieniał dane z aplikacją kliencką pisaną we Flashu pomiędzy PHP. Niby nic skomplikowanego, przesyłanie znaków z kodami ASCII uzyskiwanych flashowym odpowiednikiem metody urlencode(). Jednak pomimo prawidłowej realizacji algorytmu, nadal uzyskiwałem dziwne wyniki.
Jakież było moje zdziwienie, gdy odkryłem, co było grane.
Wpis ten dedykuje ku pamięci, bo delikatna poprawka okazała się nie lada wyzwaniem, które chodziło po głowie przez kilka dni. Dopiero szczegółowa analiza poszczególnych etapów wysyłania danych dała jednoznaczną i – można powiedzieć – zaskakującą odpowiedź, co tak naprawdę było nie tak.
A okazało się, że winne jest samo działanie interpretera. Nie od dzisiaj wiadomo, że obsługa UTF-8/Unicode w PHP niestety kuleje, ale czasem człowiek po prostu o tym zapomina.
Do rzeczy
Zacznijmy od tego, w jaki sposób transmitowane jest żądanie typu POST (GET właściwie też; nie do końca, ale… ;)). Dane przesyłane są w formacie klucz=wartosc, oddzielane przez znak &. Znaki, które nie mieszczą się w standardowym zestawie znaków ASCII oraz znaki specjalne, są kodowane poprzez znak procentu oraz heksadecymalnego kodu znaku. W przypadku kodów przekraczających zakres wartości mieszczących się w ramach jednego bajtu, jest on kodowany dwubajtowo, co umożliwia zakodowanie jednego z 65536 znaków.
I zgodnie z dokumentacją, Flash zwraca liczbę właśnie z zakresu 0-65536, która jest kodowana do postaci %XX%YY (dla jednego znaku). W tym właśnie miejscu zaczynają się tzw. „cyrki”, jeśli chodzi o PHP…
Dlaczego? Przy przetwarzaniu żądania, PHP propaguje tablicę superglobalną $_POST właśnie przy pomocy metody wykorzystywanej w funkcji urldecode. Sęk w tym, że jest ona pozbawiona obsługi wielobajtowego przetwarzania znaków. I tak zamiast np. ciągu 4-znakowego wyprodukowanego przez Flasha, otrzymujemy znak 8-znakowy, co powoduje zupełnie odwrotne do zamierzonych skutki.
Najlepiej jest przesyłać takie dane zakodowane przez algorytm base64, ale jeśli już stoimy przed problemem, w którym nie mamy możliwości zmiany sposobu transportu ciągu znaków…?
Skoro PHP nie potrafi rozkodować tych danych, jak być powinno, to musimy zrealizować to samodzielnie.
Problemem pozostaje kwestia dobrania się do danych wejściowych, które pochodzą z żądania. O ile w starszych wersjach PHP była tablica superglobalna $HTTP_RAW_POST_DATA, to już od jakiegoś czasu nie jest ona najczęściej dostępna. Głównie ze względu na pamięciożerność (jak podaje dokumentacja).
Jednak nie jesteśmy kompletnie na lodzie i damy radę. Mianowicie – PHP udostępnia kilka wrapperów do systemu plików, które – można powiedzieć – są odpowiednikiem pseudosystemu plików /proc w systemach uniksowych. Zaczynają się one od prefiksu php://. Szczegóły można odnaleźć w dokumentacji.
Interesuje nas konkretnie php://input. Zawiera on surowe dane POST przesłane przez przeglądarkę. Niestety, posiada on jedną wadę – strumień pozostanie pusty, jeśli formularz posiada atrybut enctype ustawiony z cechami wieloczęściowości, co jest wykorzystywane przy przesyłaniu plików. Wówczas trzeba skorzystać z php://stdin i zrealizować całą obsługę strumienia samodzielnie.
Jednak w większości przypadków wystarczy przetwarzanie danych tekstowych, więc wystarczy poradzić sobie mniej-więcej tym kodem:
<?PHP $data = file_get_contents('php://input'); $data = utf8_decode(urldecode($data)); parse_str($data, $_POST); ?>
Wystarczy użyć tego kodu gdzieś na początku skryptu i wszystko działa jak należy. Banalne, nie?
nieźle…przyznam ze nie slyszalem o 'php://’ – wreszcie jakis artytkuł w sieci gdzie mogę dowiedzieć się czegoś nowego…
do czego jeszcze można dobrac się za pomocą wrapperów, jak obsłużyć 'php://stdin’…może pokusisz sie o kolejne artykuły.