Secure files upload / download support.

This commit is contained in:
John Preston 2018-03-25 15:37:57 +04:00
parent f633ead3ab
commit b2014f403e
15 changed files with 986 additions and 176 deletions

View File

@ -288,6 +288,7 @@ enum LocationType {
DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation
AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation
VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation
SecureFileLocation = 0xcbc7ee28, // mtpc_inputSecureFileLocation
};
enum FileStatus {

View File

@ -4339,6 +4339,45 @@ void HistoryWidget::uploadFile(
Auth().api().sendFile(fileContent, type, options);
}
void HistoryWidget::subscribeToUploader() {
if (_uploaderSubscriptions) {
return;
}
using namespace Storage;
Auth().uploader().photoReady(
) | rpl::start_with_next([=](const UploadedPhoto &data) {
photoUploaded(data.fullId, data.silent, data.file);
}, _uploaderSubscriptions);
Auth().uploader().photoProgress(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
photoProgress(fullId);
}, _uploaderSubscriptions);
Auth().uploader().photoFailed(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
photoFailed(fullId);
}, _uploaderSubscriptions);
Auth().uploader().documentReady(
) | rpl::start_with_next([=](const UploadedDocument &data) {
documentUploaded(data.fullId, data.silent, data.file);
}, _uploaderSubscriptions);
Auth().uploader().thumbDocumentReady(
) | rpl::start_with_next([=](const UploadedThumbDocument &data) {
thumbDocumentUploaded(
data.fullId,
data.silent,
data.file,
data.thumb);
}, _uploaderSubscriptions);
Auth().uploader().documentProgress(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
documentProgress(fullId);
}, _uploaderSubscriptions);
Auth().uploader().documentFailed(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
documentFailed(fullId);
}, _uploaderSubscriptions);
}
void HistoryWidget::sendFileConfirmed(
const std::shared_ptr<FileLoadResult> &file) {
const auto channelId = peerToChannel(file->to.peer);
@ -4358,13 +4397,7 @@ void HistoryWidget::sendFileConfirmed(
it->msgId = newId;
}
connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(documentReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(thumbDocumentReady(const FullMsgId&,bool,const MTPInputFile&,const MTPInputFile&)), this, SLOT(onThumbDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&, const MTPInputFile&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(photoProgress(const FullMsgId&)), this, SLOT(onPhotoProgress(const FullMsgId&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(documentProgress(const FullMsgId&)), this, SLOT(onDocumentProgress(const FullMsgId&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(photoFailed(const FullMsgId&)), this, SLOT(onPhotoFailed(const FullMsgId&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(documentFailed(const FullMsgId&)), this, SLOT(onDocumentFailed(const FullMsgId&)), Qt::UniqueConnection);
subscribeToUploader();
Auth().uploader().upload(newId, file);
@ -4493,6 +4526,8 @@ void HistoryWidget::sendFileConfirmed(
MTP_string(messagePostAuthor),
MTP_long(groupId)),
NewMessageUnread);
} else {
Unexpected("Type in sendFilesConfirmed.");
}
Auth().data().sendHistoryChangeNotifications();
@ -4502,21 +4537,21 @@ void HistoryWidget::sendFileConfirmed(
App::main()->dialogsToUp();
}
void HistoryWidget::onPhotoUploaded(
void HistoryWidget::photoUploaded(
const FullMsgId &newId,
bool silent,
const MTPInputFile &file) {
Auth().api().sendUploadedPhoto(newId, file, silent);
}
void HistoryWidget::onDocumentUploaded(
void HistoryWidget::documentUploaded(
const FullMsgId &newId,
bool silent,
const MTPInputFile &file) {
Auth().api().sendUploadedDocument(newId, file, base::none, silent);
}
void HistoryWidget::onThumbDocumentUploaded(
void HistoryWidget::thumbDocumentUploaded(
const FullMsgId &newId,
bool silent,
const MTPInputFile &file,
@ -4524,7 +4559,7 @@ void HistoryWidget::onThumbDocumentUploaded(
Auth().api().sendUploadedDocument(newId, file, thumb, silent);
}
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
void HistoryWidget::photoProgress(const FullMsgId &newId) {
if (const auto item = App::histItemById(newId)) {
const auto photo = item->media()
? item->media()->photo()
@ -4534,7 +4569,7 @@ void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
}
}
void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
void HistoryWidget::documentProgress(const FullMsgId &newId) {
if (const auto item = App::histItemById(newId)) {
const auto media = item->media();
const auto document = media ? media->document() : nullptr;
@ -4552,7 +4587,7 @@ void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
}
}
void HistoryWidget::onPhotoFailed(const FullMsgId &newId) {
void HistoryWidget::photoFailed(const FullMsgId &newId) {
if (const auto item = App::histItemById(newId)) {
updateSendAction(
item->history(),
@ -4562,7 +4597,7 @@ void HistoryWidget::onPhotoFailed(const FullMsgId &newId) {
}
}
void HistoryWidget::onDocumentFailed(const FullMsgId &newId) {
void HistoryWidget::documentFailed(const FullMsgId &newId) {
if (const auto item = App::histItemById(newId)) {
const auto media = item->media();
const auto document = media ? media->document() : nullptr;

View File

@ -68,6 +68,9 @@ class TabbedSelector;
namespace Storage {
enum class MimeDataState;
struct PreparedList;
struct UploadedPhoto;
struct UploadedDocument;
struct UploadedThumbDocument;
} // namespace Storage
namespace HistoryView {
@ -378,16 +381,6 @@ public slots:
void onPinnedHide();
void onFieldBarCancel();
void onPhotoUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onThumbDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
void onPhotoProgress(const FullMsgId &msgId);
void onDocumentProgress(const FullMsgId &msgId);
void onPhotoFailed(const FullMsgId &msgId);
void onDocumentFailed(const FullMsgId &msgId);
void onReportSpamClicked();
void onReportSpamHide();
void onReportSpamClear();
@ -519,6 +512,26 @@ private:
MsgId replyTo,
std::shared_ptr<SendingAlbum> album = nullptr);
void subscribeToUploader();
void photoUploaded(
const FullMsgId &msgId,
bool silent,
const MTPInputFile &file);
void photoProgress(const FullMsgId &msgId);
void photoFailed(const FullMsgId &msgId);
void documentUploaded(
const FullMsgId &msgId,
bool silent,
const MTPInputFile &file);
void thumbDocumentUploaded(
const FullMsgId &msgId,
bool silent,
const MTPInputFile &file,
const MTPInputFile &thumb);
void documentProgress(const FullMsgId &msgId);
void documentFailed(const FullMsgId &msgId);
void itemRemoved(not_null<const HistoryItem*> item);
// Updates position of controls around the message field,
@ -816,6 +829,8 @@ private:
int _recordingSamples = 0;
int _recordCancelWidth;
rpl::lifetime _uploaderSubscriptions;
// This can animate for a very long time (like in music playing),
// so it should be a BasicAnimation, not an Animation.
BasicAnimation _a_recording;

View File

@ -706,8 +706,8 @@ void Messenger::killDownloadSessions() {
}
}
void Messenger::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file) {
if (!AuthSession::Exists()) return;
void Messenger::photoUpdated(const FullMsgId &msgId, const MTPInputFile &file) {
Expects(AuthSession::Exists());
auto i = photoUpdates.find(msgId);
if (i != photoUpdates.end()) {
@ -770,6 +770,7 @@ void Messenger::authSessionCreate(UserId userId) {
}
void Messenger::authSessionDestroy() {
_uploaderSubscription = rpl::lifetime();
_authSession.reset();
_private->storedAuthSession.reset();
_private->authSessionUserId = 0;
@ -961,7 +962,12 @@ void Messenger::uploadProfilePhoto(QImage &&tosend, const PeerId &peerId) {
SendMediaReady ready(SendMediaType::Photo, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, 0);
connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&, bool, const MTPInputFile&)), this, SLOT(photoUpdated(const FullMsgId&, bool, const MTPInputFile&)), Qt::UniqueConnection);
if (!_uploaderSubscription) {
_uploaderSubscription = Auth().uploader().photoReady(
) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) {
photoUpdated(data.fullId, data.file);
});
}
FullMsgId newId(peerToChannel(peerId), clientMsgId());
regPhotoUpdate(peerId, newId);

View File

@ -199,8 +199,6 @@ signals:
public slots:
void onAllKeysDestroyed();
void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onSwitchDebugMode();
void onSwitchWorkMode();
void onSwitchTestMode();
@ -216,6 +214,7 @@ private:
static void QuitAttempt();
void quitDelayed();
void photoUpdated(const FullMsgId &msgId, const MTPInputFile &file);
void loggedOut();
not_null<Core::Launcher*> _launcher;
@ -244,6 +243,9 @@ private:
base::Observable<void> _passcodedChanged;
QPointer<BoxContent> _badProxyDisableBox;
// While profile photo uploading is not moved to apiwrap.
rpl::lifetime _uploaderSubscription;
std::unique_ptr<Media::Audio::Instance> _audio;
QImage _logo;
QImage _logoNoMargin;

View File

@ -9,20 +9,136 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_form_controller.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/buttons.h"
#include "ui/text_options.h"
#include "lang/lang_keys.h"
#include "core/file_utilities.h"
#include "styles/style_widgets.h"
#include "styles/style_boxes.h"
#include "styles/style_passport.h"
namespace Passport {
class ScanButton : public Ui::RippleButton {
public:
ScanButton(
QWidget *parent,
const QString &title,
const QString &description);
void setImage(const QImage &image);
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
int countAvailableWidth() const;
int countAvailableWidth(int newWidth) const;
Text _title;
Text _description;
int _titleHeight = 0;
int _descriptionHeight = 0;
QImage _image;
object_ptr<Ui::IconButton> _delete = { nullptr };
};
ScanButton::ScanButton(
QWidget *parent,
const QString &title,
const QString &description)
: RippleButton(parent, st::passportRowRipple)
, _title(
st::semiboldTextStyle,
title,
Ui::NameTextOptions(),
st::boxWideWidth / 2)
, _description(
st::defaultTextStyle,
description,
Ui::NameTextOptions(),
st::boxWideWidth / 2)
, _delete(this, st::passportRowCheckbox) {
}
void ScanButton::setImage(const QImage &image) {
_image = image;
update();
}
int ScanButton::resizeGetHeight(int newWidth) {
const auto availableWidth = countAvailableWidth(newWidth);
_titleHeight = _title.countHeight(availableWidth);
_descriptionHeight = _description.countHeight(availableWidth);
const auto result = st::passportRowPadding.top()
+ _titleHeight
+ st::passportRowSkip
+ _descriptionHeight
+ st::passportRowPadding.bottom();
const auto right = st::passportRowPadding.right();
_delete->moveToRight(
right,
(result - _delete->height()) / 2,
newWidth);
return result;
}
int ScanButton::countAvailableWidth(int newWidth) const {
return newWidth
- st::passportRowPadding.left()
- st::passportRowPadding.right()
- _delete->width();
}
int ScanButton::countAvailableWidth() const {
return countAvailableWidth(width());
}
void ScanButton::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto ms = getms();
paintRipple(p, 0, 0, ms);
auto left = st::passportRowPadding.left();
auto availableWidth = countAvailableWidth();
auto top = st::passportRowPadding.top();
const auto size = height() - top - st::passportRowPadding.bottom();
if (_image.isNull()) {
p.fillRect(left, top, size, size, Qt::black);
} else {
PainterHighQualityEnabler hq(p);
if (_image.width() > _image.height()) {
auto newheight = size * _image.height() / _image.width();
p.drawImage(QRect(left, top + (size - newheight) / 2, size, newheight), _image);
} else {
auto newwidth = size * _image.width() / _image.height();
p.drawImage(QRect(left + (size - newwidth) / 2, top, newwidth, size), _image);
}
}
left += size + st::passportRowPadding.left();
availableWidth -= size + st::passportRowPadding.left();
_title.drawLeft(p, left, top, availableWidth, width());
top += _titleHeight + st::passportRowSkip;
_description.drawLeft(p, left, top, availableWidth, width());
top += _descriptionHeight + st::passportRowPadding.bottom();
}
IdentityBox::IdentityBox(
QWidget*,
not_null<FormController*> controller,
int fieldIndex,
const IdentityData &data)
const IdentityData &data,
std::vector<ScanInfo> &&files)
: _controller(controller)
, _fieldIndex(fieldIndex)
, _files(std::move(files))
, _uploadScan(this, "Upload scans") // #TODO langs
, _name(
this,
st::defaultInputField,
@ -38,21 +154,48 @@ IdentityBox::IdentityBox(
void IdentityBox::prepare() {
setTitle(langFactory(lng_passport_identity_title));
auto index = 0;
auto height = st::contactPadding.top();
for (const auto &scan : _files) {
_scans.push_back(object_ptr<ScanButton>(this, QString("Scan %1").arg(++index), scan.date));
_scans.back()->setImage(scan.thumb);
_scans.back()->resizeToWidth(st::boxWideWidth);
height += _scans.back()->height();
}
height += st::contactPadding.top()
+ _uploadScan->height()
+ st::contactSkip
+ _name->height()
+ st::contactSkip
+ _surname->height()
+ st::contactPadding.bottom()
+ st::boxPadding.bottom();
addButton(langFactory(lng_settings_save), [=] {
save();
});
addButton(langFactory(lng_cancel), [=] {
closeBox();
});
_controller->scanUpdated(
) | rpl::start_with_next([=](ScanInfo &&info) {
updateScan(std::move(info));
}, lifetime());
setDimensions(
st::boxWideWidth,
(st::contactPadding.top()
+ _name->height()
+ st::contactSkip
+ _surname->height()
+ st::contactPadding.bottom()
+ st::boxPadding.bottom()));
_uploadScan->addClickHandler([=] {
chooseScan();
});
setDimensions(st::boxWideWidth, height);
}
void IdentityBox::updateScan(ScanInfo &&info) {
const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) {
return file.key;
});
if (i != _files.end()) {
*i = info;
_scans[i - _files.begin()]->setImage(i->thumb);
}
}
void IdentityBox::setInnerFocus() {
@ -67,12 +210,51 @@ void IdentityBox::resizeEvent(QResizeEvent *e) {
- st::boxPadding.right()),
_name->height());
_surname->resize(_name->width(), _surname->height());
_name->moveToLeft(
st::boxPadding.left(),
st::contactPadding.top());
_surname->moveToLeft(
st::boxPadding.left(),
_name->y() + _name->height() + st::contactSkip);
auto top = st::contactPadding.top();
for (const auto &scan : _scans) {
scan->moveToLeft(0, top);
top += scan->height();
}
top += st::contactPadding.top();
_uploadScan->moveToLeft(st::boxPadding.left(), top);
top += _uploadScan->height() + st::contactSkip;
_name->moveToLeft(st::boxPadding.left(), top);
top += _name->height() + st::contactSkip;
_surname->moveToLeft(st::boxPadding.left(), top);
}
void IdentityBox::chooseScan() {
const auto filter = FileDialog::AllFilesFilter()
+ qsl(";;Image files (*")
+ cImgExtensions().join(qsl(" *"))
+ qsl(")");
const auto callback = [=](FileDialog::OpenResult &&result) {
if (result.paths.size() == 1) {
encryptScan(result.paths.front());
} else if (!result.remoteContent.isEmpty()) {
encryptScanContent(std::move(result.remoteContent));
}
};
FileDialog::GetOpenPath(
"Choose scan image",
filter,
base::lambda_guarded(this, callback));
}
void IdentityBox::encryptScan(const QString &path) {
encryptScanContent([&] {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
return QByteArray();
}
return f.readAll();
}());
}
void IdentityBox::encryptScanContent(QByteArray &&content) {
_uploadScan->hide();
_controller->uploadScan(_fieldIndex, std::move(content));
}
void IdentityBox::save() {

View File

@ -10,12 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
namespace Ui {
class LinkButton;
class InputField;
} // namespace Ui
namespace Passport {
class FormController;
struct ScanInfo;
class ScanButton;
struct IdentityData {
QString name;
@ -28,7 +31,8 @@ public:
QWidget*,
not_null<FormController*> controller,
int fieldIndex,
const IdentityData &data);
const IdentityData &data,
std::vector<ScanInfo> &&files);
protected:
void prepare() override;
@ -37,11 +41,19 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
void chooseScan();
void encryptScan(const QString &path);
void encryptScanContent(QByteArray &&content);
void updateScan(ScanInfo &&info);
void save();
not_null<FormController*> _controller;
int _fieldIndex = -1;
std::vector<ScanInfo> _files;
std::vector<object_ptr<ScanButton>> _scans;
object_ptr<Ui::LinkButton> _uploadScan;
object_ptr<Ui::InputField> _name;
object_ptr<Ui::InputField> _surname;

View File

@ -22,74 +22,6 @@ constexpr auto kMinPadding = 32;
constexpr auto kMaxPadding = 255;
constexpr auto kAlignTo = 16;
base::byte_vector SerializeData(const std::map<QString, QString> &data) {
auto root = QJsonObject();
for (const auto &[key, value] : data) {
root.insert(key, value);
}
auto document = QJsonDocument(root);
const auto result = document.toJson(QJsonDocument::Compact);
const auto bytes = gsl::as_bytes(gsl::make_span(result));
return { bytes.begin(), bytes.end() };
}
std::map<QString, QString> DeserializeData(base::const_byte_span bytes) {
const auto serialized = QByteArray::fromRawData(
reinterpret_cast<const char*>(bytes.data()),
bytes.size());
auto error = QJsonParseError();
auto document = QJsonDocument::fromJson(serialized, &error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: Could not deserialize decrypted JSON, error %1"
).arg(error.errorString()));
return {};
} else if (!document.isObject()) {
LOG(("API Error: decrypted JSON root is not an object."));
return {};
}
auto object = document.object();
auto result = std::map<QString, QString>();
for (auto i = object.constBegin(), e = object.constEnd(); i != e; ++i) {
const auto key = i.key();
switch (i->type()) {
case QJsonValue::Null: {
LOG(("API Error: null found inside decrypted JSON root. "
"Defaulting to empty string value."));
result[key] = QString();
} break;
case QJsonValue::Undefined: {
LOG(("API Error: undefined found inside decrypted JSON root. "
"Defaulting to empty string value."));
result[key] = QString();
} break;
case QJsonValue::Bool: {
LOG(("API Error: bool found inside decrypted JSON root. "
"Aborting."));
return {};
} break;
case QJsonValue::Double: {
LOG(("API Error: double found inside decrypted JSON root. "
"Converting to string."));
result[key] = QString::number(i->toDouble());
} break;
case QJsonValue::String: {
result[key] = i->toString();
} break;
case QJsonValue::Array: {
LOG(("API Error: array found inside decrypted JSON root. "
"Aborting."));
return {};
} break;
case QJsonValue::Object: {
LOG(("API Error: object found inside decrypted JSON root. "
"Aborting."));
return {};
} break;
}
}
return result;
}
} // namespace
struct AesParams {
@ -162,9 +94,8 @@ base::byte_vector PasswordHashForSecret(
}
bool CheckBytesMod255(base::const_byte_span bytes) {
const auto full = std::accumulate(
bytes.begin(),
bytes.end(),
const auto full = ranges::accumulate(
bytes,
0ULL,
[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
const auto mod = (full % 255ULL);
@ -178,9 +109,8 @@ bool CheckSecretBytes(base::const_byte_span secret) {
base::byte_vector GenerateSecretBytes() {
auto result = base::byte_vector(kSecretSize);
memset_rand(result.data(), result.size());
const auto full = std::accumulate(
result.begin(),
result.end(),
const auto full = ranges::accumulate(
result,
0ULL,
[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
const auto mod = (full % 255ULL);
@ -228,13 +158,81 @@ base::byte_vector Concatenate(
return result;
}
base::byte_vector SerializeData(const std::map<QString, QString> &data) {
auto root = QJsonObject();
for (const auto &[key, value] : data) {
root.insert(key, value);
}
auto document = QJsonDocument(root);
const auto result = document.toJson(QJsonDocument::Compact);
const auto bytes = gsl::as_bytes(gsl::make_span(result));
return { bytes.begin(), bytes.end() };
}
std::map<QString, QString> DeserializeData(base::const_byte_span bytes) {
const auto serialized = QByteArray::fromRawData(
reinterpret_cast<const char*>(bytes.data()),
bytes.size());
auto error = QJsonParseError();
auto document = QJsonDocument::fromJson(serialized, &error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: Could not deserialize decrypted JSON, error %1"
).arg(error.errorString()));
return {};
} else if (!document.isObject()) {
LOG(("API Error: decrypted JSON root is not an object."));
return {};
}
auto object = document.object();
auto result = std::map<QString, QString>();
for (auto i = object.constBegin(), e = object.constEnd(); i != e; ++i) {
const auto key = i.key();
switch (i->type()) {
case QJsonValue::Null: {
LOG(("API Error: null found inside decrypted JSON root. "
"Defaulting to empty string value."));
result[key] = QString();
} break;
case QJsonValue::Undefined: {
LOG(("API Error: undefined found inside decrypted JSON root. "
"Defaulting to empty string value."));
result[key] = QString();
} break;
case QJsonValue::Bool: {
LOG(("API Error: bool found inside decrypted JSON root. "
"Aborting."));
return {};
} break;
case QJsonValue::Double: {
LOG(("API Error: double found inside decrypted JSON root. "
"Converting to string."));
result[key] = QString::number(i->toDouble());
} break;
case QJsonValue::String: {
result[key] = i->toString();
} break;
case QJsonValue::Array: {
LOG(("API Error: array found inside decrypted JSON root. "
"Aborting."));
return {};
} break;
case QJsonValue::Object: {
LOG(("API Error: object found inside decrypted JSON root. "
"Aborting."));
return {};
} break;
}
}
return result;
}
EncryptedData EncryptData(base::const_byte_span bytes) {
return EncryptData(bytes, GenerateSecretBytes());
}
EncryptedData EncryptData(
base::const_byte_span dataSecret,
const std::map<QString, QString> &data) {
Expects(dataSecret.size() == kSecretSize);
const auto bytes = SerializeData(data);
base::const_byte_span bytes,
base::const_byte_span dataSecret) {
constexpr auto kFromPadding = kMinPadding + kAlignTo - 1;
constexpr auto kPaddingDelta = kMaxPadding - kFromPadding;
const auto randomPadding = kFromPadding
@ -254,14 +252,16 @@ EncryptedData EncryptData(
const auto dataHash = openssl::Sha256(unencrypted);
const auto dataSecretHash = openssl::Sha512(
Concatenate(dataSecret, dataHash));
auto params = PrepareAesParams(dataSecretHash);
return {
{ dataSecret.begin(), dataSecret.end() },
{ dataHash.begin(), dataHash.end() },
Encrypt(unencrypted, std::move(params))
};
}
std::map<QString, QString> DecryptData(
base::byte_vector DecryptData(
base::const_byte_span encrypted,
base::const_byte_span dataHash,
base::const_byte_span dataSecret) {
@ -292,7 +292,7 @@ std::map<QString, QString> DecryptData(
return {};
}
const auto bytes = gsl::make_span(decrypted).subspan(padding);
return DeserializeData(bytes);
return { bytes.begin(), bytes.end() };
}
base::byte_vector PrepareValueHash(
@ -302,6 +302,20 @@ base::byte_vector PrepareValueHash(
return { result.begin(), result.end() };
}
base::byte_vector PrepareFilesHash(
gsl::span<base::const_byte_span> fileHashes,
base::const_byte_span valueSecret) {
auto resultInner = base::byte_vector{
valueSecret.begin(),
valueSecret.end()
};
for (const auto &hash : base::reversed(fileHashes)) {
resultInner = Concatenate(hash, resultInner);
}
const auto result = openssl::Sha256(resultInner);
return { result.begin(), result.end() };
}
base::byte_vector EncryptValueSecret(
base::const_byte_span valueSecret,
base::const_byte_span secret,

View File

@ -20,16 +20,22 @@ base::byte_vector DecryptSecretBytes(
base::byte_vector PasswordHashForSecret(base::const_byte_span passwordUtf8);
base::byte_vector SerializeData(const std::map<QString, QString> &data);
std::map<QString, QString> DeserializeData(base::const_byte_span bytes);
struct EncryptedData {
base::byte_vector secret;
base::byte_vector hash;
base::byte_vector bytes;
};
EncryptedData EncryptData(
base::const_byte_span dataSecret,
const std::map<QString, QString> &data);
EncryptedData EncryptData(base::const_byte_span bytes);
std::map<QString, QString> DecryptData(
EncryptedData EncryptData(
base::const_byte_span bytes,
base::const_byte_span dataSecret);
base::byte_vector DecryptData(
base::const_byte_span encrypted,
base::const_byte_span dataHash,
base::const_byte_span dataSecret);
@ -48,4 +54,8 @@ base::byte_vector DecryptValueSecret(
base::const_byte_span secret,
base::const_byte_span valueHash);
base::byte_vector PrepareFilesHash(
gsl::span<base::const_byte_span> fileHashes,
base::const_byte_span valueSecret);
} // namespace Passport

View File

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "base/openssl_help.h"
#include "mainwindow.h"
#include "auth_session.h"
#include "storage/localimageloader.h"
#include "storage/file_upload.h"
#include "storage/file_download.h"
namespace Passport {
@ -28,10 +32,33 @@ FormRequest::FormRequest(
, publicKey(publicKey) {
}
FormController::UploadedScan::~UploadedScan() {
if (fullId) {
Auth().uploader().cancel(fullId);
}
}
FormController::EditFile::EditFile(
const File &fields,
std::unique_ptr<UploadedScan> &&uploaded)
: fields(std::move(fields))
, uploaded(std::move(uploaded)) {
}
FormController::Field::Field(Type type) : type(type) {
}
template <typename FileHashes>
base::byte_vector FormController::computeFilesHash(
FileHashes fileHashes,
base::const_byte_span valueSecret) {
auto hashesVector = std::vector<base::const_byte_span>();
for (const auto &hash : fileHashes) {
hashesVector.push_back(gsl::as_bytes(gsl::make_span(hash)));
}
return PrepareFilesHash(hashesVector, valueSecret);
}
FormController::FormController(
not_null<Window::Controller*> controller,
const FormRequest &request)
@ -74,6 +101,20 @@ void FormController::submitPassword(const QString &password) {
_passwordHashForSecret);
for (auto &field : _form.fields) {
field.data.values = fillData(field.data);
if (auto &document = field.document) {
const auto filesHash = gsl::as_bytes(gsl::make_span(document->filesHash));
document->filesSecret = DecryptValueSecret(
gsl::as_bytes(gsl::make_span(document->filesSecretEncrypted)),
_secret,
filesHash);
if (document->filesSecret.empty()
&& !document->files.empty()) {
LOG(("API Error: Empty decrypted files secret. "
"Forgetting files."));
document->files.clear();
document->filesHash.clear();
}
}
}
_secretReady.fire({});
}).fail([=](const RPCError &error) {
@ -96,6 +137,101 @@ QString FormController::passwordHint() const {
return _password.hint;
}
void FormController::uploadScan(int index, QByteArray &&content) {
Expects(_editBox != nullptr);
Expects(index >= 0 && index < _form.fields.size());
Expects(_form.fields[index].document.has_value());
auto &document = *_form.fields[index].document;
if (document.filesSecret.empty()) {
document.filesSecret = GenerateSecretBytes();
}
const auto weak = _editBox;
crl::async([
=,
bytes = std::move(content),
filesSecret = document.filesSecret
] {
auto data = EncryptData(
gsl::as_bytes(gsl::make_span(bytes)),
filesSecret);
auto result = UploadedScan();
result.fileId = rand_value<uint64>();
result.hash = std::move(data.hash);
result.bytes = std::move(data.bytes);
result.md5checksum.resize(32);
hashMd5Hex(
result.bytes.data(),
result.bytes.size(),
result.md5checksum.data());
crl::on_main([=, encrypted = std::move(result)]() mutable {
if (weak) {
uploadEncryptedScan(index, std::move(encrypted));
}
});
});
}
void FormController::subscribeToUploader() {
if (_uploaderSubscriptions) {
return;
}
Auth().uploader().secureReady(
) | rpl::start_with_next([=](const Storage::UploadedSecure &data) {
scanUploaded(data);
}, _uploaderSubscriptions);
Auth().uploader().secureProgress(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
}, _uploaderSubscriptions);
Auth().uploader().secureFailed(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
}, _uploaderSubscriptions);
}
void FormController::uploadEncryptedScan(int index, UploadedScan &&data) {
Expects(_editBox != nullptr);
Expects(index >= 0 && index < _form.fields.size());
Expects(_form.fields[index].document.has_value());
subscribeToUploader();
_form.fields[index].document->filesInEdit.emplace_back(
File(),
std::make_unique<UploadedScan>(std::move(data)));
auto &file = _form.fields[index].document->filesInEdit.back();
auto uploaded = std::make_shared<FileLoadResult>(
TaskId(),
file.uploaded->fileId,
FileLoadTo(PeerId(0), false, MsgId(0)),
TextWithTags(),
std::shared_ptr<SendingAlbum>(nullptr));
uploaded->type = SendMediaType::Secure;
uploaded->content = QByteArray::fromRawData(
reinterpret_cast<char*>(file.uploaded->bytes.data()),
file.uploaded->bytes.size());
uploaded->setFileData(uploaded->content);
uploaded->filemd5 = file.uploaded->md5checksum;
file.uploaded->fullId = FullMsgId(0, clientMsgId());
Auth().uploader().upload(file.uploaded->fullId, std::move(uploaded));
}
void FormController::scanUploaded(const Storage::UploadedSecure &data) {
if (const auto edit = findEditFile(data.fullId)) {
Assert(edit->uploaded != nullptr);
edit->fields.id = edit->uploaded->fileId = data.fileId;
edit->fields.size = edit->uploaded->bytes.size();
edit->fields.dcId = MTP::maindc();
edit->uploaded->partsCount = data.partsCount;
edit->fields.bytes = std::move(edit->uploaded->bytes);
edit->fields.fileHash = std::move(edit->uploaded->hash);
edit->uploaded->fullId = FullMsgId();
}
}
rpl::producer<> FormController::secretReadyEvents() const {
return _secretReady.events();
}
@ -111,6 +247,10 @@ QString FormController::defaultPhoneNumber() const {
return QString();
}
rpl::producer<ScanInfo> FormController::scanUpdated() const {
return _scanUpdated.events();
}
void FormController::fillRows(
base::lambda<void(
QString title,
@ -150,10 +290,24 @@ void FormController::editField(int index) {
Expects(index >= 0 && index < _form.fields.size());
auto box = [&]() -> object_ptr<BoxContent> {
const auto &field = _form.fields[index];
auto &field = _form.fields[index];
switch (field.type) {
case Field::Type::Identity:
return Box<IdentityBox>(this, index, fieldDataIdentity(field));
if (field.document) {
loadFiles(field.document->files);
} else {
field.document = Value();
}
field.document->filesInEdit = ranges::view::all(
field.document->files
) | ranges::view::transform([](const File &file) {
return EditFile(file, nullptr);
}) | ranges::to_vector;
return Box<IdentityBox>(
this,
index,
fieldDataIdentity(field),
fieldFilesIdentity(field));
}
return { nullptr };
}();
@ -162,6 +316,56 @@ void FormController::editField(int index) {
}
}
void FormController::loadFiles(const std::vector<File> &files) {
for (const auto &file : files) {
const auto key = FileKey{ file.id, file.dcId };
const auto i = _fileLoaders.find(key);
if (i == _fileLoaders.end()) {
const auto [i, ok] = _fileLoaders.emplace(
key,
std::make_unique<mtpFileLoader>(
file.dcId,
file.id,
file.accessHash,
0,
SecureFileLocation,
QString(),
file.size,
LoadToCacheAsWell,
LoadFromCloudOrLocal,
false));
const auto loader = i->second.get();
loader->connect(loader, &mtpFileLoader::progress, [=] {
if (loader->finished()) {
fileLoaded(key, loader->bytes());
}
});
loader->connect(loader, &mtpFileLoader::failed, [=] {
});
loader->start();
}
}
}
void FormController::fileLoaded(FileKey key, const QByteArray &bytes) {
if (const auto [field, file] = findFile(key); file != nullptr) {
const auto decrypted = DecryptData(
gsl::as_bytes(gsl::make_span(bytes)),
file->fileHash,
field->document->filesSecret);
auto image = App::readImage(QByteArray::fromRawData(
reinterpret_cast<const char*>(decrypted.data()),
decrypted.size()));
if (!image.isNull()) {
_scanUpdated.fire({
FileKey{ file->id, file->dcId },
QString("loaded"),
std::move(image),
});
}
}
}
IdentityData FormController::fieldDataIdentity(const Field &field) const {
const auto &map = field.data.values;
auto result = IdentityData();
@ -174,6 +378,20 @@ IdentityData FormController::fieldDataIdentity(const Field &field) const {
return result;
}
std::vector<ScanInfo> FormController::fieldFilesIdentity(
const Field &field) const {
Expects(field.document.has_value());
auto result = std::vector<ScanInfo>();
for (const auto &file : field.document->filesInEdit) {
result.push_back({
FileKey{ file.fields.id, file.fields.dcId },
langDateTime(QDateTime::currentDateTime()),
QImage() });
}
return result;
}
void FormController::saveFieldIdentity(
int index,
const IdentityData &data) {
@ -185,24 +403,25 @@ void FormController::saveFieldIdentity(
_form.fields[index].data.values[qsl("last_name")] = data.surname;
saveData(index);
saveFiles(index);
_editBox->closeBox();
}
std::map<QString, QString> FormController::fillData(
const Value &from) const {
if (from.data.isEmpty()) {
if (from.dataEncrypted.isEmpty()) {
return {};
}
const auto valueHash = gsl::as_bytes(gsl::make_span(from.dataHash));
const auto valueSecret = DecryptValueSecret(
gsl::as_bytes(gsl::make_span(from.dataSecret)),
gsl::as_bytes(gsl::make_span(from.dataSecretEncrypted)),
_secret,
valueHash);
return DecryptData(
gsl::as_bytes(gsl::make_span(from.data)),
return DeserializeData(DecryptData(
gsl::as_bytes(gsl::make_span(from.dataEncrypted)),
valueHash,
valueSecret);
valueSecret));
}
void FormController::saveData(int index) {
@ -215,8 +434,7 @@ void FormController::saveData(int index) {
return;
}
const auto &data = _form.fields[index].data;
const auto valueSecret = GenerateSecretBytes();
const auto encrypted = EncryptData(valueSecret, data.values);
const auto encrypted = EncryptData(SerializeData(data.values));
// #TODO file_hash + file_hash + ...
// PrepareValueHash(encrypted.hash, valueSecret);
@ -234,7 +452,7 @@ void FormController::saveData(int index) {
}
const auto encryptedValueSecret = EncryptValueSecret(
valueSecret,
encrypted.secret,
_secret,
valueHash);
request(MTPaccount_SaveSecureValue(MTP_inputSecureValueData(
@ -249,11 +467,90 @@ void FormController::saveData(int index) {
Ui::show(Box<InformBox>("Verification needed :("), LayerOption::KeepOther);
}
}).fail([=](const RPCError &error) {
// #TODO
Ui::show(Box<InformBox>("Error saving value."));
}).send();
}
void FormController::saveFiles(int index) {
Expects(index >= 0 && index < _form.fields.size());
Expects(_form.fields[index].document.has_value());
if (_secret.empty()) {
generateSecret([=] {
saveFiles(index);
});
return;
}
auto &document = *_form.fields[index].document;
if (document.filesSecret.empty()) {
Assert(document.filesInEdit.empty());
return;
}
auto filesHash = computeFilesHash(
ranges::view::all(
document.filesInEdit
) | ranges::view::transform([=](const EditFile &file) {
return gsl::as_bytes(gsl::make_span(file.fields.fileHash));
}),
document.filesSecret);
auto filesHashString = QString();
filesHashString.reserve(filesHash.size() * 2);
const auto hex = [](uchar value) -> QChar {
return (value >= 10) ? ('a' + (value - 10)) : ('0' + value);
};
for (const auto byte : filesHash) {
const auto value = uchar(byte);
const auto high = uchar(value / 16);
const auto low = uchar(value % 16);
filesHashString.append(hex(high)).append(hex(low));
}
auto files = QVector<MTPInputSecureFile>();
files.reserve(document.filesInEdit.size());
for (const auto &file : document.filesInEdit) {
if (const auto uploaded = file.uploaded.get()) {
auto fileHashString = QString();
for (const auto byte : file.fields.fileHash) {
const auto value = uchar(byte);
const auto high = uchar(value / 16);
const auto low = uchar(value % 16);
fileHashString.append(hex(high)).append(hex(low));
}
files.push_back(MTP_inputSecureFileUploaded(
MTP_long(file.fields.id),
MTP_int(uploaded->partsCount),
MTP_bytes(uploaded->md5checksum),
MTP_string(fileHashString)));
} else {
files.push_back(MTP_inputSecureFile(
MTP_long(file.fields.id),
MTP_long(file.fields.accessHash)));
}
}
const auto encryptedFilesSecret = EncryptValueSecret(
document.filesSecret,
_secret,
filesHash);
request(MTPaccount_SaveSecureValue(MTP_inputSecureValueFile(
MTP_string(document.name),
MTP_vector<MTPInputSecureFile>(files),
MTP_string(filesHashString),
MTP_bytes(encryptedFilesSecret)
))).done([=](const MTPaccount_SecureValueResult &result) {
if (result.type() == mtpc_account_secureValueResultSaved) {
Ui::show(Box<InformBox>("Files Saved"), LayerOption::KeepOther);
} else if (result.type() == mtpc_account_secureValueVerificationNeeded) {
Ui::show(Box<InformBox>("Verification needed :("), LayerOption::KeepOther);
}
}).fail([=](const RPCError &error) {
Ui::show(Box<InformBox>("Error saving files."));
}).send();
}
void FormController::generateSecret(base::lambda<void()> callback) {
_secretCallbacks.push_back(callback);
if (_saveSecretRequestId) {
return;
}
@ -274,7 +571,9 @@ void FormController::generateSecret(base::lambda<void()> callback) {
)).done([=](const MTPBool &result) {
_saveSecretRequestId = 0;
_secret = secret;
callback();
for (const auto &callback : base::take(_secretCallbacks)) {
callback();
}
}).fail([=](const RPCError &error) {
// #TODO wrong password hash error?
Ui::show(Box<InformBox>("Saving encrypted value failed."));
@ -319,7 +618,7 @@ auto FormController::convertValue(
case mtpc_secureValueData: {
const auto &data = value.c_secureValueData();
result.name = qs(data.vname);
result.data = data.vdata.v;
result.dataEncrypted = data.vdata.v;
const auto hashString = qs(data.vhash);
for (auto i = 0, count = hashString.size(); i + 1 < count; i += 2) {
auto digit = [&](QChar ch) -> int {
@ -344,7 +643,7 @@ auto FormController::convertValue(
result.dataHash.clear();
}
// result.dataHash = data.vhash.v;
result.dataSecret = data.vsecret.v;
result.dataSecretEncrypted = data.vsecret.v;
} break;
case mtpc_secureValueText: {
const auto &data = value.c_secureValueText();
@ -355,8 +654,31 @@ auto FormController::convertValue(
case mtpc_secureValueFile: {
const auto &data = value.c_secureValueFile();
result.name = qs(data.vname);
result.filesHash = data.vhash.v;
result.filesSecret = data.vsecret.v;
const auto hashString = qs(data.vhash);
for (auto i = 0, count = hashString.size(); i + 1 < count; i += 2) {
auto digit = [&](QChar ch) -> int {
const auto code = ch.unicode();
if (code >= 'a' && code <= 'f') {
return (code - 'a') + 10;
} else if (code >= 'A' && code <= 'F') {
return (code - 'A') + 10;
} else if (code >= '0' && code <= '9') {
return (code - '0');
}
return -1;
};
const auto ch1 = digit(hashString[i]);
const auto ch2 = digit(hashString[i + 1]);
if (ch1 >= 0 && ch2 >= 0) {
const auto byte = ch1 * 16 + ch2;
result.filesHash.push_back(byte);
}
}
if (result.filesHash.size() != 32) {
result.filesHash.clear();
}
// result.filesHash = data.vhash.v;
result.filesSecretEncrypted = data.vsecret.v;
for (const auto &file : data.vfile.v) {
switch (file.type()) {
case mtpc_secureFileEmpty: {
@ -369,7 +691,30 @@ auto FormController::convertValue(
normal.accessHash = fields.vaccess_hash.v;
normal.size = fields.vsize.v;
normal.dcId = fields.vdc_id.v;
normal.fileHash = qba(fields.vfile_hash);
const auto fileHashString = qs(fields.vfile_hash);
for (auto i = 0, count = fileHashString.size(); i + 1 < count; i += 2) {
auto digit = [&](QChar ch) -> int {
const auto code = ch.unicode();
if (code >= 'a' && code <= 'f') {
return (code - 'a') + 10;
} else if (code >= 'A' && code <= 'F') {
return (code - 'A') + 10;
} else if (code >= '0' && code <= '9') {
return (code - '0');
}
return -1;
};
const auto ch1 = digit(fileHashString[i]);
const auto ch2 = digit(fileHashString[i + 1]);
if (ch1 >= 0 && ch2 >= 0) {
const auto byte = ch1 * 16 + ch2;
normal.fileHash.push_back(gsl::byte(byte));
}
}
if (normal.fileHash.size() != 32) {
normal.fileHash.clear();
}
// normal.fileHash = byteVectorFromMTP(fields.vfile_hash);
result.files.push_back(std::move(normal));
} break;
}
@ -379,6 +724,33 @@ auto FormController::convertValue(
return result;
}
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
for (auto &field : _form.fields) {
if (auto &document = field.document) {
for (auto &file : document->filesInEdit) {
if (file.uploaded && file.uploaded->fullId == fullId) {
return &file;
}
}
}
}
return nullptr;
}
auto FormController::findFile(const FileKey &key)
-> std::pair<Field*, File*> {
for (auto &field : _form.fields) {
if (auto &document = field.document) {
for (auto &file : document->files) {
if (file.dcId == key.dcId && file.id == key.id) {
return { &field, &file };
}
}
}
}
return { nullptr, nullptr };
}
void FormController::formDone(const MTPaccount_AuthorizationForm &result) {
parseForm(result);
if (!_passwordRequestId) {
@ -479,4 +851,6 @@ void FormController::parsePassword(const MTPDaccount_password &result) {
gsl::as_bytes(gsl::make_span(result.vsecret_random.v)));
}
FormController::~FormController() = default;
} // namespace Passport

View File

@ -8,9 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "mtproto/sender.h"
#include "base/weak_ptr.h"
class BoxContent;
namespace Storage {
struct UploadedSecure;
} // namespace Storage
namespace Window {
class Controller;
} // namespace Window
@ -33,7 +38,39 @@ struct FormRequest {
struct IdentityData;
class FormController : private MTP::Sender {
struct FileKey {
uint64 id = 0;
int32 dcId = 0;
inline bool operator==(const FileKey &other) const {
return (id == other.id) && (dcId == other.dcId);
}
inline bool operator!=(const FileKey &other) const {
return !(*this == other);
}
inline bool operator<(const FileKey &other) const {
return (id < other.id) || ((id == other.id) && (dcId < other.dcId));
}
inline bool operator>(const FileKey &other) const {
return (other < *this);
}
inline bool operator<=(const FileKey &other) const {
return !(other < *this);
}
inline bool operator>=(const FileKey &other) const {
return !(*this < other);
}
};
struct ScanInfo {
FileKey key;
QString date;
QImage thumb;
};
class FormController : private MTP::Sender, public base::has_weak_ptr {
public:
FormController(
not_null<Window::Controller*> controller,
@ -45,10 +82,13 @@ public:
rpl::producer<QString> passwordError() const;
QString passwordHint() const;
void uploadScan(int index, QByteArray &&content);
rpl::producer<> secretReadyEvents() const;
QString defaultEmail() const;
QString defaultPhoneNumber() const;
rpl::producer<ScanInfo> scanUpdated() const;
void fillRows(
base::lambda<void(
@ -59,20 +99,41 @@ public:
void saveFieldIdentity(int index, const IdentityData &data);
~FormController();
private:
struct UploadedScan {
~UploadedScan();
FullMsgId fullId;
uint64 fileId = 0;
int partsCount = 0;
QByteArray md5checksum;
base::byte_vector hash;
base::byte_vector bytes;
};
struct File {
uint64 id = 0;
uint64 accessHash = 0;
int32 size = 0;
int32 dcId = 0;
QByteArray fileHash;
base::byte_vector fileHash;
base::byte_vector bytes;
};
struct EditFile {
EditFile(
const File &fields,
std::unique_ptr<UploadedScan> &&uploaded);
File fields;
std::unique_ptr<UploadedScan> uploaded;
};
struct Value {
QString name;
QByteArray data;
QByteArray dataEncrypted;
QByteArray dataHash;
QByteArray dataSecret;
QByteArray dataSecretEncrypted;
std::map<QString, QString> values;
QString text;
@ -80,7 +141,10 @@ private:
std::vector<File> files;
QByteArray filesHash;
QByteArray filesSecret;
QByteArray filesSecretEncrypted;
base::byte_vector filesSecret;
std::vector<EditFile> filesInEdit;
};
struct Field {
enum class Type {
@ -90,6 +154,7 @@ private:
Email,
};
explicit Field(Type type);
Field(Field &&other) = default;
Type type;
Value data;
@ -107,6 +172,8 @@ private:
bool hasRecovery = false;
};
Value convertValue(const MTPSecureValue &value) const;
EditFile *findEditFile(const FullMsgId &fullId);
std::pair<Field*, File*> findFile(const FileKey &key);
void requestForm();
void requestPassword();
@ -122,11 +189,24 @@ private:
void parsePassword(const MTPDaccount_password &settings);
IdentityData fieldDataIdentity(const Field &field) const;
std::vector<ScanInfo> fieldFilesIdentity(const Field &field) const;
void loadFiles(const std::vector<File> &files);
void fileLoaded(FileKey key, const QByteArray &bytes);
std::map<QString, QString> fillData(const Value &from) const;
void saveData(int index);
void saveFiles(int index);
void generateSecret(base::lambda<void()> callback);
template <typename FileHashes>
base::byte_vector computeFilesHash(
FileHashes fileHashes,
base::const_byte_span valueHash);
void subscribeToUploader();
void uploadEncryptedScan(int index, UploadedScan &&data);
void scanUploaded(const Storage::UploadedSecure &data);
not_null<Window::Controller*> _controller;
FormRequest _request;
UserData *_bot = nullptr;
@ -138,10 +218,13 @@ private:
PasswordSettings _password;
Form _form;
std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
rpl::event_stream<ScanInfo> _scanUpdated;
base::byte_vector _passwordHashForSecret;
base::byte_vector _passwordHashForAuth;
base::byte_vector _secret;
std::vector<base::lambda<void()>> _secretCallbacks;
mtpRequestId _saveSecretRequestId = 0;
QString _passwordEmail;
rpl::event_stream<> _secretReady;
@ -149,6 +232,8 @@ private:
QPointer<BoxContent> _editBox;
rpl::lifetime _uploaderSubscriptions;
};
} // namespace Passport

View File

@ -493,6 +493,8 @@ void mtpFileLoader::makeRequest(int offset) {
MTPInputFileLocation mtpFileLoader::computeLocation() const {
if (_location) {
return MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret()));
} else if (_locationType == SecureFileLocation) {
return MTP_inputSecureFileLocation(MTP_long(_id), MTP_long(_accessHash));
}
return MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_accessHash), MTP_int(_version));
}

View File

@ -59,7 +59,8 @@ Uploader::File::File(const SendMediaReady &media) : media(media) {
}
Uploader::File::File(const std::shared_ptr<FileLoadResult> &file)
: file(file) {
partsCount = (type() == SendMediaType::Photo)
partsCount = (type() == SendMediaType::Photo
|| type() == SendMediaType::Secure)
? file->fileparts.size()
: file->thumbparts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
@ -160,13 +161,18 @@ void Uploader::currentFailed() {
auto j = queue.find(uploadingId);
if (j != queue.end()) {
if (j->second.type() == SendMediaType::Photo) {
emit photoFailed(j->first);
} else if (j->second.type() == SendMediaType::File) {
_photoFailed.fire_copy(j->first);
} else if (j->second.type() == SendMediaType::File
|| j->second.type() == SendMediaType::Audio) {
const auto document = Auth().data().document(j->second.id());
if (document->uploading()) {
document->status = FileUploadFailed;
}
emit documentFailed(j->first);
_documentFailed.fire_copy(j->first);
} else if (j->second.type() == SendMediaType::Secure) {
_secureFailed.fire_copy(j->first);
} else {
Unexpected("Type in Uploader::currentFailed.");
}
queue.erase(j);
}
@ -220,12 +226,14 @@ void Uploader::sendNext() {
}
auto &parts = uploadingData.file
? (uploadingData.type() == SendMediaType::Photo
? ((uploadingData.type() == SendMediaType::Photo
|| uploadingData.type() == SendMediaType::Secure)
? uploadingData.file->fileparts
: uploadingData.file->thumbparts)
: uploadingData.media.parts;
const auto partsOfId = uploadingData.file
? (uploadingData.type() == SendMediaType::Photo
? ((uploadingData.type() == SendMediaType::Photo
|| uploadingData.type() == SendMediaType::Secure)
? uploadingData.file->id
: uploadingData.file->thumbId)
: uploadingData.media.thumbId;
@ -250,7 +258,7 @@ void Uploader::sendNext() {
MTP_int(uploadingData.partsCount),
MTP_string(photoFilename),
MTP_bytes(md5));
emit photoReady(uploadingId, silent, file);
_photoReady.fire({ uploadingId, silent, file });
} else if (uploadingData.type() == SendMediaType::File
|| uploadingData.type() == SendMediaType::Audio) {
QByteArray docMd5(32, Qt::Uninitialized);
@ -278,14 +286,19 @@ void Uploader::sendNext() {
MTP_int(uploadingData.partsCount),
MTP_string(thumbFilename),
MTP_bytes(thumbMd5));
emit thumbDocumentReady(
_thumbDocumentReady.fire({
uploadingId,
silent,
file,
thumb);
thumb });
} else {
emit documentReady(uploadingId, silent, file);
_documentReady.fire({ uploadingId, silent, file });
}
} else if (uploadingData.type() == SendMediaType::Secure) {
_secureReady.fire({
uploadingId,
uploadingData.id(),
uploadingData.partsCount });
}
queue.erase(uploadingId);
uploadingId = FullMsgId();
@ -457,7 +470,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
photo->uploadingData->size = file.file->partssize;
photo->uploadingData->offset = file.fileSentSize;
}
emit photoProgress(fullId);
_photoProgress.fire_copy(fullId);
} else if (file.type() == SendMediaType::File
|| file.type() == SendMediaType::Audio) {
const auto document = Auth().data().document(file.id());
@ -468,7 +481,9 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
document->uploadingData->size,
doneParts * file.docPartSize);
}
emit documentProgress(fullId);
_documentProgress.fire_copy(fullId);
} else if (file.type() == SendMediaType::Secure) {
_secureProgress.fire_copy(fullId);
}
}
}

View File

@ -12,6 +12,31 @@ struct SendMediaReady;
namespace Storage {
struct UploadedPhoto {
FullMsgId fullId;
bool silent = false;
MTPInputFile file;
};
struct UploadedDocument {
FullMsgId fullId;
bool silent = false;
MTPInputFile file;
};
struct UploadedThumbDocument {
FullMsgId fullId;
bool silent = false;
MTPInputFile file;
MTPInputFile thumb;
};
struct UploadedSecure {
FullMsgId fullId;
uint64 fileId = 0;
int partsCount = 0;
};
class Uploader : public QObject, public RPCSender {
Q_OBJECT
@ -31,6 +56,37 @@ public:
void clear();
rpl::producer<UploadedPhoto> photoReady() const {
return _photoReady.events();
}
rpl::producer<UploadedDocument> documentReady() const {
return _documentReady.events();
}
rpl::producer<UploadedThumbDocument> thumbDocumentReady() const {
return _thumbDocumentReady.events();
}
rpl::producer<UploadedSecure> secureReady() const {
return _secureReady.events();
}
rpl::producer<FullMsgId> photoProgress() const {
return _photoProgress.events();
}
rpl::producer<FullMsgId> documentProgress() const {
return _documentProgress.events();
}
rpl::producer<FullMsgId> secureProgress() const {
return _secureProgress.events();
}
rpl::producer<FullMsgId> photoFailed() const {
return _photoFailed.events();
}
rpl::producer<FullMsgId> documentFailed() const {
return _documentFailed.events();
}
rpl::producer<FullMsgId> secureFailed() const {
return _secureFailed.events();
}
~Uploader();
public slots:
@ -38,17 +94,6 @@ public slots:
void sendNext();
void stopSessions();
signals:
void photoReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void documentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void thumbDocumentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
void photoProgress(const FullMsgId &msgId);
void documentProgress(const FullMsgId &msgId);
void photoFailed(const FullMsgId &msgId);
void documentFailed(const FullMsgId &msgId);
private:
struct File;
@ -69,6 +114,17 @@ private:
std::map<FullMsgId, File> uploaded;
QTimer nextTimer, stopSessionsTimer;
rpl::event_stream<UploadedPhoto> _photoReady;
rpl::event_stream<UploadedDocument> _documentReady;
rpl::event_stream<UploadedThumbDocument> _thumbDocumentReady;
rpl::event_stream<UploadedSecure> _secureReady;
rpl::event_stream<FullMsgId> _photoProgress;
rpl::event_stream<FullMsgId> _documentProgress;
rpl::event_stream<FullMsgId> _secureProgress;
rpl::event_stream<FullMsgId> _photoFailed;
rpl::event_stream<FullMsgId> _documentFailed;
rpl::event_stream<FullMsgId> _secureFailed;
};
} // namespace Storage

View File

@ -20,6 +20,7 @@ enum class SendMediaType {
Photo,
Audio,
File,
Secure,
};
struct SendMediaPrepare {