From a32c1f2ee01450d778749edea392a14184c6f678 Mon Sep 17 00:00:00 2001
From: Artem Baskal <a.baskal@adguard.com>
Date: Wed, 15 Jul 2020 12:35:37 +0300
Subject: [PATCH] - client: Fix client Access settings normalization Close
 #1820

Squashed commit of the following:

commit 5aadec2e6e126588313ff006d6f95223ba19a526
Merge: a4db6b42 95f41285
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 11:15:31 2020 +0300

    Merge branch 'master' into fix/1820

commit a4db6b42ab9cbf43d96c783a72a99e0a2c594108
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 19:08:09 2020 +0300

    Remove textarea comma splitting

commit bb34797aac6602b405941dbd90fe6a81b663bb92
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 18:21:18 2020 +0300

    Fix client Access settings normalization

commit ac4fb536514f54c5722077d78dbbd981c4e906a8
Merge: 0c758ddc b9fca8d0
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 18:14:38 2020 +0300

    Merge branch 'master' into fix/1820

commit 0c758ddcd738136b92e6f947a8068ecc59f7ec25
Merge: 15650db3 f5a1f311
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 3 11:22:00 2020 +0300

    Merge branch 'master' into fix/1820

commit 15650db35323009001fd427a74a312705b54ac86
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jun 29 12:01:51 2020 +0300

    '- client: Don't normalise disallowed domains'
---
 client/src/actions/access.js                  |  8 ++---
 client/src/actions/dnsConfig.js               |  6 ++--
 client/src/actions/index.js                   |  6 ++--
 .../Settings/Clients/ClientsTable.js          |  4 +--
 .../components/Settings/Dns/Access/Form.js    | 13 ++++++--
 .../components/Settings/Dns/Upstream/Form.js  | 13 +++++---
 client/src/components/Settings/Dns/index.js   |  5 ++-
 client/src/helpers/helpers.js                 | 31 ++++++++++++-------
 8 files changed, 54 insertions(+), 32 deletions(-)

diff --git a/client/src/actions/access.js b/client/src/actions/access.js
index 8e1efab0..e04daac0 100644
--- a/client/src/actions/access.js
+++ b/client/src/actions/access.js
@@ -2,9 +2,9 @@ import { createAction } from 'redux-actions';
 import i18next from 'i18next';
 
 import apiClient from '../api/Api';
-import { normalizeTextarea } from '../helpers/helpers';
 import { addErrorToast, addSuccessToast } from './toasts';
 import { BLOCK_ACTIONS } from '../helpers/constants';
+import { splitByNewLine } from '../helpers/helpers';
 
 export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
 export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
@@ -31,9 +31,9 @@ export const setAccessList = (config) => async (dispatch) => {
         const { allowed_clients, disallowed_clients, blocked_hosts } = config;
 
         const values = {
-            allowed_clients: normalizeTextarea(allowed_clients),
-            disallowed_clients: normalizeTextarea(disallowed_clients),
-            blocked_hosts: normalizeTextarea(blocked_hosts),
+            allowed_clients: splitByNewLine(allowed_clients),
+            disallowed_clients: splitByNewLine(disallowed_clients),
+            blocked_hosts: splitByNewLine(blocked_hosts),
         };
 
         await apiClient.setAccessList(values);
diff --git a/client/src/actions/dnsConfig.js b/client/src/actions/dnsConfig.js
index 947638c3..c599ca8d 100644
--- a/client/src/actions/dnsConfig.js
+++ b/client/src/actions/dnsConfig.js
@@ -1,7 +1,7 @@
 import { createAction } from 'redux-actions';
 
 import apiClient from '../api/Api';
-import { normalizeTextarea } from '../helpers/helpers';
+import { splitByNewLine } from '../helpers/helpers';
 import { addErrorToast, addSuccessToast } from './toasts';
 
 export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
@@ -30,11 +30,11 @@ export const setDnsConfig = (config) => async (dispatch) => {
 
         let hasDnsSettings = false;
         if (Object.prototype.hasOwnProperty.call(data, 'bootstrap_dns')) {
-            data.bootstrap_dns = normalizeTextarea(config.bootstrap_dns);
+            data.bootstrap_dns = splitByNewLine(config.bootstrap_dns);
             hasDnsSettings = true;
         }
         if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
-            data.upstream_dns = normalizeTextarea(config.upstream_dns);
+            data.upstream_dns = splitByNewLine(config.upstream_dns);
             hasDnsSettings = true;
         }
 
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 2cef57e6..3d3f1b63 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -2,7 +2,7 @@ import { createAction } from 'redux-actions';
 import i18next from 'i18next';
 import axios from 'axios';
 
-import { isVersionGreater, normalizeTextarea, sortClients } from '../helpers/helpers';
+import { isVersionGreater, splitByNewLine, sortClients } from '../helpers/helpers';
 import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
 import { getTlsStatus } from './encryption';
 import apiClient from '../api/Api';
@@ -279,8 +279,8 @@ export const testUpstream = (config) => async (dispatch) => {
     dispatch(testUpstreamRequest());
     try {
         const values = { ...config };
-        values.bootstrap_dns = normalizeTextarea(values.bootstrap_dns);
-        values.upstream_dns = normalizeTextarea(values.upstream_dns);
+        values.bootstrap_dns = splitByNewLine(values.bootstrap_dns);
+        values.upstream_dns = splitByNewLine(values.upstream_dns);
 
         const upstreamResponse = await apiClient.testUpstream(values);
         const testMessages = Object.keys(upstreamResponse)
diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js
index e767cfb9..a179ac30 100644
--- a/client/src/components/Settings/Clients/ClientsTable.js
+++ b/client/src/components/Settings/Clients/ClientsTable.js
@@ -4,7 +4,7 @@ import { Trans, withTranslation } from 'react-i18next';
 import ReactTable from 'react-table';
 
 import { MODAL_TYPE } from '../../../helpers/constants';
-import { normalizeTextarea } from '../../../helpers/helpers';
+import { splitByNewLine } from '../../../helpers/helpers';
 import Card from '../../ui/Card';
 import Modal from './Modal';
 import CellWrap from '../../ui/CellWrap';
@@ -30,7 +30,7 @@ class ClientsTable extends Component {
             }
 
             if (values.upstreams && typeof values.upstreams === 'string') {
-                config.upstreams = normalizeTextarea(values.upstreams);
+                config.upstreams = splitByNewLine(values.upstreams);
             } else {
                 config.upstreams = [];
             }
diff --git a/client/src/components/Settings/Dns/Access/Form.js b/client/src/components/Settings/Dns/Access/Form.js
index 8edc3189..4f8342f0 100644
--- a/client/src/components/Settings/Dns/Access/Form.js
+++ b/client/src/components/Settings/Dns/Access/Form.js
@@ -4,7 +4,10 @@ import { Field, reduxForm } from 'redux-form';
 import { Trans, withTranslation } from 'react-i18next';
 import flow from 'lodash/flow';
 import { renderTextareaField } from '../../../../helpers/form';
-import { normalizeMultiline } from '../../../../helpers/helpers';
+import {
+    trimMultilineString,
+    removeEmptyLines,
+} from '../../../../helpers/helpers';
 import { FORM_NAME } from '../../../../helpers/constants';
 
 const fields = [
@@ -12,16 +15,19 @@ const fields = [
         id: 'allowed_clients',
         title: 'access_allowed_title',
         subtitle: 'access_allowed_desc',
+        normalizeOnBlur: removeEmptyLines,
     },
     {
         id: 'disallowed_clients',
         title: 'access_disallowed_title',
         subtitle: 'access_disallowed_desc',
+        normalizeOnBlur: trimMultilineString,
     },
     {
         id: 'blocked_hosts',
         title: 'access_blocked_title',
         subtitle: 'access_blocked_desc',
+        normalizeOnBlur: removeEmptyLines,
     },
 ];
 
@@ -31,7 +37,7 @@ const Form = (props) => {
     } = props;
 
     const renderField = ({
-        id, title, subtitle, disabled = processingSet,
+        id, title, subtitle, disabled = processingSet, normalizeOnBlur,
     }) => <div key={id} className="form__group mb-5">
         <label className="form__label form__label--with-desc" htmlFor={id}>
             <Trans>{title}</Trans>
@@ -46,7 +52,7 @@ const Form = (props) => {
             type="text"
             className="form-control form-control--textarea font-monospace"
             disabled={disabled}
-            normalizeOnBlur={id === 'disallowed_clients' ? normalizeMultiline : undefined}
+            normalizeOnBlur={normalizeOnBlur}
         />
     </div>;
 
@@ -55,6 +61,7 @@ const Form = (props) => {
         title: PropTypes.string,
         subtitle: PropTypes.string,
         disabled: PropTypes.bool,
+        normalizeOnBlur: PropTypes.func,
     };
 
     return (
diff --git a/client/src/components/Settings/Dns/Upstream/Form.js b/client/src/components/Settings/Dns/Upstream/Form.js
index 99ae9a95..8174b54e 100644
--- a/client/src/components/Settings/Dns/Upstream/Form.js
+++ b/client/src/components/Settings/Dns/Upstream/Form.js
@@ -6,9 +6,10 @@ import { Trans, useTranslation } from 'react-i18next';
 import classnames from 'classnames';
 
 import Examples from './Examples';
-import { renderRadioField } from '../../../../helpers/form';
+import { renderRadioField, renderTextareaField } from '../../../../helpers/form';
 import { DNS_REQUEST_OPTIONS, FORM_NAME } from '../../../../helpers/constants';
 import { testUpstream } from '../../../../actions';
+import { removeEmptyLines } from '../../../../helpers/helpers';
 
 const getInputFields = () => [{
     // eslint-disable-next-line react/display-name
@@ -17,9 +18,10 @@ const getInputFields = () => [{
     </label>,
     name: 'upstream_dns',
     type: 'text',
-    component: 'textarea',
+    component: renderTextareaField,
     className: 'form-control form-control--textarea font-monospace',
     placeholder: 'upstream_dns',
+    normalizeOnBlur: removeEmptyLines,
 },
 {
     name: 'upstream_mode',
@@ -69,7 +71,8 @@ const Form = ({
     return <form onSubmit={handleSubmit}>
         <div className="row">
             {INPUT_FIELDS.map(({
-                name, component, type, className, placeholder, getTitle, subtitle, disabled, value,
+                name, component, type, className, placeholder,
+                getTitle, subtitle, disabled, value, normalizeOnBlur,
             }) => <div className="col-12 mb-4" key={placeholder}>
                 {typeof getTitle === 'function' && getTitle()}
                 <Field
@@ -82,6 +85,7 @@ const Form = ({
                     placeholder={t(placeholder)}
                     subtitle={t(subtitle)}
                     disabled={processingSetConfig || processingTestUpstream || disabled}
+                    normalizeOnBlur={normalizeOnBlur}
                 />
             </div>)}
             <div className="col-12">
@@ -101,11 +105,12 @@ const Form = ({
                 <Field
                     id="bootstrap_dns"
                     name="bootstrap_dns"
-                    component="textarea"
+                    component={renderTextareaField}
                     type="text"
                     className="form-control form-control--textarea form-control--textarea-small font-monospace"
                     placeholder={t('bootstrap_dns')}
                     disabled={processingSetConfig}
+                    normalizeOnBlur={removeEmptyLines}
                 />
             </div>
         </div>
diff --git a/client/src/components/Settings/Dns/index.js b/client/src/components/Settings/Dns/index.js
index 41993f89..03a9e25b 100644
--- a/client/src/components/Settings/Dns/index.js
+++ b/client/src/components/Settings/Dns/index.js
@@ -45,7 +45,10 @@ const Dns = (props) => {
                         dnsConfig={dnsConfig}
                         setDnsConfig={setDnsConfig}
                     />
-                    <Access access={access} setAccessList={setAccessList} />
+                    <Access
+                        access={access}
+                        setAccessList={setAccessList}
+                    />
                 </>}
         </>
     );
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index e075a3c5..a6426ba5 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -306,15 +306,26 @@ export const redirectToCurrentProtocol = (values, httpPort = 80) => {
     }
 };
 
-export const normalizeTextarea = (text) => {
-    if (!text) {
-        return [];
-    }
+/**
+ * @param {string} text
+ * @returns []string
+ */
+export const splitByNewLine = (text) => text.split('\n')
+    .filter((n) => n.trim());
 
-    return text.replace(/[;, ]/g, '\n')
-        .split('\n')
-        .filter((n) => n);
-};
+/**
+ * @param {string} text
+ * @returns {string}
+ */
+export const trimMultilineString = (text) => splitByNewLine(text)
+    .map((line) => line.trim()).join('\n');
+
+/**
+ * @param {string} text
+ * @returns {string}
+ */
+export const removeEmptyLines = (text) => splitByNewLine(text)
+    .join('\n');
 
 /**
  * Normalizes the topClients array
@@ -533,10 +544,6 @@ export const getMap = (arr, key, value) => arr.reduce((acc, curr) => {
     return acc;
 }, {});
 
-export const normalizeMultiline = (multiline) => `${normalizeTextarea(multiline)
-    .map((line) => line.trim())
-    .join('\n')}\n`;
-
 /**
  * @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
  * @param cidr {array} ipaddr.js CIDR array