fix: 修复 isUrl 校验问题, 扩充参数 (#2797)

This commit is contained in:
liaoxuezhi 2021-10-29 16:14:30 +08:00 committed by GitHub
parent e5bd28d8c9
commit adfa9f6d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 12 deletions

View File

@ -677,6 +677,34 @@ amis 会有默认的报错信息,如果你想自定义校验信息,配置`va
- `matchRegexp3:/foo/` 必须命中某个正则。
- `matchRegexp4:/foo/` 必须命中某个正则。
#### 验证只允许 http 协议的 url 地址
> 1.4.0 及以上版本
isUrl 可以配置如下参数
- schemes 协议,默认是为: `['http', 'https', 'ftp', 'sftp']`
- allowLocal 是否允许填写本地地址
- allowDataUrl 是否允许 dataUrl
```schema: scope="body"
{
"type": "form",
"body": [
{
"name": "url",
"type": "input-text",
"label": "只允许 https 打头的 url",
"validations": {
"isUrl": {
"schemes": ["https"]
}
}
}
]
}
```
### 自定义校验函数
可以自己写代码扩展表单验证,请参考 [这里](../../docs/extend/addon#扩展表单验证)

View File

@ -14,6 +14,79 @@ const makeRegexp = (reg: string | RegExp) => {
return /^$/;
};
import memoize from 'lodash/memoize';
import isPlainObject from 'lodash/isPlainObject';
const makeUrlRegexp = memoize(function (options: any) {
options = {
schemes: ['http', 'https', 'ftp', 'sftp'],
allowLocal: true,
allowDataUrl: false,
...(isPlainObject(options) ? options : {})
};
// https://github.com/ansman/validate.js/blob/master/validate.js#L1098-L1164
let {schemes, allowLocal, allowDataUrl} = options;
if (!Array.isArray(schemes)) {
schemes = ['http', 'https', 'ftp', 'sftp'];
}
let regex =
'^' +
// protocol identifier
'(?:(?:' +
schemes.join('|') +
')://)' +
// user:pass authentication
'(?:\\S+(?::\\S*)?@)?' +
'(?:';
var tld = '(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))';
if (allowLocal) {
tld += '?';
} else {
regex +=
// IP address exclusion
// private & local networks
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})';
}
regex +=
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
'|' +
// host name
'(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' +
// domain name
'(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' +
tld +
')' +
// port number
'(?::\\d{2,5})?' +
// resource path
'(?:[/?#]\\S*)?' +
'$';
if (allowDataUrl) {
// RFC 2397
var mediaType = '\\w+\\/[-+.\\w]+(?:;[\\w=]+)*';
var urlchar = "[A-Za-z0-9-_.!~\\*'();\\/?:@&=+$,%]*";
var dataurl = 'data:(?:' + mediaType + ')?(?:;base64)?,' + urlchar;
regex = '(?:' + regex + ')|(?:^' + dataurl + '$)';
}
return new RegExp(regex, 'i');
});
export interface ValidateFn {
(
@ -55,12 +128,8 @@ export const validations: {
/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i
);
},
isUrl: function (values, value) {
return validations.matchRegexp(
values,
value,
/^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
);
isUrl: function (values, value, options) {
return validations.matchRegexp(values, value, makeUrlRegexp(options));
},
isTrue: function (values, value) {
return value === true;
@ -282,9 +351,8 @@ export function validate(
}
const fn = validations[ruleName];
const args = (Array.isArray(rules[ruleName])
? rules[ruleName]
: [rules[ruleName]]
const args = (
Array.isArray(rules[ruleName]) ? rules[ruleName] : [rules[ruleName]]
).map((item: any) => {
if (typeof item === 'string' && isPureVariable(item)) {
return resolveVariableAndFilter(item, values, '|raw');
@ -356,9 +424,9 @@ const splitValidations = function (str: string): Array<string> {
.map(str => (/^__\d+$/.test(str) ? placeholder[str] : str.trim()));
};
export function str2rules(
validations: string | {[propName: string]: any}
): {[propName: string]: any} {
export function str2rules(validations: string | {[propName: string]: any}): {
[propName: string]: any;
} {
if (typeof validations === 'string') {
return validations
? splitValidations(validations).reduce(function (