0
votes

How can I tell the streamContext, created with stream_context_create() to use different HTTP Headers according to the domain name of a requested URI?

I'm using XSL to transform an XML on server side (PHP).

To do so, I first create a stream context with some options and apply this stream to libxml:

$streamContextOptions = array(
    'http' =>array(
        'header'=>$clientHeadersString /* HTTP header */
    )
);
$streamContext = stream_context_create($streamContextOptions);
libxml_set_streams_context($streamContext);

Then I load the XML, XSL and do the transform

// Load the XML
$xmlDocument = new DOMDocument();
$xmlDocument->load($xmlURI);

// Load the XSL into
$xslDocument = new DOMDocument();
$xslDocument->load($xslURI);
$xslProcessor = new XSLTProcessor();
$xslProcessor->importStylesheet($xslDocument);

// Apply the XSL
$htmlDocument = $xslProcessor->transformToDoc($xmlDocument);

In the XSL, I have some document() calls so the PHP server calls the specified URIs using the stream context. But I have different domains inside these document() calls (let's say I have www.foo.com and www.bar.com).

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="utf-8" indent="yes"/>

    <xsl:template match="/">
        <xsl:value-of select="document('http://www.foo.com/with-user-agent.xml.php')/root"/>
        <xsl:value-of select="document('http://www.bar.com/with-accept-language.php')/root"/>
    </xsl:template>
</xsl:stylesheet>    

What I want is to change the HTTP headers the libxml is using when doing the document() calls inside the XSL, so I can send [cookies + user-agent] to foo.com, and [other cookies + accept-language] to bar.com when they are requested inside an XSL document() call.

I don't want to send "too many" header to foo.com (aka, I don't want to send Accept-Language to foo.com) nor to bar.com, and I don't want the cookies to mix (cookies send to foo.com must not be send to bar.com).

Somehow, like:

$streamContextOptions = array(
    'http-for-foo' =>array(
        'header'=>$clientHeadersStringForFoo /* HTTP header only when document() calls for foo.com*/
    ),
    'http-for-bar' =>array(
        'header'=>$clientHeadersStringForBar /* HTTP header only when document() calls for bar.com*/
    )
);

Is there a way to tell XSLTProcessor to change the streamOptions depending on the domain requested by document() function?

2
Isn't the true problem your external resource is depends on request headers? Imho the resource(s) should be static and cacheable. - Roland Franssen
@RolandFranssen No, because the XML resource could be the user's profile data (username, avatar, mail...). So user must be logged in before being able to retrieve this kind of XML. The returned XML could also be based on Accept-Language for localization data. - Xenos

2 Answers

1
votes

You can not tell about different hosts for stream context options because with the stream I/O operation, the context has been assigned - there is no room with libxml_set_streams_context to differ based on hostname (also for streams in PHP this isn't possible, too. At least AFAIK).

So instead, you need to assign the URI with the appropriate stream options.

In your scenario it seems to me this is most easily done after the DOM setup but before the transformation by creating your own external entity loader that is able to differ the context options based on hostname:

...

$xslProcessor->importStylesheet($xslDocument);

$httpOptions       = [
    'timeout' => 1,
    'header'  => "User-Agent: Godzilla Gabba Gandalf Client 42.4 - Lord of the XSLT Weed Edition"
];
$httpOptionsByHost = [
    'www.foo.com' => [
        'header' => "X-Secret-Debug-Request-Flag: verbose-verbose-verbose"
    ],
    'www.bar.com' => [
        'header' => "User-Agent: 1' OR TRUE"
    ]
];

libxml_set_external_entity_loader(
    function ($public, $system, $context) use ($httpOptions, $httpOptionsByHost) {

        $url = new Net_URL2($system);
        $url->normalize();
        $host = $url->getHost();

        $options['http'] = [];

        if (isset($httpOptionsByHost[$host])) {
            $options['http'] = $httpOptionsByHost[$host];
        }

        $options['http'] += $httpOptions;

        $context = stream_context_create($options);

        return fopen($url, 'r', false, $context);
    }
);

// Apply the XSL
$htmlDocument = $xslProcessor->transformToDoc($xmlDocument);

...

You didn't supply the XML in your question nor are the external URIs working, so I could only test partially, but as far as I could validate, this should work.

You might need to differ more in the external entity loader function as you might have additional other entities you load that should not be handled equally. But I assume you can figure that out.

0
votes

Perhaps you could proxy the request? I.e. document('//mydomain/?doc=origin-doc')

This way you can modify the request (e.g. using cURL) to your needs. The doc parameter can be used to conditionally modify the request.