github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/time/zoneinfo_read.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Parse "zoneinfo" time zone file. 6 // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. 7 // See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo, 8 // and ftp://munnari.oz.au/pub/oldtz/ 9 10 package time 11 12 import ( 13 "errors" 14 "syscall" 15 ) 16 17 // maxFileSize is the max permitted size of files read by readFile. 18 // As reference, the zoneinfo.zip distributed by Go is ~350 KB, 19 // so 10MB is overkill. 20 const maxFileSize = 10 << 20 21 22 type fileSizeError string 23 24 func (f fileSizeError) Error() string { 25 return "time: file " + string(f) + " is too large" 26 } 27 28 // Copies of io.Seek* constants to avoid importing "io": 29 const ( 30 seekStart = 0 31 seekCurrent = 1 32 seekEnd = 2 33 ) 34 35 // Simple I/O interface to binary blob of data. 36 type data struct { 37 p []byte 38 error bool 39 } 40 41 func (d *data) read(n int) []byte { 42 if len(d.p) < n { 43 d.p = nil 44 d.error = true 45 return nil 46 } 47 p := d.p[0:n] 48 d.p = d.p[n:] 49 return p 50 } 51 52 func (d *data) big4() (n uint32, ok bool) { 53 p := d.read(4) 54 if len(p) < 4 { 55 d.error = true 56 return 0, false 57 } 58 return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true 59 } 60 61 func (d *data) byte() (n byte, ok bool) { 62 p := d.read(1) 63 if len(p) < 1 { 64 d.error = true 65 return 0, false 66 } 67 return p[0], true 68 } 69 70 // Make a string by stopping at the first NUL 71 func byteString(p []byte) string { 72 for i := 0; i < len(p); i++ { 73 if p[i] == 0 { 74 return string(p[0:i]) 75 } 76 } 77 return string(p) 78 } 79 80 var badData = errors.New("malformed time zone information") 81 82 // newLocationFromTzinfo returns the Location described by Tzinfo with the given name. 83 // The expected format for Tzinfo is that of a timezone file as they are found in the 84 // the IANA Time Zone database. 85 func newLocationFromTzinfo(name string, Tzinfo []byte) (*Location, error) { 86 d := data{Tzinfo, false} 87 88 // 4-byte magic "TZif" 89 if magic := d.read(4); string(magic) != "TZif" { 90 return nil, badData 91 } 92 93 // 1-byte version, then 15 bytes of padding 94 var p []byte 95 if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' && p[0] != '3' { 96 return nil, badData 97 } 98 99 // six big-endian 32-bit integers: 100 // number of UTC/local indicators 101 // number of standard/wall indicators 102 // number of leap seconds 103 // number of transition times 104 // number of local time zones 105 // number of characters of time zone abbrev strings 106 const ( 107 NUTCLocal = iota 108 NStdWall 109 NLeap 110 NTime 111 NZone 112 NChar 113 ) 114 var n [6]int 115 for i := 0; i < 6; i++ { 116 nn, ok := d.big4() 117 if !ok { 118 return nil, badData 119 } 120 n[i] = int(nn) 121 } 122 123 // Transition times. 124 txtimes := data{d.read(n[NTime] * 4), false} 125 126 // Time zone indices for transition times. 127 txzones := d.read(n[NTime]) 128 129 // Zone info structures 130 zonedata := data{d.read(n[NZone] * 6), false} 131 132 // Time zone abbreviations. 133 abbrev := d.read(n[NChar]) 134 135 // Leap-second time pairs 136 d.read(n[NLeap] * 8) 137 138 // Whether tx times associated with local time types 139 // are specified as standard time or wall time. 140 isstd := d.read(n[NStdWall]) 141 142 // Whether tx times associated with local time types 143 // are specified as UTC or local time. 144 isutc := d.read(n[NUTCLocal]) 145 146 if d.error { // ran out of data 147 return nil, badData 148 } 149 150 // If version == 2 or 3, the entire file repeats, this time using 151 // 8-byte ints for txtimes and leap seconds. 152 // We won't need those until 2106. 153 154 // Now we can build up a useful data structure. 155 // First the zone information. 156 // utcoff[4] isdst[1] nameindex[1] 157 zone := make([]zone, n[NZone]) 158 for i := range zone { 159 var ok bool 160 var n uint32 161 if n, ok = zonedata.big4(); !ok { 162 return nil, badData 163 } 164 zone[i].offset = int(int32(n)) 165 var b byte 166 if b, ok = zonedata.byte(); !ok { 167 return nil, badData 168 } 169 zone[i].isDST = b != 0 170 if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { 171 return nil, badData 172 } 173 zone[i].name = byteString(abbrev[b:]) 174 } 175 176 // Now the transition time info. 177 tx := make([]zoneTrans, n[NTime]) 178 for i := range tx { 179 var ok bool 180 var n uint32 181 if n, ok = txtimes.big4(); !ok { 182 return nil, badData 183 } 184 tx[i].when = int64(int32(n)) 185 if int(txzones[i]) >= len(zone) { 186 return nil, badData 187 } 188 tx[i].index = txzones[i] 189 if i < len(isstd) { 190 tx[i].isstd = isstd[i] != 0 191 } 192 if i < len(isutc) { 193 tx[i].isutc = isutc[i] != 0 194 } 195 } 196 197 if len(tx) == 0 { 198 // Build fake transition to cover all time. 199 // This happens in fixed locations like "Etc/GMT0". 200 tx = append(tx, zoneTrans{when: alpha, index: 0}) 201 } 202 203 // Committed to succeed. 204 l := &Location{zone: zone, tx: tx, name: name} 205 206 // Fill in the cache with information about right now, 207 // since that will be the most common lookup. 208 sec, _, _ := now() 209 for i := range tx { 210 if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) { 211 l.cacheStart = tx[i].when 212 l.cacheEnd = omega 213 if i+1 < len(tx) { 214 l.cacheEnd = tx[i+1].when 215 } 216 l.cacheZone = &l.zone[tx[i].index] 217 } 218 } 219 220 return l, nil 221 } 222 223 // There are 500+ zoneinfo files. Rather than distribute them all 224 // individually, we ship them in an uncompressed zip file. 225 // Used this way, the zip file format serves as a commonly readable 226 // container for the individual small files. We choose zip over tar 227 // because zip files have a contiguous table of contents, making 228 // individual file lookups faster, and because the per-file overhead 229 // in a zip file is considerably less than tar's 512 bytes. 230 231 // get4 returns the little-endian 32-bit value in b. 232 func get4(b []byte) int { 233 if len(b) < 4 { 234 return 0 235 } 236 return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24 237 } 238 239 // get2 returns the little-endian 16-bit value in b. 240 func get2(b []byte) int { 241 if len(b) < 2 { 242 return 0 243 } 244 return int(b[0]) | int(b[1])<<8 245 } 246 247 // loadTzinfoFromZip returns the contents of the file with the given name 248 // in the given uncompressed zip file. 249 func loadTzinfoFromZip(zipfile, name string) ([]byte, error) { 250 fd, err := open(zipfile) 251 if err != nil { 252 return nil, errors.New("open " + zipfile + ": " + err.Error()) 253 } 254 defer closefd(fd) 255 256 const ( 257 zecheader = 0x06054b50 258 zcheader = 0x02014b50 259 ztailsize = 22 260 261 zheadersize = 30 262 zheader = 0x04034b50 263 ) 264 265 buf := make([]byte, ztailsize) 266 if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader { 267 return nil, errors.New("corrupt zip file " + zipfile) 268 } 269 n := get2(buf[10:]) 270 size := get4(buf[12:]) 271 off := get4(buf[16:]) 272 273 buf = make([]byte, size) 274 if err := preadn(fd, buf, off); err != nil { 275 return nil, errors.New("corrupt zip file " + zipfile) 276 } 277 278 for i := 0; i < n; i++ { 279 // zip entry layout: 280 // 0 magic[4] 281 // 4 madevers[1] 282 // 5 madeos[1] 283 // 6 extvers[1] 284 // 7 extos[1] 285 // 8 flags[2] 286 // 10 meth[2] 287 // 12 modtime[2] 288 // 14 moddate[2] 289 // 16 crc[4] 290 // 20 csize[4] 291 // 24 uncsize[4] 292 // 28 namelen[2] 293 // 30 xlen[2] 294 // 32 fclen[2] 295 // 34 disknum[2] 296 // 36 iattr[2] 297 // 38 eattr[4] 298 // 42 off[4] 299 // 46 name[namelen] 300 // 46+namelen+xlen+fclen - next header 301 // 302 if get4(buf) != zcheader { 303 break 304 } 305 meth := get2(buf[10:]) 306 size := get4(buf[24:]) 307 namelen := get2(buf[28:]) 308 xlen := get2(buf[30:]) 309 fclen := get2(buf[32:]) 310 off := get4(buf[42:]) 311 zname := buf[46 : 46+namelen] 312 buf = buf[46+namelen+xlen+fclen:] 313 if string(zname) != name { 314 continue 315 } 316 if meth != 0 { 317 return nil, errors.New("unsupported compression for " + name + " in " + zipfile) 318 } 319 320 // zip per-file header layout: 321 // 0 magic[4] 322 // 4 extvers[1] 323 // 5 extos[1] 324 // 6 flags[2] 325 // 8 meth[2] 326 // 10 modtime[2] 327 // 12 moddate[2] 328 // 14 crc[4] 329 // 18 csize[4] 330 // 22 uncsize[4] 331 // 26 namelen[2] 332 // 28 xlen[2] 333 // 30 name[namelen] 334 // 30+namelen+xlen - file data 335 // 336 buf = make([]byte, zheadersize+namelen) 337 if err := preadn(fd, buf, off); err != nil || 338 get4(buf) != zheader || 339 get2(buf[8:]) != meth || 340 get2(buf[26:]) != namelen || 341 string(buf[30:30+namelen]) != name { 342 return nil, errors.New("corrupt zip file " + zipfile) 343 } 344 xlen = get2(buf[28:]) 345 346 buf = make([]byte, size) 347 if err := preadn(fd, buf, off+30+namelen+xlen); err != nil { 348 return nil, errors.New("corrupt zip file " + zipfile) 349 } 350 351 return buf, nil 352 } 353 354 return nil, syscall.ENOENT 355 } 356 357 // loadTzinfoFromTzdata returns the time zone information of the time zone 358 // with the given name, from a tzdata database file as they are typically 359 // found on android. 360 func loadTzinfoFromTzdata(file, name string) ([]byte, error) { 361 const ( 362 headersize = 12 + 3*4 363 namesize = 40 364 entrysize = namesize + 3*4 365 ) 366 if len(name) > namesize { 367 return nil, errors.New(name + " is longer than the maximum zone name length (40 bytes)") 368 } 369 fd, err := open(file) 370 if err != nil { 371 return nil, err 372 } 373 defer closefd(fd) 374 375 buf := make([]byte, headersize) 376 if err := preadn(fd, buf, 0); err != nil { 377 return nil, errors.New("corrupt tzdata file " + file) 378 } 379 d := data{buf, false} 380 if magic := d.read(6); string(magic) != "tzdata" { 381 return nil, errors.New("corrupt tzdata file " + file) 382 } 383 d = data{buf[12:], false} 384 indexOff, _ := d.big4() 385 dataOff, _ := d.big4() 386 indexSize := dataOff - indexOff 387 entrycount := indexSize / entrysize 388 buf = make([]byte, indexSize) 389 if err := preadn(fd, buf, int(indexOff)); err != nil { 390 return nil, errors.New("corrupt tzdata file " + file) 391 } 392 for i := 0; i < int(entrycount); i++ { 393 entry := buf[i*entrysize : (i+1)*entrysize] 394 // len(name) <= namesize is checked at function entry 395 if string(entry[:len(name)]) != name { 396 continue 397 } 398 d := data{entry[namesize:], false} 399 off, _ := d.big4() 400 size, _ := d.big4() 401 buf := make([]byte, size) 402 if err := preadn(fd, buf, int(off+dataOff)); err != nil { 403 return nil, errors.New("corrupt tzdata file " + file) 404 } 405 return buf, nil 406 } 407 return nil, syscall.ENOENT 408 } 409 410 // loadTzinfo returns the time zone information of the time zone 411 // with the given name, from a given source. A source may be a 412 // timezone database directory, tzdata database file or an uncompressed 413 // zip file, containing the contents of such a directory. 414 func loadTzinfo(name string, source string) ([]byte, error) { 415 if len(source) >= 6 && source[len(source)-6:] == "tzdata" { 416 return loadTzinfoFromTzdata(source, name) 417 } else if len(source) > 4 && source[len(source)-4:] == ".zip" { 418 return loadTzinfoFromZip(source, name) 419 } 420 if source != "" { 421 name = source + "/" + name 422 } 423 return readFile(name) 424 } 425 426 // loadLocation returns the Location with the given name from one of 427 // the specified sources. See loadTzinfo for a list of supported sources. 428 // The first timezone data matching the given name that is successfully loaded 429 // and parsed is returned as a Location. 430 func loadLocation(name string, sources []string) (z *Location, firstErr error) { 431 for _, source := range sources { 432 var zoneData, err = loadTzinfo(name, source) 433 if err == nil { 434 if z, err = newLocationFromTzinfo(name, zoneData); err == nil { 435 return z, nil 436 } 437 } 438 if firstErr == nil && err != syscall.ENOENT { 439 firstErr = err 440 } 441 } 442 if firstErr != nil { 443 return nil, firstErr 444 } 445 return nil, errors.New("unknown time zone " + name) 446 } 447 448 // readFile reads and returns the content of the named file. 449 // It is a trivial implementation of ioutil.ReadFile, reimplemented 450 // here to avoid depending on io/ioutil or os. 451 // It returns an error if name exceeds maxFileSize bytes. 452 func readFile(name string) ([]byte, error) { 453 f, err := open(name) 454 if err != nil { 455 return nil, err 456 } 457 defer closefd(f) 458 var ( 459 buf [4096]byte 460 ret []byte 461 n int 462 ) 463 for { 464 n, err = read(f, buf[:]) 465 if n > 0 { 466 ret = append(ret, buf[:n]...) 467 } 468 if n == 0 || err != nil { 469 break 470 } 471 if len(ret) > maxFileSize { 472 return nil, fileSizeError(name) 473 } 474 } 475 return ret, err 476 }