You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpng/pngUtils.go

165 lines
3.8 KiB
Go

package main
import (
"encoding/binary"
"errors"
"hash/crc32"
"io"
"os"
)
type ChunkData struct {
length uint32
name string
data []byte
crc uint32
raw []byte
}
type PngData struct {
header []byte
chunks []ChunkData
}
// Reads the png data from a file into the struct
func (p *PngData) Read(f *os.File) error {
valid, header := ValidatePng(f)
if valid {
p.header = header
err := p.readChunks(f)
if err != io.EOF {
return err
}
} else {
return errors.New("invalid png")
}
return nil
}
// writes all the data of the png into a new file
func (p *PngData) Write(f *os.File) error {
_, err := f.Write(p.header)
if err != nil {
return err
}
err = p.writeChunks(f)
return err
}
// reads all chunks from a png file.
// must be called after reading the header
func (p *PngData) readChunks(f *os.File) error {
p.chunks = make([]ChunkData, 0)
chunk, err := ReadChunk(f)
for err == nil {
p.chunks = append(p.chunks, chunk)
chunk, err = ReadChunk(f)
}
p.chunks = append(p.chunks, chunk)
return err
}
// writes all chunks to the given file
func (p *PngData) writeChunks(f *os.File) error {
for _, chunk := range p.chunks {
_, err := f.Write(chunk.raw)
if err != nil {
return err
}
}
return nil
}
// adds a meta chunk to the chunk data before the IDAT chunk.
func (p *PngData) AddMetaChunk(metaChunk ChunkData) {
newChunks := make([]ChunkData, 0)
appended := false
for _, chunk := range p.chunks {
if chunk.name == "IDAT" && !appended {
newChunks = append(newChunks, metaChunk)
newChunks = append(newChunks, chunk)
appended = true
} else {
newChunks = append(newChunks, chunk)
}
}
p.chunks = newChunks
}
// Returns the reference of a chunk by name
func (p *PngData) GetChunk(name string) *ChunkData {
for _, chunk := range p.chunks {
if chunk.name == name {
return &chunk
}
}
return nil
}
// validates the png by reading the header of the file
func ValidatePng(f *os.File) (bool, []byte) {
headerBytes := make([]byte, 8)
_, err := f.Read(headerBytes)
check(err)
firstByteMatch := headerBytes[0] == 0x89
pngAsciiMatch := string(headerBytes[1:4]) == "PNG"
dosCRLF := headerBytes[4] == 0x0d && headerBytes[5] == 0x0a
dosEof := headerBytes[6] == 0x1a
unixLF := headerBytes[7] == 0x0a
return firstByteMatch && pngAsciiMatch && dosCRLF && dosEof && unixLF, headerBytes
}
// reads the data of one chunk
// it is assumed that the file reader is at the beginning of the chunk when reading
func ReadChunk(f *os.File) (ChunkData, error) {
lengthRaw := make([]byte, 4)
_, err := f.Read(lengthRaw)
length := binary.BigEndian.Uint32(lengthRaw)
crcRaw := make([]byte, 4)
nameRaw := make([]byte, 4)
_, _ = f.Read(nameRaw)
name := string(nameRaw)
data := make([]byte, length)
_, err = f.Read(data)
_, err = f.Read(crcRaw)
crc := binary.BigEndian.Uint32(crcRaw)
fullData := make([]byte, 0)
fullData = append(fullData, lengthRaw...)
fullData = append(fullData, nameRaw...)
fullData = append(fullData, data...)
fullData = append(fullData, crcRaw...)
return ChunkData{
length: length,
name: name,
data: data,
crc: crc,
raw: fullData,
}, err
}
// creates a chunk with the given data and name
func CreateChunk(data []byte, name string) ChunkData {
rawLength := make([]byte, 4)
binary.BigEndian.PutUint32(rawLength, uint32(len(data)))
rawName := []byte(name)
dataAndName := make([]byte, 0)
dataAndName = append(dataAndName, rawName...)
dataAndName = append(dataAndName, data...)
crc := crc32.ChecksumIEEE(dataAndName)
rawCrc := make([]byte, 4)
binary.BigEndian.PutUint32(rawCrc, crc)
fullData := make([]byte, 0)
fullData = append(fullData, rawLength...)
fullData = append(fullData, dataAndName...)
fullData = append(fullData, rawCrc...)
return ChunkData{
length: uint32(len(data)),
name: name,
data: data,
crc: crc,
raw: fullData,
}
}