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

164 lines
4.6 KiB

package office
import (
"encoding/binary"
"errors"
"io"
"slices"
)
const headerSize = 8
// recordType is an enumeration that specifies the record type of an atom record or a container record
// ([MS-PPT] 2.13.24 RecordType)
type recordType uint16
const (
recordTypeUnspecified recordType = 0
recordTypeDocument recordType = 0x03E8
recordTypeSlide recordType = 0x03EE
recordTypeEnvironment recordType = 0x03F2
recordTypeSlidePersistAtom recordType = 0x03F3
recordTypeSlideShowSlideInfoAtom recordType = 0x03F9
recordTypeExternalObjectList recordType = 0x0409
recordTypeDrawingGroup recordType = 0x040B
recordTypeDrawing recordType = 0x040C
recordTypeList recordType = 0x07D0
recordTypeSoundCollection recordType = 0x07E4
recordTypeTextCharsAtom recordType = 0x0FA0
recordTypeTextBytesAtom recordType = 0x0FA8
recordTypeHeadersFooters recordType = 0x0FD9
recordTypeSlideListWithText recordType = 0x0FF0
recordTypeUserEditAtom recordType = 0x0FF5
recordTypePersistDirectoryAtom recordType = 0x1772
recordTypeRoundTripSlideSyncInfo12 recordType = 0x3714
)
type readerAtAdapter struct {
r io.Reader
readBytes []byte
}
func ToReaderAt(r io.Reader) io.ReaderAt {
ra, ok := r.(io.ReaderAt)
if ok {
return ra
}
return &readerAtAdapter{
r: r,
}
}
func (r *readerAtAdapter) ReadAt(p []byte, off int64) (n int, err error) {
if int(off)+len(p) > len(r.readBytes) {
err := r.expandBuffer(int(off) + len(p))
if err != nil {
return 0, err
}
}
return bytesReaderAt(r.readBytes).ReadAt(p, off)
}
func (r *readerAtAdapter) expandBuffer(newSize int) error {
if cap(r.readBytes) < newSize {
r.readBytes = slices.Grow(r.readBytes, newSize-cap(r.readBytes))
}
newPart := r.readBytes[len(r.readBytes):newSize]
n, err := r.r.Read(newPart)
switch {
case err == nil:
r.readBytes = r.readBytes[:newSize]
case errors.Is(err, io.EOF):
r.readBytes = r.readBytes[:len(r.readBytes)+n]
default:
return err
}
return nil
}
func BytesReadAt(src []byte, dst []byte, off int64) (n int, err error) {
return bytesReaderAt(src).ReadAt(dst, off)
}
type bytesReaderAt []byte
func (bra bytesReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
idx := 0
for i := int(off); i < len(bra) && idx < len(p); i, idx = i+1, idx+1 {
p[idx] = bra[i]
}
if idx != len(p) {
return idx, io.EOF
}
return idx, nil
}
// LowerPart returns lower byte of record type
func (r recordType) LowerPart() byte {
const fullByte = 0xFF
return byte(r & fullByte)
}
var errMismatchRecordType = errors.New("mismatch record type")
type record struct {
header [headerSize]byte
recordData
}
// Type returns recordType of record contained in it's header
func (r record) Type() recordType {
return recordType(binary.LittleEndian.Uint16(r.header[2:4]))
}
// Length returns data length contained in record header
func (r record) Length() uint32 {
return binary.LittleEndian.Uint32(r.header[4:8])
}
// Data returns all data from record except header
func (r record) Data() []byte {
return r.recordData
}
type recordData []byte
// ReadAt copies bytes from record data at given offset into buffer p
func (rd recordData) ReadAt(p []byte, off int64) (n int, err error) {
return BytesReadAt(rd, p, off)
}
// LongAt interprets 4 bytes of record data at given offset as uint32 value and returns it
func (rd recordData) LongAt(offset int) uint32 {
return binary.LittleEndian.Uint32(rd[offset:])
}
// readRecord reads header and data of record. If wantedType is specified (not equals recordTypeUnspecified),
// also compares read type with the wanted one and returns an error is they are not equal
func readRecord(f io.ReaderAt, offset int64, wantedType recordType) (record, error) {
r, err := readRecordHeaderOnly(f, offset, wantedType)
if err != nil {
return record{}, err
}
r.recordData = make([]byte, r.Length())
_, err = f.ReadAt(r.recordData, offset+headerSize)
if err != nil {
return record{}, err
}
return r, nil
}
// readRecordHeaderOnly reads header of record. If wantedType is specified (not equals recordTypeUnspecified),
// also compares read type with the wanted one and returns an error is they are not equal
func readRecordHeaderOnly(f io.ReaderAt, offset int64, wantedType recordType) (record, error) {
r := record{}
_, err := f.ReadAt(r.header[:], offset)
if err != nil {
return record{}, err
}
if wantedType != recordTypeUnspecified && r.Type() != wantedType {
return record{}, errMismatchRecordType
}
return r, nil
}