laipower/wp-content/plugins/easy-digital-downloads/includes/process-download.php

1178 lines
47 KiB
PHP

<?php
/**
* Process Download
*
* @package EDD
* @subpackage Functions
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Process Download
*
* Handles the file download process.
*
* @access private
* @since 1.0
* @return void
*/
function edd_process_download() {
if ( ! isset( $_GET['download_id'] ) && isset( $_GET['download'] ) ) {
$_GET['download_id'] = $_GET['download'];
}
$args = apply_filters( 'edd_process_download_args', array(
'download' => ( isset( $_GET['download_id'] ) ) ? (int) $_GET['download_id'] : '',
'email' => ( isset( $_GET['email'] ) ) ? rawurldecode( $_GET['email'] ) : '',
'expire' => ( isset( $_GET['expire'] ) ) ? rawurldecode( $_GET['expire'] ) : '',
'file_key' => ( isset( $_GET['file'] ) ) ? (int) $_GET['file'] : '',
'price_id' => ( isset( $_GET['price_id'] ) ) ? (int) $_GET['price_id'] : false,
'key' => ( isset( $_GET['download_key'] ) ) ? $_GET['download_key'] : '',
'eddfile' => ( isset( $_GET['eddfile'] ) ) ? $_GET['eddfile'] : '',
'ttl' => ( isset( $_GET['ttl'] ) ) ? $_GET['ttl'] : '',
'token' => ( isset( $_GET['token'] ) ) ? $_GET['token'] : ''
) );
if ( ! empty( $args['eddfile'] ) && ! empty( $args['ttl'] ) && ! empty( $args['token'] ) ) {
// Validate a signed URL that edd_process_signed_download_urlcontains a token
$args = edd_process_signed_download_url( $args );
// Backfill some legacy super globals for backwards compatibility
$_GET['download_id'] = $args['download'];
$_GET['email'] = $args['email'];
$_GET['expire'] = $args['expire'];
$_GET['download_key'] = $args['key'];
$_GET['price_id'] = $args['price_id'];
} elseif ( ! empty( $args['download'] ) && ! empty( $args['key'] ) && ! empty( $args['email'] ) && ! empty( $args['expire'] ) && isset( $args['file_key'] ) ) {
// Validate a legacy URL without a token
$args = edd_process_legacy_download_url( $args );
} else {
return;
}
$args['has_access'] = apply_filters( 'edd_file_download_has_access', $args['has_access'], $args['payment'], $args );
if ( $args['payment'] && $args['has_access'] ) {
// We've verified that the user should have access, now see if we need to require the user to be logged in.
$require_login = edd_get_option( 'require_login_to_download', false );
if ( $require_login && ! is_user_logged_in() ) {
$parts = parse_url( add_query_arg( array() ) );
wp_parse_str( $parts['query'], $file_download_args );
EDD()->session->set( 'edd_require_login_to_download_redirect', $file_download_args );
$login_page = wp_login_url( edd_get_file_download_login_redirect( $file_download_args ) );
// Redirect to the login page, and have it continue the download upon successful login.
wp_safe_redirect( $login_page );
edd_die();
}
do_action( 'edd_process_verified_download', $args['download'], $args['email'], $args['payment'], $args );
// Determine the download method set in settings
$method = edd_get_file_download_method();
// Payment has been verified, setup the download
$download_files = edd_get_download_files( $args['download'] );
$attachment_id = ! empty( $download_files[ $args['file_key'] ]['attachment_id'] ) ? absint( $download_files[ $args['file_key'] ]['attachment_id'] ) : false;
$thumbnail_size = ! empty( $download_files[ $args['file_key'] ]['thumbnail_size'] ) ? sanitize_text_field( $download_files[ $args['file_key'] ]['thumbnail_size'] ) : false;
$requested_file = isset( $download_files[ $args['file_key'] ]['file'] ) ? $download_files[ $args['file_key'] ]['file'] : '';
/*
* If we have an attachment ID stored, use get_attached_file() to retrieve absolute URL
* If this fails or returns a relative path, we fail back to our own absolute URL detection
*/
$from_attachment_id = false;
if ( edd_is_local_file( $requested_file ) && $attachment_id && 'attachment' == get_post_type( $attachment_id ) ) {
if ( 'pdf' === strtolower( edd_get_file_extension( $requested_file ) ) ) {
// Do not ever grab the thumbnail for PDFs. See https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5491
$thumbnail_size = false;
}
if ( 'redirect' === $method ) {
if ( $thumbnail_size ) {
$attached_file = wp_get_attachment_image_url( $attachment_id, $thumbnail_size, false );
} else {
$attached_file = wp_get_attachment_url( $attachment_id );
}
} else {
if ( $thumbnail_size ) {
$attachment_data = wp_get_attachment_image_src( $attachment_id, $thumbnail_size, false );
if ( false !== $attachment_data && ! empty( $attachment_data[0] ) && filter_var( $attachment_data[0], FILTER_VALIDATE_URL) !== false ) {
$attached_file = $attachment_data['0'];
$attached_file = str_replace( site_url(), '', $attached_file );
$attached_file = realpath( ABSPATH . $attached_file );
}
}
if ( empty( $attached_file ) ) {
$attached_file = get_attached_file( $attachment_id, false );
}
// Confirm the file exists
if ( ! file_exists( $attached_file ) ) {
$attached_file = false;
}
}
if ( $attached_file ) {
$from_attachment_id = true;
$requested_file = $attached_file;
}
}
// Allow the file to be altered before any headers are sent
$requested_file = apply_filters( 'edd_requested_file', $requested_file, $download_files, $args['file_key'], $args );
if ( 'x_sendfile' == $method && ( ! function_exists( 'apache_get_modules' ) || ! in_array( 'mod_xsendfile', apache_get_modules() ) ) ) {
// If X-Sendfile is selected but is not supported, fallback to Direct
$method = 'direct';
}
$file_details = parse_url( $requested_file );
$schemes = array( 'http', 'https' ); // Direct URL schemes
$supported_streams = stream_get_wrappers();
if ( strtoupper( substr( PHP_OS, 0, 3 ) ) !== 'WIN' && isset( $file_details['scheme'] ) && ! in_array( $file_details['scheme'], $supported_streams ) ) {
wp_die( __( 'Error 103: Error downloading file. Please contact support.', 'easy-digital-downloads' ), __( 'File download error', 'easy-digital-downloads' ), 501 );
}
if ( ( ! isset( $file_details['scheme'] ) || ! in_array( $file_details['scheme'], $schemes ) ) && isset( $file_details['path'] ) && file_exists( $requested_file ) ) {
/**
* Download method is set to Redirect in settings but an absolute path was provided
* We need to switch to a direct download in order for the file to download properly
*/
$method = 'direct';
}
/**
* Allow extensions to run actions prior to recording the file download log entry
*
* @since 2.6.14
*/
do_action( 'edd_process_download_pre_record_log', $requested_file, $args, $method );
edd_record_download_in_log( $args['download'], $args['file_key'], array(), edd_get_ip(), $args['payment'], $args['price_id'] );
$file_extension = edd_get_file_extension( $requested_file );
$ctype = edd_get_file_ctype( $file_extension );
edd_set_time_limit( false );
if ( version_compare( phpversion(), '5.4', '<' ) && function_exists( 'get_magic_quotes_runtime' ) && get_magic_quotes_runtime() ) {
set_magic_quotes_runtime( 0 );
}
// If we're using an attachment ID to get the file, even by path, we can ignore this check.
if ( false === $from_attachment_id ) {
$file_is_in_allowed_location = edd_local_file_location_is_allowed( $file_details, $schemes, $requested_file );
if ( false === $file_is_in_allowed_location ) {
wp_die( __( 'Sorry, this file could not be downloaded.', 'easy-digital-downloads' ), __( 'Error Downloading File', 'easy-digital-downloads' ), 403 );
}
}
@session_write_close();
if ( function_exists( 'apache_setenv' ) ) {
@apache_setenv( 'no-gzip', 1 );
}
@ini_set( 'zlib.output_compression', 'Off' );
do_action( 'edd_process_download_headers', $requested_file, $args['download'], $args['email'], $args['payment'] );
nocache_headers();
header( 'Robots: none' );
header( 'Content-Type: ' . $ctype );
header( 'Content-Description: File Transfer' );
header( 'Content-Disposition: attachment; filename="' . apply_filters( 'edd_requested_file_name', basename( $requested_file ), $args ) . '"' );
header( 'Content-Transfer-Encoding: binary' );
// If the file isn't locally hosted, process the redirect
if ( filter_var( $requested_file, FILTER_VALIDATE_URL ) && ! edd_is_local_file( $requested_file ) ) {
edd_deliver_download( $requested_file, true );
exit;
}
switch ( $method ) {
case 'redirect' :
// Redirect straight to the file
edd_deliver_download( $requested_file, true );
break;
case 'direct':
default:
$direct = false;
$file_path = $requested_file;
if ( ( ! isset( $file_details['scheme'] ) || ! in_array( $file_details['scheme'], $schemes ) ) && isset( $file_details['path'] ) && file_exists( $requested_file ) ) {
/** This is an absolute path */
$direct = true;
$file_path = $requested_file;
} else if ( defined( 'UPLOADS' ) && strpos( $requested_file, UPLOADS ) !== false ) {
/**
* This is a local file given by URL so we need to figure out the path
* UPLOADS is always relative to ABSPATH
* site_url() is the URL to where WordPress is installed
*/
$file_path = str_replace( site_url(), '', $requested_file );
$file_path = realpath( ABSPATH . $file_path );
$direct = true;
} else if ( strpos( $requested_file, content_url() ) !== false ) {
/** This is a local file given by URL so we need to figure out the path */
$file_path = str_replace( content_url(), WP_CONTENT_DIR, $requested_file );
$file_path = realpath( $file_path );
$direct = true;
} else if ( strpos( $requested_file, set_url_scheme( content_url(), 'https' ) ) !== false ) {
/** This is a local file given by an HTTPS URL so we need to figure out the path */
$file_path = str_replace( set_url_scheme( content_url(), 'https' ), WP_CONTENT_DIR, $requested_file );
$file_path = realpath( $file_path );
$direct = true;
}
// Set the file size header
header( "Content-Length: " . @filesize( $file_path ) );
// Now deliver the file based on the kind of software the server is running / has enabled
if ( stristr( getenv( 'SERVER_SOFTWARE' ), 'lighttpd' ) ) {
header( "X-LIGHTTPD-send-file: $file_path" );
} elseif ( $direct && ( stristr( getenv( 'SERVER_SOFTWARE' ), 'nginx' ) || stristr( getenv( 'SERVER_SOFTWARE' ), 'cherokee' ) ) ) {
$ignore_x_accel_redirect_header = apply_filters( 'edd_ignore_x_accel_redirect', false );
if ( ! $ignore_x_accel_redirect_header ) {
// We need a path relative to the domain
$file_path = str_ireplace( realpath( $_SERVER['DOCUMENT_ROOT'] ), '', $file_path );
header( "X-Accel-Redirect: /$file_path" );
}
}
if ( $direct ) {
edd_deliver_download( $file_path );
} else {
// The file supplied does not have a discoverable absolute path
edd_deliver_download( $requested_file, true );
}
break;
}
edd_die();
} else {
$error_message = '';
if ( ! $args['payment'] ) {
$error_message .= 'Error 101: ';
}
if ( ! $args['has_access'] ) {
$error_message .= 'Error 102: ';
}
$error_message .= __( 'You do not have permission to download this file', 'easy-digital-downloads' );
wp_die( apply_filters( 'edd_deny_download_message', $error_message, __( 'Purchase Verification Failed', 'easy-digital-downloads' ) ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
}
exit;
}
add_action( 'init', 'edd_process_download', 100 );
/**
* Deliver the download file
*
* If enabled, the file is symlinked to better support large file downloads
*
* @param string $file
* @param bool $redirect True if we should perform a header redirect instead of calling edd_readfile_chunked()
* @return void
*/
function edd_deliver_download( $file = '', $redirect = false ) {
/*
* If symlinks are enabled, a link to the file will be created
* This symlink is used to hide the true location of the file, even when the file URL is revealed
* The symlink is deleted after it is used
*/
if( edd_symlink_file_downloads() && edd_is_local_file( $file ) ) {
$file = edd_get_local_path_from_url( $file );
// Generate a symbolic link
$ext = edd_get_file_extension( $file );
$parts = explode( '.', $file );
$name = basename( $parts[0] );
$md5 = md5( $file );
$file_name = $name . '_' . substr( $md5, 0, -15 ) . '.' . $ext;
$path = edd_get_symlink_dir() . '/' . $file_name;
$url = edd_get_symlink_url() . '/' . $file_name;
// Set a transient to ensure this symlink is not deleted before it can be used
set_transient( md5( $file_name ), '1', 30 );
// Schedule deletion of the symlink
if ( ! wp_next_scheduled( 'edd_cleanup_file_symlinks' ) ) {
wp_schedule_single_event( current_time( 'timestamp' ) + 60, 'edd_cleanup_file_symlinks' );
}
// Make sure the symlink doesn't already exist before we create it
if( ! file_exists( $path ) ) {
$link = @symlink( realpath( $file ), $path );
} else {
$link = true;
}
if( $link ) {
// Send the browser to the file
header( 'Location: ' . $url );
} else {
edd_readfile_chunked( $file );
}
} elseif( $redirect ) {
header( 'Location: ' . $file );
} else {
// Read the file and deliver it in chunks
edd_readfile_chunked( $file );
}
}
/**
* Determine if the file being requested is hosted locally or not
*
* @since 2.5.10
* @since 3.1.0.3 - Updated to also check home_url (which is what previous versions of EDD were using).
*
* @param string $requested_file The file being requested
* @return bool If the file is hosted locally or not
*/
function edd_is_local_file( $requested_file ) {
// By default, we assume the file is not locally hosted.
$is_local_file = false;
// Grab the home_url and site_url values, so we can use them to test file location.
$site_url = preg_replace('#^https?://#', '', site_url() );
$home_url = preg_replace('#^https?://#', '', home_url() );
// Sanitize the requested file.
$requested_file = preg_replace('#^(https?|file)://#', '', $requested_file );
// First, check the Site URL.
$is_local_url_site_url = strpos( $requested_file, $site_url ) === 0;
$is_local_path_site_url = strpos( $requested_file, '/' ) === 0;
$is_local_file = ( $is_local_url_site_url || $is_local_path_site_url );
/**
* If the site_url and home_url are different, and we still didn't detect a local file, try
* again with the home_url value.
*/
if ( $home_url !== $site_url && false === $is_local_file ) {
$is_local_url_home_url = strpos( $requested_file, $home_url ) === 0;
$is_local_path_home_url = strpos( $requested_file, '/' ) === 0;
$is_local_file = ( $is_local_url_home_url || $is_local_path_home_url );
}
/**
* Allow filtering the edd_is_local_file detection.
*
* EDD tries to identify if a file is hosted locally, so that we can use the proper file delivery method.
* By default we check the site_url, and then the home_url (in the event that those settings are different).
*
* @since 3.1.0.3
*
* @param boolean $is_local_file If the file is hosted locally, on the server and within the site's contents.
* @param string $requested_file The file that is being requested to download.
*/
return apply_filters( 'edd_is_local_file', $is_local_file, $requested_file );
}
/**
* Given the URL to a file, determine it's local path
*
* Used during the symlink process to determine where to make the symlink point to
*
* @since 2.5.10
* @param string $url The URL of the file requested
* @return string If found to be locally hosted, the path to the file
*/
function edd_get_local_path_from_url( $url ) {
$file = $url;
$upload_dir = wp_upload_dir();
$edd_dir = edd_get_uploads_base_dir();
$upload_url = $upload_dir['baseurl'] . '/' . $edd_dir;
if( defined( 'UPLOADS' ) && strpos( $file, UPLOADS ) !== false ) {
/**
* This is a local file given by URL so we need to figure out the path
* UPLOADS is always relative to ABSPATH
* site_url() is the URL to where WordPress is installed
*/
$file = str_replace( site_url(), '', $file );
} else if( strpos( $file, $upload_url ) !== false ) {
/** This is a local file given by URL so we need to figure out the path */
$file = str_replace( $upload_url, edd_get_upload_dir(), $file );
} else if( strpos( $file, set_url_scheme( $upload_url, 'https' ) ) !== false ) {
/** This is a local file given by an HTTPS URL so we need to figure out the path */
$file = str_replace( set_url_scheme( $upload_url, 'https' ), edd_get_upload_dir(), $file );
} elseif( strpos( $file, content_url() ) !== false ) {
$file = str_replace( content_url(), WP_CONTENT_DIR, $file );
}
return $file;
}
/**
* Get the file content type
*
* @param string file extension
* @return string
*/
function edd_get_file_ctype( $extension ) {
switch( $extension ):
case 'ac' : $ctype = "application/pkix-attr-cert"; break;
case 'adp' : $ctype = "audio/adpcm"; break;
case 'ai' : $ctype = "application/postscript"; break;
case 'aif' : $ctype = "audio/x-aiff"; break;
case 'aifc' : $ctype = "audio/x-aiff"; break;
case 'aiff' : $ctype = "audio/x-aiff"; break;
case 'air' : $ctype = "application/vnd.adobe.air-application-installer-package+zip"; break;
case 'apk' : $ctype = "application/vnd.android.package-archive"; break;
case 'asc' : $ctype = "application/pgp-signature"; break;
case 'atom' : $ctype = "application/atom+xml"; break;
case 'atomcat' : $ctype = "application/atomcat+xml"; break;
case 'atomsvc' : $ctype = "application/atomsvc+xml"; break;
case 'au' : $ctype = "audio/basic"; break;
case 'aw' : $ctype = "application/applixware"; break;
case 'avi' : $ctype = "video/x-msvideo"; break;
case 'bcpio' : $ctype = "application/x-bcpio"; break;
case 'bin' : $ctype = "application/octet-stream"; break;
case 'bmp' : $ctype = "image/bmp"; break;
case 'boz' : $ctype = "application/x-bzip2"; break;
case 'bpk' : $ctype = "application/octet-stream"; break;
case 'bz' : $ctype = "application/x-bzip"; break;
case 'bz2' : $ctype = "application/x-bzip2"; break;
case 'ccxml' : $ctype = "application/ccxml+xml"; break;
case 'cdmia' : $ctype = "application/cdmi-capability"; break;
case 'cdmic' : $ctype = "application/cdmi-container"; break;
case 'cdmid' : $ctype = "application/cdmi-domain"; break;
case 'cdmio' : $ctype = "application/cdmi-object"; break;
case 'cdmiq' : $ctype = "application/cdmi-queue"; break;
case 'cdf' : $ctype = "application/x-netcdf"; break;
case 'cer' : $ctype = "application/pkix-cert"; break;
case 'cgm' : $ctype = "image/cgm"; break;
case 'class' : $ctype = "application/octet-stream"; break;
case 'cpio' : $ctype = "application/x-cpio"; break;
case 'cpt' : $ctype = "application/mac-compactpro"; break;
case 'crl' : $ctype = "application/pkix-crl"; break;
case 'csh' : $ctype = "application/x-csh"; break;
case 'css' : $ctype = "text/css"; break;
case 'cu' : $ctype = "application/cu-seeme"; break;
case 'davmount' : $ctype = "application/davmount+xml"; break;
case 'dbk' : $ctype = "application/docbook+xml"; break;
case 'dcr' : $ctype = "application/x-director"; break;
case 'deploy' : $ctype = "application/octet-stream"; break;
case 'dif' : $ctype = "video/x-dv"; break;
case 'dir' : $ctype = "application/x-director"; break;
case 'dist' : $ctype = "application/octet-stream"; break;
case 'distz' : $ctype = "application/octet-stream"; break;
case 'djv' : $ctype = "image/vnd.djvu"; break;
case 'djvu' : $ctype = "image/vnd.djvu"; break;
case 'dll' : $ctype = "application/octet-stream"; break;
case 'dmg' : $ctype = "application/octet-stream"; break;
case 'dms' : $ctype = "application/octet-stream"; break;
case 'doc' : $ctype = "application/msword"; break;
case 'docx' : $ctype = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; break;
case 'dotx' : $ctype = "application/vnd.openxmlformats-officedocument.wordprocessingml.template"; break;
case 'dssc' : $ctype = "application/dssc+der"; break;
case 'dtd' : $ctype = "application/xml-dtd"; break;
case 'dump' : $ctype = "application/octet-stream"; break;
case 'dv' : $ctype = "video/x-dv"; break;
case 'dvi' : $ctype = "application/x-dvi"; break;
case 'dxr' : $ctype = "application/x-director"; break;
case 'ecma' : $ctype = "application/ecmascript"; break;
case 'elc' : $ctype = "application/octet-stream"; break;
case 'emma' : $ctype = "application/emma+xml"; break;
case 'eps' : $ctype = "application/postscript"; break;
case 'epub' : $ctype = "application/epub+zip"; break;
case 'etx' : $ctype = "text/x-setext"; break;
case 'exe' : $ctype = "application/octet-stream"; break;
case 'exi' : $ctype = "application/exi"; break;
case 'ez' : $ctype = "application/andrew-inset"; break;
case 'f4v' : $ctype = "video/x-f4v"; break;
case 'fli' : $ctype = "video/x-fli"; break;
case 'flv' : $ctype = "video/x-flv"; break;
case 'gif' : $ctype = "image/gif"; break;
case 'gml' : $ctype = "application/srgs"; break;
case 'gpx' : $ctype = "application/gml+xml"; break;
case 'gram' : $ctype = "application/gpx+xml"; break;
case 'grxml' : $ctype = "application/srgs+xml"; break;
case 'gtar' : $ctype = "application/x-gtar"; break;
case 'gxf' : $ctype = "application/gxf"; break;
case 'hdf' : $ctype = "application/x-hdf"; break;
case 'hqx' : $ctype = "application/mac-binhex40"; break;
case 'htm' : $ctype = "text/html"; break;
case 'html' : $ctype = "text/html"; break;
case 'ice' : $ctype = "x-conference/x-cooltalk"; break;
case 'ico' : $ctype = "image/x-icon"; break;
case 'ics' : $ctype = "text/calendar"; break;
case 'ief' : $ctype = "image/ief"; break;
case 'ifb' : $ctype = "text/calendar"; break;
case 'iges' : $ctype = "model/iges"; break;
case 'igs' : $ctype = "model/iges"; break;
case 'ink' : $ctype = "application/inkml+xml"; break;
case 'inkml' : $ctype = "application/inkml+xml"; break;
case 'ipfix' : $ctype = "application/ipfix"; break;
case 'jar' : $ctype = "application/java-archive"; break;
case 'jnlp' : $ctype = "application/x-java-jnlp-file"; break;
case 'jp2' : $ctype = "image/jp2"; break;
case 'jpe' : $ctype = "image/jpeg"; break;
case 'jpeg' : $ctype = "image/jpeg"; break;
case 'jpg' : $ctype = "image/jpeg"; break;
case 'js' : $ctype = "application/javascript"; break;
case 'json' : $ctype = "application/json"; break;
case 'jsonml' : $ctype = "application/jsonml+json"; break;
case 'kar' : $ctype = "audio/midi"; break;
case 'latex' : $ctype = "application/x-latex"; break;
case 'lha' : $ctype = "application/octet-stream"; break;
case 'lrf' : $ctype = "application/octet-stream"; break;
case 'lzh' : $ctype = "application/octet-stream"; break;
case 'lostxml' : $ctype = "application/lost+xml"; break;
case 'm3u' : $ctype = "audio/x-mpegurl"; break;
case 'm4a' : $ctype = "audio/mp4a-latm"; break;
case 'm4b' : $ctype = "audio/mp4a-latm"; break;
case 'm4p' : $ctype = "audio/mp4a-latm"; break;
case 'm4u' : $ctype = "video/vnd.mpegurl"; break;
case 'm4v' : $ctype = "video/x-m4v"; break;
case 'm21' : $ctype = "application/mp21"; break;
case 'ma' : $ctype = "application/mathematica"; break;
case 'mac' : $ctype = "image/x-macpaint"; break;
case 'mads' : $ctype = "application/mads+xml"; break;
case 'man' : $ctype = "application/x-troff-man"; break;
case 'mar' : $ctype = "application/octet-stream"; break;
case 'mathml' : $ctype = "application/mathml+xml"; break;
case 'mbox' : $ctype = "application/mbox"; break;
case 'me' : $ctype = "application/x-troff-me"; break;
case 'mesh' : $ctype = "model/mesh"; break;
case 'metalink' : $ctype = "application/metalink+xml"; break;
case 'meta4' : $ctype = "application/metalink4+xml"; break;
case 'mets' : $ctype = "application/mets+xml"; break;
case 'mid' : $ctype = "audio/midi"; break;
case 'midi' : $ctype = "audio/midi"; break;
case 'mif' : $ctype = "application/vnd.mif"; break;
case 'mods' : $ctype = "application/mods+xml"; break;
case 'mov' : $ctype = "video/quicktime"; break;
case 'movie' : $ctype = "video/x-sgi-movie"; break;
case 'm1v' : $ctype = "video/mpeg"; break;
case 'm2v' : $ctype = "video/mpeg"; break;
case 'mp2' : $ctype = "audio/mpeg"; break;
case 'mp2a' : $ctype = "audio/mpeg"; break;
case 'mp21' : $ctype = "application/mp21"; break;
case 'mp3' : $ctype = "audio/mpeg"; break;
case 'mp3a' : $ctype = "audio/mpeg"; break;
case 'mp4' : $ctype = "video/mp4"; break;
case 'mp4s' : $ctype = "application/mp4"; break;
case 'mpe' : $ctype = "video/mpeg"; break;
case 'mpeg' : $ctype = "video/mpeg"; break;
case 'mpg' : $ctype = "video/mpeg"; break;
case 'mpg4' : $ctype = "video/mpeg"; break;
case 'mpga' : $ctype = "audio/mpeg"; break;
case 'mrc' : $ctype = "application/marc"; break;
case 'mrcx' : $ctype = "application/marcxml+xml"; break;
case 'ms' : $ctype = "application/x-troff-ms"; break;
case 'mscml' : $ctype = "application/mediaservercontrol+xml"; break;
case 'msh' : $ctype = "model/mesh"; break;
case 'mxf' : $ctype = "application/mxf"; break;
case 'mxu' : $ctype = "video/vnd.mpegurl"; break;
case 'nc' : $ctype = "application/x-netcdf"; break;
case 'oda' : $ctype = "application/oda"; break;
case 'oga' : $ctype = "application/ogg"; break;
case 'ogg' : $ctype = "application/ogg"; break;
case 'ogx' : $ctype = "application/ogg"; break;
case 'omdoc' : $ctype = "application/omdoc+xml"; break;
case 'onetoc' : $ctype = "application/onenote"; break;
case 'onetoc2' : $ctype = "application/onenote"; break;
case 'onetmp' : $ctype = "application/onenote"; break;
case 'onepkg' : $ctype = "application/onenote"; break;
case 'opf' : $ctype = "application/oebps-package+xml"; break;
case 'oxps' : $ctype = "application/oxps"; break;
case 'p7c' : $ctype = "application/pkcs7-mime"; break;
case 'p7m' : $ctype = "application/pkcs7-mime"; break;
case 'p7s' : $ctype = "application/pkcs7-signature"; break;
case 'p8' : $ctype = "application/pkcs8"; break;
case 'p10' : $ctype = "application/pkcs10"; break;
case 'pbm' : $ctype = "image/x-portable-bitmap"; break;
case 'pct' : $ctype = "image/pict"; break;
case 'pdb' : $ctype = "chemical/x-pdb"; break;
case 'pdf' : $ctype = "application/pdf"; break;
case 'pki' : $ctype = "application/pkixcmp"; break;
case 'pkipath' : $ctype = "application/pkix-pkipath"; break;
case 'pfr' : $ctype = "application/font-tdpfr"; break;
case 'pgm' : $ctype = "image/x-portable-graymap"; break;
case 'pgn' : $ctype = "application/x-chess-pgn"; break;
case 'pgp' : $ctype = "application/pgp-encrypted"; break;
case 'pic' : $ctype = "image/pict"; break;
case 'pict' : $ctype = "image/pict"; break;
case 'pkg' : $ctype = "application/octet-stream"; break;
case 'png' : $ctype = "image/png"; break;
case 'pnm' : $ctype = "image/x-portable-anymap"; break;
case 'pnt' : $ctype = "image/x-macpaint"; break;
case 'pntg' : $ctype = "image/x-macpaint"; break;
case 'pot' : $ctype = "application/vnd.ms-powerpoint"; break;
case 'potx' : $ctype = "application/vnd.openxmlformats-officedocument.presentationml.template"; break;
case 'ppm' : $ctype = "image/x-portable-pixmap"; break;
case 'pps' : $ctype = "application/vnd.ms-powerpoint"; break;
case 'ppsx' : $ctype = "application/vnd.openxmlformats-officedocument.presentationml.slideshow"; break;
case 'ppt' : $ctype = "application/vnd.ms-powerpoint"; break;
case 'pptx' : $ctype = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; break;
case 'prf' : $ctype = "application/pics-rules"; break;
case 'ps' : $ctype = "application/postscript"; break;
case 'psd' : $ctype = "image/photoshop"; break;
case 'qt' : $ctype = "video/quicktime"; break;
case 'qti' : $ctype = "image/x-quicktime"; break;
case 'qtif' : $ctype = "image/x-quicktime"; break;
case 'ra' : $ctype = "audio/x-pn-realaudio"; break;
case 'ram' : $ctype = "audio/x-pn-realaudio"; break;
case 'ras' : $ctype = "image/x-cmu-raster"; break;
case 'rdf' : $ctype = "application/rdf+xml"; break;
case 'rgb' : $ctype = "image/x-rgb"; break;
case 'rm' : $ctype = "application/vnd.rn-realmedia"; break;
case 'rmi' : $ctype = "audio/midi"; break;
case 'roff' : $ctype = "application/x-troff"; break;
case 'rss' : $ctype = "application/rss+xml"; break;
case 'rtf' : $ctype = "text/rtf"; break;
case 'rtx' : $ctype = "text/richtext"; break;
case 'sgm' : $ctype = "text/sgml"; break;
case 'sgml' : $ctype = "text/sgml"; break;
case 'sh' : $ctype = "application/x-sh"; break;
case 'shar' : $ctype = "application/x-shar"; break;
case 'sig' : $ctype = "application/pgp-signature"; break;
case 'silo' : $ctype = "model/mesh"; break;
case 'sit' : $ctype = "application/x-stuffit"; break;
case 'skd' : $ctype = "application/x-koan"; break;
case 'skm' : $ctype = "application/x-koan"; break;
case 'skp' : $ctype = "application/x-koan"; break;
case 'skt' : $ctype = "application/x-koan"; break;
case 'sldx' : $ctype = "application/vnd.openxmlformats-officedocument.presentationml.slide"; break;
case 'smi' : $ctype = "application/smil"; break;
case 'smil' : $ctype = "application/smil"; break;
case 'snd' : $ctype = "audio/basic"; break;
case 'so' : $ctype = "application/octet-stream"; break;
case 'spl' : $ctype = "application/x-futuresplash"; break;
case 'spx' : $ctype = "audio/ogg"; break;
case 'src' : $ctype = "application/x-wais-source"; break;
case 'stk' : $ctype = "application/hyperstudio"; break;
case 'sv4cpio' : $ctype = "application/x-sv4cpio"; break;
case 'sv4crc' : $ctype = "application/x-sv4crc"; break;
case 'svg' : $ctype = "image/svg+xml"; break;
case 'swf' : $ctype = "application/x-shockwave-flash"; break;
case 't' : $ctype = "application/x-troff"; break;
case 'tar' : $ctype = "application/x-tar"; break;
case 'tcl' : $ctype = "application/x-tcl"; break;
case 'tex' : $ctype = "application/x-tex"; break;
case 'texi' : $ctype = "application/x-texinfo"; break;
case 'texinfo' : $ctype = "application/x-texinfo"; break;
case 'tif' : $ctype = "image/tiff"; break;
case 'tiff' : $ctype = "image/tiff"; break;
case 'torrent' : $ctype = "application/x-bittorrent"; break;
case 'tr' : $ctype = "application/x-troff"; break;
case 'tsv' : $ctype = "text/tab-separated-values"; break;
case 'txt' : $ctype = "text/plain"; break;
case 'ustar' : $ctype = "application/x-ustar"; break;
case 'vcd' : $ctype = "application/x-cdlink"; break;
case 'vrml' : $ctype = "model/vrml"; break;
case 'vsd' : $ctype = "application/vnd.visio"; break;
case 'vss' : $ctype = "application/vnd.visio"; break;
case 'vst' : $ctype = "application/vnd.visio"; break;
case 'vsw' : $ctype = "application/vnd.visio"; break;
case 'vxml' : $ctype = "application/voicexml+xml"; break;
case 'wav' : $ctype = "audio/x-wav"; break;
case 'wbmp' : $ctype = "image/vnd.wap.wbmp"; break;
case 'wbmxl' : $ctype = "application/vnd.wap.wbxml"; break;
case 'webp' : $ctype = "image/webp"; break;
case 'wm' : $ctype = "video/x-ms-wm"; break;
case 'wml' : $ctype = "text/vnd.wap.wml"; break;
case 'wmlc' : $ctype = "application/vnd.wap.wmlc"; break;
case 'wmls' : $ctype = "text/vnd.wap.wmlscript"; break;
case 'wmlsc' : $ctype = "application/vnd.wap.wmlscriptc"; break;
case 'wmv' : $ctype = "video/x-ms-wmv"; break;
case 'wmx' : $ctype = "video/x-ms-wmx"; break;
case 'wrl' : $ctype = "model/vrml"; break;
case 'xbm' : $ctype = "image/x-xbitmap"; break;
case 'xdssc' : $ctype = "application/dssc+xml"; break;
case 'xer' : $ctype = "application/patch-ops-error+xml"; break;
case 'xht' : $ctype = "application/xhtml+xml"; break;
case 'xhtml' : $ctype = "application/xhtml+xml"; break;
case 'xla' : $ctype = "application/vnd.ms-excel"; break;
case 'xlam' : $ctype = "application/vnd.ms-excel.addin.macroEnabled.12"; break;
case 'xlc' : $ctype = "application/vnd.ms-excel"; break;
case 'xlm' : $ctype = "application/vnd.ms-excel"; break;
case 'xls' : $ctype = "application/vnd.ms-excel"; break;
case 'xlsx' : $ctype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; break;
case 'xlsb' : $ctype = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"; break;
case 'xlt' : $ctype = "application/vnd.ms-excel"; break;
case 'xltx' : $ctype = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"; break;
case 'xlw' : $ctype = "application/vnd.ms-excel"; break;
case 'xml' : $ctype = "application/xml"; break;
case 'xpm' : $ctype = "image/x-xpixmap"; break;
case 'xsl' : $ctype = "application/xml"; break;
case 'xslt' : $ctype = "application/xslt+xml"; break;
case 'xul' : $ctype = "application/vnd.mozilla.xul+xml"; break;
case 'xwd' : $ctype = "image/x-xwindowdump"; break;
case 'xyz' : $ctype = "chemical/x-xyz"; break;
case 'zip' : $ctype = "application/zip"; break;
default : $ctype = "application/force-download";
endswitch;
if( wp_is_mobile() ) {
$ctype = 'application/octet-stream';
}
return apply_filters( 'edd_file_ctype', $ctype );
}
/**
* Reads file in chunks so big downloads are possible without changing PHP.INI
* See http://codeigniter.com/wiki/Download_helper_for_large_files/
*
* @param string $file The file
* @param boolean $retbytes Return the bytes of file
*
* @return bool|string If string, $status || $cnt
*/
function edd_readfile_chunked( $file, $retbytes = true ) {
while ( ob_get_level() > 0 ) {
ob_end_clean();
}
ob_start();
// If output buffers exist, make sure they are closed. See https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6387
if ( ob_get_length() ) {
ob_clean();
}
$chunksize = 1024 * 1024;
$buffer = '';
$cnt = 0;
$handle = @fopen( $file, 'r' );
if ( $size = @filesize( $file ) ) {
header( "Content-Length: " . $size );
}
if ( false === $handle ) {
return false;
}
if ( isset( $_SERVER['HTTP_RANGE'] ) ) {
list( $size_unit, $range ) = explode( '=', $_SERVER['HTTP_RANGE'], 2 );
if ( 'bytes' === $size_unit ) {
if ( strpos( ',', $range ) ) {
list( $range ) = explode( ',', $range, 1 );
}
} else {
$range = '';
header( 'HTTP/1.1 416 Requested Range Not Satisfiable' );
exit;
}
} else {
$range = '';
}
if ( empty( $range ) ) {
$seek_start = null;
$seek_end = null;
} else {
list( $seek_start, $seek_end ) = explode( '-', $range, 2 );
}
$seek_end = ( empty( $seek_end ) ) ? ( $size - 1 ) : min( abs( intval( $seek_end ) ), ( $size - 1 ) );
$seek_start = ( empty( $seek_start ) || $seek_end < abs( intval( $seek_start ) ) ) ? 0 : max( abs( intval( $seek_start ) ), 0 );
// Only send partial content header if downloading a piece of the file (IE workaround)
if ( $seek_start > 0 || $seek_end < ( $size - 1 ) ) {
header( 'HTTP/1.1 206 Partial Content' );
header( 'Content-Range: bytes ' . $seek_start . '-' . $seek_end . '/' . $size );
header( 'Content-Length: ' . ( $seek_end - $seek_start + 1 ) );
} else {
header( "Content-Length: $size" );
}
header( 'Accept-Ranges: bytes' );
edd_set_time_limit( false );
fseek( $handle, $seek_start );
while ( ! @feof( $handle ) ) {
$buffer = @fread( $handle, $chunksize );
echo $buffer;
ob_flush();
if ( ob_get_length() ) {
ob_flush();
flush();
}
if ( $retbytes ) {
$cnt += strlen( $buffer );
}
if ( connection_status() != 0 ) {
@fclose( $handle );
exit;
}
}
ob_flush();
$status = @fclose( $handle );
if ( $retbytes && $status ) {
return $cnt;
}
return $status;
}
/**
* Used to process an old URL format for downloads
*
* @since 2.3
* @param array $args Arguments provided to download a file
* @return array Same arguments, with the status of verification added
*/
function edd_process_legacy_download_url( $args ) {
// Verify the payment
$args['payment'] = edd_verify_download_link( $args['download'], $args['key'], $args['email'], $args['expire'], $args['file_key'] );
// Defaulting this to true for now because the method below doesn't work well
$args['has_access'] = true;
$args['payment'] = $args['payment'];
$args['has_access'] = $args['has_access'];
return $args;
}
/**
* Used to process a signed URL for processing downloads
*
* @since 2.3
* @param array $args Arguments provided to download a file
* @return array Same arguments, with the status of verification added
*/
function edd_process_signed_download_url( $args ) {
$parts = parse_url( add_query_arg( array() ) );
wp_parse_str( $parts['query'], $query_args );
$url = add_query_arg( $query_args, site_url() );
$valid_token = edd_validate_url_token( $url );
// Bail if the token isn't valid.
// The request should pass through EDD, or custom handling can be enabled with the action.
if ( ! $valid_token ) {
$args['payment'] = false;
$args['has_access'] = false;
return $args;
}
$order_parts = explode( ':', rawurldecode( $_GET['eddfile'] ) );
$price_id = isset( $order_parts[3] ) ? (int) $order_parts[3] : null;
// Check to make sure not at download limit
if ( edd_is_file_at_download_limit( $order_parts[1], $order_parts[0], $order_parts[2], $price_id ) ) {
wp_die( apply_filters( 'edd_download_limit_reached_text', __( 'Sorry but you have hit your download limit for this file.', 'easy-digital-downloads' ) ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
}
$order = edd_get_order( $order_parts[0] );
$args['expire'] = $_GET['ttl'];
$args['download'] = $order_parts[1];
$args['payment'] = $order->id;
$args['file_key'] = $order_parts[2];
$args['price_id'] = $price_id;
$args['email'] = $order->email;
$args['key'] = $order->payment_key;
// Access is granted if there's at least one `complete` order item that matches the order + download + price ID.
$args['has_access'] = edd_order_grants_access_to_download_files( array(
'order_id' => $order->id,
'product_id' => $args['download'],
'price_id' => $args['price_id'],
) );
return $args;
}
/**
* Determines whether or not a given order grants access to download files associated with a given
* product ID and price ID combination. Returns true if there's at least one deliverable order item
* matching the requirements.
*
* @param array $args
*
* @since 3.0
* @return bool
*/
function edd_order_grants_access_to_download_files( $args ) {
$args = wp_parse_args( $args, array(
'order_id' => 0,
'product_id' => 0,
'price_id' => null,
) );
// Order and product IDs are required.
if ( empty( $args['order_id'] ) || empty( $args['product_id'] ) ) {
return false;
}
$args['status'] = edd_get_deliverable_order_item_statuses();
if ( is_null( $args['price_id'] ) ) {
unset( $args['price_id'] );
}
// Check if the download was purchased directly.
$order_items = edd_count_order_items( $args );
if ( $order_items > 0 ) {
return true;
}
$order_items = edd_get_order_items(
array(
'order_id' => $args['order_id'],
'status' => edd_get_deliverable_order_item_statuses(),
'fields' => 'product_id',
)
);
// Unlikely, but return false if there are no order items found at all.
if ( empty( $order_items ) ) {
return false;
}
// Include some fallback checks for incorrectly created download URLs and bundled items.
$product_to_check = isset( $args['price_id'] ) && is_numeric( $args['price_id'] ) ? "{$args['product_id']}_{$args['price_id']}" : $args['product_id'];
foreach ( $order_items as $product_id ) {
$download = edd_get_download( $product_id );
if ( ! $download instanceof EDD_Download ) {
continue;
}
// Check if the requested download is part of a bundle.
if ( 'bundle' === $download->type && in_array( $product_to_check, $download->get_bundled_downloads() ) ) {
return true;
}
// Check if the requested download is not variably priced but incorrectly included a price ID.
if ( empty( $args['price_id'] ) && $args['product_id'] == $product_id && ! $download->has_variable_prices() ) {
return true;
}
}
return false;
}
/**
* Determines if we should use symbolic links during the file download process
*
* @since 2.5
* @return bool
*/
function edd_symlink_file_downloads() {
$symlink = edd_get_option( 'symlink_file_downloads', false ) && function_exists( 'symlink' );
return (bool) apply_filters( 'edd_symlink_file_downloads', $symlink );
}
/**
* Given a local URL, make sure the requests matches the request scheme
*
* @since 2.5.10
* @param string $requested_file The Requested File
* @param array $download_files The download files
* @param string $file_key The file key
* @return string The file (if local) with the matched scheme
*/
function edd_set_requested_file_scheme( $requested_file, $download_files, $file_key ) {
// If it's a URL and it's local, let's make sure the scheme matches the requested scheme
if ( filter_var( $requested_file, FILTER_VALIDATE_URL ) && edd_is_local_file( $requested_file ) ) {
if ( false === strpos( $requested_file, 'https://' ) && is_ssl() ) {
$requested_file = str_replace( 'http://', 'https://', $requested_file );
} elseif ( ! is_ssl() && 0 === strpos( $requested_file, 'https://' ) ) {
$requested_file = str_replace( 'https://', 'http://', $requested_file );
}
}
return $requested_file;
}
add_filter( 'edd_requested_file', 'edd_set_requested_file_scheme', 10, 3 );
/**
* Perform a head request on file URLs before attempting to download to check if they are accessible.
*
* @since 2.6.14
* @param string $requested_file The Requested File
* @param array $args Arguments
* @param string $method The download mehtod being sed
* @return void
*/
function edd_check_file_url_head( $requested_file, $args, $method ) {
// If this is a file URL (not a path), perform a head request to determine if it's valid
if( filter_var( $requested_file, FILTER_VALIDATE_URL ) && ! edd_is_local_file( $requested_file ) ) {
$valid = true;
$request = wp_remote_head( $requested_file );
if( is_wp_error( $request ) ) {
$valid = false;
$message = $request;
$title = __( 'Invalid file', 'easy-digital-downloads' );
}
if( 404 === wp_remote_retrieve_response_code( $request ) ) {
$valid = false;
$message = __( 'The requested file could not be found. Error 404.', 'easy-digital-downloads' );
$title = __( 'File not found', 'easy-digital-downloads' );
}
if( ! $valid ) {
do_action( 'edd_check_file_url_head_invalid', $requested_file, $args, $method );
wp_die( $message, $title, array( 'response' => 403 ) );
}
}
}
/**
* Determines if a file should be allowed to be downloaded by making sure it's within the wp-content directory.
*
* @since 2.9.13
*
* @param $file_details
* @param $schemas
* @param $requested_file
*
* @return boolean
*/
function edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) {
$should_allow = true;
// If the file is an absolute path, make sure it's in the wp-content directory, to prevent store owners from accidentally allowing privileged files from being downloaded.
if ( ( ! isset( $file_details['scheme'] ) || ! in_array( $file_details['scheme'], $schemas ) ) && isset( $file_details['path'] ) ) {
/** This is an absolute path */
$requested_file = wp_normalize_path( realpath( $requested_file ) );
$normalized_abspath = wp_normalize_path( ABSPATH );
$normalized_content_dir = wp_normalize_path( WP_CONTENT_DIR );
if ( 0 !== strpos( $requested_file, $normalized_abspath ) || false === strpos( $requested_file, $normalized_content_dir ) ) {
// If the file is not within the WP_CONTENT_DIR, it should not be able to be downloaded.
$should_allow = false;
}
}
return apply_filters( 'edd_local_file_location_is_allowed', $should_allow, $file_details, $schemas, $requested_file );
}
/**
* Detect downloading a file immediately after a forced login.
*
* When the store requires being logged in to download files, this handles the file download after logging in.
* We need this otherwise the file is downloaded immediately after successfully logging in, but the page never changes.
*
* @since 3.1
*/
function edd_redirect_file_download_after_login() {
$token = isset( $_GET['_token'] ) ? sanitize_text_field( $_GET['_token'] ) : false;
// No nonce provided, redirect to the homepage.
if ( empty( $token ) ) {
wp_safe_redirect( home_url() );
}
$redirect_session_data = EDD()->session->get( 'edd_require_login_to_download_redirect' );
// Nonce verification failed, redirect to the homepage.
if ( ! \EDD\Utils\Tokenizer::is_token_valid( $token, $redirect_session_data ) ) {
wp_safe_redirect( home_url() );
}
EDD()->session->set( 'edd_require_login_to_download_redirect', '' );
// No file download session data, redirect to the homepage.
if ( empty( $redirect_session_data ) ) {
wp_safe_redirect( home_url() );
}
// Add some Javascript to download the file and then clear the query args from the page.
add_action( 'wp_footer', function() use ($redirect_session_data) {
printf('
<script type="text/javascript">
(function(){
var download_link = document.createElement("a");
download_link.href = "' . add_query_arg( $redirect_session_data, home_url( 'index.php' ) ) . '";
download_link.setAttribute("download", "");
document.body.appendChild(download_link);
download_link.click();
setTimeout(
() => {
window.location.replace( window.location.href.split(/[?#]/)[0] );
}, 250
);
})();
</script>
');
} );
}
add_action( 'edd_process_file_download_after_login', 'edd_redirect_file_download_after_login', 10, 2 );
/**
* Filter removed in EDD 2.7
*
* @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5450
*/
// add_action( 'edd_process_download_pre_record_log', 'edd_check_file_url_head', 10, 3 );