mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-30 07:18:28 +00:00
Secure files upload / download support.
This commit is contained in:
parent
f633ead3ab
commit
b2014f403e
@ -288,6 +288,7 @@ enum LocationType {
|
||||
DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation
|
||||
AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation
|
||||
VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation
|
||||
SecureFileLocation = 0xcbc7ee28, // mtpc_inputSecureFileLocation
|
||||
};
|
||||
|
||||
enum FileStatus {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -20,6 +20,7 @@ enum class SendMediaType {
|
||||
Photo,
|
||||
Audio,
|
||||
File,
|
||||
Secure,
|
||||
};
|
||||
|
||||
struct SendMediaPrepare {
|
||||
|
Loading…
Reference in New Issue
Block a user