From 78b052a6af5fc7c87c807821bfc9267f8007ed7b Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Tue, 17 Aug 2004 21:35:26 +0000
Subject: [PATCH] - The upload (filehandler) module has landed!

---
 database/database.mysql      |  15 ++
 database/database.pgsql      |  15 ++
 database/updates.inc         |  18 +-
 includes/common.inc          |  28 +++
 includes/file.inc            | 155 +++++++++++------
 misc/drupal.css              |   4 +
 modules/node.module          |   3 +
 modules/node/node.module     |   3 +
 modules/system.module        |  12 +-
 modules/system/system.module |  12 +-
 modules/upload.module        | 326 +++++++++++++++++++++++++++++++++++
 modules/upload/upload.module | 326 +++++++++++++++++++++++++++++++++++
 modules/user.module          |  47 ++---
 modules/user/user.module     |  47 ++---
 14 files changed, 880 insertions(+), 131 deletions(-)
 create mode 100644 modules/upload.module
 create mode 100644 modules/upload/upload.module

diff --git a/database/database.mysql b/database/database.mysql
index 75f1b51c5a95..f5c3cc967396 100644
--- a/database/database.mysql
+++ b/database/database.mysql
@@ -207,6 +207,21 @@ CREATE TABLE directory (
   PRIMARY KEY  (link)
 ) TYPE=MyISAM;
 
+--
+-- Table structure for table 'files'
+--
+
+CREATE TABLE files (
+  fid int(10) unsigned NOT NULL default '0',
+  nid int(10) unsigned NOT NULL default '0',
+  filename varchar(255) NOT NULL default '',
+  filepath varchar(255) NOT NULL default '',
+  filemime varchar(255) NOT NULL default '',
+  filesize int(10) unsigned NOT NULL default '0',
+  list tinyint(1) unsigned NOT NULL default '0',
+  PRIMARY KEY  (fid)
+) TYPE=MyISAM;
+
 --
 -- Table structure for table 'filter_formats'
 --
diff --git a/database/database.pgsql b/database/database.pgsql
index 8092f0d87be1..e1103013fe98 100644
--- a/database/database.pgsql
+++ b/database/database.pgsql
@@ -208,6 +208,21 @@ CREATE TABLE directory (
   PRIMARY KEY  (link)
 );
 
+--
+-- Table structure for table 'files'
+--
+
+CREATE TABLE files (
+  fid int(10) unsigned NOT NULL default '0',
+  nid int(10) unsigned NOT NULL default '0',
+  filename varchar(255) NOT NULL default '',
+  filepath varchar(255) NOT NULL default '',
+  filemime varchar(255) NOT NULL default '',
+  filesize int(10) unsigned NOT NULL default '0',
+  list tinyint(1) unsigned NOT NULL default '0',
+  PRIMARY KEY  (fid)
+);
+
 --
 -- Table structure for table 'filters'
 --
diff --git a/database/updates.inc b/database/updates.inc
index 799e57f4df48..47344f261c2a 100644
--- a/database/updates.inc
+++ b/database/updates.inc
@@ -73,7 +73,8 @@
   "2004-08-09" => "update_99",
   "2004-08-10" => "update_100",
   "2004-08-11" => "update_101",
-  "2004-08-12" => "update_102"
+  "2004-08-12" => "update_102",
+  "2004-08-17" => "update_103"
 );
 
 function update_32() {
@@ -1506,6 +1507,21 @@ function update_102() {
   return array(update_sql("INSERT INTO {system} (filename, name, type, description, status, throttle, bootstrap) VALUES ('modules/legacy.module', 'legacy', 'module', '', 1, 0, 0)"));
 }
 
+function update_103() {
+  $ret = array();
+  $ret[] = update_sql("CREATE TABLE files (
+    fid int(10) unsigned NOT NULL default '0',
+    nid int(10) unsigned NOT NULL default '0',
+    filename varchar(255) NOT NULL default '',
+    filepath varchar(255) NOT NULL default '',
+    filemime varchar(255) NOT NULL default '',
+    filesize int(10) unsigned NOT NULL default '0',
+    list tinyint(1) unsigned NOT NULL default '0',
+    PRIMARY KEY  (fid)
+  )");
+  return $ret;
+}
+
 function update_sql($sql) {
   $edit = $_POST["edit"];
   $result = db_query($sql);
diff --git a/includes/common.inc b/includes/common.inc
index a15c0942eb86..cda0838678c8 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -1794,6 +1794,34 @@ function truncate_utf8($string, $len) {
   return substr($string, 0, $len);
 }
 
+/**
+ * Encodes MIME/HTTP header values that contain non US-ASCII
+ * characters.
+ *
+ * Example: mime_header_encode('tést.txt') returns '=?UTF-8?B?dMOpc3QudHh0?='
+ *
+ * For more info: http://www.rfc-editor.org/rfc/rfc2047.txt
+ *
+ */
+function mime_header_encode($string, $charset = 'UTF-8') {
+  // Notes:
+  //   - Only encode strings that contain non-ASCII characters.
+  //   - The chunks come in groupings of 4 bytes when using base64
+  //     encoded.
+  //   - trim() is used to ensure that no extra spacing is added by
+  //     chunk_split() or preg_replace().
+  //   - Using \n as the chunk separator may cause problems on some
+  //     systems and may have to be changed to \r\n or \r.
+
+  if (!preg_match('/^[\x20-\x7E]*$/', $string)) {
+    $chunk_size = 75 - 7 - strlen($charset);
+    $chunk_size -= $chunk_size % 4;
+    $string = trim(chunk_split(base64_encode($string), $chunk_size, "\n"));
+    $string = trim(preg_replace('/^(.*)$/m', " =?$charset?B?\\1?=", $string));
+  }
+  return $string;
+}
+
 /**
  * Wrapper around PHP's eval(). Uses output buffering to capture both returned
  * and printed text. Unlike eval(), we require code to be surrounded by <?php ?>
diff --git a/includes/file.inc b/includes/file.inc
index 84b3c6ab9889..abbd16c908c3 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -9,7 +9,10 @@
 
 define('FILE_DOWNLOADS_PUBLIC', 1);
 define('FILE_DOWNLOADS_PRIVATE', 2);
-define('FILE_SEPARATOR', PHP_OS == 'WINNT' ? '\\' : '/');
+#define('FILE_SEPARATOR', PHP_OS == 'WINNT' ? '\\' : '/');
+define('FILE_SEPARATOR', '/');
+define('FILE_CREATE_DIRECTORY', 1);
+define('FILE_MODIFY_PERMISSIONS', 2);
 
 /**
  * Create the download path to a file.
@@ -20,8 +23,7 @@ function file_create_url($path) {
   }
   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
     case FILE_DOWNLOADS_PUBLIC:
-      global $base_url;
-      return $base_url .'/'. variable_get('file_directory_path', 'files') .'/'. str_replace('\\', '/', $path);
+      return $GLOBALS['base_url'] .'/'. variable_get('file_directory_path', 'files') .'/'. str_replace('\\', '/', $path);
     case FILE_DOWNLOADS_PRIVATE:
       return url('system/files', 'file='. $path);
   }
@@ -49,11 +51,38 @@ function file_create_path($dest = 0) {
  * Check that directory exists and is writable.
  *
  * @param $directory Path to extract and verify directory for.
+ * @param $mode Try to create the directory if it does not exist.
+ * @param $form_item Optional name for a field item to attach potential errors to.
  * @return False when directory not found, or true when directory exists.
  */
-function file_check_directory(&$directory) {
+function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
   $directory = rtrim($directory, '/\\');
-  return is_dir($directory) && is_writable($directory);
+
+  // Check if directory exists.
+  if (!is_dir($directory)) {
+    if (($mode & FILE_CREATE_DIRECTORY) && mkdir($directory, 0760)) {
+      drupal_set_message(t("Created directory '%name'.", array('%name' => $directory)));
+    }
+    else {
+      if ($form_item) {
+        form_set_error($form_item, t("The directory '%name' does not exist.", array('%name' => $directory)));
+      }
+      return false;
+    }
+  }
+
+  // Check to see if the directory is writable.
+  if (!is_writable($directory)) {
+    if (($mode & FILE_MODIFY_PERMISSIONS) && chmod($directory, 0760)) {
+      drupal_set_message(t("Modified permissions on directory '%name'.", array('%name' => $directory)));
+    }
+    else {
+      form_set_error($form_item, t("The directory '%name' is not writable.", array('%name' => $directory)));
+      return false;
+    }
+  }
+
+  return true;
 }
 
 /**
@@ -84,19 +113,25 @@ function file_check_path(&$path) {
  */
 function file_check_upload($source) {
   if (is_object($source)) {
-    if (is_file($source->path)) {
+    if (is_file($source->filepath)) {
       return $source;
     }
   }
   elseif ($_FILES["edit"]["name"][$source] && is_uploaded_file($_FILES["edit"]["tmp_name"][$source])) {
-    $file->name = trim(basename($_FILES["edit"]["name"][$source]), '.');
-    $file->type = $_FILES["edit"]["type"][$source];
-    $file->path = $_FILES["edit"]["tmp_name"][$source];
+    $file->filename = trim(basename($_FILES["edit"]["name"][$source]), '.');
+    $file->filemime = $_FILES["edit"]["type"][$source];
+    $file->filepath = $_FILES["edit"]["tmp_name"][$source];
     $file->error = $_FILES["edit"]["error"][$source];
-    $file->size = $_FILES["edit"]["size"][$source];
-    $file->source = $_FILES["edit"]["source"][$source];
+    $file->filesize = $_FILES["edit"]["size"][$source];
+    $file->source = $source;
     return $file;
   }
+  else {
+    // In case of previews return previous file object.
+    if (file_exists($_SESSION['file_uploads'][$source]->filepath)) {
+      return $_SESSION['file_uploads'][$source];
+    }
+  }
 }
 
 /**
@@ -154,9 +189,9 @@ function file_copy(&$source, $dest = 0, $replace = 0) {
   // Process a file upload object.
   if (is_object($source)) {
     $file = $source;
-    $source = $file->path;
+    $source = $file->filepath;
     if (!$basename) {
-      $basename = $file->name;
+      $basename = $file->filename;
     }
   }
 
@@ -170,45 +205,53 @@ function file_copy(&$source, $dest = 0, $replace = 0) {
   $basename = $basename ? $basename : basename($source);
   $dest = $directory . FILE_SEPARATOR . $basename;
 
-  if (file_exists($dest) && !$replace) {
-    // Destination file already exists and we can't replace is so we try and
-    // and find a new filename.
-    $pos = strrpos($basename, '.');
-    $name = substr($basename, 0, $pos);
-    if ($pos = strrpos($basename, '.')) {
-      $name = substr($basename, 0, $pos);
-      $ext = substr($basename, $pos);
-    }
-    else {
-      $name = $basename;
-    }
+  // Make sure source and destination filenames are not the same, makes no sense
+  // to copy it if they are. In fact copying the file will most likely result in
+  // a 0 byte file. Which is bad. Real bad.
+  if ($source != realpath($dest)) {
+    if (file_exists($dest) && !$replace) {
+      // Destination file already exists and we can't replace is so we try and
+      // and find a new filename.
+      if ($pos = strrpos($basename, '.')) {
+        $name = substr($basename, 0, $pos);
+        $ext = substr($basename, $pos);
+      }
+      else {
+        $name = $basename;
+      }
 
-    $counter = 0;
-    do {
-      $dest = $directory . FILE_SEPARATOR . $name .'_'. $counter++ . $ext;
-    } while (file_exists($dest));
-  }
+      $counter = 0;
+      do {
+        $dest = $directory . FILE_SEPARATOR . $name .'_'. $counter++ . $ext;
+      } while (file_exists($dest));
+    }
 
-  if (!copy($source, $dest)) {
-    drupal_set_message(t('File copy failed.'), 'error');
-    return 0;
+    if (!copy($source, $dest)) {
+      drupal_set_message(t('File copy failed.'), 'error');
+      return 0;
+    }
   }
 
   if (is_object($file)) {
-    $file->name = $basename;
-    $file->path = $dest;
+    $file->filename = $basename;
+    $file->filepath = $dest;
     $source = $file;
   }
   else {
     $source = $dest;
   }
+
   return 1; // Everything went ok.
 }
 
 function file_move(&$source, $dest = 0, $replace = 0) {
-  $path = is_object($source) ? $source->path : $source;
+
+  $path_original = is_object($source) ? $source->filepath : $source;
+
   if (file_copy($source, $dest, $replace)) {
-    if (unlink($path)) {
+    $path_current = is_object($source) ? $source->filepath : $source;
+
+    if ($path_original == $path_current || file_delete($path_original)) {
       return 1;
     }
     drupal_set_message(t('Removing original file failed.'), 'error');
@@ -216,9 +259,31 @@ function file_move(&$source, $dest = 0, $replace = 0) {
   return 0;
 }
 
+function file_create_filename($basename, $directory) {
+  $dest = $directory . FILE_SEPARATOR . $basename;
+
+  if (file_exists($dest)) {
+    // Destination file already exists, generate an alternative.
+    if ($pos = strrpos($basename, '.')) {
+      $name = substr($basename, 0, $pos);
+      $ext = substr($basename, $pos);
+    }
+    else {
+      $name = $basename;
+    }
+
+    $counter = 0;
+    do {
+      $dest = $directory . FILE_SEPARATOR . $name .'_'. $counter++ . $ext;
+    } while (file_exists($dest));
+  }
+
+  return $dest;
+}
+
 function file_delete($path) {
   if (is_file($path)) {
-    unlink($path);
+    return unlink($path);
   }
 }
 
@@ -241,7 +306,7 @@ function file_save_upload($source, $dest = 0, $replace = 0) {
     if (!$dest) {
       $dest = variable_get('file_directory_temp', (PHP_OS == 'WINNT' ? 'c:\\windows\\temp' : '/tmp'));
       $temporary = 1;
-      if (is_file($_SESSION['file_uploads'][$source]->path)) {
+      if (is_file($file->filepath)) {
         // If this file was uploaded by this user before replace the temporary copy.
         $replace = 1;
       }
@@ -273,18 +338,12 @@ function file_save_upload($source, $dest = 0, $replace = 0) {
     unset($_SESSION['file_uploads'][is_object($source) ? $source->source : $source]);
     if (file_move($file, $dest, $replace)) {
       if ($temporary) {
-        $_SESSION['file_uploads'][$source] = $file;
+        $_SESSION['file_uploads'][is_object($source) ? $source->source : $source] = $file;
       }
       return $file;
     }
     return 0;
   }
-  else {
-    // In case of previews return previous file object.
-    if (file_exists($_SESSION['file_uploads'][$source]->path)) {
-      return $_SESSION['file_uploads'][$source];
-    }
-  }
   return 0;
 }
 
@@ -305,7 +364,7 @@ function file_save_data($data, $dest, $replace = 0) {
 
   $temp = variable_get('file_directory_temp', (PHP_OS == 'WINNT' ? 'c:\\windows\\temp' : '/tmp'));
   $file = tempnam($temp, 'file');
-  if (!$fp = fopen($file, 'w')) {
+  if (!$fp = fopen($file, 'wb')) {
     drupal_set_message(t('Unable to create file.'), 'error');
     return 0;
   }
@@ -378,7 +437,7 @@ function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $ca
         }
         elseif (ereg($mask, $file)) {
           $name = basename($file);
-          $files["$dir/$file"]->filename = "$dir/$file";
+          $files["$dir/$file"]->path = "$dir/$file";
           $files["$dir/$file"]->name = substr($name, 0, strrpos($name, '.'));
           if ($callback) {
             $callback("$dir/$file");
diff --git a/misc/drupal.css b/misc/drupal.css
index 548b122bbd9c..1ac512ceebf9 100644
--- a/misc/drupal.css
+++ b/misc/drupal.css
@@ -198,6 +198,10 @@ tr.light .form-item, tr.dark .form-item {
   margin-right: 2em;
   margin-bottom: 1em;
 }
+.node-form .attachments fieldset {
+  float: none;
+  display: block;
+}
 .ok {
   color: #080;
 }
diff --git a/modules/node.module b/modules/node.module
index 0e9610d7c5c3..cd20529cf97b 100644
--- a/modules/node.module
+++ b/modules/node.module
@@ -1127,6 +1127,9 @@ function node_form($edit) {
 
   $output .= '<div class="node-form">';
 
+  // Add hidden 'op' variable, which specifies the default operation (Preview).
+  $output .= '<input type="hidden" name="op" value="'. check_form(t('Preview')) ."\" />\n";
+
   // Add the admin-specific parts/
 
   if (user_access('administer nodes')) {
diff --git a/modules/node/node.module b/modules/node/node.module
index 0e9610d7c5c3..cd20529cf97b 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -1127,6 +1127,9 @@ function node_form($edit) {
 
   $output .= '<div class="node-form">';
 
+  // Add hidden 'op' variable, which specifies the default operation (Preview).
+  $output .= '<input type="hidden" name="op" value="'. check_form(t('Preview')) ."\" />\n";
+
   // Add the admin-specific parts/
 
   if (user_access('administer nodes')) {
diff --git a/modules/system.module b/modules/system.module
index 852272208100..ceb1eac969c6 100644
--- a/modules/system.module
+++ b/modules/system.module
@@ -152,17 +152,13 @@ function system_view_general() {
 
   // file system:
   $directory_path = variable_get('file_directory_path', 'files');
-  if (!file_check_directory($directory_path)) {
-    form_set_error('file_directory_path', t("The directory '%name' does not exist, or is not writable.", array('%name' => $directory_path)));
-  }
+  file_check_directory($directory_path, FILE_CREATE_DIRECTORY, 'file_directory_path');
 
-  $directory_temp = variable_get('file_directory_temp', (PHP_OS == 'WINNT' ? 'c:\\windows\\temp' : '/tmp'));
-  if (!file_check_directory($directory_temp)) {
-    form_set_error('file_directory_temp', t("The directory '%name' does not exist, or is not writable.", array('%name' => $directory_temp)));
-  }
+  $directory_temp = variable_get('file_directory_temp', 'temp');
+  file_check_directory(file_create_path($directory_temp), FILE_CREATE_DIRECTORY, 'file_directory_temp');
 
   $group = form_textfield(t('File system path'), 'file_directory_path', $directory_path, 70, 255, t('A file system path where the files will be stored. This directory has to exist and be writable by Drupal. If the download method is set to public this directory has to be relative to Drupal installation directory, and be accessible over the web. When download method is set to private this directory should not be accessible over the web. Changing this location after the site has been in use will cause problems so only change this setting on an existing site if you know what you are doing.'));
-  $group .= form_textfield(t('Temporary directory'), 'file_directory_temp', $directory_temp, 70, 255, t('Location where files can be saved temporarily. This directory should not be accessible from the web.'));
+  $group .= form_textfield(t('Temporary directory'), 'file_directory_temp', $directory_temp, 70, 255, t('Location where uploaded files will be placed for preview purposes. This directory should be relative to the file system path.'));
   $group .= form_radios(t('Download method'), 'file_downloads', variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC), array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using http directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are be transferred by Drupal.')), t('This setting can be changed at any time, however, all download URLs will change and there may be unexpected problems so it is not recommended.'));
   $output .= form_group(t('File system settings'), $group);
 
diff --git a/modules/system/system.module b/modules/system/system.module
index 852272208100..ceb1eac969c6 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -152,17 +152,13 @@ function system_view_general() {
 
   // file system:
   $directory_path = variable_get('file_directory_path', 'files');
-  if (!file_check_directory($directory_path)) {
-    form_set_error('file_directory_path', t("The directory '%name' does not exist, or is not writable.", array('%name' => $directory_path)));
-  }
+  file_check_directory($directory_path, FILE_CREATE_DIRECTORY, 'file_directory_path');
 
-  $directory_temp = variable_get('file_directory_temp', (PHP_OS == 'WINNT' ? 'c:\\windows\\temp' : '/tmp'));
-  if (!file_check_directory($directory_temp)) {
-    form_set_error('file_directory_temp', t("The directory '%name' does not exist, or is not writable.", array('%name' => $directory_temp)));
-  }
+  $directory_temp = variable_get('file_directory_temp', 'temp');
+  file_check_directory(file_create_path($directory_temp), FILE_CREATE_DIRECTORY, 'file_directory_temp');
 
   $group = form_textfield(t('File system path'), 'file_directory_path', $directory_path, 70, 255, t('A file system path where the files will be stored. This directory has to exist and be writable by Drupal. If the download method is set to public this directory has to be relative to Drupal installation directory, and be accessible over the web. When download method is set to private this directory should not be accessible over the web. Changing this location after the site has been in use will cause problems so only change this setting on an existing site if you know what you are doing.'));
-  $group .= form_textfield(t('Temporary directory'), 'file_directory_temp', $directory_temp, 70, 255, t('Location where files can be saved temporarily. This directory should not be accessible from the web.'));
+  $group .= form_textfield(t('Temporary directory'), 'file_directory_temp', $directory_temp, 70, 255, t('Location where uploaded files will be placed for preview purposes. This directory should be relative to the file system path.'));
   $group .= form_radios(t('Download method'), 'file_downloads', variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC), array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using http directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are be transferred by Drupal.')), t('This setting can be changed at any time, however, all download URLs will change and there may be unexpected problems so it is not recommended.'));
   $output .= form_group(t('File system settings'), $group);
 
diff --git a/modules/upload.module b/modules/upload.module
new file mode 100644
index 000000000000..7260ccca1d69
--- /dev/null
+++ b/modules/upload.module
@@ -0,0 +1,326 @@
+<?php
+/* $Id$ */
+
+function upload_help($section) {
+  switch ($section) {
+    case 'admin/modules#description':
+      return t('File-handling and attaching files to nodes.');
+    case 'admin/upload':
+      return t('Users with the <a href="%permissions"><em>upload files</em> permission</a> can upload attachments. You can choose which node types can take attachments on the <a href="%workflow">workflow settings</a> page.', array('%permissions' => url('admin/user/configure/permission'), '%workflow' => url('admin/node/configure/defaults')));
+    case 'admin/node/configure/defaults':
+      return t('<p>If you want users to be able to attach files to nodes, check the <em>attachments</em> column in the appropriate column.</p>');
+  }
+}
+
+function upload_perm() {
+  return array('upload files');
+}
+
+function upload_menu() {
+  // Add handlers for previewing new uploads.
+  if ($_SESSION['file_uploads']) {
+    $items = array();
+    foreach ($_SESSION['file_uploads'] as $key => $file) {
+      $filename = file_create_filename($file->filename, file_create_path());
+      $items[] = array(
+        'path' => $filename, 'title' => t('file download'),
+        'callback' => 'upload_download',
+        'access' => true,
+        'type' => MENU_DYNAMIC_ITEM & MENU_HIDDEN
+      );
+      $_SESSION['file_uploads'][$key]->_filename = $filename;
+    }
+  }
+  $items[] = array(
+    'path' => 'admin/upload', 'title' => t('uploads'),
+    'callback' => 'upload_admin',
+    'access' => true,
+    'type' => MENU_NORMAL_ITEM
+  );
+  return $items;
+}
+
+function upload_admin() {
+  system_settings_save();
+
+  $group .= form_textfield(t("Maximum total file size"), "upload_maxsize_total", variable_get("upload_maxsize_total", 0), 5, 5, t("The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited."));
+
+  $output = form_group(t('General settings'), $group);
+
+  $roles = user_roles(0, 'upload files');
+
+  foreach ($roles as $rid => $role) {
+    $group = form_textfield(t("Permitted file extensions"), "upload_extensions_$rid", variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps"), 60, 255, t("Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot."));
+    $group .= form_textfield(t("Maximum file size per upload"), "upload_uploadsize_$rid", variable_get("upload_uploadsize_$rid", 1), 5, 5, t("The maximum size of a file a user can upload (in megabytes)."));
+    $group .= form_textfield(t("Total file size per user"), "upload_usersize_$rid", variable_get("upload_usersize_$rid", 10), 5, 5, t("The maximum size of all files a user can have on the site (in megabytes)."));
+    $output .= form_group(t("Settings for '%role'", array('%role' => $role)), $group);
+  }
+
+  print theme('page', system_settings_form($output));
+}
+
+function upload_download() {
+  foreach ($_SESSION['file_uploads'] as $file) {
+    if ($file->_filename == $_GET['q']) {
+      file_transfer($file->filepath, array('Content-Type: '. $file->filemime, 'Content-Length: '. $file->filesize));
+    }
+  }
+}
+
+function upload_file_download($file) {
+  $file = file_create_path($file);
+  $result = db_query("SELECT * from {files} WHERE filepath = '%s'", $file);
+  if ($file = db_fetch_object($result)) {
+    $name = mime_header_encode($file->filename);
+    // Serve images and text inline for the browser to display rather than download.
+    $disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
+    return array('Content-Type: '. $file->filemime .'; name='. $name,
+                 'Content-Length: '. $file->filesize,
+                 'Content-Disposition: '. $disposition .'; filename='. $name);
+  }
+}
+
+function upload_nodeapi(&$node, $op, $arg) {
+  switch ($op) {
+    case 'settings':
+      $output[t('attachments')] = form_checkbox(NULL, "upload_$node->type", 1, variable_get("upload_$node->type", 0));
+      break;
+    case 'form param':
+      if (variable_get("upload_$node->type", 0)) {
+        $output['options'] = array('enctype' => 'multipart/form-data');
+      }
+      break;
+    case 'validate':
+      $node->files = upload_load($node);
+
+      // Double check existing files:
+      if (is_array($node->list)) {
+        foreach ($node->list as $key => $value) {
+          if ($file = file_check_upload($key)) {
+            $node->files[$file->source] = $file;
+            $node->files[$key]->list = $node->list[$key];
+            $node->files[$key]->remove = $node->remove[$key];
+            if ($file->source) {
+              $filesize += $file->filesize;
+            }
+          }
+        }
+      }
+      else {
+        foreach ($node->files as $key => $file) {
+          $node->list[$key] = $file->list;
+        }
+      }
+
+      if ($file = file_check_upload('upload')) {
+        global $user;
+
+        $max_size = variable_get("upload_maxsize_total", 0);
+        $total_size = upload_count_size() + $filesize;
+        $total_usersize = upload_count_size($user->uid) + $filesize;
+
+
+        if ($maxsize && $total_size > $maxsize) {
+          drupal_set_message(t("Error attaching file '%name': total file size exceeded", array('%name' => $file->filename)), 'error');
+          break;
+        }
+
+        // Validate file against all users roles. Only denies an upload when
+        // all roles prevent it.
+        foreach ($user->roles as $rid => $name) {
+          $extensions = variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps");
+          $uploadsize = variable_get("upload_uploadsize_$rid", 1);
+          $usersize = variable_get("upload_usersize_$rid", 1);
+
+          $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+
+          if (!preg_match($regex, $file->filename)) {
+            $error['extension']++;
+          }
+
+          if ($file->filesize > $uploadsize * 1024 * 1024) {
+            $error['uploadsize']++;
+          }
+
+          if ($total_usersize + $file->filesize > $usersize * 1024 * 1024) {
+            $error['usersize']++;
+          }
+        }
+
+        if ($error['extension'] == count($user->roles)) {
+          drupal_set_message(t("error attaching file '%name': invalid extension", array('%name' => $file->filename)), 'error');
+        }
+        elseif ($error['uploadsize'] == count($user->roles)) {
+          drupal_set_message(t("error attaching file '%name': exceeds maximum file size", array('%name' => $file->filename)), 'error');
+        }
+        elseif ($error['usersize'] == count($user->roles)) {
+          drupal_set_message(t("error attaching file '%name': exceeds maximum file size", array('%name' => $file->filename)), 'error');
+        }
+        else {
+          $key = 'upload_'. count($_SESSION['file_uploads']);
+          $file->source = $key;
+          $file->list = 1;
+          $file = file_save_upload($file);
+          $node->files[$key] = $file;
+        }
+      }
+      break;
+    case 'form post':
+      if (variable_get("upload_$node->type", 0) == 1) {
+        $output = upload_form($node);
+      }
+      break;
+    case 'load':
+      if (variable_get("upload_$node->type", 0) == 1) {
+        $output->files = upload_load($node);
+      }
+      break;
+    case 'view':
+      if ($node->files) {
+        $header = array(t('Attachment'), t('Size'));
+        $rows = array();
+        $previews = array();
+
+        // Build list of attached files
+        foreach ($node->files as $file) {
+          if ($file->list) {
+            $rows[] = array(
+              '<a href="'. ($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()))) . '">'. $file->filename .'</a>',
+              format_size($file->filesize)
+            );
+            // We save the list of files still in preview for later
+            if (!$file->fid) {
+              $previews[] = $file;
+            }
+          }
+        }
+
+        // URLs to files being previewed are actually Drupal paths. When Clean
+        // URLs are disabled, the two do not match. We perform an automatic
+        // replacement from temporary to permanent URLs. That way, the author
+        // can use the final URL in the body before having actually saved (to
+        // place inline images for example).
+        if (!variable_get('clean_url', 0)) {
+          foreach ($previews as $file) {
+            $old = file_create_filename($file->filename, file_create_path());
+            $new = url($old);
+            drupal_set_message("debug: $old $new");
+            $node->body = str_replace($old, $new, $node->body);
+            $node->teaser = str_replace($old, $new, $node->teaser);
+          }
+        }
+
+        $teaser = $arg;
+        // Add the attachments list
+        if (count($rows) && !$teaser) {
+          $node->body .= theme('table', $header, $rows);
+        }
+      }
+      break;
+    case 'insert':
+    case 'update':
+      upload_save($node);
+      break;
+    case 'delete':
+      upload_delete($node);
+      break;
+  }
+
+  return $output;
+}
+
+function upload_count_size($uid = 0) {
+  if ($uid) {
+    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE uid = %d", $uid);
+  }
+  else {
+    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid");
+  }
+
+  return db_result($result);
+}
+
+function upload_save($node) {
+  foreach ($node->files as $key => $file) {
+    if ($file->source && !$file->remove) {
+      // Insert new files:
+      $fid = db_next_id('{files}_fid');
+      $file = file_save_upload($file, $file->filename);
+
+      // Clean up the session:
+      unset($_SESSION['file_uploads'][$file->source]);
+
+      db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)",
+               $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
+    }
+    else {
+      // Remove or update existing files:
+      if ($node->remove[$key]) {
+        file_delete($file->filepath);
+        db_query("DELETE FROM {files} WHERE fid = %d", $key);
+      }
+      if ($file->list != $node->list[$key]) {
+        db_query("UPDATE {files} SET list = %d WHERE fid = %d", $node->list[$key], $key);
+      }
+    }
+  }
+  return;
+}
+
+function upload_delete($node) {
+  $node->files = upload_load($node);
+  foreach ($node->files as $file) {
+    file_delete($file->filepath);
+  }
+  db_query("DELETE FROM {files} WHERE nid = %d", $node->nid);
+}
+
+function upload_form($node) {
+  $header = array(t('Delete'), t('List'), t('URL'), t('Size'));
+  $rows = array();
+
+  if (is_array($node->files)) {
+    foreach ($node->files as $key => $file) {
+      $rows[] = array(
+        form_checkbox('', "remove][$key", 1, $file->remove),
+        form_checkbox('', "list][$key", 1, $file->list),
+        $file->filename ."<br /><small>". file_create_url(($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>",
+        format_size($file->filesize)
+      );
+    }
+  }
+
+  if (count($node->files)) {
+    $output = form_item('', theme('table', $header, $rows), t('Note: Changes made to the attachments list are not permanent until you save this node.'));
+  }
+  $output .= form_file(t('Attach new file'), "upload", 40);
+  $output .= form_button(t('Attach'), 'fileop');
+
+  return '<div class="attachments">'. form_group(t('Attachments'), $output) . '</div>';
+}
+
+function upload_load($node) {
+  $files = array();
+
+  if ($node->nid) {
+    $result = db_query("SELECT * FROM {files} WHERE nid = %d", $node->nid);
+    while ($file = db_fetch_object($result)) {
+      $files[$file->fid] = $file;
+    }
+  }
+
+  return $files;
+}
+
+function theme_upload_attached($file, $temp = 0) {
+  if ($temp) {
+    $file = variable_get('file_directory_path', 'files') . FILE_SEPARATOR . $file->filename .' ('. format_size($file->filesize) .')<br />';
+  }
+  else {
+    $file = file_create_path($file->filepath) .' ('. format_size($file->filesize) .")<br />";
+  }
+
+  return $file;
+}
+
+?>
diff --git a/modules/upload/upload.module b/modules/upload/upload.module
new file mode 100644
index 000000000000..7260ccca1d69
--- /dev/null
+++ b/modules/upload/upload.module
@@ -0,0 +1,326 @@
+<?php
+/* $Id$ */
+
+function upload_help($section) {
+  switch ($section) {
+    case 'admin/modules#description':
+      return t('File-handling and attaching files to nodes.');
+    case 'admin/upload':
+      return t('Users with the <a href="%permissions"><em>upload files</em> permission</a> can upload attachments. You can choose which node types can take attachments on the <a href="%workflow">workflow settings</a> page.', array('%permissions' => url('admin/user/configure/permission'), '%workflow' => url('admin/node/configure/defaults')));
+    case 'admin/node/configure/defaults':
+      return t('<p>If you want users to be able to attach files to nodes, check the <em>attachments</em> column in the appropriate column.</p>');
+  }
+}
+
+function upload_perm() {
+  return array('upload files');
+}
+
+function upload_menu() {
+  // Add handlers for previewing new uploads.
+  if ($_SESSION['file_uploads']) {
+    $items = array();
+    foreach ($_SESSION['file_uploads'] as $key => $file) {
+      $filename = file_create_filename($file->filename, file_create_path());
+      $items[] = array(
+        'path' => $filename, 'title' => t('file download'),
+        'callback' => 'upload_download',
+        'access' => true,
+        'type' => MENU_DYNAMIC_ITEM & MENU_HIDDEN
+      );
+      $_SESSION['file_uploads'][$key]->_filename = $filename;
+    }
+  }
+  $items[] = array(
+    'path' => 'admin/upload', 'title' => t('uploads'),
+    'callback' => 'upload_admin',
+    'access' => true,
+    'type' => MENU_NORMAL_ITEM
+  );
+  return $items;
+}
+
+function upload_admin() {
+  system_settings_save();
+
+  $group .= form_textfield(t("Maximum total file size"), "upload_maxsize_total", variable_get("upload_maxsize_total", 0), 5, 5, t("The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited."));
+
+  $output = form_group(t('General settings'), $group);
+
+  $roles = user_roles(0, 'upload files');
+
+  foreach ($roles as $rid => $role) {
+    $group = form_textfield(t("Permitted file extensions"), "upload_extensions_$rid", variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps"), 60, 255, t("Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot."));
+    $group .= form_textfield(t("Maximum file size per upload"), "upload_uploadsize_$rid", variable_get("upload_uploadsize_$rid", 1), 5, 5, t("The maximum size of a file a user can upload (in megabytes)."));
+    $group .= form_textfield(t("Total file size per user"), "upload_usersize_$rid", variable_get("upload_usersize_$rid", 10), 5, 5, t("The maximum size of all files a user can have on the site (in megabytes)."));
+    $output .= form_group(t("Settings for '%role'", array('%role' => $role)), $group);
+  }
+
+  print theme('page', system_settings_form($output));
+}
+
+function upload_download() {
+  foreach ($_SESSION['file_uploads'] as $file) {
+    if ($file->_filename == $_GET['q']) {
+      file_transfer($file->filepath, array('Content-Type: '. $file->filemime, 'Content-Length: '. $file->filesize));
+    }
+  }
+}
+
+function upload_file_download($file) {
+  $file = file_create_path($file);
+  $result = db_query("SELECT * from {files} WHERE filepath = '%s'", $file);
+  if ($file = db_fetch_object($result)) {
+    $name = mime_header_encode($file->filename);
+    // Serve images and text inline for the browser to display rather than download.
+    $disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
+    return array('Content-Type: '. $file->filemime .'; name='. $name,
+                 'Content-Length: '. $file->filesize,
+                 'Content-Disposition: '. $disposition .'; filename='. $name);
+  }
+}
+
+function upload_nodeapi(&$node, $op, $arg) {
+  switch ($op) {
+    case 'settings':
+      $output[t('attachments')] = form_checkbox(NULL, "upload_$node->type", 1, variable_get("upload_$node->type", 0));
+      break;
+    case 'form param':
+      if (variable_get("upload_$node->type", 0)) {
+        $output['options'] = array('enctype' => 'multipart/form-data');
+      }
+      break;
+    case 'validate':
+      $node->files = upload_load($node);
+
+      // Double check existing files:
+      if (is_array($node->list)) {
+        foreach ($node->list as $key => $value) {
+          if ($file = file_check_upload($key)) {
+            $node->files[$file->source] = $file;
+            $node->files[$key]->list = $node->list[$key];
+            $node->files[$key]->remove = $node->remove[$key];
+            if ($file->source) {
+              $filesize += $file->filesize;
+            }
+          }
+        }
+      }
+      else {
+        foreach ($node->files as $key => $file) {
+          $node->list[$key] = $file->list;
+        }
+      }
+
+      if ($file = file_check_upload('upload')) {
+        global $user;
+
+        $max_size = variable_get("upload_maxsize_total", 0);
+        $total_size = upload_count_size() + $filesize;
+        $total_usersize = upload_count_size($user->uid) + $filesize;
+
+
+        if ($maxsize && $total_size > $maxsize) {
+          drupal_set_message(t("Error attaching file '%name': total file size exceeded", array('%name' => $file->filename)), 'error');
+          break;
+        }
+
+        // Validate file against all users roles. Only denies an upload when
+        // all roles prevent it.
+        foreach ($user->roles as $rid => $name) {
+          $extensions = variable_get("upload_extensions_$rid", "jpg jpeg gif png txt html doc xls pdf ppt pps");
+          $uploadsize = variable_get("upload_uploadsize_$rid", 1);
+          $usersize = variable_get("upload_usersize_$rid", 1);
+
+          $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+
+          if (!preg_match($regex, $file->filename)) {
+            $error['extension']++;
+          }
+
+          if ($file->filesize > $uploadsize * 1024 * 1024) {
+            $error['uploadsize']++;
+          }
+
+          if ($total_usersize + $file->filesize > $usersize * 1024 * 1024) {
+            $error['usersize']++;
+          }
+        }
+
+        if ($error['extension'] == count($user->roles)) {
+          drupal_set_message(t("error attaching file '%name': invalid extension", array('%name' => $file->filename)), 'error');
+        }
+        elseif ($error['uploadsize'] == count($user->roles)) {
+          drupal_set_message(t("error attaching file '%name': exceeds maximum file size", array('%name' => $file->filename)), 'error');
+        }
+        elseif ($error['usersize'] == count($user->roles)) {
+          drupal_set_message(t("error attaching file '%name': exceeds maximum file size", array('%name' => $file->filename)), 'error');
+        }
+        else {
+          $key = 'upload_'. count($_SESSION['file_uploads']);
+          $file->source = $key;
+          $file->list = 1;
+          $file = file_save_upload($file);
+          $node->files[$key] = $file;
+        }
+      }
+      break;
+    case 'form post':
+      if (variable_get("upload_$node->type", 0) == 1) {
+        $output = upload_form($node);
+      }
+      break;
+    case 'load':
+      if (variable_get("upload_$node->type", 0) == 1) {
+        $output->files = upload_load($node);
+      }
+      break;
+    case 'view':
+      if ($node->files) {
+        $header = array(t('Attachment'), t('Size'));
+        $rows = array();
+        $previews = array();
+
+        // Build list of attached files
+        foreach ($node->files as $file) {
+          if ($file->list) {
+            $rows[] = array(
+              '<a href="'. ($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()))) . '">'. $file->filename .'</a>',
+              format_size($file->filesize)
+            );
+            // We save the list of files still in preview for later
+            if (!$file->fid) {
+              $previews[] = $file;
+            }
+          }
+        }
+
+        // URLs to files being previewed are actually Drupal paths. When Clean
+        // URLs are disabled, the two do not match. We perform an automatic
+        // replacement from temporary to permanent URLs. That way, the author
+        // can use the final URL in the body before having actually saved (to
+        // place inline images for example).
+        if (!variable_get('clean_url', 0)) {
+          foreach ($previews as $file) {
+            $old = file_create_filename($file->filename, file_create_path());
+            $new = url($old);
+            drupal_set_message("debug: $old $new");
+            $node->body = str_replace($old, $new, $node->body);
+            $node->teaser = str_replace($old, $new, $node->teaser);
+          }
+        }
+
+        $teaser = $arg;
+        // Add the attachments list
+        if (count($rows) && !$teaser) {
+          $node->body .= theme('table', $header, $rows);
+        }
+      }
+      break;
+    case 'insert':
+    case 'update':
+      upload_save($node);
+      break;
+    case 'delete':
+      upload_delete($node);
+      break;
+  }
+
+  return $output;
+}
+
+function upload_count_size($uid = 0) {
+  if ($uid) {
+    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE uid = %d", $uid);
+  }
+  else {
+    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid");
+  }
+
+  return db_result($result);
+}
+
+function upload_save($node) {
+  foreach ($node->files as $key => $file) {
+    if ($file->source && !$file->remove) {
+      // Insert new files:
+      $fid = db_next_id('{files}_fid');
+      $file = file_save_upload($file, $file->filename);
+
+      // Clean up the session:
+      unset($_SESSION['file_uploads'][$file->source]);
+
+      db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)",
+               $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
+    }
+    else {
+      // Remove or update existing files:
+      if ($node->remove[$key]) {
+        file_delete($file->filepath);
+        db_query("DELETE FROM {files} WHERE fid = %d", $key);
+      }
+      if ($file->list != $node->list[$key]) {
+        db_query("UPDATE {files} SET list = %d WHERE fid = %d", $node->list[$key], $key);
+      }
+    }
+  }
+  return;
+}
+
+function upload_delete($node) {
+  $node->files = upload_load($node);
+  foreach ($node->files as $file) {
+    file_delete($file->filepath);
+  }
+  db_query("DELETE FROM {files} WHERE nid = %d", $node->nid);
+}
+
+function upload_form($node) {
+  $header = array(t('Delete'), t('List'), t('URL'), t('Size'));
+  $rows = array();
+
+  if (is_array($node->files)) {
+    foreach ($node->files as $key => $file) {
+      $rows[] = array(
+        form_checkbox('', "remove][$key", 1, $file->remove),
+        form_checkbox('', "list][$key", 1, $file->list),
+        $file->filename ."<br /><small>". file_create_url(($file->fid ? $file->filepath : file_create_filename($file->filename, file_create_path()))) ."</small>",
+        format_size($file->filesize)
+      );
+    }
+  }
+
+  if (count($node->files)) {
+    $output = form_item('', theme('table', $header, $rows), t('Note: Changes made to the attachments list are not permanent until you save this node.'));
+  }
+  $output .= form_file(t('Attach new file'), "upload", 40);
+  $output .= form_button(t('Attach'), 'fileop');
+
+  return '<div class="attachments">'. form_group(t('Attachments'), $output) . '</div>';
+}
+
+function upload_load($node) {
+  $files = array();
+
+  if ($node->nid) {
+    $result = db_query("SELECT * FROM {files} WHERE nid = %d", $node->nid);
+    while ($file = db_fetch_object($result)) {
+      $files[$file->fid] = $file;
+    }
+  }
+
+  return $files;
+}
+
+function theme_upload_attached($file, $temp = 0) {
+  if ($temp) {
+    $file = variable_get('file_directory_path', 'files') . FILE_SEPARATOR . $file->filename .' ('. format_size($file->filesize) .')<br />';
+  }
+  else {
+    $file = file_create_path($file->filepath) .' ('. format_size($file->filesize) .")<br />";
+  }
+
+  return $file;
+}
+
+?>
diff --git a/modules/user.module b/modules/user.module
index 6c89b24cc9d7..ee8d41512e26 100644
--- a/modules/user.module
+++ b/modules/user.module
@@ -182,8 +182,8 @@ function user_validate_picture($file, &$edit, $user) {
 
   // Check that uploaded file is an image, with a maximum file size
   // and maximum height/width.
-  $extension = strtolower(strrchr($file->name, '.'));
-  $size = getimagesize($file->path);
+  $extension = strtolower(strrchr($file->filename, '.'));
+  $size = getimagesize($file->filepath);
   list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
 
   if ((!in_array($size[2], array(1, 2, 3))) || (!in_array($extension, array('.gif', '.jpg', '.png', '.jpeg')))) {
@@ -307,37 +307,13 @@ function user_mail($mail, $subject, $message, $header) {
     */
     return mail(
       $mail,
-      user_mail_encode($subject),
+      mime_header_encode($subject),
       str_replace("\r", '', $message),
       "MIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8; format=flowed\nContent-transfer-encoding: 8Bit\n" . $header
     );
   }
 }
 
-function user_mail_encode($string, $charset = 'UTF-8') {
-  /*
-  ** Used to encodes mail headers that contain non US- ASCII
-  ** characters.
-  ** http://www.rfc-editor.org/rfc/rfc2047.txt
-  **
-  ** Notes:
-  **   - Only encode strings that contain non-ASCII characters.
-  **   - The chunks come in groupings of 4 bytes when using base64
-  **     encoded.
-  **   - trim() is used to ensure that no extra spacing is added by
-  **     chunk_split() or preg_replace().
-  **   - Using \n as the chunk separator may cause problems on some
-  **     systems and may have to be changed to \r\n or \r.
-  */
-  if (!preg_match('/^[\x20-\x7E]*$/', $string)) {
-    $chunk_size = 75 - 7 - strlen($charset);
-    $chunk_size -= $chunk_size % 4;
-    $string = trim(chunk_split(base64_encode($string), $chunk_size, "\n"));
-    $string = trim(preg_replace('/^(.*)$/m', " =?$charset?B?\\1?=", $string));
-  }
-  return $string;
-}
-
 function user_deny($type, $mask) {
   $allow = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 1 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
   $deny = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 0 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
@@ -1046,7 +1022,7 @@ function user_edit_validate($uid, &$edit) {
 
   // Validate the e-mail address:
   if ($error = user_validate_mail($edit['mail'])) {
-    form_set_error('mail', ucfirst($error));
+    form_set_error('mail', $error);
   }
   else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
     form_set_error('mail', t('The e-mail address "%s" is already taken.', array('%s' => $edit['mail'])));
@@ -1247,9 +1223,7 @@ function user_configure_settings() {
   $output .= form_group(t('User email settings'), $group);
 
   // Picture settings.
-  if (!file_check_directory(file_create_path(variable_get('user_picture_path', 'pictures')))) {
-    form_set_error('user_picture_path', t('the picture directory does not exist, or is not writable.'));
-  }
+  file_check_directory(file_create_path(variable_get('user_picture_path', 'pictures')), 1, 'user_picture_path');
 
   $group  = form_radios(t('Picture support'), 'user_pictures', variable_get('user_pictures', 0), array(t('Disabled'), t('Enabled')), t('Enable picture support.'));
   $group .= form_textfield(t('Picture image path'), 'user_picture_path', variable_get('user_picture_path', 'pictures'), 45, 255, t('Subdirectory in the directory "%dir" where pictures will be stored.', array('%dir' => variable_get('file_directory_path', 'files') . FILE_SEPARATOR)));
@@ -1351,8 +1325,15 @@ function user_admin_access($edit = array()) {
   return form($output);
 }
 
-function user_roles($membersonly = 0) {
-  $result = db_query('SELECT * FROM {role} ORDER BY name');
+function user_roles($membersonly = 0, $permission = 0) {
+  $roles = array();
+
+  if ($permission) {
+    $result = db_query("SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name", $permission);
+  }
+  else {
+    $result = db_query('SELECT * FROM {role} ORDER BY name');
+  }
   while ($role = db_fetch_object($result)) {
     if (!$membersonly || ($membersonly && $role->name != 'anonymous user')) {
       $roles[$role->rid] = $role->name;
diff --git a/modules/user/user.module b/modules/user/user.module
index 6c89b24cc9d7..ee8d41512e26 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -182,8 +182,8 @@ function user_validate_picture($file, &$edit, $user) {
 
   // Check that uploaded file is an image, with a maximum file size
   // and maximum height/width.
-  $extension = strtolower(strrchr($file->name, '.'));
-  $size = getimagesize($file->path);
+  $extension = strtolower(strrchr($file->filename, '.'));
+  $size = getimagesize($file->filepath);
   list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
 
   if ((!in_array($size[2], array(1, 2, 3))) || (!in_array($extension, array('.gif', '.jpg', '.png', '.jpeg')))) {
@@ -307,37 +307,13 @@ function user_mail($mail, $subject, $message, $header) {
     */
     return mail(
       $mail,
-      user_mail_encode($subject),
+      mime_header_encode($subject),
       str_replace("\r", '', $message),
       "MIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8; format=flowed\nContent-transfer-encoding: 8Bit\n" . $header
     );
   }
 }
 
-function user_mail_encode($string, $charset = 'UTF-8') {
-  /*
-  ** Used to encodes mail headers that contain non US- ASCII
-  ** characters.
-  ** http://www.rfc-editor.org/rfc/rfc2047.txt
-  **
-  ** Notes:
-  **   - Only encode strings that contain non-ASCII characters.
-  **   - The chunks come in groupings of 4 bytes when using base64
-  **     encoded.
-  **   - trim() is used to ensure that no extra spacing is added by
-  **     chunk_split() or preg_replace().
-  **   - Using \n as the chunk separator may cause problems on some
-  **     systems and may have to be changed to \r\n or \r.
-  */
-  if (!preg_match('/^[\x20-\x7E]*$/', $string)) {
-    $chunk_size = 75 - 7 - strlen($charset);
-    $chunk_size -= $chunk_size % 4;
-    $string = trim(chunk_split(base64_encode($string), $chunk_size, "\n"));
-    $string = trim(preg_replace('/^(.*)$/m', " =?$charset?B?\\1?=", $string));
-  }
-  return $string;
-}
-
 function user_deny($type, $mask) {
   $allow = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 1 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
   $deny = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 0 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
@@ -1046,7 +1022,7 @@ function user_edit_validate($uid, &$edit) {
 
   // Validate the e-mail address:
   if ($error = user_validate_mail($edit['mail'])) {
-    form_set_error('mail', ucfirst($error));
+    form_set_error('mail', $error);
   }
   else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
     form_set_error('mail', t('The e-mail address "%s" is already taken.', array('%s' => $edit['mail'])));
@@ -1247,9 +1223,7 @@ function user_configure_settings() {
   $output .= form_group(t('User email settings'), $group);
 
   // Picture settings.
-  if (!file_check_directory(file_create_path(variable_get('user_picture_path', 'pictures')))) {
-    form_set_error('user_picture_path', t('the picture directory does not exist, or is not writable.'));
-  }
+  file_check_directory(file_create_path(variable_get('user_picture_path', 'pictures')), 1, 'user_picture_path');
 
   $group  = form_radios(t('Picture support'), 'user_pictures', variable_get('user_pictures', 0), array(t('Disabled'), t('Enabled')), t('Enable picture support.'));
   $group .= form_textfield(t('Picture image path'), 'user_picture_path', variable_get('user_picture_path', 'pictures'), 45, 255, t('Subdirectory in the directory "%dir" where pictures will be stored.', array('%dir' => variable_get('file_directory_path', 'files') . FILE_SEPARATOR)));
@@ -1351,8 +1325,15 @@ function user_admin_access($edit = array()) {
   return form($output);
 }
 
-function user_roles($membersonly = 0) {
-  $result = db_query('SELECT * FROM {role} ORDER BY name');
+function user_roles($membersonly = 0, $permission = 0) {
+  $roles = array();
+
+  if ($permission) {
+    $result = db_query("SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name", $permission);
+  }
+  else {
+    $result = db_query('SELECT * FROM {role} ORDER BY name');
+  }
   while ($role = db_fetch_object($result)) {
     if (!$membersonly || ($membersonly && $role->name != 'anonymous user')) {
       $roles[$role->rid] = $role->name;
-- 
GitLab