sfwoff.c (12289B)
1 /**************************************************************************** 2 * 3 * sfwoff.c 4 * 5 * WOFF format management (base). 6 * 7 * Copyright (C) 1996-2025 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/ftcalc.h> 22 #include <freetype/internal/ftdebug.h> 23 #include <freetype/internal/ftstream.h> 24 #include <freetype/ftgzip.h> 25 26 27 #ifdef FT_CONFIG_OPTION_USE_ZLIB 28 29 30 /************************************************************************** 31 * 32 * The macro FT_COMPONENT is used in trace mode. It is an implicit 33 * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log 34 * messages during execution. 35 */ 36 #undef FT_COMPONENT 37 #define FT_COMPONENT sfwoff 38 39 40 #define WRITE_USHORT( p, v ) \ 41 do \ 42 { \ 43 *(p)++ = (FT_Byte)( (v) >> 8 ); \ 44 *(p)++ = (FT_Byte)( (v) >> 0 ); \ 45 \ 46 } while ( 0 ) 47 48 #define WRITE_ULONG( p, v ) \ 49 do \ 50 { \ 51 *(p)++ = (FT_Byte)( (v) >> 24 ); \ 52 *(p)++ = (FT_Byte)( (v) >> 16 ); \ 53 *(p)++ = (FT_Byte)( (v) >> 8 ); \ 54 *(p)++ = (FT_Byte)( (v) >> 0 ); \ 55 \ 56 } while ( 0 ) 57 58 59 static void 60 sfnt_stream_close( FT_Stream stream ) 61 { 62 FT_Memory memory = stream->memory; 63 64 65 FT_FREE( stream->base ); 66 67 stream->size = 0; 68 stream->close = NULL; 69 } 70 71 72 FT_COMPARE_DEF( int ) 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 ) 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 woff.num_tables > 0xFFFU || 154 44 + woff.num_tables * 20UL >= woff.length || 155 12 + woff.num_tables * 16UL >= woff.totalSfntSize || 156 ( woff.totalSfntSize & 3 ) != 0 || 157 ( woff.metaOffset == 0 && ( woff.metaLength != 0 || 158 woff.metaOrigLength != 0 ) ) || 159 ( woff.metaLength != 0 && woff.metaOrigLength == 0 ) || 160 ( woff.privOffset == 0 && woff.privLength != 0 ) ) 161 { 162 FT_ERROR(( "woff_font_open: invalid WOFF header\n" )); 163 return FT_THROW( Invalid_Table ); 164 } 165 166 /* Don't trust `totalSfntSize' before thorough checks. */ 167 if ( FT_QALLOC( sfnt, 12 ) || FT_NEW( sfnt_stream ) ) 168 goto Exit; 169 170 sfnt_header = sfnt; 171 172 /* Write sfnt header. */ 173 { 174 FT_Int entrySelector = FT_MSB( woff.num_tables ); 175 FT_Int searchRange = ( 1 << entrySelector ) * 16; 176 FT_Int rangeShift = woff.num_tables * 16 - searchRange; 177 178 179 WRITE_ULONG ( sfnt_header, woff.flavor ); 180 WRITE_USHORT( sfnt_header, woff.num_tables ); 181 WRITE_USHORT( sfnt_header, searchRange ); 182 WRITE_USHORT( sfnt_header, entrySelector ); 183 WRITE_USHORT( sfnt_header, rangeShift ); 184 } 185 186 /* While the entries in the sfnt header must be sorted by the */ 187 /* tag value, the tables themselves are not. We thus have to */ 188 /* sort them by offset and check that they don't overlap. */ 189 190 if ( FT_QNEW_ARRAY( tables, woff.num_tables ) || 191 FT_QNEW_ARRAY( indices, woff.num_tables ) ) 192 goto Exit; 193 194 FT_TRACE2(( "\n" )); 195 FT_TRACE2(( " tag offset compLen origLen checksum\n" )); 196 FT_TRACE2(( " -------------------------------------------\n" )); 197 198 if ( FT_FRAME_ENTER( 20L * woff.num_tables ) ) 199 goto Exit; 200 201 for ( nn = 0; nn < woff.num_tables; nn++ ) 202 { 203 WOFF_Table table = tables + nn; 204 205 table->Tag = FT_GET_TAG4(); 206 table->Offset = FT_GET_ULONG(); 207 table->CompLength = FT_GET_ULONG(); 208 table->OrigLength = FT_GET_ULONG(); 209 table->CheckSum = FT_GET_ULONG(); 210 211 FT_TRACE2(( " %c%c%c%c %08lx %08lx %08lx %08lx\n", 212 (FT_Char)( table->Tag >> 24 ), 213 (FT_Char)( table->Tag >> 16 ), 214 (FT_Char)( table->Tag >> 8 ), 215 (FT_Char)( table->Tag ), 216 table->Offset, 217 table->CompLength, 218 table->OrigLength, 219 table->CheckSum )); 220 221 if ( table->Tag <= old_tag ) 222 { 223 FT_FRAME_EXIT(); 224 225 FT_ERROR(( "woff_font_open: table tags are not sorted\n" )); 226 error = FT_THROW( Invalid_Table ); 227 goto Exit; 228 } 229 230 old_tag = table->Tag; 231 indices[nn] = table; 232 } 233 234 FT_FRAME_EXIT(); 235 236 /* Sort by offset. */ 237 238 ft_qsort( indices, 239 woff.num_tables, 240 sizeof ( WOFF_Table ), 241 compare_offsets ); 242 243 /* Check offsets and lengths. */ 244 245 woff_offset = 44 + woff.num_tables * 20L; 246 sfnt_offset = 12 + woff.num_tables * 16L; 247 248 for ( nn = 0; nn < woff.num_tables; nn++ ) 249 { 250 WOFF_Table table = indices[nn]; 251 252 253 if ( table->Offset != woff_offset || 254 table->CompLength > woff.length || 255 table->Offset > woff.length - table->CompLength || 256 table->OrigLength > woff.totalSfntSize || 257 sfnt_offset > woff.totalSfntSize - table->OrigLength || 258 table->CompLength > table->OrigLength ) 259 { 260 FT_ERROR(( "woff_font_open: invalid table offsets\n" )); 261 error = FT_THROW( Invalid_Table ); 262 goto Exit; 263 } 264 265 table->OrigOffset = sfnt_offset; 266 267 /* The offsets must be multiples of 4. */ 268 woff_offset += ( table->CompLength + 3 ) & ~3U; 269 sfnt_offset += ( table->OrigLength + 3 ) & ~3U; 270 } 271 272 /* 273 * Final checks! 274 * 275 * We don't decode and check the metadata block. 276 * We don't check table checksums either. 277 * But other than those, I think we implement all 278 * `MUST' checks from the spec. 279 */ 280 281 if ( woff.metaOffset ) 282 { 283 if ( woff.metaOffset != woff_offset || 284 woff.metaOffset + woff.metaLength > woff.length ) 285 { 286 FT_ERROR(( "woff_font_open:" 287 " invalid `metadata' offset or length\n" )); 288 error = FT_THROW( Invalid_Table ); 289 goto Exit; 290 } 291 292 /* We have padding only ... */ 293 woff_offset += woff.metaLength; 294 } 295 296 if ( woff.privOffset ) 297 { 298 /* ... if it isn't the last block. */ 299 woff_offset = ( woff_offset + 3 ) & ~3U; 300 301 if ( woff.privOffset != woff_offset || 302 woff.privOffset + woff.privLength > woff.length ) 303 { 304 FT_ERROR(( "woff_font_open: invalid `private' offset or length\n" )); 305 error = FT_THROW( Invalid_Table ); 306 goto Exit; 307 } 308 309 /* No padding for the last block. */ 310 woff_offset += woff.privLength; 311 } 312 313 if ( sfnt_offset != woff.totalSfntSize || 314 woff_offset != woff.length ) 315 { 316 FT_ERROR(( "woff_font_open: invalid `sfnt' table structure\n" )); 317 error = FT_THROW( Invalid_Table ); 318 goto Exit; 319 } 320 321 /* Now use `totalSfntSize'. */ 322 if ( FT_QREALLOC( sfnt, 12, woff.totalSfntSize ) ) 323 goto Exit; 324 325 sfnt_header = sfnt + 12; 326 327 /* Write the tables. */ 328 329 for ( nn = 0; nn < woff.num_tables; nn++ ) 330 { 331 WOFF_Table table = tables + nn; 332 333 334 /* Write SFNT table entry. */ 335 WRITE_ULONG( sfnt_header, table->Tag ); 336 WRITE_ULONG( sfnt_header, table->CheckSum ); 337 WRITE_ULONG( sfnt_header, table->OrigOffset ); 338 WRITE_ULONG( sfnt_header, table->OrigLength ); 339 340 /* Write table data. */ 341 if ( FT_STREAM_SEEK( table->Offset ) || 342 FT_FRAME_ENTER( table->CompLength ) ) 343 goto Exit; 344 345 if ( table->CompLength == table->OrigLength ) 346 { 347 /* Uncompressed data; just copy. */ 348 ft_memcpy( sfnt + table->OrigOffset, 349 stream->cursor, 350 table->OrigLength ); 351 } 352 else 353 { 354 /* Uncompress with zlib. */ 355 FT_ULong output_len = table->OrigLength; 356 357 358 error = FT_Gzip_Uncompress( memory, 359 sfnt + table->OrigOffset, &output_len, 360 stream->cursor, table->CompLength ); 361 if ( error ) 362 goto Exit1; 363 if ( output_len != table->OrigLength ) 364 { 365 FT_ERROR(( "woff_font_open: compressed table length mismatch\n" )); 366 error = FT_THROW( Invalid_Table ); 367 goto Exit1; 368 } 369 } 370 371 FT_FRAME_EXIT(); 372 373 /* We don't check whether the padding bytes in the WOFF file are */ 374 /* actually '\0'. For the output, however, we do set them properly. */ 375 sfnt_offset = table->OrigOffset + table->OrigLength; 376 while ( sfnt_offset & 3 ) 377 { 378 sfnt[sfnt_offset] = '\0'; 379 sfnt_offset++; 380 } 381 } 382 383 /* Ok! Finally ready. Swap out stream and return. */ 384 FT_Stream_OpenMemory( sfnt_stream, sfnt, woff.totalSfntSize ); 385 sfnt_stream->memory = stream->memory; 386 sfnt_stream->close = sfnt_stream_close; 387 388 FT_Stream_Free( 389 face->root.stream, 390 ( face->root.face_flags & FT_FACE_FLAG_EXTERNAL_STREAM ) != 0 ); 391 392 face->root.stream = sfnt_stream; 393 394 face->root.face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM; 395 396 Exit: 397 FT_FREE( tables ); 398 FT_FREE( indices ); 399 400 if ( error ) 401 { 402 FT_FREE( sfnt ); 403 FT_Stream_Close( sfnt_stream ); 404 FT_FREE( sfnt_stream ); 405 } 406 407 return error; 408 409 Exit1: 410 FT_FRAME_EXIT(); 411 goto Exit; 412 } 413 414 415 #undef WRITE_USHORT 416 #undef WRITE_ULONG 417 418 #else /* !FT_CONFIG_OPTION_USE_ZLIB */ 419 420 /* ANSI C doesn't like empty source files */ 421 typedef int sfwoff_dummy_; 422 423 #endif /* !FT_CONFIG_OPTION_USE_ZLIB */ 424 425 426 /* END */