воскресенье, 14 ноября 2010 г.

Доступ к Exchange почте по протоколу Mapi

Хочу рассказать сегодня как получить список писем, аттачей из Windows Exchange и программно отсылать письма через протокол Simple MAPI.
Для этого нам понадобится небольшой класс. Он слегка недоделан: отправка почты и получение списка аттачей, но это доделать не так сложно.
Описание протокола Simple MAPI можно почитать по адресу: http://msdn.microsoft.com/en-us/library/dd296728(v=VS.85).aspx

Итак, описание класса на C++ (Builder):
#ifndef SimpleMapiH
#define SimpleMapiH

using namespace std;

#include <windows.h>
#include <windowsx.h>
#include <mapi.h>
#include <winbase.h>
#include <vector>

//структура с информацией о письме
struct TMail {
  AnsiString from;
  AnsiString subj;
  unsigned int files;
  TDateTime date;
  //указатели на письмо
  LPSTR lpszMessageID;
  MapiMessage *lpMessage;
};

class SimpleMapi {
   private:
      //библиотека и указатели на нужные нам функции
      HANDLE hMAPILib;
      LPMAPILOGON lpfnMAPILogon;
      LPMAPIFINDNEXT lpfnMAPIFindNext;
      LPMAPILOGOFF lpfnMAPILogoff;
      LPMAPIREADMAIL lpfnMAPIReadMail;
      LPMAPIFREEBUFFER lpfnMAPIFreeBuffer;

      //текущая сессия
      LHANDLE lhSession;

      //буфферы
      char szSeedMessageID[512];
      char szMessageID[512];
      LPSTR lpszSeedMessageID;
      LPSTR lpszMessageID;

      //указатель на письмо
      MapiMessage *lpMessage;

      //тип ошибки
      ULONG err;

      //разбор даты
      TDateTime __fastcall ParseDate(AnsiString date);
      
   public:
        //полный список писем
        vector<tmail> MailList;

        __fastcall SimpleMapi();
        //подключение к Exchenge
        bool __fastcall Connect();
        //перейти к первому письму из списка
        bool __fastcall GetFirst();
        //перейти к следующему письму из списка
        bool __fastcall GetNext();
        //получить указатель на полную информацию о текущем письме
        bool __fastcall GetElement();
        //заполнить структуру текущим письмом
        TMail __fastcall Fetch();
        //получить список всех писем (заполняем вектор)
        bool __fastcall GetList();
        //отключаемся от Exchange
        void __fastcall Disconnect();
        //отправить письмо - не реализовано
        void __fastcall SendMail();
        //получить список приложений письма по указателю
        vector<ansistring> __fastcall GetAttachs(LPSTR lmID, MapiMessage *lpMes);
};

#endif

среда, 29 сентября 2010 г.

Кастомизация визуального редактора Bitrix

Довольно частая вещь как вставка изображения через визуальный редактор с возможностью увеличения в Bitrix никак не решена.

Можно конечно загрузить 2 варианта изображения (большое и маленькое), в текст вставить изображение превью и указать ссылку на большое. Но хотелось бы автоматизировать этот процесс.

Официальная тех. поддержка отказалась решать эту проблему, а документация не содержала списка событий. Хорошо хоть была возможность перегрузить js класс редактора (Документация). Не понятно было какой из сотен методов мне нужен.

Методом перебора был найден один интересный метод SAttr в котором происходила установка атрибутов абсолютно всех html элементов внутри редактора.
Он принимает внутрь 3 параметра: Объект, Свойство, Значение.

Приведу пример, в котором после загрузки изображения в редактор происходит вставка превью, и устанавливается ссылка на оригинал (Добавлять надо в bitrix/php_interface/init.php):
<?
//событие загрузки визуального редактора
AddEventHandler("fileman", "OnIncludeHTMLEditorScript", "OnIncludeHTMLEditorHandler"); 

//наш обработчик
function OnIncludeHTMLEditorHandler()
{
 ?>
 <script>
        //запоминаем ссылку на родителя
        SAttr_= SAttr;
 SAttr = function (e,a,v) { //элемента, свойство, значение
          //выполняем сначала родителя (устанавливаем свойства)
          this.SAttr_(e,a,v);
          //произошла установка title изображения
          //title может быть установлен при изменении или загрузке изображения
          if(a == 'title' && e.tagName == 'IMG') {
            var src = e.src;

            //формируем новое изображение
            var img = '';
            if(e.hspace > 0) img = img + ' hspace='+e.hspace;
            if(e.vspace> 0) img = img + ' vspace='+e.vspace;
            if(e.border> 0) img = img + ' border='+e.border;
            if(e.align != "") img = img + ' align="'+e.align+'"';
            //if(e.alt!= "") img = img + ' alt="'+e.alt+'"';
            if(e.title!= "") img = img + ' title="'+e.title+'"';

            //вставляем наше измененное изображение
            //я использовал плагин zoomie для jquery, без собственно ресайза изображения
            pObj.pMainObj.insertHTML('<img alt="'+src+'" class="zoomi" width='+e.width+' height='+e.height+' src="'+src+'" '+img+'>');
            
            //удаляем неизмененное изображение
            e.parentNode.removeChild(e);
          }  
 }
 </script>
 <?
}
?>

Пример работы

суббота, 28 августа 2010 г.

Моя машинка

Вот наконец купил себе новую машинку: Kia Rio 2010.
Из дополнительного оборудования: спойлер, защита картера. Общая сумма 454 т.р.

пятница, 6 августа 2010 г.

Скрипт автоматического тестирования интерфейса

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

Программа состоит из 2 частей:
  • Запись действий: положение мыши, нажатие на кнопки, время событий
  • Воспроизведение записанных событий.
Управление клавиатурой будет добавлено поздней.

Описание классов:
#ifndef mouseH
#define mouseH

#include <vector>
#include <dos.h>
#include <time.h>
#include <windows.h>

struct step { //структура для хранения событий
        int x, y; //положение курсора
        int sleep; //время до события от предыдущего
};

class TMouseRec { //класс записи событий
        private:
                int second; //время для записи
                int teak;
                time_t time_start; //время старта
                time_t time_cur; //текущее время
                time_t time_bef; //время предыдущего события
        public:
                std::vector stp; //вектор структур событий
        public:
                TMouseRec(int _second);
                void getXY(int &x, int &y); // положение курсора
                void setStep();
                void start(); //точка входа
};

class TMousePlay { //класс воспроизведения, записанных событий
        private:
                std::vector stp;
                int width, height; //ширина, высота экрана
        public:
                TMousePlay(std::vector &_stp);
                void move(int x, int y); //переместить курсор
                void click(int x, int y); //нажать на кнопку
                void start(bool cycle = false);//точка входа
};

#endif

понедельник, 17 мая 2010 г.

Linux: создание демона

Тема написание демонов широко освещена в интернете (Гугл), так что написанное тут не будет ни для кого секретом.
Просто опишу здесь пример реализации демона, выполняющий каждые 10 минут произвольную команду linux (в данном случае - who, список подключенных пользователей) и записывающий результат в лог.
Код на Си(c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <syslog.h>

int Daemon(void);
char* getTime();
int writeLog(char msg[256]);
char* getCommand(char command[128]);

char* getTime() { //функция возвращает форматированную дату и время
    time_t now;
    struct tm *ptr;
    static char tbuf[64];
    bzero(tbuf,64);
    time(&now);
    ptr = localtime(&now);
    strftime(tbuf,64, "%Y-%m-%e %H:%M:%S", ptr);
    return tbuf;
}

char* getCommand(char command[128]) { //функция возвращает результат выполнения linux команды
    FILE *pCom;
    static char comText[256];
    bzero(comText, 256);
    char  buf[64];
    pCom = popen(command, "r"); //выполняем
    if(pCom == NULL) {
        writeLog("Error Command");
        return "";
    }
    strcpy(comText, "");
    while(fgets(buf, 64, pCom) != NULL) { //читаем результат
        strcat(comText, buf);
    }
    pclose(pCom);
    return comText;
}

int writeLog(char msg[256]) { //функция записи строки в лог
    FILE * pLog;
    pLog = fopen("/home/CENTRAL/skan/daemon/daemon.log", "a");
    if(pLog == NULL) {
        return 1;
    }
    char str[312];
    bzero(str, 312);
    strcpy(str, getTime());
    strcat(str, " ==========================\n");
    strcat(str, msg);
    strcat(str, "\n");
    fputs(str, pLog);
    //fwrite(msg, 1, sizeof(msg), pLog);
    fclose(pLog);
    return 0;
}

int main(int argc, char* argv[]) {
    writeLog("Daemon Start");

    pid_t parpid, sid;
    
    parpid = fork(); //создаем дочерний процесс
    if(parpid < 0) {
        exit(1);
    } else if(parpid != 0) {
        exit(0);
    } 
    umask(0);//даем права на работу с фс
    sid = setsid();//генерируем уникальный индекс процесса
    if(sid < 0) {
        exit(1);
    }
    if((chdir("/")) < 0) {//выходим в корень фс
        exit(1);
    }
    close(STDIN_FILENO);//закрываем доступ к стандартным потокам ввода-вывода
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    
    return Daemon();
}

int Daemon(void) { //собственно наш бесконечный цикл демона
    char *log;
    while(1) {
        log = getCommand("who");
        if(strlen(log) > 5) { //если в онлайне кто-то есть, то пишем в лог
          writeLog(log);
        }
        sleep(600);//ждем 10 минут до следующей итерации
    }
    return 0;
}

суббота, 1 мая 2010 г.

Строю дачу

Строю дачу.
Фундамент сделал еще прошлым летом.
Как было в ноябре прошлого года:

среда, 14 апреля 2010 г.

Генерация штрихкода собственными силами

Штрихкоды используются повсеместно и их часто приходится генерировать программно. Но не всегда у нас есть редакторы для этих целей или мы не хотим платить за них.
Оказывается существует шрифт ean13, позволяющий генерировать штрихкоды. Но напрямую его использовать нельзя, необходимо сместись символы по алгоритму, чтобы получить необходимое нам изображение.

Здесь я приведу рабочий код, с минимальным описанием. Функция преобразует уже готовые штрихкоды, с рассчитанными контрольными суммами (Как считать контрольную сумму, можно прочитать тамже). Код на Oracle PL/Sql.

Ean13 и UPC-A
Отличие штрихкода UPC-A от EAN13 в одном первом символе. В UPC-A он всегда = 0
CREATE OR REPLACE FUNCTION FNC_GET_BARCODE_EAN13 ( sFBarCode IN VARCHAR2)
  RETURN  VARCHAR2 IS retBarCode VARCHAR2(15);
  lcnt INTEGER := 3;
  tableA BOOLEAN := FALSE;
  first CHAR(1);
  sBarCode VARCHAR2(15);
BEGIN
   sBarCode := sFBarCode;--внутренний буфер
   IF(length(sBarCode) = 12) THEN
    sBarCode := '0' || sBarCode;
   ELSIF(length(sBarCode) <> 13) THEN
    raise_application_error(-20010, 'Штрих код EAN13 должен содержать 13 символов');
    return '';
   END IF;
   
   retBarCode := SUBSTR(sBarCode, 1, 1);
   first := retBarCode;
   retBarCode := retBarCode || chr(65+SUBSTR(sBarCode, 2, 1));
   
   -- первая половина штрихкода
   FOR lcnt IN 3..7 LOOP
    tableA := FALSE;
    
    if (lcnt=3) and ((first=0) or (first=1) or (first=2) or (first=3)) THEN tableA:=TRUE; END IF;
    if (lcnt=4) and ((first=0) or (first=4) or (first=7) or (first=8)) THEN tableA:=TRUE; END IF;
    if (lcnt=5) and ((first=0) or (first=1) or (first=4) or (first=5) or (first=9)) THEN tableA:=TRUE; END IF;
    if (lcnt=6) and ((first=0) or (first=2) or (first=5) or (first=6) or (first=7)) THEN tableA:=TRUE; END IF;
    if (lcnt=7) and ((first=0) or (first=3) or (first=6) or (first=8) or (first=9)) THEN tableA:=TRUE; END IF;
    
    If tableA = TRUE THEN 
        retBarCode := retBarCode || chr(65+SUBSTR(sBarCode, lcnt, 1));
    ELSE 
        retBarCode := retBarCode || Chr(75+SUBSTR(sBarCode, lcnt, 1));
    END IF;
    
    tableA := FALSE;
  END LOOP;
  
  --разделитель середины штрихкода
  retBarCode := retBarCode || '*';
  
  --вторая половина штрихкода
  lcnt:=8;
  FOR lcnt IN 8..13 LOOP
    retBarCode := retBarCode || Chr(97+SUBSTR(sBarCode, lcnt, 1));
  END LOOP;
  
  --конец
  retBarCode := retBarCode || '+';
   
  RETURN retBarCode;
EXCEPTION
   WHEN OTHERS THEN
      RETURN '';
END;


EAN8
EAN8 имеет более простой механизм реализации, без смещений по таблице.
CREATE OR REPLACE FUNCTION FNC_GET_BARCODE_EAN8 ( sBarCode IN VARCHAR2)
  RETURN  VARCHAR2 IS retBarCode VARCHAR2(11);
  lcnt INTEGER := 3;
BEGIN
  if(length(sBarCode) <> 8) THEN
   raise_application_error(-20010, 'Штрих код EAN8 должен содержать 8 символов');
   return '';
  END IF;
   
  retBarCode := ':';
   
  -- первая половина штрихкода
  FOR lcnt IN 1..4 LOOP
    retBarCode := retBarCode || chr(65+SUBSTR(sBarCode, lcnt, 1));
  END LOOP;
  
  --разделитель середины штрихкода
  retBarCode := retBarCode || '*';
  
  --вторая половина штрихкода
  FOR lcnt IN 5..8 LOOP
    retBarCode := retBarCode || Chr(97+SUBSTR(sBarCode, lcnt, 1));
  END LOOP;
  
  --конец
  retBarCode := retBarCode || '+';
   
  RETURN retBarCode;
EXCEPTION
   WHEN OTHERS THEN
      RETURN '';
END;


Объединение предыдущих двух функций (В зависимости от переданной строки)
CREATE OR REPLACE FUNCTION FNC_GET_BARCODE_EAN ( sBarCode IN VARCHAR2)
  RETURN  VARCHAR2 IS retBarCode VARCHAR2(15);
BEGIN
  --Объединение EAN8 и EAN13
  IF(length(sBarCode) = 8) THEN
    retBarCode := FNC_GET_BARCODE_EAN8(sBarCode);
  ELSE
    retBarCode := FNC_GET_BARCODE_EAN13(sBarCode);
  END IF;
  RETURN retBarCode;
  
EXCEPTION
   WHEN OTHERS THEN
      RETURN '';
END;


Пример использования
select
FNC_GET_BARCODE_EAN('35967101') AS ean8,
FNC_GET_BARCODE_EAN('4607024381199') AS ean13,
FNC_GET_BARCODE_EAN('607024381199') AS upc
 from DUAL;


Полученную строку (после работы функции FNC_GET_BARCODE_EAN) можно выводить шрифотом ean13 и получить работающий штрихкод.

воскресенье, 31 января 2010 г.

Vkontakte: безопасная передача данных от клиента к серверу в iFrame приложениях

Не так давно Vkontakte ввело возможность разработки iFrame приложений. В связи с этим у многих разработчиков встал вопрос как защитить данные, передаваемые между приложением и api.

Я бы хотел предложить свое решение:
Защиту данных можно разделить на следующие аспекты:

1. Подтверждение информации о пользователе.
Чтобы получить достоверные подписанные данные о пользователе, необходимо использовать первый запрос к API в настройках приложения.
method=getProfiles&uids={viewer_id}&format=json&v=2.0
Тем самым мы получаем несколько параметров, один из которых нужный нам - viewer_id, а также сигнатуру данных (auth_key), подписанных секретным ключом, который гарантирует нам верность данных.
Т.е. на следующих этапах мы можем использовать информацию о пользователе, не боясь что данные были подменены.

2. Передача данных от пользователя к приложению
Чтобы скрыть секретный ключ и при этом иметь возможность передать данные на сервер придется использовать промежуточное звено - наш ajax обработчик.
Клиентский JavaScript:
function secureSend(_data, _callback) {
  $.ajax({
   type: "POST",
   url: "ajax.php",
   data: (_data),
   dataType: "json",
   success: function(data) {
     if(data.error != undefined) {
        error(data.error);
        return false;
     } else {
      _callback(data.response);
     }
   }
 });
}
На сервере же расположить скрипт обрабатывающий только обращения к API, которые пересылают данные от пользователя к серверу. Разрешить можно все действия, т.к. пользователя мы проверили на предыдущем этапе и уже тут он волен сколько угодно раз пересылать свои голоса к приложению :)
ajax.php, серверный скрипт
$vk_allow_methods = array("secure.withdrawVotes", "secure.getBalance");
$q = $_POST;
$d = "";
if(in_array($q['method'], $vk_allow_methods)) {
  $d = vkSend($q['method']);
}

function vkSend($method) {
  $server = "http://api.vkontakte.ru/api.php?";
  $key = "000";
  $aid = 1;
  
  $qr = array();
  $qr[] = 'method='.$method;
  $qr[] = 'api_id='.$aid;
  $qr[] = 'v=2.0';
  $qr[] = 'format=JSON';
  $qr[] = 'timestamp='.time();
  $qr[] = 'random='.rand(1, 999999);
  $qr[] = 'test_mode=1';
  sort($qr);
  
  $url4sign = implode("", $qr).$key;
  $sign     = md5($url4sign);
  $url      = implode("&", $qr)."&sig=".$sign;
  
  return file_get_contents($server.$url);
}

echo $d;

3. Передача данных от приложения к пользователю.
Такие события не должны ни в коем случае инициироваться клиентским кодом. Если необходимо перевести голоса со счета приложения на счет пользователя, то лучше воспользоваться запароленым скриптом, вызывающимся по cron или вручную администратором.

суббота, 9 января 2010 г.

Flex, PHP: Безопасная передача данных с клиента на сервер

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

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

a. Генерируем секретный ключ, о котором знает только приложения клиента и сервера
b. К передаваемым данным на сервер добавляем еще один параметр равный хэшу данных с секретным ключом. Пример на Flex:
var key:String = "0000-0000-0000-0000-0000";
var sreq:String = "method=setBalance&addMoney=1000"; //сам запрос
//отсортируем параметры
//чтобы последовательность параметром и на клиенте и на сервере была одинакова. Это гарантирует схождение хэшей.
sreq = sreq.split("&").sort().join("&");
//добавим к параметрам время, чтобы нельзя было продублировать запрос 
var now:Date = new Date();
var sec = now.getTime().toString().substr(0, -3);
//конечный запрос к серверу с дополнительным параметром = хэшу запроса и секретного ключа
sreq = sreq + "&timestamp="+sec+"&sig="+MD5.hash(sreq+key);
c. Сервер принимает запрос и проверяет данные используя свой секретный ключ.
К сожалению нельзя использовать четкое совпадение времени клиента и сервера, т.к. оно может отличаться или сообщение может идти до нескольких секунд. Из-за этого на сервере допускается расхождение timestamp до 100 секунд. Серверный код на PHP:
$_TIME    = $_SERVER['REQUEST_TIME'];//время запроса
$_KEY     = "0000-0000-0000-0000-0000";//тотже ключ, что и на клиенте
$_TIMEOUT = 100; //допустимое отклонение времени
// Проверим валидность данных
//присланная подпись должна сойтись со сгенерированной на основе закрытого ключа 
$q = $_GET;
//отсортируем также как в flex
unset($q['timestamp'], $q['sig']);
ksort($q);
$req = "";
foreach($q as $k=>$v) {
  $req .= $k."=".$v."&";
}
$req = substr($req, 0, -1);

if($_GET['sig'] != md5($req.$_KEY)) {
  //подписи не сошлись. Данные были подменены.
  exit;
}
//Проверим расхождение времени
if($_GET['timestamp'] + $_TIMEOUT < $_TIME) {
  //Расхождение более чем на 100 секунд. Прерываем запрос
  exit;
}

К сожалению такой метод не гарантирует нам, что запрос не будет перехвачен на промежуточном узле и не продублирован N раз в течении этих 100 секунд. Для устранения этого служит следующий пункт:

2. Проверка запроса на дублирование.
Идею я позаимствовал из TCP/IP. Добавим к запросу еще один параметр = счетчику сообщений, а на сервере будем хранить значение последнего счетчика. На клиенте достаточно сгенерировать счетчик = текущее значение + 1, а на сервере принять, проверить что новое значение больше старого и обновить серверный счетчик. Это гарантирует нам, что если злоумышленник перехватит запрос и пошлет его заново, то сервер его отвергнет, т.к. значение будет меньше хранящегося на сервере.
Для реализации этого механизма на сервере нам уже понадобится БД, а на клиенте дополнительный запрос на получение счетчика.

Под катом можно увидеть полный код клиента и сервера:

вторник, 5 января 2010 г.

Flex: Построение древовидного объекта по XML данным

Начал изучать Flex. Хочу поделиться некоторыми наработками, которые могут пригодиться.

Класс для создания древовидного объекта по XML строке.
Допустим у нас есть XML данные, полученные с удаленного хоста или еще как-нибудь. Xml может иметь смешанный тип, т.е. есть и обязательные теги (типа response), так и повторяющиеся на разных уровнях (section, id и т.д.)
Xml:

В программе удобней работать с объектом, чем с текстовыми данными, для этого служит следующий класс:
package Lib { 
  public class XMLTree {
  private var _xml:XML = null;
  private var _data:Object = new Object();
  private var _maxElem:int = 500;//ограничение на число элементов
     
  //конструктор, передается текстовый xml
  public function XMLTree(_xml:String) {
   this._xml = new XML(_xml); //создание объекта
   this.xmlParser(this._xml, this._data);//парсинг
  }
  
  public function getData():Object {
   return this._data;
  }
  
  //рекурсивный обход дерева
  private function xmlParser(node, obj:Object):void {
   var sObjName = "";
   //по каждому ответвлению
   for (var i = 0; i < node.length() && i < this._maxElem; i++) {
     sObjName = node[i].name();
     //если это ветка (имеет дочерей)
     if(node[i].hasComplexContent()) {
       //т.к. названия веток могут повторяться. Если свойство уже существует, то создается свойство с именем = имя + _ + уникальный идентификатор
       if(obj[sObjName] != undefined) {
         sObjName = sObjName + "_" + i;
       }
       //передаем ветку и текущее положение в деревянном объекте на следующий цикл рекурсии
       obj[sObjName] = new Object;
       this.xmlParser(node[i].children(), obj[sObjName]);
     } else {
       //создаем свойство с именем ветки XML объекта и текстовым значением
       obj[sObjName] = node[i].text();
     } //if
    }//for
   }//func
 }//class
}//package
Следующий код создаем объект и перечисляет значения первой ветки section дерева:
var xmlObj:XMLTree = new XMLTree(sXml);
var data:Object = xmlObj.getData();
for(var o in data.response.section) {
  Alert.show(data.response.section[o]);
}