File indexing completed on 2024-05-05 17:32:29
0001 // SPDX-FileCopyrightText: 2021 Michael Lang <criticaltemp@protonmail.com> 0002 // 0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "mms.h" 0006 #include "settingsmanager.h" 0007 0008 #include <QBuffer> 0009 #include <QDebug> 0010 #include <QDir> 0011 #include <QImage> 0012 #include <QJsonArray> 0013 #include <QJsonDocument> 0014 #include <QJsonObject> 0015 #include <QMimeDatabase> 0016 #include <QMimeType> 0017 #include <QStandardPaths> 0018 #include <QUuid> 0019 0020 #include <random> 0021 0022 constexpr unsigned char MMS_CODE_END_STRING = 0x00; 0023 0024 constexpr unsigned char MMS_CODE_VERSION = 0x93; // v1.3 0025 0026 constexpr unsigned char MMS_CODE_NO = 0x80; 0027 constexpr unsigned char MMS_CODE_YES = 0x81; 0028 0029 constexpr unsigned char MMS_CODE_MULTIPART_MIXED = 0xA3; 0030 constexpr unsigned char MMS_CODE_MULTIPART_RELATED = 0xB3; 0031 0032 constexpr unsigned char MMS_CODE_ADDRESS_PRESENT_TOKEN = 0x80; 0033 constexpr unsigned char MMS_CODE_INSERT_ADDRESS_TOKEN = 0x81; 0034 0035 constexpr unsigned char MMS_CODE_ABSOLUTE_TOKEN = 0x80; 0036 constexpr unsigned char MMS_CODE_RELATIVE_TOKEN = 0x81; 0037 0038 constexpr unsigned char MMS_CODE_READ = 0x80; 0039 constexpr unsigned char MMS_CODE_DELETE_WITHOUT_READ = 0x81; 0040 0041 constexpr unsigned char MMS_CODE_CANCEL_REQUEST_SUCCESSFULLY_RECEIVED = 0x80; 0042 0043 constexpr unsigned char MMS_CODE_PRIORITY_LOW = 0x80; 0044 constexpr unsigned char MMS_CODE_PRIORITY_NORMAL = 0x81; 0045 constexpr unsigned char MMS_CODE_PRIORITY_HIGH = 0x82; 0046 0047 constexpr unsigned char MMS_CODE_MESSAGE_CLASS_PERSONAL = 0x80; 0048 constexpr unsigned char MMS_CODE_MESSAGE_CLASS_ADVERTISEMENT = 0x81; 0049 constexpr unsigned char MMS_CODE_MESSAGE_CLASS_INFORMATIONAL = 0x82; 0050 constexpr unsigned char MMS_CODE_MESSAGE_CLASS_AUTO = 0x83; 0051 0052 constexpr unsigned char MMS_HEADER_CANCEL_STATUS = 0xBF; 0053 constexpr unsigned char MMS_HEADER_CONTENT_LOCATION = 0x83; 0054 constexpr unsigned char MMS_HEADER_CONTENT_TYPE = 0x84; 0055 constexpr unsigned char MMS_HEADER_DATE = 0x85; 0056 constexpr unsigned char MMS_HEADER_DELIVERY_REPORT = 0x86; 0057 constexpr unsigned char MMS_HEADER_DELIVERY_TIME = 0x87; 0058 constexpr unsigned char MMS_HEADER_FROM = 0x89; 0059 constexpr unsigned char MMS_HEADER_PRIORITY = 0x8F; 0060 constexpr unsigned char MMS_HEADER_MESSAGE_CLASS = 0x8A; 0061 constexpr unsigned char MMS_HEADER_MESSAGE_TYPE = 0x8C; 0062 constexpr unsigned char MMS_HEADER_MESSAGE_ID = 0x8B; 0063 constexpr unsigned char MMS_HEADER_READ_STATUS = 0x9B; 0064 constexpr unsigned char MMS_HEADER_READ_REPORT = 0x90; 0065 constexpr unsigned char MMS_HEADER_REPORT_ALLOWED = 0x91; 0066 constexpr unsigned char MMS_HEADER_SENDER_VISIBILITY = 0x94; 0067 constexpr unsigned char MMS_HEADER_STATUS = 0x95; 0068 constexpr unsigned char MMS_HEADER_SUBJECT = 0x96; 0069 constexpr unsigned char MMS_HEADER_TO = 0x97; 0070 constexpr unsigned char MMS_HEADER_TRANSACTION_ID = 0x98; 0071 constexpr unsigned char MMS_HEADER_VERSION = 0x8D; 0072 0073 constexpr unsigned char MMS_PARAM_NAME = 0x85; 0074 constexpr unsigned char MMS_PARAM_START = 0x8A; 0075 constexpr unsigned char MMS_PARAM_TYPE_SPEC = 0x89; 0076 0077 constexpr unsigned char MMS_PDU_TYPE_PUSH = 0x06; 0078 0079 Mms::Mms(QObject *parent) 0080 : QObject(parent) 0081 { 0082 } 0083 0084 QByteArray Mms::encodeNotifyResponse(const QString &transactionId, const QString &status) 0085 { 0086 QByteArray data; 0087 data.append(encodeHeaderPrefix(SL("m-notifyresp-ind"), transactionId)); 0088 0089 data.append(MMS_HEADER_STATUS); 0090 data.append(encodeValueFromList(status, STATUS_VALUES)); 0091 0092 data.append(MMS_HEADER_REPORT_ALLOWED); 0093 data.append((SettingsManager::self()->shareDeliveryStatus() ? MMS_CODE_YES : MMS_CODE_NO)); 0094 0095 return data; 0096 } 0097 0098 QByteArray Mms::encodeDeliveryAcknowledgement(const QString &transactionId) 0099 { 0100 QByteArray data; 0101 data.append(encodeHeaderPrefix(SL("m-acknowledge-ind"), transactionId)); 0102 0103 data.append(MMS_HEADER_REPORT_ALLOWED); 0104 data.append((SettingsManager::self()->shareDeliveryStatus() ? MMS_CODE_YES : MMS_CODE_NO)); 0105 0106 return data; 0107 } 0108 0109 // confirms the read status of the MM to the MMS Proxy-Relay - send when marking mms messages as read 0110 QByteArray Mms::encodeReadReport(const QString &messageId) 0111 { 0112 QByteArray data; 0113 data.append(encodeHeaderPrefix(SL("m-read-rec-ind"), messageId, true)); 0114 0115 data.append(MMS_HEADER_TO); 0116 // data.append(); 0117 0118 data.append(MMS_HEADER_FROM); 0119 // data.append(); 0120 0121 data.append(MMS_HEADER_READ_STATUS); 0122 data.append(MMS_CODE_READ); // MMS_CODE_DELETE_WITHOUT_READ 0123 0124 // TODO: finish this 0125 return data; 0126 } 0127 0128 // TODO add forwardMessage method - m-forward-req with handling of m-forward-conf - from, to, contentLocation, reportAllowed, deliveryReport, readReport 0129 // TODO add deleteRequest method - m-delete-req with handling of m-delete-conf - contentLocation 0130 0131 QByteArray Mms::encodeCancelResponse(const QString &transactionId) 0132 { 0133 QByteArray data; 0134 data.append(encodeHeaderPrefix(SL("m-cancel-conf"), transactionId)); 0135 0136 data.append(MMS_HEADER_CANCEL_STATUS); 0137 data.append(MMS_CODE_CANCEL_REQUEST_SUCCESSFULLY_RECEIVED); 0138 0139 return data; 0140 } 0141 0142 void Mms::decodeNotification(MmsMessage &message, const QByteArray &data) 0143 { 0144 // Save copy for testing/debugging purposes. Delete later 0145 const QString folderTemp = saveLocation(SL("notifications")); 0146 const QString pathTemp = folderTemp + SL("/") + generateRandomId(); 0147 saveData(data, pathTemp); 0148 0149 int pos = 0; 0150 0151 if (data.at(++pos) != MMS_PDU_TYPE_PUSH) 0152 return; 0153 0154 int headerLen = unsignedInt(data, pos); 0155 0156 if (data.at(pos) == 40) { 0157 // skip the open parenthesis 0158 pos += 2; 0159 } 0160 0161 message.contentType = contentTypeValue(data, pos, message); 0162 if (message.contentType.isEmpty()) { 0163 qDebug() << "unknown content type"; 0164 return; 0165 } 0166 0167 pos += headerLen - pos; 0168 while (decodeHeader(message, data, pos)) 0169 ; 0170 0171 // use the message transaction id if url does not contain an id 0172 if (message.contentLocation.endsWith(SL("="))) { 0173 message.contentLocation.append(message.transactionId); 0174 } 0175 0176 // remove if successfully decoded 0177 if (!message.transactionId.isEmpty()) { 0178 QFile file(pathTemp); 0179 file.remove(); 0180 } 0181 } 0182 0183 void Mms::decodeConfirmation(MmsMessage &message, const QByteArray &data) 0184 { 0185 int pos = -1; 0186 while (decodeHeader(message, data, pos)) 0187 ; 0188 0189 // Save copy for testing/debugging purposes. Delete later 0190 if (!message.transactionId.isEmpty()) { 0191 const QString folder = saveLocation(SL("confirmations")); 0192 const QString path = folder + SL("/") + message.transactionId; 0193 saveData(data, path); 0194 } 0195 } 0196 0197 void Mms::decodeMessage(MmsMessage &message, const QByteArray &data) 0198 { 0199 // Save copy for testing/debugging purposes. Delete later 0200 const QString folderTemp = saveLocation(SL("downloads")); 0201 const QString pathTemp = folderTemp + SL("/") + generateRandomId(); 0202 saveData(data, pathTemp); 0203 0204 int pos = -1; 0205 while (decodeHeader(message, data, pos)) 0206 ; 0207 0208 // add from number and normalize numbers 0209 QStringList numbers = message.to; 0210 if (PhoneNumber(message.from).isValid()) { 0211 numbers.append(message.from); 0212 } 0213 numbers.sort(); 0214 message.phoneNumberList = PhoneNumberList(numbers.join(u'~')); 0215 0216 // remove own number if present 0217 const int index = message.phoneNumberList.indexOf(message.ownNumber); 0218 if (index >= 0) { 0219 message.phoneNumberList.removeAt(index); 0220 } 0221 0222 if (message.contentType == SL("application/vnd.wap.multipart.*") || message.contentType == SL("application/vnd.wap.multipart.mixed") 0223 || message.contentType == SL("application/vnd.wap.multipart.form-data") || message.contentType == SL("application/vnd.wap.multipart.byteranges") 0224 || message.contentType == SL("application/vnd.wap.multipart.alternative") || message.contentType == SL("application/vnd.wap.multipart.related")) { 0225 decodeMessageBody(message, data, pos); 0226 } 0227 0228 // remove if successfully decoded or if is an outgoing message 0229 if (!message.messageId.isEmpty() || !message.ownNumber.isValid() || PhoneNumber(message.from) == message.ownNumber) { 0230 QFile file(pathTemp); 0231 file.remove(); 0232 } 0233 } 0234 0235 void Mms::encodeMessage(MmsMessage &message, QByteArray &data, const QStringList &files, qint64 totalSize) 0236 { 0237 auto settings = SettingsManager::self(); 0238 int sizeLimit = settings->totalMaxAttachmentSize() * 1024; 0239 int parts = files.count() + (message.text.isEmpty() ? 0 : 1); 0240 0241 data.append(encodeHeaderPrefix(SL("m-send-req"), generateTransactionId())); 0242 0243 data.append(MMS_HEADER_DATE); 0244 data.append(encodeLongInteger(QDateTime::currentSecsSinceEpoch())); // optional 0245 0246 data.append(MMS_HEADER_FROM); 0247 data.append(encodeFromValue(message.from.remove(SL("-")).remove(SL(" ")) + SL("/TYPE=PLMN"))); 0248 message.from.clear(); 0249 0250 for (auto &number : message.to) { 0251 data.append(MMS_HEADER_TO); 0252 data.append(encodeEncodedStringValue(number.remove(SL("-")).remove(SL(" ")) + SL("/TYPE=PLMN"))); 0253 } 0254 message.to.clear(); 0255 0256 // TODO add message setting for this 0257 // data.append(MMS_HEADER_SENDER_VISIBILITY); 0258 // data.append(MMS_CODE_YES); 0259 0260 // TODO add message setting for this 0261 // data.append(MMS_HEADER_SUBJECT); 0262 // data.append(encodeEncodedStringValue(SL("NoSubject"))); 0263 0264 // TODO add message setting for this 0265 data.append(MMS_HEADER_MESSAGE_CLASS); 0266 data.append(MMS_CODE_MESSAGE_CLASS_PERSONAL); 0267 0268 // TODO add message setting for this 0269 // data.append(MMS_HEADER_PRIORITY); 0270 // data.append(MMS_CODE_PRIORITY_NORMAL); 0271 0272 // TODO add message setting for this 0273 // data.append(MMS_HEADER_DELIVERY_TIME); 0274 // data.append(encodeLongInteger(QDateTime::currentSecsSinceEpoch() + delaySeconds)); 0275 0276 data.append(MMS_HEADER_READ_REPORT); 0277 data.append((settings->requestReadReports() ? MMS_CODE_YES : MMS_CODE_NO)); 0278 0279 data.append(MMS_HEADER_DELIVERY_REPORT); 0280 data.append((settings->requestDeliveryReports() ? MMS_CODE_YES : MMS_CODE_NO)); 0281 0282 data.append(MMS_HEADER_CONTENT_TYPE); 0283 if (parts > 1 && settings->autoCreateSmil()) { 0284 data.append(0x1B); 0285 data.append(MMS_CODE_MULTIPART_RELATED); 0286 0287 // add param fields 0288 data.append(MMS_PARAM_START); 0289 data.append(encodeTextValue(SL("<smil>"))); // start 0290 data.append(MMS_PARAM_TYPE_SPEC); 0291 data.append(encodeTextValue(SL("application/smil"))); 0292 0293 parts++; // for the added smil part 0294 } else { 0295 data.append(MMS_CODE_MULTIPART_MIXED); 0296 } 0297 0298 // message body parts 0299 data.append(encodeUnsignedInt(parts)); 0300 0301 int smilInsertPos = data.length(); 0302 QString smil; 0303 0304 for (const auto &path : files) { 0305 QFile file(path.mid(6, path.length() - 6)); // remove "file://" prefix 0306 file.open(QIODevice::ReadOnly); 0307 QByteArray fileData = file.readAll(); 0308 QString name = path.mid(path.lastIndexOf(SL("/")) + 1); 0309 QMimeDatabase db; 0310 QString mimeType = db.mimeTypeForData(fileData).name(); 0311 0312 /* 0313 * WSP Content Type codes do not have numeric identifiers for the 0314 * following media types. Use alternate supported media types instead. 0315 */ 0316 if (mimeType.toLower() == SL("text/vcard")) { 0317 mimeType = SL("text/x-vCard"); 0318 } else if (mimeType.toLower() == SL("text/calendar")) { 0319 mimeType = SL("text/x-vCalendar"); 0320 } 0321 0322 // resize/transcode if total size exceeds max size 0323 if (totalSize > sizeLimit) { 0324 if (mimeType.contains(SL("image")) && mimeType != SL("image/gif")) { 0325 name = SL("Resized_") + name; 0326 QImage image, imageModified; 0327 0328 // conversion to jpg should reduce the size enough in most cases 0329 if (mimeType != SL("image/jpeg")) { 0330 totalSize -= fileData.length(); 0331 imageModified.loadFromData(fileData); 0332 fileData.clear(); 0333 QBuffer buffer(&fileData); 0334 buffer.open(QIODevice::WriteOnly); 0335 imageModified.save(&buffer, "JPG"); 0336 totalSize += fileData.length(); 0337 0338 // update extension and mimeType 0339 int extPos = name.lastIndexOf(SL(".")); 0340 name = name.remove(extPos, name.length() - extPos) + SL(".jpg"); 0341 mimeType = SL("image/jpeg"); 0342 } 0343 0344 const float ratio = (float)fileData.length() / totalSize; 0345 while (fileData.length() * ratio > sizeLimit * ratio / files.count()) { 0346 image.loadFromData(fileData); 0347 float factor = (float)fileData.length() / sizeLimit > 8 ? 2 : 1.1; 0348 imageModified = image.scaled(image.width() / factor, image.height() / factor, Qt::KeepAspectRatio, Qt::SmoothTransformation); 0349 0350 fileData.clear(); 0351 QBuffer buffer(&fileData); 0352 buffer.open(QIODevice::WriteOnly); 0353 imageModified.save(&buffer, "JPG"); 0354 } 0355 } 0356 // TODO investigate if feasible to convert/transcode audio? 0357 } 0358 0359 // shorten name and preserve extension 0360 if (name.length() > 27) { 0361 const int extPos = name.lastIndexOf(SL(".")); 0362 const int extLen = name.length() - extPos; 0363 if (extLen < 5) { 0364 name = name.left(27 - extLen - 1) + name.right(extLen); 0365 } else { 0366 name = name.left(27); 0367 } 0368 } 0369 0370 data.append(encodePart(encodeValueFromList(mimeType, CONTENT_TYPES), name, fileData)); 0371 0372 if (parts > 1 && settings->autoCreateSmil()) { 0373 smil.append(SL(R"(<par dur="5000ms">)")); 0374 0375 if (mimeType.contains(SL("image"))) { 0376 smil.append(SL(R"(<img src=")")); 0377 smil.append(name); 0378 smil.append(SL(R"(" region="Image" />)")); 0379 } 0380 0381 smil.append(SL(R"(</par>)")); 0382 } 0383 } 0384 0385 // add text as one of the parts 0386 if (!message.text.isEmpty()) { 0387 const QString name = SL("text01.txt"); 0388 data.append(encodePart(encodeValueFromList(SL("text/plain"), CONTENT_TYPES), name, message.text.toUtf8())); 0389 message.text.clear(); 0390 0391 if (parts > 1 && settings->autoCreateSmil()) { 0392 smil.remove(smil.length() - 6, 6); 0393 smil.append(SL(R"(<text src=")")); 0394 smil.append(name); 0395 smil.append(SL(R"(" region="Text" />)")); 0396 smil.append(SL(R"(</par>)")); 0397 } 0398 } 0399 0400 if (parts > 1 && settings->autoCreateSmil()) { 0401 // standard layout - image and text, other files currently not included/supported 0402 smil.prepend(SL( 0403 R"(<smil><head><layout><root-layout/><region id="Image" fit="meet" top="0" left="0" height="80%" width="100%"/><region id="Text" top="80%" left="0" height="20%" width="100%"/></layout></head><body>)")); 0404 0405 smil.append(SL(R"(</body></smil>)")); 0406 0407 const QString name = SL("smil.xml"); 0408 QByteArray params; 0409 params.append((unsigned char)0xC0); 0410 params.append(R"(")"); 0411 params.append(encodeTextValue(SL("<smil>"))); 0412 params.append((unsigned char)0x8E); 0413 params.append(encodeTextValue(SL("smil.xml"))); 0414 QByteArray smilData = encodePart(encodeTextValue(SL("application/smil")), name, smil.toUtf8(), params); 0415 data.insert(smilInsertPos, smilData); 0416 } 0417 0418 // decode message here so attachments get formatted the same as a message being sent 0419 decodeMessage(message, data); 0420 } 0421 0422 bool Mms::decodeHeader(MmsMessage &message, const QByteArray &data, int &pos) 0423 { 0424 if ((pos + 1) >= data.length()) { 0425 return false; 0426 } 0427 0428 unsigned char field = data.at(++pos); 0429 for (const auto &headerField : HEADER_FIELDS) { 0430 if (headerField.id == field) { 0431 QVariant val; 0432 QStringView type = headerField.type; 0433 if (type == u"") 0434 val = data.at(++pos); 0435 else if (type == u"unsignedInt") 0436 val = unsignedInt(data, pos); 0437 else if (type == u"longInteger") 0438 val = longInteger(data, pos); 0439 else if (type == u"shortInteger") 0440 val = shortInteger(data, pos); 0441 else if (type == u"integerValue") 0442 val = integerValue(data, pos); 0443 else if (type == u"textString") 0444 val = textString(data, pos); 0445 else if (type == u"encodedStringValue") 0446 val = encodedStringValue(data, pos); 0447 else if (type == u"fromValue") 0448 val = fromValue(data, pos); 0449 else if (type == u"contentTypeValue") 0450 val = contentTypeValue(data, pos, message); 0451 else if (type == u"messageTypeValue") 0452 val = messageTypeValue(data, pos); 0453 else if (type == u"dateValue") 0454 val = dateValue(data, pos); 0455 else if (type == u"messageClassValue") 0456 val = messageClassValue(data, pos); 0457 else if (type == u"mmsVersion") 0458 val = mmsVersion(data, pos); 0459 0460 // capture relevant values 0461 QStringView name = headerField.name; 0462 if (name == u"messageType") 0463 message.messageType = val.toString(); 0464 else if (name == u"transactionId") 0465 message.transactionId = val.toString(); 0466 else if (name == u"messageId") 0467 message.messageId = val.toString(); 0468 else if (name == u"messageSize") 0469 message.messageSize = val.toInt(); 0470 else if (name == u"expiry") 0471 message.expiry = val.toDateTime(); 0472 else if (name == u"date") 0473 message.date = val.toDateTime(); 0474 else if (name == u"to") 0475 message.to.append(PhoneNumber(val.toString().remove(QLatin1String("/TYPE=PLMN"))).toInternational()); 0476 else if (name == u"from") 0477 message.from = PhoneNumber(val.toString().remove(QLatin1String("/TYPE=PLMN"))).toInternational(); 0478 else if (name == u"subject") 0479 message.subject = val.toString(); 0480 else if (name == u"contentType") 0481 message.contentType = val.toString(); 0482 else if (name == u"contentLocation") 0483 message.contentLocation = val.toString(); 0484 else if (name == u"responseStatus") 0485 message.responseStatus = val.toInt(); 0486 else if (name == u"responseText") 0487 message.responseText = val.toString(); 0488 // else qDebug() << headerField.name << ":" << val.toString(); 0489 0490 return true; 0491 } 0492 } 0493 0494 pos--; 0495 return false; 0496 } 0497 0498 QByteArray Mms::encodeHeaderPrefix(const QString &type, const QString &id, bool msgId) 0499 { 0500 QByteArray data; 0501 data.append(MMS_HEADER_MESSAGE_TYPE); 0502 data.append(encodeValueFromList(type, MESSAGE_TYPES)); 0503 0504 data.append(msgId ? MMS_HEADER_MESSAGE_ID : MMS_HEADER_TRANSACTION_ID); 0505 data.append(encodeTextValue(id)); 0506 0507 data.append(MMS_HEADER_VERSION); 0508 data.append(MMS_CODE_VERSION); 0509 0510 return data; 0511 } 0512 0513 bool Mms::decodeMessageBody(MmsMessage &message, const QByteArray &data, int &pos) 0514 { 0515 if ((pos + 1) >= data.length()) { 0516 return false; 0517 } 0518 0519 int parts = unsignedInt(data, pos); 0520 0521 // allow extra 2 parts for text and smil 0522 if (parts > SettingsManager::self()->maxAttachments() + 2) { 0523 qDebug() << "Max attachments exceeded! Parts:" << parts; 0524 return false; 0525 } 0526 0527 const QString folder = QString::number(hash(message.phoneNumberList.toString())); 0528 const QString attachmentsFolder = saveLocation(SL("attachments"), folder); 0529 0530 QJsonArray attachments; 0531 0532 // the body header should not be confused with the actual mms header 0533 for (int i = 0; i < parts; i++) { 0534 int headerLen = unsignedInt(data, pos); 0535 int dataLen = unsignedInt(data, pos); 0536 0537 int temp = pos; 0538 QString contentType = contentTypeValue(data, pos, message); 0539 pos = temp + 1; 0540 0541 QByteArray fileData = data.mid(pos + headerLen, dataLen); 0542 pos += headerLen + dataLen - 1; 0543 0544 if (contentType == SL("application/smil")) { 0545 message.smil += QString::fromUtf8(fileData); 0546 continue; 0547 } else { 0548 QMimeDatabase db; 0549 QMimeType mime = db.mimeTypeForData(fileData); 0550 QString fileName; 0551 QString path; 0552 QString text; 0553 QString name; 0554 0555 if (contentType == SL("text/plain")) { 0556 if (parts == 1 || (parts == 2 && !message.smil.isEmpty())) { 0557 // no other content so just treat like regular text 0558 message.text.append(QString::fromUtf8(fileData)); 0559 if (parts == 2) { 0560 message.smil.clear(); 0561 } 0562 continue; 0563 } else { 0564 text = QString::fromUtf8(fileData); 0565 } 0566 } else { 0567 // save file 0568 fileName = generateRandomId() + SL(".") + mime.preferredSuffix(); 0569 path = attachmentsFolder + SL("/") + fileName; 0570 saveData(fileData, path); 0571 } 0572 0573 name = message.partNames.length() > i ? message.partNames[i] : fileName; 0574 0575 // json data for use in message page 0576 QJsonObject object{ 0577 {SL("name"), name}, 0578 {SL("fileName"), fileName}, 0579 {SL("size"), dataLen}, 0580 {SL("mimeType"), mime.name()}, 0581 {SL("iconName"), mime.iconName()}, 0582 {SL("text"), text}, 0583 }; 0584 0585 attachments.append(object); 0586 } 0587 } 0588 0589 if (attachments.size() > 0) { 0590 QJsonDocument jsonDoc; 0591 jsonDoc.setArray(attachments); 0592 QByteArray fileText = jsonDoc.toJson(QJsonDocument::Compact); 0593 message.attachments = QString::fromUtf8(fileText); 0594 } 0595 0596 return false; 0597 } 0598 0599 QString Mms::generateRandomId() 0600 { 0601 QString intermediateId = SL("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); 0602 std::shuffle(intermediateId.begin(), intermediateId.end(), std::mt19937(std::random_device()())); 0603 intermediateId.truncate(15); 0604 0605 return intermediateId; 0606 } 0607 0608 QString Mms::generateTransactionId() 0609 { 0610 return SL("m-") + QUuid::createUuid().toString(QUuid::WithoutBraces); 0611 } 0612 0613 QString Mms::saveLocation(const QString &folderName, const QString &subFolderName) 0614 { 0615 QString fileLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); 0616 fileLocation.append(SL("/spacebar/") + folderName); 0617 if (!subFolderName.isEmpty()) { 0618 fileLocation.append(SL("/") + subFolderName); 0619 } 0620 0621 if (!QDir().mkpath(fileLocation)) { 0622 qDebug() << "Could not create the storage directory at" << fileLocation; 0623 } 0624 0625 return fileLocation; 0626 } 0627 0628 bool Mms::saveData(const QByteArray &data, const QString &path) 0629 { 0630 QFile file(path); 0631 file.open(QIODevice::WriteOnly); 0632 file.write(data); 0633 file.close(); 0634 0635 return true; 0636 } 0637 0638 QString Mms::contentTypeValue(const QByteArray &data, int &pos, MmsMessage &message) 0639 { 0640 QString type; 0641 if (data.at(pos + 1) & 0x80) { 0642 type = lookupValString(data, pos, CONTENT_TYPES); 0643 } else if (data.at(pos + 1) == 0 || data.at(pos + 1) >= 0x20) { 0644 type = textString(data, pos); 0645 } else { 0646 int len = valueLength(data, pos); 0647 int end = pos + len; 0648 0649 // check for extension-media, next byte is between 32-127 0650 if (data.at(pos + 1) == 0 || (data.at(pos + 1) >= 32 && (unsigned char)data.at(pos + 1) <= 127)) { 0651 type = encodedStringValue(data, pos); 0652 } else if ((data.at(pos + 1) & 0x80) || data.at(pos + 1) <= 30) { 0653 type = lookupValString(data, pos, CONTENT_TYPES); 0654 } 0655 0656 while (pos < end) { 0657 unsigned char field = data.at(++pos) & 0x7F; 0658 int len = PARAM_FIELDS.size(); 0659 for (int i = 0; i < len; i++) { 0660 if (PARAM_FIELDS[i].id == field) { 0661 QVariant val; 0662 QStringView type = PARAM_FIELDS[i].type; 0663 if (type == u"unsignedInt") 0664 val = unsignedInt(data, pos); 0665 else if (type == u"longInteger") 0666 val = longInteger(data, pos); 0667 else if (type == u"shortInteger") 0668 val = shortInteger(data, pos); 0669 else if (type == u"integerValue") 0670 val = integerValue(data, pos); 0671 else if (type == u"textString") 0672 val = textString(data, pos); 0673 else if (type == u"encodedStringValue") 0674 val = encodedStringValue(data, pos); 0675 else if (type == u"fromValue") 0676 val = fromValue(data, pos); 0677 else 0678 val = data.at(++pos); 0679 0680 // store relevant values 0681 QStringView name = PARAM_FIELDS[i].name; 0682 if (name == u"name") 0683 message.partNames.append(val.toString()); 0684 // else qDebug() << param_fields[i].name << ":" << val.toString(); 0685 0686 i = len; 0687 } 0688 } 0689 } 0690 } 0691 0692 return type; 0693 } 0694 0695 QString Mms::messageTypeValue(const QByteArray &data, int &pos) 0696 { 0697 return lookupValString(data, pos, MESSAGE_TYPES); 0698 } 0699 0700 unsigned int Mms::unsignedInt(const QByteArray &data, int &pos) 0701 { 0702 unsigned int unint = 0; 0703 unsigned int octet; 0704 char cont = 1; 0705 0706 while (cont != 0) { 0707 unint = unint << 7; 0708 octet = data.at(++pos); 0709 unint += (octet & 0x7F); 0710 cont = (octet & 0x80); 0711 } 0712 0713 return unint; 0714 } 0715 0716 quint64 Mms::longInteger(const QByteArray &data, int &pos) 0717 { 0718 int octetcount = data.at(++pos); 0719 0720 if (octetcount > 30) { 0721 return 0; 0722 } 0723 0724 quint64 value = 0; 0725 for (int i = 0; i < octetcount; i++) { 0726 value = value << 8; 0727 value += (unsigned char)data.at(++pos); 0728 } 0729 0730 return value; 0731 } 0732 0733 int Mms::shortInteger(const QByteArray &data, int &pos) 0734 { 0735 return data.at(++pos) & 0x7F; 0736 } 0737 0738 int Mms::integerValue(const QByteArray &data, int &pos) 0739 { 0740 if (data.at(pos + 1) < 31) { 0741 return longInteger(data, pos); 0742 } else if ((unsigned char)data.at(pos + 1) > 127) { 0743 return shortInteger(data, pos); 0744 } else { 0745 pos++; 0746 qDebug() << "parse int error"; 0747 return 0; 0748 } 0749 } 0750 0751 int Mms::valueLength(const QByteArray &data, int &pos) 0752 { 0753 if (data.at(pos + 1) < 31) { 0754 return data.at(++pos); 0755 } else if (data.at(pos + 1) == 31) { 0756 // length is an Uint 0757 pos++; 0758 return unsignedInt(data, pos); 0759 } else { 0760 qDebug() << "parse error"; 0761 return 0; 0762 } 0763 } 0764 0765 QString Mms::textString(const QByteArray &data, int &pos, int len) 0766 { 0767 int length = 0, start = pos + 1; 0768 int size = len >= 0 ? len + pos + 0 : data.length(); 0769 while ((pos + 1) < size && data.at(++pos)) 0770 ++length; 0771 0772 QByteArray text = data.mid(start, length); 0773 return QString::fromUtf8(text); 0774 } 0775 0776 QString Mms::encodedStringValue(const QByteArray &data, int &pos) 0777 { 0778 if (data.at(pos + 1) <= 31) { 0779 int len = valueLength(data, pos); 0780 pos++; 0781 return textString(data, pos, len); 0782 } else { 0783 return textString(data, pos); 0784 } 0785 } 0786 0787 QString Mms::fromValue(const QByteArray &data, int &pos) 0788 { 0789 int len = valueLength(data, pos); 0790 0791 if ((unsigned char)data.at(pos + 1) == MMS_CODE_ADDRESS_PRESENT_TOKEN) { 0792 pos++; 0793 return encodedStringValue(data, pos); 0794 } else if ((unsigned char)data.at(pos + 1) == MMS_CODE_INSERT_ADDRESS_TOKEN) { 0795 pos++; 0796 return QString(); 0797 } else { 0798 qDebug() << "No from token found"; 0799 pos += (len == 0 ? 1 : (len & 0x7F)); 0800 return QString(); 0801 } 0802 } 0803 0804 QDateTime Mms::dateValue(const QByteArray &data, int &pos) 0805 { 0806 int len = data.at(++pos); 0807 quint64 value = 0; 0808 0809 if ((unsigned char)data.at(pos + 1) == MMS_CODE_RELATIVE_TOKEN) { 0810 // relative token 0811 pos++; 0812 value = integerValue(data, pos); 0813 value = QDateTime::currentSecsSinceEpoch() + value; 0814 } else { 0815 int end = pos + len; 0816 // absolute token 0817 if ((unsigned char)data.at(pos + 1) == MMS_CODE_ABSOLUTE_TOKEN) { 0818 pos++; 0819 } 0820 while (pos < end) { 0821 value = value << 8; 0822 value += data.at(++pos); 0823 } 0824 } 0825 0826 return QDateTime::fromSecsSinceEpoch(value); 0827 } 0828 0829 QString Mms::messageClassValue(const QByteArray &data, int &pos) 0830 { 0831 unsigned char value = data.at(++pos); 0832 if (value > 127 && value < 132) { 0833 // 128=personal (default), 129=advertisement, 130=informational, 131=auto 0834 return QString::number(value); 0835 } else { 0836 qDebug() << "Token-text"; 0837 QVector<unsigned char> separators = {11, 32, 40, 41, 44, 47, 58, 59, 60, 61, 62, 63, 64, 91, 92, 93, 123, 125}; 0838 0839 QByteArray token; 0840 while (value > 31 && !separators.contains(value)) { 0841 token.append(value); 0842 value = data.at(++pos); 0843 } 0844 0845 return QString::fromUtf8(token); 0846 } 0847 } 0848 0849 QString Mms::mmsVersion(const QByteArray &data, int &pos) 0850 { 0851 QString major = QString::number((data.at(pos + 1) & 0x70) >> 4); 0852 QString minor = QString::number(data.at(++pos) & 0x0F); 0853 return major + SL(".") + minor; 0854 } 0855 0856 QString Mms::lookupValString(const QByteArray &data, int &pos, std::span<const QStringView> list) 0857 { 0858 return list[shortInteger(data, pos)].toString(); 0859 } 0860 0861 QByteArray Mms::encodeUnsignedInt(unsigned int value) 0862 { 0863 QByteArray bytes; 0864 0865 bytes.append(value & 0x7F); 0866 value = value >> 7; 0867 while (value > 0) { 0868 bytes.prepend(0x80 | (value & 0x7F)); 0869 value = value >> 7; 0870 } 0871 0872 return bytes; 0873 } 0874 0875 QByteArray Mms::encodeLongInteger(quint64 value) 0876 { 0877 QByteArray bytes; 0878 while (value > 0) { 0879 bytes.prepend(value); 0880 value = value >> 8; 0881 } 0882 bytes.prepend(bytes.length()); 0883 0884 return bytes; 0885 } 0886 0887 QByteArray Mms::encodeTextValue(const QString &value) 0888 { 0889 QByteArray bytes; 0890 bytes.append(value.toUtf8()); 0891 bytes.append(MMS_CODE_END_STRING); 0892 0893 return bytes; 0894 } 0895 0896 QByteArray Mms::encodeEncodedStringValue(const QString &value) 0897 { 0898 QByteArray bytes; 0899 bytes.append(value.length() + 2); 0900 bytes.append(MMS_HEADER_CONTENT_LOCATION); 0901 bytes.append(value.toUtf8()); 0902 bytes.append(MMS_CODE_END_STRING); 0903 0904 return bytes; 0905 } 0906 0907 QByteArray Mms::encodeFromValue(const QString &value) 0908 { 0909 QByteArray bytes; 0910 if (value.length() == 0) { 0911 bytes.append(1); 0912 bytes.append(MMS_CODE_INSERT_ADDRESS_TOKEN); 0913 } else { 0914 QByteArray encodedAddress = encodeEncodedStringValue(value); 0915 bytes.append(encodedAddress.length() + 1); 0916 bytes.append(MMS_CODE_ADDRESS_PRESENT_TOKEN); 0917 bytes.append(encodedAddress); 0918 } 0919 0920 return bytes; 0921 } 0922 0923 QByteArray Mms::encodeValueFromList(const QString &value, std::span<const QStringView> list) 0924 { 0925 QByteArray type; 0926 int index = list.size(); 0927 while (index > 0) { 0928 index--; 0929 if (list[index] == value || index == 0) { 0930 type.append(index + 128); 0931 break; 0932 } 0933 } 0934 0935 return type; 0936 } 0937 0938 QByteArray Mms::encodePart(const QByteArray &type, const QString &name, const QByteArray &body, const QByteArray ¶ms) 0939 { 0940 QByteArray header; 0941 header.append(type); 0942 header.append(MMS_PARAM_NAME); 0943 header.append(encodeTextValue(name)); 0944 header.prepend(encodeUnsignedInt(header.length())); 0945 header.append(params); 0946 0947 QByteArray data; 0948 data.append(encodeUnsignedInt(header.length())); 0949 data.append(encodeUnsignedInt(body.length())); 0950 data.append(header); 0951 data.append(body); 0952 0953 return data; 0954 }