summaryrefslogtreecommitdiff
path: root/libgo/go/archive
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/archive')
-rw-r--r--libgo/go/archive/tar/reader.go2
-rw-r--r--libgo/go/archive/tar/reader_test.go1
-rw-r--r--libgo/go/archive/zip/reader.go281
-rw-r--r--libgo/go/archive/zip/reader_test.go44
-rw-r--r--libgo/go/archive/zip/struct.go61
-rw-r--r--libgo/go/archive/zip/writer.go244
-rw-r--r--libgo/go/archive/zip/writer_test.go73
-rw-r--r--libgo/go/archive/zip/zip_test.go57
8 files changed, 607 insertions, 156 deletions
diff --git a/libgo/go/archive/tar/reader.go b/libgo/go/archive/tar/reader.go
index ad06b6dac54..45d95c3df29 100644
--- a/libgo/go/archive/tar/reader.go
+++ b/libgo/go/archive/tar/reader.go
@@ -16,7 +16,7 @@ import (
)
var (
- HeaderError os.Error = os.ErrorString("invalid tar header")
+ HeaderError = os.NewError("invalid tar header")
)
// A Reader provides sequential access to the contents of a tar archive.
diff --git a/libgo/go/archive/tar/reader_test.go b/libgo/go/archive/tar/reader_test.go
index 32fc8f9151a..f473c900f26 100644
--- a/libgo/go/archive/tar/reader_test.go
+++ b/libgo/go/archive/tar/reader_test.go
@@ -178,7 +178,6 @@ func TestPartialRead(t *testing.T) {
}
}
-
func TestIncrementalRead(t *testing.T) {
test := gnuTarTest
f, err := os.Open(test.file)
diff --git a/libgo/go/archive/zip/reader.go b/libgo/go/archive/zip/reader.go
index 17464c5d8e4..f92f9297ada 100644
--- a/libgo/go/archive/zip/reader.go
+++ b/libgo/go/archive/zip/reader.go
@@ -2,18 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-/*
-Package zip provides support for reading ZIP archives.
-
-See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
-
-This package does not support ZIP64 or disk spanning.
-*/
package zip
import (
"bufio"
- "bytes"
"compress/flate"
"hash"
"hash/crc32"
@@ -24,9 +16,9 @@ import (
)
var (
- FormatError = os.NewError("not a valid zip file")
- UnsupportedMethod = os.NewError("unsupported compression algorithm")
- ChecksumError = os.NewError("checksum error")
+ FormatError = os.NewError("zip: not a valid zip file")
+ UnsupportedMethod = os.NewError("zip: unsupported compression algorithm")
+ ChecksumError = os.NewError("zip: checksum error")
)
type Reader struct {
@@ -44,15 +36,14 @@ type File struct {
FileHeader
zipr io.ReaderAt
zipsize int64
- headerOffset uint32
- bodyOffset int64
+ headerOffset int64
}
func (f *File) hasDataDescriptor() bool {
return f.Flags&0x8 != 0
}
-// OpenReader will open the Zip file specified by name and return a ReaderCloser.
+// OpenReader will open the Zip file specified by name and return a ReadCloser.
func OpenReader(name string) (*ReadCloser, os.Error) {
f, err := os.Open(name)
if err != nil {
@@ -87,18 +78,33 @@ func (z *Reader) init(r io.ReaderAt, size int64) os.Error {
return err
}
z.r = r
- z.File = make([]*File, end.directoryRecords)
+ z.File = make([]*File, 0, end.directoryRecords)
z.Comment = end.comment
rs := io.NewSectionReader(r, 0, size)
if _, err = rs.Seek(int64(end.directoryOffset), os.SEEK_SET); err != nil {
return err
}
buf := bufio.NewReader(rs)
- for i := range z.File {
- z.File[i] = &File{zipr: r, zipsize: size}
- if err := readDirectoryHeader(z.File[i], buf); err != nil {
+
+ // The count of files inside a zip is truncated to fit in a uint16.
+ // Gloss over this by reading headers until we encounter
+ // a bad one, and then only report a FormatError or UnexpectedEOF if
+ // the file count modulo 65536 is incorrect.
+ for {
+ f := &File{zipr: r, zipsize: size}
+ err = readDirectoryHeader(f, buf)
+ if err == FormatError || err == io.ErrUnexpectedEOF {
+ break
+ }
+ if err != nil {
return err
}
+ z.File = append(z.File, f)
+ }
+ if uint16(len(z.File)) != end.directoryRecords {
+ // Return the readDirectoryHeader error if we read
+ // the wrong number of directory entries.
+ return err
}
return nil
}
@@ -109,31 +115,22 @@ func (rc *ReadCloser) Close() os.Error {
}
// Open returns a ReadCloser that provides access to the File's contents.
+// It is safe to Open and Read from files concurrently.
func (f *File) Open() (rc io.ReadCloser, err os.Error) {
- off := int64(f.headerOffset)
- if f.bodyOffset == 0 {
- r := io.NewSectionReader(f.zipr, off, f.zipsize-off)
- if err = readFileHeader(f, r); err != nil {
- return
- }
- if f.bodyOffset, err = r.Seek(0, os.SEEK_CUR); err != nil {
- return
- }
+ bodyOffset, err := f.findBodyOffset()
+ if err != nil {
+ return
}
size := int64(f.CompressedSize)
- if f.hasDataDescriptor() {
- if size == 0 {
- // permit SectionReader to see the rest of the file
- size = f.zipsize - (off + f.bodyOffset)
- } else {
- size += dataDescriptorLen
- }
+ if size == 0 && f.hasDataDescriptor() {
+ // permit SectionReader to see the rest of the file
+ size = f.zipsize - (f.headerOffset + bodyOffset)
}
- r := io.NewSectionReader(f.zipr, off+f.bodyOffset, size)
+ r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size)
switch f.Method {
- case 0: // store (no compression)
+ case Store: // (no compression)
rc = ioutil.NopCloser(r)
- case 8: // DEFLATE
+ case Deflate:
rc = flate.NewReader(r)
default:
err = UnsupportedMethod
@@ -170,90 +167,102 @@ func (r *checksumReader) Read(b []byte) (n int, err os.Error) {
func (r *checksumReader) Close() os.Error { return r.rc.Close() }
-func readFileHeader(f *File, r io.Reader) (err os.Error) {
- defer func() {
- if rerr, ok := recover().(os.Error); ok {
- err = rerr
- }
- }()
- var (
- signature uint32
- filenameLength uint16
- extraLength uint16
- )
- read(r, &signature)
- if signature != fileHeaderSignature {
+func readFileHeader(f *File, r io.Reader) os.Error {
+ var b [fileHeaderLen]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ c := binary.LittleEndian
+ if sig := c.Uint32(b[:4]); sig != fileHeaderSignature {
return FormatError
}
- read(r, &f.ReaderVersion)
- read(r, &f.Flags)
- read(r, &f.Method)
- read(r, &f.ModifiedTime)
- read(r, &f.ModifiedDate)
- read(r, &f.CRC32)
- read(r, &f.CompressedSize)
- read(r, &f.UncompressedSize)
- read(r, &filenameLength)
- read(r, &extraLength)
- f.Name = string(readByteSlice(r, filenameLength))
- f.Extra = readByteSlice(r, extraLength)
- return
+ f.ReaderVersion = c.Uint16(b[4:6])
+ f.Flags = c.Uint16(b[6:8])
+ f.Method = c.Uint16(b[8:10])
+ f.ModifiedTime = c.Uint16(b[10:12])
+ f.ModifiedDate = c.Uint16(b[12:14])
+ f.CRC32 = c.Uint32(b[14:18])
+ f.CompressedSize = c.Uint32(b[18:22])
+ f.UncompressedSize = c.Uint32(b[22:26])
+ filenameLen := int(c.Uint16(b[26:28]))
+ extraLen := int(c.Uint16(b[28:30]))
+ d := make([]byte, filenameLen+extraLen)
+ if _, err := io.ReadFull(r, d); err != nil {
+ return err
+ }
+ f.Name = string(d[:filenameLen])
+ f.Extra = d[filenameLen:]
+ return nil
}
-func readDirectoryHeader(f *File, r io.Reader) (err os.Error) {
- defer func() {
- if rerr, ok := recover().(os.Error); ok {
- err = rerr
- }
- }()
- var (
- signature uint32
- filenameLength uint16
- extraLength uint16
- commentLength uint16
- startDiskNumber uint16 // unused
- internalAttributes uint16 // unused
- externalAttributes uint32 // unused
- )
- read(r, &signature)
- if signature != directoryHeaderSignature {
+// findBodyOffset does the minimum work to verify the file has a header
+// and returns the file body offset.
+func (f *File) findBodyOffset() (int64, os.Error) {
+ r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset)
+ var b [fileHeaderLen]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return 0, err
+ }
+ c := binary.LittleEndian
+ if sig := c.Uint32(b[:4]); sig != fileHeaderSignature {
+ return 0, FormatError
+ }
+ filenameLen := int(c.Uint16(b[26:28]))
+ extraLen := int(c.Uint16(b[28:30]))
+ return int64(fileHeaderLen + filenameLen + extraLen), nil
+}
+
+// readDirectoryHeader attempts to read a directory header from r.
+// It returns io.ErrUnexpectedEOF if it cannot read a complete header,
+// and FormatError if it doesn't find a valid header signature.
+func readDirectoryHeader(f *File, r io.Reader) os.Error {
+ var b [directoryHeaderLen]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ c := binary.LittleEndian
+ if sig := c.Uint32(b[:4]); sig != directoryHeaderSignature {
return FormatError
}
- read(r, &f.CreatorVersion)
- read(r, &f.ReaderVersion)
- read(r, &f.Flags)
- read(r, &f.Method)
- read(r, &f.ModifiedTime)
- read(r, &f.ModifiedDate)
- read(r, &f.CRC32)
- read(r, &f.CompressedSize)
- read(r, &f.UncompressedSize)
- read(r, &filenameLength)
- read(r, &extraLength)
- read(r, &commentLength)
- read(r, &startDiskNumber)
- read(r, &internalAttributes)
- read(r, &externalAttributes)
- read(r, &f.headerOffset)
- f.Name = string(readByteSlice(r, filenameLength))
- f.Extra = readByteSlice(r, extraLength)
- f.Comment = string(readByteSlice(r, commentLength))
- return
+ f.CreatorVersion = c.Uint16(b[4:6])
+ f.ReaderVersion = c.Uint16(b[6:8])
+ f.Flags = c.Uint16(b[8:10])
+ f.Method = c.Uint16(b[10:12])
+ f.ModifiedTime = c.Uint16(b[12:14])
+ f.ModifiedDate = c.Uint16(b[14:16])
+ f.CRC32 = c.Uint32(b[16:20])
+ f.CompressedSize = c.Uint32(b[20:24])
+ f.UncompressedSize = c.Uint32(b[24:28])
+ filenameLen := int(c.Uint16(b[28:30]))
+ extraLen := int(c.Uint16(b[30:32]))
+ commentLen := int(c.Uint16(b[32:34]))
+ // startDiskNumber := c.Uint16(b[34:36]) // Unused
+ // internalAttributes := c.Uint16(b[36:38]) // Unused
+ // externalAttributes := c.Uint32(b[38:42]) // Unused
+ f.headerOffset = int64(c.Uint32(b[42:46]))
+ d := make([]byte, filenameLen+extraLen+commentLen)
+ if _, err := io.ReadFull(r, d); err != nil {
+ return err
+ }
+ f.Name = string(d[:filenameLen])
+ f.Extra = d[filenameLen : filenameLen+extraLen]
+ f.Comment = string(d[filenameLen+extraLen:])
+ return nil
}
-func readDataDescriptor(r io.Reader, f *File) (err os.Error) {
- defer func() {
- if rerr, ok := recover().(os.Error); ok {
- err = rerr
- }
- }()
- read(r, &f.CRC32)
- read(r, &f.CompressedSize)
- read(r, &f.UncompressedSize)
- return
+func readDataDescriptor(r io.Reader, f *File) os.Error {
+ var b [dataDescriptorLen]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ c := binary.LittleEndian
+ f.CRC32 = c.Uint32(b[:4])
+ f.CompressedSize = c.Uint32(b[4:8])
+ f.UncompressedSize = c.Uint32(b[8:12])
+ return nil
}
-func readDirectoryEnd(r io.ReaderAt, size int64) (d *directoryEnd, err os.Error) {
+func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err os.Error) {
// look for directoryEndSignature in the last 1k, then in the last 65k
var b []byte
for i, bLen := range []int64{1024, 65 * 1024} {
@@ -274,53 +283,29 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (d *directoryEnd, err os.Error)
}
// read header into struct
- defer func() {
- if rerr, ok := recover().(os.Error); ok {
- err = rerr
- d = nil
- }
- }()
- br := bytes.NewBuffer(b[4:]) // skip over signature
- d = new(directoryEnd)
- read(br, &d.diskNbr)
- read(br, &d.dirDiskNbr)
- read(br, &d.dirRecordsThisDisk)
- read(br, &d.directoryRecords)
- read(br, &d.directorySize)
- read(br, &d.directoryOffset)
- read(br, &d.commentLen)
- d.comment = string(readByteSlice(br, d.commentLen))
+ c := binary.LittleEndian
+ d := new(directoryEnd)
+ d.diskNbr = c.Uint16(b[4:6])
+ d.dirDiskNbr = c.Uint16(b[6:8])
+ d.dirRecordsThisDisk = c.Uint16(b[8:10])
+ d.directoryRecords = c.Uint16(b[10:12])
+ d.directorySize = c.Uint32(b[12:16])
+ d.directoryOffset = c.Uint32(b[16:20])
+ d.commentLen = c.Uint16(b[20:22])
+ d.comment = string(b[22 : 22+int(d.commentLen)])
return d, nil
}
func findSignatureInBlock(b []byte) int {
- const minSize = 4 + 2 + 2 + 2 + 2 + 4 + 4 + 2 // fixed part of header
- for i := len(b) - minSize; i >= 0; i-- {
+ for i := len(b) - directoryEndLen; i >= 0; i-- {
// defined from directoryEndSignature in struct.go
if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 {
// n is length of comment
- n := int(b[i+minSize-2]) | int(b[i+minSize-1])<<8
- if n+minSize+i == len(b) {
+ n := int(b[i+directoryEndLen-2]) | int(b[i+directoryEndLen-1])<<8
+ if n+directoryEndLen+i == len(b) {
return i
}
}
}
return -1
}
-
-func read(r io.Reader, data interface{}) {
- if err := binary.Read(r, binary.LittleEndian, data); err != nil {
- panic(err)
- }
-}
-
-func readByteSlice(r io.Reader, l uint16) []byte {
- b := make([]byte, l)
- if l == 0 {
- return b
- }
- if _, err := io.ReadFull(r, b); err != nil {
- panic(err)
- }
- return b
-}
diff --git a/libgo/go/archive/zip/reader_test.go b/libgo/go/archive/zip/reader_test.go
index c72cd9a2347..fd5fed2af05 100644
--- a/libgo/go/archive/zip/reader_test.go
+++ b/libgo/go/archive/zip/reader_test.go
@@ -11,6 +11,7 @@ import (
"io/ioutil"
"os"
"testing"
+ "time"
)
type ZipTest struct {
@@ -24,8 +25,19 @@ type ZipTestFile struct {
Name string
Content []byte // if blank, will attempt to compare against File
File string // name of file to compare to (relative to testdata/)
+ Mtime string // modified time in format "mm-dd-yy hh:mm:ss"
}
+// Caution: The Mtime values found for the test files should correspond to
+// the values listed with unzip -l <zipfile>. However, the values
+// listed by unzip appear to be off by some hours. When creating
+// fresh test files and testing them, this issue is not present.
+// The test files were created in Sydney, so there might be a time
+// zone issue. The time zone information does have to be encoded
+// somewhere, because otherwise unzip -l could not provide a different
+// time from what the archive/zip package provides, but there appears
+// to be no documentation about this.
+
var tests = []ZipTest{
{
Name: "test.zip",
@@ -34,10 +46,12 @@ var tests = []ZipTest{
{
Name: "test.txt",
Content: []byte("This is a test text file.\n"),
+ Mtime: "09-05-10 12:12:02",
},
{
- Name: "gophercolor16x16.png",
- File: "gophercolor16x16.png",
+ Name: "gophercolor16x16.png",
+ File: "gophercolor16x16.png",
+ Mtime: "09-05-10 15:52:58",
},
},
},
@@ -45,8 +59,9 @@ var tests = []ZipTest{
Name: "r.zip",
File: []ZipTestFile{
{
- Name: "r/r.zip",
- File: "r.zip",
+ Name: "r/r.zip",
+ File: "r.zip",
+ Mtime: "03-04-10 00:24:16",
},
},
},
@@ -58,6 +73,7 @@ var tests = []ZipTest{
{
Name: "filename",
Content: []byte("This is a test textfile.\n"),
+ Mtime: "02-02-11 13:06:20",
},
},
},
@@ -136,18 +152,36 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
if f.Name != ft.Name {
t.Errorf("name=%q, want %q", f.Name, ft.Name)
}
+
+ mtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if got, want := f.Mtime_ns()/1e9, mtime.Seconds(); got != want {
+ t.Errorf("%s: mtime=%s (%d); want %s (%d)", f.Name, time.SecondsToUTC(got), got, mtime, want)
+ }
+
+ size0 := f.UncompressedSize
+
var b bytes.Buffer
r, err := f.Open()
if err != nil {
t.Error(err)
return
}
+
+ if size1 := f.UncompressedSize; size0 != size1 {
+ t.Errorf("file %q changed f.UncompressedSize from %d to %d", f.Name, size0, size1)
+ }
+
_, err = io.Copy(&b, r)
if err != nil {
t.Error(err)
return
}
r.Close()
+
var c []byte
if len(ft.Content) != 0 {
c = ft.Content
@@ -155,10 +189,12 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
t.Error(err)
return
}
+
if b.Len() != len(c) {
t.Errorf("%s: len=%d, want %d", f.Name, b.Len(), len(c))
return
}
+
for i, b := range b.Bytes() {
if b != c[i] {
t.Errorf("%s: content[%d]=%q want %q", f.Name, i, b, c[i])
diff --git a/libgo/go/archive/zip/struct.go b/libgo/go/archive/zip/struct.go
index bfe0aae2e9a..1d6e70f105a 100644
--- a/libgo/go/archive/zip/struct.go
+++ b/libgo/go/archive/zip/struct.go
@@ -1,9 +1,32 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package zip provides support for reading and writing ZIP archives.
+
+See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+
+This package does not support ZIP64 or disk spanning.
+*/
package zip
+import "os"
+import "time"
+
+// Compression methods.
+const (
+ Store uint16 = 0
+ Deflate uint16 = 8
+)
+
const (
fileHeaderSignature = 0x04034b50
directoryHeaderSignature = 0x02014b50
directoryEndSignature = 0x06054b50
+ fileHeaderLen = 30 // + filename + extra
+ directoryHeaderLen = 46 // + filename + extra + comment
+ directoryEndLen = 22 // + comment
dataDescriptorLen = 12
)
@@ -13,8 +36,8 @@ type FileHeader struct {
ReaderVersion uint16
Flags uint16
Method uint16
- ModifiedTime uint16
- ModifiedDate uint16
+ ModifiedTime uint16 // MS-DOS time
+ ModifiedDate uint16 // MS-DOS date
CRC32 uint32
CompressedSize uint32
UncompressedSize uint32
@@ -32,3 +55,37 @@ type directoryEnd struct {
commentLen uint16
comment string
}
+
+func recoverError(err *os.Error) {
+ if e := recover(); e != nil {
+ if osErr, ok := e.(os.Error); ok {
+ *err = osErr
+ return
+ }
+ panic(e)
+ }
+}
+
+// msDosTimeToTime converts an MS-DOS date and time into a time.Time.
+// The resolution is 2s.
+// See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx
+func msDosTimeToTime(dosDate, dosTime uint16) time.Time {
+ return time.Time{
+ // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980
+ Year: int64(dosDate>>9 + 1980),
+ Month: int(dosDate >> 5 & 0xf),
+ Day: int(dosDate & 0x1f),
+
+ // time bits 0-4: second/2; 5-10: minute; 11-15: hour
+ Hour: int(dosTime >> 11),
+ Minute: int(dosTime >> 5 & 0x3f),
+ Second: int(dosTime & 0x1f * 2),
+ }
+}
+
+// Mtime_ns returns the modified time in ns since epoch.
+// The resolution is 2s.
+func (h *FileHeader) Mtime_ns() int64 {
+ t := msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
+ return t.Seconds() * 1e9
+}
diff --git a/libgo/go/archive/zip/writer.go b/libgo/go/archive/zip/writer.go
new file mode 100644
index 00000000000..2065b06daac
--- /dev/null
+++ b/libgo/go/archive/zip/writer.go
@@ -0,0 +1,244 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zip
+
+import (
+ "bufio"
+ "compress/flate"
+ "encoding/binary"
+ "hash"
+ "hash/crc32"
+ "io"
+ "os"
+)
+
+// TODO(adg): support zip file comments
+// TODO(adg): support specifying deflate level
+
+// Writer implements a zip file writer.
+type Writer struct {
+ *countWriter
+ dir []*header
+ last *fileWriter
+ closed bool
+}
+
+type header struct {
+ *FileHeader
+ offset uint32
+}
+
+// NewWriter returns a new Writer writing a zip file to w.
+func NewWriter(w io.Writer) *Writer {
+ return &Writer{countWriter: &countWriter{w: bufio.NewWriter(w)}}
+}
+
+// Close finishes writing the zip file by writing the central directory.
+// It does not (and can not) close the underlying writer.
+func (w *Writer) Close() (err os.Error) {
+ if w.last != nil && !w.last.closed {
+ if err = w.last.close(); err != nil {
+ return
+ }
+ w.last = nil
+ }
+ if w.closed {
+ return os.NewError("zip: writer closed twice")
+ }
+ w.closed = true
+
+ defer recoverError(&err)
+
+ // write central directory
+ start := w.count
+ for _, h := range w.dir {
+ write(w, uint32(directoryHeaderSignature))
+ write(w, h.CreatorVersion)
+ write(w, h.ReaderVersion)
+ write(w, h.Flags)
+ write(w, h.Method)
+ write(w, h.ModifiedTime)
+ write(w, h.ModifiedDate)
+ write(w, h.CRC32)
+ write(w, h.CompressedSize)
+ write(w, h.UncompressedSize)
+ write(w, uint16(len(h.Name)))
+ write(w, uint16(len(h.Extra)))
+ write(w, uint16(len(h.Comment)))
+ write(w, uint16(0)) // disk number start
+ write(w, uint16(0)) // internal file attributes
+ write(w, uint32(0)) // external file attributes
+ write(w, h.offset)
+ writeBytes(w, []byte(h.Name))
+ writeBytes(w, h.Extra)
+ writeBytes(w, []byte(h.Comment))
+ }
+ end := w.count
+
+ // write end record
+ write(w, uint32(directoryEndSignature))
+ write(w, uint16(0)) // disk number
+ write(w, uint16(0)) // disk number where directory starts
+ write(w, uint16(len(w.dir))) // number of entries this disk
+ write(w, uint16(len(w.dir))) // number of entries total
+ write(w, uint32(end-start)) // size of directory
+ write(w, uint32(start)) // start of directory
+ write(w, uint16(0)) // size of comment
+
+ return w.w.(*bufio.Writer).Flush()
+}
+
+// Create adds a file to the zip file using the provided name.
+// It returns a Writer to which the file contents should be written.
+// The file's contents must be written to the io.Writer before the next
+// call to Create, CreateHeader, or Close.
+func (w *Writer) Create(name string) (io.Writer, os.Error) {
+ header := &FileHeader{
+ Name: name,
+ Method: Deflate,
+ }
+ return w.CreateHeader(header)
+}
+
+// CreateHeader adds a file to the zip file using the provided FileHeader
+// for the file metadata.
+// It returns a Writer to which the file contents should be written.
+// The file's contents must be written to the io.Writer before the next
+// call to Create, CreateHeader, or Close.
+func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, os.Error) {
+ if w.last != nil && !w.last.closed {
+ if err := w.last.close(); err != nil {
+ return nil, err
+ }
+ }
+
+ fh.Flags |= 0x8 // we will write a data descriptor
+ fh.CreatorVersion = 0x14
+ fh.ReaderVersion = 0x14
+
+ fw := &fileWriter{
+ zipw: w,
+ compCount: &countWriter{w: w},
+ crc32: crc32.NewIEEE(),
+ }
+ switch fh.Method {
+ case Store:
+ fw.comp = nopCloser{fw.compCount}
+ case Deflate:
+ fw.comp = flate.NewWriter(fw.compCount, 5)
+ default:
+ return nil, UnsupportedMethod
+ }
+ fw.rawCount = &countWriter{w: fw.comp}
+
+ h := &header{
+ FileHeader: fh,
+ offset: uint32(w.count),
+ }
+ w.dir = append(w.dir, h)
+ fw.header = h
+
+ if err := writeHeader(w, fh); err != nil {
+ return nil, err
+ }
+
+ w.last = fw
+ return fw, nil
+}
+
+func writeHeader(w io.Writer, h *FileHeader) (err os.Error) {
+ defer recoverError(&err)
+ write(w, uint32(fileHeaderSignature))
+ write(w, h.ReaderVersion)
+ write(w, h.Flags)
+ write(w, h.Method)
+ write(w, h.ModifiedTime)
+ write(w, h.ModifiedDate)
+ write(w, h.CRC32)
+ write(w, h.CompressedSize)
+ write(w, h.UncompressedSize)
+ write(w, uint16(len(h.Name)))
+ write(w, uint16(len(h.Extra)))
+ writeBytes(w, []byte(h.Name))
+ writeBytes(w, h.Extra)
+ return nil
+}
+
+type fileWriter struct {
+ *header
+ zipw io.Writer
+ rawCount *countWriter
+ comp io.WriteCloser
+ compCount *countWriter
+ crc32 hash.Hash32
+ closed bool
+}
+
+func (w *fileWriter) Write(p []byte) (int, os.Error) {
+ if w.closed {
+ return 0, os.NewError("zip: write to closed file")
+ }
+ w.crc32.Write(p)
+ return w.rawCount.Write(p)
+}
+
+func (w *fileWriter) close() (err os.Error) {
+ if w.closed {
+ return os.NewError("zip: file closed twice")
+ }
+ w.closed = true
+ if err = w.comp.Close(); err != nil {
+ return
+ }
+
+ // update FileHeader
+ fh := w.header.FileHeader
+ fh.CRC32 = w.crc32.Sum32()
+ fh.CompressedSize = uint32(w.compCount.count)
+ fh.UncompressedSize = uint32(w.rawCount.count)
+
+ // write data descriptor
+ defer recoverError(&err)
+ write(w.zipw, fh.CRC32)
+ write(w.zipw, fh.CompressedSize)
+ write(w.zipw, fh.UncompressedSize)
+
+ return nil
+}
+
+type countWriter struct {
+ w io.Writer
+ count int64
+}
+
+func (w *countWriter) Write(p []byte) (int, os.Error) {
+ n, err := w.w.Write(p)
+ w.count += int64(n)
+ return n, err
+}
+
+type nopCloser struct {
+ io.Writer
+}
+
+func (w nopCloser) Close() os.Error {
+ return nil
+}
+
+func write(w io.Writer, data interface{}) {
+ if err := binary.Write(w, binary.LittleEndian, data); err != nil {
+ panic(err)
+ }
+}
+
+func writeBytes(w io.Writer, b []byte) {
+ n, err := w.Write(b)
+ if err != nil {
+ panic(err)
+ }
+ if n != len(b) {
+ panic(io.ErrShortWrite)
+ }
+}
diff --git a/libgo/go/archive/zip/writer_test.go b/libgo/go/archive/zip/writer_test.go
new file mode 100644
index 00000000000..eb2a80c3f70
--- /dev/null
+++ b/libgo/go/archive/zip/writer_test.go
@@ -0,0 +1,73 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zip
+
+import (
+ "bytes"
+ "io/ioutil"
+ "rand"
+ "testing"
+)
+
+// TODO(adg): a more sophisticated test suite
+
+const testString = "Rabbits, guinea pigs, gophers, marsupial rats, and quolls."
+
+func TestWriter(t *testing.T) {
+ largeData := make([]byte, 1<<17)
+ for i := range largeData {
+ largeData[i] = byte(rand.Int())
+ }
+
+ // write a zip file
+ buf := new(bytes.Buffer)
+ w := NewWriter(buf)
+ testCreate(t, w, "foo", []byte(testString), Store)
+ testCreate(t, w, "bar", largeData, Deflate)
+ if err := w.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ // read it back
+ r, err := NewReader(sliceReaderAt(buf.Bytes()), int64(buf.Len()))
+ if err != nil {
+ t.Fatal(err)
+ }
+ testReadFile(t, r.File[0], []byte(testString))
+ testReadFile(t, r.File[1], largeData)
+}
+
+func testCreate(t *testing.T, w *Writer, name string, data []byte, method uint16) {
+ header := &FileHeader{
+ Name: name,
+ Method: method,
+ }
+ f, err := w.CreateHeader(header)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = f.Write(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testReadFile(t *testing.T, f *File, data []byte) {
+ rc, err := f.Open()
+ if err != nil {
+ t.Fatal("opening:", err)
+ }
+ b, err := ioutil.ReadAll(rc)
+ if err != nil {
+ t.Fatal("reading:", err)
+ }
+ err = rc.Close()
+ if err != nil {
+ t.Fatal("closing:", err)
+ }
+ if !bytes.Equal(b, data) {
+ t.Errorf("File contents %q, want %q", b, data)
+ }
+}
diff --git a/libgo/go/archive/zip/zip_test.go b/libgo/go/archive/zip/zip_test.go
new file mode 100644
index 00000000000..0f71fdfac17
--- /dev/null
+++ b/libgo/go/archive/zip/zip_test.go
@@ -0,0 +1,57 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Tests that involve both reading and writing.
+
+package zip
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "testing"
+)
+
+type stringReaderAt string
+
+func (s stringReaderAt) ReadAt(p []byte, off int64) (n int, err os.Error) {
+ if off >= int64(len(s)) {
+ return 0, os.EOF
+ }
+ n = copy(p, s[off:])
+ return
+}
+
+func TestOver65kFiles(t *testing.T) {
+ if testing.Short() {
+ t.Logf("slow test; skipping")
+ return
+ }
+ buf := new(bytes.Buffer)
+ w := NewWriter(buf)
+ const nFiles = (1 << 16) + 42
+ for i := 0; i < nFiles; i++ {
+ _, err := w.Create(fmt.Sprintf("%d.dat", i))
+ if err != nil {
+ t.Fatalf("creating file %d: %v", i, err)
+ }
+ }
+ if err := w.Close(); err != nil {
+ t.Fatalf("Writer.Close: %v", err)
+ }
+ rat := stringReaderAt(buf.String())
+ zr, err := NewReader(rat, int64(len(rat)))
+ if err != nil {
+ t.Fatalf("NewReader: %v", err)
+ }
+ if got := len(zr.File); got != nFiles {
+ t.Fatalf("File contains %d files, want %d", got, nFiles)
+ }
+ for i := 0; i < nFiles; i++ {
+ want := fmt.Sprintf("%d.dat", i)
+ if zr.File[i].Name != want {
+ t.Fatalf("File(%d) = %q, want %q", i, zr.File[i].Name, want)
+ }
+ }
+}