package compression import ( "bufio" "bytes" "compress/gzip" "fmt" "io" "sync" ) type ( // Compression is the state represents if compressed or not. Compression int ) const ( // Uncompressed represents the uncompressed. Uncompressed Compression = iota // Gzip is gzip compression algorithm. Gzip ) var ( bufioReader32KPool = &sync.Pool{ New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, } ) type readCloserWrapper struct { io.Reader closer func() error } func (r *readCloserWrapper) Close() error { if r.closer != nil { return r.closer() } return nil } type writeCloserWrapper struct { io.Writer closer func() error } func (w *writeCloserWrapper) Close() error { if w.closer != nil { w.closer() } return nil } // DetectCompression detects the compression algorithm of the source. func DetectCompression(source []byte) Compression { for compression, m := range map[Compression][]byte{ Gzip: {0x1F, 0x8B, 0x08}, } { if len(source) < len(m) { // Len too short continue } if bytes.Equal(m, source[:len(m)]) { return compression } } return Uncompressed } // DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. func DecompressStream(archive io.Reader) (io.ReadCloser, error) { buf := bufioReader32KPool.Get().(*bufio.Reader) buf.Reset(archive) bs, err := buf.Peek(10) if err != nil && err != io.EOF { // Note: we'll ignore any io.EOF error because there are some odd // cases where the layer.tar file will be empty (zero bytes) and // that results in an io.EOF from the Peek() call. So, in those // cases we'll just treat it as a non-compressed stream and // that means just create an empty layer. // See Issue docker/docker#18170 return nil, err } closer := func() error { buf.Reset(nil) bufioReader32KPool.Put(buf) return nil } switch compression := DetectCompression(bs); compression { case Uncompressed: readBufWrapper := &readCloserWrapper{buf, closer} return readBufWrapper, nil case Gzip: gzReader, err := gzip.NewReader(buf) if err != nil { return nil, err } readBufWrapper := &readCloserWrapper{gzReader, closer} return readBufWrapper, nil default: return nil, fmt.Errorf("unsupported compression format %s", (&compression).Extension()) } } // CompressStream compresseses the dest with specified compression algorithm. func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { switch compression { case Uncompressed: return &writeCloserWrapper{dest, nil}, nil case Gzip: return gzip.NewWriter(dest), nil default: return nil, fmt.Errorf("unsupported compression format %s", (&compression).Extension()) } } // Extension returns the extension of a file that uses the specified compression algorithm. func (compression *Compression) Extension() string { switch *compression { case Gzip: return "gz" } return "" }