1/*
2Copyright (c) 1998-2001 by Juliusz Chroboczek
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20THE SOFTWARE.
21*/
22
23/* Parser for encoding files */
24
25/* This code assumes that we are using ASCII.  We don't use the ctype
26   functions, as they depend on the current locale.  On the other
27   hand, we do use strcasecmp, but only on strings that we've checked
28   to be pure ASCII.  Bloody ``Code Set Independence''. */
29
30#ifdef HAVE_CONFIG_H
31#include <config.h>
32#endif
33
34#include <string.h>
35#include <strings.h>
36#include <stdio.h>
37
38#include <stdlib.h>
39
40#include <fcntl.h>
41#ifdef O_CLOEXEC
42#define FOPEN_CLOEXEC "e"
43#else
44#define FOPEN_CLOEXEC ""
45#endif
46
47
48#include "zlib.h"
49typedef gzFile FontFilePtr;
50
51#define FontFileGetc(f) gzgetc(f)
52#define FontFileOpen(filename) gzopen(filename, "rb")
53#define FontFileClose(f) gzclose(f)
54
55#define MAXFONTFILENAMELEN 1024
56#define MAXFONTNAMELEN 1024
57
58#include <X11/fonts/fontenc.h>
59#include "fontencI.h"
60#include "reallocarray.h"
61
62#define MAXALIASES 20
63
64#define EOF_TOKEN -1
65#define ERROR_TOKEN -2
66#define EOL_TOKEN 0
67#define NUMBER_TOKEN 1
68#define KEYWORD_TOKEN 2
69
70#define EOF_LINE -1
71#define ERROR_LINE -2
72#define STARTENCODING_LINE 1
73#define STARTMAPPING_LINE 2
74#define ENDMAPPING_LINE 3
75#define CODE_LINE 4
76#define CODE_RANGE_LINE 5
77#define CODE_UNDEFINE_LINE 6
78#define NAME_LINE 7
79#define SIZE_LINE 8
80#define ALIAS_LINE 9
81#define FIRSTINDEX_LINE 10
82
83/* Return from lexer */
84#define MAXKEYWORDLEN 100
85
86static long number_value;
87static char keyword_value[MAXKEYWORDLEN + 1];
88
89static long value1, value2, value3;
90
91/* Lexer code */
92
93/* Skip to the beginning of new line */
94static void
95skipEndOfLine(FontFilePtr f, int c)
96{
97    if (c == 0)
98        c = FontFileGetc(f);
99
100    for (;;)
101        if (c <= 0 || c == '\n')
102            return;
103        else
104            c = FontFileGetc(f);
105}
106
107/* Get a number; we're at the first digit. */
108static unsigned
109getnum(FontFilePtr f, int c, int *cp)
110{
111    unsigned n = 0;
112    int base = 10;
113
114    /* look for `0' or `0x' prefix */
115    if (c == '0') {
116        c = FontFileGetc(f);
117        base = 8;
118        if (c == 'x' || c == 'X') {
119            base = 16;
120            c = FontFileGetc(f);
121        }
122    }
123
124    /* accumulate digits */
125    for (;;) {
126        if ('0' <= c && c <= '9') {
127            n *= base;
128            n += c - '0';
129        }
130        else if ('a' <= c && c <= 'f') {
131            n *= base;
132            n += c - 'a' + 10;
133        }
134        else if ('A' <= c && c <= 'F') {
135            n *= base;
136            n += c - 'A' + 10;
137        }
138        else
139            break;
140        c = FontFileGetc(f);
141    }
142
143    *cp = c;
144    return n;
145}
146
147/* Skip to beginning of new line; return 1 if only whitespace was found. */
148static int
149endOfLine(FontFilePtr f, int c)
150{
151    if (c == 0)
152        c = FontFileGetc(f);
153
154    for (;;) {
155        if (c <= 0 || c == '\n')
156            return 1;
157        else if (c == '#') {
158            skipEndOfLine(f, c);
159            return 1;
160        }
161        else if (c == ' ' || c == '\t') {
162            skipEndOfLine(f, c);
163            return 0;
164        }
165        c = FontFileGetc(f);
166    }
167}
168
169/* Get a token; we're at first char */
170static int
171gettoken(FontFilePtr f, int c, int *cp)
172{
173    char *p;
174
175    if (c <= 0)
176        c = FontFileGetc(f);
177
178    if (c <= 0) {
179        return EOF_TOKEN;
180    }
181
182    while (c == ' ' || c == '\t')
183        c = FontFileGetc(f);
184
185    if (c == '\n') {
186        return EOL_TOKEN;
187    }
188    else if (c == '#') {
189        skipEndOfLine(f, c);
190        return EOL_TOKEN;
191    }
192    else if (c >= '0' && c <= '9') {
193        number_value = getnum(f, c, cp);
194        return NUMBER_TOKEN;
195    }
196    else if ((c >= 'A' && c <= 'Z') ||
197             (c >= 'a' && c <= 'z') ||
198             c == '/' || c == '_' || c == '-' || c == '.') {
199        p = keyword_value;
200        *p++ = c;
201        while (p - keyword_value < MAXKEYWORDLEN) {
202            c = FontFileGetc(f);
203            if (c <= ' ' || c > '~' || c == '#')
204                break;
205            *p++ = c;
206        }
207        *cp = c;
208        *p = '\0';
209        return KEYWORD_TOKEN;
210    }
211    else {
212        *cp = c;
213        return ERROR_TOKEN;
214    }
215}
216
217/* Parse a line.
218 * Always skips to the beginning of a new line, even if an error occurs */
219static int
220getnextline(FontFilePtr f)
221{
222    int c, token;
223
224    c = FontFileGetc(f);
225    if (c <= 0)
226        return EOF_LINE;
227
228 again:
229    token = gettoken(f, c, &c);
230
231    switch (token) {
232    case EOF_TOKEN:
233        return EOF_LINE;
234    case EOL_TOKEN:
235        /* empty line */
236        c = FontFileGetc(f);
237        goto again;
238    case NUMBER_TOKEN:
239        value1 = number_value;
240        token = gettoken(f, c, &c);
241        switch (token) {
242        case NUMBER_TOKEN:
243            value2 = number_value;
244            token = gettoken(f, c, &c);
245            switch (token) {
246            case NUMBER_TOKEN:
247                value3 = number_value;
248                return CODE_RANGE_LINE;
249            case EOL_TOKEN:
250                return CODE_LINE;
251            default:
252                skipEndOfLine(f, c);
253                return ERROR_LINE;
254            }
255        case KEYWORD_TOKEN:
256            if (!endOfLine(f, c))
257                return ERROR_LINE;
258            else
259                return NAME_LINE;
260        default:
261            skipEndOfLine(f, c);
262            return ERROR_LINE;
263        }
264    case KEYWORD_TOKEN:
265        if (!strcasecmp(keyword_value, "STARTENCODING")) {
266            token = gettoken(f, c, &c);
267            if (token == KEYWORD_TOKEN) {
268                if (endOfLine(f, c))
269                    return STARTENCODING_LINE;
270                else
271                    return ERROR_LINE;
272            }
273            else {
274                skipEndOfLine(f, c);
275                return ERROR_LINE;
276            }
277        }
278        else if (!strcasecmp(keyword_value, "ALIAS")) {
279            token = gettoken(f, c, &c);
280            if (token == KEYWORD_TOKEN) {
281                if (endOfLine(f, c))
282                    return ALIAS_LINE;
283                else
284                    return ERROR_LINE;
285            }
286            else {
287                skipEndOfLine(f, c);
288                return ERROR_LINE;
289            }
290        }
291        else if (!strcasecmp(keyword_value, "SIZE")) {
292            token = gettoken(f, c, &c);
293            if (token == NUMBER_TOKEN) {
294                value1 = number_value;
295                token = gettoken(f, c, &c);
296                switch (token) {
297                case NUMBER_TOKEN:
298                    value2 = number_value;
299                    return SIZE_LINE;
300                case EOL_TOKEN:
301                    value2 = 0;
302                    return SIZE_LINE;
303                default:
304                    skipEndOfLine(f, c);
305                    return ERROR_LINE;
306                }
307            }
308            else {
309                skipEndOfLine(f, c);
310                return ERROR_LINE;
311            }
312        }
313        else if (!strcasecmp(keyword_value, "FIRSTINDEX")) {
314            token = gettoken(f, c, &c);
315            if (token == NUMBER_TOKEN) {
316                value1 = number_value;
317                token = gettoken(f, c, &c);
318                switch (token) {
319                case NUMBER_TOKEN:
320                    value2 = number_value;
321                    return FIRSTINDEX_LINE;
322                case EOL_TOKEN:
323                    value2 = 0;
324                    return FIRSTINDEX_LINE;
325                default:
326                    skipEndOfLine(f, c);
327                    return ERROR_LINE;
328                }
329            }
330            else {
331                skipEndOfLine(f, c);
332                return ERROR_LINE;
333            }
334        }
335        else if (!strcasecmp(keyword_value, "STARTMAPPING")) {
336            keyword_value[0] = 0;
337            value1 = 0;
338            value2 = 0;
339            /* first a keyword */
340            token = gettoken(f, c, &c);
341            if (token != KEYWORD_TOKEN) {
342                skipEndOfLine(f, c);
343                return ERROR_LINE;
344            }
345
346            /* optional first integer */
347            token = gettoken(f, c, &c);
348            if (token == NUMBER_TOKEN) {
349                value1 = number_value;
350            }
351            else if (token == EOL_TOKEN) {
352                return STARTMAPPING_LINE;
353            }
354            else {
355                skipEndOfLine(f, c);
356                return ERROR_LINE;
357            }
358
359            /* optional second integer */
360            token = gettoken(f, c, &c);
361            if (token == NUMBER_TOKEN) {
362                value2 = number_value;
363            }
364            else if (token == EOL_TOKEN) {
365                return STARTMAPPING_LINE;
366            }
367            else {
368                skipEndOfLine(f, c);
369                return ERROR_LINE;
370            }
371
372            if (!endOfLine(f, c))
373                return ERROR_LINE;
374            else {
375                return STARTMAPPING_LINE;
376            }
377        }
378        else if (!strcasecmp(keyword_value, "UNDEFINE")) {
379            /* first integer */
380            token = gettoken(f, c, &c);
381            if (token != NUMBER_TOKEN) {
382                skipEndOfLine(f, c);
383                return ERROR_LINE;
384            }
385            value1 = number_value;
386            /* optional second integer */
387            token = gettoken(f, c, &c);
388            if (token == EOL_TOKEN) {
389                value2 = value1;
390                return CODE_UNDEFINE_LINE;
391            }
392            else if (token == NUMBER_TOKEN) {
393                value2 = number_value;
394                if (endOfLine(f, c)) {
395                    return CODE_UNDEFINE_LINE;
396                }
397                else
398                    return ERROR_LINE;
399            }
400            else {
401                skipEndOfLine(f, c);
402                return ERROR_LINE;
403            }
404        }
405        else if (!strcasecmp(keyword_value, "ENDENCODING")) {
406            if (endOfLine(f, c))
407                return EOF_LINE;
408            else
409                return ERROR_LINE;
410        }
411        else if (!strcasecmp(keyword_value, "ENDMAPPING")) {
412            if (endOfLine(f, c))
413                return ENDMAPPING_LINE;
414            else
415                return ERROR_LINE;
416        }
417        else {
418            skipEndOfLine(f, c);
419            return ERROR_LINE;
420        }
421    default:
422        return ERROR_LINE;
423    }
424}
425
426static void
427install_mapping(FontEncPtr encoding, FontMapPtr mapping)
428{
429    FontMapPtr m;
430
431    if (encoding->mappings == NULL)
432        encoding->mappings = mapping;
433    else {
434        m = encoding->mappings;
435        while (m->next != NULL)
436            m = m->next;
437        m->next = mapping;
438    }
439    mapping->next = NULL;
440    mapping->encoding = encoding;
441}
442
443static int
444setCode(unsigned from, unsigned to, unsigned row_size,
445        unsigned *first, unsigned *last,
446        unsigned *encsize, unsigned short **enc)
447{
448    unsigned index, i;
449
450    unsigned short *newenc;
451
452    if (from > 0xFFFF)
453        return 0;               /* success */
454
455    if (row_size == 0)
456        index = from;
457    else {
458        if ((value1 & 0xFF) >= row_size)
459            return 0;           /* ignore out of range mappings */
460        index = (from >> 8) * row_size + (from & 0xFF);
461    }
462
463    /* Optimize away useless identity mappings.  This is only expected
464       to be useful with linear encodings. */
465    if (index == to && (index < *first || index > *last))
466        return 0;
467    if (*encsize == 0) {
468        *encsize = (index < 256) ? 256 : 0x10000;
469        *enc = Xmallocarray(*encsize, sizeof(unsigned short));
470        if (*enc == NULL) {
471            *encsize = 0;
472            return 1;
473        }
474    }
475    else if (*encsize <= index) {
476        *encsize = 0x10000;
477        newenc = Xreallocarray(*enc, *encsize, sizeof(unsigned short));
478        if (newenc == NULL)
479            return 1;
480        *enc = newenc;
481    }
482    if (*first > *last) {
483        *first = *last = index;
484    }
485    if (index < *first) {
486        for (i = index; i < *first; i++)
487            (*enc)[i] = i;
488        *first = index;
489    }
490    if (index > *last) {
491        for (i = *last + 1; i <= index; i++)
492            (*enc)[i] = i;
493        *last = index;
494    }
495    (*enc)[index] = to;
496    return 0;
497}
498
499/* Parser.  If headerOnly is true, we're only interested in the
500   data contained in the encoding file's header. */
501
502/* As font encodings are currently never freed, the allocations done
503   by this function are mostly its private business.  Note, however,
504   that FontEncIdentify needs to free the header fields -- so if you
505   change this function, you may need to change FontEncIdentify. */
506
507/* I want a garbage collector. */
508
509static FontEncPtr
510parseEncodingFile(FontFilePtr f, int headerOnly)
511{
512    int line;
513
514    unsigned short *enc = NULL;
515    char **nam = NULL, **newnam;
516    unsigned i, first = 0xFFFF, last = 0, encsize = 0, namsize = 0;
517    FontEncPtr encoding = NULL;
518    FontMapPtr mapping = NULL;
519    FontEncSimpleMapPtr sm;
520    FontEncSimpleNamePtr sn;
521    char *aliases[MAXALIASES] = { NULL };
522    int numaliases = 0;
523
524#if 0
525    /* GCC complains about unused labels.  Please fix GCC rather than
526       obfuscating my code. */
527 no_encoding:
528#endif
529    line = getnextline(f);
530    switch (line) {
531    case EOF_LINE:
532        goto error;
533    case STARTENCODING_LINE:
534        encoding = malloc(sizeof(FontEncRec));
535        if (encoding == NULL)
536            goto error;
537        encoding->name = strdup(keyword_value);
538        if (encoding->name == NULL)
539            goto error;
540        encoding->size = 256;
541        encoding->row_size = 0;
542        encoding->mappings = NULL;
543        encoding->next = NULL;
544        encoding->first = encoding->first_col = 0;
545        goto no_mapping;
546    default:
547        goto error;
548    }
549
550 no_mapping:
551    line = getnextline(f);
552    switch (line) {
553    case EOF_LINE:
554        goto done;
555    case ALIAS_LINE:
556        if (numaliases < MAXALIASES) {
557            aliases[numaliases] = strdup(keyword_value);
558            if (aliases[numaliases] == NULL)
559                goto error;
560            numaliases++;
561        }
562        goto no_mapping;
563    case SIZE_LINE:
564        encoding->size = value1;
565        encoding->row_size = value2;
566        goto no_mapping;
567    case FIRSTINDEX_LINE:
568        encoding->first = value1;
569        encoding->first_col = value2;
570        goto no_mapping;
571    case STARTMAPPING_LINE:
572        if (headerOnly)
573            goto done;
574        if (!strcasecmp(keyword_value, "unicode")) {
575            mapping = malloc(sizeof(FontMapRec));
576            if (mapping == NULL)
577                goto error;
578            mapping->type = FONT_ENCODING_UNICODE;
579            mapping->pid = 0;
580            mapping->eid = 0;
581            mapping->recode = NULL;
582            mapping->name = NULL;
583            mapping->client_data = NULL;
584            mapping->next = NULL;
585            goto mapping;
586        }
587        else if (!strcasecmp(keyword_value, "cmap")) {
588            mapping = malloc(sizeof(FontMapRec));
589            if (mapping == NULL)
590                goto error;
591            mapping->type = FONT_ENCODING_TRUETYPE;
592            mapping->pid = value1;
593            mapping->eid = value2;
594            mapping->recode = NULL;
595            mapping->name = NULL;
596            mapping->client_data = NULL;
597            mapping->next = NULL;
598            goto mapping;
599        }
600        else if (!strcasecmp(keyword_value, "postscript")) {
601            mapping = malloc(sizeof(FontMapRec));
602            if (mapping == NULL)
603                goto error;
604            mapping->type = FONT_ENCODING_POSTSCRIPT;
605            mapping->pid = 0;
606            mapping->eid = 0;
607            mapping->recode = NULL;
608            mapping->name = NULL;
609            mapping->client_data = NULL;
610            mapping->next = NULL;
611            goto string_mapping;
612        }
613        else {                  /* unknown mapping type -- ignore */
614            goto skipmapping;
615        }
616        /* NOTREACHED */
617        goto error;
618    default:
619        goto no_mapping;        /* ignore unknown lines */
620    }
621
622 skipmapping:
623    line = getnextline(f);
624    switch (line) {
625    case ENDMAPPING_LINE:
626        goto no_mapping;
627    case EOF_LINE:
628        goto error;
629    default:
630        goto skipmapping;
631    }
632
633 mapping:
634    line = getnextline(f);
635    switch (line) {
636    case EOF_LINE:
637        goto error;
638    case ENDMAPPING_LINE:
639        mapping->recode = FontEncSimpleRecode;
640        mapping->name = FontEncUndefinedName;
641        mapping->client_data = sm = malloc(sizeof(FontEncSimpleMapRec));
642        if (sm == NULL)
643            goto error;
644        sm->row_size = encoding->row_size;
645        if (first <= last) {
646            unsigned short *newmap;
647
648            sm->first = first;
649            sm->len = last - first + 1;
650            newmap = Xmallocarray(sm->len, sizeof(unsigned short));
651            if (newmap == NULL) {
652                free(sm);
653                mapping->client_data = sm = NULL;
654                goto error;
655            }
656            for (i = 0; i < sm->len; i++)
657                newmap[i] = enc[first + i];
658            sm->map = newmap;
659        }
660        else {
661            sm->first = 0;
662            sm->len = 0;
663            sm->map = NULL;
664        }
665        install_mapping(encoding, mapping);
666        mapping = NULL;
667        first = 0xFFFF;
668        last = 0;
669        goto no_mapping;
670
671    case CODE_LINE:
672        if (setCode(value1, value2, encoding->row_size,
673                    &first, &last, &encsize, &enc))
674            goto error;
675        goto mapping;
676
677    case CODE_RANGE_LINE:
678        if (value1 > 0x10000)
679            value1 = 0x10000;
680        if (value2 > 0x10000)
681            value2 = 0x10000;
682        if (value2 < value1)
683            goto mapping;
684        /* Do the last value first to avoid having to realloc() */
685        if (setCode(value2, value3 + (value2 - value1), encoding->row_size,
686                    &first, &last, &encsize, &enc))
687            goto error;
688        for (i = value1; i < value2; i++) {
689            if (setCode(i, value3 + (i - value1), encoding->row_size,
690                        &first, &last, &encsize, &enc))
691                goto error;
692        }
693        goto mapping;
694
695    case CODE_UNDEFINE_LINE:
696        if (value1 > 0x10000)
697            value1 = 0x10000;
698        if (value2 > 0x10000)
699            value2 = 0x10000;
700        if (value2 < value1)
701            goto mapping;
702        /* Do the last value first to avoid having to realloc() */
703        if (setCode(value2, 0, encoding->row_size,
704                    &first, &last, &encsize, &enc))
705            goto error;
706        for (i = value1; i < value2; i++) {
707            if (setCode(i, 0, encoding->row_size,
708                        &first, &last, &encsize, &enc))
709                goto error;
710        }
711        goto mapping;
712
713    default:
714        goto mapping;           /* ignore unknown lines */
715    }
716
717 string_mapping:
718    line = getnextline(f);
719    switch (line) {
720    case EOF_LINE:
721        goto error;
722    case ENDMAPPING_LINE:
723        mapping->recode = FontEncUndefinedRecode;
724        mapping->name = FontEncSimpleName;
725        mapping->client_data = sn = malloc(sizeof(FontEncSimpleNameRec));
726        if (sn == NULL)
727            goto error;
728        if (first > last) {
729            free(sn);
730            mapping->client_data = sn = NULL;
731            goto error;
732        }
733        sn->first = first;
734        sn->len = last - first + 1;
735        sn->map = Xmallocarray(sn->len, sizeof(char *));
736        if (sn->map == NULL) {
737            free(sn);
738            mapping->client_data = sn = NULL;
739            goto error;
740        }
741        for (i = 0; i < sn->len; i++)
742            sn->map[i] = nam[first + i];
743        install_mapping(encoding, mapping);
744        mapping = NULL;
745        first = 0xFFFF;
746        last = 0;
747        goto no_mapping;
748    case NAME_LINE:
749        if (value1 >= 0x10000)
750            goto string_mapping;
751        if (namsize == 0) {
752            namsize = (value1) < 256 ? 256 : 0x10000;
753            nam = Xmallocarray(namsize, sizeof(char *));
754            if (nam == NULL) {
755                namsize = 0;
756                goto error;
757            }
758        }
759        else if (namsize <= value1) {
760            namsize = 0x10000;
761            if ((newnam = (char **) realloc(nam, namsize)) == NULL)
762                goto error;
763            nam = newnam;
764        }
765        if (first > last) {
766            first = last = value1;
767        }
768        if (value1 < first) {
769            for (i = value1; i < first; i++)
770                nam[i] = NULL;
771            first = value1;
772        }
773        if (value1 > last) {
774            for (i = last + 1; i <= value1; i++)
775                nam[i] = NULL;
776            last = value1;
777        }
778        nam[value1] = strdup(keyword_value);
779        if (nam[value1] == NULL) {
780            goto error;
781        }
782        goto string_mapping;
783
784    default:
785        goto string_mapping;    /* ignore unknown lines */
786    }
787
788 done:
789    if (encsize) {
790        free(enc);
791        encsize = 0;
792        enc = NULL;
793    }
794    if (namsize) {
795        free(nam);             /* don't free entries! */
796        namsize = 0;
797        nam = NULL;
798    }
799
800    encoding->aliases = NULL;
801    if (numaliases) {
802        encoding->aliases = Xmallocarray(numaliases + 1, sizeof(char *));
803        if (encoding->aliases == NULL)
804            goto error;
805        for (i = 0; i < numaliases; i++)
806            encoding->aliases[i] = aliases[i];
807        encoding->aliases[numaliases] = NULL;
808    }
809
810    return encoding;
811
812 error:
813    if (encsize) {
814        free(enc);
815        encsize = 0;
816    }
817    if (namsize) {
818        for (i = first; i <= last; i++)
819            free(nam[i]);
820        free(nam);
821    }
822    if (mapping) {
823        free(mapping->client_data);
824        free(mapping);
825    }
826    if (encoding) {
827        FontMapPtr nextmap;
828
829        free(encoding->name);
830        for (mapping = encoding->mappings; mapping; mapping = nextmap) {
831            free(mapping->client_data);
832            nextmap = mapping->next;
833            free(mapping);
834        }
835        free(encoding);
836    }
837    for (i = 0; i < numaliases; i++)
838        free(aliases[i]);
839    /* We don't need to free sn and sm as they handled locally in the body. */
840    return NULL;
841}
842
843const char *
844FontEncDirectory(void)
845{
846    static const char *dir = NULL;
847
848    if (dir == NULL) {
849        const char *c = getenv("FONT_ENCODINGS_DIRECTORY");
850
851        if (c) {
852            dir = strdup(c);
853            if (!dir)
854                return NULL;
855        }
856        else {
857            dir = FONT_ENCODINGS_DIRECTORY;
858        }
859    }
860    return dir;
861}
862
863static void
864parseFontFileName(const char *fontFileName, char *buf, char *dir)
865{
866    const char *p;
867    char *q, *lastslash;
868
869    for (p = fontFileName, q = dir, lastslash = NULL; *p; p++, q++) {
870        *q = *p;
871        if (*p == '/')
872            lastslash = q + 1;
873    }
874
875    if (!lastslash)
876        lastslash = dir;
877
878    *lastslash = '\0';
879
880    if (buf && strlen(dir) + 14 < MAXFONTFILENAMELEN) {
881        snprintf(buf, MAXFONTFILENAMELEN, "%s%s", dir, "encodings.dir");
882    }
883}
884
885static FontEncPtr
886FontEncReallyReallyLoad(const char *charset,
887                        const char *dirname, const char *dir)
888{
889    FontFilePtr f;
890    FILE *file;
891    FontEncPtr encoding;
892    char file_name[MAXFONTFILENAMELEN], encoding_name[MAXFONTNAMELEN],
893        buf[MAXFONTFILENAMELEN];
894    int count, n;
895    static char format[24] = "";
896
897    /* As we don't really expect to open encodings that often, we don't
898       take the trouble of caching encodings directories. */
899
900    if ((file = fopen(dirname, "r" FOPEN_CLOEXEC)) == NULL) {
901        return NULL;
902    }
903
904    count = fscanf(file, "%d\n", &n);
905    if (count == EOF || count != 1) {
906        fclose(file);
907        return NULL;
908    }
909
910    encoding = NULL;
911    if (!format[0]) {
912        snprintf(format, sizeof(format), "%%%ds %%%d[^\n]\n",
913                 (int) sizeof(encoding_name) - 1, (int) sizeof(file_name) - 1);
914    }
915    for (;;) {
916        count = fscanf(file, format, encoding_name, file_name);
917        if (count == EOF)
918            break;
919        if (count != 2)
920            break;
921
922        if (!strcasecmp(encoding_name, charset)) {
923            /* Found it */
924            if (file_name[0] != '/') {
925                if (strlen(dir) + strlen(file_name) >= MAXFONTFILENAMELEN) {
926                    fclose(file);
927                    return NULL;
928                }
929                snprintf(buf, MAXFONTFILENAMELEN, "%s%s", dir, file_name);
930            }
931            else {
932                snprintf(buf, MAXFONTFILENAMELEN, "%s", file_name);
933            }
934
935            f = FontFileOpen(buf);
936            if (f == NULL) {
937                fclose(file);
938                return NULL;
939            }
940            encoding = parseEncodingFile(f, 0);
941            FontFileClose(f);
942            break;
943        }
944    }
945
946    fclose(file);
947
948    return encoding;
949}
950
951/* Parser ntrypoint -- used by FontEncLoad */
952FontEncPtr
953FontEncReallyLoad(const char *charset, const char *fontFileName)
954{
955    FontEncPtr encoding;
956    char dir[MAXFONTFILENAMELEN], dirname[MAXFONTFILENAMELEN];
957    const char *d;
958
959    if (fontFileName) {
960        parseFontFileName(fontFileName, dirname, dir);
961        encoding = FontEncReallyReallyLoad(charset, dirname, dir);
962        if (encoding)
963            return (encoding);
964    }
965
966    d = FontEncDirectory();
967    if (d) {
968        parseFontFileName(d, NULL, dir);
969        encoding = FontEncReallyReallyLoad(charset, d, dir);
970        return encoding;
971    }
972
973    return NULL;
974}
975
976/* Return a NULL-terminated array of encoding names.  Note that this
977 * function has incestuous knowledge of the allocations done by
978 * parseEncodingFile. */
979
980char **
981FontEncIdentify(const char *fileName)
982{
983    FontFilePtr f;
984    FontEncPtr encoding;
985    char **names, **name, **alias;
986    int numaliases;
987
988    if ((f = FontFileOpen(fileName)) == NULL) {
989        return NULL;
990    }
991    encoding = parseEncodingFile(f, 1);
992    FontFileClose(f);
993
994    if (!encoding)
995        return NULL;
996
997    numaliases = 0;
998    if (encoding->aliases)
999        for (alias = encoding->aliases; *alias; alias++)
1000            numaliases++;
1001
1002    names = Xmallocarray(numaliases + 2, sizeof(char *));
1003    if (names == NULL) {
1004        free(encoding->aliases);
1005        free(encoding);
1006        return NULL;
1007    }
1008
1009    name = names;
1010    *(name++) = encoding->name;
1011    if (numaliases > 0)
1012        for (alias = encoding->aliases; *alias; alias++, name++)
1013            *name = *alias;
1014
1015    *name = NULL;
1016    free(encoding->aliases);
1017    free(encoding);
1018
1019    return names;
1020}
1021