add rewrite functions
This commit is contained in:
@@ -59,7 +59,7 @@ foreach ( $_SERVER as $key => $value ) {
|
||||
if ( substr( $key, 0, 5 ) == 'HTTP_' ) {
|
||||
$headername = str_replace( '_', ' ', substr( $key, 5 ) );
|
||||
$headername = str_replace( ' ', '-', ucwords( strtolower( $headername ) ) );
|
||||
if ( !in_array( $headername, array( 'Host', 'X-Proxy-Url' ) ) ) {
|
||||
if ( !in_array( $headername, array( 'Accept-Encoding', 'Host', 'X-Proxy-Url' ) ) ) {
|
||||
$request_headers[] = "$headername: $value";
|
||||
}
|
||||
}
|
||||
@@ -179,15 +179,29 @@ curl_close( $ch );
|
||||
// split response to header and content
|
||||
list($response_headers, $response_content) = preg_split( '/(\r\n){2}/', $response, 2 );
|
||||
|
||||
// Rewrite links in HTML so subsequent browser requests also pass through this proxy.
|
||||
$is_html_response = preg_match( '/^Content-Type:\s*(?:text\/html|application\/xhtml\+xml)\b/im', $response_headers );
|
||||
$is_css_response = preg_match( '/^Content-Type:\s*text\/css\b/im', $response_headers );
|
||||
if ( $is_html_response ) {
|
||||
$response_content = csajax_rewrite_html_links( $response_content, $request_url );
|
||||
} elseif ( $is_css_response ) {
|
||||
$response_content = csajax_rewrite_css_urls( $response_content, $request_url );
|
||||
}
|
||||
$is_rewritten_response = $is_html_response || $is_css_response;
|
||||
|
||||
// (re-)send the headers
|
||||
$response_headers = preg_split( '/(\r\n){1}/', $response_headers );
|
||||
foreach ( $response_headers as $key => $response_header ) {
|
||||
// Rewrite the `Location` header, so clients will also use the proxy for redirects.
|
||||
if ( preg_match( '/^Location:/', $response_header ) ) {
|
||||
if ( preg_match( '/^Location:/i', $response_header ) ) {
|
||||
list($header, $value) = preg_split( '/: /', $response_header, 2 );
|
||||
$response_header = 'Location: ' . $_SERVER['REQUEST_URI'] . '?csurl=' . $value;
|
||||
$redirect_url = csajax_resolve_url( $request_url, $value );
|
||||
$response_header = 'Location: ' . csajax_proxy_url( $redirect_url );
|
||||
}
|
||||
if ( !preg_match( '/^(Transfer-Encoding):/', $response_header ) ) {
|
||||
if ( $is_html_response && preg_match( '/^Content-Security-Policy(?:-Report-Only)?:/i', $response_header ) ) {
|
||||
$response_header = csajax_rewrite_content_security_policy( $response_header );
|
||||
}
|
||||
if ( !preg_match( '/^(Transfer-Encoding):/i', $response_header ) && !( $is_rewritten_response && preg_match( '/^Content-Length:/i', $response_header ) ) ) {
|
||||
header( $response_header, false );
|
||||
}
|
||||
}
|
||||
@@ -201,3 +215,243 @@ function csajax_debug_message( $message )
|
||||
print $message . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
function csajax_rewrite_html_links( $html, $document_url )
|
||||
{
|
||||
if ( empty( $html ) ) {
|
||||
return $html;
|
||||
}
|
||||
if ( !class_exists( 'DOMDocument' ) ) {
|
||||
return csajax_rewrite_html_attributes( $html, $document_url );
|
||||
}
|
||||
|
||||
$previous_errors = libxml_use_internal_errors( true );
|
||||
$document = new DOMDocument();
|
||||
$loaded = $document->loadHTML( '<?xml encoding="UTF-8">' . $html );
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors( $previous_errors );
|
||||
|
||||
if ( !$loaded ) {
|
||||
return csajax_rewrite_html_attributes( $html, $document_url );
|
||||
}
|
||||
|
||||
foreach ( iterator_to_array( $document->childNodes ) as $child ) {
|
||||
if ( $child->nodeType == XML_PI_NODE || ( $child->nodeType == XML_COMMENT_NODE && strpos( $child->nodeValue, '?xml encoding=' ) === 0 ) ) {
|
||||
$document->removeChild( $child );
|
||||
}
|
||||
}
|
||||
|
||||
$base_url = $document_url;
|
||||
$base_elements = $document->getElementsByTagName( 'base' );
|
||||
if ( $base_elements->length > 0 && $base_elements->item( 0 )->hasAttribute( 'href' ) ) {
|
||||
$resolved_base_url = csajax_resolve_url( $document_url, $base_elements->item( 0 )->getAttribute( 'href' ) );
|
||||
if ( $resolved_base_url !== null ) {
|
||||
$base_url = $resolved_base_url;
|
||||
}
|
||||
$base_elements->item( 0 )->removeAttribute( 'href' );
|
||||
}
|
||||
|
||||
$link_attributes = array( 'href', 'src', 'action', 'formaction', 'poster' );
|
||||
foreach ( $document->getElementsByTagName( '*' ) as $element ) {
|
||||
foreach ( $link_attributes as $attribute ) {
|
||||
if ( !$element->hasAttribute( $attribute ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resolved_url = csajax_resolve_url( $base_url, $element->getAttribute( $attribute ) );
|
||||
if ( $resolved_url !== null ) {
|
||||
$element->setAttribute( $attribute, csajax_proxy_url( $resolved_url ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $element->hasAttribute( 'srcset' ) ) {
|
||||
$element->setAttribute( 'srcset', csajax_rewrite_srcset( $element->getAttribute( 'srcset' ), $base_url ) );
|
||||
}
|
||||
if ( $element->hasAttribute( 'style' ) ) {
|
||||
$element->setAttribute( 'style', csajax_rewrite_css_urls( $element->getAttribute( 'style' ), $base_url ) );
|
||||
}
|
||||
}
|
||||
foreach ( $document->getElementsByTagName( 'style' ) as $style_element ) {
|
||||
$style_element->nodeValue = csajax_rewrite_css_urls( $style_element->nodeValue, $base_url );
|
||||
}
|
||||
|
||||
return $document->saveHTML();
|
||||
}
|
||||
|
||||
function csajax_rewrite_html_attributes( $html, $document_url )
|
||||
{
|
||||
$html = preg_replace_callback(
|
||||
'/\b(href|src|action|formaction|poster|srcset|style)\s*=\s*(?:(["\'])(.*?)\2|([^\s>]+))/is',
|
||||
function ( $matches ) use ( $document_url ) {
|
||||
$attribute = strtolower( $matches[1] );
|
||||
$quote = isset( $matches[2] ) ? $matches[2] : '';
|
||||
$value = $quote !== '' ? $matches[3] : $matches[4];
|
||||
if ( $attribute == 'srcset' ) {
|
||||
$value = csajax_rewrite_srcset( $value, $document_url );
|
||||
} elseif ( $attribute == 'style' ) {
|
||||
$value = csajax_rewrite_css_urls( $value, $document_url );
|
||||
} else {
|
||||
$resolved_url = csajax_resolve_url( $document_url, $value );
|
||||
if ( $resolved_url !== null ) {
|
||||
$value = csajax_proxy_url( $resolved_url );
|
||||
}
|
||||
}
|
||||
|
||||
return $matches[1] . '=' . $quote . $value . $quote;
|
||||
},
|
||||
$html
|
||||
);
|
||||
|
||||
return preg_replace_callback(
|
||||
'/<style\b([^>]*)>(.*?)<\/style>/is',
|
||||
function ( $matches ) use ( $document_url ) {
|
||||
return '<style' . $matches[1] . '>' . csajax_rewrite_css_urls( $matches[2], $document_url ) . '</style>';
|
||||
},
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
function csajax_rewrite_css_urls( $css, $document_url )
|
||||
{
|
||||
$css = preg_replace_callback(
|
||||
'/url\(\s*(["\']?)(.*?)\1\s*\)/i',
|
||||
function ( $matches ) use ( $document_url ) {
|
||||
$resolved_url = csajax_resolve_url( $document_url, $matches[2] );
|
||||
if ( $resolved_url === null ) {
|
||||
return $matches[0];
|
||||
}
|
||||
return 'url(' . $matches[1] . csajax_proxy_url( $resolved_url ) . $matches[1] . ')';
|
||||
},
|
||||
$css
|
||||
);
|
||||
|
||||
return preg_replace_callback(
|
||||
'/@import\s+(["\'])(.*?)\1/i',
|
||||
function ( $matches ) use ( $document_url ) {
|
||||
$resolved_url = csajax_resolve_url( $document_url, $matches[2] );
|
||||
if ( $resolved_url === null ) {
|
||||
return $matches[0];
|
||||
}
|
||||
return '@import ' . $matches[1] . csajax_proxy_url( $resolved_url ) . $matches[1];
|
||||
},
|
||||
$css
|
||||
);
|
||||
}
|
||||
|
||||
function csajax_rewrite_srcset( $srcset, $base_url )
|
||||
{
|
||||
$candidates = array();
|
||||
foreach ( explode( ',', $srcset ) as $candidate ) {
|
||||
$candidate = trim( $candidate );
|
||||
if ( $candidate === '' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = preg_split( '/\s+/', $candidate, 2 );
|
||||
$resolved_url = csajax_resolve_url( $base_url, $parts[0] );
|
||||
if ( $resolved_url !== null ) {
|
||||
$parts[0] = csajax_proxy_url( $resolved_url );
|
||||
}
|
||||
$candidates[] = implode( ' ', $parts );
|
||||
}
|
||||
|
||||
return implode( ', ', $candidates );
|
||||
}
|
||||
|
||||
function csajax_rewrite_content_security_policy( $header )
|
||||
{
|
||||
list($header_name, $policy) = preg_split( '/:\s*/', $header, 2 );
|
||||
$proxy_directives = array(
|
||||
'default-src', 'script-src', 'script-src-elem', 'style-src', 'style-src-elem', 'img-src',
|
||||
'media-src', 'frame-src', 'child-src', 'manifest-src', 'form-action'
|
||||
);
|
||||
$directives = array();
|
||||
|
||||
foreach ( explode( ';', $policy ) as $directive ) {
|
||||
$directive = trim( $directive );
|
||||
if ( $directive === '' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = preg_split( '/\s+/', $directive );
|
||||
$name = strtolower( $parts[0] );
|
||||
if ( in_array( $name, $proxy_directives ) && !in_array( "'self'", $parts ) ) {
|
||||
$parts[] = "'self'";
|
||||
}
|
||||
$directives[] = implode( ' ', $parts );
|
||||
}
|
||||
|
||||
return $header_name . ': ' . implode( '; ', $directives );
|
||||
}
|
||||
|
||||
function csajax_proxy_url( $target_url )
|
||||
{
|
||||
return $_SERVER['SCRIPT_NAME'] . '?csurl=' . rawurlencode( $target_url );
|
||||
}
|
||||
|
||||
function csajax_resolve_url( $base_url, $url )
|
||||
{
|
||||
$url = trim( html_entity_decode( $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) );
|
||||
if ( $url === '' || $url[0] == '#' || preg_match( '/^(?:data|javascript|mailto|tel|blob|about):/i', $url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( preg_match( '!^https?://!i', $url ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$base = parse_url( $base_url );
|
||||
if ( $base === false || !isset( $base['scheme'], $base['host'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$origin = $base['scheme'] . '://' . $base['host'];
|
||||
if ( isset( $base['port'] ) ) {
|
||||
$origin .= ':' . $base['port'];
|
||||
}
|
||||
|
||||
if ( substr( $url, 0, 2 ) == '//' ) {
|
||||
return $base['scheme'] . ':' . $url;
|
||||
}
|
||||
|
||||
$relative = parse_url( $url );
|
||||
if ( $relative === false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( isset( $relative['path'] ) && substr( $relative['path'], 0, 1 ) == '/' ) {
|
||||
$path = $relative['path'];
|
||||
} elseif ( isset( $relative['path'] ) && $relative['path'] !== '' ) {
|
||||
$base_path = isset( $base['path'] ) ? $base['path'] : '/';
|
||||
$path = preg_replace( '!/[^/]*$!', '/', $base_path ) . $relative['path'];
|
||||
} else {
|
||||
$path = isset( $base['path'] ) ? $base['path'] : '/';
|
||||
}
|
||||
|
||||
$segments = array();
|
||||
foreach ( explode( '/', $path ) as $segment ) {
|
||||
if ( $segment === '' || $segment === '.' ) {
|
||||
continue;
|
||||
}
|
||||
if ( $segment === '..' ) {
|
||||
array_pop( $segments );
|
||||
} else {
|
||||
$segments[] = $segment;
|
||||
}
|
||||
}
|
||||
$resolved_url = $origin . '/' . implode( '/', $segments );
|
||||
if ( substr( $path, -1 ) == '/' && substr( $resolved_url, -1 ) != '/' ) {
|
||||
$resolved_url .= '/';
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'query', $relative ) ) {
|
||||
$resolved_url .= '?' . $relative['query'];
|
||||
} elseif ( !isset( $relative['path'] ) && isset( $base['query'] ) ) {
|
||||
$resolved_url .= '?' . $base['query'];
|
||||
}
|
||||
if ( isset( $relative['fragment'] ) ) {
|
||||
$resolved_url .= '#' . $relative['fragment'];
|
||||
}
|
||||
|
||||
return $resolved_url;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user