1 /****************************************************************************
2  *
3  * sfwoff.c
4  *
5  *   WOFF format management (base).
6  *
7  * Copyright (C) 1996-2019 by
8  * David Turner, Robert Wilhelm, and Werner Lemberg.
9  *
10  * This file is part of the FreeType project, and may only be used,
11  * modified, and distributed under the terms of the FreeType project
12  * license, LICENSE.TXT.  By continuing to use, modify, or distribute
13  * this file you indicate that you have read the license and
14  * understand and accept it fully.
15  *
16  */
17 
18 
19 #include <ft2build.h>
20 #include "sfwoff.h"
21 #include FT_TRUETYPE_TAGS_H
22 #include FT_INTERNAL_DEBUG_H
23 #include FT_INTERNAL_STREAM_H
24 #include FT_GZIP_H
25 
26 
27   /**************************************************************************
28    *
29    * The macro FT_COMPONENT is used in trace mode.  It is an implicit
30    * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
31    * messages during execution.
32    */
33 #undef  FT_COMPONENT
34 #define FT_COMPONENT  sfwoff
35 
36 
37 #define WRITE_USHORT( p, v )                \
38           do                                \
39           {                                 \
40             *(p)++ = (FT_Byte)( (v) >> 8 ); \
41             *(p)++ = (FT_Byte)( (v) >> 0 ); \
42                                             \
43           } while ( 0 )
44 
45 #define WRITE_ULONG( p, v )                  \
46           do                                 \
47           {                                  \
48             *(p)++ = (FT_Byte)( (v) >> 24 ); \
49             *(p)++ = (FT_Byte)( (v) >> 16 ); \
50             *(p)++ = (FT_Byte)( (v) >>  8 ); \
51             *(p)++ = (FT_Byte)( (v) >>  0 ); \
52                                              \
53           } while ( 0 )
54 
55 
56   static void
sfnt_stream_close(FT_Stream stream)57   sfnt_stream_close( FT_Stream  stream )
58   {
59     FT_Memory  memory = stream->memory;
60 
61 
62     FT_FREE( stream->base );
63 
64     stream->size  = 0;
65     stream->base  = NULL;
66     stream->close = NULL;
67   }
68 
69 
70   FT_CALLBACK_DEF( int )
compare_offsets(const void * a,const void * b)71   compare_offsets( const void*  a,
72                    const void*  b )
73   {
74     WOFF_Table  table1 = *(WOFF_Table*)a;
75     WOFF_Table  table2 = *(WOFF_Table*)b;
76 
77     FT_ULong  offset1 = table1->Offset;
78     FT_ULong  offset2 = table2->Offset;
79 
80 
81     if ( offset1 > offset2 )
82       return 1;
83     else if ( offset1 < offset2 )
84       return -1;
85     else
86       return 0;
87   }
88 
89 
90   /* Replace `face->root.stream' with a stream containing the extracted */
91   /* SFNT of a WOFF font.                                               */
92 
93   FT_LOCAL_DEF( FT_Error )
woff_open_font(FT_Stream stream,TT_Face face)94   woff_open_font( FT_Stream  stream,
95                   TT_Face    face )
96   {
97     FT_Memory       memory = stream->memory;
98     FT_Error        error  = FT_Err_Ok;
99 
100     WOFF_HeaderRec  woff;
101     WOFF_Table      tables  = NULL;
102     WOFF_Table*     indices = NULL;
103 
104     FT_ULong        woff_offset;
105 
106     FT_Byte*        sfnt        = NULL;
107     FT_Stream       sfnt_stream = NULL;
108 
109     FT_Byte*        sfnt_header;
110     FT_ULong        sfnt_offset;
111 
112     FT_Int          nn;
113     FT_ULong        old_tag = 0;
114 
115     static const FT_Frame_Field  woff_header_fields[] =
116     {
117 #undef  FT_STRUCTURE
118 #define FT_STRUCTURE  WOFF_HeaderRec
119 
120       FT_FRAME_START( 44 ),
121         FT_FRAME_ULONG ( signature ),
122         FT_FRAME_ULONG ( flavor ),
123         FT_FRAME_ULONG ( length ),
124         FT_FRAME_USHORT( num_tables ),
125         FT_FRAME_USHORT( reserved ),
126         FT_FRAME_ULONG ( totalSfntSize ),
127         FT_FRAME_USHORT( majorVersion ),
128         FT_FRAME_USHORT( minorVersion ),
129         FT_FRAME_ULONG ( metaOffset ),
130         FT_FRAME_ULONG ( metaLength ),
131         FT_FRAME_ULONG ( metaOrigLength ),
132         FT_FRAME_ULONG ( privOffset ),
133         FT_FRAME_ULONG ( privLength ),
134       FT_FRAME_END
135     };
136 
137 
138     FT_ASSERT( stream == face->root.stream );
139     FT_ASSERT( FT_STREAM_POS() == 0 );
140 
141     if ( FT_STREAM_READ_FIELDS( woff_header_fields, &woff ) )
142       return error;
143 
144     /* Make sure we don't recurse back here or hit TTC code. */
145     if ( woff.flavor == TTAG_wOFF || woff.flavor == TTAG_ttcf )
146       return FT_THROW( Invalid_Table );
147 
148     /* Miscellaneous checks. */
149     if ( woff.length != stream->size                              ||
150          woff.num_tables == 0                                     ||
151          44 + woff.num_tables * 20UL >= woff.length               ||
152          12 + woff.num_tables * 16UL >= woff.totalSfntSize        ||
153          ( woff.totalSfntSize & 3 ) != 0                          ||
154          ( woff.metaOffset == 0 && ( woff.metaLength != 0     ||
155                                      woff.metaOrigLength != 0 ) ) ||
156          ( woff.metaLength != 0 && woff.metaOrigLength == 0 )     ||
157          ( woff.privOffset == 0 && woff.privLength != 0 )         )
158     {
159       FT_ERROR(( "woff_font_open: invalid WOFF header\n" ));
160       return FT_THROW( Invalid_Table );
161     }
162 
163     /* Don't trust `totalSfntSize' before thorough checks. */
164     if ( FT_ALLOC( sfnt, 12 + woff.num_tables * 16UL ) ||
165          FT_NEW( sfnt_stream )                         )
166       goto Exit;
167 
168     sfnt_header = sfnt;
169 
170     /* Write sfnt header. */
171     {
172       FT_UInt  searchRange, entrySelector, rangeShift, x;
173 
174 
175       x             = woff.num_tables;
176       entrySelector = 0;
177       while ( x )
178       {
179         x            >>= 1;
180         entrySelector += 1;
181       }
182       entrySelector--;
183 
184       searchRange = ( 1 << entrySelector ) * 16;
185       rangeShift  = woff.num_tables * 16 - searchRange;
186 
187       WRITE_ULONG ( sfnt_header, woff.flavor );
188       WRITE_USHORT( sfnt_header, woff.num_tables );
189       WRITE_USHORT( sfnt_header, searchRange );
190       WRITE_USHORT( sfnt_header, entrySelector );
191       WRITE_USHORT( sfnt_header, rangeShift );
192     }
193 
194     /* While the entries in the sfnt header must be sorted by the */
195     /* tag value, the tables themselves are not.  We thus have to */
196     /* sort them by offset and check that they don't overlap.     */
197 
198     if ( FT_NEW_ARRAY( tables, woff.num_tables )  ||
199          FT_NEW_ARRAY( indices, woff.num_tables ) )
200       goto Exit;
201 
202     FT_TRACE2(( "\n"
203                 "  tag    offset    compLen  origLen  checksum\n"
204                 "  -------------------------------------------\n" ));
205 
206     if ( FT_FRAME_ENTER( 20L * woff.num_tables ) )
207       goto Exit;
208 
209     for ( nn = 0; nn < woff.num_tables; nn++ )
210     {
211       WOFF_Table  table = tables + nn;
212 
213       table->Tag        = FT_GET_TAG4();
214       table->Offset     = FT_GET_ULONG();
215       table->CompLength = FT_GET_ULONG();
216       table->OrigLength = FT_GET_ULONG();
217       table->CheckSum   = FT_GET_ULONG();
218 
219       FT_TRACE2(( "  %c%c%c%c  %08lx  %08lx  %08lx  %08lx\n",
220                   (FT_Char)( table->Tag >> 24 ),
221                   (FT_Char)( table->Tag >> 16 ),
222                   (FT_Char)( table->Tag >> 8  ),
223                   (FT_Char)( table->Tag       ),
224                   table->Offset,
225                   table->CompLength,
226                   table->OrigLength,
227                   table->CheckSum ));
228 
229       if ( table->Tag <= old_tag )
230       {
231         FT_FRAME_EXIT();
232 
233         FT_ERROR(( "woff_font_open: table tags are not sorted\n" ));
234         error = FT_THROW( Invalid_Table );
235         goto Exit;
236       }
237 
238       old_tag     = table->Tag;
239       indices[nn] = table;
240     }
241 
242     FT_FRAME_EXIT();
243 
244     /* Sort by offset. */
245 
246     ft_qsort( indices,
247               woff.num_tables,
248               sizeof ( WOFF_Table ),
249               compare_offsets );
250 
251     /* Check offsets and lengths. */
252 
253     woff_offset = 44 + woff.num_tables * 20L;
254     sfnt_offset = 12 + woff.num_tables * 16L;
255 
256     for ( nn = 0; nn < woff.num_tables; nn++ )
257     {
258       WOFF_Table  table = indices[nn];
259 
260 
261       if ( table->Offset != woff_offset                         ||
262            table->CompLength > woff.length                      ||
263            table->Offset > woff.length - table->CompLength      ||
264            table->OrigLength > woff.totalSfntSize               ||
265            sfnt_offset > woff.totalSfntSize - table->OrigLength ||
266            table->CompLength > table->OrigLength                )
267       {
268         FT_ERROR(( "woff_font_open: invalid table offsets\n" ));
269         error = FT_THROW( Invalid_Table );
270         goto Exit;
271       }
272 
273       table->OrigOffset = sfnt_offset;
274 
275       /* The offsets must be multiples of 4. */
276       woff_offset += ( table->CompLength + 3 ) & ~3U;
277       sfnt_offset += ( table->OrigLength + 3 ) & ~3U;
278     }
279 
280     /*
281      * Final checks!
282      *
283      * We don't decode and check the metadata block.
284      * We don't check table checksums either.
285      * But other than those, I think we implement all
286      * `MUST' checks from the spec.
287      */
288 
289     if ( woff.metaOffset )
290     {
291       if ( woff.metaOffset != woff_offset                  ||
292            woff.metaOffset + woff.metaLength > woff.length )
293       {
294         FT_ERROR(( "woff_font_open:"
295                    " invalid `metadata' offset or length\n" ));
296         error = FT_THROW( Invalid_Table );
297         goto Exit;
298       }
299 
300       /* We have padding only ... */
301       woff_offset += woff.metaLength;
302     }
303 
304     if ( woff.privOffset )
305     {
306       /* ... if it isn't the last block. */
307       woff_offset = ( woff_offset + 3 ) & ~3U;
308 
309       if ( woff.privOffset != woff_offset                  ||
310            woff.privOffset + woff.privLength > woff.length )
311       {
312         FT_ERROR(( "woff_font_open: invalid `private' offset or length\n" ));
313         error = FT_THROW( Invalid_Table );
314         goto Exit;
315       }
316 
317       /* No padding for the last block. */
318       woff_offset += woff.privLength;
319     }
320 
321     if ( sfnt_offset != woff.totalSfntSize ||
322          woff_offset != woff.length        )
323     {
324       FT_ERROR(( "woff_font_open: invalid `sfnt' table structure\n" ));
325       error = FT_THROW( Invalid_Table );
326       goto Exit;
327     }
328 
329     /* Now use `totalSfntSize'. */
330     if ( FT_REALLOC( sfnt,
331                      12 + woff.num_tables * 16UL,
332                      woff.totalSfntSize ) )
333       goto Exit;
334 
335     sfnt_header = sfnt + 12;
336 
337     /* Write the tables. */
338 
339     for ( nn = 0; nn < woff.num_tables; nn++ )
340     {
341       WOFF_Table  table = tables + nn;
342 
343 
344       /* Write SFNT table entry. */
345       WRITE_ULONG( sfnt_header, table->Tag );
346       WRITE_ULONG( sfnt_header, table->CheckSum );
347       WRITE_ULONG( sfnt_header, table->OrigOffset );
348       WRITE_ULONG( sfnt_header, table->OrigLength );
349 
350       /* Write table data. */
351       if ( FT_STREAM_SEEK( table->Offset )     ||
352            FT_FRAME_ENTER( table->CompLength ) )
353         goto Exit;
354 
355       if ( table->CompLength == table->OrigLength )
356       {
357         /* Uncompressed data; just copy. */
358         ft_memcpy( sfnt + table->OrigOffset,
359                    stream->cursor,
360                    table->OrigLength );
361       }
362       else
363       {
364 #ifdef FT_CONFIG_OPTION_USE_ZLIB
365 
366         /* Uncompress with zlib. */
367         FT_ULong  output_len = table->OrigLength;
368 
369 
370         error = FT_Gzip_Uncompress( memory,
371                                     sfnt + table->OrigOffset, &output_len,
372                                     stream->cursor, table->CompLength );
373         if ( error )
374           goto Exit;
375         if ( output_len != table->OrigLength )
376         {
377           FT_ERROR(( "woff_font_open: compressed table length mismatch\n" ));
378           error = FT_THROW( Invalid_Table );
379           goto Exit;
380         }
381 
382 #else /* !FT_CONFIG_OPTION_USE_ZLIB */
383 
384         error = FT_THROW( Unimplemented_Feature );
385         goto Exit;
386 
387 #endif /* !FT_CONFIG_OPTION_USE_ZLIB */
388       }
389 
390       FT_FRAME_EXIT();
391 
392       /* We don't check whether the padding bytes in the WOFF file are     */
393       /* actually '\0'.  For the output, however, we do set them properly. */
394       sfnt_offset = table->OrigOffset + table->OrigLength;
395       while ( sfnt_offset & 3 )
396       {
397         sfnt[sfnt_offset] = '\0';
398         sfnt_offset++;
399       }
400     }
401 
402     /* Ok!  Finally ready.  Swap out stream and return. */
403     FT_Stream_OpenMemory( sfnt_stream, sfnt, woff.totalSfntSize );
404     sfnt_stream->memory = stream->memory;
405     sfnt_stream->close  = sfnt_stream_close;
406 
407     FT_Stream_Free(
408       face->root.stream,
409       ( face->root.face_flags & FT_FACE_FLAG_EXTERNAL_STREAM ) != 0 );
410 
411     face->root.stream = sfnt_stream;
412 
413     face->root.face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
414 
415   Exit:
416     FT_FREE( tables );
417     FT_FREE( indices );
418 
419     if ( error )
420     {
421       FT_FREE( sfnt );
422       FT_Stream_Close( sfnt_stream );
423       FT_FREE( sfnt_stream );
424     }
425 
426     return error;
427   }
428 
429 
430 #undef WRITE_USHORT
431 #undef WRITE_ULONG
432 
433 
434 /* END */
435