1

I'd like to create a ColdFusion page that handles CORS and serves fonts based on a URL param. So far I am able to download the font file, but the file is ~28KB (~15%) larger than the original. When I open it in Windows Font Viewer (Windows 7), I get the message The requested file ... is not a valid font file. So some junk must be getting added to the file as it passes through.

In this simplified example I have only one font and a directory /fonts/icomoon/ which contains one .ttf font file.

The following code is the index.cfc file, and it is accessed w/ the url params index.cfc?method=get&font=icomoon.

<cfcomponent>

<cffunction name="get" access="remote" returntype="any">
    <cfargument name="font" type="string" required="true">
    <cfsetting showDebugOutput="No" enableCFoutputOnly="Yes">
    <cfscript>
        if (font eq "icomoon") var fn = "IcoMoon-Free.ttf";
        setFontHeaders(fn);
        setCORSHeaders([                // Optional
            "http://example.com",       // Example origins
            "http://stackoverflow.com"
        ]);
        include "fonts/icomoon/#fn#";
    </cfscript>
</cffunction>

<cffunction name="setFontHeaders" access="private" output="false">
    <cfargument name="filename" type="string" required="false" default="">
    <cfscript>
        var response = getPageContext().getResponse();
        response.setHeader("Content-Type", "application/x-opentype"); // See below
        if (arguments.filename neq "") {
            response.setHeader("Content-Disposition", 
                "attachment; filename=#arguments.filename#");
        }
    </cfscript>
</cffunction>

<cffunction name="setCORSHeaders" access="private" output="false">
    <cfargument name="whitelistOriginArray" type="array" required="true">
    <cfargument name="accessControlAllowMethods" type="string" required="false" 
        default="GET">      
    <cfscript>
        var response = getPageContext().getResponse();
        var headers = getHttpRequestData().headers;
        var origin = (StructKeyExists(headers, "Origin")) ? headers['Origin'] : '';
        if (ArrayFind(arguments.whitelistOriginArray, LCase(origin))) {
            response.setHeader("Access-Control-Allow-Origin", origin);
        }
        response.setHeader("Access-Control-Allow-Methods", 
            arguments.accessControlAllowMethods);
    </cfscript>
</cffunction>

</cfcomponent>

I tried...

  • all the Content-Types I could find, but that didn't seem to make a difference. (Source: Proper MIME type for fonts)
  • wrapping the content in <cfprocessingdirective suppressWhiteSpace="true">
  • doing savecontent variable="c" { include "fonts/icomoon/#fn#"; }; return c; rather than just the include
  • a bunch more stupid things, but to no avail

NOTE: The CORS functionality (setCORSHeaders) is optional and if left off the problem persists. I kept it in to avoid the question "Why go through ColdFusion at all and just include the original font file?".

Community
  • 1
  • 1
Luke
  • 18,811
  • 16
  • 99
  • 115
  • try to download this file via browser, open original file and downloaded, what is the difference? – Iłya Bursov Dec 29 '14 at 23:05
  • The original file (192KB) opens fine in Windows Font Viewer. The downloaded file (221KB) does not open because it is not a valid font file. Both files are illegible if opened in a text editor. – Luke Dec 29 '14 at 23:16
  • _Both files are illegible if opened in a text editor._ Notepad is a text editor. Fonts do not make sense to most humans in notepad or any other text editors. – Luke Dec 29 '14 at 23:24
  • but in notepad you can see what is appended to this file and find root cause, also consider any hex viewer/comparer to see difference – Iłya Bursov Dec 29 '14 at 23:25

2 Answers2

2

Why are you doing all the Java stuff and not using built-in functions for setting and reading headers?

Check out the GetHttpRequestData() function for getting the incoming headers as well as the cfheader tag for setting response headers.

Chances are, your problem is that you're introducing whitespace into the output and/or hitting encoding issues by cfinclude a binary file. Instead, try using cfcontent which will automatically reset out the output buffer when using the file attribute.

Brad Wood
  • 3,863
  • 16
  • 23
  • Thanks! Replacing the `include` with `cfcontent` seems to solve the problem. The Java code is equivalent to the corresponding CF tags; it's just coding style. – Luke Dec 30 '14 at 20:10
  • I'd call it bording use of undocumented underlying Java methods, not coding style. There's no benifit I'm aware of using Java other than more code and the possibility of it breaking in the future. – Brad Wood Dec 30 '14 at 22:33
  • That is certainly a topic worthy of debate, albeit off-topic for this particular question. See http://stackoverflow.com/questions/1331087/safe-to-call-underlying-java-method-on-string-in-coldfusion – Luke Dec 31 '14 at 21:14
1

Here is the code that fixed the problem, thanks to Brad Wood's suggestion to use cfcontent.

<cffunction name="get" access="remote" returntype="any">
    <cfargument name="font" type="string" required="true">
    <cfsetting showDebugOutput="No">
    <cfscript>
        if (font eq "icomoon") var fn = "IcoMoon-Free.ttf";
        setCORSHeaders([                // See function in original question
            "http://example.com",
            "http://stackoverflow.com"
        ]);
        var filePath = ExpandPath("fonts/icomoon/#fn#");
    </cfscript>
    <cfheader name="Content-Disposition" value="attachment; filename=#fn#">
    <cfcontent file="#filePath#" type="font/ttf">
</cffunction>

I'm not 100% sure about the type for the content -- there are many opinions on this (see: Proper MIME type for fonts) -- but using "font/ttf" seems to work and avoids warnings in Chrome.

Community
  • 1
  • 1
Luke
  • 18,811
  • 16
  • 99
  • 115