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