Проверка серверного сертификата SSL в Delphi

Понадобилось мне осуществить проверку серверного сертификата в моей программе на Delphi. Эта программа была написана довольно давно, на Delphi 7 с использованием Indy 9.

Вот как выглядит проверка сертификата:

var
IdHTTP: TIdHTTP;
IdSSLIOHandlerSocket: TIdSSLIOHandlerSocket;
begin
IdHTTP := TIdHTTP.Create(nil);
IdSSLIOHandlerSocket := TIdSSLIOHandlerSocket.Create(nil);
IdHTTP.IOHandler := IdSSLIOHandlerSocket;
with IdSSLIOHandlerSocket do
begin
SSLOptions.Method := sslvSSLv23;
SSLOptions.Mode := sslmClient;
SSLOptions.VerifyMode := [sslvrfPeer];
SSLOptions.VerifyDepth := 10;
end;
IdHTTP.Get('https://www.google.com');
FreeAndNil(IdHTTP);
FreeAndNil(IdSSLIOHandlerSocket);
end;

Если попробовать выполнить выше приведенный код, то результат будет всегда такой: Error connecting with SSL.

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

Посмотрим в исходник модуля IdSSLOpenSSL. В нем есть такая функция:

function VerifyCallback(Ok: Integer; ctx: PX509_STORE_CTX): Integer; cdecl;
var
hcert: PX509;
Certificate: TIdX509;
hSSL: PSSL;
IdSSLSocket: TIdSSLSocket;
// str: String;
VerifiedOK: Boolean;
Depth: Integer;
// Error: Integer;
begin
LockVerifyCB.Enter;
try
VerifiedOK := True;
try
hcert := IdSslX509StoreCtxGetCurrentCert(ctx);
hSSL := IdSslX509StoreCtxGetAppData(ctx);
Certificate := TIdX509.Create(hcert);
if hSSL <> nil then begin
IdSSLSocket := TIdSSLSocket(IdSslGetAppData(hSSL));
end
else begin
Result := Ok;
exit;
end;
//Error :=
IdSslX509StoreCtxGetError(ctx);
//
Depth := IdSslX509StoreCtxGetErrorDepth(ctx);
// str := Format('Certificate: %s', [Certificate.Subject.OneLine]); {Do not Localize}
// str := IdSSLSocket.GetSessionIDAsString;
// ShowMessage(str);
if (IdSSLSocket.fParent is TIdSSLIOHandlerSocket) then begin
VerifiedOK := TIdSSLIOHandlerSocket(IdSSLSocket.fParent).DoVerifyPeer(Certificate);
end;
if (IdSSLSocket.fParent is TIdServerIOHandlerSSL) then begin
VerifiedOK := TIdServerIOHandlerSSL(IdSSLSocket.fParent).DoVerifyPeer(Certificate);
end;
if not ((Ok>0) and (IdSSLSocket.fSSLContext.VerifyDepth>=Depth)) then begin
Ok := 0;
{if Error = OPENSSL_X509_V_OK then begin
Error := OPENSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG;
end;}
end;
FreeAndNil(Certificate);
except
end;
if VerifiedOK and (Ok > 0) then begin
Result := 1;
end
else begin
Result := 0;
end;
// Result := Ok; // testing
finally
LockVerifyCB.Leave;
end;
end;

Эта callback-функция вызывается при проверке сертификата библиотекой OpenSSL, и для того, чтобы не случилось ошибки “Error connecting with SSL” она должна возвращать значение Result = 1. Видно, что VerifyCallback возвращает Result = 1 только в том случае, когда Ok > 0. А параметр Ok для последнего сертификата в цепочке проверяемых всегда будет = 0, т.к. корневой сертификат, с помощью которого его можно проверить, не определен.

Таким образом, задача в следующем: проверить - подписан ли последний в цепочке сертификат одним из доверенных корневых (ROOT) сертификатов в хранилище Windows, и если подписан, то присвоить Ok = 1.

К сожалению, при проектировании Indy 9 было допущено много ошибок, и для того, чтобы решить поставленную задачу, придется вносить изменения в код модуля IdSSLOpenSSL, а затем перекомпилировать пакет
Indy70.dpk.

Внесем следующие изменения:

1. Изменим определение события TVerifyPeerEvent на следующее:

TVerifyPeerEvent = function(Certificate: TIdX509; Error: Integer; var Ok: Integer): Boolean of object;

2. Изменим определение метода DoVerifyPeer в классах TIdSSLIOHandlerSocket и TIdServerIOHandlerSSL на следующее:

function DoVerifyPeer(Certificate: TIdX509; Error: Integer; var Ok: Integer): Boolean; virtual;

3. Изменим реализации метода DoVerifyPeer в классах TIdSSLIOHandlerSocket и TIdServerIOHandlerSSL на следующие:

function TIdServerIOHandlerSSL.DoVerifyPeer(Certificate: TIdX509;
Error: Integer; var Ok: Integer): Boolean;
begin
Result := True;
if Assigned(fOnVerifyPeer) then
Result := fOnVerifyPeer(Certificate, Error, Ok);
end;
function TIdSSLIOHandlerSocket.DoVerifyPeer(Certificate: TIdX509;
Error: Integer; var Ok: Integer): Boolean;
begin
Result := True;
if Assigned(fOnVerifyPeer) then
Result := fOnVerifyPeer(Certificate, Error, Ok);
end;

4. Добавим public-свойство X509 классу TIdX509:

property X509: PX509 read FX509;

5. Изменим функцию VerifyCallback на следующую:

function VerifyCallback(Ok: Integer; ctx: PX509_STORE_CTX): Integer; cdecl;
var
hcert: PX509;
Certificate: TIdX509;
hSSL: PSSL;
IdSSLSocket: TIdSSLSocket;
// str: String;
VerifiedOK: Boolean;
Depth: Integer;
Error: Integer;
begin
LockVerifyCB.Enter;
try
VerifiedOK := True;
try
hcert := IdSslX509StoreCtxGetCurrentCert(ctx);
hSSL := IdSslX509StoreCtxGetAppData(ctx);
Certificate := TIdX509.Create(hcert);
if hSSL <> nil then begin
IdSSLSocket := TIdSSLSocket(IdSslGetAppData(hSSL));
end
else begin
Result := Ok;
exit;
end;
Error :=
IdSslX509StoreCtxGetError(ctx);
//
Depth := IdSslX509StoreCtxGetErrorDepth(ctx);
// str := Format('Certificate: %s', [Certificate.Subject.OneLine]); {Do not Localize}
// str := IdSSLSocket.GetSessionIDAsString;
// ShowMessage(str);
if (IdSSLSocket.fParent is TIdSSLIOHandlerSocket) then begin
VerifiedOK := TIdSSLIOHandlerSocket(IdSSLSocket.fParent).DoVerifyPeer(Certificate, Error, Ok);
end;
if (IdSSLSocket.fParent is TIdServerIOHandlerSSL) then begin
VerifiedOK := TIdServerIOHandlerSSL(IdSSLSocket.fParent).DoVerifyPeer(Certificate, Error, Ok);
end;
if not ((Ok>0) and (IdSSLSocket.fSSLContext.VerifyDepth>=Depth)) then begin
Ok := 0;
{if Error = OPENSSL_X509_V_OK then begin
Error := OPENSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG;
end;}
end;
FreeAndNil(Certificate);
except
end;
if VerifiedOK and (Ok > 0) then begin
Result := 1;
end
else begin
Result := 0;
end;
// Result := Ok; // testing
finally
LockVerifyCB.Leave;
end;
end;

Теперь нужно перекомпилировать Indy70.dpk.

В результате наших изменений обработчик события OnVerifyPeer получает два дополнительных параметра: Error - код ошибки проверки сертификата (это может быть, например, OPENSSL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) и параметр Ok, который можно изменить. Ниже приведена реализация обработчика:

function TForm1.SSLIOHandlerVerifyPeer(ThePeerCert: TIdX509; Error: Integer; var Ok: Integer): Boolean;
var
LBIO: PBIO;
LLen: Integer;
LStr: String;
hStore: HCERTSTORE;
pCert, pIssuer: PCCERT_CONTEXT;
dwFlags: DWORD;
begin
//Result := False;
//
if (Ok = 0)
then begin
hStore := nil;
pCert := nil;
pIssuer := nil;
LBIO := BIO_new(BIO_s_mem());
try
i2d_X509_bio(LBIO, ThePeerCert.X509);
LLen := BIO_ctrl_pending(LBIO);
SetLength(LStr, LLen);
BIO_read(LBIO, @LStr[1], LLen);
BIO_free(LBIO);
LBIO := nil;
hStore := CertOpenSystemStore(0, 'ROOT');
if hStore <> nil then
begin
pCert := CertCreateCertificateContext(
PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, @LStr[1], LLen);
if pCert <> nil then
begin
dwFlags := CERT_STORE_REVOCATION_FLAG or
CERT_STORE_SIGNATURE_FLAG or
CERT_STORE_TIME_VALIDITY_FLAG;
pIssuer := CertGetIssuerCertificateFromStore(
hStore, pCert, nil, @dwFlags);
if pIssuer <> nil then
begin
Ok := 1;
CertFreeCertificateContext(pIssuer);
pIssuer := nil;
end;
CertFreeCertificateContext(pCert);
pCert := nil;
end;
CertCloseStore(hStore, 0);
hStore := nil;
end;
finally
if LBIO <> nil then
BIO_free(LBIO);
if pIssuer <> nil then
CertFreeCertificateContext(pIssuer);
if pCert <> nil then
CertFreeCertificateContext(pCert);
if hStore <> nil then
CertCloseStore(hStore, 0);
end;
end;
//
Result := (Ok > 0);
end;

В этом обработчике использованы функции OpenSSL и СryptoAPI, объявления которых можно найти в libeay32.pas и wcrypt2.pas. Собственно, из-за необходимости подключения этих модулей я и вынес проверку в обработчик события OnVerifyPeer, вместо того, чтобы сделать все в функции VerifyCallback.

Подключаем обработчик SSLIOHandlerVerifyPeer к IdSSLIOHandlerSocket и окончательно получаем загрузку страницы по протоколу https: с проверкой серверного сертификата:

var
IdHTTP: TIdHTTP;
IdSSLIOHandlerSocket: TIdSSLIOHandlerSocket;
begin
IdHTTP := TIdHTTP.Create(nil);
IdSSLIOHandlerSocket := TIdSSLIOHandlerSocket.Create(nil);
IdHTTP.IOHandler := IdSSLIOHandlerSocket;
with IdSSLIOHandlerSocket do
begin
SSLOptions.Method := sslvSSLv23;
SSLOptions.Mode := sslmClient;
SSLOptions.VerifyMode := [sslvrfPeer];
SSLOptions.VerifyDepth := 10;
OnVerifyPeer := SSLIOHandlerVerifyPeer;
end;
IdHTTP.Get('https://www.google.com');
FreeAndNil(IdHTTP);
FreeAndNil(IdSSLIOHandlerSocket);
end;

PS. Проверка серверного сертификата и Enhanced Security

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Google code highlight

Подключил подсветку кода от гугла (http://google-code-prettify.googlecode.com).

Проверка:

#include <QtCore>
#include <QtGui>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QDialog *dialog = new QDialog;
QLabel *label = new QLabel(dialog);
label->setText("<font color=red>Hello, World!</font>");
dialog->show();
return app.exec();
}

Вроде работает. К Blogger-у подключается просто:
в шаблоне (Дизайн->Изменить HTML) перед добавляем:

<link href="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css" rel="stylesheet" type="text/css"/>
<script src="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js" type="text/javascript"/>

Затем к тегу нужно добавить: onload=”prettyPrint()”

Использовать так:

<pre class="prettyprint">
// put your code here
</pre>

Можно указать явным образом язык, например lang-html:

<pre class="prettyprint lang-html">
The lang-* class specifies the language file extensions.
File extensions supported by default include
"bsh", "c", "cc", "cpp", "cs", "csh", "cyc", "cv", "htm",
"html", "java", "js", "m", "mxml", "perl", "pl", "pm",
"py", "rb", "sh", "xhtml", "xml", "xsl".
</pre>

PS. Гугловский Highlighter похоже Delphi не поддерживает. Поэтому я его удалил, а вместо него подключил highlight.js.
Сделал я это так:

1. Скачал HIGHLIGHT.JS (при этом выбрал нужный мне Delphi).
2. Файлы highlight.js, highlight.pack.js и стили styles/*.css из скачанного архива разместил в google apps.
3. В шаблоне Blogger-а (Дизайн->Изменить HTML) перед добавил:

<link href='http://testapp.coolsoftware.ru/styles/default.css' rel='stylesheet'/>
<script src='http://testapp.coolsoftware.ru/highlight.pack.js'></script>
<script>
hljs.initHighlightingOnLoad();
</script>

Чтобы использовать подсветку синтаксиса, нужно заключать код в

...
. Пример:

type
TMyObject = class(TObject)
private
FStr: String;
public
constructor Create;
end;
{ TMyObject }
constructor TMyObject.Create;
begin
FStr := 'Hello, world!';
end;

Active Server Pages

Пара замечаний про конфигурирование IIS.

1. Если при попытке обратиться к динамической странице (ASP) на вашем сервере IIS выдается ошибка “HTTP Error 404 - File or Directory not found” (при этом статические страницы нормально выдаются), то нужно включить “Active Server Pages” в настройках “Web Service Extensions”.

2. При обращении из ASP к базе Access (MDB): если SELECT выполняется нормально, а при попытке добавить, удалить или отредактировать записи выдаются сообщения “В операции должен использоваться обновляемый запрос.”, “Невозможно удаление записей из указанных таблиц.”, то нужно дать права пользователю, от имени которого выполняется сайт (IUSR или что-то похожее), на файл базы MDB и на каталог, в котором этот файл расположен (права на создание/удаление/изменение).

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Classic ASP и Microsoft Access Databases (MDB) на IIS 7 и 64-битной системе

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

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Android: Hello World

Как создать HelloWorld для андроида написано тут: http://developer.android.com/resources/tutorials/hello-world.html

У меня при старте сразу вывалилась ошибка:

invalid command-line parameter: Files.
Hint: use ‘@foo’ to launch a virtual device named ‘foo’.

please use -help for more information

Причина - эмулятор андроида не любит длинные пути к SDK (у меня: c:\program files (x86)\Android\android-sdk). Заработало, когда заменил путь на короткий: C:\Progra~2\Android\android-sdk.

===

Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Google Plugin for Eclipse

Для последней версии Eclipse (Indigo) нужно устанавливать правильную версию Google Plugin:

http://dl.google.com/eclipse/plugin/3.7

(http://code.google.com/intl/ru/eclipse/docs/download.html)

Если устанавливать неверную версию, то будет выдаваться сообщение типа такого:

Cannot complete the install because one or more required items could not be found.
Software being installed: Google App Engine Java SDK 1.3.4 1.3.4.v201005212032 (com.google.appengine.eclipse.sdkbundle.e35.feature.1.3.4.feature.group 1.3.4.v201005212032)
Missing requirement: Google Plugin for Eclipse 3.5 1.3.2.v201003242055 (com.google.gdt.eclipse.suite.e35.feature.feature.group 1.3.2.v201003242055) requires ‘org.eclipse.platform.feature.group [3.5.0,3.6.0)’ but it could not be found
Cannot satisfy dependency:
From: Google App Engine Java SDK 1.3.4 1.3.4.v201005212032 (com.google.appengine.eclipse.sdkbundle.e35.feature.1.3.4.feature.group 1.3.4.v201005212032)
To: com.google.gdt.eclipse.suite.e35.feature.feature.group 1.3.2

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Google App Engine

Согласно Вики: Google App Engine — сервис хостинга сайтов и web-приложений на серверах Google с бесплатным именем <имя_сайта>.appspot.com, либо с собственным именем, задействованным[1] с помощью служб Google

Отличная, оказывается, штука. Установил плагин Google под эклипс, за пять минут создал и задеплоил простейшее приложение: http://coolsoftware-test.appspot.com/.

Update. Немного шаманства с ДНС и приложение доступно на домене coolsoftware.ru: http://testapp.coolsoftware.ru/

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Пример использования SAX-парсера на Java

В статье про SAX-парсер я рассказал что это такое и пообещал привести примеры не только на Visual Basic, но и на других языках программирования, в частности на Java. Ниже приведен такой пример - класс VXMLChecker, который умеет проверять XML-текст и XML-файл на соответствие XSD-схеме. Для этого у него есть два публичных метода: checkXML и checkFile.

package VXMLParseUtils;
/**

 *
 * @author Vitaly (http://blog.coolsoftware.ru/)
 */

import java.io.*;

import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import org.xml.sax.SAXParseException;
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.XMLReader;

public class VXMLChecker extends DefaultHandler {
    boolean m_bOutLog;
    Locator m_Locator;
    String m_sErrorString;
   
    public VXMLChecker() {
        m_bOutLog = true;
        m_Locator = null;
        m_sErrorString = “”;
    }

    ////////////////////////////////////////////////////////////////////
    // content handlers
    ////////////////////////////////////////////////////////////////////

    public void setDocumentLocator (Locator locator)
    {
    m_Locator = locator;
    }

    public void startDocument ()
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“startDocument”);
    }

    public void endDocument ()
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“endDocument”);
    }

    public void endPrefixMapping (String prefix)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“endPrefixMapping: “+prefix);
    }

    public void startElement (String uri, String localName,
                  String qName, Attributes attributes)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“startElement: “+uri+“,”+localName+“,”+qName);
    }

    public void endElement (String uri, String localName, String qName)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“endElement: “+uri+“,”+localName+“,”+qName);
    }

    public void characters (char ch[], int start, int length)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“characters: “+new String(ch, start, length));
    }

    public void ignorableWhitespace (char ch[], int start, int length)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“ignorableWhitespace: “+new String(ch, start, length));
    }

    public void processingInstruction (String target, String data)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“processingInstruction: “+target+“,”+data);
    }

    public void skippedEntity (String name)
    throws SAXException
    {
    if (m_bOutLog) System.out.println(“skippedEntity: “+name);
    }

    ////////////////////////////////////////////////////////////////////
    // error handlers
    ////////////////////////////////////////////////////////////////////
    public void warning (SAXParseException e)
        throws SAXException
    {
        if (m_bOutLog) System.out.println(“warning: “+e.getMessage());
    }

    public void error (SAXParseException e)
        throws SAXException
    {
        if (m_bOutLog) System.out.println(“error: “+e.getMessage());
        String sAddErrorString = “[XMLChecker2.error] SAXParseException thrown:\n+ e.getMessage();
        m_sErrorString = m_sErrorString +\n+ sAddErrorString;
    }

    public void fatalError (SAXParseException e)
        throws SAXException
    {
        if (m_bOutLog) System.out.println(“fatal error: “+e.getMessage());
        String sAddErrorString = “[XMLChecker2.fatalError] SAXParseException thrown:\n+ e.getMessage();
        m_sErrorString = m_sErrorString +\n+ sAddErrorString;
        throw e;
    }
    ////////////////////////////////////////////////////////////////////
   
    public boolean parseXML(Reader xmlReader, Reader xsdReader)
        throws IOException, SAXException, ParserConfigurationException
    {
        boolean bRes = false;

        m_sErrorString = “”;

        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
        SAXParser parser = saxFactory.newSAXParser();
        XMLReader reader = parser.getXMLReader();

        reader.setContentHandler(this);
        reader.setErrorHandler(this);

        if (xsdReader != null)
        {
            reader.setProperty(
                http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                http://www.w3.org/2001/XMLSchema");
            reader.setProperty(
                http://java.sun.com/xml/jaxp/properties/schemaSource",
                new InputSource(xsdReader));
            reader.setFeature(http://xml.org/sax/features/validation", true);
        }
        try
        {
            reader.parse(new InputSource(xmlReader));
        }
        catch (Exception e)
        {
            if (m_bOutLog) System.out.println(“parse failed: “+e.getMessage());
            if (m_sErrorString.length() <= 0) m_sErrorString = e.getMessage();
        }
        bRes = (m_sErrorString.length() == 0);

        return bRes;
    }

    public static String checkXML(String XmlText, String SchemaFileName)
        throws ClassNotFoundException, IOException, SAXException
    {
        VXMLChecker checker = new VXMLChecker();
        Reader xmlReader = null;
        Reader xsdReader = null;
        try
        {
            xmlReader = new StringReader(XmlText);
            xsdReader = new FileReader(SchemaFileName);
            checker.parseXML(xmlReader, xsdReader);
        }
        catch(Exception e)
        {
            if (xmlReader != null) xmlReader.close();
            if (xsdReader != null) xsdReader.close();
            return e.getMessage();
        }
        if (xmlReader != null) xmlReader.close();
        if (xsdReader != null) xsdReader.close();
        return checker.m_sErrorString;
    }

    public static String checkFile(String XmlFileName, String SchemaFileName)
        throws ClassNotFoundException, IOException, SAXException
    {
        VXMLChecker checker = new VXMLChecker();
        Reader xmlReader = null;
        Reader xsdReader = null;
        try
        {
            xmlReader = new FileReader(XmlFileName);
            xsdReader = new FileReader(SchemaFileName);
            checker.parseXML(xmlReader, xsdReader);
        }
        catch(Exception e)
        {
            if (xmlReader != null) xmlReader.close();
            if (xsdReader != null) xsdReader.close();
            return e.getMessage();
        }
        if (xmlReader != null) xmlReader.close();
        if (xsdReader != null) xsdReader.close();
        return checker.m_sErrorString;
    }
}

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

SAX-парсер

В заметке Schematron и XSD я вскользь упомянул про SAX-парсер и то, что с его помощью можно проверить XML-файл на соответствие XSD-схеме. Здесь я постараюсь описать это подробнее на примере SAX-парсера от Microsoft.

Итак, способ разбора XML-файла с помощью SAX-парсера является событийно-ориентированным. Это означает, что приложение, которое  использует SAX-парсер, получает от него уведомления о начале и об окончании XML-элементов, атрибутах, тексте внутри XML-узлов, ошибках разбора и т.д. - в том порядке, в котором SAX-парсер встретил их в XML-файле. При этом, SAX-парсер не сохраняет в памяти разобранные элементы, в отличие от интерфейса DOM,  для которого строится и хранится в памяти объектная модель всего XML. Это делает SAX-парсер пригодным для потоковой обработки (например, для проверки или для загрузки куда-либо) XML-файлов любого размера.

Посмотрим на пример использования SAX-парсера в Visual Basic 6.0 (примеры на Java, C# обязательно будут в след. статьях).

Для начала в нашем проекте на VB нам необходимо подключить Reference “Microsoft XML, v6.0”.

Затем нам необходимо создать ClassModule - класс объекта, который будет принимать уведомления от SAX-парсера. Назовем его MySAXReader. Он должен реализовывать интерфейсы MSXML2.IVBSAXContentHandler и MSXML2.IVBSAXErrorHandler.

Implements MSXML2.IVBSAXContentHandler
Implements MSXML2.IVBSAXErrorHandler

Private Sub IVBSAXContentHandler_endElement(strNamespaceURI As String, _

strLocalName As String, strQName As String)

    Debug.Print “endElement: “ & strLocalName

End Sub

Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As String, _

strLocalName As String, strQName As String, ByVal oAttributes As MSXML2.IVBSAXAttributes)

    Debug.Print “startElement: “ & strLocalName

End Sub

Private Sub IVBSAXErrorHandler_error(ByVal oLocator As MSXML2.IVBSAXLocator, _
strErrorMessage As String, ByVal nErrorCode As Long)

    Debug.Assert “error: “ & strErrorMessage

End Sub

Методы IVBSAXContentHandler_startElement и IVBSAXContentHandler_endElement будут вызываться SAX-парсером в начале и в конце XML-элемента соответственно. В этих методах, а также в других методах интерфейсов MSXML2.IVBSAXContentHandler и MSXML2.IVBSAXErrorHandler, нужно реализовывать логику приложения. Это может быть, например, загрузка значений в базу данных. Или запись ошибок в лог.

Теперь сделаем у нашего класса публичный метод, который будет осуществлять обработку XML-файла, а заодно и проверку его по XSD-схеме. Вот код:

Public Sub VerifyFile(ByVal sXmlFileName As String, ByVal sSchemaFileName As String)
    Dim oSaxReader As MSXML2.SAXXMLReader60
    Dim oSC As MSXML2.XMLSchemaCache60
    Set oSaxReader = CreateObject(“MSXML2.SAXXMLReader.6.0”) ‘создаем объект SAXXMLReader
    Set oSaxReader.contentHandler = Me ‘Обработчик событий SAX-паресера -
                                                               ‘объект, реализующий интерфейс IVBSAXContentHandler
    Set oSaxReader.errorHandler = Me ‘Обработчик ошибок  -
                                                            ‘объект, реализующий интерфейс IVBSAXErrorHandler
    Set oSC = CreateObject(“MSXML2.XMLSchemaCache.6.0”) ‘Кэш XSD-схем
    oSC.Add “”, sSchemaFileName ‘Добавляем в кэш имя файла нашей XSD-схемы
    oSaxReader.putFeature “schema-validation”, True ‘Включаем проверку по схеме
    oSaxReader.putFeature “exhaustive-errors”, True ‘Включаем проверку всех ошибок
    oSaxReader.putProperty “schemas”, oSC ‘Связываем SAXXMLReader и XMLSchemaCache
    oSaxReader.parseURL sXmlFileName ‘Запускаем SAX-парсер
End Sub

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru