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 <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
  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 )
  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 )
  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 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 #else /* !FT_CONFIG_OPTION_USE_ZLIB */
 383 
 384         error = FT_THROW( Unimplemented_Feature );
 385         goto Exit1;
 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   Exit1:
 429     FT_FRAME_EXIT();
 430     goto Exit;
 431   }
 432 
 433 
 434 #undef WRITE_USHORT
 435 #undef WRITE_ULONG
 436 
 437 
 438 /* END */