1/****************************************************************************
2 *
3 * sfwoff.c
4 *
5 * WOFF format management (base).
6 *
7 * Copyright (C) 1996-2023 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
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->close = NULL;
68 }
69
70
71 FT_COMPARE_DEF( int )
72 compare_offsets( const void* a,
73 const void* b )
74 {
75 WOFF_Table table1 = *(WOFF_Table*)a;
76 WOFF_Table table2 = *(WOFF_Table*)b;
77
78 FT_ULong offset1 = table1->Offset;
79 FT_ULong offset2 = table2->Offset;
80
81
82 if ( offset1 > offset2 )
83 return 1;
84 else if ( offset1 < offset2 )
85 return -1;
86 else
87 return 0;
88 }
89
90
91 /* Replace `face->root.stream' with a stream containing the extracted */
92 /* SFNT of a WOFF font. */
93
94 FT_LOCAL_DEF( FT_Error )
95 woff_open_font( FT_Stream stream,
96 TT_Face face )
97 {
98 FT_Memory memory = stream->memory;
99 FT_Error error = FT_Err_Ok;
100
101 WOFF_HeaderRec woff;
102 WOFF_Table tables = NULL;
103 WOFF_Table* indices = NULL;
104
105 FT_ULong woff_offset;
106
107 FT_Byte* sfnt = NULL;
108 FT_Stream sfnt_stream = NULL;
109
110 FT_Byte* sfnt_header;
111 FT_ULong sfnt_offset;
112
113 FT_Int nn;
114 FT_Tag old_tag = 0;
115
116 static const FT_Frame_Field woff_header_fields[] =
117 {
118#undef FT_STRUCTURE
119#define FT_STRUCTURE WOFF_HeaderRec
120
121 FT_FRAME_START( 44 ),
122 FT_FRAME_ULONG ( signature ),
123 FT_FRAME_ULONG ( flavor ),
124 FT_FRAME_ULONG ( length ),
125 FT_FRAME_USHORT( num_tables ),
126 FT_FRAME_USHORT( reserved ),
127 FT_FRAME_ULONG ( totalSfntSize ),
128 FT_FRAME_USHORT( majorVersion ),
129 FT_FRAME_USHORT( minorVersion ),
130 FT_FRAME_ULONG ( metaOffset ),
131 FT_FRAME_ULONG ( metaLength ),
132 FT_FRAME_ULONG ( metaOrigLength ),
133 FT_FRAME_ULONG ( privOffset ),
134 FT_FRAME_ULONG ( privLength ),
135 FT_FRAME_END
136 };
137
138
139 FT_ASSERT( stream == face->root.stream );
140 FT_ASSERT( FT_STREAM_POS() == 0 );
141
142 if ( FT_STREAM_READ_FIELDS( woff_header_fields, &woff ) )
143 return error;
144
145 /* Make sure we don't recurse back here or hit TTC code. */
146 if ( woff.flavor == TTAG_wOFF || woff.flavor == TTAG_ttcf )
147 return FT_THROW( Invalid_Table );
148
149 /* Miscellaneous checks. */
150 if ( woff.length != stream->size ||
151 woff.num_tables == 0 ||
152 44 + woff.num_tables * 20UL >= woff.length ||
153 12 + woff.num_tables * 16UL >= woff.totalSfntSize ||
154 ( woff.totalSfntSize & 3 ) != 0 ||
155 ( woff.metaOffset == 0 && ( woff.metaLength != 0 ||
156 woff.metaOrigLength != 0 ) ) ||
157 ( woff.metaLength != 0 && woff.metaOrigLength == 0 ) ||
158 ( woff.privOffset == 0 && woff.privLength != 0 ) )
159 {
160 FT_ERROR(( "woff_font_open: invalid WOFF header\n" ));
161 return FT_THROW( Invalid_Table );
162 }
163
164 /* Don't trust `totalSfntSize' before thorough checks. */
165 if ( FT_QALLOC( sfnt, 12 ) || 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_QNEW_ARRAY( tables, woff.num_tables ) ||
199 FT_QNEW_ARRAY( indices, woff.num_tables ) )
200 goto Exit;
201
202 FT_TRACE2(( "\n" ));
203 FT_TRACE2(( " tag offset compLen origLen checksum\n" ));
204 FT_TRACE2(( " -------------------------------------------\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_QREALLOC( sfnt, 12, woff.totalSfntSize ) )
331 goto Exit;
332
333 sfnt_header = sfnt + 12;
334
335 /* Write the tables. */
336
337 for ( nn = 0; nn < woff.num_tables; nn++ )
338 {
339 WOFF_Table table = tables + nn;
340
341
342 /* Write SFNT table entry. */
343 WRITE_ULONG( sfnt_header, table->Tag );
344 WRITE_ULONG( sfnt_header, table->CheckSum );
345 WRITE_ULONG( sfnt_header, table->OrigOffset );
346 WRITE_ULONG( sfnt_header, table->OrigLength );
347
348 /* Write table data. */
349 if ( FT_STREAM_SEEK( table->Offset ) ||
350 FT_FRAME_ENTER( table->CompLength ) )
351 goto Exit;
352
353 if ( table->CompLength == table->OrigLength )
354 {
355 /* Uncompressed data; just copy. */
356 ft_memcpy( sfnt + table->OrigOffset,
357 stream->cursor,
358 table->OrigLength );
359 }
360 else
361 {
362 /* Uncompress with zlib. */
363 FT_ULong output_len = table->OrigLength;
364
365
366 error = FT_Gzip_Uncompress( memory,
367 sfnt + table->OrigOffset, &output_len,
368 stream->cursor, table->CompLength );
369 if ( error )
370 goto Exit1;
371 if ( output_len != table->OrigLength )
372 {
373 FT_ERROR(( "woff_font_open: compressed table length mismatch\n" ));
374 error = FT_THROW( Invalid_Table );
375 goto Exit1;
376 }
377 }
378
379 FT_FRAME_EXIT();
380
381 /* We don't check whether the padding bytes in the WOFF file are */
382 /* actually '\0'. For the output, however, we do set them properly. */
383 sfnt_offset = table->OrigOffset + table->OrigLength;
384 while ( sfnt_offset & 3 )
385 {
386 sfnt[sfnt_offset] = '\0';
387 sfnt_offset++;
388 }
389 }
390
391 /* Ok! Finally ready. Swap out stream and return. */
392 FT_Stream_OpenMemory( sfnt_stream, sfnt, woff.totalSfntSize );
393 sfnt_stream->memory = stream->memory;
394 sfnt_stream->close = sfnt_stream_close;
395
396 FT_Stream_Free(
397 face->root.stream,
398 ( face->root.face_flags & FT_FACE_FLAG_EXTERNAL_STREAM ) != 0 );
399
400 face->root.stream = sfnt_stream;
401
402 face->root.face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
403
404 Exit:
405 FT_FREE( tables );
406 FT_FREE( indices );
407
408 if ( error )
409 {
410 FT_FREE( sfnt );
411 FT_Stream_Close( sfnt_stream );
412 FT_FREE( sfnt_stream );
413 }
414
415 return error;
416
417 Exit1:
418 FT_FRAME_EXIT();
419 goto Exit;
420 }
421
422
423#undef WRITE_USHORT
424#undef WRITE_ULONG
425
426#else /* !FT_CONFIG_OPTION_USE_ZLIB */
427
428 /* ANSI C doesn't like empty source files */
429 typedef int sfwoff_dummy_;
430
431#endif /* !FT_CONFIG_OPTION_USE_ZLIB */
432
433
434/* END */
435