dates.cc 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "pdf/pdf_utils/dates.h"
  5. #include <stdint.h>
  6. #include "base/strings/string_number_conversions.h"
  7. #include "base/strings/string_piece.h"
  8. #include "base/strings/string_util.h"
  9. #include "base/time/time.h"
  10. #include "third_party/abseil-cpp/absl/types/optional.h"
  11. namespace chrome_pdf {
  12. namespace {
  13. class DateDeserializer final {
  14. public:
  15. // `parsing` must outlive `this` because `base::StringPiece` has reference
  16. // semantics.
  17. explicit DateDeserializer(base::StringPiece parsing)
  18. : deserializing_(parsing) {}
  19. ~DateDeserializer() = default;
  20. // Pops the first `num_digits` characters from the string and converts them to
  21. // an int if possible. Popping too many characters or characters that cannot
  22. // be converted puts the deserializer in a stopped state.
  23. absl::optional<int> PopDigits(size_t num_digits) {
  24. if (stopped_)
  25. return absl::nullopt;
  26. // `base::StringToUint()` allows leading sign characters, so also verify
  27. // that the front character is a digit.
  28. uint32_t value;
  29. if (deserializing_.size() < num_digits ||
  30. !base::IsAsciiDigit(deserializing_.front()) ||
  31. !base::StringToUint(deserializing_.substr(0, num_digits), &value)) {
  32. stopped_ = true;
  33. return absl::nullopt;
  34. }
  35. // Pop front characters.
  36. deserializing_ = deserializing_.substr(num_digits);
  37. return value;
  38. }
  39. // Pops the front character if it is not a digit. Otherwise, does not change
  40. // the state of the deserializer and returns `absl::nullopt`.
  41. absl::optional<char> TryPopNonDigit() {
  42. if (stopped_ || deserializing_.empty())
  43. return absl::nullopt;
  44. const char front = deserializing_.front();
  45. if (base::IsAsciiDigit(front))
  46. return absl::nullopt;
  47. deserializing_ = deserializing_.substr(1);
  48. return front;
  49. }
  50. // Takes the deserializer out of a stopped state.
  51. void unstop() { stopped_ = false; }
  52. private:
  53. base::StringPiece deserializing_;
  54. bool stopped_ = false;
  55. };
  56. // Parses the offset info in `deserializer`, which is the time offset portion of
  57. // the date format provided in section 7.9.4 "Dates" of the ISO 32000-1:2008
  58. // spec. An input is expected to look like "HH'mm", such that "HH" is the hour
  59. // and "mm" is the minute.
  60. base::TimeDelta ParseOffset(DateDeserializer& deserializer) {
  61. base::TimeDelta offset;
  62. // UTC is assumed if no time zone information is provided.
  63. const absl::optional<char> sign = deserializer.TryPopNonDigit();
  64. if (!sign.has_value() || (sign.value() != '+' && sign.value() != '-'))
  65. return offset;
  66. offset += base::Hours(deserializer.PopDigits(2).value_or(0));
  67. // The spec requires that the hours offset be followed by an apostrophe, but
  68. // don't be strict about its presence.
  69. const absl::optional<char> apostrophe = deserializer.TryPopNonDigit();
  70. if (apostrophe.has_value() && apostrophe.value() != '\'')
  71. return sign.value() == '+' ? offset : -offset;
  72. // The minutes offset follows the hours offset. Be lenient about anything
  73. // following the minutes offset. One reason for the leniency is the apostrophe
  74. // following the minues, which is only mentioned in earlier versions of the
  75. // spec.
  76. offset += base::Minutes(deserializer.PopDigits(2).value_or(0));
  77. return sign.value() == '+' ? offset : -offset;
  78. }
  79. } // namespace
  80. base::Time ParsePdfDate(base::StringPiece date) {
  81. // The prefix "D:" is required according to the spec, but don't require it as
  82. // earlier versions of the spec weren't strict about it.
  83. if (date.substr(0, 2) == "D:")
  84. date = date.substr(2);
  85. DateDeserializer deserializer(date);
  86. // Year is the only required part of a valid date.
  87. const absl::optional<int> deserialized_year = deserializer.PopDigits(4);
  88. if (!deserialized_year.has_value())
  89. return base::Time();
  90. // Month and day default to 1. The rest of the parts of a date default to 0.
  91. base::Time::Exploded exploded = {};
  92. exploded.year = deserialized_year.value();
  93. exploded.month = deserializer.PopDigits(2).value_or(1);
  94. exploded.day_of_month = deserializer.PopDigits(2).value_or(1);
  95. exploded.hour = deserializer.PopDigits(2).value_or(0);
  96. exploded.minute = deserializer.PopDigits(2).value_or(0);
  97. exploded.second = deserializer.PopDigits(2).value_or(0);
  98. base::Time parsed;
  99. if (!base::Time::FromUTCExploded(exploded, &parsed))
  100. return base::Time();
  101. // `base::Time` is in UTC, so `parsed` must be normalized if there is an
  102. // offset.
  103. deserializer.unstop();
  104. return parsed - ParseOffset(deserializer);
  105. }
  106. } // namespace chrome_pdf