@qiniu/miku-delivery-mp-ks
Version:
Kuaishou Mini Program SDK for Miku Delivery
734 lines (733 loc) • 26.3 kB
JavaScript
/**
* class URL 实现(小程序环境没有提供内置的 class URL)
* 参考 https://github.com/webcomponents/polyfills/blob/master/packages/url/url.js
*/
const relative = Object.create(null);
relative['ftp'] = 21;
relative['file'] = 0;
relative['gopher'] = 70;
relative['http'] = 80;
relative['https'] = 443;
relative['ws'] = 80;
relative['wss'] = 443;
const relativePathDotMapping = Object.create(null);
relativePathDotMapping['%2e'] = '.';
relativePathDotMapping['.%2e'] = '..';
relativePathDotMapping['%2e.'] = '..';
relativePathDotMapping['%2e%2e'] = '..';
function isRelativeScheme(scheme) {
return relative[scheme] !== undefined;
}
function percentEscape(c) {
var unicode = c.charCodeAt(0);
if (unicode > 0x20 &&
unicode < 0x7f &&
// " # < > ? `
[0x22, 0x23, 0x3c, 0x3e, 0x3f, 0x60].indexOf(unicode) == -1) {
return c;
}
return encodeURIComponent(c);
}
function percentEscapeQuery(c) {
// XXX This actually needs to encode c using encoding and then
// convert the bytes one-by-one.
var unicode = c.charCodeAt(0);
if (unicode > 0x20 &&
unicode < 0x7f &&
// " # < > ` (do not escape '?')
[0x22, 0x23, 0x3c, 0x3e, 0x60].indexOf(unicode) == -1) {
return c;
}
return encodeURIComponent(c);
}
const EOF = undefined;
const ALPHA = /[a-zA-Z]/;
const ALPHANUMERIC = /[a-zA-Z0-9+\-.]/;
/**
* 同 Web 中的 class URL
* 如果命名为 `URL`,小程序开发工具中会报 `SyntaxError: Identifier 'URL' has already been declared`,因此命名为 `Polyfilled_URL`
*/
export class Polyfilled_URL {
constructor(url, base /* , encoding */) {
Object.defineProperty(this, "_url", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_scheme", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_schemeData", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_username", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_password", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "_host", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_port", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_path", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "_query", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_fragment", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "_isInvalid", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_isRelative", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
if (base !== undefined && !(base instanceof Polyfilled_URL)) {
base = new Polyfilled_URL(String(base));
}
this._url = '' + url;
this.clear();
const input = this._url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
// const encoding = encoding || 'utf-8'
this.parse(input, undefined, base);
}
toString() {
return this.href;
}
get href() {
if (this._isInvalid) {
return this._url;
}
var authority = '';
if ('' != this._username || null != this._password) {
authority =
this._username +
(null != this._password ? ':' + this._password : '') +
'@';
}
return (this.protocol +
(this._isRelative ? '//' + authority + this.host : '') +
this.pathname +
this._query +
this._fragment);
}
set href(href) {
this.clear();
this.parse(href);
}
get protocol() {
return this._scheme + ':';
}
set protocol(protocol) {
if (this._isInvalid) {
return;
}
this.parse(protocol + ':', 'scheme start');
}
get username() {
return this._isInvalid ? '' : this._username;
}
set username(username) {
if (this._isInvalid || !this._isRelative) {
return;
}
throw new Error('Not supported');
}
get password() {
return this._isInvalid ? '' : this._password;
}
set password(password) {
if (this._isInvalid || !this._isRelative) {
return;
}
throw new Error('Not supported');
}
get host() {
return this._isInvalid
? ''
: this._port
? this._host + ':' + this._port
: this._host;
}
set host(host) {
if (this._isInvalid || !this._isRelative) {
return;
}
this.parse(host, 'host');
}
get hostname() {
return this._host;
}
set hostname(hostname) {
if (this._isInvalid || !this._isRelative) {
return;
}
this.parse(hostname, 'hostname');
}
get port() {
return this._port;
}
set port(port) {
if (this._isInvalid || !this._isRelative) {
return;
}
this.parse(port, 'port');
}
get pathname() {
return this._isInvalid
? ''
: this._isRelative
? '/' + this._path.join('/')
: this._schemeData;
}
set pathname(pathname) {
if (this._isInvalid || !this._isRelative) {
return;
}
this._path = [];
this.parse(pathname, 'relative path start');
}
get search() {
return this._isInvalid || !this._query || '?' == this._query
? ''
: this._query;
}
set search(search) {
if (this._isInvalid || !this._isRelative) {
return;
}
if ('?' == search[0]) {
search = search.slice(1);
}
this._query = search !== '' ? '?' : '';
this.parse(search, 'query');
}
get hash() {
return this._isInvalid || !this._fragment || '#' == this._fragment
? ''
: this._fragment;
}
set hash(hash) {
if (this._isInvalid) {
return;
}
if (!hash) {
this._fragment = '';
return;
}
this._fragment = '#';
if ('#' == hash[0]) {
hash = hash.slice(1);
}
this.parse(hash, 'fragment');
}
get origin() {
var host;
if (this._isInvalid || !this._scheme) {
return '';
}
// javascript: Gecko returns String(""), WebKit/Blink String("null")
// Gecko throws error for "data://"
// data: Gecko returns "", Blink returns "data://", WebKit returns "null"
// Gecko returns String("") for file: mailto:
// WebKit/Blink returns String("SCHEME://") for file: mailto:
switch (this._scheme) {
case 'data':
case 'file':
case 'javascript':
case 'mailto':
return 'null';
}
host = this.host;
if (!host) {
return '';
}
return this._scheme + '://' + host;
}
invalid() {
this.clear();
this._isInvalid = true;
}
IDNAToASCII(h) {
if ('' == h) {
this.invalid();
}
// XXX
return h.toLowerCase();
}
clear() {
this._scheme = '';
this._schemeData = '';
this._username = '';
this._password = null;
this._host = '';
this._port = '';
this._path = [];
this._query = '';
this._fragment = '';
this._isInvalid = false;
this._isRelative = false;
}
parse(input, stateOverride, base) {
function err(message) {
errors.push(message);
}
var state = stateOverride || 'scheme start', cursor = 0, buffer = '', seenAt = false, seenBracket = false, errors = [];
loop: while ((input[cursor - 1] != EOF || cursor == 0) &&
!this._isInvalid) {
var c = input[cursor];
switch (state) {
case 'scheme start':
if (c && ALPHA.test(c)) {
buffer += c.toLowerCase(); // ASCII-safe
state = 'scheme';
}
else if (!stateOverride) {
buffer = '';
state = 'no scheme';
continue;
}
else {
err('Invalid scheme.');
break loop;
}
break;
case 'scheme':
if (c && ALPHANUMERIC.test(c)) {
buffer += c.toLowerCase(); // ASCII-safe
}
else if (':' == c) {
this._scheme = buffer;
buffer = '';
if (stateOverride) {
break loop;
}
if (isRelativeScheme(this._scheme)) {
this._isRelative = true;
}
if ('file' == this._scheme) {
state = 'relative';
}
else if (this._isRelative &&
base &&
base._scheme == this._scheme) {
state = 'relative or authority';
}
else if (this._isRelative) {
state = 'authority first slash';
}
else {
state = 'scheme data';
}
}
else if (!stateOverride) {
buffer = '';
cursor = 0;
state = 'no scheme';
continue;
}
else if (EOF == c) {
break loop;
}
else {
err('Code point not allowed in scheme: ' + c);
break loop;
}
break;
case 'scheme data':
if ('?' == c) {
this._query = '?';
state = 'query';
}
else if ('#' == c) {
this._fragment = '#';
state = 'fragment';
}
else {
// XXX error handling
if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
this._schemeData += percentEscape(c);
}
}
break;
case 'no scheme':
if (!base || !isRelativeScheme(base._scheme)) {
err('Missing scheme.');
this.invalid();
}
else {
state = 'relative';
continue;
}
break;
case 'relative or authority':
if ('/' == c && '/' == input[cursor + 1]) {
state = 'authority ignore slashes';
}
else {
err('Expected /, got: ' + c);
state = 'relative';
continue;
}
break;
case 'relative':
if (base == null) {
err('Missing base.');
break loop;
}
this._isRelative = true;
if ('file' != this._scheme) {
this._scheme = base._scheme;
}
if (EOF == c) {
this._host = base._host;
this._port = base._port;
this._path = base._path.slice();
this._query = base._query;
this._username = base._username;
this._password = base._password;
break loop;
}
else if ('/' == c || '\\' == c) {
if ('\\' == c) {
err('\\ is an invalid code point.');
}
state = 'relative slash';
}
else if ('?' == c) {
this._host = base._host;
this._port = base._port;
this._path = base._path.slice();
this._query = '?';
this._username = base._username;
this._password = base._password;
state = 'query';
}
else if ('#' == c) {
this._host = base._host;
this._port = base._port;
this._path = base._path.slice();
this._query = base._query;
this._fragment = '#';
this._username = base._username;
this._password = base._password;
state = 'fragment';
}
else {
var nextC = input[cursor + 1];
var nextNextC = input[cursor + 2];
if ('file' != this._scheme ||
!ALPHA.test(c) ||
(nextC != ':' && nextC != '|') ||
(EOF != nextNextC &&
'/' != nextNextC &&
'\\' != nextNextC &&
'?' != nextNextC &&
'#' != nextNextC)) {
this._host = base._host;
this._port = base._port;
this._username = base._username;
this._password = base._password;
this._path = base._path.slice();
this._path.pop();
}
state = 'relative path';
continue;
}
break;
case 'relative slash':
if (base == null) {
err('Missing base.');
break loop;
}
if ('/' == c || '\\' == c) {
if ('\\' == c) {
err('\\ is an invalid code point.');
}
if ('file' == this._scheme) {
state = 'file host';
}
else {
state = 'authority ignore slashes';
}
}
else {
if ('file' != this._scheme) {
this._host = base._host;
this._port = base._port;
this._username = base._username;
this._password = base._password;
}
state = 'relative path';
continue;
}
break;
case 'authority first slash':
if ('/' == c) {
state = 'authority second slash';
}
else {
err("Expected '/', got: " + c);
state = 'authority ignore slashes';
continue;
}
break;
case 'authority second slash':
state = 'authority ignore slashes';
if ('/' != c) {
err("Expected '/', got: " + c);
continue;
}
break;
case 'authority ignore slashes':
if ('/' != c && '\\' != c) {
state = 'authority';
continue;
}
else {
err('Expected authority, got: ' + c);
}
break;
case 'authority':
if ('@' == c) {
if (seenAt) {
err('@ already seen.');
buffer += '%40';
}
seenAt = true;
for (var i = 0; i < buffer.length; i++) {
var cp = buffer[i];
if ('\t' == cp || '\n' == cp || '\r' == cp) {
err('Invalid whitespace in authority.');
continue;
}
// XXX check URL code points
if (':' == cp && null === this._password) {
this._password = '';
continue;
}
var tempC = percentEscape(cp);
null !== this._password
? (this._password += tempC)
: (this._username += tempC);
}
buffer = '';
}
else if (EOF == c ||
'/' == c ||
'\\' == c ||
'?' == c ||
'#' == c) {
cursor -= buffer.length;
buffer = '';
state = 'host';
continue;
}
else {
buffer += c;
}
break;
case 'file host':
if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
if (buffer.length == 2 &&
ALPHA.test(buffer[0]) &&
(buffer[1] == ':' || buffer[1] == '|')) {
state = 'relative path';
}
else if (buffer.length == 0) {
state = 'relative path start';
}
else {
this._host = this.IDNAToASCII(buffer);
buffer = '';
state = 'relative path start';
}
continue;
}
else if ('\t' == c || '\n' == c || '\r' == c) {
err('Invalid whitespace in file host.');
}
else {
buffer += c;
}
break;
case 'host':
case 'hostname':
if (':' == c && !seenBracket) {
// XXX host parsing
this._host = this.IDNAToASCII(buffer);
buffer = '';
state = 'port';
if ('hostname' == stateOverride) {
break loop;
}
}
else if (EOF == c ||
'/' == c ||
'\\' == c ||
'?' == c ||
'#' == c) {
this._host = this.IDNAToASCII(buffer);
buffer = '';
state = 'relative path start';
if (stateOverride) {
break loop;
}
continue;
}
else if ('\t' != c && '\n' != c && '\r' != c) {
if ('[' == c) {
seenBracket = true;
}
else if (']' == c) {
seenBracket = false;
}
buffer += c;
}
else {
err('Invalid code point in host/hostname: ' + c);
}
break;
case 'port':
if (/[0-9]/.test(c)) {
buffer += c;
}
else if (EOF == c ||
'/' == c ||
'\\' == c ||
'?' == c ||
'#' == c ||
stateOverride) {
if ('' != buffer) {
var temp = parseInt(buffer, 10);
if (temp != relative[this._scheme]) {
this._port = temp + '';
}
buffer = '';
}
if (stateOverride) {
break loop;
}
state = 'relative path start';
continue;
}
else if ('\t' == c || '\n' == c || '\r' == c) {
err('Invalid code point in port: ' + c);
}
else {
this.invalid();
}
break;
case 'relative path start':
if ('\\' == c) {
err("'\\' not allowed in path.");
}
state = 'relative path';
if ('/' != c && '\\' != c) {
continue;
}
break;
case 'relative path':
if (EOF == c ||
'/' == c ||
'\\' == c ||
(!stateOverride && ('?' == c || '#' == c))) {
if ('\\' == c) {
err('\\ not allowed in relative path.');
}
var tmp;
// eslint-disable-next-line no-cond-assign
if ((tmp = relativePathDotMapping[buffer.toLowerCase()])) {
buffer = tmp;
}
if ('..' == buffer) {
this._path.pop();
if ('/' != c && '\\' != c) {
this._path.push('');
}
}
else if ('.' == buffer && '/' != c && '\\' != c) {
this._path.push('');
}
else if ('.' != buffer) {
if ('file' == this._scheme &&
this._path.length == 0 &&
buffer.length == 2 &&
ALPHA.test(buffer[0]) &&
buffer[1] == '|') {
buffer = buffer[0] + ':';
}
this._path.push(buffer);
}
buffer = '';
if ('?' == c) {
this._query = '?';
state = 'query';
}
else if ('#' == c) {
this._fragment = '#';
state = 'fragment';
}
}
else if ('\t' != c && '\n' != c && '\r' != c) {
buffer += percentEscape(c);
}
break;
case 'query':
if (!stateOverride && '#' == c) {
this._fragment = '#';
state = 'fragment';
}
else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
this._query += percentEscapeQuery(c);
}
break;
case 'fragment':
if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
this._fragment += c;
}
break;
}
cursor++;
}
}
}