Это дополнение к моей предыдущей статье «Шифровка потока информации PHP-HTTP-Qt». Для того что бы понять о чём тут речь, прочитайте её сначала. Здесь приводится простенькая демонстрационная программа-клиент на Java, которая делает то же самое, что я сделал там на C++/Qt, то есть обеспечивает приём и расшифровку данных по тому же протоколу. Она работает с тем же самым серверным скриптом на PHP. Принятые расшифрованные данные (в моём случае XML) выводятся в стандартный поток вывода. Вот исходный код (URL-скрипта и пароли я естесвенно убрал):
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Array;
import java.io.*;
import java.net.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import gnu.crypto.Registry;
import gnu.crypto.hash.MD5;
import gnu.crypto.jce.GnuCrypto; // GNU-JCE провайдер алгоритма TwoFish
// import iaik.security.provider.IAIK; // IAIK-JCE провайдер алгоритма TwoFish
public class MainClass {
final static String surl = "http://......./xml.php"; // "baseurl()+"/xml.php";
final static String iv0 = "i.vector";
final static String key0 = ".............."; // Hex encoded
final static String password0 = "........."; // personal password in plain text
// prepare cipher
public static Cipher createCipher(int mode, byte[] key, byte[] ivBytes) throws
UnsupportedEncodingException,
NoSuchAlgorithmException,
NoSuchProviderException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException
{
IvParameterSpec iv = new IvParameterSpec(ivBytes);
SecretKey secretKey = new SecretKeySpec( key, "Twofish" );
// get Cipher and init it for encryption
//Cipher cipher = Cipher.getInstance("Twofish/CBC/NoPadding", "IAIK"); // IAIK
Cipher cipher = Cipher.getInstance("Twofish/CBC/NoPadding", Registry.GNU_CRYPTO); // GNU
cipher.init( mode, secretKey, iv );
return cipher;
}
// encrypt string
public static String encrypt(String source, String key, byte[] iv)
throws
UnsupportedEncodingException,
InvalidKeyException,
IllegalBlockSizeException,
BadPaddingException,
NoSuchAlgorithmException,
NoSuchProviderException,
NoSuchPaddingException,
InvalidAlgorithmParameterException
{
Cipher cipher = createCipher( Cipher.ENCRYPT_MODE, key.getBytes("UTF-8"), iv );
// encrypt data
while ( source.length() % cipher.getBlockSize() !=0 ) source+="\0"; // padding
byte[] cipherText = cipher.doFinal( source.getBytes("UTF-8") );
// перевод cipherText в HEX-представление
String encryptedString = "";
for (int i = 0; i < cipherText.length; i++) {
encryptedString += String.format("%02x", cipherText[i]);
} // encryptedText = Base64Coder.encodeLines(encryptedText);
return encryptedString;
}
public static void main(String[] args) {
System.out.println("DecryptorTest HELLO");
// Security.addProvider( new IAIK() ); // using IAIK Security Provider
java.security.Security.addProvider(new GnuCrypto());
// Using Hex in Apache Commons:
// byte[] bytes = Hex.decodeHex(key0.toCharArray());
StringBuilder keyc = new StringBuilder();
for (int i = 0; i < key0.length(); i+=2) {
String str = key0.substring(i, i+2);
keyc.append((char)Byte.parseByte(str, 16));
}
System.out.println("DecryptorTest: Common secret key (plain) =" + keyc);
MD5 md5 = new MD5();
//IMessageDigest md5 = HashFactory.getInstance("MD5");
byte[] iv;
try {
iv = iv0.getBytes( "UTF-8" );
} catch (UnsupportedEncodingException e1) {
System.out.println("DecryptorTest: Error: UnsupportedEncodingException (UTF-8)");
return;
}
System.out.println("DecryptorTest: Common initialization vector length (bytes) =" + iv.length);
md5.update(iv, 0, iv.length);
String ivh = "";
iv = md5.digest();
for (int i = 0; i < iv.length; i++) {
ivh += String.format("%02x", iv[i] );
}
System.out.println("DecryptorTest: Common initialization vector (MD5) =" + ivh);
// encrypt the user password and convert it to Hex
String passwordh;
try {
passwordh = encrypt( password0, keyc.toString(), iv );
} catch ( InvalidKeyException
| UnsupportedEncodingException
| IllegalBlockSizeException
| BadPaddingException
| NoSuchAlgorithmException
| NoSuchProviderException
| NoSuchPaddingException
| InvalidAlgorithmParameterException e)
{
System.out.println("DecryptorTest: Error: cannot encrypt user password! Exception="+e.toString());
return;
}
System.out.println("DecryptorTest: Personal password (encripted, hex) =" + passwordh);
md5.reset();
try {
md5.update(password0.getBytes("UTF-8"), 0, password0.getBytes("UTF-8").length);
} catch (UnsupportedEncodingException e) {
System.out.println("DecryptorTest: Error: cannot prepare password (no UTF-8 encoding)");
return;
}
byte[] pwd5 = md5.digest();
//System.out.println("DecryptorTest: Personal password (MD5, hex) =" + password5);
int keysize;
keysize = 32; // Cipher.getMaxAllowedKeyLength("Twofish/CBC/NoPadding");
System.out.println("DecryptorTest: maximum key size ="+keysize );
URL url;
try {
url = new URL(surl);
} catch (MalformedURLException e) {
System.out.println("DecryptorTest: Error: Malformed URL");
return;
}
HttpURLConnection conn;
InputStream stream;
try {
conn = (HttpURLConnection) url.openConnection();
String post;
post = "user=shestero";
post+= "&password="+passwordh;
conn.setDoOutput(true); // мы будем писать POST данные
conn.setDoInput(true);
OutputStreamWriter out =
new OutputStreamWriter( conn.getOutputStream(), "UTF-8" );
out.write(post);
// out.write("\r\n"); // перевод строки попадает в значения, передаваемые POST-ом
out.close();
stream = conn.getInputStream();
stream.mark(1);
int i0 = 0;//stream.read();
BufferedReader r;
if (i0==0)
{
stream.skip(32); // skip header
// encrypted
System.out.println("DecryptorTest: Note: data comes encrypted!");
byte[] key2 = new byte[32];
Arrays.fill(key2,(byte)0);
int j=0;
for (int i=0; i<keyc.length() || i<pwd5.length; i++)
{
if (i<keyc.length()) key2[j++]=keyc.toString().substring(i,i+1).getBytes()[0];
if (i<pwd5.length) key2[j++]=pwd5[i];
if (j>=keysize)
break;
}
System.out.println("DecryptorTest: Personal key to decript reply =["+key2+"]");
// TODO: Fix key size
if (j<=16) j=16; else if (j<=24) j=24; else if (j<32) j=32;
System.out.println("DecryptorTest: key2.length="+j);
Cipher cipher = createCipher( Cipher.DECRYPT_MODE, key2, iv );
r = new BufferedReader( new InputStreamReader( new CipherInputStream( stream, cipher ) ) );
}
else
{
// plain
stream.reset();
r = new BufferedReader( new InputStreamReader( stream ) );
System.out.println("DecryptorTest: Warning: data comes unencrypted!");
}
// Чтение строка за строкой для проверки
System.out.println("====[REPLY FROM SERVER:]===========================");
String inputLine;
while ((inputLine = r.readLine()) != null)
{
System.out.println(inputLine);
}
r.close();
System.out.println("====[SUCESS]=======================================");
} catch (IOException e) {
System.out.println("DecryptorTest: Error: IOException; URL="+surl);
} catch ( InvalidKeyException
| NoSuchAlgorithmException
| NoSuchProviderException
| NoSuchPaddingException
| InvalidAlgorithmParameterException e)
{
System.out.println("DecryptorTest: Error: cannot decode: Exception="+e.toString());
}
System.out.println("DecryptorTest BYE");
}
}Исходник обработан: Java2html
В этом посте расскажу о том, как поточно шифровать данные, передаваемые из 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)));
....Может кто-то поможет с этим окончательно разобраться?