Skip to content

Commit

Permalink
Merge pull request #31219 from Expensify/revert-27414-@kosmydel/handl…
Browse files Browse the repository at this point in the history
…e-invisible-characters

[CP-staging] Revert "Handle invisible characters in forms"
  • Loading branch information
marcaaron authored Nov 11, 2023
2 parents 9f9cb09 + 7c65b04 commit 5e2c39f
Show file tree
Hide file tree
Showing 9 changed files with 33 additions and 313 deletions.
4 changes: 0 additions & 4 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,10 +1366,6 @@ const CONST = {
ILLEGAL_FILENAME_CHARACTERS: /\/|<|>|\*|"|:|\?|\\|\|/g,

ENCODE_PERCENT_CHARACTER: /%(25)+/g,

INVISIBLE_CHARACTERS_GROUPS: /[\p{C}\p{Z}]/gu,

OTHER_INVISIBLE_CHARACTERS: /[\u3164]/g,
},

PRONOUNS: {
Expand Down
22 changes: 12 additions & 10 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import stylePropTypes from '@styles/stylePropTypes';
import styles from '@styles/styles';
Expand Down Expand Up @@ -127,8 +126,14 @@ function Form(props) {
*/
const onValidate = useCallback(
(values, shouldClearServerError = true) => {
// Trim all string values
const trimmedStringValues = ValidationUtils.prepareValues(values);
const trimmedStringValues = {};
_.each(values, (inputValue, inputID) => {
if (_.isString(inputValue)) {
trimmedStringValues[inputID] = inputValue.trim();
} else {
trimmedStringValues[inputID] = inputValue;
}
});

if (shouldClearServerError) {
FormActions.setErrors(props.formID, null);
Expand Down Expand Up @@ -186,7 +191,7 @@ function Form(props) {

return touchedInputErrors;
},
[props.formID, validate, errors],
[errors, touchedInputs, props.formID, validate],
);

useEffect(() => {
Expand Down Expand Up @@ -223,14 +228,11 @@ function Form(props) {
return;
}

// Trim all string values
const trimmedStringValues = ValidationUtils.prepareValues(inputValues);

// Touches all form inputs so we can validate the entire form
_.each(inputRefs.current, (inputRef, inputID) => (touchedInputs.current[inputID] = true));

// Validate form and return early if any errors are found
if (!_.isEmpty(onValidate(trimmedStringValues))) {
if (!_.isEmpty(onValidate(inputValues))) {
return;
}

Expand All @@ -240,8 +242,8 @@ function Form(props) {
}

// Call submit handler
onSubmit(trimmedStringValues);
}, [props.formState.isLoading, props.network.isOffline, props.enabledWhenOffline, inputValues, onValidate, onSubmit]);
onSubmit(inputValues);
}, [props.formState, onSubmit, inputRefs, inputValues, onValidate, touchedInputs, props.network.isOffline, props.enabledWhenOffline]);

/**
* Loops over Form's children and automatically supplies Form props to them
Expand Down
17 changes: 10 additions & 7 deletions src/components/Form/FormProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import _ from 'underscore';
import networkPropTypes from '@components/networkPropTypes';
import {withNetwork} from '@components/OnyxProvider';
import compose from '@libs/compose';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import stylePropTypes from '@styles/stylePropTypes';
import * as FormActions from '@userActions/FormActions';
Expand Down Expand Up @@ -109,7 +108,14 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC

const onValidate = useCallback(
(values, shouldClearServerError = true) => {
const trimmedStringValues = ValidationUtils.prepareValues(values);
const trimmedStringValues = {};
_.each(values, (inputValue, inputID) => {
if (_.isString(inputValue)) {
trimmedStringValues[inputID] = inputValue.trim();
} else {
trimmedStringValues[inputID] = inputValue;
}
});

if (shouldClearServerError) {
FormActions.setErrors(formID, null);
Expand Down Expand Up @@ -180,14 +186,11 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC
return;
}

// Prepare values before submitting
const trimmedStringValues = ValidationUtils.prepareValues(inputValues);

// Touches all form inputs so we can validate the entire form
_.each(inputRefs.current, (inputRef, inputID) => (touchedInputs.current[inputID] = true));

// Validate form and return early if any errors are found
if (!_.isEmpty(onValidate(trimmedStringValues))) {
if (!_.isEmpty(onValidate(inputValues))) {
return;
}

Expand All @@ -196,7 +199,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC
return;
}

onSubmit(trimmedStringValues);
onSubmit(inputValues);
}, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]);

const registerInput = useCallback(
Expand Down
48 changes: 1 addition & 47 deletions src/libs/StringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,4 @@ function sanitizeString(str: string): string {
return _.deburr(str).toLowerCase().replaceAll(CONST.REGEX.NON_ALPHABETIC_AND_NON_LATIN_CHARS, '');
}

/**
* Check if the string would be empty if all invisible characters were removed.
*/
function isEmptyString(value: string): boolean {
// \p{C} matches all 'Other' characters
// \p{Z} matches all separators (spaces etc.)
// Source: http://www.unicode.org/reports/tr18/#General_Category_Property
let transformed = value.replace(CONST.REGEX.INVISIBLE_CHARACTERS_GROUPS, '');

// Remove other invisible characters that are not in the above unicode categories
transformed = transformed.replace(CONST.REGEX.OTHER_INVISIBLE_CHARACTERS, '');

// Check if after removing invisible characters the string is empty
return transformed === '';
}

/**
* Remove invisible characters from a string except for spaces and format characters for emoji, and trim it.
*/
function removeInvisibleCharacters(value: string): string {
let result = value;

// Remove spaces:
// - \u200B: zero-width space
// - \u00A0: non-breaking space
// - \u2060: word joiner
result = result.replace(/[\u200B\u00A0\u2060]/g, '');

// Remove all characters from the 'Other' (C) category except for format characters (Cf)
// because some of them are used for emojis
result = result.replace(/[\p{Cc}\p{Cs}\p{Co}\p{Cn}]/gu, '');

// Remove characters from the (Cf) category that are not used for emojis
result = result.replace(/[\u200E-\u200F]/g, '');

// Remove all characters from the 'Separator' (Z) category except for Space Separator (Zs)
result = result.replace(/[\p{Zl}\p{Zp}]/gu, '');

// If the result consist of only invisible characters, return an empty string
if (isEmptyString(result)) {
return '';
}

return result.trim();
}

export default {sanitizeString, isEmptyString, removeInvisibleCharacters};
export default {sanitizeString};
23 changes: 1 addition & 22 deletions src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {Report} from '@src/types/onyx';
import * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import * as CardUtils from './CardUtils';
import * as LoginUtils from './LoginUtils';
import StringUtils from './StringUtils';

/**
* Implements the Luhn Algorithm, a checksum formula used to validate credit card
Expand Down Expand Up @@ -74,7 +73,7 @@ function isValidPastDate(date: string | Date): boolean {
*/
function isRequiredFulfilled(value: string | Date | unknown[] | Record<string, unknown>): boolean {
if (typeof value === 'string') {
return !StringUtils.isEmptyString(value);
return value.trim().length > 0;
}

if (isDate(value)) {
Expand Down Expand Up @@ -353,25 +352,6 @@ function isValidAccountRoute(accountID: number): boolean {
return CONST.REGEX.NUMBER.test(String(accountID)) && accountID > 0;
}

type ValuesType = Record<string, unknown>;

/**
* This function is used to remove invisible characters from strings before validation and submission.
*/
function prepareValues(values: ValuesType): ValuesType {
const trimmedStringValues: ValuesType = {};

for (const [inputID, inputValue] of Object.entries(values)) {
if (typeof inputValue === 'string') {
trimmedStringValues[inputID] = StringUtils.removeInvisibleCharacters(inputValue);
} else {
trimmedStringValues[inputID] = inputValue;
}
}

return trimmedStringValues;
}

export {
meetsMinimumAgeRequirement,
meetsMaximumAgeRequirement,
Expand Down Expand Up @@ -405,5 +385,4 @@ export {
isNumeric,
isValidAccountRoute,
isValidRecoveryCode,
prepareValues,
};
3 changes: 1 addition & 2 deletions src/pages/workspace/WorkspaceSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import styles from '@styles/styles';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -78,7 +77,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) {
const errors = {};
const name = values.name.trim();

if (!ValidationUtils.isRequiredFulfilled(name)) {
if (!name || !name.length) {
errors.name = 'workspace.editor.nameIsRequiredError';
} else if ([...name].length > CONST.WORKSPACE_NAME_CHARACTER_LIMIT) {
// Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16
Expand Down
17 changes: 8 additions & 9 deletions src/stories/Form.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import StatePicker from '@components/StatePicker';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import NetworkConnection from '@libs/NetworkConnection';
import * as ValidationUtils from '@libs/ValidationUtils';
import styles from '@styles/styles';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -178,28 +177,28 @@ const defaultArgs = {
submitButtonText: 'Submit',
validate: (values) => {
const errors = {};
if (!ValidationUtils.isRequiredFulfilled(values.routingNumber)) {
if (!values.routingNumber) {
errors.routingNumber = 'Please enter a routing number';
}
if (!ValidationUtils.isRequiredFulfilled(values.accountNumber)) {
if (!values.accountNumber) {
errors.accountNumber = 'Please enter an account number';
}
if (!ValidationUtils.isRequiredFulfilled(values.street)) {
if (!values.street) {
errors.street = 'Please enter an address';
}
if (!ValidationUtils.isRequiredFulfilled(values.dob)) {
if (!values.dob) {
errors.dob = 'Please enter your date of birth';
}
if (!ValidationUtils.isRequiredFulfilled(values.pickFruit)) {
if (!values.pickFruit) {
errors.pickFruit = 'Please select a fruit';
}
if (!ValidationUtils.isRequiredFulfilled(values.pickAnotherFruit)) {
if (!values.pickAnotherFruit) {
errors.pickAnotherFruit = 'Please select a fruit';
}
if (!ValidationUtils.isRequiredFulfilled(values.state)) {
if (!values.state) {
errors.state = 'Please select a state';
}
if (!ValidationUtils.isRequiredFulfilled(values.checkbox)) {
if (!values.checkbox) {
errors.checkbox = 'You must accept the Terms of Service to continue';
}
return errors;
Expand Down
92 changes: 0 additions & 92 deletions tests/unit/isEmptyString.js

This file was deleted.

Loading

0 comments on commit 5e2c39f

Please sign in to comment.