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