Merged in feature/support-more-types (pull request #219)
support new types for import * tests pass * missing file * bug fixes
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
A reader for Microsoft's Compound File Binary File Format.
|
||||
|
||||
Example usage:
|
||||
|
||||
file, _ := os.Open("test/test.doc")
|
||||
defer file.Close()
|
||||
doc, err := mscfb.New(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
|
||||
buf := make([]byte, 512)
|
||||
i, _ := doc.Read(buf)
|
||||
if i > 0 {
|
||||
fmt.Println(buf[:i])
|
||||
}
|
||||
fmt.Println(entry.Name)
|
||||
}
|
||||
|
||||
The Compound File Binary File Format is also known as the Object Linking and Embedding (OLE) or Component Object Model (COM) format and was used by early MS software such as MS Office. See [http://msdn.microsoft.com/en-us/library/dd942138.aspx](http://msdn.microsoft.com/en-us/library/dd942138.aspx) for more details
|
||||
|
||||
Install with `go get github.com/richardlehane/mscfb`
|
||||
|
||||
[](https://travis-ci.org/richardlehane/mscfb)
|
||||
+525
@@ -0,0 +1,525 @@
|
||||
// Copyright 2013 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mscfb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/richardlehane/msoleps/types"
|
||||
)
|
||||
|
||||
// objectType types
|
||||
const (
|
||||
unknown uint8 = 0x0 // this means unallocated - typically zeroed dir entries
|
||||
storage uint8 = 0x1 // this means dir
|
||||
stream uint8 = 0x2 // this means file
|
||||
rootStorage uint8 = 0x5 // this means root
|
||||
)
|
||||
|
||||
type directoryEntryFields struct {
|
||||
rawName [32]uint16 //64 bytes, unicode string encoded in UTF-16. If root, "Root Entry\0" w
|
||||
nameLength uint16 //2 bytes
|
||||
objectType uint8 //1 byte Must be one of the types specified above
|
||||
color uint8 //1 byte Must be 0x00 RED or 0x01 BLACK
|
||||
leftSibID uint32 //4 bytes, Dir? Stream ID of left sibling, if none set to NOSTREAM
|
||||
rightSibID uint32 //4 bytes, Dir? Stream ID of right sibling, if none set to NOSTREAM
|
||||
childID uint32 //4 bytes, Dir? Stream ID of child object, if none set to NOSTREAM
|
||||
clsid types.Guid // Contains an object class GUID (must be set to zeroes for stream object)
|
||||
stateBits [4]byte // user-defined flags for storage object
|
||||
create types.FileTime // Windows FILETIME structure
|
||||
modify types.FileTime // Windows FILETIME structure
|
||||
startingSectorLoc uint32 // if a stream object, first sector location. If root, first sector of ministream
|
||||
streamSize [8]byte // if a stream, size of user-defined data. If root, size of ministream
|
||||
}
|
||||
|
||||
func makeDirEntry(b []byte) *directoryEntryFields {
|
||||
d := &directoryEntryFields{}
|
||||
for i := range d.rawName {
|
||||
d.rawName[i] = binary.LittleEndian.Uint16(b[i*2 : i*2+2])
|
||||
}
|
||||
d.nameLength = binary.LittleEndian.Uint16(b[64:66])
|
||||
d.objectType = uint8(b[66])
|
||||
d.color = uint8(b[67])
|
||||
d.leftSibID = binary.LittleEndian.Uint32(b[68:72])
|
||||
d.rightSibID = binary.LittleEndian.Uint32(b[72:76])
|
||||
d.childID = binary.LittleEndian.Uint32(b[76:80])
|
||||
d.clsid = types.MustGuid(b[80:96])
|
||||
copy(d.stateBits[:], b[96:100])
|
||||
d.create = types.MustFileTime(b[100:108])
|
||||
d.modify = types.MustFileTime(b[108:116])
|
||||
d.startingSectorLoc = binary.LittleEndian.Uint32(b[116:120])
|
||||
copy(d.streamSize[:], b[120:128])
|
||||
return d
|
||||
}
|
||||
|
||||
func (r *Reader) setDirEntries() error {
|
||||
c := 20
|
||||
if r.header.numDirectorySectors > 0 {
|
||||
c = int(r.header.numDirectorySectors)
|
||||
}
|
||||
de := make([]*File, 0, c)
|
||||
cycles := make(map[uint32]bool)
|
||||
num := int(r.sectorSize / 128)
|
||||
sn := r.header.directorySectorLoc
|
||||
for sn != endOfChain {
|
||||
buf, err := r.readAt(fileOffset(r.sectorSize, sn), int(r.sectorSize))
|
||||
if err != nil {
|
||||
return Error{ErrRead, "directory entries read error (" + err.Error() + ")", fileOffset(r.sectorSize, sn)}
|
||||
}
|
||||
for i := 0; i < num; i++ {
|
||||
f := &File{r: r}
|
||||
f.directoryEntryFields = makeDirEntry(buf[i*128:])
|
||||
fixFile(r.header.majorVersion, f)
|
||||
f.curSector = f.startingSectorLoc
|
||||
de = append(de, f)
|
||||
}
|
||||
nsn, err := r.findNext(sn, false)
|
||||
if err != nil {
|
||||
return Error{ErrRead, "directory entries error finding sector (" + err.Error() + ")", int64(nsn)}
|
||||
}
|
||||
if nsn <= sn {
|
||||
if nsn == sn || cycles[nsn] {
|
||||
return Error{ErrRead, "directory entries sector cycle", int64(nsn)}
|
||||
}
|
||||
cycles[nsn] = true
|
||||
}
|
||||
sn = nsn
|
||||
}
|
||||
r.direntries = de
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixFile(v uint16, f *File) {
|
||||
fixName(f)
|
||||
if f.objectType != stream {
|
||||
return
|
||||
}
|
||||
// if the MSCFB major version is 4, then this can be a uint64 otherwise is a uint32 and the least signficant bits can contain junk
|
||||
if v > 3 {
|
||||
f.Size = int64(binary.LittleEndian.Uint64(f.streamSize[:]))
|
||||
} else {
|
||||
f.Size = int64(binary.LittleEndian.Uint32(f.streamSize[:4]))
|
||||
}
|
||||
}
|
||||
|
||||
func fixName(f *File) {
|
||||
// From the spec:
|
||||
// "The length [name] MUST be a multiple of 2, and include the terminating null character in the count.
|
||||
// This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field."
|
||||
if f.nameLength < 4 || f.nameLength > 64 {
|
||||
return
|
||||
}
|
||||
nlen := int(f.nameLength/2 - 1)
|
||||
f.Initial = f.rawName[0]
|
||||
var slen int
|
||||
if !unicode.IsPrint(rune(f.Initial)) {
|
||||
slen = 1
|
||||
}
|
||||
f.Name = string(utf16.Decode(f.rawName[slen:nlen]))
|
||||
}
|
||||
|
||||
func (r *Reader) traverse() error {
|
||||
r.File = make([]*File, 0, len(r.direntries))
|
||||
var (
|
||||
recurse func(int, []string)
|
||||
err error
|
||||
counter int
|
||||
)
|
||||
recurse = func(i int, path []string) {
|
||||
// prevent cycles, number of recurse calls can't exceed number of directory entries
|
||||
counter++
|
||||
if counter > len(r.direntries) {
|
||||
err = Error{ErrTraverse, "traversal counter overflow", int64(i)}
|
||||
return
|
||||
}
|
||||
if i < 0 || i >= len(r.direntries) {
|
||||
err = Error{ErrTraverse, "illegal traversal index", int64(i)}
|
||||
return
|
||||
}
|
||||
file := r.direntries[i]
|
||||
if file.leftSibID != noStream {
|
||||
recurse(int(file.leftSibID), path)
|
||||
}
|
||||
r.File = append(r.File, file)
|
||||
file.Path = path
|
||||
if file.childID != noStream {
|
||||
if i > 0 {
|
||||
recurse(int(file.childID), append(path, file.Name))
|
||||
} else {
|
||||
recurse(int(file.childID), path)
|
||||
}
|
||||
}
|
||||
if file.rightSibID != noStream {
|
||||
recurse(int(file.rightSibID), path)
|
||||
}
|
||||
}
|
||||
recurse(0, []string{})
|
||||
return err
|
||||
}
|
||||
|
||||
// File represents a MSCFB directory entry
|
||||
type File struct {
|
||||
Name string // stream or directory name
|
||||
Initial uint16 // the first character in the name (identifies special streams such as MSOLEPS property sets)
|
||||
Path []string // file path
|
||||
Size int64 // size of stream
|
||||
i int64 // bytes read
|
||||
curSector uint32 // next sector for Read | Write
|
||||
rem int64 // offset in current sector remaining previous Read | Write
|
||||
*directoryEntryFields
|
||||
r *Reader
|
||||
}
|
||||
|
||||
type fileInfo struct{ *File }
|
||||
|
||||
func (fi fileInfo) Name() string { return fi.File.Name }
|
||||
func (fi fileInfo) Size() int64 {
|
||||
if fi.objectType != stream {
|
||||
return 0
|
||||
}
|
||||
return fi.File.Size
|
||||
}
|
||||
func (fi fileInfo) IsDir() bool { return fi.mode().IsDir() }
|
||||
func (fi fileInfo) ModTime() time.Time { return fi.Modified() }
|
||||
func (fi fileInfo) Mode() os.FileMode { return fi.File.mode() }
|
||||
func (fi fileInfo) Sys() interface{} { return nil }
|
||||
|
||||
func (f *File) mode() os.FileMode {
|
||||
if f.objectType != stream {
|
||||
return os.ModeDir | 0777
|
||||
}
|
||||
return 0666
|
||||
}
|
||||
|
||||
// FileInfo for this directory entry. Useful for IsDir() (whether a directory entry is a stream (file) or a storage object (dir))
|
||||
func (f *File) FileInfo() os.FileInfo {
|
||||
return fileInfo{f}
|
||||
}
|
||||
|
||||
// ID returns this directory entry's CLSID field
|
||||
func (f *File) ID() string {
|
||||
return f.clsid.String()
|
||||
}
|
||||
|
||||
// Created returns this directory entry's created field
|
||||
func (f *File) Created() time.Time {
|
||||
return f.create.Time()
|
||||
}
|
||||
|
||||
// Created returns this directory entry's modified field
|
||||
func (f *File) Modified() time.Time {
|
||||
return f.modify.Time()
|
||||
}
|
||||
|
||||
// Read this directory entry
|
||||
// Returns 0, io.EOF if no stream is available (i.e. for a storage object)
|
||||
func (f *File) Read(b []byte) (int, error) {
|
||||
if f.Size < 1 || f.i >= f.Size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
sz := len(b)
|
||||
if int64(sz) > f.Size-f.i {
|
||||
sz = int(f.Size - f.i)
|
||||
}
|
||||
// get sectors and lengths for reads
|
||||
str, err := f.stream(sz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// now read
|
||||
var idx, i int
|
||||
for _, v := range str {
|
||||
jdx := idx + int(v[1])
|
||||
if jdx < idx || jdx > sz {
|
||||
return 0, Error{ErrRead, "bad read length", int64(jdx)}
|
||||
}
|
||||
j, err := f.r.ra.ReadAt(b[idx:jdx], v[0])
|
||||
i = i + j
|
||||
if err != nil {
|
||||
f.i += int64(i)
|
||||
return i, Error{ErrRead, "underlying reader fail (" + err.Error() + ")", int64(idx)}
|
||||
}
|
||||
idx = jdx
|
||||
}
|
||||
f.i += int64(i)
|
||||
if i != sz {
|
||||
err = Error{ErrRead, "bytes read do not match expected read size", int64(i)}
|
||||
} else if i < len(b) {
|
||||
err = io.EOF
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
// Write to this directory entry
|
||||
// Depends on the io.ReaderAt supplied to mscfb.New() being a WriterAt too
|
||||
// Returns 0, io.EOF if no stream is available (i.e. for a storage object)
|
||||
func (f *File) Write(b []byte) (int, error) {
|
||||
if f.Size < 1 || f.i >= f.Size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if f.r.wa == nil {
|
||||
wa, ok := f.r.ra.(io.WriterAt)
|
||||
if !ok {
|
||||
return 0, Error{ErrWrite, "mscfb.New must be given ReaderAt convertible to a io.WriterAt in order to write", 0}
|
||||
}
|
||||
f.r.wa = wa
|
||||
}
|
||||
sz := len(b)
|
||||
if int64(sz) > f.Size-f.i {
|
||||
sz = int(f.Size - f.i)
|
||||
}
|
||||
// get sectors and lengths for writes
|
||||
str, err := f.stream(sz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// now read
|
||||
var idx, i int
|
||||
for _, v := range str {
|
||||
jdx := idx + int(v[1])
|
||||
if jdx < idx || jdx > sz {
|
||||
return 0, Error{ErrWrite, "bad write length", int64(jdx)}
|
||||
}
|
||||
j, err := f.r.wa.WriteAt(b[idx:jdx], v[0])
|
||||
i = i + j
|
||||
if err != nil {
|
||||
f.i += int64(i)
|
||||
return i, Error{ErrWrite, "underlying writer fail (" + err.Error() + ")", int64(idx)}
|
||||
}
|
||||
idx = jdx
|
||||
}
|
||||
f.i += int64(i)
|
||||
if i != sz {
|
||||
err = Error{ErrWrite, "bytes written do not match expected write size", int64(i)}
|
||||
} else if i < len(b) {
|
||||
err = io.EOF
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
// ReadAt reads p bytes at offset off from start of file. Does not affect seek place for other reads/writes.
|
||||
func (f *File) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
// memorize place
|
||||
mi, mrem, mcur := f.i, f.rem, f.curSector
|
||||
_, err = f.Seek(off, 0)
|
||||
if err == nil {
|
||||
n, err = f.Read(p)
|
||||
}
|
||||
f.i, f.rem, f.curSector = mi, mrem, mcur
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteAt reads p bytes at offset off from start of file. Does not affect seek place for other reads/writes.
|
||||
func (f *File) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
// memorize place
|
||||
mi, mrem, mcur := f.i, f.rem, f.curSector
|
||||
_, err = f.Seek(off, 0)
|
||||
if err == nil {
|
||||
n, err = f.Write(p)
|
||||
}
|
||||
f.i, f.rem, f.curSector = mi, mrem, mcur
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Seek sets the offset for the next Read or Write to offset, interpreted according to whence: 0 means relative to the
|
||||
// start of the file, 1 means relative to the current offset, and 2 means relative to the end. Seek returns the new
|
||||
// offset relative to the start of the file and an error, if any.
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
var abs int64
|
||||
switch whence {
|
||||
default:
|
||||
return 0, Error{ErrSeek, "invalid whence", int64(whence)}
|
||||
case 0:
|
||||
abs = offset
|
||||
case 1:
|
||||
abs = f.i + offset
|
||||
case 2:
|
||||
abs = f.Size - offset
|
||||
}
|
||||
switch {
|
||||
case abs < 0:
|
||||
return f.i, Error{ErrSeek, "can't seek before start of File", abs}
|
||||
case abs >= f.Size:
|
||||
return f.i, Error{ErrSeek, "can't seek past File length", abs}
|
||||
case abs == f.i:
|
||||
return abs, nil
|
||||
case abs > f.i:
|
||||
t := f.i
|
||||
f.i = abs
|
||||
return f.i, f.seek(abs - t)
|
||||
}
|
||||
if f.rem >= f.i-abs {
|
||||
f.rem = f.rem - (f.i - abs)
|
||||
f.i = abs
|
||||
return f.i, nil
|
||||
}
|
||||
f.rem = 0
|
||||
f.curSector = f.startingSectorLoc
|
||||
f.i = abs
|
||||
return f.i, f.seek(abs)
|
||||
}
|
||||
|
||||
func (f *File) seek(sz int64) error {
|
||||
// calculate ministream and sector size
|
||||
var mini bool
|
||||
var ss int64
|
||||
if f.Size < miniStreamCutoffSize {
|
||||
mini = true
|
||||
ss = 64
|
||||
} else {
|
||||
ss = int64(f.r.sectorSize)
|
||||
}
|
||||
|
||||
var j int64
|
||||
var err error
|
||||
// if we have a remainder in the current sector, use it first
|
||||
if f.rem > 0 {
|
||||
if ss-f.rem <= sz {
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j += ss - f.rem
|
||||
f.rem = 0
|
||||
if j == sz {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
f.rem += sz
|
||||
return nil
|
||||
}
|
||||
if f.curSector == endOfChain {
|
||||
return Error{ErrRead, "unexpected early end of chain", int64(f.curSector)}
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// check if we are at the last sector
|
||||
if sz-j < ss {
|
||||
f.rem = sz - j
|
||||
return nil
|
||||
} else {
|
||||
j += ss
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we might be at the last sector if there is no remainder, if so can return
|
||||
if j == sz {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return offsets and lengths for read or write
|
||||
func (f *File) stream(sz int) ([][2]int64, error) {
|
||||
// calculate ministream, cap for sector slice, and sector size
|
||||
var mini bool
|
||||
var l int
|
||||
var ss int64
|
||||
if f.Size < miniStreamCutoffSize {
|
||||
mini = true
|
||||
l = sz/64 + 2
|
||||
ss = 64
|
||||
} else {
|
||||
l = sz/int(f.r.sectorSize) + 2
|
||||
ss = int64(f.r.sectorSize)
|
||||
}
|
||||
|
||||
sectors := make([][2]int64, 0, l)
|
||||
var i, j int
|
||||
|
||||
// if we have a remainder from a previous read, use it first
|
||||
if f.rem > 0 {
|
||||
offset, err := f.r.getOffset(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ss-f.rem >= int64(sz) {
|
||||
sectors = append(sectors, [2]int64{offset + f.rem, int64(sz)})
|
||||
} else {
|
||||
sectors = append(sectors, [2]int64{offset + f.rem, ss - f.rem})
|
||||
}
|
||||
if ss-f.rem <= int64(sz) {
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j += int(ss - f.rem)
|
||||
f.rem = 0
|
||||
} else {
|
||||
f.rem += int64(sz)
|
||||
}
|
||||
if sectors[0][1] == int64(sz) {
|
||||
return sectors, nil
|
||||
}
|
||||
if f.curSector == endOfChain {
|
||||
return nil, Error{ErrRead, "unexpected early end of chain", int64(f.curSector)}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
for {
|
||||
// emergency brake!
|
||||
if i >= cap(sectors) {
|
||||
return nil, Error{ErrRead, "index overruns sector length", int64(i)}
|
||||
}
|
||||
// grab the next offset
|
||||
offset, err := f.r.getOffset(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check if we are at the last sector
|
||||
if sz-j < int(ss) {
|
||||
sectors = append(sectors, [2]int64{offset, int64(sz - j)})
|
||||
f.rem = int64(sz - j)
|
||||
return compressChain(sectors), nil
|
||||
} else {
|
||||
sectors = append(sectors, [2]int64{offset, ss})
|
||||
j += int(ss)
|
||||
f.curSector, err = f.r.findNext(f.curSector, mini)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we might be at the last sector if there is no remainder, if so can return
|
||||
if j == sz {
|
||||
return compressChain(sectors), nil
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func compressChain(locs [][2]int64) [][2]int64 {
|
||||
l := len(locs)
|
||||
for i, x := 0, 0; i < l && x+1 < len(locs); i++ {
|
||||
if locs[x][0]+locs[x][1] == locs[x+1][0] {
|
||||
locs[x][1] = locs[x][1] + locs[x+1][1]
|
||||
for j := range locs[x+1 : len(locs)-1] {
|
||||
locs[x+1+j] = locs[j+x+2]
|
||||
}
|
||||
locs = locs[:len(locs)-1]
|
||||
} else {
|
||||
x += 1
|
||||
}
|
||||
}
|
||||
return locs
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
// fuzzing with https://github.com/dvyukov/go-fuzz
|
||||
package mscfb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// todo: replace with Fuzzing from go test package
|
||||
func Fuzz(data []byte) int {
|
||||
doc, err := New(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if doc != nil {
|
||||
panic("doc != nil on error " + err.Error())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
for entry, err := doc.Next(); ; entry, err = doc.Next() {
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return 1
|
||||
}
|
||||
if entry != nil {
|
||||
panic("entry != nil on error " + err.Error())
|
||||
}
|
||||
}
|
||||
buf.Reset()
|
||||
buf.ReadFrom(entry)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
// Copyright 2013 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package mscfb implements a reader for Microsoft's Compound File Binary File Format (http://msdn.microsoft.com/en-us/library/dd942138.aspx).
|
||||
//
|
||||
// The Compound File Binary File Format is also known as the Object Linking and Embedding (OLE) or Component Object Model (COM) format and was used by many
|
||||
// early MS software such as MS Office.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// file, _ := os.Open("test/test.doc")
|
||||
// defer file.Close()
|
||||
// doc, err := mscfb.New(file)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
|
||||
// buf := make([]byte, 512)
|
||||
// i, _ := entry.Read(buf)
|
||||
// if i > 0 {
|
||||
// fmt.Println(buf[:i])
|
||||
// }
|
||||
// fmt.Println(entry.Name)
|
||||
// }
|
||||
package mscfb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func fileOffset(ss, sn uint32) int64 {
|
||||
return int64((sn + 1) * ss)
|
||||
}
|
||||
|
||||
const (
|
||||
signature uint64 = 0xE11AB1A1E011CFD0
|
||||
miniStreamSectorSize uint32 = 64
|
||||
miniStreamCutoffSize int64 = 4096
|
||||
dirEntrySize uint32 = 128 //128 bytes
|
||||
)
|
||||
|
||||
const (
|
||||
maxRegSect uint32 = 0xFFFFFFFA // Maximum regular sector number
|
||||
difatSect uint32 = 0xFFFFFFFC //Specifies a DIFAT sector in the FAT
|
||||
fatSect uint32 = 0xFFFFFFFD // Specifies a FAT sector in the FAT
|
||||
endOfChain uint32 = 0xFFFFFFFE // End of linked chain of sectors
|
||||
freeSect uint32 = 0xFFFFFFFF // Speficies unallocated sector in the FAT, Mini FAT or DIFAT
|
||||
maxRegStreamID uint32 = 0xFFFFFFFA // maximum regular stream ID
|
||||
noStream uint32 = 0xFFFFFFFF // empty pointer
|
||||
)
|
||||
|
||||
const lenHeader int = 8 + 16 + 10 + 6 + 12 + 8 + 16 + 109*4
|
||||
|
||||
const sliceLimit uint32 = 64000
|
||||
|
||||
type headerFields struct {
|
||||
signature uint64
|
||||
_ [16]byte //CLSID - ignore, must be null
|
||||
minorVersion uint16 //Version number for non-breaking changes. This field SHOULD be set to 0x003E if the major version field is either 0x0003 or 0x0004.
|
||||
majorVersion uint16 //Version number for breaking changes. This field MUST be set to either 0x0003 (version 3) or 0x0004 (version 4).
|
||||
_ [2]byte //byte order - ignore, must be little endian
|
||||
sectorSize uint16 //This field MUST be set to 0x0009, or 0x000c, depending on the Major Version field. This field specifies the sector size of the compound file as a power of 2. If Major Version is 3, then the Sector Shift MUST be 0x0009, specifying a sector size of 512 bytes. If Major Version is 4, then the Sector Shift MUST be 0x000C, specifying a sector size of 4096 bytes.
|
||||
_ [2]byte // ministream sector size - ignore, must be 64 bytes
|
||||
_ [6]byte // reserved - ignore, not used
|
||||
numDirectorySectors uint32 //This integer field contains the count of the number of directory sectors in the compound file. If Major Version is 3, then the Number of Directory Sectors MUST be zero. This field is not supported for version 3 compound files.
|
||||
numFatSectors uint32 //This integer field contains the count of the number of FAT sectors in the compound file.
|
||||
directorySectorLoc uint32 //This integer field contains the starting sector number for the directory stream.
|
||||
_ [4]byte // transaction - ignore, not used
|
||||
_ [4]byte // mini stream size cutooff - ignore, must be 4096 bytes
|
||||
miniFatSectorLoc uint32 //This integer field contains the starting sector number for the mini FAT.
|
||||
numMiniFatSectors uint32 //This integer field contains the count of the number of mini FAT sectors in the compound file.
|
||||
difatSectorLoc uint32 //This integer field contains the starting sector number for the DIFAT.
|
||||
numDifatSectors uint32 //This integer field contains the count of the number of DIFAT sectors in the compound file.
|
||||
initialDifats [109]uint32 //The first 109 difat sectors are included in the header
|
||||
}
|
||||
|
||||
func makeHeader(b []byte) *headerFields {
|
||||
h := &headerFields{}
|
||||
h.signature = binary.LittleEndian.Uint64(b[:8])
|
||||
h.minorVersion = binary.LittleEndian.Uint16(b[24:26])
|
||||
h.majorVersion = binary.LittleEndian.Uint16(b[26:28])
|
||||
h.sectorSize = binary.LittleEndian.Uint16(b[30:32])
|
||||
h.numDirectorySectors = binary.LittleEndian.Uint32(b[40:44])
|
||||
h.numFatSectors = binary.LittleEndian.Uint32(b[44:48])
|
||||
h.directorySectorLoc = binary.LittleEndian.Uint32(b[48:52])
|
||||
h.miniFatSectorLoc = binary.LittleEndian.Uint32(b[60:64])
|
||||
h.numMiniFatSectors = binary.LittleEndian.Uint32(b[64:68])
|
||||
h.difatSectorLoc = binary.LittleEndian.Uint32(b[68:72])
|
||||
h.numDifatSectors = binary.LittleEndian.Uint32(b[72:76])
|
||||
var idx int
|
||||
for i := 76; i < 512; i = i + 4 {
|
||||
h.initialDifats[idx] = binary.LittleEndian.Uint32(b[i : i+4])
|
||||
idx++
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
type header struct {
|
||||
*headerFields
|
||||
difats []uint32
|
||||
miniFatLocs []uint32
|
||||
miniStreamLocs []uint32 // chain of sectors containing the ministream
|
||||
}
|
||||
|
||||
func (r *Reader) setHeader() error {
|
||||
buf, err := r.readAt(0, lenHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.header = &header{headerFields: makeHeader(buf)}
|
||||
// sanity check - check signature
|
||||
if r.header.signature != signature {
|
||||
return Error{ErrFormat, "bad signature", int64(r.header.signature)}
|
||||
}
|
||||
// check for legal sector size
|
||||
if r.header.sectorSize == 0x0009 || r.header.sectorSize == 0x000c {
|
||||
r.sectorSize = uint32(1 << r.header.sectorSize)
|
||||
} else {
|
||||
return Error{ErrFormat, "illegal sector size", int64(r.header.sectorSize)}
|
||||
}
|
||||
// check for DIFAT overflow
|
||||
if r.header.numDifatSectors > 0 {
|
||||
sz := (r.sectorSize / 4) - 1
|
||||
if int(r.header.numDifatSectors*sz+109) < 0 {
|
||||
return Error{ErrFormat, "DIFAT int overflow", int64(r.header.numDifatSectors)}
|
||||
}
|
||||
if r.header.numDifatSectors*sz+109 > r.header.numFatSectors+sz {
|
||||
return Error{ErrFormat, "num DIFATs exceeds FAT sectors", int64(r.header.numDifatSectors)}
|
||||
}
|
||||
}
|
||||
// check for mini FAT overflow
|
||||
if r.header.numMiniFatSectors > 0 {
|
||||
if int(r.sectorSize/4*r.header.numMiniFatSectors) < 0 {
|
||||
return Error{ErrFormat, "mini FAT int overflow", int64(r.header.numMiniFatSectors)}
|
||||
}
|
||||
if r.header.numMiniFatSectors > r.header.numFatSectors*(r.sectorSize/miniStreamSectorSize) {
|
||||
return Error{ErrFormat, "num mini FATs exceeds FAT sectors", int64(r.header.numFatSectors)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) setDifats() error {
|
||||
r.header.difats = r.header.initialDifats[:]
|
||||
// return early if no extra DIFAT sectors
|
||||
if r.header.numDifatSectors == 0 {
|
||||
return nil
|
||||
}
|
||||
sz := (r.sectorSize / 4) - 1
|
||||
// prevent creation of an arbitrarily large slice
|
||||
if r.header.numDifatSectors < sliceLimit {
|
||||
n := make([]uint32, 109, r.header.numDifatSectors*sz+109)
|
||||
copy(n, r.header.difats)
|
||||
r.header.difats = n
|
||||
}
|
||||
off := r.header.difatSectorLoc
|
||||
cycles := make(map[uint32]bool)
|
||||
for i := 0; i < int(r.header.numDifatSectors); i++ {
|
||||
buf, err := r.readAt(fileOffset(r.sectorSize, off), int(r.sectorSize))
|
||||
if err != nil {
|
||||
return Error{ErrFormat, "error setting DIFAT(" + err.Error() + ")", int64(off)}
|
||||
}
|
||||
for j := 0; j < int(sz); j++ {
|
||||
r.header.difats = append(r.header.difats, binary.LittleEndian.Uint32(buf[j*4:j*4+4]))
|
||||
}
|
||||
// detect cycles in difat
|
||||
noff := binary.LittleEndian.Uint32(buf[len(buf)-4:])
|
||||
if noff <= off {
|
||||
if noff == off || cycles[noff] {
|
||||
return Error{ErrRead, "cycle detected in difat", int64(noff)}
|
||||
}
|
||||
cycles[noff] = true
|
||||
}
|
||||
off = noff
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// set the ministream FAT and sector slices in the header
|
||||
func (r *Reader) setMiniStream() error {
|
||||
// do nothing if there is no ministream
|
||||
if r.direntries[0].startingSectorLoc == endOfChain || r.header.miniFatSectorLoc == endOfChain || r.header.numMiniFatSectors == 0 {
|
||||
return nil
|
||||
}
|
||||
// build a slice of minifat sectors (akin to the DIFAT slice)
|
||||
c := int(r.header.numMiniFatSectors)
|
||||
r.header.miniFatLocs = make([]uint32, c)
|
||||
r.header.miniFatLocs[0] = r.header.miniFatSectorLoc
|
||||
for i := 1; i < c; i++ {
|
||||
loc, err := r.findNext(r.header.miniFatLocs[i-1], false)
|
||||
if err != nil {
|
||||
return Error{ErrFormat, "setting mini stream (" + err.Error() + ")", int64(r.header.miniFatLocs[i-1])}
|
||||
}
|
||||
r.header.miniFatLocs[i] = loc
|
||||
}
|
||||
// build a slice of ministream sectors
|
||||
c = int(r.sectorSize / 4 * r.header.numMiniFatSectors)
|
||||
// prevent creation of an arbitrarily large slice
|
||||
if r.header.numMiniFatSectors > sliceLimit {
|
||||
c = int(sliceLimit)
|
||||
}
|
||||
r.header.miniStreamLocs = make([]uint32, 0, c)
|
||||
cycles := make(map[uint32]bool)
|
||||
sn := r.direntries[0].startingSectorLoc
|
||||
for sn != endOfChain {
|
||||
r.header.miniStreamLocs = append(r.header.miniStreamLocs, sn)
|
||||
nsn, err := r.findNext(sn, false)
|
||||
if err != nil {
|
||||
return Error{ErrFormat, "setting mini stream (" + err.Error() + ")", int64(sn)}
|
||||
}
|
||||
if nsn <= sn {
|
||||
if nsn == sn || cycles[nsn] {
|
||||
return Error{ErrRead, "cycle detected in mini stream", int64(nsn)}
|
||||
}
|
||||
cycles[nsn] = true
|
||||
}
|
||||
sn = nsn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) readAt(offset int64, length int) ([]byte, error) {
|
||||
if r.slicer {
|
||||
b, err := r.ra.(slicer).Slice(offset, length)
|
||||
if err != nil {
|
||||
return nil, Error{ErrRead, "slicer read error (" + err.Error() + ")", offset}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
if length > len(r.buf) {
|
||||
return nil, Error{ErrRead, "read length greater than read buffer", int64(length)}
|
||||
}
|
||||
if _, err := r.ra.ReadAt(r.buf[:length], offset); err != nil {
|
||||
return nil, Error{ErrRead, err.Error(), offset}
|
||||
}
|
||||
return r.buf[:length], nil
|
||||
}
|
||||
|
||||
func (r *Reader) getOffset(sn uint32, mini bool) (int64, error) {
|
||||
if mini {
|
||||
num := r.sectorSize / 64
|
||||
sec := int(sn / num)
|
||||
if sec >= len(r.header.miniStreamLocs) {
|
||||
return 0, Error{ErrRead, "minisector number is outside minisector range", int64(sec)}
|
||||
}
|
||||
dif := sn % num
|
||||
return int64((r.header.miniStreamLocs[sec]+1)*r.sectorSize + dif*64), nil
|
||||
}
|
||||
return fileOffset(r.sectorSize, sn), nil
|
||||
}
|
||||
|
||||
// check the FAT sector for the next sector in a chain
|
||||
func (r *Reader) findNext(sn uint32, mini bool) (uint32, error) {
|
||||
entries := r.sectorSize / 4
|
||||
index := int(sn / entries) // find position in DIFAT or minifat array
|
||||
var sect uint32
|
||||
if mini {
|
||||
if index < 0 || index >= len(r.header.miniFatLocs) {
|
||||
return 0, Error{ErrRead, "minisector index is outside miniFAT range", int64(index)}
|
||||
}
|
||||
sect = r.header.miniFatLocs[index]
|
||||
} else {
|
||||
if index < 0 || index >= len(r.header.difats) {
|
||||
return 0, Error{ErrRead, "FAT index is outside DIFAT range", int64(index)}
|
||||
}
|
||||
sect = r.header.difats[index]
|
||||
}
|
||||
fatIndex := sn % entries // find position within FAT or MiniFAT sector
|
||||
offset := fileOffset(r.sectorSize, sect) + int64(fatIndex*4)
|
||||
buf := make([]byte, 4)
|
||||
_, err := r.ra.ReadAt(buf, offset)
|
||||
if err != nil {
|
||||
return 0, Error{ErrRead, "bad read finding next sector (" + err.Error() + ")", offset}
|
||||
}
|
||||
return binary.LittleEndian.Uint32(buf), nil
|
||||
}
|
||||
|
||||
// Reader provides sequential access to the contents of a MS compound file (MSCFB)
|
||||
type Reader struct {
|
||||
slicer bool
|
||||
sectorSize uint32
|
||||
buf []byte
|
||||
header *header
|
||||
File []*File // File is an ordered slice of final directory entries.
|
||||
direntries []*File // unordered raw directory entries
|
||||
entry int
|
||||
|
||||
ra io.ReaderAt
|
||||
wa io.WriterAt
|
||||
}
|
||||
|
||||
// New returns a MSCFB reader
|
||||
func New(ra io.ReaderAt) (*Reader, error) {
|
||||
r := &Reader{ra: ra}
|
||||
if _, ok := ra.(slicer); ok {
|
||||
r.slicer = true
|
||||
} else {
|
||||
r.buf = make([]byte, lenHeader)
|
||||
}
|
||||
if err := r.setHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// resize the buffer to 4096 if sector size isn't 512
|
||||
if !r.slicer && int(r.sectorSize) > len(r.buf) {
|
||||
r.buf = make([]byte, r.sectorSize)
|
||||
}
|
||||
if err := r.setDifats(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.setDirEntries(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.setMiniStream(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.traverse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ID returns the CLSID (class ID) field from the root directory entry
|
||||
func (r *Reader) ID() string {
|
||||
return r.File[0].ID()
|
||||
}
|
||||
|
||||
// Created returns the created field from the root directory entry
|
||||
func (r *Reader) Created() time.Time {
|
||||
return r.File[0].Created()
|
||||
}
|
||||
|
||||
// Modified returns the last modified field from the root directory entry
|
||||
func (r *Reader) Modified() time.Time {
|
||||
return r.File[0].Modified()
|
||||
}
|
||||
|
||||
// Next iterates to the next directory entry.
|
||||
// This isn't necessarily an adjacent *File within the File slice, but is based on the Left Sibling, Right Sibling and Child information in directory entries.
|
||||
func (r *Reader) Next() (*File, error) {
|
||||
r.entry++
|
||||
if r.entry >= len(r.File) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return r.File[r.entry], nil
|
||||
}
|
||||
|
||||
// Read the current directory entry
|
||||
func (r *Reader) Read(b []byte) (n int, err error) {
|
||||
if r.entry >= len(r.File) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return r.File[r.entry].Read(b)
|
||||
}
|
||||
|
||||
// Debug provides granular information from an mscfb file to assist with debugging
|
||||
func (r *Reader) Debug() map[string][]uint32 {
|
||||
ret := map[string][]uint32{
|
||||
"sector size": {r.sectorSize},
|
||||
"mini fat locs": r.header.miniFatLocs,
|
||||
"mini stream locs": r.header.miniStreamLocs,
|
||||
"directory sector": {r.header.directorySectorLoc},
|
||||
"mini stream start/size": {r.File[0].startingSectorLoc, binary.LittleEndian.Uint32(r.File[0].streamSize[:])},
|
||||
}
|
||||
for f, err := r.Next(); err == nil; f, err = r.Next() {
|
||||
ret[f.Name+" start/size"] = []uint32{f.startingSectorLoc, binary.LittleEndian.Uint32(f.streamSize[:])}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
const (
|
||||
// ErrFormat reports issues with the MSCFB's header structures
|
||||
ErrFormat = iota
|
||||
// ErrRead reports issues attempting to read MSCFB streams
|
||||
ErrRead
|
||||
// ErrSeek reports seek issues
|
||||
ErrSeek
|
||||
// ErrWrite reports write issues
|
||||
ErrWrite
|
||||
// ErrTraverse reports issues attempting to traverse the child-parent-sibling relations
|
||||
// between MSCFB storage objects
|
||||
ErrTraverse
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
typ int
|
||||
msg string
|
||||
val int64
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return "mscfb: " + e.msg + "; " + strconv.FormatInt(e.val, 10)
|
||||
}
|
||||
|
||||
// Typ gives the type of MSCFB error
|
||||
func (e Error) Typ() int {
|
||||
return e.typ
|
||||
}
|
||||
|
||||
// Slicer interface avoids a copy by obtaining a byte slice directly from the underlying reader
|
||||
type slicer interface {
|
||||
Slice(offset int64, length int) ([]byte, error)
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//The CURRENCY type specifies currency information. It is represented as an 8-byte integer, scaled by 10,000, to give a fixed-point number with 15 digits to the left of the decimal point, and four digits to the right. This representation provides a range of 922337203685477.5807 to –922337203685477.5808. For example, $5.25 is stored as the value 52500.
|
||||
|
||||
type Currency int64
|
||||
|
||||
func (c Currency) String() string {
|
||||
return "$" + strconv.FormatFloat(float64(c)/10000, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func (c Currency) Type() string {
|
||||
return "Currency"
|
||||
}
|
||||
|
||||
func (c Currency) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func MakeCurrency(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return Currency(0), ErrType
|
||||
}
|
||||
return Currency(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/cc237601.aspx
|
||||
type Date float64
|
||||
|
||||
func (d Date) Time() time.Time {
|
||||
start := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
|
||||
day := float64(time.Hour * 24)
|
||||
dur := time.Duration(day * float64(d))
|
||||
return start.Add(dur)
|
||||
}
|
||||
|
||||
func (d Date) String() string {
|
||||
return d.Time().String()
|
||||
}
|
||||
|
||||
func (d Date) Type() string {
|
||||
return "Date"
|
||||
}
|
||||
|
||||
func (d Date) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func MakeDate(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return Date(0), ErrType
|
||||
}
|
||||
return Date(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/cc237603.aspx
|
||||
type Decimal struct {
|
||||
res [2]byte
|
||||
scale byte
|
||||
sign byte
|
||||
high32 uint32
|
||||
low64 uint64
|
||||
}
|
||||
|
||||
func (d Decimal) Type() string {
|
||||
return "Decimal"
|
||||
}
|
||||
|
||||
func (d Decimal) Length() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
func (d Decimal) String() string {
|
||||
h, l, b := new(big.Int), new(big.Int), new(big.Int)
|
||||
l.SetUint64(d.low64)
|
||||
h.Lsh(big.NewInt(int64(d.high32)), 64)
|
||||
b.Add(h, l)
|
||||
q, f, r := new(big.Rat), new(big.Rat), new(big.Rat)
|
||||
q.SetFloat64(math.Pow10(int(d.scale)))
|
||||
r.Quo(f.SetInt(b), q)
|
||||
if d.sign == 0x80 {
|
||||
r.Neg(r)
|
||||
}
|
||||
return r.FloatString(20)
|
||||
}
|
||||
|
||||
func MakeDecimal(b []byte) (Type, error) {
|
||||
if len(b) < 16 {
|
||||
return Decimal{}, ErrType
|
||||
}
|
||||
return Decimal{
|
||||
res: [2]byte{b[0], b[1]},
|
||||
scale: b[2],
|
||||
sign: b[3],
|
||||
high32: binary.LittleEndian.Uint32(b[4:8]),
|
||||
low64: binary.LittleEndian.Uint64(b[8:16]),
|
||||
}, nil
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Win FILETIME type
|
||||
// http://msdn.microsoft.com/en-us/library/cc230324.aspx
|
||||
type FileTime struct {
|
||||
Low uint32 // Windows FILETIME structure
|
||||
High uint32 // Windows FILETIME structure
|
||||
}
|
||||
|
||||
const (
|
||||
tick uint64 = 10000000
|
||||
gregToUnix uint64 = 11644473600
|
||||
)
|
||||
|
||||
func winToUnix(low, high uint32) int64 {
|
||||
gregTime := ((uint64(high) << 32) + uint64(low)) / tick
|
||||
if gregTime < gregToUnix {
|
||||
return 0
|
||||
}
|
||||
return int64(gregTime - gregToUnix)
|
||||
}
|
||||
|
||||
func (f FileTime) Time() time.Time {
|
||||
return time.Unix(winToUnix(f.Low, f.High), 0)
|
||||
}
|
||||
|
||||
func (f FileTime) String() string {
|
||||
return f.Time().String()
|
||||
}
|
||||
|
||||
func (f FileTime) Type() string {
|
||||
return "FileTime"
|
||||
}
|
||||
|
||||
func (f FileTime) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func MakeFileTime(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return FileTime{}, ErrType
|
||||
}
|
||||
return MustFileTime(b), nil
|
||||
}
|
||||
|
||||
func MustFileTime(b []byte) FileTime {
|
||||
return FileTime{
|
||||
Low: binary.LittleEndian.Uint32(b[:4]),
|
||||
High: binary.LittleEndian.Uint32(b[4:8]),
|
||||
}
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Win GUID and UUID type
|
||||
// http://msdn.microsoft.com/en-us/library/cc230326.aspx
|
||||
type Guid struct {
|
||||
DataA uint32
|
||||
DataB uint16
|
||||
DataC uint16
|
||||
DataD [8]byte
|
||||
}
|
||||
|
||||
func (g Guid) String() string {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(buf[:4], g.DataA)
|
||||
binary.BigEndian.PutUint16(buf[4:6], g.DataB)
|
||||
binary.BigEndian.PutUint16(buf[6:], g.DataC)
|
||||
return strings.ToUpper("{" +
|
||||
hex.EncodeToString(buf[:4]) +
|
||||
"-" +
|
||||
hex.EncodeToString(buf[4:6]) +
|
||||
"-" +
|
||||
hex.EncodeToString(buf[6:]) +
|
||||
"-" +
|
||||
hex.EncodeToString(g.DataD[:2]) +
|
||||
"-" +
|
||||
hex.EncodeToString(g.DataD[2:]) +
|
||||
"}")
|
||||
}
|
||||
|
||||
func (g Guid) Type() string {
|
||||
return "Guid"
|
||||
}
|
||||
|
||||
func (g Guid) Length() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
func GuidFromString(str string) (Guid, error) {
|
||||
gerr := "Invalid GUID: expecting in format {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, got " + str
|
||||
if len(str) != 38 {
|
||||
return Guid{}, errors.New(gerr + "; bad length, should be 38 chars")
|
||||
}
|
||||
trimmed := strings.Trim(str, "{}")
|
||||
parts := strings.Split(trimmed, "-")
|
||||
if len(parts) != 5 {
|
||||
return Guid{}, errors.New(gerr + "; expecting should five '-' separators")
|
||||
}
|
||||
buf, err := hex.DecodeString(strings.Join(parts, ""))
|
||||
if err != nil {
|
||||
return Guid{}, errors.New(gerr + "; error decoding hex: " + err.Error())
|
||||
}
|
||||
return makeGuid(buf, binary.BigEndian), nil
|
||||
}
|
||||
|
||||
func MakeGuid(b []byte) (Type, error) {
|
||||
if len(b) < 16 {
|
||||
return Guid{}, ErrType
|
||||
}
|
||||
return makeGuid(b, binary.LittleEndian), nil
|
||||
}
|
||||
|
||||
func makeGuid(b []byte, order binary.ByteOrder) Guid {
|
||||
g := Guid{
|
||||
DataA: order.Uint32(b[:4]),
|
||||
DataB: order.Uint16(b[4:6]),
|
||||
DataC: order.Uint16(b[6:8]),
|
||||
DataD: [8]byte{},
|
||||
}
|
||||
copy(g.DataD[:], b[8:])
|
||||
return g
|
||||
}
|
||||
|
||||
func MustGuidFromString(str string) Guid {
|
||||
g, err := GuidFromString(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func MustGuid(b []byte) Guid {
|
||||
return makeGuid(b, binary.LittleEndian)
|
||||
}
|
||||
|
||||
func GuidFromName(n string) (Guid, error) {
|
||||
n = strings.ToLower(n)
|
||||
buf, err := charConvert([]byte(n))
|
||||
if err != nil {
|
||||
return Guid{}, err
|
||||
}
|
||||
return makeGuid(buf, binary.LittleEndian), nil
|
||||
}
|
||||
|
||||
func charConvert(in []byte) ([]byte, error) {
|
||||
if len(in) != 26 {
|
||||
return nil, errors.New("invalid GUID: expecting 26 characters")
|
||||
}
|
||||
out := make([]byte, 16)
|
||||
var idx, shift uint
|
||||
var b byte
|
||||
for _, v := range in {
|
||||
this, ok := characterMapping[v]
|
||||
if !ok {
|
||||
return nil, errors.New("invalid Guid: invalid character")
|
||||
}
|
||||
b = b | this<<shift
|
||||
if shift >= 3 {
|
||||
out[idx] = b
|
||||
idx++
|
||||
b = this >> (8 - shift) // write any remainder back to b, or 0 if shift is 3
|
||||
}
|
||||
shift = shift + 5
|
||||
if shift > 7 {
|
||||
shift = shift - 8
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
const (
|
||||
charA byte = iota
|
||||
charB
|
||||
charC
|
||||
charD
|
||||
charE
|
||||
charF
|
||||
charG
|
||||
charH
|
||||
charI
|
||||
charJ
|
||||
charK
|
||||
charL
|
||||
charM
|
||||
charN
|
||||
charO
|
||||
charP
|
||||
charQ
|
||||
charR
|
||||
charS
|
||||
charT
|
||||
charU
|
||||
charV
|
||||
charW
|
||||
charX
|
||||
charY
|
||||
charZ
|
||||
char0
|
||||
char1
|
||||
char2
|
||||
char3
|
||||
char4
|
||||
char5
|
||||
)
|
||||
|
||||
var characterMapping = map[byte]byte{
|
||||
'a': charA,
|
||||
'b': charB,
|
||||
'c': charC,
|
||||
'd': charD,
|
||||
'e': charE,
|
||||
'f': charF,
|
||||
'g': charG,
|
||||
'h': charH,
|
||||
'i': charI,
|
||||
'j': charJ,
|
||||
'k': charK,
|
||||
'l': charL,
|
||||
'm': charM,
|
||||
'n': charN,
|
||||
'o': charO,
|
||||
'p': charP,
|
||||
'q': charQ,
|
||||
'r': charR,
|
||||
's': charS,
|
||||
't': charT,
|
||||
'u': charU,
|
||||
'v': charV,
|
||||
'w': charW,
|
||||
'x': charX,
|
||||
'y': charY,
|
||||
'z': charZ,
|
||||
'0': char0,
|
||||
'1': char1,
|
||||
'2': char2,
|
||||
'3': char3,
|
||||
'4': char4,
|
||||
'5': char5,
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (i Null) Type() string {
|
||||
return "Null"
|
||||
}
|
||||
|
||||
func (i Null) Length() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (i Null) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type Bool bool
|
||||
|
||||
func (i Bool) Type() string {
|
||||
return "Boolean"
|
||||
}
|
||||
|
||||
func (i Bool) Length() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i Bool) String() string {
|
||||
if i {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func MakeBool(b []byte) (Type, error) {
|
||||
if len(b) < 2 {
|
||||
return Bool(false), ErrType
|
||||
}
|
||||
switch binary.LittleEndian.Uint16(b[:2]) {
|
||||
case 0xFFFF:
|
||||
return Bool(true), nil
|
||||
case 0x0000:
|
||||
return Bool(false), nil
|
||||
}
|
||||
return Bool(false), ErrType
|
||||
}
|
||||
|
||||
type I1 int8
|
||||
|
||||
func (i I1) Type() string {
|
||||
return "Int8"
|
||||
}
|
||||
|
||||
func (i I1) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func (i I1) Length() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func MakeI1(b []byte) (Type, error) {
|
||||
if len(b) < 1 {
|
||||
return I1(0), ErrType
|
||||
}
|
||||
return I1(b[0]), nil
|
||||
}
|
||||
|
||||
type I2 int16
|
||||
|
||||
func (i I2) Type() string {
|
||||
return "Int16"
|
||||
}
|
||||
|
||||
func (i I2) Length() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i I2) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeI2(b []byte) (Type, error) {
|
||||
if len(b) < 2 {
|
||||
return I2(0), ErrType
|
||||
}
|
||||
return I2(binary.LittleEndian.Uint16(b[:2])), nil
|
||||
}
|
||||
|
||||
type I4 int32
|
||||
|
||||
func (i I4) Type() string {
|
||||
return "Int32"
|
||||
}
|
||||
|
||||
func (i I4) Length() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (i I4) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeI4(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return I4(0), ErrType
|
||||
}
|
||||
return I4(binary.LittleEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
type I8 int64
|
||||
|
||||
func (i I8) Type() string {
|
||||
return "Int64"
|
||||
}
|
||||
|
||||
func (i I8) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (i I8) String() string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
}
|
||||
|
||||
func MakeI8(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return I8(0), ErrType
|
||||
}
|
||||
return I8(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
|
||||
type UI1 uint8
|
||||
|
||||
func (i UI1) Type() string {
|
||||
return "Uint8"
|
||||
}
|
||||
|
||||
func (i UI1) Length() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (i UI1) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeUI1(b []byte) (Type, error) {
|
||||
if len(b) < 1 {
|
||||
return UI1(0), ErrType
|
||||
}
|
||||
return UI1(b[0]), nil
|
||||
}
|
||||
|
||||
type UI2 uint16
|
||||
|
||||
func (i UI2) Type() string {
|
||||
return "Uint16"
|
||||
}
|
||||
|
||||
func (i UI2) Length() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i UI2) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func MakeUI2(b []byte) (Type, error) {
|
||||
if len(b) < 2 {
|
||||
return UI2(0), ErrType
|
||||
}
|
||||
return UI2(binary.LittleEndian.Uint16(b[:2])), nil
|
||||
}
|
||||
|
||||
type UI4 uint32
|
||||
|
||||
func (i UI4) Type() string {
|
||||
return "Uint32"
|
||||
}
|
||||
|
||||
func (i UI4) Length() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (i UI4) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func MakeUI4(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return UI4(0), ErrType
|
||||
}
|
||||
return UI4(binary.LittleEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
type UI8 uint64
|
||||
|
||||
func (i UI8) Type() string {
|
||||
return "Uint64"
|
||||
}
|
||||
|
||||
func (i UI8) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (i UI8) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func MakeUI8(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return UI8(0), ErrType
|
||||
}
|
||||
return UI8(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
|
||||
type R4 float32
|
||||
|
||||
func (r R4) Type() string {
|
||||
return "Float32"
|
||||
}
|
||||
|
||||
func (r R4) Length() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (r R4) String() string {
|
||||
return strconv.FormatFloat(float64(r), 'f', -1, 32)
|
||||
}
|
||||
|
||||
func MakeR4(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return R4(0), ErrType
|
||||
}
|
||||
return R4(binary.LittleEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
type R8 float64
|
||||
|
||||
func (r R8) Type() string {
|
||||
return "Float64"
|
||||
}
|
||||
|
||||
func (r R8) Length() int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (r R8) String() string {
|
||||
return strconv.FormatFloat(float64(r), 'f', -1, 64)
|
||||
}
|
||||
|
||||
func MakeR8(b []byte) (Type, error) {
|
||||
if len(b) < 8 {
|
||||
return R8(0), ErrType
|
||||
}
|
||||
return R8(binary.LittleEndian.Uint64(b[:8])), nil
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
func nullTerminated(s string) string {
|
||||
return s[:strings.Index(s, "\x00")]
|
||||
}
|
||||
|
||||
type UnicodeString []uint16
|
||||
|
||||
func (s UnicodeString) Type() string {
|
||||
return "UnicodeString"
|
||||
}
|
||||
|
||||
func (s UnicodeString) Length() int {
|
||||
return 4 + len(s)*2
|
||||
}
|
||||
|
||||
func (s UnicodeString) String() string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return nullTerminated(string(utf16.Decode(s)))
|
||||
}
|
||||
|
||||
func MakeUnicode(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return UnicodeString{}, ErrType
|
||||
}
|
||||
l := int(binary.LittleEndian.Uint32(b[:4]))
|
||||
if l == 0 {
|
||||
return UnicodeString{}, nil
|
||||
}
|
||||
if len(b) < l*2+4 {
|
||||
return UnicodeString{}, ErrType
|
||||
}
|
||||
s := make(UnicodeString, l)
|
||||
for i := range s {
|
||||
start := i*2 + 4
|
||||
s[i] = binary.LittleEndian.Uint16(b[start : start+2])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type CodeString struct {
|
||||
id CodePageID
|
||||
Chars []byte
|
||||
}
|
||||
|
||||
func (s *CodeString) SetId(i CodePageID) {
|
||||
s.id = i
|
||||
}
|
||||
|
||||
func (s *CodeString) Encoding() string {
|
||||
return CodePageIDs[s.id]
|
||||
}
|
||||
|
||||
func (s *CodeString) Type() string {
|
||||
return "CodeString"
|
||||
}
|
||||
|
||||
func (s *CodeString) Length() int {
|
||||
return 4 + len(s.Chars)
|
||||
}
|
||||
|
||||
func (s *CodeString) String() string {
|
||||
if len(s.Chars) == 0 {
|
||||
return ""
|
||||
}
|
||||
if s.id == 1200 {
|
||||
chars := make([]uint16, len(s.Chars)/2)
|
||||
for i := range chars {
|
||||
chars[i] = binary.LittleEndian.Uint16(s.Chars[i*2 : i*2+2])
|
||||
}
|
||||
return nullTerminated(string(utf16.Decode(chars)))
|
||||
}
|
||||
return nullTerminated(string(s.Chars))
|
||||
}
|
||||
|
||||
func MakeCodeString(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return &CodeString{}, ErrType
|
||||
}
|
||||
s := &CodeString{}
|
||||
l := int(binary.LittleEndian.Uint32(b[:4]))
|
||||
if l == 0 {
|
||||
return s, nil
|
||||
}
|
||||
if len(b) < l+4 {
|
||||
return s, ErrType
|
||||
}
|
||||
s.Chars = make([]byte, l)
|
||||
copy(s.Chars, b[4:l+4])
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type CodePageID uint16
|
||||
|
||||
var CodePageIDs map[CodePageID]string = map[CodePageID]string{
|
||||
37: "IBM037 - IBM EBCDIC US-Canada",
|
||||
437: "IBM437 - OEM United States",
|
||||
500: "IBM500 - IBM EBCDIC International",
|
||||
708: "ASMO-708 - Arabic (ASMO 708)",
|
||||
709: "Arabic (ASMO-449+, BCON V4)",
|
||||
710: "Arabic - Transparent Arabic",
|
||||
720: "DOS-720 - Arabic (Transparent ASMO); Arabic (DOS)",
|
||||
737: "ibm737 - OEM Greek (formerly 437G); Greek (DOS)",
|
||||
775: "ibm775 - OEM Baltic; Baltic (DOS)",
|
||||
850: "ibm850 - OEM Multilingual Latin 1; Western European (DOS)",
|
||||
852: "ibm852 - OEM Latin 2; Central European (DOS)",
|
||||
855: "IBM855 - OEM Cyrillic (primarily Russian)",
|
||||
857: "ibm857 - OEM Turkish; Turkish (DOS)",
|
||||
858: "IBM00858 - OEM Multilingual Latin 1 + Euro symbol",
|
||||
860: "IBM860 - OEM Portuguese; Portuguese (DOS)",
|
||||
861: "ibm861 - OEM Icelandic; Icelandic (DOS)",
|
||||
862: "DOS-862 - OEM Hebrew; Hebrew (DOS)",
|
||||
863: "IBM863 - OEM French Canadian; French Canadian (DOS)",
|
||||
864: "IBM864 - OEM Arabic; Arabic (864)",
|
||||
865: "IBM865 - OEM Nordic; Nordic (DOS)",
|
||||
866: "cp866 - OEM Russian; Cyrillic (DOS)",
|
||||
869: "ibm869 - OEM Modern Greek; Greek, Modern (DOS)",
|
||||
870: "IBM870 - IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2",
|
||||
874: "windows-874 - ANSI/OEM Thai (ISO 8859-11); Thai (Windows)",
|
||||
875: "cp875 - IBM EBCDIC Greek Modern",
|
||||
932: "shift_jis - ANSI/OEM Japanese; Japanese (Shift-JIS)",
|
||||
936: "gb2312 - ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)",
|
||||
949: "ks_c_5601-1987 - ANSI/OEM Korean (Unified Hangul Code)",
|
||||
950: "big5 - ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)",
|
||||
1026: "IBM1026 - IBM EBCDIC Turkish (Latin 5)",
|
||||
1047: "IBM01047 - BM EBCDIC Latin 1/Open System",
|
||||
1140: "IBM01140 - IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)",
|
||||
1141: "IBM01141 - IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)",
|
||||
1142: "IBM01142 - IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)",
|
||||
1143: "IBM01143 - IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)",
|
||||
1144: "IBM01144 - IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)",
|
||||
1145: "IBM01145 - IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)",
|
||||
1146: "IBM01146 - IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)",
|
||||
1147: "IBM01147 - IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)",
|
||||
1148: "IBM01148 - IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)",
|
||||
1149: "IBM01149 - IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)",
|
||||
1200: "utf-16 - Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications",
|
||||
1201: "unicodeFFFE - Unicode UTF-16, big endian byte order; available only to managed applications",
|
||||
1250: "windows-1250 - ANSI Central European; Central European (Windows)",
|
||||
1251: "windows-1251 - ANSI Cyrillic; Cyrillic (Windows)",
|
||||
1252: "windows-1252 - ANSI Latin 1; Western European (Windows)",
|
||||
1253: "windows-1253 - ANSI Greek; Greek (Windows)",
|
||||
1254: "windows-1254 - ANSI Turkish; Turkish (Windows)",
|
||||
1255: "windows-1255 - ANSI Hebrew; Hebrew (Windows)",
|
||||
1256: "windows-1256 - ANSI Arabic; Arabic (Windows)",
|
||||
1257: "windows-1257 - ANSI Baltic; Baltic (Windows)",
|
||||
1258: "windows-1258 - ANSI/OEM Vietnamese; Vietnamese (Windows)",
|
||||
1361: "Johab - Korean (Johab)",
|
||||
10000: "macintosh - MAC Roman; Western European (Mac)",
|
||||
10001: "x-mac-japanese - Japanese (Mac)",
|
||||
10002: "x-mac-chinesetrad - MAC Traditional Chinese (Big5); Chinese Traditional (Mac)",
|
||||
10003: "x-mac-korean - Korean (Mac)",
|
||||
10004: "x-mac-arabic - Arabic (Mac)",
|
||||
10005: "x-mac-hebrew - Hebrew (Mac)",
|
||||
10006: "x-mac-greek - Greek (Mac)",
|
||||
10007: "x-mac-cyrillic - Cyrillic (Mac)",
|
||||
10008: "x-mac-chinesesimp - MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)",
|
||||
10010: "x-mac-romanian - Romanian (Mac)",
|
||||
10017: "x-mac-ukrainian - Ukrainian (Mac)",
|
||||
10021: "x-mac-thai - Thai (Mac)",
|
||||
10029: "x-mac-ce - MAC Latin 2; Central European (Mac)",
|
||||
10079: "x-mac-icelandic - Icelandic (Mac)",
|
||||
10081: "x-mac-turkish - Turkish (Mac)",
|
||||
10082: "x-mac-croatian - Croatian (Mac)",
|
||||
12000: "utf-32 - Unicode UTF-32, little endian byte order; available only to managed applications",
|
||||
12001: "utf-32BE - Unicode UTF-32, big endian byte order; available only to managed applications",
|
||||
20000: "x-Chinese_CNS - CNS Taiwan; Chinese Traditional (CNS)",
|
||||
20001: "x-cp20001 - TCA Taiwan",
|
||||
20002: "x_Chinese-Eten - Eten Taiwan; Chinese Traditional (Eten)",
|
||||
20003: "x-cp20003 - IBM5550 Taiwan",
|
||||
20004: "x-cp20004 - TeleText Taiwan",
|
||||
20005: "x-cp20005 - Wang Taiwan",
|
||||
20105: "x-IA5 - IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)",
|
||||
20106: "x-IA5-German - IA5 German (7-bit)",
|
||||
20107: "x-IA5-Swedish - IA5 Swedish (7-bit)",
|
||||
20108: "x-IA5-Norwegian - IA5 Norwegian (7-bit)",
|
||||
20127: "us-ascii - US-ASCII (7-bit)",
|
||||
20261: "x-cp20261 - T.61",
|
||||
20269: "x-cp20269 - ISO 6937 Non-Spacing Accent",
|
||||
20273: "IBM273 - IBM EBCDIC Germany",
|
||||
20277: "IBM277 - IBM EBCDIC Denmark-Norway",
|
||||
20278: "IBM278 - IBM EBCDIC Finland-Sweden",
|
||||
20280: "IBM280 - IBM EBCDIC Italy",
|
||||
20284: "IBM284 - IBM EBCDIC Latin America-Spain",
|
||||
20285: "IBM285 - IBM EBCDIC United Kingdom",
|
||||
20290: "IBM290 - IBM EBCDIC Japanese Katakana Extended",
|
||||
20297: "IBM297 - IBM EBCDIC France",
|
||||
20420: "IBM420 - IBM EBCDIC Arabic",
|
||||
20423: "IBM423 - IBM EBCDIC Greek",
|
||||
20424: "IBM424 - IBM EBCDIC Hebrew",
|
||||
20833: "x-EBCDIC-KoreanExtended - IBM EBCDIC Korean Extended",
|
||||
20838: "IBM-Thai - IBM EBCDIC Thai",
|
||||
20866: "koi8-r - Russian (KOI8-R); Cyrillic (KOI8-R)",
|
||||
20871: "IBM871 - IBM EBCDIC Icelandic",
|
||||
20880: "IBM880 - IBM EBCDIC Cyrillic Russian",
|
||||
20905: "IBM905 - IBM EBCDIC Turkish",
|
||||
20924: "IBM00924 - IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)",
|
||||
20932: "EUC-JP - Japanese (JIS 0208-1990 and 0212-1990)",
|
||||
20936: "x-cp20936 - Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)",
|
||||
20949: "x-cp20949 - Korean Wansung",
|
||||
21025: "cp1025 - IBM EBCDIC Cyrillic Serbian-Bulgarian",
|
||||
21027: "(deprecated)",
|
||||
21866: "koi8-u - Ukrainian (KOI8-U); Cyrillic (KOI8-U)",
|
||||
28591: "iso-8859-1 - ISO 8859-1 Latin 1; Western European (ISO)",
|
||||
28592: "iso-8859-2 - ISO 8859-2 Central European; Central European (ISO)",
|
||||
28593: "iso-8859-3 - ISO 8859-3 Latin 3",
|
||||
28594: "iso-8859-4 - ISO 8859-4 Baltic",
|
||||
28595: "iso-8859-5 - ISO 8859-5 Cyrillic",
|
||||
28596: "iso-8859-6 - ISO 8859-6 Arabic",
|
||||
28597: "iso-8859-7 - ISO 8859-7 Greek",
|
||||
28598: "iso-8859-8 - ISO 8859-8 Hebrew; Hebrew (ISO-Visual)",
|
||||
28599: "iso-8859-9 - ISO 8859-9 Turkish",
|
||||
28603: "iso-8859-13 - ISO 8859-13 Estonian",
|
||||
28605: "iso-8859-15 - ISO 8859-15 Latin 9",
|
||||
29001: "x-Europa - Europa 3",
|
||||
38598: "iso-8859-8-i - ISO 8859-8 Hebrew; Hebrew (ISO-Logical)",
|
||||
50220: "iso-2022-jp - ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)",
|
||||
50221: "csISO2022JP - ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)",
|
||||
50222: "iso-2022-jp - ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)",
|
||||
50225: "iso-2022-kr - ISO 2022 Korean",
|
||||
50227: "x-cp50227 - ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)",
|
||||
50229: "ISO 2022 - Traditional Chinese",
|
||||
50930: "EBCDIC - Japanese (Katakana) Extended",
|
||||
50931: "EBCDIC - US-Canada and Japanese",
|
||||
50933: "EBCDIC - Korean Extended and Korean",
|
||||
50935: "EBCDIC - Simplified Chinese Extended and Simplified Chinese",
|
||||
50936: "EBCDIC - Simplified Chinese",
|
||||
50937: "EBCDIC - US-Canada and Traditional Chinese",
|
||||
50939: "EBCDIC - Japanese (Latin) Extended and Japanese",
|
||||
51932: "euc-jp - EUC Japanese",
|
||||
51936: "EUC-CN - EUC Simplified Chinese; Chinese Simplified (EUC)",
|
||||
51949: "euc-kr - EUC Korean",
|
||||
51950: "EUC - Traditional Chinese",
|
||||
52936: "hz-gb-2312 - HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)",
|
||||
54936: "GB18030 - Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)",
|
||||
57002: "x-iscii-de - ISCII Devanagari",
|
||||
57003: "x-iscii-be - ISCII Bengali",
|
||||
57004: "x-iscii-ta - ISCII Tamil",
|
||||
57005: "x-iscii-te - ISCII Telugu",
|
||||
57006: "x-iscii-as - ISCII Assamese",
|
||||
57007: "x-iscii-or - ISCII Oriya",
|
||||
57008: "x-iscii-ka - ISCII Kannada",
|
||||
57009: "x-iscii-ma - ISCII Malayalam",
|
||||
57010: "x-iscii-gu - ISCII Gujarati",
|
||||
57011: "x-iscii-pa - ISCII Punjabi",
|
||||
65000: "utf-7 - Unicode (UTF-7)",
|
||||
65001: "utf-8 - Unicode (UTF-8)",
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// Copyright 2014 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// MakeVariant is defined in vectorArray.go. It calls Evaluate, which refers to the MakeTypes map, so must add at runtime
|
||||
func init() { MakeTypes[VT_VARIANT] = MakeVariant }
|
||||
|
||||
var (
|
||||
ErrType = errors.New("msoleps: error coercing byte stream to type")
|
||||
ErrUnknownType = errors.New("msoleps: unknown type error")
|
||||
)
|
||||
|
||||
type Type interface {
|
||||
String() string
|
||||
Type() string
|
||||
Length() int
|
||||
}
|
||||
|
||||
const (
|
||||
scalar uint16 = iota
|
||||
vector
|
||||
array
|
||||
)
|
||||
|
||||
func Evaluate(b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return I1(0), ErrType
|
||||
}
|
||||
id := TypeID(binary.LittleEndian.Uint16(b[:2]))
|
||||
f, ok := MakeTypes[id]
|
||||
if !ok {
|
||||
return I1(0), ErrUnknownType
|
||||
}
|
||||
switch binary.LittleEndian.Uint16(b[2:4]) {
|
||||
case vector:
|
||||
return MakeVector(f, b[4:])
|
||||
case array:
|
||||
return MakeArray(f, b[4:])
|
||||
case scalar:
|
||||
if id != VT_VARIANT { // a VT_VARIANT can only be in a vector or array
|
||||
return f(b[4:])
|
||||
}
|
||||
}
|
||||
return I1(0), ErrUnknownType
|
||||
|
||||
}
|
||||
|
||||
type TypeID uint16
|
||||
|
||||
const (
|
||||
VT_EMPTY TypeID = iota // 0x00
|
||||
VT_NULL
|
||||
VT_I2
|
||||
VT_I4
|
||||
VT_R4
|
||||
VT_R8
|
||||
VT_CY
|
||||
VT_DATE
|
||||
VT_BSTR
|
||||
_
|
||||
VT_ERROR
|
||||
VT_BOOL
|
||||
VT_VARIANT
|
||||
_
|
||||
VT_DECIMAL
|
||||
_
|
||||
VT_I1
|
||||
VT_U1
|
||||
VT_UI2
|
||||
VT_UI4
|
||||
VT_I8
|
||||
VT_UI8
|
||||
VT_INT
|
||||
VT_UINT //0x17
|
||||
_ = iota + 5
|
||||
VT_LPSTR //0x1E
|
||||
VT_LPWSTR
|
||||
VT_FILETIME = iota + 0x25 // 0x40
|
||||
VT_BLOB
|
||||
VT_STREAM
|
||||
VT_STORAGE
|
||||
VT_STREAMED_OBJECT
|
||||
VT_STORED_OBJECT
|
||||
VT_BLOB_OBJECT
|
||||
VT_CF
|
||||
VT_CLSID
|
||||
VT_VERSIONED_STREAM // 0x49
|
||||
)
|
||||
|
||||
type MakeType func([]byte) (Type, error)
|
||||
|
||||
var MakeTypes map[TypeID]MakeType = map[TypeID]MakeType{
|
||||
VT_I2: MakeI2,
|
||||
VT_I4: MakeI4,
|
||||
VT_R4: MakeR4,
|
||||
VT_R8: MakeR8,
|
||||
VT_CY: MakeCurrency,
|
||||
VT_DATE: MakeDate,
|
||||
VT_BSTR: MakeCodeString,
|
||||
VT_BOOL: MakeBool,
|
||||
VT_DECIMAL: MakeDecimal,
|
||||
VT_I1: MakeI1,
|
||||
VT_U1: MakeUI1,
|
||||
VT_UI2: MakeUI2,
|
||||
VT_UI4: MakeUI4,
|
||||
VT_I8: MakeI8,
|
||||
VT_UI8: MakeUI8,
|
||||
VT_INT: MakeI4,
|
||||
VT_UINT: MakeUI4,
|
||||
VT_LPSTR: MakeCodeString,
|
||||
VT_LPWSTR: MakeUnicode,
|
||||
VT_FILETIME: MakeFileTime,
|
||||
VT_CLSID: MakeGuid,
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Copyright 2015 Richard Lehane. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type Vector []Type
|
||||
|
||||
func (v Vector) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v Vector) Type() string {
|
||||
if len(v) > 0 {
|
||||
return "Vector of " + v[0].Type()
|
||||
}
|
||||
return "Vector (empty)"
|
||||
}
|
||||
|
||||
func (v Vector) Length() int {
|
||||
ret := 4
|
||||
for _, t := range v {
|
||||
ret += t.Length()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func MakeVector(f MakeType, b []byte) (Type, error) {
|
||||
if len(b) < 4 {
|
||||
return Vector{}, ErrType
|
||||
}
|
||||
l := int(binary.LittleEndian.Uint32(b[:4]))
|
||||
v := make(Vector, l)
|
||||
place := 4
|
||||
for i := 0; i < l; i++ {
|
||||
t, err := f(b[place:])
|
||||
if err != nil {
|
||||
return Vector{}, ErrType
|
||||
}
|
||||
v[i] = t
|
||||
place += t.Length()
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type Array [][]Type
|
||||
|
||||
func (a Array) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a Array) Type() string {
|
||||
if len(a) > 0 && len(a[0]) > 0 {
|
||||
return "Array of " + a[0][0].Type()
|
||||
}
|
||||
return "Array (empty)"
|
||||
}
|
||||
|
||||
func (a Array) Length() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO: Array not implemented yet
|
||||
func MakeArray(f MakeType, b []byte) (Type, error) {
|
||||
return Array{}, nil
|
||||
}
|
||||
|
||||
type Variant struct {
|
||||
t Type
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
return "Typed Property Value containing " + v.t.String()
|
||||
}
|
||||
|
||||
func (v Variant) Type() string {
|
||||
return "Typed Property Value containing " + v.t.Type()
|
||||
}
|
||||
|
||||
func (v Variant) Length() int {
|
||||
return 4 + v.t.Length()
|
||||
}
|
||||
|
||||
func MakeVariant(b []byte) (Type, error) {
|
||||
if len(b) < 4 || binary.LittleEndian.Uint16(b[2:4]) != scalar { // only scalar values allowed
|
||||
return Variant{}, ErrType
|
||||
}
|
||||
id := TypeID(binary.LittleEndian.Uint16(b[:2]))
|
||||
if id == VT_VARIANT {
|
||||
return Variant{}, ErrType // no recursive types allowed
|
||||
}
|
||||
f, ok := MakeTypes[id]
|
||||
if !ok {
|
||||
return Variant{}, ErrUnknownType
|
||||
}
|
||||
t, err := f(b[4:])
|
||||
if err != nil {
|
||||
return Variant{}, err
|
||||
}
|
||||
return Variant{t}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user