lang_file_parser.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "lang/lang_file_parser.h"
  8. #include "base/parse_helper.h"
  9. #include "base/debug_log.h"
  10. #include <QtCore/QTextStream>
  11. #include <QtCore/QFile>
  12. #include <QtCore/QFileInfo>
  13. namespace Lang {
  14. namespace {
  15. constexpr auto kLangFileLimit = 1024 * 1024;
  16. } // namespace
  17. FileParser::FileParser(const QString &file, const std::set<ushort> &request)
  18. : _content(base::parse::stripComments(ReadFile(file, file)))
  19. , _request(request) {
  20. parse();
  21. }
  22. FileParser::FileParser(const QByteArray &content, Fn<void(QLatin1String key, const QByteArray &value)> callback)
  23. : _content(base::parse::stripComments(content))
  24. , _callback(std::move(callback)) {
  25. parse();
  26. }
  27. void FileParser::parse() {
  28. if (_content.isEmpty()) {
  29. error(u"Got empty lang file content"_q);
  30. return;
  31. }
  32. auto text = _content.constData(), end = text + _content.size();
  33. while (text != end) {
  34. if (!readKeyValue(text, end)) {
  35. break;
  36. }
  37. }
  38. }
  39. const QString &FileParser::errors() const {
  40. if (_errors.isEmpty() && !_errorsList.isEmpty()) {
  41. _errors = _errorsList.join('\n');
  42. }
  43. return _errors;
  44. }
  45. const QString &FileParser::warnings() const {
  46. if (_warnings.isEmpty() && !_warningsList.isEmpty()) {
  47. _warnings = _warningsList.join('\n');
  48. }
  49. return _warnings;
  50. }
  51. bool FileParser::readKeyValue(const char *&from, const char *end) {
  52. using base::parse::skipWhitespaces;
  53. if (!skipWhitespaces(from, end)) return false;
  54. if (*from != '"') {
  55. return error("Expected quote before key name!");
  56. }
  57. ++from;
  58. const char *nameStart = from;
  59. while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9') || *from == '#')) {
  60. ++from;
  61. }
  62. auto key = QLatin1String(nameStart, from - nameStart);
  63. if (from == end || *from != '"') {
  64. return error(u"Expected quote after key name '%1'!"_q.arg(key));
  65. }
  66. ++from;
  67. if (!skipWhitespaces(from, end)) {
  68. return error(u"Unexpected end of file in key '%1'!"_q.arg(key));
  69. }
  70. if (*from != '=') {
  71. return error(u"'=' expected in key '%1'!"_q.arg(key));
  72. }
  73. if (!skipWhitespaces(++from, end)) {
  74. return error(u"Unexpected end of file in key '%1'!"_q.arg(key));
  75. }
  76. if (*from != '"') {
  77. return error(u"Expected string after '=' in key '%1'!"_q.arg(key));
  78. }
  79. auto skipping = false;
  80. auto keyIndex = kKeysCount;
  81. if (!_callback) {
  82. keyIndex = GetKeyIndex(key);
  83. skipping = (_request.find(keyIndex) == _request.end());
  84. }
  85. auto value = QByteArray();
  86. auto appendValue = [&value, skipping](auto&&... args) {
  87. if (!skipping) {
  88. value.append(std::forward<decltype(args)>(args)...);
  89. }
  90. };
  91. const char *start = ++from;
  92. while (from < end && *from != '"') {
  93. if (*from == '\n') {
  94. return error(u"Unexpected end of string in key '%1'!"_q.arg(key));
  95. }
  96. if (*from == '\\') {
  97. if (from + 1 >= end) {
  98. return error(u"Unexpected end of file in key '%1'!"_q.arg(key));
  99. }
  100. if (*(from + 1) == '"' || *(from + 1) == '\\') {
  101. if (from > start) appendValue(start, from - start);
  102. start = ++from;
  103. } else if (*(from + 1) == 'n') {
  104. if (from > start) appendValue(start, from - start);
  105. appendValue('\n');
  106. start = (++from) + 1;
  107. }
  108. }
  109. ++from;
  110. }
  111. if (from >= end) {
  112. return error(u"Unexpected end of file in key '%1'!"_q.arg(key));
  113. }
  114. if (from > start) {
  115. appendValue(start, from - start);
  116. }
  117. if (!skipWhitespaces(++from, end)) {
  118. return error(u"Unexpected end of file in key '%1'!"_q.arg(key));
  119. }
  120. if (*from != ';') {
  121. return error(u"';' expected after \"value\" in key '%1'!"_q.arg(key));
  122. }
  123. skipWhitespaces(++from, end);
  124. if (_callback) {
  125. _callback(key, value);
  126. } else if (!skipping) {
  127. _result.insert(keyIndex, QString::fromUtf8(value));
  128. }
  129. return true;
  130. }
  131. QByteArray FileParser::ReadFile(const QString &absolutePath, const QString &relativePath) {
  132. QFile file(QFileInfo::exists(relativePath) ? relativePath : absolutePath);
  133. if (!file.open(QIODevice::ReadOnly)) {
  134. LOG(("Lang Error: Could not open file at '%1' ('%2')")
  135. .arg(relativePath, absolutePath));
  136. return QByteArray();
  137. }
  138. if (file.size() > kLangFileLimit) {
  139. LOG(("Lang Error: File is too big: %1").arg(file.size()));
  140. return QByteArray();
  141. }
  142. constexpr auto kCodecMagicSize = 3;
  143. auto codecMagic = file.read(kCodecMagicSize);
  144. if (codecMagic.size() < kCodecMagicSize) {
  145. LOG(("Lang Error: Found bad file at '%1' ('%2')").arg(relativePath, absolutePath));
  146. return QByteArray();
  147. }
  148. file.seek(0);
  149. QByteArray data;
  150. auto readUtf16Stream = [relativePath, absolutePath](auto &&stream) {
  151. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  152. stream.setEncoding(QStringConverter::Utf16);
  153. #else // Qt >= 6.0.0
  154. stream.setCodec("UTF-16");
  155. #endif // Qt < 6.0.0
  156. auto string = stream.readAll();
  157. if (stream.status() != QTextStream::Ok) {
  158. LOG(("Lang Error: Could not read UTF-16 data from '%1' ('%2')").arg(relativePath, absolutePath));
  159. return QByteArray();
  160. }
  161. if (string.isEmpty()) {
  162. LOG(("Lang Error: Empty UTF-16 content in '%1' ('%2')").arg(relativePath, absolutePath));
  163. return QByteArray();
  164. }
  165. return string.toUtf8();
  166. };
  167. if ((codecMagic.at(0) == '\xFF' && codecMagic.at(1) == '\xFE') || (codecMagic.at(0) == '\xFE' && codecMagic.at(1) == '\xFF') || (codecMagic.at(1) == 0)) {
  168. return readUtf16Stream(QTextStream(&file));
  169. } else if (codecMagic.at(0) == 0) {
  170. auto utf16WithBOM = "\xFE\xFF" + file.readAll();
  171. return readUtf16Stream(QTextStream(utf16WithBOM));
  172. }
  173. data = file.readAll();
  174. if (codecMagic.at(0) == '\xEF' && codecMagic.at(1) == '\xBB' && codecMagic.at(2) == '\xBF') {
  175. data = data.mid(3); // skip UTF-8 BOM
  176. }
  177. if (data.isEmpty()) {
  178. LOG(("Lang Error: Empty UTF-8 content in '%1' ('%2')").arg(relativePath, absolutePath));
  179. return QByteArray();
  180. }
  181. return data;
  182. }
  183. } // namespace Lang