1/****************************************************************************
2 *
3 * ftgzip.c
4 *
5 * FreeType support for .gz compressed files.
6 *
7 * This optional component relies on zlib. It should mainly be used to
8 * parse compressed PCF fonts, as found with many X11 server
9 * distributions.
10 *
11 * Copyright (C) 2002-2023 by
12 * David Turner, Robert Wilhelm, and Werner Lemberg.
13 *
14 * This file is part of the FreeType project, and may only be used,
15 * modified, and distributed under the terms of the FreeType project
16 * license, LICENSE.TXT. By continuing to use, modify, or distribute
17 * this file you indicate that you have read the license and
18 * understand and accept it fully.
19 *
20 */
21
22
23#include <freetype/internal/ftmemory.h>
24#include <freetype/internal/ftstream.h>
25#include <freetype/internal/ftdebug.h>
26#include <freetype/ftgzip.h>
27#include FT_CONFIG_STANDARD_LIBRARY_H
28
29
30#include <freetype/ftmoderr.h>
31
32#undef FTERRORS_H_
33
34#undef FT_ERR_PREFIX
35#define FT_ERR_PREFIX Gzip_Err_
36#define FT_ERR_BASE FT_Mod_Err_Gzip
37
38#include <freetype/fterrors.h>
39
40
41#ifdef FT_CONFIG_OPTION_USE_ZLIB
42
43#ifdef FT_CONFIG_OPTION_SYSTEM_ZLIB
44
45#include <zlib.h>
46
47#else /* !FT_CONFIG_OPTION_SYSTEM_ZLIB */
48
49 /* In this case, we include our own modified sources of the ZLib */
50 /* within the `gzip' component. The modifications were necessary */
51 /* to #include all files without conflicts, as well as preventing */
52 /* the definition of `extern' functions that may cause linking */
53 /* conflicts when a program is linked with both FreeType and the */
54 /* original ZLib. */
55
56#ifndef USE_ZLIB_ZCALLOC
57#define MY_ZCALLOC /* prevent all zcalloc() & zfree() in zutil.c */
58#endif
59
60 /* Note that our `zlib.h' includes `ftzconf.h' instead of `zconf.h'; */
61 /* the main reason is that even a global `zlib.h' includes `zconf.h' */
62 /* with */
63 /* */
64 /* #include "zconf.h" */
65 /* */
66 /* instead of the expected */
67 /* */
68 /* #include <zconf.h> */
69 /* */
70 /* so that configuration with `FT_CONFIG_OPTION_SYSTEM_ZLIB' might */
71 /* include the wrong `zconf.h' file, leading to errors. */
72
73#define ZEXPORT
74 /* prevent zlib functions from being visible outside their object files */
75#define ZEXTERN static
76
77#define HAVE_MEMCPY 1
78#define Z_SOLO 1
79#define Z_FREETYPE 1
80
81#if defined( _MSC_VER ) /* Visual C++ (and Intel C++) */
82 /* We disable the warning `conversion from XXX to YYY, */
83 /* possible loss of data' in order to compile cleanly with */
84 /* the maximum level of warnings: zlib is non-FreeType */
85 /* code. */
86#pragma warning( push )
87#pragma warning( disable : 4244 )
88#endif /* _MSC_VER */
89
90#if defined( __GNUC__ )
91#pragma GCC diagnostic push
92#ifndef __cplusplus
93#pragma GCC diagnostic ignored "-Wstrict-prototypes"
94#endif
95#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
96#pragma GCC diagnostic ignored "-Wredundant-decls"
97#endif
98
99#include "zutil.c"
100#include "inffast.c"
101#include "inflate.c"
102#include "inftrees.c"
103#include "adler32.c"
104#include "crc32.c"
105
106#if defined( __GNUC__ )
107#pragma GCC diagnostic pop
108#endif
109
110#if defined( _MSC_VER )
111#pragma warning( pop )
112#endif
113
114#endif /* !FT_CONFIG_OPTION_SYSTEM_ZLIB */
115
116
117/***************************************************************************/
118/***************************************************************************/
119/***** *****/
120/***** Z L I B M E M O R Y M A N A G E M E N T *****/
121/***** *****/
122/***************************************************************************/
123/***************************************************************************/
124
125 /* it is better to use FreeType memory routines instead of raw
126 'malloc/free' */
127
128 static voidpf
129 ft_gzip_alloc( voidpf opaque,
130 uInt items,
131 uInt size )
132 {
133 FT_Memory memory = (FT_Memory)opaque;
134 FT_ULong sz = (FT_ULong)size * items;
135 FT_Error error;
136 FT_Pointer p = NULL;
137
138
139 /* allocate and zero out */
140 FT_MEM_ALLOC( p, sz );
141 return p;
142 }
143
144
145 static void
146 ft_gzip_free( voidpf opaque,
147 voidpf address )
148 {
149 FT_Memory memory = (FT_Memory)opaque;
150
151
152 FT_MEM_FREE( address );
153 }
154
155/***************************************************************************/
156/***************************************************************************/
157/***** *****/
158/***** Z L I B F I L E D E S C R I P T O R *****/
159/***** *****/
160/***************************************************************************/
161/***************************************************************************/
162
163#define FT_GZIP_BUFFER_SIZE 4096
164
165 typedef struct FT_GZipFileRec_
166 {
167 FT_Stream source; /* parent/source stream */
168 FT_Stream stream; /* embedding stream */
169 FT_Memory memory; /* memory allocator */
170 z_stream zstream; /* zlib input stream */
171
172 FT_ULong start; /* starting position, after .gz header */
173 FT_Byte input[FT_GZIP_BUFFER_SIZE]; /* input read buffer */
174
175 FT_Byte buffer[FT_GZIP_BUFFER_SIZE]; /* output buffer */
176 FT_ULong pos; /* position in output */
177 FT_Byte* cursor;
178 FT_Byte* limit;
179
180 } FT_GZipFileRec, *FT_GZipFile;
181
182
183 /* gzip flag byte */
184#define FT_GZIP_ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
185#define FT_GZIP_HEAD_CRC 0x02 /* bit 1 set: header CRC present */
186#define FT_GZIP_EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
187#define FT_GZIP_ORIG_NAME 0x08 /* bit 3 set: original file name present */
188#define FT_GZIP_COMMENT 0x10 /* bit 4 set: file comment present */
189#define FT_GZIP_RESERVED 0xE0 /* bits 5..7: reserved */
190
191
192 /* check and skip .gz header - we don't support `transparent' compression */
193 static FT_Error
194 ft_gzip_check_header( FT_Stream stream )
195 {
196 FT_Error error;
197 FT_Byte head[4];
198
199
200 if ( FT_STREAM_SEEK( 0 ) ||
201 FT_STREAM_READ( head, 4 ) )
202 goto Exit;
203
204 /* head[0] && head[1] are the magic numbers; */
205 /* head[2] is the method, and head[3] the flags */
206 if ( head[0] != 0x1F ||
207 head[1] != 0x8B ||
208 head[2] != Z_DEFLATED ||
209 (head[3] & FT_GZIP_RESERVED) )
210 {
211 error = FT_THROW( Invalid_File_Format );
212 goto Exit;
213 }
214
215 /* skip time, xflags and os code */
216 (void)FT_STREAM_SKIP( 6 );
217
218 /* skip the extra field */
219 if ( head[3] & FT_GZIP_EXTRA_FIELD )
220 {
221 FT_UInt len;
222
223
224 if ( FT_READ_USHORT_LE( len ) ||
225 FT_STREAM_SKIP( len ) )
226 goto Exit;
227 }
228
229 /* skip original file name */
230 if ( head[3] & FT_GZIP_ORIG_NAME )
231 for (;;)
232 {
233 FT_UInt c;
234
235
236 if ( FT_READ_BYTE( c ) )
237 goto Exit;
238
239 if ( c == 0 )
240 break;
241 }
242
243 /* skip .gz comment */
244 if ( head[3] & FT_GZIP_COMMENT )
245 for (;;)
246 {
247 FT_UInt c;
248
249
250 if ( FT_READ_BYTE( c ) )
251 goto Exit;
252
253 if ( c == 0 )
254 break;
255 }
256
257 /* skip CRC */
258 if ( head[3] & FT_GZIP_HEAD_CRC )
259 if ( FT_STREAM_SKIP( 2 ) )
260 goto Exit;
261
262 Exit:
263 return error;
264 }
265
266
267 static FT_Error
268 ft_gzip_file_init( FT_GZipFile zip,
269 FT_Stream stream,
270 FT_Stream source )
271 {
272 z_stream* zstream = &zip->zstream;
273 FT_Error error = FT_Err_Ok;
274
275
276 zip->stream = stream;
277 zip->source = source;
278 zip->memory = stream->memory;
279
280 zip->limit = zip->buffer + FT_GZIP_BUFFER_SIZE;
281 zip->cursor = zip->limit;
282 zip->pos = 0;
283
284 /* check and skip .gz header */
285 {
286 stream = source;
287
288 error = ft_gzip_check_header( stream );
289 if ( error )
290 goto Exit;
291
292 zip->start = FT_STREAM_POS();
293 }
294
295 /* initialize zlib -- there is no zlib header in the compressed stream */
296 zstream->zalloc = ft_gzip_alloc;
297 zstream->zfree = ft_gzip_free;
298 zstream->opaque = stream->memory;
299
300 zstream->avail_in = 0;
301 zstream->next_in = zip->buffer;
302
303 if ( inflateInit2( zstream, -MAX_WBITS ) != Z_OK ||
304 !zstream->next_in )
305 error = FT_THROW( Invalid_File_Format );
306
307 Exit:
308 return error;
309 }
310
311
312 static void
313 ft_gzip_file_done( FT_GZipFile zip )
314 {
315 z_stream* zstream = &zip->zstream;
316
317
318 inflateEnd( zstream );
319
320 /* clear the rest */
321 zstream->zalloc = NULL;
322 zstream->zfree = NULL;
323 zstream->opaque = NULL;
324 zstream->next_in = NULL;
325 zstream->next_out = NULL;
326 zstream->avail_in = 0;
327 zstream->avail_out = 0;
328
329 zip->memory = NULL;
330 zip->source = NULL;
331 zip->stream = NULL;
332 }
333
334
335 static FT_Error
336 ft_gzip_file_reset( FT_GZipFile zip )
337 {
338 FT_Stream stream = zip->source;
339 FT_Error error;
340
341
342 if ( !FT_STREAM_SEEK( zip->start ) )
343 {
344 z_stream* zstream = &zip->zstream;
345
346
347 inflateReset( zstream );
348
349 zstream->avail_in = 0;
350 zstream->next_in = zip->input;
351 zstream->avail_out = 0;
352 zstream->next_out = zip->buffer;
353
354 zip->limit = zip->buffer + FT_GZIP_BUFFER_SIZE;
355 zip->cursor = zip->limit;
356 zip->pos = 0;
357 }
358
359 return error;
360 }
361
362
363 static FT_Error
364 ft_gzip_file_fill_input( FT_GZipFile zip )
365 {
366 z_stream* zstream = &zip->zstream;
367 FT_Stream stream = zip->source;
368 FT_ULong size;
369
370
371 if ( stream->read )
372 {
373 size = stream->read( stream, stream->pos, zip->input,
374 FT_GZIP_BUFFER_SIZE );
375 if ( size == 0 )
376 {
377 zip->limit = zip->cursor;
378 return FT_THROW( Invalid_Stream_Operation );
379 }
380 }
381 else
382 {
383 size = stream->size - stream->pos;
384 if ( size > FT_GZIP_BUFFER_SIZE )
385 size = FT_GZIP_BUFFER_SIZE;
386
387 if ( size == 0 )
388 {
389 zip->limit = zip->cursor;
390 return FT_THROW( Invalid_Stream_Operation );
391 }
392
393 FT_MEM_COPY( zip->input, stream->base + stream->pos, size );
394 }
395 stream->pos += size;
396
397 zstream->next_in = zip->input;
398 zstream->avail_in = size;
399
400 return FT_Err_Ok;
401 }
402
403
404 static FT_Error
405 ft_gzip_file_fill_output( FT_GZipFile zip )
406 {
407 z_stream* zstream = &zip->zstream;
408 FT_Error error = FT_Err_Ok;
409
410
411 zip->cursor = zip->buffer;
412 zstream->next_out = zip->cursor;
413 zstream->avail_out = FT_GZIP_BUFFER_SIZE;
414
415 while ( zstream->avail_out > 0 )
416 {
417 int err;
418
419
420 if ( zstream->avail_in == 0 )
421 {
422 error = ft_gzip_file_fill_input( zip );
423 if ( error )
424 break;
425 }
426
427 err = inflate( zstream, Z_NO_FLUSH );
428
429 if ( err == Z_STREAM_END )
430 {
431 zip->limit = zstream->next_out;
432 if ( zip->limit == zip->cursor )
433 error = FT_THROW( Invalid_Stream_Operation );
434 break;
435 }
436 else if ( err != Z_OK )
437 {
438 zip->limit = zip->cursor;
439 error = FT_THROW( Invalid_Stream_Operation );
440 break;
441 }
442 }
443
444 return error;
445 }
446
447
448 /* fill output buffer; `count' must be <= FT_GZIP_BUFFER_SIZE */
449 static FT_Error
450 ft_gzip_file_skip_output( FT_GZipFile zip,
451 FT_ULong count )
452 {
453 FT_Error error = FT_Err_Ok;
454
455
456 for (;;)
457 {
458 FT_ULong delta = (FT_ULong)( zip->limit - zip->cursor );
459
460
461 if ( delta >= count )
462 delta = count;
463
464 zip->cursor += delta;
465 zip->pos += delta;
466
467 count -= delta;
468 if ( count == 0 )
469 break;
470
471 error = ft_gzip_file_fill_output( zip );
472 if ( error )
473 break;
474 }
475
476 return error;
477 }
478
479
480 static FT_ULong
481 ft_gzip_file_io( FT_GZipFile zip,
482 FT_ULong pos,
483 FT_Byte* buffer,
484 FT_ULong count )
485 {
486 FT_ULong result = 0;
487 FT_Error error;
488
489
490 /* Reset inflate stream if we're seeking backwards. */
491 /* Yes, that is not too efficient, but it saves memory :-) */
492 if ( pos < zip->pos )
493 {
494 error = ft_gzip_file_reset( zip );
495 if ( error )
496 goto Exit;
497 }
498
499 /* skip unwanted bytes */
500 if ( pos > zip->pos )
501 {
502 error = ft_gzip_file_skip_output( zip, (FT_ULong)( pos - zip->pos ) );
503 if ( error )
504 goto Exit;
505 }
506
507 if ( count == 0 )
508 goto Exit;
509
510 /* now read the data */
511 for (;;)
512 {
513 FT_ULong delta;
514
515
516 delta = (FT_ULong)( zip->limit - zip->cursor );
517 if ( delta >= count )
518 delta = count;
519
520 FT_MEM_COPY( buffer, zip->cursor, delta );
521 buffer += delta;
522 result += delta;
523 zip->cursor += delta;
524 zip->pos += delta;
525
526 count -= delta;
527 if ( count == 0 )
528 break;
529
530 error = ft_gzip_file_fill_output( zip );
531 if ( error )
532 break;
533 }
534
535 Exit:
536 return result;
537 }
538
539
540/***************************************************************************/
541/***************************************************************************/
542/***** *****/
543/***** G Z E M B E D D I N G S T R E A M *****/
544/***** *****/
545/***************************************************************************/
546/***************************************************************************/
547
548 static void
549 ft_gzip_stream_close( FT_Stream stream )
550 {
551 FT_GZipFile zip = (FT_GZipFile)stream->descriptor.pointer;
552 FT_Memory memory = stream->memory;
553
554
555 if ( zip )
556 {
557 /* finalize gzip file descriptor */
558 ft_gzip_file_done( zip );
559
560 FT_FREE( zip );
561
562 stream->descriptor.pointer = NULL;
563 }
564
565 if ( !stream->read )
566 FT_FREE( stream->base );
567 }
568
569
570 static unsigned long
571 ft_gzip_stream_io( FT_Stream stream,
572 unsigned long offset,
573 unsigned char* buffer,
574 unsigned long count )
575 {
576 FT_GZipFile zip = (FT_GZipFile)stream->descriptor.pointer;
577
578
579 return ft_gzip_file_io( zip, offset, buffer, count );
580 }
581
582
583 static FT_ULong
584 ft_gzip_get_uncompressed_size( FT_Stream stream )
585 {
586 FT_Error error;
587 FT_ULong old_pos;
588 FT_ULong result = 0;
589
590
591 old_pos = stream->pos;
592 if ( !FT_Stream_Seek( stream, stream->size - 4 ) )
593 {
594 result = FT_Stream_ReadULongLE( stream, &error );
595 if ( error )
596 result = 0;
597
598 (void)FT_Stream_Seek( stream, old_pos );
599 }
600
601 return result;
602 }
603
604
605 /* documentation is in ftgzip.h */
606
607 FT_EXPORT_DEF( FT_Error )
608 FT_Stream_OpenGzip( FT_Stream stream,
609 FT_Stream source )
610 {
611 FT_Error error;
612 FT_Memory memory;
613 FT_GZipFile zip = NULL;
614
615
616 if ( !stream || !source )
617 {
618 error = FT_THROW( Invalid_Stream_Handle );
619 goto Exit;
620 }
621
622 memory = source->memory;
623
624 /*
625 * check the header right now; this prevents allocating un-necessary
626 * objects when we don't need them
627 */
628 error = ft_gzip_check_header( source );
629 if ( error )
630 goto Exit;
631
632 FT_ZERO( stream );
633 stream->memory = memory;
634
635 if ( !FT_QNEW( zip ) )
636 {
637 error = ft_gzip_file_init( zip, stream, source );
638 if ( error )
639 {
640 FT_FREE( zip );
641 goto Exit;
642 }
643
644 stream->descriptor.pointer = zip;
645 }
646
647 /*
648 * We use the following trick to try to dramatically improve the
649 * performance while dealing with small files. If the original stream
650 * size is less than a certain threshold, we try to load the whole font
651 * file into memory. This saves us from using the 32KB buffer needed
652 * to inflate the file, plus the two 4KB intermediate input/output
653 * buffers used in the `FT_GZipFile' structure.
654 */
655 {
656 FT_ULong zip_size = ft_gzip_get_uncompressed_size( source );
657
658
659 if ( zip_size != 0 && zip_size < 40 * 1024 )
660 {
661 FT_Byte* zip_buff = NULL;
662
663
664 if ( !FT_QALLOC( zip_buff, zip_size ) )
665 {
666 FT_ULong count;
667
668
669 count = ft_gzip_file_io( zip, 0, zip_buff, zip_size );
670 if ( count == zip_size )
671 {
672 ft_gzip_file_done( zip );
673 FT_FREE( zip );
674
675 stream->descriptor.pointer = NULL;
676
677 stream->size = zip_size;
678 stream->pos = 0;
679 stream->base = zip_buff;
680 stream->read = NULL;
681 stream->close = ft_gzip_stream_close;
682
683 goto Exit;
684 }
685
686 ft_gzip_file_io( zip, 0, NULL, 0 );
687 FT_FREE( zip_buff );
688 }
689 error = FT_Err_Ok;
690 }
691
692 if ( zip_size )
693 stream->size = zip_size;
694 else
695 stream->size = 0x7FFFFFFFL; /* don't know the real size! */
696 }
697
698 stream->pos = 0;
699 stream->base = NULL;
700 stream->read = ft_gzip_stream_io;
701 stream->close = ft_gzip_stream_close;
702
703 Exit:
704 return error;
705 }
706
707
708 /* documentation is in ftgzip.h */
709
710 FT_EXPORT_DEF( FT_Error )
711 FT_Gzip_Uncompress( FT_Memory memory,
712 FT_Byte* output,
713 FT_ULong* output_len,
714 const FT_Byte* input,
715 FT_ULong input_len )
716 {
717 z_stream stream;
718 int err;
719
720
721 /* check for `input' delayed to `inflate' */
722
723 if ( !memory || !output_len || !output )
724 return FT_THROW( Invalid_Argument );
725
726 /* this function is modeled after zlib's `uncompress' function */
727
728 stream.next_in = (Bytef*)input;
729 stream.avail_in = (uInt)input_len;
730
731 stream.next_out = output;
732 stream.avail_out = (uInt)*output_len;
733
734 stream.zalloc = ft_gzip_alloc;
735 stream.zfree = ft_gzip_free;
736 stream.opaque = memory;
737
738 err = inflateInit2( &stream, MAX_WBITS|32 );
739
740 if ( err != Z_OK )
741 return FT_THROW( Invalid_Argument );
742
743 err = inflate( &stream, Z_FINISH );
744 if ( err != Z_STREAM_END )
745 {
746 inflateEnd( &stream );
747 if ( err == Z_OK )
748 err = Z_BUF_ERROR;
749 }
750 else
751 {
752 *output_len = stream.total_out;
753
754 err = inflateEnd( &stream );
755 }
756
757 if ( err == Z_MEM_ERROR )
758 return FT_THROW( Out_Of_Memory );
759
760 if ( err == Z_BUF_ERROR )
761 return FT_THROW( Array_Too_Large );
762
763 if ( err == Z_DATA_ERROR )
764 return FT_THROW( Invalid_Table );
765
766 if ( err == Z_NEED_DICT )
767 return FT_THROW( Invalid_Table );
768
769 return FT_Err_Ok;
770 }
771
772
773#else /* !FT_CONFIG_OPTION_USE_ZLIB */
774
775 FT_EXPORT_DEF( FT_Error )
776 FT_Stream_OpenGzip( FT_Stream stream,
777 FT_Stream source )
778 {
779 FT_UNUSED( stream );
780 FT_UNUSED( source );
781
782 return FT_THROW( Unimplemented_Feature );
783 }
784
785
786 FT_EXPORT_DEF( FT_Error )
787 FT_Gzip_Uncompress( FT_Memory memory,
788 FT_Byte* output,
789 FT_ULong* output_len,
790 const FT_Byte* input,
791 FT_ULong input_len )
792 {
793 FT_UNUSED( memory );
794 FT_UNUSED( output );
795 FT_UNUSED( output_len );
796 FT_UNUSED( input );
797 FT_UNUSED( input_len );
798
799 return FT_THROW( Unimplemented_Feature );
800 }
801
802#endif /* !FT_CONFIG_OPTION_USE_ZLIB */
803
804
805/* END */
806