Skip to content

Commit 273dce2

Browse files
authoredFeb 11, 2017
Merge pull request #16 from Feedbooks/obfuscated_fonts
We add decoder function to deobfuscated fonts from adobe and idpf methods
2 parents dc8df77 + c17000c commit 273dce2

10 files changed

+288
-13
lines changed
 

‎.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "test/readium-test-files"]
2+
path = test/readium-test-files
3+
url = git@github.com:readium/readium-test-files.git

‎decoder/adobe_fonts.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package decoder
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"io/ioutil"
8+
"strconv"
9+
"strings"
10+
"unicode"
11+
12+
"github.com/feedbooks/r2-streamer-go/models"
13+
)
14+
15+
func init() {
16+
decoderList = append(decoderList, List{decoderAlgorithm: "http://ns.adobe.com/pdf/enc#RC", decoder: DecodeAdobeFont})
17+
}
18+
19+
// DecodeAdobeFont decode obfuscate fonts using idpf spec http://www.idpf.org/epub/20/spec/FontManglingSpec.html
20+
func DecodeAdobeFont(publication models.Publication, link models.Link, reader io.ReadSeeker) (io.ReadSeeker, error) {
21+
var count int
22+
23+
key := getAdobeHashKey(publication)
24+
if string(key) == "" {
25+
return nil, errors.New("can't find hash key")
26+
}
27+
28+
buff, _ := ioutil.ReadAll(reader)
29+
if len(buff) > 1024 {
30+
count = 1024
31+
} else {
32+
count = len(buff)
33+
}
34+
35+
j := 0
36+
for i := 0; i < count; i++ {
37+
buff[i] = buff[i] ^ key[j]
38+
39+
j++
40+
if j == 16 {
41+
j = 0
42+
}
43+
}
44+
readerSeeker := bytes.NewReader(buff)
45+
return readerSeeker, nil
46+
}
47+
48+
func getAdobeHashKey(publication models.Publication) []byte {
49+
var stringKey []rune
50+
var key []byte
51+
52+
id := strings.Replace(publication.Metadata.Identifier, "urn:uuid:", "", -1)
53+
id = strings.Replace(id, "-", "", -1)
54+
for _, c := range id {
55+
if !unicode.IsSpace(c) {
56+
stringKey = append(stringKey, c)
57+
}
58+
}
59+
60+
for i := 0; i < 16; i++ {
61+
byteHex := stringKey[i*2 : i*2+2]
62+
byteNumer, _ := strconv.ParseInt(string(byteHex), 16, 32)
63+
key = append(key, byte(byteNumer))
64+
}
65+
66+
return key
67+
}

‎decoder/decoder.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package decoder
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
8+
"github.com/feedbooks/r2-streamer-go/models"
9+
)
10+
11+
// List TODO add doc
12+
type List struct {
13+
decoderAlgorithm string
14+
decoder (func(models.Publication, models.Link, io.ReadSeeker) (io.ReadSeeker, error))
15+
}
16+
17+
var decoderList []List
18+
19+
// Decode decode the ressource
20+
func Decode(publication models.Publication, link models.Link, reader io.ReadSeeker) (io.ReadSeeker, error) {
21+
22+
fmt.Println(link.CryptAlgorithm)
23+
for _, decoderFunc := range decoderList {
24+
if link.CryptAlgorithm == decoderFunc.decoderAlgorithm {
25+
return decoderFunc.decoder(publication, link, reader)
26+
}
27+
}
28+
29+
return nil, errors.New("can't find fetcher")
30+
}

‎decoder/fonts_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package decoder
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"os"
7+
"testing"
8+
9+
"github.com/feedbooks/r2-streamer-go/models"
10+
"github.com/feedbooks/r2-streamer-go/parser"
11+
. "github.com/smartystreets/goconvey/convey"
12+
)
13+
14+
var testPublication models.Publication
15+
var testFonts []byte
16+
17+
func init() {
18+
19+
testPublication, _ = parser.Parse("../test/readium-test-files/functional/smoke-tests/SmokeTestFXL")
20+
ft, _ := os.Open("../test/readium-test-files/functional/smoke-tests/SmokeTestFXL/fonts/cut-cut.woff")
21+
testFonts, _ = ioutil.ReadAll(ft)
22+
}
23+
24+
func TestAdobeFonts(t *testing.T) {
25+
26+
f, _ := os.Open("../test/readium-test-files/functional/smoke-tests/SmokeTestFXL/fonts/cut-cut.adb.woff")
27+
28+
Convey("Given cut-cut.adb.woff fonts", t, func() {
29+
fd, _ := DecodeAdobeFont(testPublication, models.Link{}, f)
30+
buff, _ := ioutil.ReadAll(fd)
31+
Convey("The adobe fonts is deobfuscated", func() {
32+
So(bytes.Equal(buff, testFonts), ShouldBeTrue)
33+
})
34+
35+
})
36+
37+
}
38+
39+
func TestIdpfFonts(t *testing.T) {
40+
41+
f, _ := os.Open("../test/readium-test-files/functional/smoke-tests/SmokeTestFXL/fonts/cut-cut.obf.woff")
42+
43+
Convey("Given cut-cut.obf.woff fonts", t, func() {
44+
fd, _ := DecodeIdpfFont(testPublication, models.Link{}, f)
45+
buff, _ := ioutil.ReadAll(fd)
46+
Convey("The idpf fonts is deobfuscated", func() {
47+
So(bytes.Equal(buff, testFonts), ShouldBeTrue)
48+
})
49+
50+
})
51+
52+
}

‎decoder/idpf_fonts.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package decoder
2+
3+
import (
4+
"bytes"
5+
"crypto/sha1"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"io/ioutil"
10+
"unicode"
11+
12+
"github.com/feedbooks/r2-streamer-go/models"
13+
)
14+
15+
func init() {
16+
decoderList = append(decoderList, List{decoderAlgorithm: "http://www.idpf.org/2008/embedding", decoder: DecodeIdpfFont})
17+
}
18+
19+
// DecodeIdpfFont decode obfuscate fonts using idpf spec http://www.idpf.org/epub/20/spec/FontManglingSpec.html
20+
func DecodeIdpfFont(publication models.Publication, link models.Link, reader io.ReadSeeker) (io.ReadSeeker, error) {
21+
var count int
22+
23+
key := getHashKey(publication)
24+
fmt.Println(key)
25+
if string(key) == "" {
26+
return nil, errors.New("can't find hash key")
27+
}
28+
29+
buff, _ := ioutil.ReadAll(reader)
30+
if len(buff) > 1040 {
31+
count = 1040
32+
} else {
33+
count = len(buff)
34+
}
35+
36+
j := 0
37+
for i := 0; i < count; i++ {
38+
buff[i] = buff[i] ^ key[j]
39+
40+
j++
41+
if j == 20 {
42+
j = 0
43+
}
44+
}
45+
readerSeeker := bytes.NewReader(buff)
46+
return readerSeeker, nil
47+
}
48+
49+
func getHashKey(publication models.Publication) []byte {
50+
var stringKey []rune
51+
52+
for _, c := range publication.Metadata.Identifier {
53+
if !unicode.IsSpace(c) {
54+
stringKey = append(stringKey, c)
55+
}
56+
}
57+
58+
h := sha1.New()
59+
io.WriteString(h, string(stringKey))
60+
61+
return h.Sum(nil)
62+
}

‎fetcher/epub.go

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"io/ioutil"
88

9+
"github.com/feedbooks/r2-streamer-go/decoder"
910
"github.com/feedbooks/r2-streamer-go/models"
1011
)
1112

@@ -18,6 +19,7 @@ func FetchEpub(publication models.Publication, publicationResource string) (io.R
1819
var mediaType string
1920
var reader *zip.ReadCloser
2021
var assetFd io.ReadCloser
22+
var link models.Link
2123

2224
for _, data := range publication.Internal {
2325
if data.Name == "epub" {
@@ -32,9 +34,18 @@ func FetchEpub(publication models.Publication, publicationResource string) (io.R
3234
}
3335
}
3436

37+
for _, linkRes := range publication.Resources {
38+
if publicationResource == linkRes.Href {
39+
link = linkRes
40+
}
41+
}
3542
buff, _ := ioutil.ReadAll(assetFd)
3643
assetFd.Close()
3744
readerSeeker := bytes.NewReader(buff)
45+
readerSeekerDecode, err := decoder.Decode(publication, link, readerSeeker)
46+
if err != nil {
47+
return readerSeekerDecode, mediaType, nil
48+
}
3849

3950
return readerSeeker, mediaType, nil
4051
}

‎fetcher/epub_dir.go

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path"
88

9+
"github.com/feedbooks/r2-streamer-go/decoder"
910
"github.com/feedbooks/r2-streamer-go/models"
1011
)
1112

@@ -18,6 +19,7 @@ func FetchEpubDir(publication models.Publication, publicationResource string) (i
1819
var mediaType string
1920
var basePath string
2021
var rootFile string
22+
var link models.Link
2123

2224
for _, data := range publication.Internal {
2325
if data.Name == "basepath" {
@@ -34,5 +36,15 @@ func FetchEpubDir(publication models.Publication, publicationResource string) (i
3436
fmt.Println(err)
3537
}
3638

39+
for _, linkRes := range publication.Resources {
40+
if publicationResource == linkRes.Href {
41+
link = linkRes
42+
}
43+
}
44+
45+
readerSeekerDecode, err := decoder.Decode(publication, link, fd)
46+
if err == nil {
47+
return readerSeekerDecode, mediaType, nil
48+
}
3749
return fd, mediaType, nil
3850
}

‎models/publication.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,17 @@ type Internal struct {
3131

3232
// Link object used in collections and links
3333
type Link struct {
34-
Href string `json:"href"`
35-
TypeLink string `json:"type,omitempty"`
36-
Rel []string `json:"rel,omitempty"`
37-
Height int `json:"height,omitempty"`
38-
Width int `json:"width,omitempty"`
39-
Title string `json:"title,omitempty"`
40-
Properties *Properties `json:"properties,omitempty"`
41-
Duration string `json:"duration,omitempty"`
42-
Templated bool `json:"templated,omitempty"`
43-
Children []Link `json:"children,omitempty"`
34+
Href string `json:"href"`
35+
TypeLink string `json:"type,omitempty"`
36+
Rel []string `json:"rel,omitempty"`
37+
Height int `json:"height,omitempty"`
38+
Width int `json:"width,omitempty"`
39+
Title string `json:"title,omitempty"`
40+
Properties *Properties `json:"properties,omitempty"`
41+
Duration string `json:"duration,omitempty"`
42+
Templated bool `json:"templated,omitempty"`
43+
Children []Link `json:"children,omitempty"`
44+
CryptAlgorithm string `json:"-"`
4445
}
4546

4647
// PublicationCollection is used as an extension points for other collections in a Publication

‎parser/epub.go

+39-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package parser
22

33
import (
44
"errors"
5+
"path"
56
"path/filepath"
67
"strconv"
78
"strings"
@@ -34,16 +35,19 @@ func EpubParser(filePath string) (models.Publication, error) {
3435
fileExt := filepath.Ext(filePath)
3536
if fileExt == "" {
3637
book, err = epub.OpenDir(filePath)
38+
if err != nil {
39+
return models.Publication{}, errors.New("can't open or parse epub file with err : " + err.Error())
40+
}
3741
publication.Internal = append(publication.Internal, models.Internal{Name: "type", Value: "epub_dir"})
3842
publication.Internal = append(publication.Internal, models.Internal{Name: "basepath", Value: filePath})
3943
} else {
4044
book, err = epub.Open(filePath)
45+
if err != nil {
46+
return models.Publication{}, errors.New("can't open or parse epub file with err : " + err.Error())
47+
}
4148
publication.Internal = append(publication.Internal, models.Internal{Name: "type", Value: "epub"})
4249
publication.Internal = append(publication.Internal, models.Internal{Name: "epub", Value: book.ZipReader()})
4350
}
44-
if err != nil {
45-
return models.Publication{}, errors.New("can't open or parse epub file with err : " + err.Error())
46-
}
4751

4852
if book.Container.Rootfile.Version != "" {
4953
epubVersion = book.Container.Rootfile.Version
@@ -106,6 +110,8 @@ func EpubParser(filePath string) (models.Publication, error) {
106110
}
107111

108112
fillCalibreSerieInfo(&publication, book)
113+
fillEncryptionInfo(&publication, book)
114+
109115
return publication, nil
110116
}
111117

@@ -558,3 +564,33 @@ func fillCalibreSerieInfo(publication *models.Publication, book *epub.Book) {
558564
}
559565

560566
}
567+
568+
func fillEncryptionInfo(publication *models.Publication, book *epub.Book) {
569+
570+
for _, encInfo := range book.Encryption.EncryptedData {
571+
resURI := encInfo.CipherData.CipherReference.URI
572+
for i, l := range publication.Resources {
573+
if resURI == FilePath(*publication, l.Href) {
574+
publication.Resources[i].CryptAlgorithm = encInfo.EncryptionMethod.Algorithm
575+
}
576+
}
577+
for i, l := range publication.Spine {
578+
if resURI == FilePath(*publication, l.Href) {
579+
publication.Spine[i].CryptAlgorithm = encInfo.EncryptionMethod.Algorithm
580+
}
581+
}
582+
}
583+
}
584+
585+
// FilePath return the complete path for the ressource
586+
func FilePath(publication models.Publication, publicationResource string) string {
587+
var rootFile string
588+
589+
for _, data := range publication.Internal {
590+
if data.Name == "rootfile" {
591+
rootFile = data.Value.(string)
592+
}
593+
}
594+
595+
return path.Join(path.Dir(rootFile), publicationResource)
596+
}

‎test/readium-test-files

Submodule readium-test-files added at 321967f

0 commit comments

Comments
 (0)
Please sign in to comment.