first import
This commit is contained in:
510
sites/all/modules/plupload/plupload.module
Normal file
510
sites/all/modules/plupload/plupload.module
Normal file
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implementation of plupload.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function plupload_menu() {
|
||||
$items['plupload-handle-uploads'] = array(
|
||||
'title' => 'Handles uploads',
|
||||
'page callback' => 'plupload_handle_uploads',
|
||||
'type' => MENU_CALLBACK,
|
||||
'access callback' => 'plupload_upload_access',
|
||||
'access arguments' => array('access content'),
|
||||
);
|
||||
$items['plupload-test'] = array(
|
||||
'title' => 'Test Plupload',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('plupload_test'),
|
||||
// @todo: change this to something appropriate, not sure what.
|
||||
'access arguments' => array('Administer site configuration'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the token for this request.
|
||||
*/
|
||||
function plupload_upload_access() {
|
||||
foreach (func_get_args() as $permission) {
|
||||
if (!user_access($permission)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return !empty($_REQUEST['plupload_token']) && drupal_valid_token($_REQUEST['plupload_token'], 'plupload-handle-uploads');
|
||||
}
|
||||
|
||||
/**
|
||||
* Form callback function for test page visible at URL "plupload-test".
|
||||
*/
|
||||
function plupload_test($form, &$form_state) {
|
||||
$form['pud'] = array(
|
||||
'#type' => 'plupload',
|
||||
'#title' => 'Plupload',
|
||||
// '#validators' => array(...);
|
||||
);
|
||||
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => 'Submit',
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for plupload_test form.
|
||||
*/
|
||||
function plupload_test_submit($form, &$form_state) {
|
||||
$saved_files = array();
|
||||
$scheme = variable_get('file_default_scheme', 'public') . '://';
|
||||
// We can't use file_save_upload() because of
|
||||
// http://www.jacobsingh.name/content/tight-coupling-no-not
|
||||
// file_uri_to_object();
|
||||
foreach ($form_state['values']['pud'] as $uploaded_file) {
|
||||
if ($uploaded_file['status'] == 'done') {
|
||||
$source = $uploaded_file['tmppath'];
|
||||
$destination = file_stream_wrapper_uri_normalize($scheme . $uploaded_file['name']);
|
||||
// Rename it to its original name, and put it in its final home.
|
||||
// Note - not using file_move here because if we call file_get_mime
|
||||
// (in file_uri_to_object) while it has a .tmp extension, it horks.
|
||||
$destination = file_unmanaged_move($source, $destination, FILE_EXISTS_RENAME);
|
||||
$file = plupload_file_uri_to_object($destination);
|
||||
file_save($file);
|
||||
$saved_files[] = $file;
|
||||
}
|
||||
else {
|
||||
// @todo: move this to element validate or something and clean up t().
|
||||
form_set_error('pud', "Upload of {$uploaded_file['name']} failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_element_info().
|
||||
*/
|
||||
function plupload_element_info() {
|
||||
$types = array();
|
||||
$module_path = drupal_get_path('module', 'plupload');
|
||||
$types['plupload'] = array(
|
||||
'#input' => TRUE,
|
||||
'#attributes' => array('class' => array('plupload-element')),
|
||||
// @todo
|
||||
// '#element_validate' => array('file_managed_file_validate'),
|
||||
'#theme_wrappers' => array('form_element'),
|
||||
'#theme' => 'container',
|
||||
'#value_callback' => 'plupload_element_value',
|
||||
'#attached' => array(
|
||||
'library' => array(array('plupload', 'plupload')),
|
||||
'js' => array($module_path . '/plupload.js'),
|
||||
'css' => array($module_path . '/plupload.css'),
|
||||
),
|
||||
'#process' => array('plupload_element_process'),
|
||||
'#element_validate' => array('plupload_element_validate'),
|
||||
'#pre_render' => array('plupload_element_pre_render'),
|
||||
);
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate callback for plupload form element.
|
||||
*/
|
||||
function plupload_element_value(&$element, $input = FALSE, $form_state = NULL) {
|
||||
$id = $element['#id'];
|
||||
$files = array();
|
||||
foreach ($form_state['input'] as $key => $value) {
|
||||
if (preg_match('/' . $id . '_([0-9]+)_(.*)/', $key, $reg)) {
|
||||
$i = $reg[1];
|
||||
$key = $reg[2];
|
||||
|
||||
// Only add the keys we expect.
|
||||
if (!in_array($key, array('tmpname', 'name', 'status'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Munge the submitted file names for security.
|
||||
//
|
||||
// Similar munging is normally done by file_save_upload(), but submit
|
||||
// handlers for forms containing plupload elements can't use
|
||||
// file_save_upload(), for reasons discussed in plupload_test_submit().
|
||||
// So we have to do this for them.
|
||||
//
|
||||
// Note that we do the munging here in the value callback function
|
||||
// (rather than during form validation or elsewhere) because we want to
|
||||
// actually modify the submitted values rather than reject them outright;
|
||||
// file names that require munging can be innocent and do not necessarily
|
||||
// indicate an attempted exploit. Actual validation of the file names is
|
||||
// performed later, in plupload_element_validate().
|
||||
if (in_array($key, array('tmpname', 'name'))) {
|
||||
// Find the whitelist of extensions to use when munging. If there are
|
||||
// none, we'll be adding default ones in plupload_element_process(), so
|
||||
// use those here.
|
||||
if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
|
||||
$extensions = $element['#upload_validators']['file_validate_extensions'][0];
|
||||
}
|
||||
else {
|
||||
$validators = _plupload_default_upload_validators();
|
||||
$extensions = $validators['file_validate_extensions'][0];
|
||||
}
|
||||
$value = file_munge_filename($value, $extensions, FALSE);
|
||||
// To prevent directory traversal issues, make sure the file name does
|
||||
// not contain any directory components in it. (This more properly
|
||||
// belongs in the form validation step, but it's simpler to do here so
|
||||
// that we don't have to deal with the temporary file names during form
|
||||
// validation and can just focus on the final file name.)
|
||||
//
|
||||
// This step is necessary since this module allows a large amount of
|
||||
// flexibility in where its files are placed (for example, they could
|
||||
// be intended for public://subdirectory rather than public://, and we
|
||||
// don't want an attacker to be able to get them back into the top
|
||||
// level of public:// in that case).
|
||||
$value = rtrim(basename($value), '.');
|
||||
|
||||
|
||||
// Based on the same feture from file_save_upload().
|
||||
if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $value) && (substr($value, -4) != '.txt')) {
|
||||
$value .= '.txt';
|
||||
|
||||
// The .txt extension may not be in the allowed list of extensions.
|
||||
// We have to add it here or else the file upload will fail.
|
||||
if (!empty($extensions)) {
|
||||
$element['#upload_validators']['file_validate_extensions'][0] .= ' txt';
|
||||
drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The temporary file name has to be processed further so it matches what
|
||||
// was used when the file was written; see plupload_handle_uploads().
|
||||
if ($key == 'tmpname') {
|
||||
$value = _plupload_fix_temporary_filename($value);
|
||||
// We also define an extra key 'tmppath' which is useful so that submit
|
||||
// handlers do not need to know which directory plupload stored the
|
||||
// temporary files in before trying to copy them.
|
||||
$files[$i]['tmppath'] = variable_get('plupload_temporary_uri', 'temporary://') . $value;
|
||||
}
|
||||
elseif ($key == 'name') {
|
||||
if (module_exists('transliteration')) {
|
||||
$value = transliteration_clean_filename($value);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the final value in the array we will return.
|
||||
$files[$i][$key] = $value;
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process callback (#process) for plupload form element.
|
||||
*/
|
||||
function plupload_element_process($element) {
|
||||
// Start session if not there yet. We need session if we want security
|
||||
// tokens to work properly.
|
||||
if (!drupal_session_started()) {
|
||||
drupal_session_start();
|
||||
}
|
||||
|
||||
if (!isset($element['#upload_validators'])) {
|
||||
$element['#upload_validators'] = array();
|
||||
}
|
||||
$element['#upload_validators'] += _plupload_default_upload_validators();
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element validation handler for a Plupload element.
|
||||
*/
|
||||
function plupload_element_validate($element, &$form_state) {
|
||||
foreach ($element['#value'] as $file_info) {
|
||||
// @todo Here we create a $file object for a file that doesn't exist yet,
|
||||
// because saving the file to its destination is done in a submit handler.
|
||||
// Need more investigation into what else is needed for it to be okay to
|
||||
// call file_validate(). For example, file_validate_size() will not have
|
||||
// access to a meaningful $file->filesize unless we set that here. For
|
||||
// now, we can at least rely on file_validate_extensions() running
|
||||
// successfully.
|
||||
$destination = variable_get('file_default_scheme', 'public') . '://' . $file_info['name'];
|
||||
$file = plupload_file_uri_to_object($destination);
|
||||
foreach (file_validate($file, $element['#upload_validators']) as $error_message) {
|
||||
$message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
|
||||
form_error($element, $message . ' ' . $error_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre render (#pre_render) callback to attach JS settings for the element.
|
||||
*/
|
||||
function plupload_element_pre_render($element) {
|
||||
$settings = isset($element['#plupload_settings']) ? $element['#plupload_settings'] : array();
|
||||
|
||||
// The Plupload library supports client-side validation of file extension, so
|
||||
// pass along the information for it to do that. However, as with all client-
|
||||
// side validation, this is a UI enhancement only, and not a replacement for
|
||||
// server-side validation.
|
||||
if (empty($settings['filters']) && isset($element['#upload_validators']['file_validate_extensions'][0])) {
|
||||
$settings['filters'][] = array(
|
||||
// @todo Some runtimes (e.g., flash) require a non-empty title for each
|
||||
// filter, but I don't know what this title is used for. Seems a shame
|
||||
// to hard-code it, but what's a good way to avoid that?
|
||||
'title' => t('Allowed files'),
|
||||
'extensions' => str_replace(' ', ',', $element['#upload_validators']['file_validate_extensions'][0]),
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($element['#description'])) {
|
||||
$element['#description'] = '';
|
||||
}
|
||||
$element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
|
||||
|
||||
$element['#attached']['js'][] = array(
|
||||
'type' => 'setting',
|
||||
'data' => array('plupload' => array($element['#id'] => $settings)),
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the plupload library.
|
||||
*/
|
||||
function _plupload_library_path() {
|
||||
return variable_get('plupload_library_path', module_exists('libraries') ? libraries_get_path('plupload') : 'sites/all/libraries/plupload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_library().
|
||||
*/
|
||||
function plupload_library() {
|
||||
$library_path = _plupload_library_path();
|
||||
$libraries['plupload'] = array(
|
||||
'title' => 'Plupload',
|
||||
'website' => 'http://www.plupload.com',
|
||||
'version' => '1.5.1.1',
|
||||
'js' => array(
|
||||
// @todo - only add gears JS if gears is an enabled runtime.
|
||||
// $library_path . '/js/gears_init.js' => array(),
|
||||
$library_path . '/js/jquery.plupload.queue/jquery.plupload.queue.js' => array(),
|
||||
$library_path . '/js/plupload.full.js' => array(),
|
||||
0 => array(
|
||||
'type' => 'setting',
|
||||
'data' => array(
|
||||
'plupload' => array(
|
||||
// Element-specific settings get keyed by the element id (see
|
||||
// plupload_element_pre_render()), so put default settings in
|
||||
// '_default' (Drupal element ids do not have underscores, because
|
||||
// they have hyphens instead).
|
||||
'_default' => array(
|
||||
// @todo Provide a settings page for configuring these.
|
||||
'runtimes' => 'html5,flash,html4',
|
||||
'url' => url('plupload-handle-uploads', array('query' => array('plupload_token' => drupal_get_token('plupload-handle-uploads')))),
|
||||
'max_file_size' => file_upload_max_size() . 'b',
|
||||
'chunk_size' => parse_size(ini_get('post_max_size')) . 'b',
|
||||
'unique_names' => TRUE,
|
||||
'flash_swf_url' => file_create_url($library_path . '/js/plupload.flash.swf'),
|
||||
'silverlight_xap_url' => file_create_url($library_path . '/js/plupload.silverlight.xap'),
|
||||
),
|
||||
// The plupload.js integration file in the module folder can do
|
||||
// additional browser checking to remove unsupported runtimes.
|
||||
// This is in addition to what is done by the Plupload library.
|
||||
'_requirements' => array(
|
||||
'html5' => array(
|
||||
// The Plupload library recognizes Firefox 3.5 as supporting
|
||||
// HTML 5, but Firefox 3.5 does not support the HTML 5
|
||||
// "multiple" attribute for file input controls. This makes the
|
||||
// html5 runtime much less appealing, so we treat all Firefox
|
||||
// versions less than 3.6 as ineligible for the html5 runtime.
|
||||
'mozilla' => '1.9.2',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (module_exists('locale')) {
|
||||
$module_path = drupal_get_path('module', 'plupload');
|
||||
$libraries['plupload']['js'][$module_path . '/js/i18n.js'] = array('scope' => 'footer');
|
||||
}
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that handles and saves uploaded files.
|
||||
*
|
||||
* This will respond to the URL on which plupoad library will upload files.
|
||||
*/
|
||||
function plupload_handle_uploads() {
|
||||
// @todo: Implement file_validate_size();
|
||||
// Added a variable for this because in HA environments, temporary may need
|
||||
// to be a shared location for this to work.
|
||||
$temp_directory = variable_get('plupload_temporary_uri', 'temporary://');
|
||||
$writable = file_prepare_directory($temp_directory, FILE_CREATE_DIRECTORY);
|
||||
if (!$writable) {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 104, "message": "Failed to open temporary directory."}, "id" : "id"}');
|
||||
}
|
||||
// Try to make sure this is private via htaccess.
|
||||
file_create_htaccess($temp_directory, TRUE);
|
||||
|
||||
// Chunk it?
|
||||
$chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0;
|
||||
|
||||
// Get and clean the filename.
|
||||
$file_name = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
|
||||
$file_name = _plupload_fix_temporary_filename($file_name);
|
||||
|
||||
// Check the file name for security reasons; it must contain letters, numbers
|
||||
// and underscores followed by a (single) ".tmp" extension. Since this check
|
||||
// is more stringent than the one performed in plupload_element_value(), we
|
||||
// do not need to run the checks performed in that function here. This is
|
||||
// fortunate, because it would be difficult for us to get the correct list of
|
||||
// allowed extensions to pass in to file_munge_filename() from this point in
|
||||
// the code (outside the form API).
|
||||
if (empty($file_name) || !preg_match('/^\w+\.tmp$/', $file_name)) {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 105, "message": "Invalid temporary file name."}, "id" : "id"}');
|
||||
}
|
||||
|
||||
// Look for the content type header.
|
||||
if (isset($_SERVER["HTTP_CONTENT_TYPE"])) {
|
||||
$content_type = $_SERVER["HTTP_CONTENT_TYPE"];
|
||||
}
|
||||
if (isset($_SERVER["CONTENT_TYPE"])) {
|
||||
$content_type = $_SERVER["CONTENT_TYPE"];
|
||||
}
|
||||
|
||||
// Is this a multipart upload?.
|
||||
if (strpos($content_type, "multipart") !== FALSE) {
|
||||
if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
|
||||
// Open temp file.
|
||||
$out = fopen($temp_directory . $file_name, $chunk == 0 ? "wb" : "ab");
|
||||
if ($out) {
|
||||
// Read binary input stream and append it to temp file.
|
||||
$in = fopen($_FILES['file']['tmp_name'], "rb");
|
||||
|
||||
if ($in) {
|
||||
while ($buff = fread($in, 4096)) {
|
||||
fwrite($out, $buff);
|
||||
}
|
||||
fclose($in);
|
||||
}
|
||||
else {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
drupal_unlink($_FILES['file']['tmp_name']);
|
||||
}
|
||||
else {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
|
||||
}
|
||||
}
|
||||
else {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Open temp file.
|
||||
$out = fopen($temp_directory . $file_name, $chunk == 0 ? "wb" : "ab");
|
||||
if ($out) {
|
||||
// Read binary input stream and append it to temp file.
|
||||
$in = fopen("php://input", "rb");
|
||||
|
||||
if ($in) {
|
||||
while ($buff = fread($in, 4096)) {
|
||||
fwrite($out, $buff);
|
||||
}
|
||||
fclose($in);
|
||||
}
|
||||
else {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
}
|
||||
else {
|
||||
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
|
||||
}
|
||||
}
|
||||
|
||||
// Return JSON-RPC response.
|
||||
die('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file object which can be passed to file_save().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI, path, or filename.
|
||||
*
|
||||
* @return boolean
|
||||
* A file object, or FALSE on error.
|
||||
*
|
||||
* @todo Replace with calls to this function with file_uri_to_object() when
|
||||
* http://drupal.org/node/685818 is fixed in core.
|
||||
*/
|
||||
function plupload_file_uri_to_object($uri) {
|
||||
global $user;
|
||||
$uri = file_stream_wrapper_uri_normalize($uri);
|
||||
$wrapper = file_stream_wrapper_get_instance_by_uri($uri);
|
||||
$file = new StdClass();
|
||||
$file->uid = $user->uid;
|
||||
$file->filename = basename($uri);
|
||||
$file->uri = $uri;
|
||||
$file->filemime = file_get_mimetype($uri);
|
||||
// This is gagged because some uris will not support it.
|
||||
$file->filesize = @filesize($uri);
|
||||
$file->timestamp = REQUEST_TIME;
|
||||
$file->status = FILE_STATUS_PERMANENT;
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the temporary filename provided by the plupload library.
|
||||
*
|
||||
* Newer versions of the plupload JavaScript library upload temporary files
|
||||
* with names that contain the intended final prefix of the uploaded file
|
||||
* (e.g., ".jpg" or ".png"). Older versions of the plupload library always use
|
||||
* ".tmp" as the temporary file extension.
|
||||
*
|
||||
* We prefer the latter behavior, since although the plupload temporary
|
||||
* directory where these files live is always expected to be private (and we
|
||||
* protect it via .htaccess; see plupload_handle_uploads()), in case it ever
|
||||
* isn't we don't want people to be able to upload files with an arbitrary
|
||||
* extension into that directory.
|
||||
*
|
||||
* This function therefore fixes the plupload temporary filenames so that they
|
||||
* will always use a ".tmp" extension.
|
||||
*
|
||||
* @param string $filename
|
||||
* The original temporary filename provided by the plupload library.
|
||||
*
|
||||
* @return string
|
||||
* The corrected temporary filename, with a ".tmp" extension replacing the
|
||||
* original one.
|
||||
*/
|
||||
function _plupload_fix_temporary_filename($filename) {
|
||||
$pos = strpos($filename, '.');
|
||||
if ($pos !== FALSE) {
|
||||
$filename = substr_replace($filename, '.tmp', $pos);
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to add defaults to $element['#upload_validators'].
|
||||
*/
|
||||
function _plupload_default_upload_validators() {
|
||||
return array(
|
||||
// See file_save_upload() for details.
|
||||
'file_validate_extensions' => array('jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user