1/****************************************************************************
2 *
3 * ftbzip2.c
4 *
5 * FreeType support for .bz2 compressed files.
6 *
7 * This optional component relies on libbz2. It should mainly be used to
8 * parse compressed PCF fonts, as found with many X11 server
9 * distributions.
10 *
11 * Copyright (C) 2010-2023 by
12 * Joel Klinghed.
13 *
14 * based on `src/gzip/ftgzip.c'
15 *
16 * This file is part of the FreeType project, and may only be used,
17 * modified, and distributed under the terms of the FreeType project
18 * license, LICENSE.TXT. By continuing to use, modify, or distribute
19 * this file you indicate that you have read the license and
20 * understand and accept it fully.
21 *
22 */
23
24
25#include <freetype/internal/ftmemory.h>
26#include <freetype/internal/ftstream.h>
27#include <freetype/internal/ftdebug.h>
28#include <freetype/ftbzip2.h>
29#include FT_CONFIG_STANDARD_LIBRARY_H
30
31
32#include <freetype/ftmoderr.h>
33
34#undef FTERRORS_H_
35
36#undef FT_ERR_PREFIX
37#define FT_ERR_PREFIX Bzip2_Err_
38#define FT_ERR_BASE FT_Mod_Err_Bzip2
39
40#include <freetype/fterrors.h>
41
42
43#ifdef FT_CONFIG_OPTION_USE_BZIP2
44
45#define BZ_NO_STDIO /* Do not need FILE */
46#include <bzlib.h>
47
48
49/***************************************************************************/
50/***************************************************************************/
51/***** *****/
52/***** B Z I P 2 M E M O R Y M A N A G E M E N T *****/
53/***** *****/
54/***************************************************************************/
55/***************************************************************************/
56
57 /* it is better to use FreeType memory routines instead of raw
58 'malloc/free' */
59
60 typedef void* (*alloc_func)( void*, int, int );
61 typedef void (*free_func) ( void*, void* );
62
63
64 static void*
65 ft_bzip2_alloc( void* memory_, /* FT_Memory */
66 int items,
67 int size )
68 {
69 FT_Memory memory = (FT_Memory)memory_;
70
71 FT_ULong sz = (FT_ULong)size * (FT_ULong)items;
72 FT_Error error;
73 FT_Pointer p = NULL;
74
75
76 FT_MEM_QALLOC( p, sz );
77 return p;
78 }
79
80
81 static void
82 ft_bzip2_free( void* memory_, /* FT_Memory */
83 void* address )
84 {
85 FT_Memory memory = (FT_Memory)memory_;
86
87
88 FT_MEM_FREE( address );
89 }
90
91
92/***************************************************************************/
93/***************************************************************************/
94/***** *****/
95/***** B Z I P 2 F I L E D E S C R I P T O R *****/
96/***** *****/
97/***************************************************************************/
98/***************************************************************************/
99
100#define FT_BZIP2_BUFFER_SIZE 4096
101
102 typedef struct FT_BZip2FileRec_
103 {
104 FT_Stream source; /* parent/source stream */
105 FT_Stream stream; /* embedding stream */
106 FT_Memory memory; /* memory allocator */
107 bz_stream bzstream; /* bzlib input stream */
108
109 FT_Byte input[FT_BZIP2_BUFFER_SIZE]; /* input read buffer */
110
111 FT_Byte buffer[FT_BZIP2_BUFFER_SIZE]; /* output buffer */
112 FT_ULong pos; /* position in output */
113 FT_Byte* cursor;
114 FT_Byte* limit;
115 FT_Bool reset; /* reset before next read */
116
117 } FT_BZip2FileRec, *FT_BZip2File;
118
119
120 /* check and skip .bz2 header - we don't support `transparent' compression */
121 static FT_Error
122 ft_bzip2_check_header( FT_Stream stream )
123 {
124 FT_Error error = FT_Err_Ok;
125 FT_Byte head[4];
126
127
128 if ( FT_STREAM_SEEK( 0 ) ||
129 FT_STREAM_READ( head, 4 ) )
130 goto Exit;
131
132 /* head[0] && head[1] are the magic numbers; */
133 /* head[2] is the version, and head[3] the blocksize */
134 if ( head[0] != 0x42 ||
135 head[1] != 0x5A ||
136 head[2] != 0x68 ) /* only support bzip2 (huffman) */
137 {
138 error = FT_THROW( Invalid_File_Format );
139 goto Exit;
140 }
141
142 Exit:
143 return error;
144 }
145
146
147 static FT_Error
148 ft_bzip2_file_init( FT_BZip2File zip,
149 FT_Stream stream,
150 FT_Stream source )
151 {
152 bz_stream* bzstream = &zip->bzstream;
153 FT_Error error = FT_Err_Ok;
154
155
156 zip->stream = stream;
157 zip->source = source;
158 zip->memory = stream->memory;
159
160 zip->limit = zip->buffer + FT_BZIP2_BUFFER_SIZE;
161 zip->cursor = zip->limit;
162 zip->pos = 0;
163 zip->reset = 0;
164
165 /* check .bz2 header */
166 {
167 stream = source;
168
169 error = ft_bzip2_check_header( stream );
170 if ( error )
171 goto Exit;
172
173 if ( FT_STREAM_SEEK( 0 ) )
174 goto Exit;
175 }
176
177 /* initialize bzlib */
178 bzstream->bzalloc = ft_bzip2_alloc;
179 bzstream->bzfree = ft_bzip2_free;
180 bzstream->opaque = zip->memory;
181
182 bzstream->avail_in = 0;
183 bzstream->next_in = (char*)zip->buffer;
184
185 if ( BZ2_bzDecompressInit( bzstream, 0, 0 ) != BZ_OK ||
186 !bzstream->next_in )
187 error = FT_THROW( Invalid_File_Format );
188
189 Exit:
190 return error;
191 }
192
193
194 static void
195 ft_bzip2_file_done( FT_BZip2File zip )
196 {
197 bz_stream* bzstream = &zip->bzstream;
198
199
200 BZ2_bzDecompressEnd( bzstream );
201
202 /* clear the rest */
203 bzstream->bzalloc = NULL;
204 bzstream->bzfree = NULL;
205 bzstream->opaque = NULL;
206 bzstream->next_in = NULL;
207 bzstream->next_out = NULL;
208 bzstream->avail_in = 0;
209 bzstream->avail_out = 0;
210
211 zip->memory = NULL;
212 zip->source = NULL;
213 zip->stream = NULL;
214 }
215
216
217 static FT_Error
218 ft_bzip2_file_reset( FT_BZip2File zip )
219 {
220 FT_Stream stream = zip->source;
221 FT_Error error;
222
223
224 if ( !FT_STREAM_SEEK( 0 ) )
225 {
226 bz_stream* bzstream = &zip->bzstream;
227
228
229 BZ2_bzDecompressEnd( bzstream );
230
231 bzstream->avail_in = 0;
232 bzstream->next_in = (char*)zip->input;
233 bzstream->avail_out = 0;
234 bzstream->next_out = (char*)zip->buffer;
235
236 zip->limit = zip->buffer + FT_BZIP2_BUFFER_SIZE;
237 zip->cursor = zip->limit;
238 zip->pos = 0;
239 zip->reset = 0;
240
241 BZ2_bzDecompressInit( bzstream, 0, 0 );
242 }
243
244 return error;
245 }
246
247
248 static FT_Error
249 ft_bzip2_file_fill_input( FT_BZip2File zip )
250 {
251 bz_stream* bzstream = &zip->bzstream;
252 FT_Stream stream = zip->source;
253 FT_ULong size;
254
255
256 if ( stream->read )
257 {
258 size = stream->read( stream, stream->pos, zip->input,
259 FT_BZIP2_BUFFER_SIZE );
260 if ( size == 0 )
261 {
262 zip->limit = zip->cursor;
263 return FT_THROW( Invalid_Stream_Operation );
264 }
265 }
266 else
267 {
268 size = stream->size - stream->pos;
269 if ( size > FT_BZIP2_BUFFER_SIZE )
270 size = FT_BZIP2_BUFFER_SIZE;
271
272 if ( size == 0 )
273 {
274 zip->limit = zip->cursor;
275 return FT_THROW( Invalid_Stream_Operation );
276 }
277
278 FT_MEM_COPY( zip->input, stream->base + stream->pos, size );
279 }
280 stream->pos += size;
281
282 bzstream->next_in = (char*)zip->input;
283 bzstream->avail_in = size;
284
285 return FT_Err_Ok;
286 }
287
288
289 static FT_Error
290 ft_bzip2_file_fill_output( FT_BZip2File zip )
291 {
292 bz_stream* bzstream = &zip->bzstream;
293 FT_Error error = FT_Err_Ok;
294
295
296 zip->cursor = zip->buffer;
297 bzstream->next_out = (char*)zip->cursor;
298 bzstream->avail_out = FT_BZIP2_BUFFER_SIZE;
299
300 while ( bzstream->avail_out > 0 )
301 {
302 int err;
303
304
305 if ( bzstream->avail_in == 0 )
306 {
307 error = ft_bzip2_file_fill_input( zip );
308 if ( error )
309 break;
310 }
311
312 err = BZ2_bzDecompress( bzstream );
313
314 if ( err != BZ_OK )
315 {
316 zip->reset = 1;
317
318 if ( err == BZ_STREAM_END )
319 {
320 zip->limit = (FT_Byte*)bzstream->next_out;
321 if ( zip->limit == zip->cursor )
322 error = FT_THROW( Invalid_Stream_Operation );
323 break;
324 }
325 else
326 {
327 zip->limit = zip->cursor;
328 error = FT_THROW( Invalid_Stream_Operation );
329 break;
330 }
331 }
332 }
333
334 return error;
335 }
336
337
338 /* fill output buffer; `count' must be <= FT_BZIP2_BUFFER_SIZE */
339 static FT_Error
340 ft_bzip2_file_skip_output( FT_BZip2File zip,
341 FT_ULong count )
342 {
343 FT_Error error = FT_Err_Ok;
344
345
346 for (;;)
347 {
348 FT_ULong delta = (FT_ULong)( zip->limit - zip->cursor );
349
350
351 if ( delta >= count )
352 delta = count;
353
354 zip->cursor += delta;
355 zip->pos += delta;
356
357 count -= delta;
358 if ( count == 0 )
359 break;
360
361 error = ft_bzip2_file_fill_output( zip );
362 if ( error )
363 break;
364 }
365
366 return error;
367 }
368
369
370 static FT_ULong
371 ft_bzip2_file_io( FT_BZip2File zip,
372 FT_ULong pos,
373 FT_Byte* buffer,
374 FT_ULong count )
375 {
376 FT_ULong result = 0;
377 FT_Error error;
378
379
380 /* Reset inflate stream if seeking backwards or bzip reported an error. */
381 /* Yes, that is not too efficient, but it saves memory :-) */
382 if ( pos < zip->pos || zip->reset )
383 {
384 error = ft_bzip2_file_reset( zip );
385 if ( error )
386 goto Exit;
387 }
388
389 /* skip unwanted bytes */
390 if ( pos > zip->pos )
391 {
392 error = ft_bzip2_file_skip_output( zip, (FT_ULong)( pos - zip->pos ) );
393 if ( error )
394 goto Exit;
395 }
396
397 if ( count == 0 )
398 goto Exit;
399
400 /* now read the data */
401 for (;;)
402 {
403 FT_ULong delta;
404
405
406 delta = (FT_ULong)( zip->limit - zip->cursor );
407 if ( delta >= count )
408 delta = count;
409
410 FT_MEM_COPY( buffer, zip->cursor, delta );
411 buffer += delta;
412 result += delta;
413 zip->cursor += delta;
414 zip->pos += delta;
415
416 count -= delta;
417 if ( count == 0 )
418 break;
419
420 error = ft_bzip2_file_fill_output( zip );
421 if ( error )
422 break;
423 }
424
425 Exit:
426 return result;
427 }
428
429
430/***************************************************************************/
431/***************************************************************************/
432/***** *****/
433/***** B Z E M B E D D I N G S T R E A M *****/
434/***** *****/
435/***************************************************************************/
436/***************************************************************************/
437
438 static void
439 ft_bzip2_stream_close( FT_Stream stream )
440 {
441 FT_BZip2File zip = (FT_BZip2File)stream->descriptor.pointer;
442 FT_Memory memory = stream->memory;
443
444
445 if ( zip )
446 {
447 /* finalize bzip file descriptor */
448 ft_bzip2_file_done( zip );
449
450 FT_FREE( zip );
451
452 stream->descriptor.pointer = NULL;
453 }
454 }
455
456
457 static unsigned long
458 ft_bzip2_stream_io( FT_Stream stream,
459 unsigned long offset,
460 unsigned char* buffer,
461 unsigned long count )
462 {
463 FT_BZip2File zip = (FT_BZip2File)stream->descriptor.pointer;
464
465
466 return ft_bzip2_file_io( zip, offset, buffer, count );
467 }
468
469
470 FT_EXPORT_DEF( FT_Error )
471 FT_Stream_OpenBzip2( FT_Stream stream,
472 FT_Stream source )
473 {
474 FT_Error error;
475 FT_Memory memory;
476 FT_BZip2File zip = NULL;
477
478
479 if ( !stream || !source )
480 {
481 error = FT_THROW( Invalid_Stream_Handle );
482 goto Exit;
483 }
484
485 memory = source->memory;
486
487 /*
488 * check the header right now; this prevents allocating unnecessary
489 * objects when we don't need them
490 */
491 error = ft_bzip2_check_header( source );
492 if ( error )
493 goto Exit;
494
495 FT_ZERO( stream );
496 stream->memory = memory;
497
498 if ( !FT_QNEW( zip ) )
499 {
500 error = ft_bzip2_file_init( zip, stream, source );
501 if ( error )
502 {
503 FT_FREE( zip );
504 goto Exit;
505 }
506
507 stream->descriptor.pointer = zip;
508 }
509
510 stream->size = 0x7FFFFFFFL; /* don't know the real size! */
511 stream->pos = 0;
512 stream->base = NULL;
513 stream->read = ft_bzip2_stream_io;
514 stream->close = ft_bzip2_stream_close;
515
516 Exit:
517 return error;
518 }
519
520#else /* !FT_CONFIG_OPTION_USE_BZIP2 */
521
522 FT_EXPORT_DEF( FT_Error )
523 FT_Stream_OpenBzip2( FT_Stream stream,
524 FT_Stream source )
525 {
526 FT_UNUSED( stream );
527 FT_UNUSED( source );
528
529 return FT_THROW( Unimplemented_Feature );
530 }
531
532#endif /* !FT_CONFIG_OPTION_USE_BZIP2 */
533
534
535/* END */
536