Задача: при SAX-разборе перехватить полное содержимое определённых тегов в виде исходного текста, как оно есть, со всеми вложенными подтегами.
Хотя задача эта, по-моему, достаточно простая, банальная и затребованная, но по какой-то не понятной причине не нашёл избытка описания готовых её решений.
Зачем удобно перехватывать полный «сырой» текст XML? С ходу столкнулся с двумя возможными причинами:
1. Как известно и у SAX и у DOM разбора есть свои преимущества и недостатки. Возможен большой XML-документ, который по крайней мере удобно разбирать SAX-парсером, но в нём попадаются отсносительно небольшие участки, которые как раз было бы удобно и естественно с помощью DOM.
2. В определённных XML-тегах может находится XHTML-содержимое, разбирать которое не нужно вообще, его надо просто целиком сохранить, например, отправить на визуализацию в GUI-контрол.
Вероятно хватает и других задач, при которых удобно временно перевести SAX-разбор в режим «перехвата», при попадании на определённый тег.
Итак, как оказалось в Qt это сделать очень просто: достаточно перекрыть в классе QXmlInputSource публичный виртуальный метод next, который «кормит» прасер анализируемым XML посимвольно.
Вариант №1:
Обработчик содержимого (наследник QXmlDefaultHandler):
(В случае если при SAX-разборе попался один из интересующих нас «особых» тегов, мы переходим в режим «перехвата», а при закрытии тега выходим из этого режима, обрабатываем результат).
bool Report::startElement(const QString &namespaceURI, const QString &localName, const QString &name, const QXmlAttributes &attrs) { if (name=="information") // "information" is a special tag name { m_xmlsource->BeginIntercept(); } return RemoteTable::startElement(namespaceURI, localName, name, attrs); } // RemoteTable is a parent class that handle XML content bool Report::endElement(const QString &namespaceURI, const QString &localName, const QString &name) { if (name=="information") { QString info = m_xmlsource->EndIntercept(); if (m_pHeader!=NULL) { // send XHTML content into QLabel const QString was = m_pHeader->text(); m_pHeader->setText( was+info ); } } return RemoteTable::endElement(namespaceURI, localName, name); }
#ifndef EXTXMLINPUTSOURCE_H #define EXTXMLINPUTSOURCE_H #include <QXmlInputSource> #include <QIODevice> class ExtXmlInputSource : public QXmlInputSource { public: ExtXmlInputSource(QIODevice* dev); virtual void BeginIntercept(); virtual QString EndIntercept(); virtual QChar next(); protected: bool m_interception; QString m_content; }; #endif // EXTXMLINPUTSOURCE_H // cpp: ExtXmlInputSource::ExtXmlInputSource(QIODevice* dev) : QXmlInputSource(dev) , m_interception(false) { } void ExtXmlInputSource::BeginIntercept() { m_interception = true; } QChar ExtXmlInputSource::next() { QChar ret = QXmlInputSource::next(); if (m_interception) m_content+=ret; return ret; }Таким образом в накопителе ExtXmlInputSource::m_content получится нужный блок XML-текста плюс закрывающийся тег (если открывающий тег не был вообще «самозакрытым»). Тег для симментрии убираем (если результат направляется в DOM, можно наоборот в начале приписать открывающийся тег):
QString ExtXmlInputSource::EndIntercept() { m_interception = false; const QString res = m_content; m_content.clear(); // if needed: remove the last (closing) tag if (!res.isEmpty()) // non-empty tag? { int pos = res.lastIndexOf("</"); if (pos>=0) return res.left(pos); } return res; }
#ifndef EXTXMLINPUTSOURCE2_H #define EXTXMLINPUTSOURCE2_H #include "extxmlinputsource.h" #include <QXmlDefaultHandler> #include <QXmlSimpleReader> class ExtXmlInputSource2 : public ExtXmlInputSource, public QXmlDefaultHandler { public: ExtXmlInputSource2(QIODevice* dev); void Intercept(const QString& spectag, QXmlSimpleReader* reader, QXmlDefaultHandler* old); virtual bool endElement(const QString& , const QString& , const QString &name); protected: QString m_spectag; QXmlSimpleReader* m_reader; QXmlDefaultHandler* m_old; }; #endif // EXTXMLINPUTSOURCE2_H // cpp: ExtXmlInputSource2::ExtXmlInputSource2(QIODevice* dev) : ExtXmlInputSource(dev) { } void ExtXmlInputSource2::Intercept(const QString& spectag, QXmlSimpleReader* reader, QXmlDefaultHandler* old) { m_spectag = spectag; m_reader = reader; m_old = old; if (m_reader!=NULL) m_reader->setContentHandler(this); BeginIntercept(); } bool ExtXmlInputSource2::endElement(const QString& , const QString& , const QString &name) { if (name==m_spectag) // warning: no nested tags 'spectag' are expected! { if (m_old!=NULL) m_old->characters( EndIntercept() ); if (m_reader!=NULL) m_reader->setContentHandler(m_old); } return true; // QXmlDefaultHandler::endElement(namespaceURI, localName, name); == always true }
bool Report::startElement(const QString &namespaceURI, const QString &localName, const QString &name, const QXmlAttributes &attrs) { if (name=="information") { m_xmlsource->Intercept(name, m_reader, this); } return RemoteTable::startElement(namespaceURI, localName, name, attrs); } // RemoteTable is a parent class that handle XML content bool Report::characters(const QString &str) { if (m_name=="information") // m_name set in RemoteTable::startElement { if (m_pHeader!=NULL) { // send XHTML content into QLabel const QString was = m_pHeader->text(); m_pHeader->setText( was+str ); } } return RemoteTable::characters(str); }
Это второе дополнение к моей предыдущей статье «Шифровка потока информации PHP-HTTP-Qt». Для того что бы разобраться с тем что здесь написано, следует сначала ознакомиться с ней.
Вопрос о возможности их сжатия потока естественно возникает в связи с постоянной передачей больших объёмов разряженных данных (в формате XML) множеству клиентов. Тем более что у конечных пользователей программ-клиантов может быть и не быстрый доступ в Интернет, и сжатие в этом случае может дать существенный выйгрыш в скорости работы.
Я делаю сжатие/разжатие сразу в паре с шифровкой/дешифровкой и так же в связи с шифровкой о нём сейчас рассказываю, тем более что это делается и на стороне сервера и на стороне клиента подобным образом. Но вы, естественно, можете попробовать сделать сжатие/разжатие и без шифровки.
Для сдатия в PHP я использовал всё поточный фильтр zlib.deflate. На стороне клиента я применил класс QtIOCompressor из подзаброшенной ныне коллекции-библиотеки Qt Solutions.
Что бы XML лучше ужался, конечно, надо сжать его до шифровки, а не после.
В моём коде сжатие происходит опционально, в зависимости от значения переменной $compress в PHP и .. в коде клиента C++.
Итак, установка сжимающего фильтра на PHP (естественно, соответствующий фильтр должен быть доступен; проверка: phpinfo() ) выглядит так:if ($compress=="RawZip")
{
$compress_alg = "zlib.deflate";
}
else
{
$compress_alg = false;
}
if ($compress_alg)
{
stream_filter_append($f, $compress_alg, STREAM_FILTER_WRITE);
}
В исходнике в статье про шифрование место, где фильт вставляется у меня помечено комментарием: ...[*]....
В программе клиенте поток-декомпрессор вставляется так:
if (use_compression()) // if use decompressor { // the input stream is compressed m_decompressor = new QtIOCompressor(m_decrypter); // QtIOCompressor *m_decompressor; m_decompressor->setStreamFormat(QtIOCompressor::RawZipFormat); m_decompressor->open(QIODevice::ReadOnly); m_xmlsource = new QXmlInputSource(m_decompressor); } else { // the input stream isn't compressed m_xmlsource = new QXmlInputSource(m_decrypter); }(Соответствующее место в исходнике я помечетил комменарием // [**]).
void WorkplaceTab::onReadyRead() { if ( m_reader==NULL || m_xmlsource==NULL ) { qDebug() << "WorkplaceTab::onReadyRead() - warning"; return; } if (m_first) { // выполняется один раз - при приёме превой порции данных qDebug() << "m_reader->parse(m_xmlsource, true);"; if (!m_reader->parse(m_xmlsource, true)) to_idle_state(); // прекращение режима приёма m_first = false; } long watchdog = 100; while ( m_decrypter!=NULL && (m_decompressor!=NULL? m_decompressor->bytesAvailable(): m_decrypter->bytesAvailable())>0 ) { m_xmlsource->fetchData(); if (!m_reader->parseContinue()) to_idle_state(); // прекращение режима приёма if (watchdog--<0) break; } } void WorkplaceTab::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { logmessage( tr("%1 bytes and %2 rows loaded").arg(bytesReceived).arg(m_row) ); if (bytesReceived==bytesTotal) { // выполняется один раз - после приёма последней порции данных qDebug() << "WorkplaceTab::onDownloadProgress LAST TIME"; if (m_reader==NULL || m_xmlsource==NULL || m_reply==NULL) { qDebug() << "Warning: m_reader==NULL or m_xmlsource==NULL or m_reply==NULL!"; return; } long watchdog=1000000; bool b=true; while ( b && m_xmlsource->data().length()>0 && --watchdog ) { b = m_reader->parseContinue(); } if (b) { m_xmlsource->next(); // и ещё раз - вхолостую - m_reader->parseContinue(); // что бы вызвалось QXmlInputSource::EndOfDocument } } }
В этом посте расскажу о том, как поточно шифровать данные, передаваемые из PHP-скрипта в программу-клиент по HTTP. «Поточно» обозначает, что шифровка-передача-расшифровка происходит параллельно их поступлению (в данном случае из небуферизированного запроса к MySQL).
Как известно, данные передаваемые по HTTP никак не защищены и могут быть легко перехвачены и даже изменены на промежуточных узлах сети. Поэтому, если не использовать HTTPS, весьма уместно ценные данные при передаче зашифравать.
Данные я предаю в формате XML. Программа клиент сделаная на C++ и Qt расшифровав, направляет их в SAX-парсер (QXmlSimpleReader, XML-handler) и далее в GUI-таблицу QTableWidget «на лету».
Шифровка на сервере выполняется исключительно средствами PHP (поточным фильтром). Для расшифровки информации в клиенте я использовал библиотеку CryptoC++.
Я использовал древнюю Qt 4.5.0. Программа клиент разрабатывалась и компилировалась под Linux, а также (кросс-компилятором) под Windows.
Шифр я выбрал Twofish, который сейчас считается весьма стойким. Шифромание и дешифровка Twofish происходит блоками по 16 байт, причём я использовал режим сцеплания блоков шифротекста (CBC).
Я использовал два пароля — один общий для всех пользователей — вбивается в HEX-формате в настройках программы. Там же вбивается общий начальный вектор инициализации блока шифрования. Также у пользователя есть индивидуальный (относительно короткий) пароль, который он вводит каждый раз при старте программы для авторизации.
Для авторизации я посылаю POST-параметром HEX-форму индивидуального пароля пользователя, зашифрованный общим паролем (переведённым в бинарный формат из HEX-представления). На сервере происходит расшифровка его и сопоставление с хранящимся там паролем (или MD5-суммой).
Конечно, ещё стоит добавить до шифровки изменяющуюся не повотояющуюся последовательность, для того что бы нельзя было тупо перехватить и использовать это шифрованное значение. Сделаю это в будущем.
Рабочий ключ для шифровки и расшифровки данных создаю из бинарного представления MD5-суммы индивидуального пароля и из общего пароля беря оттуда и оттуда байты по очереди.
Мой серверный скрипт умеет посылать нешифрованный ответ — это удобно в случае если произошла какая-то ошибка (например, пароль не верный) и для отладки. Для того что бы отличать шифрованный ответ от не шифрованного в первом случае я в начале отправляю «заголовок» - 32 байта (два 16-байтовых блока), первый из которых является нулём. В XML-е вообще не должно быть нулевых байт, так что это — надёжный признак. Не шифрованный XML идёт обычно, без всякого заголовка.
Все эти детали не относятся прямо к теме, но, надеюсь, они помогут разобраться в моём коде, который я привожу в статье. Он уже прошёл "обкадку" и с уверенностью можно сказать, что всё работает надёжно под Linux и Windows (по крайней мере при компиляции с библиотеками, которые у меня)!
Вот важные фрагменты PHP серверной части:
Для того что бы это работало, разумеется нужна поддержка соответствующего фильтра mcrypt.* в PHP. Проверить это можно с помощью функции phpinfo().
date_default_timezone_set("Europe/Moscow");
@ini_set('output_buffering', 0);
header('Content-Transfer-Encoding: binary');
$alg = MCRYPT_TWOFISH; // MCRYPT_TripleDES; // MCRYPT_PANAMA;
$mode = MCRYPT_MODE_CBC; // MCRYPT_MODE_STREAM;
$ivsize = mcrypt_get_iv_size($alg,$mode);
$z="";for($i=0;$i<$ivsize;$i++) $z.="\0"; // нулевой блок
$iv=$z;
// получаем вектор инициализации из табл
$ksql = "SELECT *, MD5(value) as M FROM paramete
$rr0a = $mysqli1->query($ksql);
if ( $rr0a && ($row = $rr0a->fetch_assoc()) )
{
$ivh = $row["M"]; // MD5
// assert length($ivh)=32
$b = pack("H*", $ivh); // OR:
// $b = hex2bin($ivh); // request P
if ($b===false)
{
$message = "110\tCannot decode initialization vecto
}
else
{
$iv = $b;
}
}
…
// проверка авторизации происходит так
$password = (string)pack("H*", $password );
$password = trim( mcrypt_decrypt($alg, $key, $password, $mode, $iv), "\x00..\x1F" );
user_check( $user, $password ); // set second argument to false here to d
$pwd = md5($password);
…
ob_flush();
$f = fopen('php://output', 'w'); // пишем XML-информацию так: fwrite( $f,
// ...[*]...
$encr = ((!$message) || $message<=""); // $message у меня сигнализирует об ошиб
//$encr = false; ← что бы отключить шифр
if ($encr) echo $z.md5($iv,true); // write 16 zero bytes + 16 bytes of iv
…
// формирование рабочего ключа mix $key a
$pwd = pack("H*", $pwd); // convert to binary
$key1="";
for ($i=0; $i<strlen($key) && $i<strlen($pwd); $i++)
{ $key1.=$key[$i].$pwd[$i]; }
if (strlen($key)>strlen($pwd)) $key1.=substr($key,strlen($pwd));
if (strlen($key)<strlen($pwd)) $key1.=substr($pwd,strlen($key));
// note that maximum key lenght may exce
// Twofish::MAX_KEYLENGTH
//$key1=$pwd;
$opts = array('iv'=>$iv, 'key'=>$key1);
if ($encr) stream_filter_append($f, "mcrypt.$alg", STREAM_FILTER_WRITE, $opts); // <== активируем поточный фильтр-шифров
//stream_filter_append($f, 'convert.base
fwrite($f, '<?xml version="1.0" encoding="UTF-8"?>'
…
На стороне клиента шифрование индивидуального пароля для авторизации и формирование запроса происходит так:
QByteArray Dialog_Settings::encrypt(const QString& plain_str) { byte iv[ CryptoPP::Twofish::BLOCKSIZE ]; memset(iv,0,sizeof(iv)); CryptoPP::SecByteBlock keyblock( CryptoPP::Twofish::DEFAULT_KEYLENGTH ); const QByteArray iva = get_iv(); // assert iva.size()==BLOCKSIZE()==16 for (int i=0; i<CryptoPP::Twofish::BLOCKSIZE; i++) iv[i]=iva[i]; const QString k = get_key(); qDebug() << "key=" << k; keyblock.Assign( (const unsigned char*) QByteArray::fromHex(k.toLatin1()).data(), // k.toStdString().c_str(), k.toStdString().length()/2 ); try { const std::string plain = plain_str.toStdString(); std::string cipher; CryptoPP::CBC_Mode< CryptoPP::Twofish >::Encryption e( keyblock, keyblock.size(), iv ); // The StreamTransformationFilter adds padding // as required. ECB and CBC Mode must be padded // to the block size of the cipher. { CryptoPP::StringSource ss( plain, true, new CryptoPP::StreamTransformationFilter( e, new CryptoPP::StringSink( cipher ) ) // StreamTransformationFilter ); // StringSource } qDebug() << plain_str << "encryped. size=" << cipher.length(); return QByteArray( cipher.c_str(), cipher.length() ); } catch( const CryptoPP::Exception& e ) { qDebug() << "Error encryption: " << e.what() << endl; return QByteArray(); } } .......... bool ok = false; QString p = QInputDialog::getText( this, // NULL, // this tr("Password"), get_username().isEmpty()? tr("Enter user passord"): tr("Enter password for %1").arg( QString("<b>")+get_username()+"</b>"), QLineEdit::Password, v, &ok, Qt::Window | Qt::WindowStaysOnTopHint // Qt::WType_TopLevel // obsolete ); if (ok) { m_encrypted = encrypt(p); .......... const QString surl = baseurl()+"/xml.php?user="+user; QUrl url( surl ); QUrl postData; foreach (const RequestItem& wh, hr.reqrec.where()) { postData.addQueryItem("where[]", wh.where()); } foreach (const QString& col, m_pTableWidget->m_hidden[hr.table]) { postData.addQueryItem("skip[]", col); qDebug() << "at table" << hr.table << " skipped " << col; } qDebug() << "sending password..."; qDebug() << m_pSettings->get_enc().toHex(); postData.addQueryItem("password", m_pSettings->get_enc().toHex()); // ← m_pSettings->m_encrypted QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");так как шифрование происходит блоками, нужно что бы входная информация была дополнена до размера кратного длине блока («padding»). Для этого служит класс StreamTransformationFilter, дополнение происходит нулями. Кстати, поточный фильтр mcrypt.TwoFish в PHP тоже для этого использует нулевые байты (и это вроде нигде не написано). Т.к. мы передаём XML, т. е. plain-текст, в котором нет нулевых байт, мы легко можем удалить их с конца (см «де-padding» в конце кода метода Decrypter::readData ниже).
class Decrypter0 : public QIODevice { Q_OBJECT public: // Decrypter0() {} // my methods: virtual void unset() { m_source=NULL; } // { setSource(NULL); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" virtual bool setSource(QIODevice* source, QWidget* topwidget) { m_source=source; return false; } #pragma GCC diagnostic pop // QIODevice virtual; virtual qint64 bytesAvailable () const { return m_source==NULL? 0: m_source->bytesAvailable(); } virtual bool atEnd() const { return m_source==NULL? true: m_source->atEnd(); } virtual qint64 size() const { return m_source==NULL? 0: m_source->size(); } virtual bool open(OpenMode mode) { qDebug() << "open called"; QIODevice::open(mode); if (m_source==NULL) return false; return m_source->open(mode); } virtual void close() { QIODevice::close(); if (m_source!=NULL) m_source->close(); } // ### Qt 5: pos() and seek() should not be virtual, and // ### seek() should call a virtual seekData() function. virtual qint64 pos() const { return m_source==NULL? qint64(0): m_source->pos(); } virtual bool seek(qint64 pos) const { return m_source==NULL? false: m_source->seek(pos); } virtual bool reset() const { return m_source==NULL? false: m_source->reset(); } virtual bool canReadLine() const { return m_source==NULL? false: m_source->canReadLine(); } virtual bool isSequential() const { return true; } virtual bool waitForReadyRead ( int msecs ) { return m_source==NULL? false: m_source->waitForReadyRead(msecs); } protected: virtual void connectNotify ( const char * signal ) { qDebug() << "Don't try to connect the signal to decriptor! " << QLatin1String(signal); exit(109); } virtual qint64 readData(char *data, qint64 maxlen) { return m_source==NULL? 0: m_source->read(data,maxlen); } // read method is not virtual ! virtual qint64 readLineData(char *, qint64) { exit(111); } virtual qint64 writeData(const char *, qint64) { return 0; } // read-only QIODevice* m_source; };А вот сам Decrypter:
#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wtype-limits" #include "cryptopp/cryptlib.h" #include "cryptopp/twofish.h" #include "cryptopp/modes.h" #pragma GCC diagnostic pop #include <QIODevice> class Decrypter : public QIODevice { Q_OBJECT public: Decrypter(QIODevice* source, const Dialog_Settings* settings); inline qint64 BLOCKSIZE() const; virtual qint64 bytesAvailable() const; virtual bool atEnd() const { return m_source==NULL? true: m_source->atEnd(); } bool unencrypted() { return !m_protected; } // признак отсутствия шифрования size_t get_gc() { return m_gc; } // только для отладки protected: virtual qint64 readData(char *data, qint64 maxlen); virtual qint64 writeData(const char *, qint64) { return 0; } //Этот класс read-only QIODevice* m_source; bool m_first; bool m_protected; size_t m_gc;// globar data bytes counter; temporary/ debug - только для отладки CryptoPP::CBC_Mode< CryptoPP::Twofish >::Decryption m_d; // дешифратор };Имплементация:
#include "decrypter.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wtype-limits" #include "cryptopp/filters.h" // StringSink #include "cryptopp/osrng.h" // AutoSeededRandomPool //#include "cryptopp/hex.h" // HexEncoder #include "cryptopp/base64.h" #pragma GCC diagnostic pop #include <string> #include <iostream> #include "dialog_settings.h" #include <QDebug> using namespace CryptoPP; Decrypter::Decrypter(QIODevice* source, const Dialog_Settings* settings) : m_source(source) , m_first(true) , m_protected(false) // Обозначает, что принимаются/приняты шифрованные данные , m_gc(0) { m_first = true; m_protected = false; byte iv[ BLOCKSIZE() ]; memset(iv,0,sizeof(iv)); SecByteBlock keyblock( Twofish::DEFAULT_KEYLENGTH ); if (settings!=NULL) { const QByteArray iva = settings->get_iv(); // initialization vector // assert iva.size()==BLOCKSIZE()==16 for (int i=0; i<BLOCKSIZE(); i++) iv[i]=iva[i]; // формирование рабочего ключа QByteArray kk = QByteArray::fromHex(settings->get_key().toLatin1()); QByteArray kp = QByteArray::fromHex(settings->get_pwd().toLatin1()); QByteArray k; // Keyblock is to compose from common secret key and user password for (int i=0; i<kk.size() || i<kp.size(); i++) { if (i<kk.size()) k.push_back( kk[i] ); if (i<kp.size()) k.push_back( kp[i] ); if (k.size()>=Twofish::MAX_KEYLENGTH) break; } keyblock.Assign( (const unsigned char*) k.data(), // k.toStdString().c_str(), k.size() ); } m_d.SetKeyWithIV( keyblock, keyblock.size(), iv ); // m_d=new CryptoPP::CBC_Mode< CryptoPP::Twofish >::Decryption( // keyblock, keyblock.size(), iv ); } qint64 Decrypter::BLOCKSIZE() const { return Twofish::BLOCKSIZE; } qint64 Decrypter::bytesAvailable() const { if (m_source==NULL) return 0; return m_protected? BLOCKSIZE()*(qint64)( m_source->bytesAvailable() / BLOCKSIZE() ) : m_source->bytesAvailable(); //return m_source->bytesAvailable(); // <-- так не работает } qint64 Decrypter::readData(char *data, qint64 maxlen) { //qDebug() << "Decrypter::m_source=" << m_source; if (m_source==NULL) return 0; //qDebug() << "Decrypter::m_first=" << m_first; if (m_first) { // выполняется только при приёме первого байта // - проверка признака шифровки const QByteArray test( (const QByteArray&) m_source->peek(1) ); if (test.size()==0) return 0; m_protected = (test.size()==1 && test[0]==0); // передача зашифрована? qDebug() << "Data stream:" << (m_protected?"CRYPTED":"OPEN"); if (m_protected) m_source->seek(32); // skip first 32 bytes ("header") m_first = false; } if (!m_protected) // Если передача не шифруется просто перекладываем данные { qint64 r = m_source->read(data,maxlen); m_gc+=r; return r; } // qint64 len = BLOCKSIZE()*(qint64)( m_source->bytesAvailable() / BLOCKSIZE() ); qint64 len = bytesAvailable(); // qDebug() << "LEN=" << len; if (len>maxlen) len = maxlen; qint64 len1 = BLOCKSIZE()*((qint64)len/BLOCKSIZE()); // assign(len1<=maxlen) qint64 s = m_source->read(data, len1); // assign(s==len1) // s - размер прочитанных данных до расшифровки size_t s2 = 0; // s2 - размер данных на выходе try { ArraySink *a = new ArraySink( (byte*)data, (size_t)s ); // объект-посредник для приёма данных в массив data StringSource ss( (const byte*)data, (size_t)s, true, new StreamTransformationFilter( m_d, // это объект CryptoPP::CBC_Mode< CryptoPP::Twofish >::Decryption a, StreamTransformationFilter::ZEROS_PADDING //StreamTransformationFilter::NO_PADDING ) // StreamTransformationFilter ); // Source s2 = a->TotalPutLength(); // получаем размер данных на выходе } catch( CryptoPP::Exception& e ) { std::cerr << "Caught Crypto++ Exception: " << e.what() << std::endl; } for (int j=s-1; j>=s-BLOCKSIZE() && j>=0; j--) // де-padding if (data[j]==0) data[j]=' '; // нулевые байты в конце заменяем пробелами else break; //qDebug() << "s2="<< s2 << "rest0="<< (len-s) << "rest="<< m_source->bytesAvailable(); m_gc+=s2; //qDebug() << "global.counter=" << m_gc; // чисто для проверки return s2; }
...Это продолжение, начало см. Часть 1!
Decrypter-объект m_decrypter логически "вставляется" между входным потоком QNetworkReply и приёмником QxmlInputSource :
connect(m_pManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinish(QNetworkReply*))); … m_reader = new QXmlSimpleReader; // connect QXmlSimpleReader to handlers m_reader->setContentHandler(this); m_reader->setErrorHandler(this); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); m_first = true; m_timestart.start(); // = QTime::currentTime(); m_timestop = QTime(); m_reply = m_pManager->post( request, postData().encodedQuery() ); qDebug() << "Got new m_reply=" << m_reply; //m_reply->setReadBufferSize(16*1024); m_decrypter = new Decrypter(m_reply, m_pSettings); m_xmlsource = new QxmlInputSource(m_decrypter); // [**] connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()) ); connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));К сожалению, мне не удалось добиться того, что бы Decryptor работал в точности также как объект QNetworkReply; возможно причина в том, что он выдаёт информацию только 16-байтовыми порциями и не может в конце правильно оценить размер, но в handler-е SAX-парсера не запускался endDocument();
void WorkplaceTab::onReadyRead() { if ( m_reader==NULL || m_xmlsource==NULL ) { qDebug() << "WorkplaceTab::onReadyRead() - warning"; return; } if (m_first) { // выполняется один раз - при приёме превой порции данных qDebug() << "m_reader->parse(m_xmlsource, true);"; if (!m_reader->parse(m_xmlsource, true)) to_idle_state(); // прекращение режима приёма m_first = false; } long watchdog = 100; while ( m_decrypter!=NULL && m_decrypter->bytesAvailable()>0 ) { m_xmlsource->fetchData(); if (!m_reader->parseContinue()) to_idle_state(); // прекращение режима приёма if (watchdog--<0) break; } } void WorkplaceTab::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { logmessage( tr("%1 bytes and %2 rows loaded").arg(bytesReceived).arg(m_row) ); if (bytesReceived==bytesTotal) { // выполняется один раз - после приёма последней порции данных qDebug() << "WorkplaceTab::onDownloadProgress LAST TIME"; if (m_reader==NULL || m_xmlsource==NULL || m_reply==NULL) { qDebug() << "Warning: m_reader==NULL or m_xmlsource==NULL or m_reply==NULL!"; return; } long watchdog=1000000; bool b=true; while (b && m_xmlsource->data().length()>0 &&--watchdog) b = m_reader->parseContinue(); if (b) { m_xmlsource->next(); // и ещё раз - вхолостую - m_reader->parseContinue(); // что бы вызвалось QXmlInputSource::EndOfDocument } /* if (m_row<=0) // TODO: проверка что QXmlInputSource::EndOfDocument не отработал to_idle_state(); // грубая остановка */ } } // ?? иногда вызывается до завершения разборки XML! :-O void WorkplaceTab::onFinish(QNetworkReply *reply) { qDebug() << "WorkplaceTab::onFinish"; m_timestop.start(); // = QTime::currentTime(); bool anew = false; // отсоединяем обработчики onReadyRead и onDownloadProgress //reply->disconnect(); disconnect(reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()) ); disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64))); ....Может кто-то поможет с этим окончательно разобраться?
В стандартном виджете для организации вкадок в Qt нет возможности сокрытия отдельных вкладок (show/hide). Интернете не нашёл решения – предлагают сложные пути: временно удалять вкладки или переделывать виджет.
Кажется мне удалось найти простое решение, оно годится если вы не используйте «задисейбленные» («серые», «режим только-чтение») вкладки (disabled). Кстати, для сокрытых вкладок это и не имеет смысла.
Идея в том, что бы ставить режим disabled вкладкам, которые надо скрыть, а для маскировки вкладок использовать приём задание стиля с нулевым размером вкладки. Причём при установке режима disabled виджет автоматически переключит активную вкладку.
Нужный стиль устанавливается так:
#include "qtesttabwidget.h" #include <QTabBar> QTestTabWidget::QTestTabWidget() // наследник QTabWidget { setStyleSheet(); } void QTestTabWidget::setStyleSheet() { tabBar()->setStyleSheet( "QTabBar::tab:disabled { width: 0; height: 0; right: 1px; }" // ??? border-style: none; margin-left: 1px; ); }У меня чуть-чуть кривовато выглядит последняя граница последней видимой вкладки, если она не активная и если скрыть самую последнюю. Вероятно тут можно как-то добиться идеального отображения и в этом случае поколдовав с полями, смещениями итд в стиле. Если кто разберётся с этим — пожалуйста, отправьте мне результат!
#include "mainwindow.h" #include <QCheckBox> #include <QVBoxLayout> #include <QHBoxLayout> #include <QLabel> #include <QTabBar> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QWidget* w = new QWidget(this); QVBoxLayout* vl = new QVBoxLayout( w ); QHBoxLayout* hl = new QHBoxLayout; vl->addLayout( hl ); vl->addWidget( m_tabs = new QTestTabWidget ); for (int i=0; i<3; i++) { const QString num = QString::number(i+1); QCheckBox* ch = new QCheckBox( QString("Show tab#")+num ); ch->setChecked(true); hl->addWidget(ch); m_map.insert( ch, m_tabs->addTab(new QLabel( QString("Label ")+num ), QString("tab#")+num ) ); // ^ QMap<QCheckBox*,int> m_map; connect( ch, SIGNAL(stateChanged(int)), this, SLOT(stateChanged(int)) ); } setCentralWidget( w ); } void MainWindow::stateChanged( int state ) { QCheckBox *p = qobject_cast<QCheckBox*>( sender() ); if ( p==NULL||!m_map.contains(p) ) return; m_tabs->setTabEnabled( m_map[p], state!=Qt::Unchecked ); m_tabs->setStyleSheet(); }
QComboBox *ysel = new YearInTabComboBox( years ); // наследник QComboBox tabBar()->setTabButton(num, QTabBar::RightSide, ysel); // установка виджета во вкладку num справа от текстато при сокрытии вкладки этим методом эти виджеты у меня должным образом не скрываются и могут образовать "стопку" уложившись рядом. (С кнопками закрытия, наверное, будет то же самое). Впрочем, на сколько я представляю, во-первых, мало кто ставит виджеты во вкладки, а во-вторых это можно обойти, дополнительно скрывая и показывая такие виджеты на соответствующих вкладках обычным способом (методами show/hide).