| Index: compiled/subscription/DownloadableSubscription.cpp |
| =================================================================== |
| --- a/compiled/subscription/DownloadableSubscription.cpp |
| +++ b/compiled/subscription/DownloadableSubscription.cpp |
| @@ -81,59 +81,133 @@ |
| text, beginValue, text.length() - beginValue); |
| } |
| } |
| return param; |
| } |
| } |
| DownloadableSubscription_Parser::DownloadableSubscription_Parser() |
| - : mFirstLine(true) |
| { |
| annotate_address(this, "DownloadableSubscription_Parser"); |
| } |
| namespace { |
| const DependentString ADBLOCK_HEADER(u"[Adblock"_str); |
| + const DependentString ADBLOCK_PLUS_EXTRA_HEADER(u"Plus"_str); |
| + |
| + const DependentString ERROR_INVALID_DATA(u"synchronize_invalid_data"_str); |
| // Only check for trailing base64 padding. There should be at most 2 '='. |
| // In that case return a truncated string. |
| DependentString CleanUpChecksum(const String& checksum) |
| { |
| const auto len = checksum.length(); |
| if ((len > 22 && len <= 24) && |
| (checksum[22] == u'=' && (len == 23 || checksum[23] == u'='))) |
| return DependentString(checksum, 0, 22); |
| return DependentString(checksum); |
| } |
| } |
| -void DownloadableSubscription_Parser::Process(const String& line) |
| +/// Return true if more line expected. |
| +bool DownloadableSubscription_Parser::GetNextLine(DependentString& buffer, DependentString& line) |
| { |
| - bool isHeader = false; |
| - isHeader = line.find(ADBLOCK_HEADER) != String::npos; |
| - if (!isHeader) |
| + StringScanner scanner(buffer); |
| + String::value_type ch = 0; |
| + while (ch != u'\r' && ch != u'\n') |
| { |
| - auto param = ParseParam(line); |
| - if (param.first.is_invalid()) |
| - mFiltersText.emplace_back(line); |
| - else if (param.first == u"checksum"_str) |
| + ch = scanner.next(); |
| + if (ch == 0) |
| + break; |
| + } |
| + |
| + auto eol = scanner.position(); |
| + line.reset(buffer, 0, eol); |
| + if (eol == 0 || ch == 0) |
| + return false; |
| + while (scanner.skipOne(u'\r') || scanner.skipOne(u'\n')) |
| + ; |
| + buffer.reset(buffer, scanner.position() + 1); |
| + return true; |
| +} |
| + |
| +bool DownloadableSubscription_Parser::Process(const String& buffer) |
| +{ |
| + DependentString currentBuffer(buffer); |
| + bool firstLine = true; |
| + |
| + DependentString line; |
| + while (true) |
| + { |
| + bool more = GetNextLine(currentBuffer, line); |
| + if (firstLine) |
| { |
| - mParams[param.first] = CleanUpChecksum(param.second); |
| - return; |
| + if (!ProcessFirstLine(line)) |
| + { |
| + mError = ERROR_INVALID_DATA; |
| + return false; |
| + } |
| + firstLine = false; |
| } |
| else |
| - mParams[param.first] = param.second; |
| + ProcessLine(line); |
| + if (!more) |
| + break; |
| } |
| + return true; |
| +} |
| + |
| +bool DownloadableSubscription_Parser::ProcessFirstLine(const String& line) |
| +{ |
| + auto index = line.find(ADBLOCK_HEADER); |
| + if (index == String::npos) |
| + return false; |
| + |
| + DependentString minVersion; |
| + DependentString current(line, index + ADBLOCK_HEADER.length()); |
| + StringScanner scanner(current); |
| + if (scanner.skipWhiteSpace() && scanner.skipString(ADBLOCK_PLUS_EXTRA_HEADER)) |
| + scanner.skipWhiteSpace(); |
| + index = scanner.position() + 1; |
| + String::value_type ch = u'\0'; |
| + while((ch = scanner.next()) && (ch == u'.' || std::iswdigit(ch))) |
| + ; |
| + if (ch) |
| + scanner.back(); |
| + if (scanner.position() + 1 > index) |
| + minVersion.reset(current, index, scanner.position() + 1 - index); |
| + |
| + if (ch != u']') |
| + return false; |
| + |
| + mRequiredVersion = minVersion; |
| + mChecksum.Update(line); |
| + return true; |
| +} |
| + |
| +void DownloadableSubscription_Parser::ProcessLine(const String& line) |
| +{ |
| + auto param = ParseParam(line); |
| + if (param.first.is_invalid()) |
| + { |
| + if (!line.empty()) |
| + mFiltersText.emplace_back(line); |
| + } |
| + else if (param.first == u"checksum"_str) |
| + { |
| + mParams[param.first] = CleanUpChecksum(param.second); |
| + return; |
| + } |
| + else |
| + mParams[param.first] = param.second; |
| + |
| // Checksum is an MD5 checksum (base64-encoded without the trailing "=") of |
| // all lines in UTF-8 without the checksum line, joined with "\n". |
| - if (!mFirstLine) |
| - mChecksum.Update((const uint8_t*)"\n", 1); |
| - else |
| - mFirstLine = false; |
| + mChecksum.Update((const uint8_t*)"\n", 1); |
| mChecksum.Update(line); |
| } |
| int64_t DownloadableSubscription_Parser::ParseExpires(const String& expires) |
| { |
| bool isHour = false; |
| StringScanner scanner(expires); |
| String::size_type numStart = 0; |
| @@ -199,16 +273,23 @@ |
| return (mParams[u"checksum"_str] == mB64Checksum); |
| } |
| int64_t DownloadableSubscription_Parser::Finalize(DownloadableSubscription& subscription) |
| { |
| if (mB64Checksum.is_invalid()) |
| VerifyChecksum(); // here we ignore the checksum, but we calculate it. |
| + FilterNotifier::SubscriptionChange( |
| + FilterNotifier::Topic::SUBSCRIPTION_BEFORE_FILTERS_REPLACED, |
| + subscription); |
| + |
| + if (!mRequiredVersion.empty()) |
| + subscription.SetRequiredVersion(mRequiredVersion); |
| + |
| auto entry = mParams.find(u"title"_str); |
| if (entry) |
| { |
| subscription.SetTitle(entry->second); |
| subscription.SetFixedTitle(true); |
| } |
| else |
| subscription.SetFixedTitle(false); |
| @@ -219,20 +300,16 @@ |
| version = entry->second.toInt<int32_t>(); |
| subscription.SetDataRevision(version); |
| int64_t expires = 0; |
| entry = mParams.find(u"expires"_str); |
| if (entry) |
| expires = ParseExpires(entry->second); |
| - FilterNotifier::SubscriptionChange( |
| - FilterNotifier::Topic::SUBSCRIPTION_BEFORE_FILTERS_REPLACED, |
| - subscription); |
| - |
| Subscription::Filters filters; |
| filters.reserve(mFiltersText.size()); |
| for (auto text : mFiltersText) |
| { |
| DependentString dependent(text); |
| filters.emplace_back(Filter::FromText(dependent), false); |
| } |