package zsig import ( "bufio" "encoding/binary" "errors" "hash" "hash/crc32" "io" "time" ) var ErrHeader = errors.New("invalid header") const ( gzipID1 = 0x1f gzipID2 = 0x8b gzipDeflate = 8 osUnix = 3 flagText = 1 << 0 flagHdrCrc = 1 << 1 flagExtra = 1 << 2 flagName = 1 << 3 flagComment = 1 << 4 ) var fake = []byte{gzipID1, gzipID2, gzipDeflate, flagComment, 0, 0, 0, 0, 0, osUnix} type Header struct { Comment string Extra []byte ModTime time.Time Name string OS byte CRC uint16 } type Reader struct { Header *bufio.Reader crc hash.Hash32 } func noEOF(err error) error { if err == io.EOF { return io.ErrUnexpectedEOF } return err } var le = binary.LittleEndian func NewReader(r io.Reader) (*Reader, error) { z := &Reader{Reader: bufio.NewReader(r)} if err := z.readHeader(); err != nil { return nil, err } return z, nil } func (z *Reader) readHeader() error { var buf [10]byte if _, err := io.ReadFull(z, buf[:10]); err != nil { return err } if buf[0] != gzipID1 || buf[1] != gzipID2 || buf[2] != gzipDeflate { return ErrHeader } flg := buf[3] if t := le.Uint32(buf[4:8]); t > 0 { z.ModTime = time.Unix(int64(t), 0) } z.OS = buf[9] z.crc = crc32.NewIEEE() z.crc.Write(buf[:10]) // init CRC if flg&flagExtra != 0 { if _, err := io.ReadFull(z, buf[:2]); err != nil { return noEOF(err) } z.crc.Write(buf[:2]) // update CRC data := make([]byte, le.Uint16(buf[:2])) if _, err := io.ReadFull(z, data); err != nil { return noEOF(err) } z.crc.Write(data) // update CRC z.Extra = data } if flg&flagName != 0 { s, err := z.readString() if err != nil { return err } z.Name = s } if flg&flagComment != 0 { s, err := z.readString() if err != nil { return err } z.Comment = s } if flg&flagHdrCrc != 0 { if _, err := io.ReadFull(z, buf[:2]); err != nil { return noEOF(err) } z.CRC = uint16(z.crc.Sum32()) if z.CRC != le.Uint16(buf[:2]) { return ErrHeader } } return nil } func (z *Reader) readString() (string, error) { s, err := z.ReadString(0) if err != nil { return "", err } io.WriteString(z.crc, s) // update CRC if l := len(s); l > 0 && s[l-1] == 0 { s = s[:l-1] // strip last zero } return s, nil } type Writer struct { Header wroteHeader bool w io.Writer } // TODO func NewWriter(w io.Writer) (*Writer, error) { z := &Writer{ Header: Header{ OS: osUnix, // UNIX }, w: w, } return z, nil } func (z *Writer) Write(p []byte) (n int, err error) { buf := [10]byte{0: gzipID1, 1: gzipID2, 2: gzipDeflate} if !z.wroteHeader { if z.Comment != "" { buf[3] |= flagComment } if z.Extra != nil { buf[3] |= flagExtra } if z.Name != "" { buf[3] |= flagName } if z.CRC != 0 { buf[3] |= flagHdrCrc } if !z.ModTime.IsZero() { le.PutUint32(buf[4:8], uint32(z.ModTime.Unix())) } buf[9] = z.OS if _, err := z.w.Write(buf[:10]); err != nil { return 0, err } if z.Extra != nil { if err := z.writeBytes(z.Extra); err != nil { return 0, err } } if z.Name != "" { if err := z.writeString(z.Name); err != nil { return 0, err } } if z.Comment != "" { if err := z.writeString(z.Comment); err != nil { return 0, err } } z.wroteHeader = true } return z.w.Write(p) } func (z *Writer) writeString(s string) error { if _, err := io.WriteString(z.w, s); err != nil { return err } if _, err := z.w.Write([]byte{0}); err != nil { return err } return nil } func (z *Writer) writeBytes(b []byte) error { if len(b) > 0xffff { return errors.New("binary data is too large") } var sz [2]byte le.PutUint16(sz[:2], uint16(len(b))) if _, err := z.w.Write(sz[:2]); err != nil { return err } if _, err := z.w.Write(b); err != nil { return err } return nil }