Merge branch 'master' into master

This commit is contained in:
RainLoop Team 2019-03-28 01:55:15 +03:00 committed by GitHub
commit 628f8077a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
393 changed files with 74330 additions and 64616 deletions

8
.cmds
View file

@ -6,8 +6,14 @@ yarn upgrade xxx@1.2.3
# transifex # transifex
tx pull -a tx pull -a
# dependencies checker (checking only) # dependencies
yarn outdated yarn outdated
yarn upgrade-interactive --exact --latest
# webpack # webpack
webpack --color --watch webpack --color --watch
#
gpg --import x
gpg --detach-sign --armor --openpgp -u 87DA4591 x
for ff in `ls *.zip`; do gpg --detach-sign --armor --openpgp -u 87DA4591 $ff; done

233
.docker/mail/setup.sh Executable file
View file

@ -0,0 +1,233 @@
#! /bin/sh
##
# Wrapper for various setup scripts included in the docker-mailserver
#
INFO=$(docker ps \
--no-trunc \
--format="{{.Image}}\t{{.Names}}\t{{.Command}}" | \
grep "/bin/sh -c 'supervisord -c /etc/supervisor/supervisord.conf'")
IMAGE_NAME=$(echo $INFO | awk '{print $1}')
CONTAINER_NAME=$(echo $INFO | awk '{print $2}')
CONFIG_PATH="$(pwd)/config"
if [ -z "$IMAGE_NAME" ]; then
IMAGE_NAME=tvial/docker-mailserver:latest
fi
_inspect() {
if _docker_image_exists "$IMAGE_NAME"; then
echo "Image: $IMAGE_NAME"
else
echo "Image: '$IMAGE_NAME' cant be found."
fi
if [ -n "$CONTAINER_NAME" ]; then
echo "Container: $CONTAINER_NAME"
else
echo "Container: Not running, please start docker-mailserver."
fi
}
_usage() {
echo "Usage: $0 [-i IMAGE_NAME] [-c CONTAINER_NAME] <subcommand> <subcommand> [args]
OPTIONS:
-i IMAGE_NAME The name of the docker-mailserver image, by default
'tvial/docker-mailserver:latest'.
-c CONTAINER_NAME The name of the running container.
-p PATH config folder path (default: $(pwd)/config)
SUBCOMMANDS:
email:
$0 email add <email> [<password>]
$0 email update <email> [<password>]
$0 email del <email>
$0 email restrict <add|del|list> <send|receive> [<email>]
$0 email list
alias:
$0 alias add <email> <recipient>
$0 alias del <email> <recipient>
$0 alias list
config:
$0 config dkim
$0 config ssl
debug:
$0 debug fetchmail
$0 debug fail2ban [<unban> <ip-address>]
$0 debug show-mail-logs
$0 debug inspect
$0 debug login <commands>
"
exit 1
}
_docker_image_exists() {
if docker history -q "$1" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
_docker_image() {
if ! _docker_image_exists "$IMAGE_NAME"; then
echo "Image '$IMAGE_NAME' not found. Pulling ..."
docker pull "$IMAGE_NAME"
fi
docker run \
--rm \
-v "$CONFIG_PATH":/tmp/docker-mailserver \
-ti "$IMAGE_NAME" $@
}
_docker_container() {
if [ -n "$CONTAINER_NAME" ]; then
docker exec -ti "$CONTAINER_NAME" $@
else
echo "The docker-mailserver is not running!"
exit 1
fi
}
while getopts ":c:i:p:" OPT; do
case $OPT in
c)
CONTAINER_NAME="$OPTARG"
;;
i)
IMAGE_NAME="$OPTARG"
;;
p)
case "$OPTARG" in
/*)
CONFIG_PATH="$OPTARG"
;;
*)
CONFIG_PATH="$(pwd)/$OPTARG"
;;
esac
if [ ! -d "$CONFIG_PATH" ]; then
echo "Directory doesn't exist"
_usage
exit 1
fi
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
shift $((OPTIND-1))
case $1 in
email)
shift
case $1 in
add)
shift
_docker_image addmailuser $@
;;
update)
shift
_docker_image updatemailuser $@
;;
del)
shift
_docker_image delmailuser $@
;;
restrict)
shift
_docker_container restrict-access $@
;;
list)
_docker_image listmailuser
;;
*)
_usage
;;
esac
;;
alias)
shift
case $1 in
add)
shift
_docker_image addalias $@
;;
del)
shift
_docker_image delalias $@
;;
list)
shift
_docker_image listalias $@
;;
*)
_usage
;;
esac
;;
config)
shift
case $1 in
dkim)
_docker_image generate-dkim-config
;;
ssl)
_docker_image generate-ssl-certificate
;;
*)
_usage
;;
esac
;;
debug)
shift
case $1 in
fetchmail)
_docker_image debug-fetchmail
;;
fail2ban)
shift
_docker_container fail2ban $@
;;
show-mail-logs)
_docker_container cat /var/log/mail/mail.log
;;
inspect)
_inspect
;;
login)
shift
if [ -z "$1" ]; then
_docker_container /bin/bash
else
_docker_container /bin/bash -c "$@"
fi
;;
*)
_usage
;;
esac
;;
*)
_usage
;;
esac

View file

@ -0,0 +1,51 @@
server {
listen 80 default;
listen 443 ssl;
server_name localhost _;
root /var/www;
index index.php index.html;
autoindex on;
charset utf-8;
client_max_body_size 500m;
ssl_certificate /etc/nginx/ssl/localhost.cert;
ssl_certificate_key /etc/nginx/ssl/localhost.key;
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
gzip_vary on;
access_log /var/log/nginx/default.access.log;
error_log /var/log/nginx/default.error.log;
location = /favicon.ico { access_log off; log_not_found off; }
location ~* favicon\.(ico|png)$ { access_log off; log_not_found off; }
location = /browserconfig.xml { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location = /humans.txt { access_log off; log_not_found off; }
location = /apple-touch-icon.png { access_log off; log_not_found off; }
location = /apple-touch-icon-precomposed.png { access_log off; log_not_found off; }
location ~ /\.ht { deny all; return 404; }
location ~ /\.git { deny all; return 404; }
location ~ /\.svn { deny all; return 404; }
location ~* ^.+\.(?:jpe?g|gif|bmp|ico|png|css|js|swf)$ {
expires 30d;
access_log off;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
location ~ \.php(/|$) {
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
fastcgi_pass php:9000;
}
}

41
.docker/nginx/ssl.sh Normal file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
SERVER_NAME=localhost
SUBJECT="/C=RU/ST=RND/L=Taganrog/O=Umbrella Web/CN=${SERVER_NAME}"
mkdir -p ./ssl
if [ -f ./ssl/${SERVER_NAME}.cert ]; then
rm -rf ./ssl/${SERVER_NAME}.cert
fi
if [ -f ./ssl/${SERVER_NAME}.key ]; then
rm -rf ./ssl/${SERVER_NAME}.key
fi
# Generating ROOT pem files
openssl req -x509 -new -nodes -newkey rsa:2048 -keyout ./ssl/server_rootCA.key -sha256 -days 1024 -out ./ssl/server_rootCA.pem -subj "${SUBJECT}" 2> /dev/null
# Generating v3.ext file
cat <<EOF > ./ssl/v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${SERVER_NAME}
DNS.2 = www.${SERVER_NAME}
EOF
echo " - Generating SSL key file"
openssl req -new -newkey rsa:2048 -sha256 -nodes -newkey rsa:2048 -keyout ./ssl/${SERVER_NAME}.key -subj "${SUBJECT}" -out ./ssl/server_rootCA.csr 2> /dev/null
echo " - Generating SSL certificate file"
openssl x509 -req -in ./ssl/server_rootCA.csr -CA ./ssl/server_rootCA.pem -CAkey ./ssl/server_rootCA.key -CAcreateserial -out ./ssl/${SERVER_NAME}.cert -days 3650 -sha256 -extfile ./ssl/v3.ext 2> /dev/null
# echo " - Adding certificate into local keychain"
# sudo security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" ./ssl/server_rootCA.pem 2> /dev/null
echo " - Runing garbage collector"
rm -rf ./ssl/server_rootCA.csr ./ssl/server_rootCA.key ./ssl/server_rootCA.pem ./ssl/v3.ext ./.srl

View file

6
.docker/node/Dockerfile Normal file
View file

@ -0,0 +1,6 @@
FROM node:9.11.2-alpine
RUN apk add --no-cache git
RUN yarn global add gulp@3.9.1
CMD ["node", "--version"]

30
.docker/php/Dockerfile Normal file
View file

@ -0,0 +1,30 @@
FROM php:7.3-fpm
RUN apt-get update
RUN apt-get install -y \
git unzip wget zip curl mlocate \
libmcrypt-dev libicu-dev libpcre3-dev libicu-dev \
build-essential chrpath libssl-dev \
libxft-dev libfreetype6 libfreetype6-dev \
libpng-dev libjpeg62-turbo-dev \
libfontconfig1 libfontconfig1-dev libzip-dev
RUN pecl install mcrypt-1.0.2 && \
docker-php-ext-enable mcrypt
RUN docker-php-ext-configure intl && \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
docker-php-ext-install opcache pdo_mysql zip intl gd
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN curl --location --output /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar && chmod +x /usr/local/bin/phpunit
RUN apt-get -y autoremove && apt-get clean
RUN sed -i '/^;catch_workers_output/ccatch_workers_output = yes' '/usr/local/etc/php-fpm.d/www.conf'
EXPOSE 9000
CMD ["php-fpm"]

8
.docker/php/rainloop.ini Normal file
View file

@ -0,0 +1,8 @@
date.timezone = UTC
upload_max_filesize = 1G
post_max_size = 1G
# log_errors = On
# display_errors = On
# error_reporting = E_ALL
# error_log = /dev/stderr

5
.docker/tx/Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM python:3.6-alpine
RUN pip install transifex-client
CMD ["tx", "--version"]

View file

@ -11,7 +11,7 @@ module.exports = {
'commonjs': true, 'commonjs': true,
'es6': true 'es6': true
}, },
'plugins': ['compat'], // 'plugins': ['compat'],
'globals': { 'globals': {
'RL_COMMUNITY': true, 'RL_COMMUNITY': true,
'RL_ES6': true 'RL_ES6': true
@ -20,11 +20,11 @@ module.exports = {
// http://eslint.org/docs/rules/ // http://eslint.org/docs/rules/
'rules': { 'rules': {
// plugins // plugins
'compat/compat': 2, // 'compat/compat': 2,
// errors // errors
'no-cond-assign': [2, 'always'], 'no-cond-assign': [2, 'always'],
'no-console': 2, 'no-console': 0,
'no-constant-condition': 2, 'no-constant-condition': 2,
'no-control-regex': 2, 'no-control-regex': 2,
'no-debugger': 2, 'no-debugger': 2,

6
.gitignore vendored
View file

@ -18,6 +18,12 @@
/build/local /build/local
/build/dist /build/dist
/build/tmp /build/tmp
/build/docker
/.docker/.cache
/.docker/mail/config
/.docker/nginx/ssl/*
!/.docker/nginx/ssl/.gitempty
/dist
/data /data
.DS_Store .DS_Store
/tests/fix.php /tests/fix.php

View file

@ -1,16 +0,0 @@
filter:
path:
- 'rainloop-webmail/rainloop/v/0.0.0/app/libraries/MailSo/*'
- 'rainloop-webmail/rainloop/v/0.0.0/app/libraries/RainLoop/*'
- 'dev/*'
- '/*.js'
excluded_paths:
- '*.min.js'
- '*/min/*'
- '*.min.css'
- 'vendors/*'
- 'build/*'
- 'data/*'
checks:
javascript: true
php: true

View file

@ -1,13 +0,0 @@
language: php
php:
- '5.4'
- '5.5'
- '5.6'
# - '7.0'
# - '7.1'
#before_script:
# - sudo apt-get install -y build-essential libssl-dev
# - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash
# - nvm install 4.4.5
# - nvm use 4.4.5
# - npm install -g eslint babel-eslint

View file

@ -1,7 +1,7 @@
[main] [main]
host = https://www.transifex.com host = https://www.transifex.com
type = YAML
minimum_perc = 60 minimum_perc = 60
type = YAML
[rainloop-webmail.rainloop-webmail] [rainloop-webmail.rainloop-webmail]
file_filter = rainloop/v/0.0.0/app/localization/webmail/<lang>.yml file_filter = rainloop/v/0.0.0/app/localization/webmail/<lang>.yml
@ -12,3 +12,4 @@ source_lang = en
file_filter = rainloop/v/0.0.0/app/localization/admin/<lang>.yml file_filter = rainloop/v/0.0.0/app/localization/admin/<lang>.yml
source_file = rainloop/v/0.0.0/app/localization/admin/_source.en.yml source_file = rainloop/v/0.0.0/app/localization/admin/_source.en.yml
source_lang = en source_lang = en

74
Makefile Normal file
View file

@ -0,0 +1,74 @@
#!make
rebuild: _down
docker-compose build --no-cache
up: _up status
_up:
docker-compose up -d
stop: _stop status
_stop:
docker-compose stop
down: _down status
_down:
docker-compose down
restart: _stop _up status
status:
@docker-compose ps
tx:
@docker-compose run --no-deps --rm tx tx pull -a -s -f -d
console-node:
@docker-compose run --no-deps --rm node sh
console-tx:
@docker-compose run --no-deps --rm tx sh
console-php:
@docker-compose exec php sh
console: console-node
logs:
@docker-compose logs --tail=100 -f
logs-db:
@docker-compose logs --tail=100 -f db
logs-php:
@docker-compose logs --tail=100 -f php
logs-node:
@docker-compose logs --tail=100 -f node
logs-nginx:
@docker-compose logs --tail=100 -f nginx
logs-mail:
@docker-compose logs --tail=100 -f mail
logs-tx:
@docker-compose logs --tail=100 -f tx
rl-lint:
@docker-compose run --no-deps --rm node gulp lint
rl-dev:
@docker-compose run --no-deps --rm node gulp
rl-watch-css:
@docker-compose run --no-deps --rm node npm run watch-css
rl-watch-js:
@docker-compose run --no-deps --rm node npm run watch-js
rl-build:
@docker-compose run --no-deps --rm node gulp all
rl-build-pro:
@docker-compose run --no-deps --rm node gulp all --pro
yarn-install:
@docker-compose run --no-deps --rm node yarn install
yarn-outdated:
@docker-compose run --no-deps --rm node yarn outdated
yarn-upgrade:
@docker-compose run --no-deps --rm node yarn upgrade-interactive --exact --latest
gpg:
docker run -it --rm -w=/var/www \
-v $(shell pwd)/.docker/.cache/.gnupg:/root/.gnupg \
-v $(shell pwd):/var/www \
ubuntu:latest bash

View file

@ -26,4 +26,4 @@ Information about installing the product, check the [documentation page](http://
**GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL)**. **GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL)**.
http://www.gnu.org/licenses/agpl-3.0.html http://www.gnu.org/licenses/agpl-3.0.html
Copyright (c) 2017 Rainloop Team Copyright (c) 2018 Rainloop Team

BIN
assets/favicon-light.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/favicon-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

View file

@ -3,7 +3,7 @@
* ownCloud/Nextcloud - RainLoop Webmail package * ownCloud/Nextcloud - RainLoop Webmail package
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team * @copyright 2018 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
* *

View file

@ -4,7 +4,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -4,7 +4,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -4,7 +4,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -4,7 +4,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/owncloud * https://github.com/RainLoop/owncloud
*/ */

View file

@ -4,7 +4,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -3,7 +3,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -3,7 +3,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -63,25 +63,115 @@ class OC_RainLoop_Helper
return $sUrl; return $sUrl;
} }
/**
* @return boolean
*/
public static function mcryptSupported()
{
return function_exists('mcrypt_encrypt') &&
function_exists('mcrypt_decrypt') &&
defined('MCRYPT_RIJNDAEL_256') &&
defined('MCRYPT_MODE_ECB');
}
/**
* @return string
*/
public static function openSslSupportedMethod()
{
$method = 'AES-256-CBC';
return function_exists('openssl_encrypt') &&
function_exists('openssl_decrypt') &&
function_exists('openssl_random_pseudo_bytes') &&
function_exists('openssl_cipher_iv_length') &&
function_exists('openssl_get_cipher_methods') &&
defined('OPENSSL_RAW_DATA') && defined('OPENSSL_ZERO_PADDING') &&
in_array($method, openssl_get_cipher_methods()) ? $method : '';
}
/**
* @param string $sMethod
* @param string $sPassword
* @param string $sSalt
*
* @return string
*/
public static function encodePasswordSsl($sMethod, $sPassword, $sSalt)
{
$sData = base64_encode($sPassword);
$iv = @openssl_random_pseudo_bytes(openssl_cipher_iv_length($sMethod));
$r = @openssl_encrypt($sData, $sMethod, md5($sSalt), OPENSSL_RAW_DATA, $iv);
return @base64_encode(base64_encode($r).'|'.base64_encode($iv));
}
/**
* @param string $sMethod
* @param string $sPassword
* @param string $sSalt
*
* @return string
*/
public static function decodePasswordSsl($sMethod, $sPassword, $sSalt)
{
$sLine = base64_decode(trim($sPassword));
$aParts = explode('|', $sLine, 2);
if (is_array($aParts) && !empty($aParts[0]) && !empty($aParts[1])) {
$sData = @base64_decode($aParts[0]);
$iv = @base64_decode($aParts[1]);
return @base64_decode(trim(
@openssl_decrypt($sData, $sMethod, md5($sSalt), OPENSSL_RAW_DATA, $iv)
));
}
return '';
}
/**
* @param string $sPassword
* @param string $sSalt
*
* @return string
*/
public static function encodePassword($sPassword, $sSalt) public static function encodePassword($sPassword, $sSalt)
{ {
if (function_exists('mcrypt_encrypt') && function_exists('mcrypt_create_iv') && function_exists('mcrypt_get_iv_size') && $method = self::openSslSupportedMethod();
defined('MCRYPT_RIJNDAEL_256') && defined('MCRYPT_MODE_ECB') && defined('MCRYPT_RAND')) if ($method)
{ {
return @trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_encode($sPassword), return self::encodePasswordSsl($method, $sPassword, $sSalt);
MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)))); }
else if (self::mcryptSupported())
{
return @trim(base64_encode(
@mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_encode($sPassword), MCRYPT_MODE_ECB)
));
} }
return @trim(base64_encode($sPassword)); return @trim(base64_encode($sPassword));
} }
/**
* @param string $sPassword
* @param string $sSalt
*
* @return string
*/
public static function decodePassword($sPassword, $sSalt) public static function decodePassword($sPassword, $sSalt)
{ {
if (function_exists('mcrypt_encrypt') && function_exists('mcrypt_create_iv') && function_exists('mcrypt_get_iv_size') && $method = self::openSslSupportedMethod();
defined('MCRYPT_RIJNDAEL_256') && defined('MCRYPT_MODE_ECB') && defined('MCRYPT_RAND')) if ($method)
{ {
return @base64_decode(trim(@mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_decode(trim($sPassword)), return self::decodePasswordSsl($method, $sPassword, $sSalt);
MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)))); }
else if (self::mcryptSupported())
{
return @base64_decode(trim(
@mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($sSalt), base64_decode(trim($sPassword)), MCRYPT_MODE_ECB)
));
} }
return @base64_decode(trim($sPassword)); return @base64_decode(trim($sPassword));

View file

@ -4,7 +4,6 @@
* ownCloud - RainLoop mail plugin * ownCloud - RainLoop mail plugin
* *
* @author RainLoop Team * @author RainLoop Team
* @copyright 2017 RainLoop Team
* *
* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud
*/ */

View file

@ -1,122 +0,0 @@
<project name="Build" basedir=".">
<property name="INDEX_ROOT" value=".." />
<property name="DIST_PATH" value="dist" />
<property name="DIST_PLUGINS_PATH" value="dist/plugins" />
<property name="GUID" value="tmp" />
<target name="_pre_">
<mkdir dir="${DIST_PATH}" />
<mkdir dir="${DIST_PLUGINS_PATH}" />
</target>
<target name="_build_plugin_" depends="_pre_">
<loadfile property="plugin-version" srcfile="${INDEX_ROOT}/plugins/${plugin-name}/VERSION" />
<mkdir dir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}" />
<mkdir dir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}/${plugin-name}" />
<copy todir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}/${plugin-name}">
<fileset dir="${INDEX_ROOT}/plugins/${plugin-name}" />
</copy>
<zip destfile="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}.zip"
basedir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}" update="true" />
<delete dir="${DIST_PLUGINS_PATH}/${plugin-name}-${plugin-version}-${GUID}" />
</target>
<target name="add-x-originating-ip-header">
<antcall target="_build_plugin_">
<param name="plugin-name" value="add-x-originating-ip-header"/>
</antcall>
</target>
<target name="google-analytics">
<antcall target="_build_plugin_">
<param name="plugin-name" value="google-analytics"/>
</antcall>
</target>
<target name="piwik-analytics">
<antcall target="_build_plugin_">
<param name="plugin-name" value="piwik-analytics"/>
</antcall>
</target>
<target name="convert-headers-styles">
<antcall target="_build_plugin_">
<param name="plugin-name" value="convert-headers-styles"/>
</antcall>
</target>
<target name="recaptcha">
<antcall target="_build_plugin_">
<param name="plugin-name" value="recaptcha"/>
</antcall>
</target>
<target name="cpanel-change-password">
<antcall target="_build_plugin_">
<param name="plugin-name" value="cpanel-change-password"/>
</antcall>
</target>
<target name="directadmin-change-password">
<antcall target="_build_plugin_">
<param name="plugin-name" value="directadmin-change-password"/>
</antcall>
</target>
<target name="ispconfig-change-password">
<antcall target="_build_plugin_">
<param name="plugin-name" value="ispconfig-change-password"/>
</antcall>
</target>
<target name="poppassd-change-password">
<antcall target="_build_plugin_">
<param name="plugin-name" value="poppassd-change-password"/>
</antcall>
</target>
<target name="hmailserver-change-password">
<antcall target="_build_plugin_">
<param name="plugin-name" value="hmailserver-change-password"/>
</antcall>
</target>
<target name="ldap-change-password">
<antcall target="_build_plugin_">
<param name="plugin-name" value="ldap-change-password"/>
</antcall>
</target>
<target name="ldap-contacts-suggestions">
<antcall target="_build_plugin_">
<param name="plugin-name" value="ldap-contacts-suggestions"/>
</antcall>
</target>
<target name="snowfall-on-login-screen">
<antcall target="_build_plugin_">
<param name="plugin-name" value="snowfall-on-login-screen"/>
</antcall>
</target>
<target name="black-list">
<antcall target="_build_plugin_">
<param name="plugin-name" value="black-list"/>
</antcall>
</target>
<target name="white-list">
<antcall target="_build_plugin_">
<param name="plugin-name" value="white-list"/>
</antcall>
</target>
<target name="override-smtp-credentials">
<antcall target="_build_plugin_">
<param name="plugin-name" value="override-smtp-credentials"/>
</antcall>
</target>
<target name="custom-login-mapping">
<antcall target="_build_plugin_">
<param name="plugin-name" value="custom-login-mapping"/>
</antcall>
</target>
<target name="video-on-login-screen">
<antcall target="_build_plugin_">
<param name="plugin-name" value="video-on-login-screen"/>
</antcall>
</target>
</project>

View file

@ -1,13 +0,0 @@
<?php
if (!\class_exists('Crypt_RSA'))
{
\set_include_path(\get_include_path().PATH_SEPARATOR.\dirname(__FILE__).'/../rainloop/v/0.0.0/app/libraries/phpseclib');
include_once 'Crypt/RSA.php';
\defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
}
$rsa = new Crypt_RSA();
$key = $rsa->createKey(1024);
var_dump($key);

View file

@ -0,0 +1,30 @@
<?php
// simple connection test
$host = 'imap.gmail.com';
$port = 993;
echo $host.':'.$port;
$streamContextSettings = array(
'ssl' => array(
'verify_host' => true,
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false
)
);
$streamContext = stream_context_create($streamContextSettings);
$errorStr = '';
$errorNo = 0;
$connection = stream_socket_client($host.':'.$port, $errorNo, $errorStr, 5, STREAM_CLIENT_CONNECT, $streamContext);
if (is_resource($connection)) {
echo ' = OK';
fclose($connection);
} else {
echo ' = ERROR ([#'.$errorNo.'] '.$errorStr.')';
}

View file

@ -342,6 +342,8 @@ class AbstractApp extends AbstractBoot
if (!mobile) if (!mobile)
{ {
$html.addClass('rl-desktop');
ssm.addState({ ssm.addState({
id: 'mobile', id: 'mobile',
query: '(max-width: 767px)', query: '(max-width: 767px)',

View file

@ -494,7 +494,7 @@ class AppUser extends AbstractApp
if (item.userId) if (item.userId)
{ {
email.clear(); email.clear();
email.mailsoParse(item.userId.userid); email.parse(item.userId.userid);
if (email.validate()) if (email.validate())
{ {
aEmails.push(email.email); aEmails.push(email.email);

View file

@ -7,16 +7,13 @@ import {getHash, setHash, clearHash} from 'Storage/RainLoop';
let RL_APP_DATA_STORAGE = null; let RL_APP_DATA_STORAGE = null;
/* eslint-disable */ /* eslint-disable camelcase,spaced-comment */
window.__rlah = () => getHash(); window.__rlah = () => getHash();
window.__rlah_set = () => setHash(); window.__rlah_set = () => setHash();
window.__rlah_clear = () => clearHash(); window.__rlah_clear = () => clearHash();
window.__rlah_data = () => RL_APP_DATA_STORAGE; window.__rlah_data = () => RL_APP_DATA_STORAGE;
/* eslint-enable */
const useJsNextBundle = (function() { const useJsNextBundle = (function() {
/* eslint-disable */
// try { // try {
// //
// (function() { // (function() {
@ -47,10 +44,9 @@ const useJsNextBundle = (function() {
// return true; // return true;
// } // }
// catch (e) {} // catch (e) {}
return false; return false;
/* eslint-enable */
}()); }());
/* eslint-enable */
/** /**
* @param {string} id * @param {string} id
@ -89,11 +85,12 @@ function includeLayout()
{ {
const app = window.document.getElementById('rl-app'); const app = window.document.getElementById('rl-app');
require('style-loader!Styles/@Boot.css'); require('Styles/@Boot.css');
if (app) if (app)
{ {
app.innerHTML = require('Html/Layout.html').replace(/[\r\n\t]+/g, ''); const layout = require('Html/Layout.html');
app.innerHTML = ((layout && layout.default ? layout.default : layout) || '').replace(/[\r\n\t]+/g, '');
return true; return true;
} }
@ -225,7 +222,7 @@ function runApp()
p.setOptions({theme: 'rainloop'}); p.setOptions({theme: 'rainloop'});
p.start().set(5); p.start().set(5);
const libs = jassl(appData.StaticLibJsLink).then(() => { const libs = () => jassl(appData.StaticLibJsLink).then(() => {
if (window.$) if (window.$)
{ {
window.$('#rl-check').remove(); window.$('#rl-check').remove();
@ -243,12 +240,14 @@ function runApp()
} }
}); });
const common = window.Promise.all([ libs()
.then(() => {
p.set(20);
return window.Promise.all([
jassl(appData.TemplatesLink), jassl(appData.TemplatesLink),
jassl(appData.LangLink) jassl(appData.LangLink)
]); ]);
})
window.Promise.all([libs, common])
.then(() => { .then(() => {
p.set(30); p.set(30);
return jassl(useJsNextBundle ? appData.StaticAppJsNextLink : appData.StaticAppJsLink); return jassl(useJsNextBundle ? appData.StaticAppJsNextLink : appData.StaticAppJsLink);

View file

@ -5,7 +5,7 @@ import _ from '_';
import ko from 'ko'; import ko from 'ko';
import {$body} from 'Common/Globals'; import {$body} from 'Common/Globals';
import {EventKeyCode, Magics} from 'Common/Enums'; import {EventKeyCode, Magics} from 'Common/Enums';
import {trim, inArray, changeTheme} from 'Common/Utils'; import {trim, deModule, inArray, changeTheme} from 'Common/Utils';
import {reload as translatorReload} from 'Common/Translator'; import {reload as translatorReload} from 'Common/Translator';
import * as Settings from 'Storage/Settings'; import * as Settings from 'Storage/Settings';
@ -22,7 +22,7 @@ let
* @returns {string} * @returns {string}
*/ */
function cmdError(cmd) { function cmdError(cmd) {
return require('Html/Cmds/Error.html').replace('{{ cmd }}', cmd); return deModule(require('Html/Cmds/Error.html')).replace('{{ cmd }}', cmd);
} }
/** /**
@ -37,7 +37,7 @@ function cmdClear(dom) {
* @returns {string} * @returns {string}
*/ */
function cmdHelp(cmds) { function cmdHelp(cmds) {
return require('Html/Cmds/Help.html').replace('{{ commands }}', cmds.join(' ')); return deModule(require('Html/Cmds/Help.html')).replace('{{ commands }}', cmds.join(' '));
} }
/** /**
@ -49,7 +49,7 @@ function cmdTheme(param, themes) {
changeTheme(param); changeTheme(param);
return ''; return '';
} }
return require('Html/Cmds/ThemeEmpty.html').replace('{{ themes }}', themes.join(', ')); return deModule(require('Html/Cmds/ThemeEmpty.html')).replace('{{ themes }}', themes.join(', '));
} }
/** /**
@ -61,14 +61,14 @@ function cmdLang(param, isAdmin, langs) {
translatorReload(isAdmin, param); translatorReload(isAdmin, param);
return ''; return '';
} }
return require('Html/Cmds/LangEmpty.html').replace('{{ langs }}', langs.join(', ')); return deModule(require('Html/Cmds/LangEmpty.html')).replace('{{ langs }}', langs.join(', '));
} }
/** /**
* @returns {string} * @returns {string}
*/ */
function cmdVersion() { function cmdVersion() {
return require('Html/Cmds/Version.html').replace('{{ version }}', return deModule(require('Html/Cmds/Version.html')).replace('{{ version }}',
Settings.appSettingsGet('version') + ' (' + Settings.appSettingsGet('appVersionType') + ')'); Settings.appSettingsGet('version') + ' (' + Settings.appSettingsGet('appVersionType') + ')');
} }
@ -231,7 +231,7 @@ class CmdContoller
if (h && h[0]) if (h && h[0])
{ {
h.append($('<div></div>').html(require('Html/Cmds/Main.html').replace('{{ cmd }}', cmdLine))); h.append($('<div></div>').html(deModule(require('Html/Cmds/Main.html')).replace('{{ cmd }}', cmdLine)));
if (result) if (result)
{ {
h.append($('<div></div>').html(result)); h.append($('<div></div>').html(result));

View file

@ -41,3 +41,5 @@ export const RAINLOOP_TRIAL_KEY = 'RAINLOOP-TRIAL-KEY';
export const DATA_IMAGE_USER_DOT_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAHHklEQVRoQ7VZW08bVxCeXRuwIbTGXIwNtBBaqjwgVUiR8lDlbza9qe1DpVZ9aNQ/0KpPeaJK07SpcuEeCEmUAObm21bfrL9lONjexSYrWfbunj37zXdmvpkz9oIgCKTD0Wg0xPd94TDP83Q0zvWa50vzklSrdanVanqf4/D84GBGr+F+Op3S8fqoJxLOdnZgTvsO/nYhenHA+UC7CWF1uXwkb9++ldPTUwVerVbVqFQqpR8YPjQ0JCMjI5LNDijoRgP3PQVu5+5Eor2XGLg7IV4GkIdHJ/LmzRs5ODiIwNbrdR0O0GCcq4Xz4eFhmZyclP7+tDQaIik/BG5XKQn4SwG3zJTLZXn9+rUclI8UHD5YVoDDN8bSzXhONwL48fFxGR4eilzFZT1uFRIB5yT8BqCdnR3Z3d0VP9Un6XRawYJpggVrZBv38ME4XKtUKnLt2jUplUoy1PR/l3U7T6sVSAQcgMAkj8PDQ9ne3pajoyMRL7zeKsYZWHgWYDGmv78/mmdwcFA+mJlSgziHDWrERrsjEXDXegTi1tZW+DLxI2bxIrqFNYTXyDyCFweMAHCwb8e4RnTNuOsqe3t7sra21pTD0Kct666E8XlcZyzw9/RUUXK5nK5oUinUQI6TQ3cynO/v78vq6qrKXCNwlTiJJpyNGc3nZHp6uqV2dwrQWOCtZBDAV1ZWwsQk7f0wiQn5kffbAu/0/KWBYzIC1+XukfGx0RGZmppKlC2tIV0Bh4aDcZW7HhkfH8urLLZL7T2pihvlkMNnz56FiadHxicL41IsFpN41bkxsYxbRdFo9jwB8KdPn14J8KnSpBQKhQs63nPmbCVRcBUAR2Lq1VVmpksyMTFxAXjcEsQybiegESionjx5osCZOeNe1O4+EhCAX7bQSgQcxRHTMgAgcz5+/Dis/hL4uHU3/B4YGNASGHIKxuEql0k+l05AeIAF1vPnz5VxFFmdDlaJrMtZITJeSsXCOTlMunKxjLtMYOKNjQ158eJFuAuKkUOb5sEwgff19SkJUBVkThZUbnXZrtCKBQ6gbnWIkjZpyne3ejAWoGnA7Icz6irvBLgbOMicCM6TkxPx/LAkbXfgWcsazuE2kFRsKD5Z+CiqDumKncpZvieWcS6dDVD8xiYCNflpJdwcdwJOf9airLmVQ7DPzMxIYWLsXGXoVqLt5k0M3K3JUVPDZdbWNzsCp48TPFdvdnZWUz32nDha7bJ63kgAJPzSdRks9/Kf9xMJAQ1gq2NpaUmy2Yz4zar4nQC3xb99AQwCcGzLAAwuhG8YiWvcOKts+r4GOe5nMhm5efOm9lUA3E3vSZJRrKvE0fnPv//Jy5cvo5cTHIPQbSjhOoqq69evS19f6lxDKK4+sVhigZPtKJqbrQeqxd5+WR4+fKgqgT0k2XX3nhiPgETWXFhYkFzuPZ2yVq1GTSOXpE47/VjgNnD4m4GG7/LhsTx69EiwD4Vr2MwIIxgbAH18fKx1yfz8vEogNvGtWnCuhLZa9UTAreVWFsHy/b/+Vrbdl7E5REMQD2jDoUbByty+/ZnU64GkU2HzyJLhktU1cLv8nARgkYS2d3ajAgwG8qU2oLmDZ92CMaOjo7K4uCiZgbDWaRWgnZhPxLhrMUCvr69riwKZk1LHF7XqrWAO9hJxH6ozNzcnCx/PqztZg9mf6SQMscCtm2C5ke4BGMlHWTUp36036AJajDVrFMzBrhhWslQsSrFYiOqVpMriNYIgqFRq2j3FAb/zffT6zuxFXxsNzs3NTXn16lW4gYiW96w1FyedF+83xG/2FNGCRpU4NjamMsn+OZ9xE5RXqdaDdPpib6RWCzuwKF9RxqI2AVNQBwQYJoK0wdBejnqtEikP3pfP51XjUTESl12FqJEKxsEorARYDD44ONTeID7YpsEnrRvQfWAI2e8WfDaTUSIwJ0iBCmFOtOUAHvVMPp/TPwvYFVYFIuP8l+DBgwdaa2Miqwa0GgYwfeMltovbDfh6c1vIgMYcliSsKv4IWFr6VDHxvldvBAH+1sA+cnl5WYOPmmr9ir+1l9I0Cgz0yjhXjfJJ0JROnmezWbl165ayr/5fqwcBNr7IfhjMqKcvESSM4eRcCasQ3bDNObmKPLdGUGpZsN24cUNLBm9zazu4d++e6qpNBFaTuUS26U5dpuR1CxyA7J9ddrMRqlz4pwLLYawymPd++/2PADt2ugcGwq9gCCdhQ96C6xWwa6j1ceuq+I0EhW0i8MAIVJfeL3d/DVD8EKi12P6/2S2jV/EccVB54O/ejz/9HGCpoBBMta5rXMXLu53D1XAwjhXwvvv+h4BAXVe4bOu3O3ChxF08LiZFG3fel199G9CH3fLyqv24NcB44MRhpdK788U3CpyKwsCw590xmfSpzsBt0Fqc3ud3vtZigxWcVZCklVpSiN0w3q5E/h9TGMIUuA3+EQAAAABJRU5ErkJggg=='; export const DATA_IMAGE_USER_DOT_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAHHklEQVRoQ7VZW08bVxCeXRuwIbTGXIwNtBBaqjwgVUiR8lDlbza9qe1DpVZ9aNQ/0KpPeaJK07SpcuEeCEmUAObm21bfrL9lONjexSYrWfbunj37zXdmvpkz9oIgCKTD0Wg0xPd94TDP83Q0zvWa50vzklSrdanVanqf4/D84GBGr+F+Op3S8fqoJxLOdnZgTvsO/nYhenHA+UC7CWF1uXwkb9++ldPTUwVerVbVqFQqpR8YPjQ0JCMjI5LNDijoRgP3PQVu5+5Eor2XGLg7IV4GkIdHJ/LmzRs5ODiIwNbrdR0O0GCcq4Xz4eFhmZyclP7+tDQaIik/BG5XKQn4SwG3zJTLZXn9+rUclI8UHD5YVoDDN8bSzXhONwL48fFxGR4eilzFZT1uFRIB5yT8BqCdnR3Z3d0VP9Un6XRawYJpggVrZBv38ME4XKtUKnLt2jUplUoy1PR/l3U7T6sVSAQcgMAkj8PDQ9ne3pajoyMRL7zeKsYZWHgWYDGmv78/mmdwcFA+mJlSgziHDWrERrsjEXDXegTi1tZW+DLxI2bxIrqFNYTXyDyCFweMAHCwb8e4RnTNuOsqe3t7sra21pTD0Kct666E8XlcZyzw9/RUUXK5nK5oUinUQI6TQ3cynO/v78vq6qrKXCNwlTiJJpyNGc3nZHp6uqV2dwrQWOCtZBDAV1ZWwsQk7f0wiQn5kffbAu/0/KWBYzIC1+XukfGx0RGZmppKlC2tIV0Bh4aDcZW7HhkfH8urLLZL7T2pihvlkMNnz56FiadHxicL41IsFpN41bkxsYxbRdFo9jwB8KdPn14J8KnSpBQKhQs63nPmbCVRcBUAR2Lq1VVmpksyMTFxAXjcEsQybiegESionjx5osCZOeNe1O4+EhCAX7bQSgQcxRHTMgAgcz5+/Dis/hL4uHU3/B4YGNASGHIKxuEql0k+l05AeIAF1vPnz5VxFFmdDlaJrMtZITJeSsXCOTlMunKxjLtMYOKNjQ158eJFuAuKkUOb5sEwgff19SkJUBVkThZUbnXZrtCKBQ6gbnWIkjZpyne3ejAWoGnA7Icz6irvBLgbOMicCM6TkxPx/LAkbXfgWcsazuE2kFRsKD5Z+CiqDumKncpZvieWcS6dDVD8xiYCNflpJdwcdwJOf9airLmVQ7DPzMxIYWLsXGXoVqLt5k0M3K3JUVPDZdbWNzsCp48TPFdvdnZWUz32nDha7bJ63kgAJPzSdRks9/Kf9xMJAQ1gq2NpaUmy2Yz4zar4nQC3xb99AQwCcGzLAAwuhG8YiWvcOKts+r4GOe5nMhm5efOm9lUA3E3vSZJRrKvE0fnPv//Jy5cvo5cTHIPQbSjhOoqq69evS19f6lxDKK4+sVhigZPtKJqbrQeqxd5+WR4+fKgqgT0k2XX3nhiPgETWXFhYkFzuPZ2yVq1GTSOXpE47/VjgNnD4m4GG7/LhsTx69EiwD4Vr2MwIIxgbAH18fKx1yfz8vEogNvGtWnCuhLZa9UTAreVWFsHy/b/+Vrbdl7E5REMQD2jDoUbByty+/ZnU64GkU2HzyJLhktU1cLv8nARgkYS2d3ajAgwG8qU2oLmDZ92CMaOjo7K4uCiZgbDWaRWgnZhPxLhrMUCvr69riwKZk1LHF7XqrWAO9hJxH6ozNzcnCx/PqztZg9mf6SQMscCtm2C5ke4BGMlHWTUp36036AJajDVrFMzBrhhWslQsSrFYiOqVpMriNYIgqFRq2j3FAb/zffT6zuxFXxsNzs3NTXn16lW4gYiW96w1FyedF+83xG/2FNGCRpU4NjamMsn+OZ9xE5RXqdaDdPpib6RWCzuwKF9RxqI2AVNQBwQYJoK0wdBejnqtEikP3pfP51XjUTESl12FqJEKxsEorARYDD44ONTeID7YpsEnrRvQfWAI2e8WfDaTUSIwJ0iBCmFOtOUAHvVMPp/TPwvYFVYFIuP8l+DBgwdaa2Miqwa0GgYwfeMltovbDfh6c1vIgMYcliSsKv4IWFr6VDHxvldvBAH+1sA+cnl5WYOPmmr9ir+1l9I0Cgz0yjhXjfJJ0JROnmezWbl165ayr/5fqwcBNr7IfhjMqKcvESSM4eRcCasQ3bDNObmKPLdGUGpZsN24cUNLBm9zazu4d++e6qpNBFaTuUS26U5dpuR1CxyA7J9ddrMRqlz4pwLLYawymPd++/2PADt2ugcGwq9gCCdhQ96C6xWwa6j1ceuq+I0EhW0i8MAIVJfeL3d/DVD8EKi12P6/2S2jV/EccVB54O/ejz/9HGCpoBBMta5rXMXLu53D1XAwjhXwvvv+h4BAXVe4bOu3O3ChxF08LiZFG3fel199G9CH3fLyqv24NcB44MRhpdK788U3CpyKwsCw590xmfSpzsBt0Fqc3ud3vtZigxWcVZCklVpSiN0w3q5E/h9TGMIUuA3+EQAAAABJRU5ErkJggg==';
export const DATA_IMAGE_TRANSP_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII='; export const DATA_IMAGE_TRANSP_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=';
export const DATA_IMAGE_LAZY_PLACEHOLDER_PIC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC';

View file

@ -210,7 +210,8 @@ export const ClientSideKeyName = {
'LastReplyAction': 6, 'LastReplyAction': 6,
'LastSignMe': 7, 'LastSignMe': 7,
'ComposeLastIdentityID': 8, 'ComposeLastIdentityID': 8,
'MessageHeaderFullInfo': 9 'MessageHeaderFullInfo': 9,
'MessageAttachmnetControls': 10
}; };
/** /**

View file

@ -10,7 +10,6 @@ const
SUB_QUERY_PREFIX = '&q[]=', SUB_QUERY_PREFIX = '&q[]=',
VERSION = Settings.appSettingsGet('version'), VERSION = Settings.appSettingsGet('version'),
IS_MOBILE = Settings.appSettingsGet('mobile'),
WEB_PREFIX = Settings.appSettingsGet('webPath') || '', WEB_PREFIX = Settings.appSettingsGet('webPath') || '',
VERSION_PREFIX = Settings.appSettingsGet('webVersionPath') || 'rainloop/v/' + VERSION + '/', VERSION_PREFIX = Settings.appSettingsGet('webVersionPath') || 'rainloop/v/' + VERSION + '/',
@ -59,7 +58,7 @@ export function rootAdmin()
*/ */
export function rootUser() export function rootUser()
{ {
return IS_MOBILE ? SERVER_PREFIX + '/Mobile/' : ROOT; return ROOT;
} }
/** /**
@ -171,7 +170,7 @@ export function append()
*/ */
export function change(email) export function change(email)
{ {
return serverRequest('Change' + (IS_MOBILE ? 'Mobile' : '')) + encodeURIComponent(email) + '/'; return serverRequest('Change') + encodeURIComponent(email) + '/';
} }
/** /**

View file

@ -254,6 +254,14 @@ const timeOutActionSecond = (function() {
export {timeOutAction, timeOutActionSecond}; export {timeOutAction, timeOutActionSecond};
/**
* @param {any} m
* @returns {any}
*/
export function deModule(m) {
return (m && m.default ? m.default : m) || '';
}
/** /**
* @returns {boolean} * @returns {boolean}
*/ */
@ -629,7 +637,7 @@ export function previewMessage({title, subject, date, fromCreds, toCreds, toLabe
const html = bodyClone ? bodyClone.html() : ''; const html = bodyClone ? bodyClone.html() : '';
doc.write(require('Html/PreviewMessage.html') doc.write(deModule(require('Html/PreviewMessage.html'))
.replace('{{title}}', encodeHtml(title)) .replace('{{title}}', encodeHtml(title))
.replace('{{subject}}', encodeHtml(subject)) .replace('{{subject}}', encodeHtml(subject))
.replace('{{date}}', encodeHtml(date)) .replace('{{date}}', encodeHtml(date))
@ -736,7 +744,6 @@ export function settingsSaveHelperSubscribeFunction(remote, settingName, type, f
*/ */
export function findEmailAndLinks(html) export function findEmailAndLinks(html)
{ {
// return html;
return Autolinker ? Autolinker.link(html, { return Autolinker ? Autolinker.link(html, {
newWindow: true, newWindow: true,
stripPrefix: false, stripPrefix: false,
@ -1455,6 +1462,15 @@ export function mimeContentType(fileName)
return result; return result;
} }
/**
* @param {string} color
* @returns {boolean}
*/
export function isTransparent(color)
{
return 'rgba(0, 0, 0, 0)' === color || 'transparent' === color;
}
/** /**
* @param {Object} $el * @param {Object} $el
* @returns {number} * @returns {number}
@ -1508,14 +1524,14 @@ export function resizeAndCrop(url, value, fCallback)
/** /**
* @param {string} mailToUrl * @param {string} mailToUrl
* @param {Function} PopupComposeVoreModel * @param {Function} PopupComposeViewModel
* @returns {boolean} * @returns {boolean}
*/ */
export function mailToHelper(mailToUrl, PopupComposeVoreModel) export function mailToHelper(mailToUrl, PopupComposeViewModel)
{ {
if (mailToUrl && 'mailto:' === mailToUrl.toString().substr(0, 7).toLowerCase()) if (mailToUrl && 'mailto:' === mailToUrl.toString().substr(0, 7).toLowerCase())
{ {
if (!PopupComposeVoreModel) if (!PopupComposeViewModel)
{ {
return true; return true;
} }
@ -1531,28 +1547,47 @@ export function mailToHelper(mailToUrl, PopupComposeVoreModel)
const const
email = mailToUrl.replace(/\?.+$/, ''), email = mailToUrl.replace(/\?.+$/, ''),
query = mailToUrl.replace(/^[^\?]*\?/, ''), query = mailToUrl.replace(/^[^\?]*\?/, ''),
EmailModel = require('Model/Email').default, EmailModel = require('Model/Email').default;
emailObj = new EmailModel(),
fParseEmailLine = (line) => (line ? _.compact(_.map(decodeURIComponent(line).split(/[,]/), (item) => {
emailObj.clear();
emailObj.mailsoParse(item);
return '' !== emailObj.email ? emailObj : null;
})) : null);
to = fParseEmailLine(email);
params = simpleQueryParser(query); params = simpleQueryParser(query);
if (!isUnd(params.to))
{
to = EmailModel.parseEmailLine(decodeURIComponent(email + ',' + params.to));
to = _.values(to.reduce((result, value) => {
if (value)
{
if (result[value.email])
{
if (!result[value.email].name)
{
result[value.email] = value;
}
}
else
{
result[value.email] = value;
}
}
return result;
}, {}));
}
else
{
to = EmailModel.parseEmailLine(email);
}
if (!isUnd(params.cc)) if (!isUnd(params.cc))
{ {
cc = fParseEmailLine(decodeURIComponent(params.cc)); cc = EmailModel.parseEmailLine(decodeURIComponent(params.cc));
} }
if (!isUnd(params.bcc)) if (!isUnd(params.bcc))
{ {
bcc = fParseEmailLine(decodeURIComponent(params.bcc)); bcc = EmailModel.parseEmailLine(decodeURIComponent(params.bcc));
} }
require('Knoin/Knoin').showScreenPopup(PopupComposeVoreModel, [ require('Knoin/Knoin').showScreenPopup(PopupComposeViewModel, [
ComposeType.Empty, null, to, cc, bcc, ComposeType.Empty, null, to, cc, bcc,
isUnd(params.subject) ? null : pString(decodeURIComponent(params.subject)), isUnd(params.subject) ? null : pString(decodeURIComponent(params.subject)),
isUnd(params.body) ? null : plainToHtml(pString(decodeURIComponent(params.body))) isUnd(params.body) ? null : plainToHtml(pString(decodeURIComponent(params.body)))

49
dev/External/ko.js vendored
View file

@ -158,6 +158,7 @@ ko.bindingHandlers.tooltip = {
$el = $(element), $el = $(element),
fValue = fValueAccessor(), fValue = fValueAccessor(),
isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'), isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'),
isI18N = 'on' === ($el.data('tooltip-i18n') || 'on'),
Globals = require('Common/Globals'); Globals = require('Common/Globals');
if (!Globals.bMobileDevice || isMobile) if (!Globals.bMobileDevice || isMobile)
@ -187,7 +188,7 @@ ko.bindingHandlers.tooltip = {
element.__opentip.activate(); element.__opentip.activate();
} }
if ('on' === ($el.data('tooltip-i18n') || 'on')) if (isI18N)
{ {
const Translator = require('Common/Translator'); const Translator = require('Common/Translator');
@ -225,6 +226,7 @@ ko.bindingHandlers.tooltip = {
$el = $(element), $el = $(element),
fValue = fValueAccessor(), fValue = fValueAccessor(),
isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'), isMobile = 'on' === ($el.data('tooltip-mobile') || 'off'),
isI18N = 'on' === ($el.data('tooltip-i18n') || 'on'),
Globals = require('Common/Globals'); Globals = require('Common/Globals');
if ((!Globals.bMobileDevice || isMobile) && element.__opentip) if ((!Globals.bMobileDevice || isMobile) && element.__opentip)
@ -232,9 +234,7 @@ ko.bindingHandlers.tooltip = {
const sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue); const sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
if (sValue) if (sValue)
{ {
element.__opentip.setContent('on' === ($el.data('tooltip-i18n') || 'on') ? element.__opentip.setContent(isI18N ? require('Common/Translator').i18n(sValue) : sValue);
require('Common/Translator').i18n(sValue) : sValue);
element.__opentip.activate(); element.__opentip.activate();
} }
else else
@ -884,6 +884,7 @@ ko.bindingHandlers.emailsTags = {
fValue = fValueAccessor(), fValue = fValueAccessor(),
fAllBindings = fAllBindingsAccessor(), fAllBindings = fAllBindingsAccessor(),
fAutoCompleteSource = fAllBindings.autoCompleteSource || null, fAutoCompleteSource = fAllBindings.autoCompleteSource || null,
inputDelimiters = [',', ';', '\n'],
fFocusCallback = (value) => { fFocusCallback = (value) => {
if (fValue && fValue.focused) if (fValue && fValue.focused)
{ {
@ -895,26 +896,26 @@ ko.bindingHandlers.emailsTags = {
parseOnBlur: true, parseOnBlur: true,
allowDragAndDrop: true, allowDragAndDrop: true,
focusCallback: fFocusCallback, focusCallback: fFocusCallback,
inputDelimiters: [',', ';', '\n'], inputDelimiters: inputDelimiters,
autoCompleteSource: fAutoCompleteSource, autoCompleteSource: fAutoCompleteSource,
// elementHook: (el, item) => { splitHook: (value) => {
// if (el && item) const v = Utils.trim(value);
// { if (v && -1 < inputDelimiters.indexOf(v.substr(-1))) {
// el.addClass('pgp'); return EmailModel.splitEmailLine(value);
// }
// },
parseHook: (input) => _.map(input, (inputValue) => {
const value = Utils.trim(inputValue);
if ('' !== value)
{
const email = new EmailModel();
email.mailsoParse(value);
return [email.toLine(false), email];
} }
return [value, null]; return null;
},
}), parseHook: (input) => _.map(
'change': (event) => { _.flatten(_.map(
input,
(inputValue) => {
const values = EmailModel.parseEmailLine(inputValue);
return values.length ? values : inputValue;
}
)),
(item) => (_.isObject(item) ? [item.toLine(false), item] : [item, null])
),
change: (event) => {
$el.data('EmailsTagsValue', event.target.value); $el.data('EmailsTagsValue', event.target.value);
fValue(event.target.value); fValue(event.target.value);
} }
@ -1208,7 +1209,7 @@ ko.observable.fn.validateEmail = function() {
this.hasError = ko.observable(false); this.hasError = ko.observable(false);
this.subscribe((value) => { this.subscribe((value) => {
this.hasError('' !== value && !(/^[^@\s]+@[^@\s]+$/.test(value))); this.hasError('' !== value && !((/^[^@\s]+@[^@\s]+$/).test(value)));
}); });
this.valueHasMutated(); this.valueHasMutated();
@ -1220,7 +1221,7 @@ ko.observable.fn.validateSimpleEmail = function() {
this.hasError = ko.observable(false); this.hasError = ko.observable(false);
this.subscribe((value) => { this.subscribe((value) => {
this.hasError('' !== value && !(/^.+@.+$/.test(value))); this.hasError('' !== value && !((/^.+@.+$/).test(value)));
}); });
this.valueHasMutated(); this.valueHasMutated();

View file

@ -1,5 +1,7 @@
import {trim, pString, encodeHtml} from 'Common/Utils'; import _ from '_';
import addressparser from 'emailjs-addressparser';
import {trim, encodeHtml, isNonEmptyArray} from 'Common/Utils';
class EmailModel class EmailModel
{ {
@ -34,46 +36,6 @@ class EmailModel
return email.initByJson(json) ? email : null; return email.initByJson(json) ? email : null;
} }
/**
* @static
* @param {string} line
* @param {string=} delimiter = ';'
* @returns {Array}
*/
static splitHelper(line, delimiter = ';') {
line = line.replace(/[\r\n]+/g, '; ').replace(/[\s]+/g, ' ');
let
index = 0,
len = 0,
at = false,
char = '',
result = '';
for (len = line.length; index < len; index++)
{
char = line.charAt(index);
switch (char)
{
case '@':
at = true;
break;
case ' ':
if (at)
{
at = false;
result += delimiter;
}
break;
// no default
}
result += char;
}
return result.split(delimiter);
}
/** /**
* @returns {void} * @returns {void}
*/ */
@ -118,32 +80,6 @@ class EmailModel
return -1 < (this.name + ' ' + this.email).toLowerCase().indexOf(query.toLowerCase()); return -1 < (this.name + ' ' + this.email).toLowerCase().indexOf(query.toLowerCase());
} }
/**
* @param {string} str
*/
parse(str) {
this.clear();
str = trim(str);
const
regex = /(?:"([^"]+)")? ?[<]?(.*?@[^>,]+)>?,? ?/g,
match = regex.exec(str);
if (match)
{
this.name = match[1] || '';
this.email = match[2] || '';
this.clearDuplicateName();
}
else if ((/^[^@]+@[^@]+$/).test(str))
{
this.name = '';
this.email = str;
}
}
/** /**
* @param {AjaxJsonEmail} oJsonEmail * @param {AjaxJsonEmail} oJsonEmail
* @returns {boolean} * @returns {boolean}
@ -176,8 +112,10 @@ class EmailModel
{ {
if (friendlyView && '' !== this.name) if (friendlyView && '' !== this.name)
{ {
result = wrapWithLink ? '<a href="mailto:' + encodeHtml('"' + this.name + '" <' + this.email + '>') + result = wrapWithLink ? '<a href="mailto:' + encodeHtml(this.email) + '?to=' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
'" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name); '" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name);
// result = wrapWithLink ? '<a href="mailto:' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
// '" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name);
} }
else else
{ {
@ -187,11 +125,17 @@ class EmailModel
if (wrapWithLink) if (wrapWithLink)
{ {
result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' + result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' +
encodeHtml('"' + this.name + '" <' + this.email + '>') + encodeHtml(this.email) + '?to=' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
'" target="_blank" tabindex="-1">' + '" target="_blank" tabindex="-1">' +
encodeHtml(result) + encodeHtml(result) +
'</a>' + '</a>' +
encodeHtml('>'); encodeHtml('>');
// result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' +
// encodeHtml('"' + this.name + '" <' + this.email + '>') +
// '" target="_blank" tabindex="-1">' +
// encodeHtml(result) +
// '</a>' +
// encodeHtml('>');
} }
else else
{ {
@ -212,166 +156,69 @@ class EmailModel
return result; return result;
} }
static splitEmailLine(line) {
const parsedResult = addressparser(line);
if (isNonEmptyArray(parsedResult))
{
const result = [];
let exists = false;
parsedResult.forEach((item) => {
const address = item.address ? new EmailModel(
item.address.replace(/^[<]+(.*)[>]+$/g, '$1'),
item.name || ''
) : null;
if (address && address.email) {
exists = true;
}
result.push(address ? address.toLine(false) : item.name);
});
return exists ? result : null;
}
return null;
}
static parseEmailLine(line) {
const parsedResult = addressparser(line);
if (isNonEmptyArray(parsedResult))
{
return _.compact(parsedResult.map(
(item) => (item.address ? new EmailModel(
item.address.replace(/^[<]+(.*)[>]+$/g, '$1'),
item.name || ''
) : null)
));
}
return [];
}
/** /**
* @param {string} $sEmailAddress * @param {string} emailAddress
* @returns {boolean} * @returns {boolean}
*/ */
mailsoParse($sEmailAddress) { parse(emailAddress) {
$sEmailAddress = trim($sEmailAddress); emailAddress = trim(emailAddress);
if ('' === $sEmailAddress) if ('' === emailAddress)
{ {
return false; return false;
} }
const substr = (str, start, len) => { const result = addressparser(emailAddress);
str = pString(str); if (isNonEmptyArray(result) && result[0])
let end = str.length;
if (0 > start)
{ {
start += end; this.name = result[0].name || '';
} this.email = result[0].address || '';
end = 'undefined' === typeof len ? end : (0 > len ? len + end : len + start);
return start >= str.length || 0 > start || start > end ? false : str.slice(start, end);
};
const substrReplace = (str, replace, start, length) => {
str = pString(str);
if (0 > start)
{
start += str.length;
}
length = 'undefined' !== typeof length ? length : str.length;
if (0 > length)
{
length = length + str.length - start;
}
return str.slice(0, start) + replace.substr(0, length) + replace.slice(length) + str.slice(start + length);
};
let
$sName = '',
$sEmail = '',
$sComment = '',
$bInName = false,
$bInAddress = false,
$bInComment = false,
$aRegs = null,
$iStartIndex = 0,
$iEndIndex = 0,
$iCurrentIndex = 0;
while ($iCurrentIndex < $sEmailAddress.length)
{
switch ($sEmailAddress.substr($iCurrentIndex, 1))
{
case '"':
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
{
$bInName = true;
$iStartIndex = $iCurrentIndex;
}
else if ((!$bInAddress) && (!$bInComment))
{
$iEndIndex = $iCurrentIndex;
$sName = substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
$sEmailAddress = substrReplace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
$iEndIndex = 0;
$iCurrentIndex = 0;
$iStartIndex = 0;
$bInName = false;
}
break;
case '<':
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
{
if (0 < $iCurrentIndex && 0 === $sName.length)
{
$sName = substr($sEmailAddress, 0, $iCurrentIndex);
}
$bInAddress = true;
$iStartIndex = $iCurrentIndex;
}
break;
case '>':
if ($bInAddress)
{
$iEndIndex = $iCurrentIndex;
$sEmail = substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
$sEmailAddress = substrReplace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
$iEndIndex = 0;
$iCurrentIndex = 0;
$iStartIndex = 0;
$bInAddress = false;
}
break;
case '(':
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
{
$bInComment = true;
$iStartIndex = $iCurrentIndex;
}
break;
case ')':
if ($bInComment)
{
$iEndIndex = $iCurrentIndex;
$sComment = substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
$sEmailAddress = substrReplace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
$iEndIndex = 0;
$iCurrentIndex = 0;
$iStartIndex = 0;
$bInComment = false;
}
break;
case '\\':
$iCurrentIndex += 1;
break;
// no default
}
$iCurrentIndex += 1;
}
if (0 === $sEmail.length)
{
$aRegs = $sEmailAddress.match(/[^@\s]+@\S+/i);
if ($aRegs && $aRegs[0])
{
$sEmail = $aRegs[0];
}
else
{
$sName = $sEmailAddress;
}
}
if (0 < $sEmail.length && 0 === $sName.length && 0 === $sComment.length)
{
$sName = $sEmailAddress.replace($sEmail, '');
}
$sEmail = trim($sEmail).replace(/^[<]+/, '').replace(/[>]+$/, '');
$sName = trim($sName).replace(/^["']+/, '').replace(/["']+$/, '');
$sComment = trim($sComment).replace(/^[(]+/, '').replace(/[)]+$/, '');
// Remove backslash
$sName = $sName.replace(/\\\\(.)/g, '$1');
$sComment = $sComment.replace(/\\\\(.)/g, '$1');
this.name = $sName;
this.email = $sEmail;
this.clearDuplicateName(); this.clearDuplicateName();
return true; return true;
} }
return false;
}
} }
export {EmailModel, EmailModel as default}; export {EmailModel, EmailModel as default};

View file

@ -4,9 +4,11 @@ import $ from '$';
import ko from 'ko'; import ko from 'ko';
import moment from 'moment'; import moment from 'moment';
import classnames from 'classnames'; import classnames from 'classnames';
import lozad from 'lozad';
import {MessagePriority, SignedVerifyStatus} from 'Common/Enums'; import {MessagePriority, SignedVerifyStatus} from 'Common/Enums';
import {i18n} from 'Common/Translator'; import {i18n} from 'Common/Translator';
import {DATA_IMAGE_LAZY_PLACEHOLDER_PIC} from 'Common/Consts';
import { import {
pInt, inArray, isArray, isUnd, trim, pInt, inArray, isArray, isUnd, trim,
@ -743,11 +745,29 @@ class MessageModel extends AbstractModel
if (this.body) if (this.body)
{ {
$('.lazy.lazy-inited[data-original]', this.body).each(function() { $('.lazy.lazy-inited[data-original]', this.body).each(function() {
$(this).attr('src', $(this).attr('data-original')).removeAttr('data-original'); // eslint-disable-line no-invalid-this $(this).attr('src', $(this).attr('data-original')) // eslint-disable-line no-invalid-this
.removeAttr('data-original').removeAttr('data-loaded');
}); });
} }
} }
lozad() {
lozad('img.lazy:not(.lazy-inited)', {
threshold: 0.4,
load: (element) => {
// console.log('lazy', element.dataset.original);
element.src = DATA_IMAGE_LAZY_PLACEHOLDER_PIC;
$(element)
.addClass('lazy-inited')
.attr('src', element.dataset.original)
.removeAttr('data-loaded')
.removeAttr('data-original')
.css({opacity: 0.3})
.animate({opacity: 1}, 500);
}
}).observe();
}
showExternalImages(lazy = false) { showExternalImages(lazy = false) {
if (this.body && this.body.data('rl-has-images')) if (this.body && this.body.data('rl-has-images'))
{ {
@ -762,11 +782,12 @@ class MessageModel extends AbstractModel
$this $this
.addClass('lazy') .addClass('lazy')
.attr('data-original', $this.attr(attr)) .attr('data-original', $this.attr(attr))
.removeAttr(attr); .removeAttr('data-loaded');
} }
else else
{ {
$this.attr('src', $this.attr(attr)).removeAttr(attr); $this.attr('src', $this.attr(attr))
.removeAttr('data-loaded');
} }
}); });
@ -775,18 +796,12 @@ class MessageModel extends AbstractModel
const $this = $(this); // eslint-disable-line no-invalid-this const $this = $(this); // eslint-disable-line no-invalid-this
let style = trim($this.attr('style')); let style = trim($this.attr('style'));
style = '' === style ? '' : (';' === style.substr(-1) ? style + ' ' : style + '; '); style = '' === style ? '' : (';' === style.substr(-1) ? style + ' ' : style + '; ');
$this.attr('style', style + $this.attr(attr)).removeAttr(attr); $this.attr('style', style + $this.attr(attr));
}); });
if (lazy) if (lazy)
{ {
$('img.lazy', this.body).addClass('lazy-inited').lazyload({ this.lozad();
'threshold': 400,
'effect': 'fadeIn',
'skip_invisible': false,
'container': $('.RL-MailMessageView .messageView .messageItem .content')[0]
});
$win.resize(); $win.resize();
} }
@ -867,16 +882,8 @@ class MessageModel extends AbstractModel
if (lazy) if (lazy)
{ {
(function($oImg, oContainer) { // $('.RL-MailMessageView .messageView .messageItem .content')[0]
_.delay(() => { _.delay(() => this.lozad(), 300);
$oImg.addClass('lazy-inited').lazyload({
'threshold': 400,
'effect': 'fadeIn',
'skip_invisible': false,
'container': oContainer
});
}, 300);
}($('img.lazy', self.body), $('.RL-MailMessageView .messageView .messageItem .content')[0]));
} }
windowResize(500); windowResize(500);

View file

@ -19,7 +19,6 @@ class ContactsAdminSettings
constructor() { constructor() {
this.defautOptionsAfterRender = defautOptionsAfterRender; this.defautOptionsAfterRender = defautOptionsAfterRender;
this.enableContacts = ko.observable(!!settingsGet('ContactsEnable')); this.enableContacts = ko.observable(!!settingsGet('ContactsEnable'));
this.contactsSharing = ko.observable(!!settingsGet('ContactsSharing'));
this.contactsSync = ko.observable(!!settingsGet('ContactsSync')); this.contactsSync = ko.observable(!!settingsGet('ContactsSync'));
const const
@ -177,12 +176,6 @@ class ContactsAdminSettings
}); });
}); });
this.contactsSharing.subscribe((value) => {
Remote.saveAdminConfig(null, {
'ContactsSharing': boolToAjax(value)
});
});
this.contactsSync.subscribe((value) => { this.contactsSync.subscribe((value) => {
Remote.saveAdminConfig(null, { Remote.saveAdminConfig(null, {
'ContactsSync': boolToAjax(value) 'ContactsSync': boolToAjax(value)

View file

@ -44,6 +44,8 @@ class GeneralAdminSettings
this.weakPassword = AppAdminStore.weakPassword; this.weakPassword = AppAdminStore.weakPassword;
this.newMoveToFolder = AppAdminStore.newMoveToFolder; this.newMoveToFolder = AppAdminStore.newMoveToFolder;
this.dataFolderAccess = AppAdminStore.dataFolderAccess;
this.mainAttachmentLimit = ko.observable(pInt(settingsGet('AttachmentLimit')) / (Magics.BitLength1024 * Magics.BitLength1024)).extend({posInterer: 25}); this.mainAttachmentLimit = ko.observable(pInt(settingsGet('AttachmentLimit')) / (Magics.BitLength1024 * Magics.BitLength1024)).extend({posInterer: 25});
this.uploadData = settingsGet('PhpUploadSizes'); this.uploadData = settingsGet('PhpUploadSizes');

View file

@ -10,7 +10,13 @@ const TIME_KEY = '__rlT';
*/ */
export function isStorageSupported(storageName) export function isStorageSupported(storageName)
{ {
if (storageName in window && window[storageName] && window[storageName].setItem) let storageIsAvailable = false;
try {
storageIsAvailable = storageName in window && window[storageName] && window[storageName].setItem;
}
catch(e) {} // at: window[storageName] firefox throws SecurityError: The operation is insecure. when in iframe
if (storageIsAvailable)
{ {
const const
s = window[storageName], s = window[storageName],
@ -28,6 +34,7 @@ export function isStorageSupported(storageName)
catch (e) {} // eslint-disable-line no-empty catch (e) {} // eslint-disable-line no-empty
} }
return false; return false;
} }

View file

@ -1,4 +1,6 @@
import window from 'window';
import $ from '$';
import ko from 'ko'; import ko from 'ko';
import {settingsGet} from 'Storage/Settings'; import {settingsGet} from 'Storage/Settings';
import {AbstractAppStore} from 'Stores/AbstractApp'; import {AbstractAppStore} from 'Stores/AbstractApp';
@ -13,6 +15,8 @@ class AppAdminStore extends AbstractAppStore
this.weakPassword = ko.observable(false); this.weakPassword = ko.observable(false);
this.useLocalProxyForExternalImages = ko.observable(false); this.useLocalProxyForExternalImages = ko.observable(false);
this.dataFolderAccess = ko.observable(false);
} }
populate() { populate() {
@ -23,6 +27,10 @@ class AppAdminStore extends AbstractAppStore
this.weakPassword(!!settingsGet('WeakPassword')); this.weakPassword(!!settingsGet('WeakPassword'));
this.useLocalProxyForExternalImages(!!settingsGet('UseLocalProxyForExternalImages')); this.useLocalProxyForExternalImages(!!settingsGet('UseLocalProxyForExternalImages'));
if (settingsGet('Auth')) {
$.get('./data/VERSION?' + window.Math.random()).then(() => this.dataFolderAccess(true));
}
} }
} }

View file

@ -2,6 +2,8 @@
import ko from 'ko'; import ko from 'ko';
import _ from '_'; import _ from '_';
import {settingsGet} from 'Storage/Settings';
import {FolderType} from 'Common/Enums'; import {FolderType} from 'Common/Enums';
import {UNUSED_OPTION_VALUE} from 'Common/Consts'; import {UNUSED_OPTION_VALUE} from 'Common/Consts';
import {isArray, folderListOptionsBuilder} from 'Common/Utils'; import {isArray, folderListOptionsBuilder} from 'Common/Utils';
@ -35,6 +37,8 @@ class FolderUserStore
this.currentFolder = ko.observable(null).extend({toggleSubscribeProperty: [this, 'selected']}); this.currentFolder = ko.observable(null).extend({toggleSubscribeProperty: [this, 'selected']});
this.sieveAllowFileintoInbox = !!settingsGet('SieveAllowFileintoInbox');
this.computers(); this.computers();
this.subscribers(); this.subscribers();
} }
@ -117,7 +121,7 @@ class FolderUserStore
this.folderMenuForFilters = ko.computed( this.folderMenuForFilters = ko.computed(
() => folderListOptionsBuilder( () => folderListOptionsBuilder(
this.folderListSystem(), this.folderList(), this.folderListSystem(), this.folderList(),
['INBOX'], [['', '']], null, null, null, (item) => (item ? item.localName() : '')) [(this.sieveAllowFileintoInbox ? '' : 'INBOX')], [['', '']], null, null, null, (item) => (item ? item.localName() : ''))
); );
} }

View file

@ -276,11 +276,11 @@ html.rl-no-preview-pane {
z-index: 2; z-index: 2;
cursor: pointer; cursor: pointer;
border-radius: 5px; border-radius: 5px;
opacity: 0.3; .opacity(50);
&:hover { &:hover {
.opacity(80); .opacity(80);
border-color: #000; border-color: #666;
background-color: #888; background-color: #888;
color: #fff; color: #fff;
} }
@ -637,17 +637,40 @@ html.rl-message-fullscreen {
z-index: 10000 !important; z-index: 10000 !important;
border: @rlLowBorderSize solid @rlMainDarkColor !important; border: @rlLowBorderSize solid @rlMainDarkColor !important;
border-radius: @rlLowBorderRadius !important; border-radius: @rlLowBorderRadius !important;
}
}
html .messageItem {
.buttonUp, .buttonUp {
display: none !important;
}
&.scroller-shadow-top .buttonUp {
display: inline-block !important;
}
}
html.rl-desktop .messageItem {
.buttonUp, .buttonFull {
display: none !important;
}
&:hover {
&.scroller-shadow-top .buttonUp, .buttonFull {
display: inline-block !important;
}
}
}
html.rl-message-fullscreen .messageItem {
.buttonUnFull { .buttonUnFull {
display: inline-block !important; display: inline-block !important;
} }
.buttonFull { .buttonFull {
display: none !important; display: none !important;
} }
.buttonUp {
display: none !important;
}
&.scroller-shadow-top .buttonUp {
display: inline-block !important;
} }
} }
.nano.scroller-shadow-top .buttonUp {
display: inline-block !important;
}

View file

@ -38,7 +38,7 @@ class AddOpenPgpKeyPopupView extends AbstractViewNext
let keyTrimmed = trim(this.key()); let keyTrimmed = trim(this.key());
if (/[\n]/.test(keyTrimmed)) if ((/[\n]/).test(keyTrimmed))
{ {
keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n'); keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
} }

View file

@ -110,6 +110,8 @@ class ComposePopupView extends AbstractViewNext
this.replyTo = ko.observable(''); this.replyTo = ko.observable('');
this.replyTo.focused = ko.observable(false); this.replyTo.focused = ko.observable(false);
// this.to.subscribe((v) => console.log(v));
ko.computed(() => { ko.computed(() => {
switch (true) switch (true)
{ {

View file

@ -409,7 +409,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext
rec = rec.join(', ').split(','); rec = rec.join(', ').split(',');
rec = _.compact(_.map(rec, (value) => { rec = _.compact(_.map(rec, (value) => {
email.clear(); email.clear();
email.mailsoParse(trim(value)); email.parse(trim(value));
return '' === email.email ? false : email.email; return '' === email.email ? false : email.email;
})); }));

View file

@ -327,7 +327,7 @@ class ContactsPopupView extends AbstractViewNext
properties = []; properties = [];
_.each(this.viewProperties(), (oItem) => { _.each(this.viewProperties(), (oItem) => {
if (oItem.type() && '' !== trim(oItem.value())) if (oItem.type() && oItem.type() !== ContactPropertyType.FullName && '' !== trim(oItem.value()))
{ {
properties.push([oItem.type(), oItem.value(), oItem.typeStr()]); properties.push([oItem.type(), oItem.value(), oItem.typeStr()]);
} }

View file

@ -465,7 +465,7 @@ class MessageListMailBoxUserView extends AbstractViewNext
return false; return false;
} }
if (/is:unseen/.test(this.mainMessageListSearch())) if ((/is:unseen/).test(this.mainMessageListSearch()))
{ {
return false; return false;
} }

View file

@ -23,7 +23,7 @@ import {
import { import {
inArray, isArray, isNonEmptyArray, trim, noop, inArray, isArray, isNonEmptyArray, trim, noop,
windowResize, windowResizeCallback, inFocus, windowResize, windowResizeCallback, inFocus,
removeSelection, removeInFocus, mailToHelper removeSelection, removeInFocus, mailToHelper, isTransparent
} from 'Common/Utils'; } from 'Common/Utils';
import Audio from 'Common/Audio'; import Audio from 'Common/Audio';
@ -132,6 +132,10 @@ class MessageViewMailBoxUserView extends AbstractViewNext
this.showAttachmnetControls = ko.observable(false); this.showAttachmnetControls = ko.observable(false);
this.showAttachmnetControlsState = (v) => {
Local.set(ClientSideKeyName.MessageAttachmnetControls, !!v);
};
this.allowAttachmnetControls = ko.computed( this.allowAttachmnetControls = ko.computed(
() => 0 < this.attachmentsActions().length && Settings.capa(Capa.AttachmentsActions) () => 0 < this.attachmentsActions().length && Settings.capa(Capa.AttachmentsActions)
); );
@ -320,6 +324,12 @@ class MessageViewMailBoxUserView extends AbstractViewNext
if (message) if (message)
{ {
this.showAttachmnetControls(false); this.showAttachmnetControls(false);
if (Local.get(ClientSideKeyName.MessageAttachmnetControls))
{
_.delay(() => {
this.showAttachmnetControls(true);
}, Magics.Time50ms);
}
if (this.viewHash !== message.hash) if (this.viewHash !== message.hash)
{ {
@ -448,7 +458,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext
color = inputDom.css('background-color') || ''; color = inputDom.css('background-color') || '';
if (!inputDom.is('table')) if (!inputDom.is('table'))
{ {
color = 'rgba(0, 0, 0, 0)' === color || 'transparent' === color ? '' : color; color = isTransparent(color) ? '' : color;
} }
} }
@ -477,7 +487,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext
} }
} }
result = 'rgba(0, 0, 0, 0)' === result || 'transparent' === result ? '' : result; result = isTransparent(result) ? '' : result;
} }
return result; return result;
@ -529,7 +539,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext
// fParseEmailLine = function(sLine) { // fParseEmailLine = function(sLine) {
// return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function(sItem) { // return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function(sItem) {
// var oEmailModel = new EmailModel(); // var oEmailModel = new EmailModel();
// oEmailModel.mailsoParse(sItem); // oEmailModel.parse(sItem);
// return '' !== oEmailModel.email ? oEmailModel : null; // return '' !== oEmailModel.email ? oEmailModel : null;
// })) : null; // })) : null;
// } // }

View file

@ -1,8 +1,11 @@
import window from 'window'; import window from 'window';
import elementDatasetPolyfill from 'element-dataset';
import 'es6-object-assign/auto';
import {Promise} from 'es6-promise-polyfill/promise.js'; import {Promise} from 'es6-promise-polyfill/promise.js';
import {progressJs} from '../node_modules/Progress.js/src/progress.js'; import {progressJs} from '../vendors/Progress.js/src/progress.js';
window.Promise = window.Promise || Promise; window.Promise = window.Promise || Promise;
window.progressJs = window.progressJs || progressJs(); window.progressJs = window.progressJs || progressJs();
@ -21,7 +24,10 @@ window.progressJs.onbeforeend(() => {
} }
}); });
elementDatasetPolyfill();
require('json3'); require('json3');
require('intersection-observer');
require('../vendors/modernizr/modernizr-custom.js'); require('../vendors/modernizr/modernizr-custom.js');
require('Common/Booter'); require('Common/Booter');

100
docker-compose.yml Normal file
View file

@ -0,0 +1,100 @@
version: '2'
services:
mail:
image: tvial/docker-mailserver:latest
hostname: mail
container_name: rl.mail
domainname: domain.com
restart: always
ports:
- 25:25
- 143:143
volumes:
- maildata:/var/mail
- mailstate:/var/mail-state
- ./.docker/mail/config/:/tmp/docker-mailserver/
environment:
- ENABLE_SPAMASSASSIN=0
- ENABLE_CLAMAV=0
- ENABLE_FAIL2BAN=0
- ENABLE_POSTGREY=0
- ENABLE_MANAGESIEVE=1
- ONE_DIR=1
- DMS_DEBUG=0
cap_add:
- NET_ADMIN
- SYS_PTRACE
db:
image: mysql:5.7
hostname: db
container_name: rl.db
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: rainloop
MYSQL_PASSWORD: rainloop
MYSQL_DATABASE: rainloop
volumes:
- mysql:/var/lib/mysql
- tmp:/tmp
php:
build:
context: ./.docker/php
hostname: php
container_name: rl.php
expose:
- 9000
depends_on:
- db
- mail
volumes:
- ./:/var/www
# - ./build/local/:/var/www
- ./.docker/php/rainloop.ini:/usr/local/etc/php/conf.d/rainloop.ini
- tmp:/tmp
node:
build:
context: ./.docker/node
hostname: node
container_name: rl.node
working_dir: /var/www
command: sh -c 'yarn --version'
volumes:
- ./:/var/www
- tmp:/tmp
nginx:
image: nginx:latest
hostname: nginx
container_name: rl.nginx
depends_on:
- php
ports:
- 443:443
- 80:80
volumes:
- ./:/var/www
# - ./build/local/:/var/www
- ./.docker/nginx/ssl:/etc/nginx/ssl
- ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
- tmp:/tmp
tx:
build:
context: ./.docker/tx
hostname: tx
container_name: rl.tx
working_dir: /var/www
command: sh -c 'tx --version'
volumes:
- ./:/var/www
- ./.docker/.cache/tx/root:/root
- tmp:/tmp
volumes:
mysql:
driver: local
maildata:
driver: local
mailstate:
driver: local
tmp:
driver: local

View file

@ -1,4 +1,5 @@
/* RainLoop Webmail (c) RainLoop Team | Licensed under AGPL 3 */ /* RainLoop Webmail (c) RainLoop Team | Licensed under AGPL 3 */
/* eslint-disable */
'use strict'; 'use strict';
var var
@ -130,37 +131,6 @@ function copyFile(sFile, sNewFile, callback)
callback(); callback();
} }
function signFile(sFile, callback)
{
var exec = require('child_process').exec;
exec('gpg2 --openpgp -u 87DA4591 -a -b ' + sFile, function(err) {
if (err) {
gutil.log('gpg error: skip');
}
callback();
});
}
function signFileTask(callback) {
if (argv.sign)
{
signFile(cfg.destPath + cfg.zipFile, function() {
if (cfg.zipFileShort)
{
signFile(cfg.destPath + cfg.zipFileShort, callback);
}
else
{
callback();
}
});
}
else
{
callback();
}
};
cfg.paths.globjs = 'dev/**/*.js'; cfg.paths.globjs = 'dev/**/*.js';
cfg.paths.static = 'rainloop/v/' + cfg.devVersion + '/static/'; cfg.paths.static = 'rainloop/v/' + cfg.devVersion + '/static/';
cfg.paths.staticJS = 'rainloop/v/' + cfg.devVersion + '/static/js/'; cfg.paths.staticJS = 'rainloop/v/' + cfg.devVersion + '/static/js/';
@ -198,9 +168,9 @@ cfg.paths.css = {
'vendors/flags/flags-fixed.css', 'vendors/flags/flags-fixed.css',
'node_modules/opentip/css/opentip.css', 'node_modules/opentip/css/opentip.css',
'node_modules/pikaday/css/pikaday.css', 'node_modules/pikaday/css/pikaday.css',
'node_modules/lightgallery/dist/css/lightgallery.min.css', 'vendors/lightgallery/dist/css/lightgallery.min.css',
'node_modules/lightgallery/dist/css/lg-transitions.min.css', 'vendors/lightgallery/dist/css/lg-transitions.min.css',
'node_modules/Progress.js/minified/progressjs.min.css', 'vendors/Progress.js/minified/progressjs.min.css',
'dev/Styles/_progressjs.css' 'dev/Styles/_progressjs.css'
] ]
}, },
@ -223,10 +193,10 @@ cfg.paths.js = {
name: 'libs.js', name: 'libs.js',
src: [ src: [
'node_modules/jquery/dist/jquery.min.js', 'node_modules/jquery/dist/jquery.min.js',
'node_modules/jquery-migrate/dist/jquery-migrate.min.js',
'node_modules/jquery-mousewheel/jquery.mousewheel.js', 'node_modules/jquery-mousewheel/jquery.mousewheel.js',
'node_modules/jquery-scrollstop/jquery.scrollstop.js', 'node_modules/jquery-scrollstop/jquery.scrollstop.js',
'node_modules/jquery-lazyload/jquery.lazyload.js ', 'node_modules/jquery-backstretch/jquery.backstretch.min.js',
'node_modules/jquery.backstretch/jquery.backstretch.min.js',
'vendors/jquery-ui/js/jquery-ui-1.10.3.custom.min.js', // custom 'vendors/jquery-ui/js/jquery-ui-1.10.3.custom.min.js', // custom
'vendors/jquery-nanoscroller/jquery.nanoscroller.js', // custom (modified) 'vendors/jquery-nanoscroller/jquery.nanoscroller.js', // custom (modified)
'vendors/jquery-wakeup/jquery.wakeup.js', // no-npm 'vendors/jquery-wakeup/jquery.wakeup.js', // no-npm
@ -242,7 +212,7 @@ cfg.paths.js = {
'node_modules/underscore/underscore-min.js', 'node_modules/underscore/underscore-min.js',
'node_modules/moment/min/moment.min.js', 'node_modules/moment/min/moment.min.js',
'node_modules/knockout/build/output/knockout-latest.js', 'node_modules/knockout/build/output/knockout-latest.js',
'node_modules/knockout-projections/dist/knockout-projections.min.js', 'node_modules/knockout-transformations/dist/knockout-transformations.min.js',
'node_modules/knockout-sortable/build/knockout-sortable.min.js ', 'node_modules/knockout-sortable/build/knockout-sortable.min.js ',
'node_modules/matchmedia-polyfill/matchMedia.js', 'node_modules/matchmedia-polyfill/matchMedia.js',
'node_modules/matchmedia-polyfill/matchMedia.addListener.js', 'node_modules/matchmedia-polyfill/matchMedia.addListener.js',
@ -250,11 +220,11 @@ cfg.paths.js = {
'node_modules/autolinker/dist/Autolinker.min.js', 'node_modules/autolinker/dist/Autolinker.min.js',
'node_modules/opentip/lib/opentip.js', 'node_modules/opentip/lib/opentip.js',
'node_modules/opentip/lib/adapter-jquery.js', 'node_modules/opentip/lib/adapter-jquery.js',
'node_modules/lightgallery/dist/js/lightgallery.min.js', 'vendors/lightgallery/dist/js/lightgallery.min.js',
'node_modules/lightgallery/dist/js/lg-fullscreen.min.js', 'vendors/lightgallery/dist/js/lg-fullscreen.min.js',
'node_modules/lightgallery/dist/js/lg-thumbnail.min.js', 'vendors/lightgallery/dist/js/lg-thumbnail.min.js',
'node_modules/lightgallery/dist/js/lg-zoom.min.js', 'vendors/lightgallery/dist/js/lg-zoom.min.js',
'node_modules/lightgallery/dist/js/lg-autoplay.min.js', 'vendors/lightgallery/dist/js/lg-autoplay.min.js',
'node_modules/ifvisible.js/src/ifvisible.min.js' 'node_modules/ifvisible.js/src/ifvisible.min.js'
] ]
}, },
@ -267,7 +237,7 @@ cfg.paths.js = {
}; };
// assers // assets
gulp.task('assets:clean', function() { gulp.task('assets:clean', function() {
return cleanDir(cfg.paths.static); return cleanDir(cfg.paths.static);
@ -427,7 +397,7 @@ gulp.task('fontastic-fonts:clear', function() {
}); });
gulp.task('lightgallery-fonts:copy', ['lightgallery-fonts:clear'], function() { gulp.task('lightgallery-fonts:copy', ['lightgallery-fonts:clear'], function() {
return gulp.src('node_modules/lightgallery/dist/fonts/lg.*') return gulp.src('vendors/lightgallery/dist/fonts/lg.*')
.pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/css/fonts')); .pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/css/fonts'));
}); });
@ -521,8 +491,6 @@ gulp.task('rainloop:shortname', ['rainloop:zip'], function(callback) {
copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback); copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback);
}); });
gulp.task('rainloop:sign', ['rainloop:shortname'], signFileTask);
// build (OwnCloud) // build (OwnCloud)
gulp.task('rainloop:owncloud:copy', function() { gulp.task('rainloop:owncloud:copy', function() {
@ -594,7 +562,27 @@ gulp.task('rainloop:owncloud:shortname', ['rainloop:owncloud:zip'], function(cal
copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback); copyFile(cfg.destPath + cfg.zipFile, cfg.destPath + cfg.zipFileShort, callback);
}); });
gulp.task('rainloop:owncloud:sign', ['rainloop:owncloud:shortname'], signFileTask); gulp.task('plugins:build', [], function() {
var name = argv.name || '';
if (true === name || !name) {
throw new Error('Empty name parameter');
}
var
source = 'plugins/' + name,
vesrion = fs.readFileSync(source + '/VERSION', 'utf8')
;
cfg.destPath = 'build/dist/plugins/';
cfg.zipFile = name + '-' + vesrion + '.zip';
fs.mkdirSync(cfg.destPath, '0777', true);
return gulp.src(source + '/**/*', {base: source})
.pipe(require('gulp-zip')(cfg.zipFile))
.pipe(gulp.dest(cfg.destPath));
});
// main // main
gulp.task('moment', ['moment:locales']); gulp.task('moment', ['moment:locales']);
@ -607,11 +595,11 @@ gulp.task('clean', ['js:clean', 'css:clean', 'assets:clean']);
gulp.task('rainloop:start', ['rainloop:copy', 'rainloop:setup']); gulp.task('rainloop:start', ['rainloop:copy', 'rainloop:setup']);
gulp.task('rainloop', ['rainloop:start', 'rainloop:zip', 'rainloop:clean', 'rainloop:shortname', 'rainloop:sign']); gulp.task('rainloop', ['rainloop:start', 'rainloop:zip', 'rainloop:clean', 'rainloop:shortname']);
gulp.task('owncloud', ['rainloop:owncloud:copy', gulp.task('owncloud', ['rainloop:owncloud:copy',
'rainloop:owncloud:copy-rainloop', 'rainloop:owncloud:copy-rainloop:clean', 'rainloop:owncloud:copy-rainloop', 'rainloop:owncloud:copy-rainloop:clean',
'rainloop:owncloud:setup', 'rainloop:owncloud:zip', 'rainloop:owncloud:clean', 'rainloop:owncloud:shortname', 'rainloop:owncloud:sign']); 'rainloop:owncloud:setup', 'rainloop:owncloud:zip', 'rainloop:owncloud:clean', 'rainloop:owncloud:shortname']);
// default // default
gulp.task('default', function(callback) { gulp.task('default', function(callback) {
@ -627,8 +615,13 @@ gulp.task('watch', ['css:main', 'js:validate'], function() {
}); });
// aliases // aliases
gulp.task('lint', ['js:eslint']);
gulp.task('build', ['rainloop']); gulp.task('build', ['rainloop']);
gulp.task('all', function(callback) {
runSequence('rainloop', 'owncloud', callback);
});
gulp.task('d', ['default']); gulp.task('d', ['default']);
gulp.task('w', ['watch']); gulp.task('w', ['watch']);
gulp.task('l', ['js:libs']); gulp.task('l', ['js:libs']);
@ -636,3 +629,5 @@ gulp.task('v', ['js:validate']);
gulp.task('b', ['build']); gulp.task('b', ['build']);
gulp.task('o', ['owncloud']); gulp.task('o', ['owncloud']);
gulp.task('p', ['plugins:build']);

View file

@ -2,8 +2,15 @@
"compilerOptions": { "compilerOptions": {
"target": "es2015", "target": "es2015",
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"experimentalDecorators": true "experimentalDecorators": true,
"baseUrl": "./dev",
"paths": {
"*": [
"*",
]
},
}, },
"exclude": [ "exclude": [
"build", "build",

View file

@ -3,19 +3,22 @@
"title": "RainLoop Webmail", "title": "RainLoop Webmail",
"description": "Simple, modern & fast web-based email client", "description": "Simple, modern & fast web-based email client",
"private": true, "private": true,
"version": "1.11.1", "version": "1.12.1",
"ownCloudVersion": "5.0.0", "ownCloudVersion": "5.1.1",
"homepage": "http://rainloop.net", "homepage": "https://www.rainloop.net",
"main": "gulpfile.js",
"author": { "author": {
"name": "RainLoop Team", "name": "RainLoop Team",
"email": "support@rainloop.net", "email": "support@rainloop.net",
"web": "http://www.rainloop.net" "web": "https://www.rainloop.net"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/RainLoop/rainloop-webmail.git" "url": "git://github.com/RainLoop/rainloop-webmail.git"
}, },
"scripts": {
"watch-css": "gulp watch",
"watch-js": "webpack --color --watch"
},
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"licenses": [ "licenses": [
{ {
@ -24,7 +27,7 @@
}, },
{ {
"type": "RainLoop Software License", "type": "RainLoop Software License",
"ulr": "http://www.rainloop.net/licensing/" "ulr": "https://www.rainloop.net/licensing/"
} }
], ],
"bugs": { "bugs": {
@ -33,94 +36,94 @@
"keywords": [ "keywords": [
"webmail", "webmail",
"php", "php",
"javascript",
"simple", "simple",
"modern", "modern",
"mail", "mail",
"web-based", "web-based",
"email", "email",
"client", "client",
"openpgp", "openpgp"
"plugins"
], ],
"readmeFilename": "README.md", "readmeFilename": "README.md",
"engines": {
"node": ">= 4"
},
"browserslist": [ "browserslist": [
"last 3 versions", "last 3 versions",
"ie >= 9", "IE >= 9",
"firefox esr" "firefox esr"
], ],
"devDependencies": { "devDependencies": {
"Progress.js": "github:usablica/progress.js#v0.1.0", "@babel/core": "7.4.0",
"autolinker": "1.4.3", "@babel/plugin-proposal-class-properties": "7.4.0",
"babel-core": "6.25.0", "@babel/plugin-proposal-decorators": "7.4.0",
"babel-eslint": "7.2.3", "@babel/plugin-transform-runtime": "7.4.0",
"babel-loader": "7.1.1", "@babel/preset-env": "7.4.2",
"babel-plugin-transform-decorators-legacy": "1.3.4", "@babel/runtime-corejs2": "7.4.2",
"babel-plugin-transform-runtime": "6.23.0", "autolinker": "3.0.5",
"babel-preset-env": "1.6.0", "babel-eslint": "10.0.1",
"babel-preset-stage-0": "6.24.1", "babel-loader": "8.0.5",
"babel-runtime": "6.23.0", "classnames": "2.2.6",
"classnames": "2.2.5", "copy-webpack-plugin": "5.0.2",
"copy-webpack-plugin": "4.0.1", "css-loader": "2.1.1",
"element-dataset": "2.2.6",
"emailjs-addressparser": "2.0.2",
"es6-object-assign": "1.1.0",
"es6-promise-polyfill": "1.2.0", "es6-promise-polyfill": "1.2.0",
"eslint": "4.1.1", "eslint": "5.15.3",
"eslint-plugin-compat": "1.0.4",
"gulp": "3.9.1", "gulp": "3.9.1",
"gulp-autoprefixer": "4.0.0", "gulp-autoprefixer": "6.0.0",
"gulp-cached": "1.1.1", "gulp-cached": "1.1.1",
"gulp-chmod": "2.0.0", "gulp-chmod": "2.0.0",
"gulp-clean-css": "3.5.0", "gulp-clean-css": "3.10.0",
"gulp-concat-util": "0.5.5", "gulp-concat-util": "0.5.5",
"gulp-eol": "0.1.2", "gulp-eol": "0.2.0",
"gulp-eslint": "4.0.0", "gulp-eslint": "5.0.0",
"gulp-expect-file": "0.0.7", "gulp-expect-file": "1.0.1",
"gulp-filter": "5.0.0", "gulp-filter": "5.1.0",
"gulp-header": "1.8.8", "gulp-header": "2.0.7",
"gulp-if": "2.0.2", "gulp-if": "2.0.2",
"gulp-less": "3.3.2", "gulp-less": "4.0.1",
"gulp-livereload": "3.8.1", "gulp-livereload": "4.0.1",
"gulp-notify": "3.0.0", "gulp-notify": "3.2.0",
"gulp-plumber": "1.1.0", "gulp-plumber": "1.2.1",
"gulp-rename": "1.2.2", "gulp-rename": "1.4.0",
"gulp-replace": "0.6.1", "gulp-replace": "1.0.0",
"gulp-rimraf": "0.2.1", "gulp-rimraf": "0.2.2",
"gulp-size": "2.1.0", "gulp-size": "3.0.0",
"gulp-stripbom": "1.0.4", "gulp-stripbom": "1.0.4",
"gulp-through": "0.4.0", "gulp-through": "0.4.0",
"gulp-uglify": "3.0.0", "gulp-uglify": "3.0.2",
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"gulp-zip": "4.0.0", "gulp-zip": "4.2.0",
"ifvisible.js": "1.0.6", "ifvisible.js": "1.0.6",
"jquery": "2.2.4", "intersection-observer": "0.5.1",
"jquery-lazyload": "1.9.7", "jquery": "3.3.1",
"jquery-backstretch": "2.1.17",
"jquery-migrate": "3.0.1",
"jquery-mousewheel": "3.1.13", "jquery-mousewheel": "3.1.13",
"jquery-scrollstop": "1.2.0", "jquery-scrollstop": "1.2.0",
"jquery.backstretch": "2.1.15", "js-cookie": "2.2.0",
"js-cookie": "2.1.4", "json-loader": "0.5.7",
"json-loader": "0.5.4",
"json3": "3.3.2", "json3": "3.3.2",
"knockout": "3.4.2", "knockout": "3.4.2",
"knockout-projections": "github:stevesanderson/knockout-projections#v1.1.0", "knockout-sortable": "1.1.1",
"knockout-sortable": "0.14.1", "knockout-transformations": "2.1.0",
"lightgallery": "1.2.21", "lozad": "1.9.0",
"matchmedia-polyfill": "0.3.0", "matchmedia-polyfill": "0.3.1",
"moment": "2.18.1", "moment": "2.24.0",
"node-fs": "0.1.7", "node-fs": "0.1.7",
"node-notifier": "5.1.2", "node-notifier": "5.4.0",
"normalize.css": "7.0.0", "normalize.css": "8.0.1",
"openpgp": "2.5.5", "openpgp": "2.6.2",
"opentip": "2.4.3", "opentip": "2.4.3",
"pikaday": "1.6.1", "pikaday": "1.8.0",
"raw-loader": "0.5.1", "raw-loader": "2.0.0",
"rifraf": "2.0.3", "rimraf": "2.6.3",
"rimraf": "2.6.1", "run-sequence": "2.2.1",
"run-sequence": "2.0.0", "simplestatemanager": "4.1.1",
"simplestatemanager": "3.4.0", "style-loader": "0.23.1",
"style-loader": "0.18.2", "underscore": "1.9.1",
"underscore": "1.8.3", "webpack": "4.29.6",
"webpack": "3.1.0", "webpack-cli": "3.3.0",
"webpack-notifier": "1.5.0" "webpack-notifier": "1.7.0"
} }
} }

View file

@ -12,6 +12,10 @@
class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
private $imap_prefix = "mail.";
private $smtp_prefix = "mail.";
public function Init() public function Init()
{ {
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials'); $this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
@ -19,7 +23,7 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
} }
/** /**
* This function detects the IMAP Host, and if it is set to "auto", replaces it with the email domain. * This function detects the IMAP Host, and if it is set to "auto", replaces it with the MX or email domain.
* *
* @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Model\Account $oAccount
* @param array $aImapCredentials * @param array $aImapCredentials
@ -31,13 +35,22 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
// Check for mail.$DOMAIN as entered value in RL settings // Check for mail.$DOMAIN as entered value in RL settings
if (!empty($aImapCredentials['Host']) && 'auto' === $aImapCredentials['Host']) if (!empty($aImapCredentials['Host']) && 'auto' === $aImapCredentials['Host'])
{ {
$aImapCredentials['Host'] = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email()); $domain = substr(strrchr($oAccount->Email(), "@"), 1);
$mxhosts = array();
if(getmxrr($domain, $mxhosts) && sizeof($mxhosts) > 0)
{
$aImapCredentials['Host'] = $mxhosts[0];
}
else
{
$aImapCredentials['Host'] = $this->imap_prefix.$domain;
}
} }
} }
} }
/** /**
* This function detects the SMTP Host, and if it is set to "auto", replaces it with the email domain. * This function detects the SMTP Host, and if it is set to "auto", replaces it with the MX or email domain.
* *
* @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Model\Account $oAccount
* @param array $aSmtpCredentials * @param array $aSmtpCredentials
@ -49,7 +62,16 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
// Check for mail.$DOMAIN as entered value in RL settings // Check for mail.$DOMAIN as entered value in RL settings
if (!empty($aSmtpCredentials['Host']) && 'auto' === $aSmtpCredentials['Host']) if (!empty($aSmtpCredentials['Host']) && 'auto' === $aSmtpCredentials['Host'])
{ {
$aSmtpCredentials['Host'] = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email()); $domain = substr(strrchr($oAccount->Email(), "@"), 1);
$mxhosts = array();
if(getmxrr($domain, $mxhosts) && sizeof($mxhosts) > 0)
{
$aSmtpCredentials['Host'] = $mxhosts[0];
}
else
{
$aSmtpCredentials['Host'] = $this->smtp_prefix.$domain;
}
} }
} }
} }

View file

@ -146,7 +146,7 @@ class ChangePasswordCustomSqlDriver implements \RainLoop\Providers\ChangePasswor
$dsn = 'mysql:host='.$this->mHost.';dbname='.$this->mDatabase.';charset=utf8'; $dsn = 'mysql:host='.$this->mHost.';dbname='.$this->mDatabase.';charset=utf8';
$options = array( $options = array(
PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_PERSISTENT => true, PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
); );
@ -160,15 +160,52 @@ class ChangePasswordCustomSqlDriver implements \RainLoop\Providers\ChangePasswor
$sEmailUser = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail); $sEmailUser = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail);
$sEmailDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail); $sEmailDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
//simple check // some variables cannot be prepared
$this->mSql = str_replace(array(
':table'
), array(
$this->mTable
), $this->mSql);
$old = array(':email', ':oldpass', ':newpass', ':domain', ':username', ':table' ); $placeholders = array(
$new = array($sEmail, $sPrevPassword, $sNewPassword, $sEmailDomain, $sEmailUser, $this->mTable); ':email' => $sEmail,
':oldpass' => $sPrevPassword,
':newpass' => $sNewPassword,
':domain' => $sEmailDomain,
':username' => $sEmailUser
);
$this->mSql = str_replace($old, $new, $this->mSql); // we have to check that all placehoders are used in the query, passing any unused placeholders will generate an error
$used_placeholders = array();
foreach($placeholders as $placeholder => $value) {
if(preg_match_all('/'.$placeholder . '(?![a-zA-Z0-9\-])'.'/', $this->mSql) === 1) {
// backwards-compabitibility: remove single and double quotes around placeholders
$this->mSql = str_replace('`'.$placeholder.'`', $placeholder, $this->mSql);
$this->mSql = str_replace("'".$placeholder."'", $placeholder, $this->mSql);
$this->mSql = str_replace('"'.$placeholder.'"', $placeholder, $this->mSql);
$used_placeholders[$placeholder] = $value;
}
}
$statement = $conn->prepare($this->mSql);
// everything is ready (hopefully), bind the values
foreach($used_placeholders as $placeholder => $value) {
$statement->bindValue($placeholder, $value);
}
// and execute
$mSqlReturn = $statement->execute();
/* can be used for debugging
ob_start();
$statement->debugDumpParams();
$r = ob_get_contents();
ob_end_clean();
$this->oLogger->Write($r);
*/
$update = $conn->prepare($this->mSql);
$mSqlReturn = $update->execute(array());
if ($mSqlReturn == true) if ($mSqlReturn == true)
{ {
$bResult = true; $bResult = true;

View file

@ -46,7 +46,7 @@ class ChangePasswordCustomSqlPlugin extends \RainLoop\Plugins\AbstractPlugin
\RainLoop\Plugins\Property::NewInstance('mTable')->SetLabel('MySQL Table'), \RainLoop\Plugins\Property::NewInstance('mTable')->SetLabel('MySQL Table'),
\RainLoop\Plugins\Property::NewInstance('mSql')->SetLabel('SQL statement') \RainLoop\Plugins\Property::NewInstance('mSql')->SetLabel('SQL statement')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('SQL statement (allowed wildcards :table, :email, :oldpass, :newpass, :domain, :username). When using MD5 OR SHA1 at tables, write it directly here as SQL functions. Fro non-SQL encryptions use another plugin or wait for new version.') ->SetDescription('SQL statement (allowed wildcards :table, :email, :oldpass, :newpass, :domain, :username). Use SQL functions for encryption.')
->SetDefaultValue('UPDATE :table SET password = md5(:newpass) WHERE domain = :domain AND username = :username and oldpass = md5(:oldpass)') ->SetDefaultValue('UPDATE :table SET password = md5(:newpass) WHERE domain = :domain AND username = :username and oldpass = md5(:oldpass)')
); );
} }

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 https://github.com/korukugashi/
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,4 @@
Add ability to paste multi row email addresses (from Excel for instance) to a contact in a single row, in order to create a contact group.
Usage : open address book, paste your emails separated by line breaks in an email field. Emails will be collapsed with "," separator.
Then, when you write an email, select the email group and all addresses will be added.

View file

@ -0,0 +1 @@
0.1

View file

@ -0,0 +1,9 @@
<?php
class ContactGroupExcelPastePlugin extends \RainLoop\Plugins\AbstractPlugin
{
public function Init()
{
$this->addJs('js/excel_contact_group.js');
}
}

View file

@ -0,0 +1,20 @@
(function(window, $) {
$(function() {
$(window.document).on('paste', '.RL-PopupsContacts .contactValueInput', function() {
// trigger the process on click and paste
var $this = $(this);
setTimeout(function () {
$this.trigger('keyup');
});
}).on('keyup', '.RL-PopupsContacts .contactValueInput', function() {
var $this = $(this),
value = $this.val(),
match = value && value.match(/@/ig);
if (match && match.length > 1) {
$this.val($this.val().replace(/\n| /ig, ','));
}
});
});
}(window, $));

View file

@ -0,0 +1,293 @@
<?php
class FroxlorChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
/**
* @var string
*/
private $sDsn = '';
/**
* @var string
*/
private $sUser = '';
/**
* @var string
*/
private $sPassword = '';
/**
* @var string
*/
private $sAllowedEmails = '';
/**
* @var \MailSo\Log\Logger
*/
private $oLogger = null;
/**
* @param string $sDsn
* @param string $sUser
* @param string $sPassword
*
* @return \FroxlorChangePasswordDriver
*/
public function SetConfig($sDsn, $sUser, $sPassword)
{
$this->sDsn = $sDsn;
$this->sUser = $sUser;
$this->sPassword = $sPassword;
return $this;
}
/**
* @param string $sAllowedEmails
*
* @return \FroxlorChangePasswordDriver
*/
public function SetAllowedEmails($sAllowedEmails)
{
$this->sAllowedEmails = $sAllowedEmails;
return $this;
}
/**
* @param \MailSo\Log\Logger $oLogger
*
* @return \FroxlorChangePasswordDriver
*/
public function SetLogger($oLogger)
{
if ($oLogger instanceof \MailSo\Log\Logger)
{
$this->oLogger = $oLogger;
}
return $this;
}
/**
* @param \RainLoop\Account $oAccount
*
* @return bool
*/
public function PasswordChangePossibility($oAccount)
{
return $oAccount && $oAccount->Email() &&
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
}
/**
* @param \RainLoop\Account $oAccount
* @param string $sPrevPassword
* @param string $sNewPassword
*
* @return bool
*/
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
{
if ($this->oLogger) {
$this->oLogger->Write('Froxlor: Try to change password for '.$oAccount->Email());
}
$bResult = false;
if (!empty($this->sDsn) && 0 < \strlen($this->sUser) && 0 < \strlen($this->sPassword) && $oAccount)
{
try
{
$oPdo = new \PDO($this->sDsn, $this->sUser, $this->sPassword);
$oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$oStmt = $oPdo->prepare('SELECT password_enc, id FROM mail_users WHERE username = ? LIMIT 1');
if ($oStmt->execute(array($oAccount->IncLogin())))
{
$aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
if (\is_array($aFetchResult) && isset($aFetchResult[0]['password_enc'], $aFetchResult[0]['id']))
{
$sDbPassword = \stripslashes($aFetchResult[0]['password_enc']);
if ( $this->validatePasswordLogin( $sDbPassword, $sPrevPassword ) ) {
$sEncNewPassword = $this->cryptPassword($sNewPassword, 3);
$oStmt = $oPdo->prepare('UPDATE mail_users SET password_enc = ?,password = ? WHERE id = ?');
$bResult = (bool) $oStmt->execute(
array($sEncNewPassword, $sNewPassword, $aFetchResult[0]['id']));
}
}
}
}
catch (\Exception $oException)
{
if ($this->oLogger)
{
$this->oLogger->WriteException($oException);
}
}
}
return $bResult;
}
/**
* @param string $sPassword
* @return string
*/
private function cryptPassword($sPassword, $type = 3)
{
return $this->makeCryptPassword($sPassword,$type);
}
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michal Wojcik <m.wojcik@sonet3.pl>
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Functions
*
*/
/**
* Make crypted password from clear text password
*
* @author Michal Wojcik <m.wojcik@sonet3.pl>
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
*
* 0 - default crypt (depenend on system configuration)
* 1 - MD5 $1$
* 2 - BLOWFISH $2a$ | $2y$07$ (on php 5.3.7+)
* 3 - SHA-256 $5$ (default)
* 4 - SHA-512 $6$
*
* @param string $password Password to be crypted
*
* @return string encrypted password
*/
private function makeCryptPassword ($password,$type = 3) {
switch ($type) {
case 0:
$cryptPassword = \crypt($password);
break;
case 1:
$cryptPassword = \crypt($password, '$1$' . $this->generatePassword(true). $this->generatePassword(true));
break;
case 2:
if (\version_compare(\phpversion(), '5.3.7', '<')) {
$cryptPassword = \crypt($password, '$2a$' . $this->generatePassword(true). $this->generatePassword(true));
} else {
// Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$",
// a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z"
$cryptPassword = \crypt(
$password,
'$2y$07$' . \substr($this->generatePassword(true).$this->generatePassword(true).$this->generatePassword(true), 0, 22)
);
}
break;
case 3:
$cryptPassword = \crypt($password, '$5$' . $this->generatePassword(true). $this->generatePassword(true));
break;
case 4:
$cryptPassword = \crypt($password, '$6$' . $this->generatePassword(true). $this->generatePassword(true));
break;
default:
$cryptPassword = \crypt($password);
break;
}
return $cryptPassword;
}
/**
* Generates a random password
*
* @param boolean $isSalt
* optional, create a hash for a salt used in makeCryptPassword because crypt() does not like some special characters in its salts, default is false
*/
private function generatePassword($isSalt = false)
{
$alpha_lower = 'abcdefghijklmnopqrstuvwxyz';
$alpha_upper = \strtoupper($alpha_lower);
$numeric = '0123456789';
$special = '!?<>§$%&+#=';
$length = 10;
$pw = $this->special_shuffle($alpha_lower);
$n = \floor(($length) / 4);
$pw .= \mb_substr($this->special_shuffle($alpha_upper), 0, $n);
$pw .= \mb_substr($this->special_shuffle($numeric), 0, $n);
$pw = \mb_substr($pw, - $length);
return $this->special_shuffle($pw);
}
/**
* multibyte-character safe shuffle function
*
* @param string $str
*
* @return string
*/
private function special_shuffle($str = null)
{
$len = \mb_strlen($str);
$sploded = array();
while ($len -- > 0) {
$sploded[] = \mb_substr($str, $len, 1);
}
\shuffle($sploded);
return \join('', $sploded);
}
/**
* Function validatePasswordLogin
*
* compare user password-hash with given user-password
* and check if they are the same
* additionally it updates the hash if the system settings changed
* or if the very old md5() sum is used
*
* @param array $userinfo user-data from table
* @param string $password the password to validate
* @param string $table either panel_customers or panel_admins
* @param string $uid user-id-field in $table
*
* @return boolean
*/
private function validatePasswordLogin($pwd_hash, $password = null) {
$systype = 3; // SHA256
$update_hash = false;
// check for good'ole md5
if (\strlen($pwd_hash) == 32 && \ctype_xdigit($pwd_hash)) {
$pwd_check = \md5($password);
$update_hash = true;
} else {
// cut out the salt from the hash
$pwd_salt = \str_replace(\substr(\strrchr($pwd_hash, "$"), 1), "", $pwd_hash);
// create same hash to compare
$pwd_check = \crypt($password, $pwd_salt);
// check whether the hash needs to be updated
$hash_type_chk = \substr($pwd_hash, 0, 3);
if (($systype == 1 && $hash_type_chk != '$1$') || // MD5
($systype == 2 && $hash_type_chk != '$2$') || // BLOWFISH
($systype == 3 && $hash_type_chk != '$5$') || // SHA256
($systype == 4 && $hash_type_chk != '$6$') // SHA512
) {
$update_hash = true;
}
}
return $pwd_check;
}
}

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 Bob Kromonos Achten
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1 @@
Plugin that adds functionality to change the email account password (Froxlor).

View file

@ -0,0 +1 @@
1.0

View file

@ -0,0 +1,76 @@
<?php
class FroxlorchangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
public function Init()
{
$this->addHook('main.fabrica', 'MainFabrica');
}
/**
* @return string
*/
public function Supported()
{
if (!extension_loaded('pdo') || !class_exists('PDO'))
{
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
}
$aDrivers = \PDO::getAvailableDrivers();
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
{
return 'The PHP exention PDO (mysql) must be installed to use this plugin';
}
return '';
}
/**
* @param string $sName
* @param mixed $oProvider
*/
public function MainFabrica($sName, &$oProvider)
{
switch ($sName)
{
case 'change-password':
$sDsn = \trim($this->Config()->Get('plugin', 'pdo_dsn', ''));
$sUser = (string) $this->Config()->Get('plugin', 'user', '');
$sPassword = (string) $this->Config()->Get('plugin', 'password', '');
if (!empty($sDsn) && 0 < \strlen($sUser) && 0 < \strlen($sPassword))
{
include_once __DIR__.'/FroxlorChangePasswordDriver.php';
$oProvider = new FroxlorChangePasswordDriver();
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
$oProvider->SetConfig($sDsn, $sUser, $sPassword);
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
}
break;
}
}
/**
* @return array
*/
public function configMapping()
{
return array(
\RainLoop\Plugins\Property::NewInstance('pdo_dsn')->SetLabel('Froxlor PDO dsn')
->SetDefaultValue('mysql:host=127.0.0.1;dbname=froxlor'),
\RainLoop\Plugins\Property::NewInstance('user')->SetLabel('DB User')
->SetDefaultValue('root'),
\RainLoop\Plugins\Property::NewInstance('password')->SetLabel('DB Password')
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
->SetDefaultValue(''),
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
->SetDefaultValue('*')
);
}
}

View file

@ -14,7 +14,7 @@ class HmailserverChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!class_exists('COM')) if (!class_exists('COM'))
{ {
return 'The PHP exention COM must be installed to use this plugin'; return 'The PHP extension COM must be installed to use this plugin';
} }
return ''; return '';

View file

@ -14,13 +14,13 @@ class IspconfigChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!extension_loaded('pdo') || !class_exists('PDO')) if (!extension_loaded('pdo') || !class_exists('PDO'))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql) must be installed to use this plugin';
} }
$aDrivers = \PDO::getAvailableDrivers(); $aDrivers = \PDO::getAvailableDrivers();
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers)) if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql) must be installed to use this plugin';
} }
return ''; return '';

View file

@ -14,13 +14,13 @@ class IspmailChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!extension_loaded('pdo') || !class_exists('PDO')) if (!extension_loaded('pdo') || !class_exists('PDO'))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql) must be installed to use this plugin';
} }
$aDrivers = \PDO::getAvailableDrivers(); $aDrivers = \PDO::getAvailableDrivers();
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers)) if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql) must be installed to use this plugin';
} }
return ''; return '';

View file

@ -118,13 +118,25 @@ class ChangePasswordLdapDriver implements \RainLoop\Providers\ChangePassword\Cha
'{login}' => $oAccount->Login(), '{login}' => $oAccount->Login(),
'{imap:login}' => $oAccount->Login(), '{imap:login}' => $oAccount->Login(),
'{imap:host}' => $oAccount->DomainIncHost(), '{imap:host}' => $oAccount->DomainIncHost(),
'{imap:port}' => $oAccount->DomainIncPort() '{imap:port}' => $oAccount->DomainIncPort(),
'{gecos}' => function_exists('posix_getpwnam') ? posix_getpwnam($oAccount->Login()) : ''
)); ));
$oCon = @\ldap_connect($this->sHostName, $this->iHostPort); $oCon = @\ldap_connect($this->sHostName, $this->iHostPort);
if ($oCon) if ($oCon)
{ {
@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); if (!@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3))
{
$this->oLogger->Write(
'Failed to set LDAP Protocol version to 3, TLS not supported.',
\MailSo\Log\Enumerations\Type::WARNING,
'LDAP'
);
}
else if (@!ldap_start_tls($oCon))
{
$this->oLogger->Write("ldap_start_tls failed: ".$oCon, \MailSo\Log\Enumerations\Type::WARNING, 'LDAP');
}
if (!@\ldap_bind($oCon, $sUserDn, $sPrevPassword)) if (!@\ldap_bind($oCon, $sUserDn, $sPrevPassword))
{ {

View file

@ -14,7 +14,7 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!\function_exists('ldap_connect')) if (!\function_exists('ldap_connect'))
{ {
return 'The LDAP PHP exention must be installed to use this plugin'; return 'The LDAP PHP extension must be installed to use this plugin';
} }
return ''; return '';
@ -64,7 +64,7 @@ class LdapChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
->SetDefaultValue(389), ->SetDefaultValue(389),
\RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format') \RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format')
->SetDescription('LDAP user dn format. Supported tokens: {email}, {email:user}, {email:domain}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}') ->SetDescription('LDAP user dn format. Supported tokens: {email}, {email:user}, {email:domain}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}, {gecos}')
->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'), ->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'),
\RainLoop\Plugins\Property::NewInstance('password_field')->SetLabel('Password field') \RainLoop\Plugins\Property::NewInstance('password_field')->SetLabel('Password field')
->SetDefaultValue('userPassword'), ->SetDefaultValue('userPassword'),

View file

@ -15,12 +15,12 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
/** /**
* @var string * @var string
*/ */
private $sAccessDn = ''; private $sAccessDn = null;
/** /**
* @var string * @var string
*/ */
private $sAccessPassword = ''; private $sAccessPassword = null;
/** /**
* @var string * @var string
@ -32,6 +32,11 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
*/ */
private $sObjectClass = 'inetOrgPerson'; private $sObjectClass = 'inetOrgPerson';
/**
* @var string
*/
private $sUidField = 'uid';
/** /**
* @var string * @var string
*/ */
@ -64,14 +69,18 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
* *
* @return \LdapContactsSuggestions * @return \LdapContactsSuggestions
*/ */
public function SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sNameField, $sEmailField) public function SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sUidField, $sNameField, $sEmailField)
{ {
$this->sHostName = $sHostName; $this->sHostName = $sHostName;
$this->iHostPort = $iHostPort; $this->iHostPort = $iHostPort;
if (0 < \strlen($sAccessDn))
{
$this->sAccessDn = $sAccessDn; $this->sAccessDn = $sAccessDn;
$this->sAccessPassword = $sAccessPassword; $this->sAccessPassword = $sAccessPassword;
}
$this->sUsersDn = $sUsersDn; $this->sUsersDn = $sUsersDn;
$this->sObjectClass = $sObjectClass; $this->sObjectClass = $sObjectClass;
$this->sUidField = $sUidField;
$this->sNameField = $sNameField; $this->sNameField = $sNameField;
$this->sEmailField = $sEmailField; $this->sEmailField = $sEmailField;
@ -128,9 +137,9 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
* *
* @return array * @return array
*/ */
private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields) private function findNameAndEmail($aLdapItem, $aEmailFields, $aNameFields, $aUidFields)
{ {
$sEmail = $sName = ''; $sEmail = $sName = $sUid = '';
if ($aLdapItem) if ($aLdapItem)
{ {
foreach ($aEmailFields as $sField) foreach ($aEmailFields as $sField)
@ -156,9 +165,21 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
} }
} }
} }
foreach ($aUidFields as $sField)
{
if (!empty($aLdapItem[$sField][0]))
{
$sUid = \trim($aLdapItem[$sField][0]);
if (!empty($sUid))
{
break;
}
}
}
} }
return array($sEmail, $sName); return array($sEmail, $sName, $sUid);
} }
/** /**
@ -180,8 +201,16 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
@\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); @\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3);
if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword)) if (!@\ldap_bind($oCon, $this->sAccessDn, $this->sAccessPassword))
{
if (is_null($this->sAccessDn))
{
$this->logLdapError($oCon, 'ldap_bind (anonymous)');
}
else
{ {
$this->logLdapError($oCon, 'ldap_bind'); $this->logLdapError($oCon, 'ldap_bind');
}
return $aResult; return $aResult;
} }
@ -200,11 +229,13 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
$aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField); $aEmails = empty($this->sEmailField) ? array() : \explode(',', $this->sEmailField);
$aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField); $aNames = empty($this->sNameField) ? array() : \explode(',', $this->sNameField);
$aUIDs = empty($this->sUidField) ? array() : \explode(',', $this->sUidField);
$aEmails = \array_map('trim', $aEmails); $aEmails = \array_map('trim', $aEmails);
$aNames = \array_map('trim', $aNames); $aNames = \array_map('trim', $aNames);
$aUIDs = \array_map('trim', $aUIDs);
$aFields = \array_merge($aEmails, $aNames); $aFields = \array_merge($aEmails, $aNames, $aUIDs);
$aItems = array(); $aItems = array();
$sSubFilter = ''; $sSubFilter = '';
@ -238,7 +269,7 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
if ($aItem) if ($aItem)
{ {
$sName = $sEmail = ''; $sName = $sEmail = '';
list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames); list ($sEmail, $sName) = $this->findNameAndEmail($aItem, $aEmails, $aNames, $aUIDs);
if (!empty($sEmail)) if (!empty($sEmail))
{ {
$aResult[] = array($sEmail, $sName); $aResult[] = array($sEmail, $sName);

View file

@ -1 +1 @@
1.0 1.1

View file

@ -14,7 +14,7 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!\function_exists('ldap_connect')) if (!\function_exists('ldap_connect'))
{ {
return 'The LDAP PHP exention must be installed to use this plugin'; return 'The LDAP PHP extension must be installed to use this plugin';
} }
return ''; return '';
@ -41,16 +41,16 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
$sAccessPassword = \trim($this->Config()->Get('plugin', 'access_password', '')); $sAccessPassword = \trim($this->Config()->Get('plugin', 'access_password', ''));
$sUsersDn = \trim($this->Config()->Get('plugin', 'users_dn_format', '')); $sUsersDn = \trim($this->Config()->Get('plugin', 'users_dn_format', ''));
$sObjectClass = \trim($this->Config()->Get('plugin', 'object_class', '')); $sObjectClass = \trim($this->Config()->Get('plugin', 'object_class', ''));
$sSearchField = \trim($this->Config()->Get('plugin', 'search_field', ''));
$sNameField = \trim($this->Config()->Get('plugin', 'name_field', '')); $sNameField = \trim($this->Config()->Get('plugin', 'name_field', ''));
$sEmailField = \trim($this->Config()->Get('plugin', 'mail_field', '')); $sEmailField = \trim($this->Config()->Get('plugin', 'mail_field', ''));
if (0 < \strlen($sAccessDn) && 0 < \strlen($sAccessPassword) && 0 < \strlen($sUsersDn) && if (0 < \strlen($sUsersDn) && 0 < \strlen($sObjectClass) && 0 < \strlen($sEmailField))
0 < \strlen($sObjectClass) && 0 < \strlen($sEmailField))
{ {
include_once __DIR__.'/LdapContactsSuggestions.php'; include_once __DIR__.'/LdapContactsSuggestions.php';
$oProvider = new LdapContactsSuggestions(); $oProvider = new LdapContactsSuggestions();
$oProvider->SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sNameField, $sEmailField); $oProvider->SetConfig($sHostName, $iHostPort, $sAccessDn, $sAccessPassword, $sUsersDn, $sObjectClass, $sSearchField, $sNameField, $sEmailField);
$mResult[] = $oProvider; $mResult[] = $oProvider;
} }
@ -71,6 +71,7 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
->SetDefaultValue(389), ->SetDefaultValue(389),
\RainLoop\Plugins\Property::NewInstance('access_dn')->SetLabel('Access dn (login)') \RainLoop\Plugins\Property::NewInstance('access_dn')->SetLabel('Access dn (login)')
->SetDescription('LDAP bind DN to authentifcate with. If left blank, anonymous bind will be tried and Access password will be ignored')
->SetDefaultValue(''), ->SetDefaultValue(''),
\RainLoop\Plugins\Property::NewInstance('access_password')->SetLabel('Access password') \RainLoop\Plugins\Property::NewInstance('access_password')->SetLabel('Access password')
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
@ -80,6 +81,8 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
->SetDefaultValue('ou=People,dc=domain,dc=com'), ->SetDefaultValue('ou=People,dc=domain,dc=com'),
\RainLoop\Plugins\Property::NewInstance('object_class')->SetLabel('objectClass value') \RainLoop\Plugins\Property::NewInstance('object_class')->SetLabel('objectClass value')
->SetDefaultValue('inetOrgPerson'), ->SetDefaultValue('inetOrgPerson'),
\RainLoop\Plugins\Property::NewInstance('search_field')->SetLabel('Search field')
->SetDefaultValue('uid'),
\RainLoop\Plugins\Property::NewInstance('name_field')->SetLabel('Name field') \RainLoop\Plugins\Property::NewInstance('name_field')->SetLabel('Name field')
->SetDefaultValue('givenname'), ->SetDefaultValue('givenname'),
\RainLoop\Plugins\Property::NewInstance('mail_field')->SetLabel('Mail field') \RainLoop\Plugins\Property::NewInstance('mail_field')->SetLabel('Mail field')

View file

@ -14,13 +14,13 @@ class MailcowChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!extension_loaded('pdo') || !class_exists('PDO')) if (!extension_loaded('pdo') || !class_exists('PDO'))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql) must be installed to use this plugin';
} }
$aDrivers = \PDO::getAvailableDrivers(); $aDrivers = \PDO::getAvailableDrivers();
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers)) if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql) must be installed to use this plugin';
} }
return ''; return '';

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 Marius Gripsgard <marius@ubports.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,157 @@
<?php
/*
* Mail-in-a-box Password Change Plugin
*
* Based on VirtualminChangePasswordDriver
*
* Author: Marius Gripsgard
*/
class MailInABoxChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
/**
* @var string
*/
private $sAllowedEmails = '';
/**
* @var string
*/
private $sHost = '';
/**
* @var string
*/
private $sAdminUser = '';
/**
* @var string
*/
private $sAdminPassword = '';
/**
* @var \MailSo\Log\Logger
*/
private $oLogger = null;
/**
* @param string $sHost
* @param string $sAdminUser
* @param string $sAdminPassword
*
* @return \MailInABoxChangePasswordDriver
*/
public function SetConfig($sHost, $sAdminUser, $sAdminPassword)
{
$this->sHost = $sHost;
$this->sAdminUser = $sAdminUser;
$this->sAdminPassword = $sAdminPassword;
return $this;
}
/**
* @param string $sAllowedEmails
*
* @return \MailInABoxChangePasswordDriver
*/
public function SetAllowedEmails($sAllowedEmails)
{
$this->sAllowedEmails = $sAllowedEmails;
return $this;
}
/**
* @param \MailSo\Log\Logger $oLogger
*
* @return \MailInABoxChangePasswordDriver
*/
public function SetLogger($oLogger)
{
if ($oLogger instanceof \MailSo\Log\Logger)
{
$this->oLogger = $oLogger;
}
return $this;
}
/**
* @param string $sDesc
* @param int $iType = \MailSo\Log\Enumerations\Type::INFO
*
* @return \MailInABoxChangePasswordDriver
*/
public function WriteLog($sDesc, $iType = \MailSo\Log\Enumerations\Type::INFO)
{
if ($this->oLogger)
{
$this->oLogger->Write($sDesc, $iType);
}
return $this;
}
/**
* @param \RainLoop\Model\Account $oAccount
*
* @return bool
*/
public function PasswordChangePossibility($oAccount)
{
return $oAccount && $oAccount->Email() &&
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
}
/**
* @param \RainLoop\Model\Account $oAccount
* @param string $sPrevPassword
* @param string $sNewPassword
*
* @return bool
*/
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
{
$this->WriteLog('Mail-in-a-box: Try to change password for '.$oAccount->Email());
$bResult = false;
if (!empty($this->sHost) && !empty($this->sAdminUser) && !empty($this->sAdminPassword) && $oAccount)
{
$this->WriteLog('Mail-in-a-box:[Check] Required Fields Present');
$sEmail = \trim(\strtolower($oAccount->Email()));
$sHost = \rtrim(\trim($this->sHost), '/');
$sUrl = $sHost.'/admin/mail/users/password';
$sAdminUser = $this->sAdminUser;
$sAdminPassword = $this->sAdminPassword;
$iCode = 0;
$aPost = array(
'email' => $sEmail,
'password' => $sNewPassword,
);
$aOptions = array(
CURLOPT_URL => $sUrl,
CURLOPT_HEADER => false,
CURLOPT_FAILONERROR => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => \http_build_query($aPost, '', '&'),
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERPWD => $sAdminUser.':'.$sAdminPassword,
CURLOPT_HTTPAUTH => CURLAUTH_BASIC
);
$oCurl = \curl_init();
\curl_setopt_array($oCurl, $aOptions);
$this->WriteLog('Mail-in-a-box: Send post request: '.$sUrl);
$mResult = \curl_exec($oCurl);
$iCode = (int) \curl_getinfo($oCurl, CURLINFO_HTTP_CODE);
$sContentType = (string) \curl_getinfo($oCurl, CURLINFO_CONTENT_TYPE);
$this->WriteLog('Mail-in-a-box: Post request result: (Status: '.$iCode.', ContentType: '.$sContentType.')');
if (false === $mResult || 200 !== $iCode)
{
$this->WriteLog('Mail-in-a-box: Error: '.\curl_error($oCurl), \MailSo\Log\Enumerations\Type::WARNING);
}
if (\is_resource($oCurl))
{
\curl_close($oCurl);
}
if (false !== $mResult && 200 === $iCode)
{
$this->WriteLog('Mail-in-a-box: Password Change Status: Success');
$bResult = true;
}
else
{
$this->WriteLog('Mail-in-a-box[Error]: Empty Response: Code: '.$iCode);
}
}
return $bResult;
}
}

View file

@ -0,0 +1 @@
Plugin that adds functionality to change the email account password (Mail-in-a-Box).

View file

@ -0,0 +1 @@
1.0

View file

@ -0,0 +1,54 @@
<?php
/*
* Mail-in-a-box Password Change Plugin
*
* Based on VirtualminChangePassword
*
* Author: Marius Gripsgard
*/
class MailInABoxChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
public function Init()
{
$this->addHook('main.fabrica', 'MainFabrica');
}
/**
* @param string $sName
* @param mixed $oProvider
*/
public function MainFabrica($sName, &$oProvider)
{
switch ($sName)
{
case 'change-password':
include_once __DIR__.'/MailInABoxChangePasswordDriver.php';
$sHost = \trim($this->Config()->Get('plugin', 'host', ''));
$sAdminUser = (string) $this->Config()->Get('plugin', 'admin_user', '');
$sAdminPassword = (string) $this->Config()->Get('plugin', 'admin_password', '');
$oProvider = new \MailInABoxChangePasswordDriver();
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
$oProvider->SetConfig($sHost, $sAdminUser, $sAdminPassword);
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
break;
}
}
/**
* @return array
*/
public function configMapping()
{
return array(
\RainLoop\Plugins\Property::NewInstance('host')->SetLabel('Mail-in-a-box Host')
->SetDefaultValue('https://box.mailinabox.email')
->SetDescription('Mail-in-a-box host URL. Example: https://box.mailinabox.email'),
\RainLoop\Plugins\Property::NewInstance('admin_user')->SetLabel('Admin User')
->SetDefaultValue(''),
\RainLoop\Plugins\Property::NewInstance('admin_password')->SetLabel('Admin Password')
->SetDefaultValue(''),
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
->SetDefaultValue('*')
);
}
}

View file

@ -2,6 +2,11 @@
class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{ {
/**
* @var string
*/
private $sEngine = 'MySQL';
/** /**
* @var string * @var string
*/ */
@ -57,6 +62,17 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
*/ */
private $oLogger = null; private $oLogger = null;
/**
* @param string $sEngine
*
* @return \ChangePasswordPostfixAdminDriver
*/
public function SetEngine($sEngine)
{
$this->sEngine = $sEngine;
return $this;
}
/** /**
* @param string $sHost * @param string $sHost
* *
@ -215,7 +231,19 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
{ {
try try
{ {
$sDsn = '';
switch($this->sEngine){
case 'MySQL':
$sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase; $sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
break;
case 'PostgreSQL':
$sDsn = 'pgsql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
break;
default:
$sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase;
break;
}
$oPdo = new \PDO($sDsn, $this->sUser, $this->sPassword); $oPdo = new \PDO($sDsn, $this->sUser, $this->sPassword);
$oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
@ -258,7 +286,11 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
private function cryptPassword($sPassword, $oPdo) private function cryptPassword($sPassword, $oPdo)
{ {
$sResult = ''; $sResult = '';
if (function_exists('random_bytes')) {
$sSalt = substr(base64_encode(random_bytes(32)), 0, 16);
} else {
$sSalt = substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 16); $sSalt = substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 16);
}
switch (strtolower($this->sEncrypt)) switch (strtolower($this->sEncrypt))
{ {
default: default:
@ -289,6 +321,7 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
break; break;
case 'mysql_encrypt': case 'mysql_encrypt':
if($this->sEngine == 'MySQL'){
$oStmt = $oPdo->prepare('SELECT ENCRYPT(?) AS encpass'); $oStmt = $oPdo->prepare('SELECT ENCRYPT(?) AS encpass');
if ($oStmt->execute(array($sPassword))) if ($oStmt->execute(array($sPassword)))
{ {
@ -298,6 +331,9 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass
$sResult = $aFetchResult[0]['encpass']; $sResult = $aFetchResult[0]['encpass'];
} }
} }
}else{
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CouldNotSaveNewPassword);
}
break; break;
} }

View file

@ -1 +1 @@
1.2 1.3

View file

@ -14,13 +14,13 @@ class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
if (!extension_loaded('pdo') || !class_exists('PDO')) if (!extension_loaded('pdo') || !class_exists('PDO'))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO must be installed to use this plugin';
} }
$aDrivers = \PDO::getAvailableDrivers(); $aDrivers = \PDO::getAvailableDrivers();
if (!is_array($aDrivers) || !in_array('mysql', $aDrivers)) if (!is_array($aDrivers) || (!in_array('mysql', $aDrivers) && !in_array('pgsql', $aDrivers)))
{ {
return 'The PHP exention PDO (mysql) must be installed to use this plugin'; return 'The PHP extension PDO (mysql or pgsql) must be installed to use this plugin';
} }
return ''; return '';
@ -41,6 +41,7 @@ class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
$oProvider = new ChangePasswordPostfixAdminDriver(); $oProvider = new ChangePasswordPostfixAdminDriver();
$oProvider $oProvider
->SetEngine($this->Config()->Get('plugin', 'engine',''))
->SetHost($this->Config()->Get('plugin', 'host', '')) ->SetHost($this->Config()->Get('plugin', 'host', ''))
->SetPort((int) $this->Config()->Get('plugin', 'port', 3306)) ->SetPort((int) $this->Config()->Get('plugin', 'port', 3306))
->SetDatabase($this->Config()->Get('plugin', 'database', '')) ->SetDatabase($this->Config()->Get('plugin', 'database', ''))
@ -64,22 +65,26 @@ class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
public function configMapping() public function configMapping()
{ {
return array( return array(
\RainLoop\Plugins\Property::NewInstance('host')->SetLabel('MySQL Host') \RainLoop\Plugins\Property::NewInstance('engine')->SetLabel('Engine')
->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION)
->SetDefaultValue(array('MySQL', 'PostgreSQL'))
->SetDescription('Database Engine'),
\RainLoop\Plugins\Property::NewInstance('host')->SetLabel('Host')
->SetDefaultValue('127.0.0.1'), ->SetDefaultValue('127.0.0.1'),
\RainLoop\Plugins\Property::NewInstance('port')->SetLabel('MySQL Port') \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('Port')
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
->SetDefaultValue(3306), ->SetDefaultValue(3306),
\RainLoop\Plugins\Property::NewInstance('database')->SetLabel('MySQL Database') \RainLoop\Plugins\Property::NewInstance('database')->SetLabel('Database')
->SetDefaultValue('postfixadmin'), ->SetDefaultValue('postfixadmin'),
\RainLoop\Plugins\Property::NewInstance('table')->SetLabel('MySQL table') \RainLoop\Plugins\Property::NewInstance('table')->SetLabel('table')
->SetDefaultValue('mailbox'), ->SetDefaultValue('mailbox'),
\RainLoop\Plugins\Property::NewInstance('usercol')->SetLabel('MySQL username column') \RainLoop\Plugins\Property::NewInstance('usercol')->SetLabel('username column')
->SetDefaultValue('username'), ->SetDefaultValue('username'),
\RainLoop\Plugins\Property::NewInstance('passcol')->SetLabel('MySQL password column') \RainLoop\Plugins\Property::NewInstance('passcol')->SetLabel('password column')
->SetDefaultValue('password'), ->SetDefaultValue('password'),
\RainLoop\Plugins\Property::NewInstance('user')->SetLabel('MySQL User') \RainLoop\Plugins\Property::NewInstance('user')->SetLabel('User')
->SetDefaultValue('postfixadmin'), ->SetDefaultValue('postfixadmin'),
\RainLoop\Plugins\Property::NewInstance('password')->SetLabel('MySQL Password') \RainLoop\Plugins\Property::NewInstance('password')->SetLabel('Password')
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
->SetDefaultValue(''), ->SetDefaultValue(''),
\RainLoop\Plugins\Property::NewInstance('encrypt')->SetLabel('Encrypt') \RainLoop\Plugins\Property::NewInstance('encrypt')->SetLabel('Encrypt')

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 RainLoop Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1 @@
Plugin that adds functionality to change the email account password (Generic REST).

View file

@ -0,0 +1,172 @@
<?php
class RestChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
/**
* @var string
*/
private $sUrl = '';
/**
* @var string
*/
private $sKey = '';
/**
* @var string
*/
private $sFieldEmail = '';
/**
* @var string
*/
private $sFieldOldpassword = '';
/**
* @var string
*/
private $sFieldNewpassword = '';
/**
* @var string
*/
private $sAllowedEmails = '';
/**
* @var \MailSo\Log\Logger
*/
private $oLogger = null;
/**
* @param string $sHost
* @param int $iPort
*
* @return \RestChangePasswordDriver
*/
public function SetConfig($sUrl, $sKey)
{
$this->sUrl = $sUrl;
$this->sKey = $sKey;
return $this;
}
$oProvider->SetFieldNames($sFieldEmail, $sFieldOldpassword, $sFieldNewpassword);
/**
* @param string $sFieldEmail
* @param string $sFieldOldpassword
* @param string $sFieldNewpassword
*
* @return \RestChangePasswordDriver
*/
public function SetFieldNames($sFieldEmail, $sFieldOldpassword, $sFieldNewpassword)
{
$this->sFieldEmail = $sFieldEmail;
$this->sFieldOldpassword = $sFieldOldpassword;
$this->sFieldNewpassword = $sFieldNewpassword;
return $this;
}
/**
* @param string $sAllowedEmails
*
* @return \RestChangePasswordDriver
*/
public function SetAllowedEmails($sAllowedEmails)
{
$this->sAllowedEmails = $sAllowedEmails;
return $this;
}
/**
* @param \MailSo\Log\Logger $oLogger
*
* @return \RestChangePasswordDriver
*/
public function SetLogger($oLogger)
{
if ($oLogger instanceof \MailSo\Log\Logger)
{
$this->oLogger = $oLogger;
}
return $this;
}
/**
* @param \RainLoop\Account $oAccount
*
* @return bool
*/
public function PasswordChangePossibility($oAccount)
{
return $oAccount && $oAccount->Email() &&
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
}
/**
* @param \RainLoop\Account $oAccount
* @param string $sPrevPassword
* @param string $sNewPassword
*
* @return bool
*/
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
{
if ($this->oLogger)
{
$this->oLogger->Write('Rest: Try to change password for '.$oAccount->Email());
}
$bResult = false;
if (!empty($this->sHost) && 0 < $this->iPort && $oAccount)
{
$sEmail = \trim(\strtolower($oAccount->Email()));
# Adding the REST Api key to the url, try to use always https
$sUrl = str_replace('://', '://'+$this->sKey+"@", $this->sUrl);
$iCode = 0;
$oHttp = \MailSo\Base\Http::SingletonInstance();
if ($this->oLogger)
{
$this->oLogger->Write('Rest[Api Request]:'.$sUrl);
}
$mResult = $oHttp->SendPostRequest($sUrl,
array(
$this->sFieldEmail => $sEmail,
$this->sFieldOldpassword => $sPrevPassword,
$this->sFieldNewpassword => $sNewPassword,
), 'MailSo Http User Agent (v1)', $iCode, $this->oLogger);
if (false !== $mResult && 200 === $iCode)
{
$aRes = null;
@\parse_str($mResult, $aRes);
if (is_array($aRes) && (!isset($aRes['error']) || (int) $aRes['error'] !== 1))
{
$bResult = true;
}
else
{
if ($this->oLogger)
{
$this->oLogger->Write('Rest[Error]: Response: '.$mResult);
}
}
}
else
{
if ($this->oLogger)
{
$this->oLogger->Write('Rest[Error]: Empty Response: Code:'.$iCode);
}
}
}
return $bResult;
}
}

View file

@ -0,0 +1 @@
1.0

View file

@ -0,0 +1,78 @@
<?php
class RestChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
public function Init()
{
$this->addHook('main.fabrica', 'MainFabrica');
}
/**
* @param string $sName
* @param mixed $oProvider
*/
public function MainFabrica($sName, &$oProvider)
{
switch ($sName)
{
case 'change-password':
$sUrl = \trim($this->Config()->Get('plugin', 'rest_url', ''));
$sKey = \trim($this->Config()->Get('plugin', 'rest_key', ''));
$sFieldEmail = \trim($this->Config()->Get('plugin', 'rest_field_email', ''));
$sFieldOldpassword = \trim($this->Config()->Get('plugin', 'rest_field_oldpassword', ''));
$sFieldNewpassword = \trim($this->Config()->Get('plugin', 'rest_field_newpassword', ''));
if (!empty($sHost) && (!empty($sKey)))
{
include_once __DIR__.'/RestChangePasswordDriver.php';
$oProvider = new RestChangePasswordDriver();
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
$oProvider->SetConfig($sUrl, $sKey);
$oProvider->SetFieldNames($sFieldEmail, $sFieldOldpassword, $sFieldNewpassword);
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
}
break;
}
}
/**
* @return array
*/
public function configMapping()
{
return array(
\RainLoop\Plugins\Property::NewInstance('rest_url')
->SetLabel('REST API Url')
->SetDefaultValue('')
->SetDescription('Ex: http://localhost:8080/api/change_password or https://domain.com/api/user/passsword_update'),
\RainLoop\Plugins\Property::NewInstance('rest_key')
->SetLabel('REST API key')
->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
->SetDescription('REST API Key for authentication, if you have "user" and "passsword" enter it as "user:password"')
->SetDefaultValue(''),
\RainLoop\Plugins\Property::NewInstance('rest_field_email')
->SetLabel('Field "email" name')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Enter the name of the REST field name for email')
->SetDefaultValue('email'),
\RainLoop\Plugins\Property::NewInstance('rest_field_oldpassword')
->SetLabel('Field "oldpassword" name')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Enter the name of the REST field name for oldpassword')
->SetDefaultValue('oldpassword'),
\RainLoop\Plugins\Property::NewInstance('rest_field_newpassword')
->SetLabel('Field "newpassword" name')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Enter the name of the REST field name for newpassword')
->SetDefaultValue('newpassword'),
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
->SetDefaultValue('*')
);
}
}

View file

@ -42,7 +42,7 @@ class VestaChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
return array( return array(
\RainLoop\Plugins\Property::NewInstance('vesta_host')->SetLabel('Vesta Host') \RainLoop\Plugins\Property::NewInstance('vesta_host')->SetLabel('Vesta Host')
->SetDefaultValue('') ->SetDefaultValue('')
->SetDescription('Allowed patterns: {user:host-imap}, {user:host-smtp}, {user:domain}'), ->SetDescription('Ex: localhost or domain.com'),
\RainLoop\Plugins\Property::NewInstance('Vesta_port')->SetLabel('Vesta Port') \RainLoop\Plugins\Property::NewInstance('Vesta_port')->SetLabel('Vesta Port')
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
->SetDefaultValue(8083), ->SetDefaultValue(8083),

View file

@ -1 +1,9 @@
Deny from all <ifModule mod_authz_core.c>
Require all denied
</ifModule>
<ifModule !mod_authz_core.c>
Deny from all
</ifModule>
<IfModule mod_autoindex.c>
Options -Indexes
</ifModule>

View file

@ -22,7 +22,7 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH'))
function rainLoopSplAutoloadNamespaces() function rainLoopSplAutoloadNamespaces()
{ {
return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop', 'Predis') : return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop', 'Predis') :
array('RainLoop', 'Facebook', 'GuzzleHttp', 'PHPThumb', 'Predis', 'SabreForRainLoop', 'Imagine', 'Detection'); array('RainLoop', 'Facebook', 'PHPThumb', 'Predis', 'SabreForRainLoop', 'Imagine', 'Detection');
} }
/** /**

View file

@ -1,23 +0,0 @@
<?php
namespace GuzzleHttp\Adapter;
use GuzzleHttp\Message\ResponseInterface;
/**
* Adapter interface used to transfer HTTP requests.
*
* @link http://docs.guzzlephp.org/en/guzzle4/adapters.html for a full
* explanation of adapters and their responsibilities.
*/
interface AdapterInterface
{
/**
* Transfers an HTTP request and populates a response
*
* @param TransactionInterface $transaction Transaction abject to populate
*
* @return ResponseInterface
*/
public function send(TransactionInterface $transaction);
}

View file

@ -1,158 +0,0 @@
<?php
namespace GuzzleHttp\Adapter\Curl;
use GuzzleHttp\Adapter\TransactionInterface;
use GuzzleHttp\Exception\AdapterException;
/**
* Provides context for a Curl transaction, including active handles,
* pending transactions, and whether or not this is a batch or single
* transaction.
*/
class BatchContext
{
/** @var resource Curl multi resource */
private $multi;
/** @var \SplObjectStorage Map of transactions to curl resources */
private $handles;
/** @var \Iterator Yields pending transactions */
private $pending;
/** @var bool Whether or not to throw transactions */
private $throwsExceptions;
/**
* @param resource $multiHandle Initialized curl_multi resource
* @param bool $throwsExceptions Whether or not exceptions are thrown
* @param \Iterator $pending Iterator yielding pending transactions
*/
public function __construct(
$multiHandle,
$throwsExceptions,
\Iterator $pending = null
) {
$this->multi = $multiHandle;
$this->handles = new \SplObjectStorage();
$this->throwsExceptions = $throwsExceptions;
$this->pending = $pending;
}
/**
* Find a transaction for a given curl handle
*
* @param resource $handle Curl handle
*
* @return TransactionInterface
* @throws AdapterException if a transaction is not found
*/
public function findTransaction($handle)
{
foreach ($this->handles as $transaction) {
if ($this->handles[$transaction] === $handle) {
return $transaction;
}
}
throw new AdapterException('No curl handle was found');
}
/**
* Returns true if there are any remaining pending transactions
*
* @return bool
*/
public function hasPending()
{
return $this->pending && $this->pending->valid();
}
/**
* Pop the next transaction from the transaction queue
*
* @return TransactionInterface|null
*/
public function nextPending()
{
if (!$this->hasPending()) {
return null;
}
$current = $this->pending->current();
$this->pending->next();
return $current;
}
/**
* Checks if the batch is to throw exceptions on error
*
* @return bool
*/
public function throwsExceptions()
{
return $this->throwsExceptions;
}
/**
* Get the curl_multi handle
*
* @return resource
*/
public function getMultiHandle()
{
return $this->multi;
}
/**
* Add a transaction to the multi handle
*
* @param TransactionInterface $transaction Transaction to add
* @param resource $handle Resource to use with the handle
*
* @throws AdapterException If the handle is already registered
*/
public function addTransaction(TransactionInterface $transaction, $handle)
{
if (isset($this->handles[$transaction])) {
throw new AdapterException('Transaction already registered');
}
$code = curl_multi_add_handle($this->multi, $handle);
if ($code != CURLM_OK) {
MultiAdapter::throwMultiError($code);
}
$this->handles[$transaction] = $handle;
}
/**
* Remove a transaction and associated handle from the context
*
* @param TransactionInterface $transaction Transaction to remove
*
* @return array Returns the curl_getinfo array
* @throws AdapterException if the transaction is not found
*/
public function removeTransaction(TransactionInterface $transaction)
{
if (!isset($this->handles[$transaction])) {
throw new AdapterException('Transaction not registered');
}
$handle = $this->handles[$transaction];
$code = curl_multi_remove_handle($this->multi, $handle);
if ($code != CURLM_OK) {
MultiAdapter::throwMultiError($code);
}
$info = curl_getinfo($handle);
curl_close($handle);
unset($this->handles[$transaction]);
return $info;
}
}

Some files were not shown because too many files have changed in this diff Show more