1
+ var ContentTypeMap = ( {
2
+ "htm" : "text/html" ,
3
+ "xml" : "text/xml" ,
4
+
5
+ "gif" : "image/gif" ,
6
+ "jpg" : "image/jpeg" ,
7
+ "png" : "image/png" ,
8
+
9
+ "mso" : "application/x-mso" ,
10
+ "thmx" : "application/vnd.ms-officetheme" ,
11
+ "sh33tj5" : "application/octet-stream"
12
+ } /*:any*/ ) ;
13
+
14
+ function get_content_type ( fi /*:CFBEntry*/ , fp /*:string*/ ) /*:string*/ {
15
+ if ( fi . ctype ) return fi . ctype ;
16
+
17
+ var ext = fi . name || "" , m = ext . match ( / \. ( [ ^ \. ] + ) $ / ) ;
18
+ if ( m && ContentTypeMap [ m [ 1 ] ] ) return ContentTypeMap [ m [ 1 ] ] ;
19
+
20
+ if ( fp ) {
21
+ m = ( ext = fp ) . match ( / [ \. \\ ] ( [ ^ \. \\ ] ) + $ / ) ;
22
+ if ( m && ContentTypeMap [ m [ 1 ] ] ) return ContentTypeMap [ m [ 1 ] ] ;
23
+ }
24
+
25
+ return "application/octet-stream" ;
26
+ }
27
+
28
+ /* 76 character chunks TODO: intertwine encoding */
29
+ function write_base64_76 ( bstr /*:string*/ ) /*:string*/ {
30
+ var data = Base64 . encode ( bstr ) ;
31
+ var o = [ ] ;
32
+ for ( var i = 0 ; i < data . length ; i += 76 ) o . push ( data . slice ( i , i + 76 ) ) ;
33
+ return o . join ( "\r\n" ) + "\r\n" ;
34
+ }
35
+
36
+ /*
37
+ Rules for QP:
38
+ - escape =## applies for all non-display characters and literal "="
39
+ - space or tab at end of line must be encoded
40
+ - \r\n newlines can be preserved, but bare \r and \n must be escaped
41
+ - lines must not exceed 76 characters, use soft breaks =\r\n
42
+
43
+ TODO: Some files from word appear to write line extensions with bare equals:
44
+
45
+ ```
46
+ <table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
47
+ ="70%"
48
+ ```
49
+ */
50
+ function write_quoted_printable ( text /*:string*/ ) /*:string*/ {
51
+ var encoded = text . replace ( / [ \x00 - \x08 \x0B \x0C \x0E - \x1F \x7E - \xFF = ] / g, function ( c ) {
52
+ var w = c . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) ;
53
+ return "=" + ( w . length == 1 ? "0" + w : w ) ;
54
+ } ) ;
55
+
56
+ encoded = encoded . replace ( / $ / mg, "=20" ) . replace ( / \t $ / mg, "=09" ) ;
57
+
58
+ if ( encoded . charAt ( 0 ) == "\n" ) encoded = "=0D" + encoded . slice ( 1 ) ;
59
+ encoded = encoded . replace ( / \r (? ! \n ) / mg, "=0D" ) . replace ( / \n \n / mg, "\n=0A" ) . replace ( / ( [ ^ \r \n ] ) \n / mg, "$1=0A" ) ;
60
+
61
+ var o /*:Array<string>*/ = [ ] , split = encoded . split ( "\r\n" ) ;
62
+ for ( var si = 0 ; si < split . length ; ++ si ) {
63
+ var str = split [ si ] ;
64
+ if ( str . length == 0 ) { o . push ( "" ) ; continue ; }
65
+ for ( var i = 0 ; i < str . length ; ) {
66
+ var end = 76 ;
67
+ var tmp = str . slice ( i , i + end ) ;
68
+ if ( tmp . charAt ( end - 1 ) == "=" ) end -- ;
69
+ else if ( tmp . charAt ( end - 2 ) == "=" ) end -= 2 ;
70
+ else if ( tmp . charAt ( end - 3 ) == "=" ) end -= 3 ;
71
+ tmp = str . slice ( i , i + end ) ;
72
+ i += end ;
73
+ if ( i < str . length ) tmp += "=" ;
74
+ o . push ( tmp ) ;
75
+ }
76
+ }
77
+
78
+ return o . join ( "\r\n" ) ;
79
+ }
80
+ function parse_quoted_printable ( data /*:Array<string>*/ ) /*:RawBytes*/ {
81
+ var o = [ ] ;
82
+
83
+ /* unify long lines */
84
+ for ( var di = 0 ; di < data . length ; ++ di ) {
85
+ var line = data [ di ] ;
86
+ while ( di <= data . length && line . charAt ( line . length - 1 ) == "=" ) line = line . slice ( 0 , line . length - 1 ) + data [ ++ di ] ;
87
+ o . push ( line ) ;
88
+ }
89
+
90
+ /* decode */
91
+ for ( var oi = 0 ; oi < o . length ; ++ oi ) o [ oi ] = o [ oi ] . replace ( / = [ 0 - 9 A - F a - f ] { 2 } / g, function ( $$ ) { return String . fromCharCode ( parseInt ( $$ . slice ( 1 ) , 16 ) ) ; } ) ;
92
+ return s2a ( o . join ( "\r\n" ) ) ;
93
+ }
94
+
95
+
96
+ function parse_mime ( cfb /*:CFBContainer*/ , data /*:Array<string>*/ , root /*:string*/ ) /*:void*/ {
97
+ var fname = "" , cte = "" , ctype = "" , fdata ;
98
+ var di = 0 ;
99
+ for ( ; di < 10 ; ++ di ) {
100
+ var line = data [ di ] ;
101
+ if ( ! line || line . match ( / ^ \s * $ / ) ) break ;
102
+ var m = line . match ( / ^ ( .* ?) : \s * ( [ ^ \s ] .* ) $ / ) ;
103
+ if ( m ) switch ( m [ 1 ] . toLowerCase ( ) ) {
104
+ case "content-location" : fname = m [ 2 ] . trim ( ) ; break ;
105
+ case "content-type" : ctype = m [ 2 ] . trim ( ) ; break ;
106
+ case "content-transfer-encoding" : cte = m [ 2 ] . trim ( ) ; break ;
107
+ }
108
+ }
109
+ ++ di ;
110
+ switch ( cte . toLowerCase ( ) ) {
111
+ case 'base64' : fdata = s2a ( Base64 . decode ( data . slice ( di ) . join ( "" ) ) ) ; break ;
112
+ case 'quoted-printable' : fdata = parse_quoted_printable ( data . slice ( di ) ) ; break ;
113
+ default : throw new Error ( "Unsupported Content-Transfer-Encoding " + cte ) ;
114
+ }
115
+ var file = cfb_add ( cfb , fname . slice ( root . length ) , fdata , { unsafe : true } ) ;
116
+ if ( ctype ) file . ctype = ctype ;
117
+ }
118
+
119
+ function parse_mad ( file /*:RawBytes*/ , options /*:CFBReadOpts*/ ) /*:CFBContainer*/ {
120
+ if ( a2s ( file . slice ( 0 , 13 ) ) . toLowerCase ( ) != "mime-version:" ) throw new Error ( "Unsupported MAD header" ) ;
121
+ var root = ( options && options . root || "" ) ;
122
+ // $FlowIgnore
123
+ var data = ( has_buf && Buffer . isBuffer ( file ) ? file . toString ( "binary" ) : a2s ( file ) ) . split ( "\r\n" ) ;
124
+ var di = 0 , row = "" ;
125
+
126
+ /* if root is not specified, scan for the common prefix */
127
+ for ( di = 0 ; di < data . length ; ++ di ) {
128
+ row = data [ di ] ;
129
+ if ( ! / ^ C o n t e n t - L o c a t i o n : / i. test ( row ) ) continue ;
130
+ row = row . slice ( row . indexOf ( "file" ) ) ;
131
+ if ( ! root ) root = row . slice ( 0 , row . lastIndexOf ( "/" ) + 1 ) ;
132
+ if ( row . slice ( 0 , root . length ) == root ) continue ;
133
+ while ( root . length > 0 ) {
134
+ root = root . slice ( 0 , root . length - 1 ) ;
135
+ root = root . slice ( 0 , root . lastIndexOf ( "/" ) + 1 ) ;
136
+ if ( row . slice ( 0 , root . length ) == root ) break ;
137
+ }
138
+ }
139
+
140
+ var mboundary = ( data [ 1 ] || "" ) . match ( / b o u n d a r y = " ( .* ?) " / ) ;
141
+ if ( ! mboundary ) throw new Error ( "MAD cannot find boundary" ) ;
142
+ var boundary = "--" + ( mboundary [ 1 ] || "" ) ;
143
+
144
+ var FileIndex /*:CFBFileIndex*/ = [ ] , FullPaths /*:Array<string>*/ = [ ] ;
145
+ var o = {
146
+ FileIndex : FileIndex ,
147
+ FullPaths : FullPaths
148
+ } ;
149
+ init_cfb ( o ) ;
150
+ var start_di , fcnt = 0 ;
151
+ for ( di = 0 ; di < data . length ; ++ di ) {
152
+ var line = data [ di ] ;
153
+ if ( line !== boundary && line !== boundary + "--" ) continue ;
154
+ if ( fcnt ++ ) parse_mime ( o , data . slice ( start_di , di ) , root ) ;
155
+ start_di = di ;
156
+ }
157
+ return o ;
158
+ }
159
+
160
+ function write_mad ( cfb /*:CFBContainer*/ , options /*:CFBWriteOpts*/ ) /*:string*/ {
161
+ var opts = options || { } ;
162
+ var boundary = opts . boundary || "SheetJS" ;
163
+ boundary = '------=' + boundary ;
164
+
165
+ var out = [
166
+ 'MIME-Version: 1.0' ,
167
+ 'Content-Type: multipart/related; boundary="' + boundary . slice ( 2 ) + '"' ,
168
+ '' ,
169
+ '' ,
170
+ ''
171
+ ] ;
172
+
173
+ var root = cfb . FullPaths [ 0 ] , fp = root , fi = cfb . FileIndex [ 0 ] ;
174
+ for ( var i = 1 ; i < cfb . FullPaths . length ; ++ i ) {
175
+ fp = cfb . FullPaths [ i ] . slice ( root . length ) ;
176
+ fi = cfb . FileIndex [ i ] ;
177
+ if ( ! fi . size || ! fi . content || fp == "\u0001Sh33tJ5" ) continue ;
178
+
179
+ /* Normalize filename */
180
+ fp = fp . replace ( / [ \x00 - \x08 \x0B \x0C \x0E - \x1F \x7E - \xFF ] / g, function ( c ) {
181
+ return "_x" + c . charCodeAt ( 0 ) . toString ( 16 ) + "_" ;
182
+ } ) . replace ( / [ \u0080 - \uFFFF ] / g, function ( u ) {
183
+ return "_u" + u . charCodeAt ( 0 ) . toString ( 16 ) + "_" ;
184
+ } ) ;
185
+
186
+ /* Extract content as binary string */
187
+ var ca = fi . content ;
188
+ // $FlowIgnore
189
+ var cstr = has_buf && Buffer . isBuffer ( ca ) ? ca . toString ( "binary" ) : a2s ( ca ) ;
190
+
191
+ /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */
192
+ var dispcnt = 0 , L = Math . min ( 1024 , cstr . length ) , cc = 0 ;
193
+ for ( var csl = 0 ; csl <= L ; ++ csl ) if ( ( cc = cstr . charCodeAt ( csl ) ) >= 0x20 && cc < 0x80 ) ++ dispcnt ;
194
+ var qp = dispcnt >= L * 4 / 5 ;
195
+
196
+ out . push ( boundary ) ;
197
+ out . push ( 'Content-Location: ' + ( opts . root || 'file:///C:/SheetJS/' ) + fp ) ;
198
+ out . push ( 'Content-Transfer-Encoding: ' + ( qp ? 'quoted-printable' : 'base64' ) ) ;
199
+ out . push ( 'Content-Type: ' + get_content_type ( fi , fp ) ) ;
200
+ out . push ( '' ) ;
201
+
202
+ out . push ( qp ? write_quoted_printable ( cstr ) : write_base64_76 ( cstr ) ) ;
203
+ }
204
+ out . push ( boundary + '--\r\n' ) ;
205
+ return out . join ( "\r\n" ) ;
206
+ }
0 commit comments