Resolve #199, init password protection spreadsheet support
This commit is contained in:
parent
88de2f8d51
commit
4177c1585e
|
@ -0,0 +1,304 @@
|
||||||
|
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||||
|
// this source code is governed by a BSD-style license that can be found in
|
||||||
|
// the LICENSE file.
|
||||||
|
//
|
||||||
|
// Package excelize providing a set of functions that allow you to write to
|
||||||
|
// and read from XLSX files. Support reads and writes XLSX file generated by
|
||||||
|
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
||||||
|
// charts of XLSX. This library needs Go version 1.10 or later.
|
||||||
|
|
||||||
|
package excelize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/xml"
|
||||||
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/richardlehane/mscfb"
|
||||||
|
"golang.org/x/crypto/md4"
|
||||||
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption
|
||||||
|
packageOffset = 8 // First 8 bytes are the size of the stream
|
||||||
|
packageEncryptionChunkSize = 4096
|
||||||
|
cryptoIdentifier = []byte{ // checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
|
||||||
|
0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
|
||||||
|
0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
|
||||||
|
0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
|
||||||
|
0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encryption specifies the encryption structure, streams, and storages are
|
||||||
|
// required when encrypting ECMA-376 documents.
|
||||||
|
type Encryption struct {
|
||||||
|
KeyData KeyData `xml:"keyData"`
|
||||||
|
DataIntegrity DataIntegrity `xml:"dataIntegrity"`
|
||||||
|
KeyEncryptors KeyEncryptors `xml:"keyEncryptors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyData specifies the cryptographic attributes used to encrypt the data.
|
||||||
|
type KeyData struct {
|
||||||
|
SaltSize int `xml:"saltSize,attr"`
|
||||||
|
BlockSize int `xml:"blockSize,attr"`
|
||||||
|
KeyBits int `xml:"keyBits,attr"`
|
||||||
|
HashSize int `xml:"hashSize,attr"`
|
||||||
|
CipherAlgorithm string `xml:"cipherAlgorithm,attr"`
|
||||||
|
CipherChaining string `xml:"cipherChaining,attr"`
|
||||||
|
HashAlgorithm string `xml:"hashAlgorithm,attr"`
|
||||||
|
SaltValue string `xml:"saltValue,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataIntegrity specifies the encrypted copies of the salt and hash values
|
||||||
|
// used to help ensure that the integrity of the encrypted data has not been
|
||||||
|
// compromised.
|
||||||
|
type DataIntegrity struct {
|
||||||
|
EncryptedHmacKey string `xml:"encryptedHmacKey,attr"`
|
||||||
|
EncryptedHmacValue string `xml:"encryptedHmacValue,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyEncryptors specifies the key encryptors used to encrypt the data.
|
||||||
|
type KeyEncryptors struct {
|
||||||
|
KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyEncryptor specifies that the schema used by this encryptor is the schema
|
||||||
|
// specified for password-based encryptors.
|
||||||
|
type KeyEncryptor struct {
|
||||||
|
XMLName xml.Name `xml:"keyEncryptor"`
|
||||||
|
URI string `xml:"uri,attr"`
|
||||||
|
EncryptedKey EncryptedKey `xml:"encryptedKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptedKey used to generate the encrypting key.
|
||||||
|
type EncryptedKey struct {
|
||||||
|
XMLName xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"`
|
||||||
|
SpinCount int `xml:"spinCount,attr"`
|
||||||
|
EncryptedVerifierHashInput string `xml:"encryptedVerifierHashInput,attr"`
|
||||||
|
EncryptedVerifierHashValue string `xml:"encryptedVerifierHashValue,attr"`
|
||||||
|
EncryptedKeyValue string `xml:"encryptedKeyValue,attr"`
|
||||||
|
KeyData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt API decrypt the CFB file format with Agile Encryption. Support
|
||||||
|
// cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, SHA384 and
|
||||||
|
// SHA512.
|
||||||
|
func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
|
||||||
|
doc, err := mscfb.New(bytes.NewReader(raw))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
encryptionInfoBuf, encryptedPackageBuf := extractPart(doc)
|
||||||
|
var encryptionInfo Encryption
|
||||||
|
if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Convert the password into an encryption key.
|
||||||
|
key, err := convertPasswdToKey(opt.Password, encryptionInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Use the key to decrypt the package key.
|
||||||
|
encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey
|
||||||
|
saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packageKey, err := crypt(false, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, key, saltValue, encryptedKeyValue)
|
||||||
|
// Use the package key to decrypt the package.
|
||||||
|
return cryptPackage(false, packageKey, encryptedPackageBuf, encryptionInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractPart extract data from storage by specified part name.
|
||||||
|
func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []byte) {
|
||||||
|
for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
|
||||||
|
switch entry.Name {
|
||||||
|
case "EncryptionInfo":
|
||||||
|
buf := make([]byte, entry.Size)
|
||||||
|
i, _ := doc.Read(buf)
|
||||||
|
if i > 0 {
|
||||||
|
encryptionInfoBuf = buf
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "EncryptedPackage":
|
||||||
|
buf := make([]byte, entry.Size)
|
||||||
|
i, _ := doc.Read(buf)
|
||||||
|
if i > 0 {
|
||||||
|
encryptedPackageBuf = buf
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertPasswdToKey convert the password into an encryption key.
|
||||||
|
func convertPasswdToKey(passwd string, encryption Encryption) (key []byte, err error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Write(saltValue)
|
||||||
|
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||||
|
passwordBuffer, err := encoder.Bytes([]byte(passwd))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Write(passwordBuffer)
|
||||||
|
// Generate the initial hash.
|
||||||
|
key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes())
|
||||||
|
// Now regenerate until spin count.
|
||||||
|
for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ {
|
||||||
|
iterator := createUInt32LEBuffer(i)
|
||||||
|
key = hashing(encryption.KeyData.HashAlgorithm, iterator, key)
|
||||||
|
}
|
||||||
|
// Now generate the final hash.
|
||||||
|
key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey)
|
||||||
|
// Truncate or pad as needed to get to length of keyBits.
|
||||||
|
keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8
|
||||||
|
if len(key) < keyBytes {
|
||||||
|
tmp := make([]byte, 0x36)
|
||||||
|
key = append(key, tmp...)
|
||||||
|
key = tmp
|
||||||
|
} else if len(key) > keyBytes {
|
||||||
|
key = key[:keyBytes]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashing data by specified hash algorithm.
|
||||||
|
func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) {
|
||||||
|
var hashMap = map[string]hash.Hash{
|
||||||
|
"md4": md4.New(),
|
||||||
|
"md5": md5.New(),
|
||||||
|
"ripemd-160": ripemd160.New(),
|
||||||
|
"sha1": sha1.New(),
|
||||||
|
"sha256": sha256.New(),
|
||||||
|
"sha384": sha512.New384(),
|
||||||
|
"sha512": sha512.New(),
|
||||||
|
}
|
||||||
|
handler, ok := hashMap[strings.ToLower(hashAlgorithm)]
|
||||||
|
if !ok {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
for _, buf := range buffer {
|
||||||
|
handler.Write(buf)
|
||||||
|
}
|
||||||
|
key = handler.Sum(nil)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// createUInt32LEBuffer create buffer with little endian 32-bit unsigned
|
||||||
|
// integer.
|
||||||
|
func createUInt32LEBuffer(value int) []byte {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(buf, uint32(value))
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEncryptionInfo parse the encryption info XML into an object.
|
||||||
|
func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) {
|
||||||
|
err = xml.Unmarshal(encryptionInfo, &encryption)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// crypt encrypt / decrypt input by given cipher algorithm, cipher chaining,
|
||||||
|
// key and initialization vector.
|
||||||
|
func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input []byte) (packageKey []byte, err error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return input, err
|
||||||
|
}
|
||||||
|
stream := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
stream.CryptBlocks(input, input)
|
||||||
|
return input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cryptPackage encrypt / decrypt package by given packageKey and encryption
|
||||||
|
// info.
|
||||||
|
func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) {
|
||||||
|
encryptedKey := encryption.KeyData
|
||||||
|
var offset = packageOffset
|
||||||
|
if encrypt {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
var i, start, end int
|
||||||
|
var iv, outputChunk []byte
|
||||||
|
for end < len(input) {
|
||||||
|
start = end
|
||||||
|
end = start + packageEncryptionChunkSize
|
||||||
|
|
||||||
|
if end > len(input) {
|
||||||
|
end = len(input)
|
||||||
|
}
|
||||||
|
// Grab the next chunk
|
||||||
|
var inputChunk []byte
|
||||||
|
if (end + offset) < len(input) {
|
||||||
|
inputChunk = input[start+offset : end+offset]
|
||||||
|
} else {
|
||||||
|
inputChunk = input[start+offset : end]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad the chunk if it is not an integer multiple of the block size
|
||||||
|
remainder := len(inputChunk) % encryptedKey.BlockSize
|
||||||
|
if remainder != 0 {
|
||||||
|
inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...)
|
||||||
|
}
|
||||||
|
// Create the initialization vector
|
||||||
|
iv, err = createIV(encrypt, i, encryption)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Encrypt/decrypt the chunk and add it to the array
|
||||||
|
outputChunk, err = crypt(encrypt, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, packageKey, iv, inputChunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outputChunks = append(outputChunks, outputChunk...)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// createIV create an initialization vector (IV).
|
||||||
|
func createIV(encrypt bool, blockKey int, encryption Encryption) ([]byte, error) {
|
||||||
|
encryptedKey := encryption.KeyData
|
||||||
|
// Create the block key from the current index
|
||||||
|
blockKeyBuf := createUInt32LEBuffer(blockKey)
|
||||||
|
var b bytes.Buffer
|
||||||
|
saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.Write(saltValue)
|
||||||
|
b.Write(blockKeyBuf)
|
||||||
|
// Create the initialization vector by hashing the salt with the block key.
|
||||||
|
// Truncate or pad as needed to meet the block size.
|
||||||
|
iv := hashing(encryptedKey.HashAlgorithm, b.Bytes())
|
||||||
|
if len(iv) < encryptedKey.BlockSize {
|
||||||
|
tmp := make([]byte, 0x36)
|
||||||
|
iv = append(iv, tmp...)
|
||||||
|
iv = tmp
|
||||||
|
} else if len(iv) > encryptedKey.BlockSize {
|
||||||
|
iv = iv[0:encryptedKey.BlockSize]
|
||||||
|
}
|
||||||
|
return iv, nil
|
||||||
|
}
|
47
excelize.go
47
excelize.go
|
@ -56,15 +56,30 @@ type File struct {
|
||||||
|
|
||||||
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
|
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
|
||||||
|
|
||||||
// OpenFile take the name of an spreadsheet file and returns a populated
|
// Options define the options for open spreadsheet.
|
||||||
// spreadsheet file struct for it.
|
type Options struct {
|
||||||
func OpenFile(filename string) (*File, error) {
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct
|
||||||
|
// for it. For example, open spreadsheet with password protection:
|
||||||
|
//
|
||||||
|
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func OpenFile(filename string, opt ...Options) (*File, error) {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
f, err := OpenReader(file)
|
var option Options
|
||||||
|
for _, o := range opt {
|
||||||
|
option = o
|
||||||
|
}
|
||||||
|
f, err := OpenReader(file, option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -91,25 +106,23 @@ func newFile() *File {
|
||||||
|
|
||||||
// OpenReader read data stream from io.Reader and return a populated
|
// OpenReader read data stream from io.Reader and return a populated
|
||||||
// spreadsheet file.
|
// spreadsheet file.
|
||||||
func OpenReader(r io.Reader) (*File, error) {
|
func OpenReader(r io.Reader, opt ...Options) (*File, error) {
|
||||||
b, err := ioutil.ReadAll(r)
|
b, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if bytes.Contains(b, cryptoIdentifier) {
|
||||||
|
var option Options
|
||||||
|
for _, o := range opt {
|
||||||
|
option = o
|
||||||
|
}
|
||||||
|
b, err = Decrypt(b, &option)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypted file failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
|
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
identifier := []byte{
|
|
||||||
// checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
|
|
||||||
0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
|
|
||||||
0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
|
|
||||||
0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
|
|
||||||
0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
|
|
||||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
|
||||||
}
|
|
||||||
if bytes.Contains(b, identifier) {
|
|
||||||
return nil, errors.New("not support encrypted file currently")
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,14 +201,14 @@ func TestCharsetTranscoder(t *testing.T) {
|
||||||
func TestOpenReader(t *testing.T) {
|
func TestOpenReader(t *testing.T) {
|
||||||
_, err := OpenReader(strings.NewReader(""))
|
_, err := OpenReader(strings.NewReader(""))
|
||||||
assert.EqualError(t, err, "zip: not a valid zip file")
|
assert.EqualError(t, err, "zip: not a valid zip file")
|
||||||
_, err = OpenReader(bytes.NewReader([]byte{
|
_, err = OpenReader(bytes.NewReader(cryptoIdentifier))
|
||||||
0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
|
assert.EqualError(t, err, "decrypted file failed")
|
||||||
0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
|
|
||||||
0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
|
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
|
||||||
0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
|
assert.NoError(t, err)
|
||||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
val, err := f.GetCellValue("Sheet1", "A1")
|
||||||
}))
|
assert.NoError(t, err)
|
||||||
assert.EqualError(t, err, "not support encrypted file currently")
|
assert.Equal(t, "SECRET", val)
|
||||||
|
|
||||||
// Test unexpected EOF.
|
// Test unexpected EOF.
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
|
19
go.mod
19
go.mod
|
@ -1,17 +1,14 @@
|
||||||
module github.com/360EntSecGroup-Skylar/excelize/v2
|
module github.com/360EntSecGroup-Skylar/excelize/v2
|
||||||
|
|
||||||
go 1.12
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/richardlehane/mscfb v1.0.3
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/xuri/efp v0.0.0-20191019043341-b7dc4fe9aa91
|
github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
|
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||||
golang.org/x/text v0.3.2 // indirect
|
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
golang.org/x/text v0.3.3
|
||||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
48
go.sum
48
go.sum
|
@ -1,39 +1,35 @@
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI=
|
||||||
|
github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||||
|
github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o=
|
||||||
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/xuri/efp v0.0.0-20191019043341-b7dc4fe9aa91 h1:gp02YctZuIPTk0t7qI+wvg3VQwTPyNmSGG6ZqOsjSL8=
|
github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf h1:spotWVWg9DP470pPFQ7LaYtUqDpWEOS/BUrSmwFZE4k=
|
||||||
github.com/xuri/efp v0.0.0-20191019043341-b7dc4fe9aa91/go.mod h1:uBiSUepVYMhGTfDeBKKasV4GpgBlzJ46gXUBAqV8qLk=
|
github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf/go.mod h1:uBiSUepVYMhGTfDeBKKasV4GpgBlzJ46gXUBAqV8qLk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||||
|
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue