САМОУЧИТЕЛЬ PHP 4

Расшифровка URL-кодированных данных


Если бы в предыдущем примере мы ввели параметры, содержащие, например, буквы кириллицы, то сценарию они бы поступили не в "нормальном" виде, а в URL-закодированном. Пожалуй, ни один сценарий не обходится без функции расшифровки URL-кодированных данных. И это совсем не удивительно. Радует только то, что такую функцию нужно написать один раз, а дальше можно пользоваться ей по мере необходимости.

Как уже упоминалось, кодирование заключается в том, что некоторые неалфавитно-цифровые символы (в том числе и "русские"

буквы, которые тоже считаются неалфавитными) преобразуются в форму %XX, где XX— код символа в шестнадцатеричной системе счисления. Далее представлена функция на Си, которая умеет декодировать подобные данные и приводить их к нормальному представлению.

Мы не можем сначала все данные (например, полученные из стандартного потока ввода) декодировать, а уж потом работать с ними (в частности, разбивать по месту вхождения символов & и =). Действительно, вдруг после перекодировки появятся символы & и =, которые могут быть введены пользователем? Как мы тогда узнаем, разделяют ли они параметры или просто набраны с клавиатуры? Очевидно, никак. Поэтому такой способ нам не подходит, и придется работать с каждым значением отдельно, уже после разделения строки на части.

Итак, приходим к следующему алгоритму: сначала разбиваем строку параметров на блоки (параметр=значение), затем из каждого блока выделяем имя параметра и его значение (обособленные символом =), а уж потом для них вызываем функцию перекодировки, приведенную ниже:

Листинг 3.5. Функция URL-декодирования

// Ôóíêöèÿ ïðåîáðàçóåò ñòðîêó äàííûõ st â íîðìàëüíîå ïðåäñòàâëåíèå.


// Ðåçóëüòàò ïîìåùàåòñÿ â òó æå ñòðîêó, ÷òî áûëà ïåðåäàíà â ïàðàìåòðàõ.

void UrlDecode(char *st) {

  char *p=st;  // óêàçûâàåò íà òåêóùèé ñèìâîë ñòðîêè

  char hex[3]; // âðåìåííûé áóôåð äëÿ õðàíåíèÿ %XX



  int code;    // ïðåîáðàçîâàííûé êîä

  // çàïóñêàåì öèêë, ïîêà íå êîí÷èòñÿ ñòðîêà (òî åñòü, ïîêà íå

  // ïîÿâèòñÿ ñèìâîë ñ êîäîì 0, ñì. íèæå)

  do {

    // Åñëè ýòî %-êîä ...

    if(*st == '%') { // òîãäà êîïèðóåì åãî âî âðåìåííûé áóôåð

      hex[0]=*(++st); hex[1]=*(++st); hex[2]=0;



      // ïåðåâîäèì åãî â ÷èñëî

      sscanf(hex,"%X",&code);

      // è çàïèñûâàåì îáðàòíî â ñòðîêó

      *p++=(char)code;

      // óêàçàòåëü p âñåãäà îòìå÷àåò òî ìåñòî â ñòðîêå, â êîòîðîå

      // áóäåò ïîìåùåí î÷åðåäíîé äåêîäèðîâàííûé ñèìâîë

    }

    // èíà÷å, åñëè ýòî "+", òî çàìåíÿåì åãî íà " "

    else if(*st=='+') *p++=' ';

    // à åñëè íå òî, íè äðóãîå — îñòàâëÿåì êàê åñòü

    else *p++=*st;

  } while(*st++!=0); // ïîêà íå íàéäåì íóëåâîé êîä

}

Функция основана на том свойстве, что длина декодированных данных всегда меньше, чем кодированных, а значит, всегда можно поместить результат в ту же строку, не опасаясь ее переполнения. Конечно, примененный алгоритм далеко не оптимален, т. к. использует довольно медлительную функцию sscanf() для перекодирования каждого символа. Тем не менее, это самое простое, что можно придумать, вот почему я так и сделал.



Итак, теперь мы можем слегка модифицировать предыдущий пример сценария, заставив его перед выводом данных декодировать их. Попробуем написать это так:

Листинг 3.6. Получение POST-данных с URL-декодированием

#include <stdio.h>

#include <stdlib.h>

void main(void) {

// ïîëó÷àåì çíà÷åíèÿ ïåðåìåííûõ îêðóæåíèÿ

  char *RemoteAddr = getenv("REMOTE_ADDR");

  char *ContentLength = getenv("CONTENT_LENGTH");

// âûäåëÿåì ïàìÿòü äëÿ áóôåðà QUERY_STRING

  char *QueryString = malloc(strlen(getenv("QUERY_STRING")) + 1);

// êîïèðóåì QUERY_STRING â ñîçäàííûé áóôåð

  strcpy(QueryString, getenv("QUERY_STRING"));

// äåêîäèðóåì QUERY_STRING

  UrlDecode(QueryString);

// âû÷èñëÿåì êîëè÷åñòâî áàéòîâ äàííûõ — ïåðåâîäèì ñòðîêó â ÷èñëî

  int NumBytes = atoi(ContentLength);

// âûäåëÿåì â ñâîáîäíîé ïàìÿòè áóôåð íóæíîãî ðàçìåðà



  char *Data = (char*)malloc(NumBytes + 1);

// ÷èòàåì äàííûå èç ñòàíäàðòíîãî ïîòîêà ââîäà

  fread(Data, 1, NumBytes, stdin);

// äîáàâëÿåì íóëåâîé êîä â êîíåö ñòðîêè

// (â Ñè íóëåâîé êîä ñèãíàëèçèðóåò î êîíöå ñòðîêè)

  Data[NumBytes] = 0;

// äåêîäèðóåì äàííûå (õîòü ýòî è íå ñîâñåì îñìûñëåííî, íî âûïîëíÿåì

// ñðàçó äëÿ âñåõ POST-äàííûõ, íå ðàçáèâàÿ èõ íà ïàðàìåòðû)

 UrlDecode(Data);

// âûâîäèì çàãîëîâîê

  printf("Content-type: text/html\n\n");

// âûâîäèì äîêóìåíò

  printf("<html><body>");

  printf("<h1>Çäðàâñòâóéòå. Ìû çíàåì î Вàñ âñå!</h1>");



  printf("Âàø IP-àäðåñ: %s<br>",RemoteAddr);

  printf("Êîëè÷åñòâî áàéòîâ äàííûõ: %d<br>", NumBytes);

  printf("Âîò ïàðàìåòðû, êîòîðûå Âû óêàçàëè: %s<br>",Data);

  printf("À âîò òî, ÷òî ìû ïîëó÷èëè ÷åðåç URL: %s",

    QueryString);

  printf("</body></html>");

}

Обратите внимание на строки, выделенные жирным шрифтом. Теперь мы используем промежуточный буфер для хранения QUERY_STRING. Зачем? Попробуем поставить все на место, т. е. не задействовать промежуточный буфер, а работать с переменной окружения напрямую, как это было в листинге 3.5. Тогда в одних операционных системах этот код будет работать прекрасно, а в других — генерировать ошибку общей защиты, что приведет к немедленному завершению работы сценария. В чем же дело? Очень просто: переменная QueryString ссылается на значение переменной окружения QUERY_STRING, которая расположена в системной области памяти, а значит, доступна только для чтения. В то же время функция UrlDecode(), как я уже замечал, помещает результат своей работы в ту же область памяти, где находится ее параметр, что и вызывает ошибку.

Чтобы избавиться от указанного недостатка, мы и копируем значение переменной окружения QUERY_STRING в область памяти, доступной сценарию для записи — например, в какой-нибудь буфер, а потом уже преобразовываем его. Что и было сделано в последнем сценарии.

Несколько однообразно и запутанно, не так ли? Да, пожалуй. Но, как говорится, "это даже хорошо, что пока нам плохо" — тем больше будет причин предпочитать PHP другим языкам программирования (так как в PHP эти проблемы изжиты как класс).


Содержание раздела