Improve system security by using optional special URL with security token
Allows to define separate server/path for image/js/css files
Fix bugs where CSRF attacks were still possible on some requests
| | |
| | | # security rules: |
| | | # - deny access to files not containing a dot or starting with a dot |
| | | # in all locations except installer directory |
| | | RewriteRule ^(?!installer)(\.?[^\.]+)$ - [F] |
| | | RewriteRule ^(?!installer|[a-f0-9]{16})(\.?[^\.]+)$ - [F] |
| | | # - deny access to some locations |
| | | RewriteRule ^/?(\.git|\.tx|SQL|bin|config|logs|temp|tests|program\/(include|lib|localization|steps)) - [F] |
| | | # - deny access to some documentation files |
| | |
| | | CHANGELOG Roundcube Webmail |
| | | =========================== |
| | | |
| | | - Improve system security by using optional special URL with security token - use_secure_urls |
| | | - Allow to define separate server/path for image/js/css files - assets_url/assets_dir |
| | | - Fix import of multiple contact email addresses from Outlook-csv format (#1490169) |
| | | - Fix drag-n-drop to folders expanded while dragging (#1490157) |
| | | - Fix import of multiple contact groups from Google-csv format (#1490159) |
| | | - Fix import of contacts with multiple email addresses from Google-csv format (#1490178) |
| | | - Fix generation of Blowfish-based password hashes (#1490184) |
| | | - Fix bugs where CSRF attacks were still possible on some requests |
| | | |
| | | RELEASE 1.1-beta |
| | | ---------------- |
| | |
| | | // Note: useful when SMTP server stores sent mail in user mailbox |
| | | $config['no_save_sent_messages'] = false; |
| | | |
| | | // Improve system security by using special URL with security token. |
| | | // This can be set to a number defining token length. Default: 16. |
| | | // Warning: This requires http server configuration. Sample: |
| | | // RewriteRule ^/roundcubemail/[a-f0-9]{16}/(.*) /roundcubemail/$1 [PT] |
| | | // Alias /roundcubemail /var/www/roundcubemail/ |
| | | // Note: Use assets_path to not prevent the browser from caching assets |
| | | $config['use_secure_urls'] = false; |
| | | |
| | | // Allows to define separate server/path for image/js/css files |
| | | // Warning: If the domain is different cross-domain access to some |
| | | // resources need to be allowed |
| | | // Sample: |
| | | // <FilesMatch ".(eot|ttf|woff)"> |
| | | // Header set Access-Control-Allow-Origin "*" |
| | | // </FilesMatch> |
| | | $config['assets_path'] = ''; |
| | | |
| | | // While assets_path is for the browser, assets_dir informs |
| | | // PHP code about the location of asset files in filesystem |
| | | $config['assets_dir'] = ''; |
| | | |
| | | |
| | | // ---------------------------------- |
| | | // PLUGINS |
| | | // ---------------------------------- |
| | |
| | | |
| | | // try to log in |
| | | if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') { |
| | | $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login'); |
| | | $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(); |
| | | |
| | | // purge the session in case of new login when a session already exists |
| | | $RCMAIL->kill_session(); |
| | |
| | | unset($redir['abort'], $redir['_err']); |
| | | |
| | | // send redirect |
| | | $OUTPUT->redirect($redir); |
| | | $OUTPUT->redirect($redir, 0, true); |
| | | } |
| | | else { |
| | | if (!$auth['valid']) { |
| | |
| | | } |
| | | } |
| | | |
| | | // end session (after optional referer check) |
| | | else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) |
| | | && (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer()) |
| | | ) { |
| | | // end session |
| | | else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id'])) { |
| | | $RCMAIL->request_security_check($mode = rcube_utils::INPUT_GET); |
| | | |
| | | $userdata = array( |
| | | 'user' => $_SESSION['username'], |
| | | 'host' => $_SESSION['storage_host'], |
| | |
| | | |
| | | $OUTPUT->send($plugin['task']); |
| | | } |
| | | // CSRF prevention |
| | | else { |
| | | // don't check for valid request tokens in these actions |
| | | $request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1); |
| | | |
| | | if (!$request_check_whitelist[$RCMAIL->action]) { |
| | | // check client X-header to verify request origin |
| | | if ($OUTPUT->ajax_call) { |
| | | if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) { |
| | | header('HTTP/1.1 403 Forbidden'); |
| | | die("Invalid Request"); |
| | | } |
| | | } |
| | | // check request token in POST form submissions |
| | | else if (!empty($_POST) && !$RCMAIL->check_request()) { |
| | | $OUTPUT->show_message('invalidrequest', 'error'); |
| | | $OUTPUT->send($RCMAIL->task); |
| | | } |
| | | |
| | | // check referer if configured |
| | | if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) { |
| | | raise_error(array( |
| | | 'code' => 403, 'type' => 'php', |
| | | 'message' => "Referer check failed"), true, true); |
| | | } |
| | | } |
| | | // CSRF prevention |
| | | $RCMAIL->request_security_check(); |
| | | |
| | | // check access to disabled actions |
| | | $disabled_actions = (array) $RCMAIL->config->get('disabled_actions'); |
| | |
| | | var users = this.acl_get_usernames(); |
| | | |
| | | if (users && users.length && confirm(this.get_label('acl.deleteconfirm'))) { |
| | | this.http_request('settings/plugin.acl', '_act=delete&_user='+urlencode(users.join(',')) |
| | | + '&_mbox='+urlencode(this.env.mailbox), |
| | | this.http_post('settings/plugin.acl', { |
| | | _act: 'delete', |
| | | _user: users.join(','), |
| | | _mbox: this.env.mailbox |
| | | }, |
| | | this.set_busy(true, 'acl.deleting')); |
| | | } |
| | | } |
| | |
| | | // Save ACL data |
| | | rcube_webmail.prototype.acl_save = function() |
| | | { |
| | | var user = $('#acluser', this.acl_form).val(), rights = '', type; |
| | | var data, type, rights = '', user = $('#acluser', this.acl_form).val(); |
| | | |
| | | $((this.env.acl_advanced ? '#advancedrights :checkbox' : '#simplerights :checkbox'), this.acl_form).map(function() { |
| | | if (this.checked) |
| | |
| | | return; |
| | | } |
| | | |
| | | this.http_request('settings/plugin.acl', '_act=save' |
| | | + '&_user='+urlencode(user) |
| | | + '&_acl=' +rights |
| | | + '&_mbox='+urlencode(this.env.mailbox) |
| | | + (this.acl_id ? '&_old='+this.acl_id : ''), |
| | | this.set_busy(true, 'acl.saving')); |
| | | data = { |
| | | _act: 'save', |
| | | _user: user, |
| | | _acl: rights, |
| | | _mbox: this.env.mailbox |
| | | } |
| | | |
| | | if (this.acl_id) { |
| | | data._old = this.acl_id; |
| | | } |
| | | |
| | | this.http_post('settings/plugin.acl', data, this.set_busy(true, 'acl.saving')); |
| | | } |
| | | |
| | | // Cancel/Hide form |
| | |
| | | */ |
| | | private function action_save() |
| | | { |
| | | $mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP |
| | | $user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC)); |
| | | $acl = trim(rcube_utils::get_input_value('_acl', rcube_utils::INPUT_GPC)); |
| | | $oldid = trim(rcube_utils::get_input_value('_old', rcube_utils::INPUT_GPC)); |
| | | $mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true)); // UTF7-IMAP |
| | | $user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)); |
| | | $acl = trim(rcube_utils::get_input_value('_acl', rcube_utils::INPUT_POST)); |
| | | $oldid = trim(rcube_utils::get_input_value('_old', rcube_utils::INPUT_POST)); |
| | | |
| | | $acl = array_intersect(str_split($acl), $this->rights_supported()); |
| | | $users = $oldid ? array($user) : explode(',', $user); |
| | |
| | | */ |
| | | private function action_delete() |
| | | { |
| | | $mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC, true)); //UTF7-IMAP |
| | | $user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC)); |
| | | $mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true)); //UTF7-IMAP |
| | | $user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)); |
| | | |
| | | $user = explode(',', $user); |
| | | |
| | |
| | | rcube_webmail.prototype.async_upload_form_frame = function(name) |
| | | { |
| | | document.body.insertAdjacentHTML('BeforeEnd', '<iframe name="' + name + '"' |
| | | + ' src="program/resources/blank.gif" style="width:0; height:0; visibility:hidden"></iframe>'); |
| | | + ' src="' + rcmail.assets_path('program/resources/blank.gif') + '" style="width:0; height:0; visibility:hidden"></iframe>'); |
| | | |
| | | return $('iframe[name="' + name + '"]'); |
| | | }; |
| | |
| | | } |
| | | } |
| | | else if ($action == 'setact' && !$error) { |
| | | $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); |
| | | $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true); |
| | | $result = $this->activate_script($script_name); |
| | | $kep14 = $this->rc->config->get('managesieve_kolab_master'); |
| | | |
| | |
| | | } |
| | | } |
| | | else if ($action == 'deact' && !$error) { |
| | | $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); |
| | | $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true); |
| | | $result = $this->deactivate_script($script_name); |
| | | |
| | | if ($result === true) { |
| | |
| | | } |
| | | } |
| | | else if ($action == 'setdel' && !$error) { |
| | | $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); |
| | | $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true); |
| | | $result = $this->remove_script($script_name); |
| | | |
| | | if ($result === true) { |
| | |
| | | $this->rc->output->command('managesieve_updatelist', 'list', array('list' => $result)); |
| | | } |
| | | else if ($action == 'ruleadd') { |
| | | $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_GPC); |
| | | $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_POST); |
| | | $id = $this->genid(); |
| | | $content = $this->rule_div($fid, $id, false); |
| | | |
| | | $this->rc->output->command('managesieve_rulefill', $content, $id, $rid); |
| | | } |
| | | else if ($action == 'actionadd') { |
| | | $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_GPC); |
| | | $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_POST); |
| | | $id = $this->genid(); |
| | | $content = $this->action_div($fid, $id, false); |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Generate a unique token to be used in a form request |
| | | * |
| | | * @return string The request token |
| | | */ |
| | | public function get_request_token() |
| | | { |
| | | $sess_id = $_COOKIE[ini_get('session.name')]; |
| | | |
| | | if (!$sess_id) { |
| | | $sess_id = session_id(); |
| | | } |
| | | |
| | | $plugin = $this->plugins->exec_hook('request_token', array( |
| | | 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); |
| | | |
| | | return $plugin['value']; |
| | | } |
| | | |
| | | /** |
| | | * Check if the current request contains a valid token |
| | | * |
| | | * @param int Request method |
| | | * |
| | | * @return boolean True if request token is valid false if not |
| | | */ |
| | | public function check_request($mode = rcube_utils::INPUT_POST) |
| | | { |
| | | $token = rcube_utils::get_input_value('_token', $mode); |
| | | $sess_id = $_COOKIE[ini_get('session.name')]; |
| | | |
| | | return !empty($sess_id) && $token == $this->get_request_token(); |
| | | } |
| | | |
| | | /** |
| | | * Build a valid URL to this instance of Roundcube |
| | | * |
| | | * @param mixed Either a string with the action or url parameters as key-value pairs |
| | | * @param boolean Build an URL absolute to document root |
| | | * @param boolean Create fully qualified URL including http(s):// and hostname |
| | | * @param bool Return absolute URL in secure location |
| | | * |
| | | * @return string Valid application URL |
| | | */ |
| | | public function url($p, $absolute = false, $full = false) |
| | | public function url($p, $absolute = false, $full = false, $secure = false) |
| | | { |
| | | if (!is_array($p)) { |
| | | if (strpos($p, 'http') === 0) { |
| | |
| | | } |
| | | } |
| | | |
| | | $base_path = strval($_SERVER['REDIRECT_SCRIPT_URL'] ?: $_SERVER['SCRIPT_NAME']); |
| | | $base_path = preg_replace('![^/]+$!', '', $base_path); |
| | | |
| | | if ($secure && ($token = $this->get_secure_url_token(true))) { |
| | | // add token to the url |
| | | $url = $token . '/' . $url; |
| | | |
| | | // remove old token from the path |
| | | $base_path = rtrim($base_path, '/'); |
| | | $base_path = preg_replace('/\/[a-f0-9]{' . strlen($token) . '}$/', '', $base_path); |
| | | |
| | | // this need to be full url to make redirects work |
| | | $absolute = true; |
| | | } |
| | | |
| | | if ($absolute || $full) { |
| | | // add base path to this Roundcube installation |
| | | $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME'])); |
| | | if ($base_path == '') $base_path = '/'; |
| | | $prefix = $base_path; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * CSRF attack prevention code |
| | | * |
| | | * @param int Request mode |
| | | */ |
| | | public function request_security_check($mode = rcube_utils::INPUT_POST) |
| | | { |
| | | // don't check for valid request tokens in these actions |
| | | // @TODO: get rid of this |
| | | $request_check_whitelist = array('spell'=>1, 'spell_html'=>1); |
| | | |
| | | if ($request_check_whitelist[$this->action]) { |
| | | return; |
| | | } |
| | | |
| | | // check request token |
| | | if (!$this->check_request($mode)) { |
| | | self::raise_error(array( |
| | | 'code' => 403, 'type' => 'php', |
| | | 'message' => "Request security check failed"), false, true); |
| | | } |
| | | |
| | | // check referer if configured |
| | | if ($this->config->get('referer_check') && !rcube_utils::check_referer()) { |
| | | self::raise_error(array( |
| | | 'code' => 403, 'type' => 'php', |
| | | 'message' => "Referer check failed"), true, true); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Registers action aliases for current task |
| | | * |
| | | * @param array $map Alias-to-filename hash array |
| | |
| | | abstract class rcmail_output extends rcube_output |
| | | { |
| | | const JS_OBJECT_NAME = 'rcmail'; |
| | | const BLANK_GIF = 'R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7'; |
| | | |
| | | public $type = 'html'; |
| | | public $ajax_call = false; |
| | |
| | | protected $footer = ''; |
| | | protected $body = ''; |
| | | protected $base_path = ''; |
| | | protected $assets_path; |
| | | protected $assets_dir = RCUBE_INSTALL_PATH; |
| | | protected $devel_mode = false; |
| | | |
| | | // deprecated names of templates used before 0.5 |
| | |
| | | $skin = $this->config->get('skin'); |
| | | $this->set_skin($skin); |
| | | $this->set_env('skin', $skin); |
| | | |
| | | $this->set_assets_path($this->config->get('assets_path'), $this->config->get('assets_dir')); |
| | | |
| | | if (!empty($_REQUEST['_extwin'])) |
| | | $this->set_env('extwin', 1); |
| | |
| | | if ($addtojs || isset($this->js_env[$name])) { |
| | | $this->js_env[$name] = $value; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Parse and set assets path |
| | | * |
| | | * @param string Assets path (relative or absolute URL) |
| | | */ |
| | | public function set_assets_path($path, $fs_dir = null) |
| | | { |
| | | if (empty($path)) { |
| | | return; |
| | | } |
| | | |
| | | $path = rtrim($path, '/') . '/'; |
| | | |
| | | // handle relative assets path |
| | | if (!preg_match('|^https?://|', $path) && $path[0] != '/') { |
| | | // save the path to search for asset files later |
| | | $this->assets_dir = $path; |
| | | |
| | | $base = preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI']); |
| | | $base = rtrim($base, '/'); |
| | | |
| | | // remove url token if exists |
| | | if ($len = intval($this->config->get('use_secure_urls'))) { |
| | | $_base = explode('/', $base); |
| | | $last = count($_base) - 1; |
| | | $length = $len > 1 ? $len : 16; // as in rcube::get_secure_url_token() |
| | | |
| | | // we can't use real token here because it |
| | | // does not exists in unauthenticated state, |
| | | // hope this will not produce false-positive matches |
| | | if ($last > -1 && preg_match('/^[a-f0-9]{' . $length . '}$/', $_base[$last])) { |
| | | $path = '../' . $path; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // set filesystem path for assets |
| | | if ($fs_dir) { |
| | | if ($fs_dir[0] != '/') { |
| | | $fs_dir = realpath(RCUBE_INSTALL_PATH . $fs_dir); |
| | | } |
| | | // ensure the path ends with a slash |
| | | $this->assets_dir = rtrim($fs_dir, '/') . '/'; |
| | | } |
| | | |
| | | $this->assets_path = $path; |
| | | $this->set_env('assets_path', $path); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param string File name/path to resolve (starting with /) |
| | | * @param string Reference to the base path of the matching skin |
| | | * @param string Additional path to search in |
| | | * |
| | | * @return mixed Relative path to the requested file or False if not found |
| | | */ |
| | | public function get_skin_file($file, &$skin_path = null, $add_path = null) |
| | |
| | | } |
| | | |
| | | foreach ($skin_paths as $skin_path) { |
| | | $path = realpath($skin_path . $file); |
| | | if (is_file($path)) { |
| | | $path = realpath(RCUBE_INSTALL_PATH . $skin_path . $file); |
| | | |
| | | if ($path && is_file($path)) { |
| | | return $skin_path . $file; |
| | | } |
| | | |
| | | if ($this->assets_dir != RCUBE_INSTALL_PATH) { |
| | | $path = realpath($this->assets_dir . $skin_path . $file); |
| | | |
| | | if ($path && is_file($path)) { |
| | | return $skin_path . $file; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | * |
| | | * @param mixed $p Either a string with the action or url parameters as key-value pairs |
| | | * @param int $delay Delay in seconds |
| | | * @param bool $secure Redirect to secure location (see rcmail::url()) |
| | | */ |
| | | public function redirect($p = array(), $delay = 1) |
| | | public function redirect($p = array(), $delay = 1, $secure = false) |
| | | { |
| | | if ($this->env['extwin']) |
| | | $p['extwin'] = 1; |
| | | $location = $this->app->url($p); |
| | | $location = $this->app->url($p, false, false, $secure); |
| | | header('Location: ' . $location); |
| | | exit; |
| | | } |
| | |
| | | // find skin template |
| | | $path = false; |
| | | foreach ($this->skin_paths as $skin_path) { |
| | | $path = "$skin_path/templates/$name.html"; |
| | | $path = RCUBE_INSTALL_PATH . "$skin_path/templates/$name.html"; |
| | | |
| | | // fallback to deprecated template names |
| | | if (!is_readable($path) && $this->deprecated_templates[$realname]) { |
| | | $path = "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html"; |
| | | $path = RCUBE_INSTALL_PATH . "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html"; |
| | | |
| | | if (is_readable($path)) { |
| | | rcube::raise_error(array( |
| | |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | | * Modify path by adding URL prefix if configured |
| | | */ |
| | | public function asset_url($path) |
| | | { |
| | | // iframe content can't be in a different domain |
| | | // @TODO: check if assests are on a different domain |
| | | |
| | | if (!$this->assets_path || in_array($path[0], array('?', '/', '.')) || strpos($path, '://')) { |
| | | return $path; |
| | | } |
| | | |
| | | return $this->assets_path . $path; |
| | | } |
| | | |
| | | |
| | | /***** Template parsing methods *****/ |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Callback function for preg_replace_callback in write() |
| | | * Callback function for preg_replace_callback in fix_paths() |
| | | * |
| | | * @return string Parsed string |
| | | */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * Correct paths of asset files according to assets_path |
| | | */ |
| | | protected function fix_assets_paths($output) |
| | | { |
| | | return preg_replace_callback( |
| | | '!(src|href|background)=(["\']?)([a-z0-9/_.?=-]+)(["\'\s>])!i', |
| | | array($this, 'assets_callback'), $output); |
| | | } |
| | | |
| | | /** |
| | | * Callback function for preg_replace_callback in fix_assets_paths() |
| | | * |
| | | * @return string Parsed string |
| | | */ |
| | | protected function assets_callback($matches) |
| | | { |
| | | $file = $this->asset_url($matches[3]); |
| | | |
| | | return $matches[1] . '=' . $matches[2] . $file . $matches[4]; |
| | | } |
| | | |
| | | /** |
| | | * Modify file by adding mtime indicator |
| | | */ |
| | | protected function file_mod($file) |
| | |
| | | // use minified file if exists (not in development mode) |
| | | if (!$this->devel_mode && !preg_match('/\.min\.' . $ext . '$/', $file)) { |
| | | $minified_file = substr($file, 0, strlen($ext) * -1) . 'min.' . $ext; |
| | | if ($fs = @filemtime($minified_file)) { |
| | | if ($fs = @filemtime($this->assets_dir . $minified_file)) { |
| | | return $minified_file . '?s=' . $fs; |
| | | } |
| | | } |
| | | |
| | | if ($fs = @filemtime($file)) { |
| | | if ($fs = @filemtime($this->assets_dir . $file)) { |
| | | $file .= '?s=' . $fs; |
| | | } |
| | | |
| | |
| | | if (!empty($attrib['skin_path'])) $attrib['skinpath'] = $attrib['skin_path']; |
| | | if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) { |
| | | $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin) |
| | | $path = realpath($path); |
| | | $path = realpath(RCUBE_INSTALL_PATH . $path); |
| | | } |
| | | |
| | | if (is_readable($path)) { |
| | |
| | | |
| | | $output = $this->parse_with_globals($this->fix_paths($output)); |
| | | |
| | | if ($this->assets_path) { |
| | | $output = $this->fix_assets_paths($output); |
| | | } |
| | | |
| | | // trigger hook with final HTML content to be sent |
| | | $hook = $this->app->plugins->exec_hook("send_page", array('content' => $output)); |
| | | if (!$hook['abort']) { |
| | |
| | | // register as 'contentframe' object |
| | | if ($is_contentframe || $attrib['contentframe']) { |
| | | $this->set_env('contentframe', $attrib['contentframe'] ? $attrib['contentframe'] : $attrib['name']); |
| | | $this->set_env('blankpage', $attrib['src']); |
| | | $this->set_env('blankpage', $this->asset_url($attrib['src'])); |
| | | } |
| | | |
| | | return html::iframe($attrib); |
| | |
| | | { |
| | | $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY); |
| | | $images = array_map(array($this, 'abs_url'), $images); |
| | | $images = array_map(array($this, 'asset_url'), $images); |
| | | |
| | | if (empty($images) || $this->app->task == 'logout') |
| | | if (empty($images) || $_REQUEST['_task'] == 'logout') { |
| | | return; |
| | | } |
| | | |
| | | $this->add_script('var images = ' . self::json_serialize($images) .'; |
| | | for (var i=0; i<images.length; i++) { |
| | |
| | | */ |
| | | public function raise_error($code, $message) |
| | | { |
| | | if ($code == 403) { |
| | | header('HTTP/1.1 403 Forbidden'); |
| | | die("Invalid Request"); |
| | | } |
| | | |
| | | $this->show_message("Application Error ($code): $message", 'error'); |
| | | $this->remote_response(); |
| | | exit; |
| | |
| | | request_timeout: 180, // seconds |
| | | draft_autosave: 0, // seconds |
| | | comm_path: './', |
| | | blankpage: 'program/resources/blank.gif', |
| | | recipients_separator: ',', |
| | | recipients_delimiter: ', ', |
| | | popup_width: 1150, |
| | |
| | | this.goto_url('error', '_code=0x199'); |
| | | return; |
| | | } |
| | | |
| | | if (!this.env.blankpage) |
| | | this.env.blankpage = this.assets_path('program/resources/blank.gif'); |
| | | |
| | | // find all registered gui containers |
| | | for (n in this.gui_containers) |
| | |
| | | |
| | | if (task == 'mail') |
| | | url += '&_mbox=INBOX'; |
| | | else if (task == 'logout' && !this.env.server_error) |
| | | else if (task == 'logout' && !this.env.server_error) { |
| | | url += '&_token=' + this.env.request_token; |
| | | this.clear_compose_data(); |
| | | } |
| | | |
| | | this.redirect(url); |
| | | }; |
| | |
| | | if (!url) |
| | | url = this.env.comm_path; |
| | | |
| | | return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task); |
| | | if (url.match(/[?&]_task=[a-zA-Z0-9_-]+/)) |
| | | return url.replace(/_task=[a-zA-Z0-9_-]+/, '_task=' + task); |
| | | else |
| | | return url.replace(/\?.*$/, '') + '?_task=' + task; |
| | | }; |
| | | |
| | | this.reload = function(delay) |
| | |
| | | |
| | | img.onload = function() { ref.env.browser_capabilities.tif = 1; }; |
| | | img.onerror = function() { ref.env.browser_capabilities.tif = 0; }; |
| | | img.src = 'program/resources/blank.tif'; |
| | | img.src = this.assets_path('program/resources/blank.tif'); |
| | | }; |
| | | |
| | | this.pdf_support_check = function() |
| | |
| | | return 0; |
| | | }; |
| | | |
| | | this.assets_path = function(path) |
| | | { |
| | | if (this.env.assets_path && !path.startsWith(this.env.assets_path)) { |
| | | path = this.env.assets_path + path; |
| | | } |
| | | |
| | | return path; |
| | | }; |
| | | |
| | | // Cookie setter |
| | | this.set_cookie = function(name, value, expires) |
| | | { |
| | |
| | | function rcube_text_editor(config, id) |
| | | { |
| | | var ref = this, |
| | | abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''), |
| | | conf = { |
| | | selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'), |
| | | cache_suffix: 's=4010700', |
| | | theme: 'modern', |
| | | language: config.lang, |
| | | content_css: 'program/js/tinymce/roundcube/content.css', |
| | | content_css: rcmail.assets_path('program/js/tinymce/roundcube/content.css'), |
| | | menubar: false, |
| | | statusbar: false, |
| | | toolbar_items_size: 'small', |
| | |
| | | toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify' |
| | | + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' |
| | | + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', |
| | | spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1', |
| | | spellchecker_rpc_url: abs_url + '/?_task=utils&_action=spell_html&_remote=1', |
| | | spellchecker_language: rcmail.env.spell_lang, |
| | | accessibility_focus: false, |
| | | file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, |
| | |
| | | */ |
| | | class rcube |
| | | { |
| | | // Init options |
| | | const INIT_WITH_DB = 1; |
| | | const INIT_WITH_PLUGINS = 2; |
| | | |
| | | // Request status |
| | | const REQUEST_VALID = 0; |
| | | const REQUEST_ERROR_URL = 1; |
| | | const REQUEST_ERROR_TOKEN = 2; |
| | | |
| | | /** |
| | | * Singleton instace of rcube |
| | |
| | | */ |
| | | public $user; |
| | | |
| | | /** |
| | | * Request status |
| | | * |
| | | * @var int |
| | | */ |
| | | public $request_status = 0; |
| | | |
| | | /* private/protected vars */ |
| | | protected $texts; |
| | |
| | | |
| | | |
| | | /** |
| | | * Returns session token for secure URLs |
| | | * |
| | | * @param bool $generate Generate token if not exists in session yet |
| | | * |
| | | * @return string|bool Token string, False when disabled |
| | | */ |
| | | public function get_secure_url_token($generate = false) |
| | | { |
| | | if ($len = $this->config->get('use_secure_urls')) { |
| | | if (empty($_SESSION['secure_token']) && $generate) { |
| | | // generate x characters long token |
| | | $length = $len > 1 ? $len : 16; |
| | | $token = openssl_random_pseudo_bytes($length / 2); |
| | | $token = bin2hex($token); |
| | | |
| | | $plugin = $this->plugins->exec_hook('secure_token', |
| | | array('value' => $token, 'length' => $length)); |
| | | |
| | | $_SESSION['secure_token'] = $plugin['value']; |
| | | } |
| | | |
| | | return $_SESSION['secure_token']; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Generate a unique token to be used in a form request |
| | | * |
| | | * @return string The request token |
| | | */ |
| | | public function get_request_token() |
| | | { |
| | | $sess_id = $_COOKIE[ini_get('session.name')]; |
| | | if (!$sess_id) { |
| | | $sess_id = session_id(); |
| | | } |
| | | |
| | | $plugin = $this->plugins->exec_hook('request_token', array( |
| | | 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); |
| | | |
| | | return $plugin['value']; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Check if the current request contains a valid token. |
| | | * Empty requests aren't checked until use_secure_urls is set. |
| | | * |
| | | * @param int Request method |
| | | * |
| | | * @return boolean True if request token is valid false if not |
| | | */ |
| | | public function check_request($mode = rcube_utils::INPUT_POST) |
| | | { |
| | | // check secure token in URL if enabled |
| | | if ($token = $this->get_secure_url_token()) { |
| | | foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { |
| | | if ($tok == $token) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | $this->request_status = self::REQUEST_ERROR_URL; |
| | | |
| | | return false; |
| | | } |
| | | |
| | | $sess_tok = $this->get_request_token(); |
| | | |
| | | // ajax requests |
| | | if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { |
| | | return true; |
| | | } |
| | | |
| | | // skip empty requests |
| | | if (($mode == rcube_utils::INPUT_POST && empty($_POST)) |
| | | || ($mode == rcube_utils::INPUT_GET && empty($_GET)) |
| | | ) { |
| | | return true; |
| | | } |
| | | |
| | | // default method of securing requests |
| | | $token = rcube_utils::get_input_value('_token', $mode); |
| | | $sess_id = $_COOKIE[ini_get('session.name')]; |
| | | |
| | | if (empty($sess_id) || $token != $sess_tok) { |
| | | $this->request_status = self::REQUEST_ERROR_TOKEN; |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Build a valid URL to this instance of Roundcube |
| | | * |
| | | * @param mixed Either a string with the action or url parameters as key-value pairs |
| | |
| | | */ |
| | | |
| | | // process ajax requests only |
| | | if (!$OUTPUT->ajax_call) |
| | | if (!$OUTPUT->ajax_call) { |
| | | return; |
| | | } |
| | | |
| | | $cids = rcmail_get_cids(); |
| | | $cids = rcmail_get_cids(null, rcube_utils::INPUT_POST); |
| | | $delcnt = 0; |
| | | |
| | | // remove previous deletes |
| | |
| | | if ($result = $CONTACTS->get_result()) |
| | | $record = $result->first(); |
| | | |
| | | $photo_img = $attrib['placeholder'] ? $RCMAIL->output->get_skin_file($attrib['placeholder']) : 'program/resources/blank.gif'; |
| | | $photo_img = $attrib['placeholder'] ? $RCMAIL->output->abs_url($attrib['placeholder'], true) : 'program/resources/blank.gif'; |
| | | if ($record['_type'] == 'group' && $attrib['placeholdergroup']) |
| | | $photo_img = $RCMAIL->output->get_skin_file($attrib['placeholdergroup']); |
| | | $photo_img = $RCMAIL->output->abs_url($attrib['placeholdergroup'], true); |
| | | |
| | | $RCMAIL->output->set_env('photo_placeholder', $photo_img); |
| | | $RCMAIL->output->set_env('photo_placeholder', $RCMAIL->output->asset_url($photo_img)); |
| | | |
| | | unset($attrib['placeholder']); |
| | | |
| | | $plugin = $RCMAIL->plugins->exec_hook('contact_photo', array('record' => $record, 'data' => $record['photo'])); |
| | |
| | | * |
| | | * @return array List of contact IDs per-source |
| | | */ |
| | | function rcmail_get_cids($filter = null) |
| | | function rcmail_get_cids($filter = null, $request_type = rcube_utils::INPUT_GPC) |
| | | { |
| | | // contact ID (or comma-separated list of IDs) is provided in two |
| | | // forms. If _source is an empty string then the ID is a string |
| | | // containing contact ID and source name in form: <ID>-<SOURCE> |
| | | |
| | | $cid = rcube_utils::get_input_value('_cid', rcube_utils::INPUT_GPC); |
| | | $cid = rcube_utils::get_input_value('_cid', $request_type); |
| | | $source = (string) rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); |
| | | |
| | | if (is_array($cid)) { |
| | |
| | | $RCMAIL->output->future_expire_header(86400); |
| | | } |
| | | |
| | | if ($data) { |
| | | header('Content-Type: ' . rcube_mime::image_content_type($data)); |
| | | echo $data ? $data : file_get_contents('program/resources/blank.gif'); |
| | | echo $data; |
| | | } |
| | | else { |
| | | header('Content-Type: image/gif'); |
| | | echo base64_decode(rcmail_output::BLANK_GIF); |
| | | } |
| | | exit; |
| | |
| | | "googie.setCurrentLanguage('%s');\n". |
| | | "googie.setDecoration(false);\n". |
| | | "googie.decorateTextarea('%s');\n", |
| | | $RCMAIL->output->get_skin_path(), |
| | | $RCMAIL->output->asset_url($RCMAIL->output->get_skin_path()), |
| | | $RCMAIL->url(array('_task' => 'utils', '_action' => 'spell', '_remote' => 1)), |
| | | !empty($dictionary) ? 'true' : 'false', |
| | | rcube::JQ(rcube::Q($RCMAIL->gettext('checkspelling'))), |
| | |
| | | { |
| | | global $RCMAIL, $MESSAGE; |
| | | |
| | | $placeholder = $attrib['placeholder'] ? $RCMAIL->config->get('skin_path') . $attrib['placeholder'] : null; |
| | | $placeholder = $attrib['placeholder'] ? $RCMAIL->output->abs_url($attrib['placeholder'], true) : null; |
| | | $placeholder = $RCMAIL->output->asset_url($placeholder ? $placeholder : 'program/resources/blank.gif'); |
| | | |
| | | if ($MESSAGE->sender) { |
| | | $photo_img = $RCMAIL->url(array( |
| | | '_task' => 'addressbook', |
| | | '_action' => 'photo', |
| | | '_email' => $MESSAGE->sender['mailto'], |
| | | '_alt' => $placeholder, |
| | | )); |
| | | |
| | | $attrib['onerror'] = "this.src = '" . ($placeholder ? $placeholder : 'program/resources/blank.gif') . "'"; |
| | | $attrib['onerror'] = "this.src = '$placeholder'"; |
| | | } |
| | | else { |
| | | $photo_img = $placeholder ? $placeholder : 'program/resources/blank.gif'; |
| | | $photo_img = $placeholder; |
| | | } |
| | | |
| | | return html::img(array('src' => $photo_img, 'alt' => $RCMAIL->gettext('contactphoto')) + $attrib); |
| | |
| | | $input = new html_radiobutton(array('name'=>'_skin')); |
| | | |
| | | foreach ($skins as $skin) { |
| | | $thumbnail = "./skins/$skin/thumbnail.png"; |
| | | if (!is_file($thumbnail)) |
| | | $thumbnail = './program/resources/blank.gif'; |
| | | |
| | | $thumbnail = "skins/$skin/thumbnail.png"; |
| | | $skinname = ucfirst($skin); |
| | | $author_link = $license_link = ''; |
| | | $meta = @json_decode(@file_get_contents("./skins/$skin/meta.json"), true); |
| | | $meta = @json_decode(@file_get_contents(INSTALL_PATH . "skins/$skin/meta.json"), true); |
| | | |
| | | if (is_array($meta) && $meta['name']) { |
| | | $skinname = $meta['name']; |
| | |
| | | $license_link = $meta['license-url'] ? html::a(array('href' => $meta['license-url'], 'target' => '_blank', 'tabindex' => '-1'), rcube::Q($meta['license'])) : rcube::Q($meta['license']); |
| | | } |
| | | |
| | | $img = html::img(array( |
| | | 'src' => $thumbnail, |
| | | 'class' => 'skinthumbnail', |
| | | 'alt' => $skin, |
| | | 'width' => 64, |
| | | 'height' => 64, |
| | | 'onerror' => "this.src = rcmail.assets_path('program/resources/blank.gif')", |
| | | )); |
| | | |
| | | $skinnames[] = mb_strtolower($skinname); |
| | | $blocks['skin']['options'][$skin]['content'] = html::label(array('class' => 'skinselection'), |
| | | html::span('skinitem', $input->show($config['skin'], array('value' => $skin, 'id' => $field_id.$skin))) . |
| | | html::span('skinitem', html::img(array('src' => $thumbnail, 'class' => 'skinthumbnail', 'alt' => $skin, 'width' => 64, 'height' => 64))) . |
| | | html::span('skinitem', $img) . |
| | | html::span('skinitem', html::span('skinname', rcube::Q($skinname)) . html::br() . |
| | | html::span('skinauthor', $author_link ? 'by ' . $author_link : '') . html::br() . |
| | | html::span('skinlicense', $license_link ? $RCMAIL->gettext('license').': ' . $license_link : '')) |
| | |
| | | |
| | | // forbidden due to request check |
| | | else if ($ERROR_CODE == 403) { |
| | | if ($_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->request_status == rcube::REQUEST_ERROR_URL) { |
| | | parse_str($_SERVER['QUERY_STRING'], $url); |
| | | $url = $rcmail->url($url, true, false, true); |
| | | $add = "<br /><a href=\"$url\">Click here to try again.<a/>"; |
| | | } |
| | | else { |
| | | $add = "Please contact your server-administrator."; |
| | | } |
| | | |
| | | $__error_title = "REQUEST CHECK FAILED"; |
| | | $__error_text = "Access to this service was denied due to failing security checks!<br />\n" |
| | | . "Please contact your server-administrator."; |
| | | $__error_text = "Access to this service was denied due to failing security checks!<br />\n$add"; |
| | | } |
| | | |
| | | // failed request (wrong step in URL) |