• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium OS 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 
5 #include "brillo/timezone/tzif_parser.h"
6 
7 #include <arpa/inet.h>
8 #include <stdint.h>
9 #include <string.h>
10 #include <utility>
11 #include <vector>
12 
13 #include <base/files/file.h>
14 #include <base/files/file_path.h>
15 #include <base/logging.h>
16 #include <base/stl_util.h>
17 #include <base/strings/string_util.h>
18 
19 namespace {
20 
21 struct tzif_header {
22   char magic[4];
23   char version;
24   char reserved[15];
25   int32_t ttisgmtcnt;
26   int32_t ttisstdcnt;
27   int32_t leapcnt;
28   int32_t timecnt;
29   int32_t typecnt;
30   int32_t charcnt;
31 };
32 
ReadInt(base::File * file,int32_t * out_int)33 bool ReadInt(base::File* file, int32_t* out_int) {
34   DCHECK(out_int);
35   int32_t buf;
36   int read = file->ReadAtCurrentPos(reinterpret_cast<char*>(&buf), sizeof(buf));
37   if (read != sizeof(buf)) {
38     return false;
39   }
40   // Values are stored in network byte order (highest-order byte first).
41   // We probably need to convert them to match the endianness of our system.
42   *out_int = ntohl(buf);
43   return true;
44 }
45 
ParseTzifHeader(base::File * tzfile,struct tzif_header * header)46 bool ParseTzifHeader(base::File* tzfile, struct tzif_header* header) {
47   DCHECK(header);
48   int read = tzfile->ReadAtCurrentPos(header->magic, sizeof(header->magic));
49   if (read != sizeof(header->magic)) {
50     return false;
51   }
52   if (memcmp(header->magic, "TZif", 4) != 0) {
53     return false;
54   }
55 
56   read = tzfile->ReadAtCurrentPos(&header->version, sizeof(header->version));
57   if (read != sizeof(header->version)) {
58     return false;
59   }
60   if (header->version != '\0' && header->version != '2' &&
61       header->version != '3') {
62     return false;
63   }
64 
65   read = tzfile->ReadAtCurrentPos(header->reserved, sizeof(header->reserved));
66   if (read != sizeof(header->reserved)) {
67     return false;
68   }
69   for (size_t i = 0; i < sizeof(header->reserved); i++) {
70     if (header->reserved[i] != 0) {
71       return false;
72     }
73   }
74 
75   if (!ReadInt(tzfile, &header->ttisgmtcnt) || header->ttisgmtcnt < 0) {
76     return false;
77   }
78   if (!ReadInt(tzfile, &header->ttisstdcnt) || header->ttisstdcnt < 0) {
79     return false;
80   }
81   if (!ReadInt(tzfile, &header->leapcnt) || header->leapcnt < 0) {
82     return false;
83   }
84   if (!ReadInt(tzfile, &header->timecnt) || header->timecnt < 0) {
85     return false;
86   }
87   if (!ReadInt(tzfile, &header->typecnt) || header->typecnt < 0) {
88     return false;
89   }
90   if (!ReadInt(tzfile, &header->charcnt) || header->charcnt < 0) {
91     return false;
92   }
93   return true;
94 }
95 
96 }  // namespace
97 
98 namespace brillo {
99 
100 namespace timezone {
101 
GetPosixTimezone(const base::FilePath & tzif_path)102 base::Optional<std::string> GetPosixTimezone(const base::FilePath& tzif_path) {
103   base::FilePath to_parse;
104   if (tzif_path.IsAbsolute()) {
105     to_parse = tzif_path;
106   } else {
107     to_parse = base::FilePath("/usr/share/zoneinfo").Append(tzif_path);
108   }
109   base::File tzfile(to_parse, base::File::FLAG_OPEN | base::File::FLAG_READ);
110   struct tzif_header first_header;
111   if (!tzfile.IsValid() || !ParseTzifHeader(&tzfile, &first_header)) {
112     return base::nullopt;
113   }
114 
115   if (first_header.version == '\0') {
116     // Generating a POSIX-style TZ string from a TZif version 1 file is hard;
117     // TZ strings need relative dates (1st Sunday in March, 1st Sunday in Nov,
118     // etc.), but the version 1 files only give absolute dates for each
119     // year up to 2037. Fortunately version 1 files are no longer created by
120     // the newer versions of the timezone-data package.
121     //
122     // Because of this, we're not going to try and handle this, and instead just
123     // return an error.
124     return base::nullopt;
125   }
126 
127   // TZif versions 2 and 3 embed a POSIX-style TZ string after their
128   // contents. We read that out and return it.
129 
130   // Skip past the first body section and all of the second section. See
131   // 'man tzfile' for an explanation of these offset values.
132   int64_t first_body_size =
133       4 * first_header.timecnt + 1 * first_header.timecnt +
134       (4 + 1 + 1) * first_header.typecnt + 1 * first_header.charcnt +
135       (4 + 4) * first_header.leapcnt + 1 * first_header.ttisstdcnt +
136       1 * first_header.ttisgmtcnt;
137   tzfile.Seek(base::File::FROM_CURRENT, first_body_size);
138 
139   struct tzif_header second_header;
140   if (!ParseTzifHeader(&tzfile, &second_header)) {
141     return base::nullopt;
142   }
143 
144   int64_t second_body_size =
145       8 * second_header.timecnt + 1 * second_header.timecnt +
146       (4 + 1 + 1) * second_header.typecnt + 1 * second_header.charcnt +
147       (8 + 4) * second_header.leapcnt + 1 * second_header.ttisstdcnt +
148       1 * second_header.ttisgmtcnt;
149   int64_t offset = tzfile.Seek(base::File::FROM_CURRENT, second_body_size);
150 
151   std::string time_string(tzfile.GetLength() - offset, '\0');
152   if (tzfile.ReadAtCurrentPos(base::data(time_string), time_string.size()) !=
153       time_string.size()) {
154     return base::nullopt;
155   }
156 
157   // According to the spec, the embedded string is enclosed by '\n' characters.
158   base::TrimWhitespaceASCII(time_string, base::TRIM_ALL, &time_string);
159   return std::move(time_string);
160 }
161 
162 }  // namespace timezone
163 
164 }  // namespace brillo
165