/**
 * @name CeL function for net
 * @fileoverview 本檔案包含了處理網路傳輸相關功能的 functions。
 * @since
 */

// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式，可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
	// module name
	name : 'application.net',

	// includes() @ data.code.compatibility.
	require : 'data.code.compatibility.' + '|data.native.'
	//
	+ '|application.OS.Windows.get_WScript_object'
	//
	+ '|interact.DOM.HTML_to_Unicode',

	// 設定不匯出的子函式。
	// no_extend : '*',

	// 為了方便格式化程式碼，因此將 module 函式主體另外抽出。
	code : module_code
});

function module_code(library_namespace) {
	'use strict';

	var module_name = this.id;
	// @see PATTERN_has_URI_invalid_character @ library_namespace.character
	var PATTERN_has_URI_invalid_character = /[^a-zA-Z0-9;,/?:@&=+$\-_.!~*'()#]/;
	var check_encoding = function(encoding) {
		// console.trace(encoding);
		if (encoding && !/^UTF-?8$/i.test(encoding)) {
			library_namespace.warn('您必須先載入 CeL.! 這訊息只會顯示一次!');
			check_encoding = null;
		}
	},
	// 本函數亦使用於 CeL.application.net.work_crawler
	// 本函式將使用之 encodeURIComponent()，包含對 charset 之處理。
	// @see function_placeholder() @ module.js
	encode_URI_component = function(string, encoding) {
		if (library_namespace.character) {
			library_namespace.debug('採用 ' + library_namespace.Class
			// 有則用之。 use CeL.data.character.encode_URI_component()
			+ '.character.encode_URI_component 編碼 ' + encoding, 1, module_name);
			encode_URI_component = library_namespace.character.encode_URI_component;
			check_encoding = null;
			return encode_URI_component(string, encoding);
		}
		check_encoding(encoding);
		return encodeURIComponent(string);
	};
	var encode_URI = function(string, encoding) {
		if (library_namespace.character) {
			library_namespace.debug('採用 ' + library_namespace.Class
			// 有則用之。 use CeL.data.character.encode_URI()
			+ '.character.encode_URI 編碼 ' + encoding, 1, module_name);
			encode_URI = library_namespace.character.encode_URI;
			check_encoding = null;
			return encode_URI(string, encoding);
		}
		check_encoding(encoding);
		return encodeURI(string);
	};
	var decode_URI_component = function(string, encoding) {
		if (library_namespace.character) {
			library_namespace.debug('採用 ' + library_namespace.Class
			// 有則用之。 use CeL.data.character.decode_URI_component()
			+ '.character.decode_URI_component 解碼 ' + encoding, 1, module_name);
			decode_URI = library_namespace.character.decode_URI;
			decode_URI_component = library_namespace.character.decode_URI_component;
			check_encoding = null;
			return decode_URI_component(string, encoding);
		}
		check_encoding(encoding);
		return decodeURIComponent(string);
	};
	var decode_URI = decode_URI_component;

	// requiring
	var KEY_not_native = library_namespace.env.not_native_keyword;
	var get_WScript_object = this.r('get_WScript_object'), HTML_to_Unicode = this
			.r('HTML_to_Unicode');

	/**
	 * null module constructor
	 * 
	 * @class net 的 functions
	 */
	var _// JSDT:_module_
	= function() {
		// null module constructor
	};

	/**
	 * for JSDT: 有 prototype 才會將之當作 Class
	 */
	_// JSDT:_module_
	.prototype = {};

	/** {Number}未發現之index。 const: 基本上與程式碼設計合一，僅表示名義，不可更改。(=== -1) */
	var NOT_FOUND = ''.indexOf('_');

	// ------------------------------------------------------------------------

	function is_IP(host, IPv6_only) {
		return !IPv6_only
		// for IPv4 addresses
		&& /^[12]?\d{1,2}(?:\.[12]?\d{1,2}){3}$/.test(host) && 4
		// for IPv6 addresses
		|| /^[\dA-F]{1,4}(?::[\dA-F]{1,4}){7}$/i.test(host) && 6;
	}

	_.is_IP = is_IP;

	/**
	 * get full path.
	 */
	function get_full_URL(relative_path, base_URL) {
		if (/([a-z\d]+:)\/\//.test(relative_path)) {
			// e.g., "https://host.name/"
			return relative_path;
		}
		if (relative_path.startsWith('/')) {
			// e.g., "/path/to/file"
			var matched = base_URL.match(/([a-z\d]+:)\/\/[^\/]+/);
			if (matched) {
				return matched[0] + relative_path;
			}
		}
		// e.g., "relative/path/to/file"
		return base_URL.replace(/[^\/]+$/, '') + relative_path;
	}

	_.get_full_URL = get_full_URL;

	// gethost[generateCode.dLK]='Sleep';
	/**
	 * get host name & IP 2005/3/1 22:32 只能用於WinXP, Win2000
	 * server（換個版本指令以及輸出可能就不同！），而且非常可能出狀況！ Win98 不能反查，只能 check local IP
	 * 
	 * @deprecated 改用 getNetInfo()
	 */
	function gethost(host) {
		var IP, p, c, t, i, f, cmd;
		// 決定shell cmd 對於 ".. > ""path+filename"" " 似乎不能對應的很好，
		// 所以還是使用 "cd /D path;.. > ""filename"" "
		try {
			c = '%COMSPEC% /U /c "', WshShell.Run(c + '"');
			p = WScript.ScriptFullName.replace(/[^\\]+$/, '');
			c += 'cd /D ""' + p + '"" && ';
			cmd = 1;
		} catch (e) {
			try {
				c = '%COMSPEC% /c ';
				WshShell.Run(c);
				p = 'C:\\';
			} catch (e) {
				return;
			}
		}
		if (host) {
			if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host))
				IP = host, host = 0;
		} else {
			f = 'ipconfig.tmp.txt';
			// winipcfg
			WshShell.Run(c + 'ipconfig > ' + (cmd ? '""' + f + '"" "' : p + f),
					0, true);
			if (t = simpleRead(f = p + f)) {
				// TODO: use t.between()
				if ((i = t.indexOf('PPP adapter')) !== NOT_FOUND)
					t = t.slice(i);
				else if ((i = t.indexOf('Ethernet adapter')) !== NOT_FOUND)
					t = t.slice(i);
				if ((i = t.indexOf('IP Address')) !== NOT_FOUND)
					t = t.slice(i);
				if (t.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/))
					IP = RegExp.$1;
			}
			try {
				fso.DeleteFile(f);
			} catch (e) {
			}
			if (!IP)
				return [ 0, 0 ];
		}
		if (!cmd)
			// Win98沒有nslookup
			return [ host, IP ];
		f = 'qDNS.tmp.txt';
		WshShell.Run(c
				+ 'nslookup '
				+ (cmd ? '""' + (IP || host) + '"" > ""' + f + '"" "'
						: (IP || host) + '>' + p + f), 0, true);
		// /C:執行字串中所描述的指令然後結束指令視窗 (x)因為用/c，怕尚未執行完。
		// try { WScript.Sleep(200); } catch (e) { }
		if ((t = simpleRead(f = p + f)) && t.match(/Server:/)
				&& t.match(/Address:\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)) {
			t = t.slice(RegExp.lastIndex);
			host = t.match(/Name:\s*(\S+)/) ? RegExp.$1 : 0;
			IP = t.match(/Address:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) ? RegExp.$1
					: 0;
			// library_namespace.debug(host + '\n' + IP);
		} else
			host = IP = 0;
		try {
			fso.DeleteFile(f);
		} catch (e) {
		}
		return [ host, IP ];
	}

	// for element.dataset
	if (false)
		if (!library_namespace.global.DOMStringMap)
			library_namespace.global.DOMStringMap = library_namespace.setting_pair;

	// ------------------------------------------------------------------------

	var port_of_protocol = {
		// https://tools.ietf.org/html/rfc1928#section-3
		// The SOCKS service is conventionally located on TCP port 1080.
		// https://github.com/TooTallNate/node-socks-proxy-agent/blob/master/src/agent.ts
		socks4 : 1080,
		socks4a : 1080,
		socks5 : 1080,
		socks : 1080,
		socks5h : 1080,
		ftp : 21,
		http : 80,
		https : 443
	};

	_.port_of_protocol = port_of_protocol;

	var PATTERN_URI =
	// [ all, 1: `protocol:`, 2: '//', 3: host, 4: path ]
	/^([\w\-]{2,}:)?(\/\/)?(\/[A-Z]:|(?:[^@]*@)?[^\/#?&:.][^\/#?&:]+(?::\d{1,5})?)?(.*)$/i
	// /^(?:(https?:)\/\/)?(?:([^:@]+)(?::([^@]*))?@)?([^:@]+)(?::(\d{1,5}))?$/
	;

	/**
	 * URI class.
	 * 
	 * 本組函數之目的:<br />
	 * 1. polyfill for W3C URL API.<br />
	 * 2. CeL.Search_parameters() 採用{Object}操作 hash 更方便重複利用，且可支援 charset。
	 * 
	 * new URLSearchParams() 會將數值轉成字串。 想二次利用 {Object}, {Array}，得採用 new CeL.URI()
	 * 而非 new URL()。
	 * 
	 * @example <code>

	// 警告: 這不能保證 a 和 fg 的順序!! 僅保證 fg=23 → fg=24。欲保持不同名稱 parameters 間的順序，請採用 {String}parameter+parameter。
	var url = new CeL.URI('ftp://user:cgh@dr.fxgv.sfdg:4231/3452/dgh.rar?fg=23&a=2&fg=24#hhh');
	alert(url.hostname);
	// to URL()
	new URL(url).toString() === url.toString()
	// parameters to URLSearchParams() 
	new URLSearchParams(url.search_params.toString()).toString() === url.search_params.toString();
	
	</code>
	 * 
	 * <code>

	test:
	/fsghj.sdf
	a.htm
	http://www.whatwg.org/specs/web-apps/current-work/#attr-input-pattern
	file:///D:/USB/cgi-bin/lib/JS/_test_suit/test.htm
	//www.whatwg.org/specs/web-apps/current-work/#attr-input-pattern

	TODO:
	file:///D:/USB/cgi-bin/lib/JS/_test_suit/test.htm
	→ .file_path:
	D:\USB\cgi-bin\lib\JS\_test_suit\test.htm

	eURI : /^((file|telnet|ftp|https?)\:\/\/|~?\/)?(\w+(:\w+)?@)?(([-\w]+\.)+([a-z]{2}|com|org|net))?(:\d{1,5})?(\/([-\w~!$+|.,=]|%[\dA-F]{2})*)?(\?(([-\w~!$+|.,*:]|%[\dA-F]{2})+(=([-\w~!$+|.,*:=]|%[\dA-F]{2})*)?&?)*)?(#([-\w~!$+|.,*:=]|%[\dA-F]{2})*)?$/i,

	TODO:
	input [ host + path, search, hash ]
	URI, IRI, XRI
	WHATWG URL parser

	 * </code>
	 * 
	 * @param {String}uri
	 *            URI to parse
	 * @param {String}[base_uri]
	 *            當做基底的 URL。 see
	 *            CeL.application.storage.file.get_relative_path()
	 * @param {Object}[options]
	 *            附加參數/設定選擇性/特殊功能與選項
	 * 
	 * @return parsed object
	 * 
	 * @since 2010/4/13 23:53:14 from parseURI+parseURL
	 * @since 2021/2/27 6:10:25 Parses URI, function parse_URI(uri) → new
	 *        URI(uri)
	 * 
	 * @_memberOf _module_
	 * 
	 * @see https://developer.mozilla.org/en-US/docs/Web/API/URL_API
	 * @see RFC 1738, RFC 2396, RFC 3986, Uniform Resource Identifier (URI):
	 *      Generic Syntax, http://tools.ietf.org/html/rfc3987,
	 *      http://flanders.co.nz/2009/11/08/a-good-url-regular-expression-repost/,
	 *      http://www.mattfarina.com/2009/01/08/rfc-3986-url-validation,
	 *      https://developer.mozilla.org/en/DOM/window.location, also see
	 *      batURL.htm
	 */
	function URI(uri, base_uri, options) {
		if (!is_URI(this)) {
			// Call URI(value), like String(value)
			if (is_URI(uri))
				return uri;

			return new URI(uri, base_uri, options);
		}

		options = library_namespace.new_options(options);
		if (options.charset === 'buffer') {
			// Although the content is buffer, the URI itself should be not.
			delete options.charset;
		}
		if ((uri instanceof URL) || is_URI(uri)) {
			// uri.href
			uri = uri.toString();
		}

		if (!uri
		// 不能用 instanceof String!
		|| typeof uri !== 'string') {
			throw new Error('Invalid URI type: (' + (typeof uri) + ') ' + uri);
		}

		var href = library_namespace.simplify_path(uri);
		if (/^\/\//.test(uri)) {
			// CeL.simplify_path('//hostname') === '/hostname'
			href = '/' + href;
		}
		var matched = href.match(PATTERN_URI), path;
		if (!matched) {
			throw new Error('Invalid URI: (' + (typeof uri) + ') ' + uri);
		}

		// console.log(href);
		library_namespace.debug('parse [' + uri + ']: '
				+ matched.join('<br />\n'), 8, 'URI');

		// console.trace([ matched, base_uri, options ]);
		uri = base_uri && URI(base_uri) || options.as_URL
		//
		|| library_namespace.is_WWW() && {
			// protocol包含最後的':',search包含'?',hash包含'#'.
			// file|telnet|ftp|https
			protocol : location.protocol,
			hostname : location.hostname,
			port : location.port,
			host : location.host,
			// local file @ IE: C:\xx\xx\ff, others: /C:/xx/xx/ff
			pathname : location.pathname
		};
		if (library_namespace.need_avoid_assign_to_setter) {
			for ( var key in uri) {
				if (key !== 'search'
				// &&key !== 'hash'
				) {
					this[key] = uri[key];
				}
			}
			uri = this;
		} else {
			uri = Object.assign(this, uri);
		}
		// uri.uri = href;

		/**
		 * ** filename 可能歸至m[4]!<br />
		 * 判斷準則:<br />
		 * gsh.sdf.df#dhfjk filename|hostname<br />
		 * gsh.sdf.df/dhfjk hostname<br />
		 * gsh.sdf.df?dhfjk filename<br />
		 * gsh.sdf.df filename<br />
		 */
		href = matched[3] && matched[3].toLowerCase() || '';
		path = matched[4] || '';
		// 可辨識出為 domain 的這個 hostname. e.g., gTLD
		// https://en.wikipedia.org/wiki/Generic_top-level_domain
		if (/(?:\w+\.)+(?:com|org|net|info)$/i.test(href)) {
			// e.g., URI("www.example.com")
			path = path || '/';
			if (uri.protocol === 'file:')
				uri.protocol = 'https:';
		}

		if (matched[1])
			uri.protocol = matched[1].toLowerCase();
		// uri._protocol = uri.protocol.slice(0, -1).toLowerCase();
		// library_namespace.debug('protocol [' + uri._protocol + ']', 2);

		if (href && !/^\/[A-Z]:$/i.test(href)
				&& (path.charAt(0) === '/' || /[@:]/.test(href))) {
			// 處理 username:password
			if (matched = href.match(/^([^@]*)@(.+)$/)) {
				matched.user_passwords = matched[1].match(/^([^:]+)(:(.*))?$/);
				if (!matched.user_passwords)
					return;
				uri.username = matched.user_passwords[1];
				if (matched.user_passwords[3])
					uri.password = matched.user_passwords[3];
				href = matched[2];
			} else {
				// W3C URL API 不論有沒有帳號密碼皆會設定這兩個值
				uri.password = '';
				uri.username = '';
			}

			// [ all, host, (integer)port ]
			matched = href.match(/^([^\/#?&\s:]+)(?::(\d{1,5}))?$/);
			if (!matched) {
				throw new Error('Invalid host: ' + href);
			}

			// 處理 host
			// host=hostname:port
			uri.hostname = uri.host = matched[1];
			if (matched[2]
					&& matched[2] != port_of_protocol[uri.protocol.slice(0, -1)
							.toLowerCase()]) {
				// uri[KEY_port] = parseInt(matched[2], 10);
				uri.port = String(parseInt(matched[2], 10));
				uri.host += ':' + uri.port;
			} else if (false) {
				uri[KEY_port] = parseInt(matched[2]
						|| port_of_protocol[uri.protocol.slice(0, -1)
								.toLowerCase()]);
			}

		} else {
			// test uri.protocol === 'file:'
			path = href + path;
			href = '';
			// uri.protocol === 'file:'
			uri.port = uri.port || '';
			uri.host = uri.host || '';
			uri.hostname = uri.hostname || '';
			uri.username = uri.username || '';
			uri.password = uri.password || '';
		}

		uri.origin = uri.protocol + '//' + uri.host;

		// Normalize Windows path
		// "d:\\p\\" → "d:/p/"
		path = path.replace(/\\/g, '/');
		if (/^[A-Z]:/i.test(path)) {
			// "d:/p/" → "/d:/p/"
			path = '/' + path;
		}
		if (!href) {
			// test /C:/path
			if (!/^\/[A-Z]:/i.test(path)) {
				if (!base_uri) {
					library_namespace.debug(
					// 將 [' + path + '] 當作 pathname! not hostname!
					'Treat [' + path + '] as pathname!', 1, 'URI');
				}
				if (uri.pathname) {
					if (/^\//.test(path)) {
						// path 為 absolute path
						matched = !/^\/[A-Z]:/i.test(path)
								&& uri.pathname.match(/^\/[A-Z]:/i);
						if (matched)
							path = matched[0] + path;
					} else {
						// 僅取 uri.pathname 之 directory path
						path = uri.pathname.replace(/[^\\\/]+$/, '') + path;
					}
					path = library_namespace.simplify_path(path);
				}
			}
			// console.trace(path);
		}
		// upper-cased driver letter: "/d:/p/" → "/D:/p/"
		path = path.replace(/^\/[a-z]:/g, function($0) {
			return $0.toUpperCase();
		});
		if (library_namespace.is_WWW()) {
			library_namespace.debug('local file: [' + location.pathname + ']',
					9, 'URI');
		}

		// NG: /^([^%]+|%[\dA-F]{2})+$/
		// prevent catastrophic backtracking. e.g., '.'.repeat(300)+'%'
		// Thanks for James Davis.
		if (false && path && !/^(?:[^%]|%[\dA-F]{2})+$/i.test(path)) {
			library_namespace.warn('URI: encoding error: [' + path + ']');
		}

		// console.trace([ href, path, uri ]);
		library_namespace.debug('parse path: [' + path + ']', 9);
		if (path && (matched = path
		// https://cdn.dongmanmanhua.cn/16189006774011603165.jpg?x-oss-process=image/quality,q_90
		.match(/^(([^#?]*\/)?([^\/#?]*))?(\?([^#]*))?(#.*)?$/))) {
			library_namespace.debug('pathname: [' + matched + ']', 9);
			// pathname={path}filename
			uri.pathname = matched[1] || '';
			if (/%[\dA-F]{2}/i.test(uri.pathname)) {
				try {
					// console.trace([ uri.pathname, decodeURI(uri.pathname) ]);
					// Try to get decoded path.
					uri.pathname = decodeURI(uri.pathname);
				} catch (e) {
					// uri.pathname = decode_URI(uri.pathname, charset);
				}
			}
			if (PATTERN_has_URI_invalid_character.test(uri.pathname)) {
				// console.trace([ uri.pathname, encode_URI(uri.pathname,
				// options.charset) ]);
				uri.pathname = encode_URI(uri.pathname, options.charset);
			}
			// .directory_path 會隨不同 OS 之 local file 表示法作變動!
			uri.directory_path = /^\/[A-Z]:/i.test(uri.pathname) ? matched[2]
					.slice(1).replace(/\//g, '\\')
			// e.g., 'file:///D:/directory/file.name'
			// → D:\directory\
			: /^[A-Z]:(?:\/([^\/]|$)|$)/i.test(uri.pathname) ? matched[2]
					.replace(/\//g, '\\') : matched[2];
			uri.filename = matched[3];
			// request path used @ node.js http.request(options)
			// uri.path = uri.pathname + uri.search
			// uri.path = uri.pathname + (matched[5] ? '?' + matched[5] : '');

			var _options;
			if (Object.defineProperty[KEY_not_native]) {
				// hash without '#': using uri.hash.slice(1)
				uri.hash = matched[6];
				uri.search = matched[4];
				_options = Object.assign({
					// @see (typeof options.URI === 'object')
					URI : uri
				}, options);
			} else {
				Object.defineProperty(uri, KEY_hash, {
					value : matched[6] ? matched[6].slice(1) : '',
					writable : true
				});
				_options = options;
			}
			matched = matched[5];
			// console.trace([ matched, _options ]);
		} else {
			if (!href) {
				throw new Error('Invalid URI: ' + uri);
			}
			if (uri.pathname) {
				uri.directory_path = uri.pathname.replace(/[^\/]+$/, '');
				// uri.path = uri.pathname;
			}
			matched = '';
		}

		if (options.as_URL) {
			// 盡可能模擬 W3C URL()
			// library_namespace.debug('search: [' + matched[5] + ']', 2);
			// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
			uri.searchParams = new URLSearchParams(matched, _options);
		} else {
			// do not set uri.search_params directly.
			_options = Object.assign({
				URI : uri
			}, _options);
			// console.trace(_options);
			uri.search_params = new Search_parameters(matched, _options);
		}

		if (options.charset)
			uri.charset = options.charset;

		library_namespace.debug('path: [' + uri.path + ']', 9);

		if (Object.defineProperty[KEY_not_native]) {
			library_namespace.debug('Generate .href of URI by URI_toString()',
					10);
			uri.toString();
		}
		// console.trace(uri);

		library_namespace.debug('href: [' + uri.href + ']', 8);
		// return uri;
	}

	Object.defineProperties(URI.prototype, {
		hash : {
			enumerable : true,
			get : function get() {
				if (!this[KEY_hash])
					return '';
				return '#' + this[KEY_hash];
			},
			set : function set(value) {
				value = String(value);
				if (value.startsWith('#')) {
					value = value.slice(1);
				}
				this[KEY_hash] = value;
			}
		},
		// URI.prototype.search
		search : {
			enumerable : true,
			get : search_getter,
			set : function set(value) {
				var search_params = this.search_params || this.searchParams;
				if (false && this.search_params) {
					this.search_params[KEY_URL] = this;
				}

				// search_params.clean_parameters();
				search_clean_parameters(search_params);

				// node.js v0.10.48 有 bug? 需要取得 search_params 一次才不會造成
				// ReferenceError: CeL is not defined
				// @ URI.prototype.href.set
				// @ site_name #17 @ _test suite/test.js
				URL[KEY_not_native] && search_params && Math.abs(0);

				value = String(value);
				if (value.startsWith('?')) {
					value = value.slice(1);
				}
				if (value) {
					// search_params.set_parameters(value);
					search_set_parameters.call(search_params, value);
				}
			}
		},
		// URI.prototype.href
		href : {
			enumerable : true,
			get : Object.defineProperty[KEY_not_native] ? URI_toString
					: URI_href,
			set : function set(href) {
				URI.call(this, href);
			}
		},
		toString : {
			value : Object.defineProperty[KEY_not_native] ? URI_toString
					: URI_href
		}
	});

	function search_getter(options) {
		// library_namespace.debug('normalize properties by search_getter');
		// library_namespace.debug(this.search_params);

		if (false && this.search_params) {
			this.search_params[KEY_URL] = this;
		}

		var uri = this;
		// console.trace([ uri, uri.searchParams ]);
		var search = 'search_params' in uri
		// function parameters_toString(options)
		? uri.search_params.toString(options)
		// options.as_URL?
		: uri.searchParams.toString();
		return search ? '?' + search : '';
	}

	function URI_href() {
		var uri = this;
		// console.trace([ uri, uri.search ]);
		// href=protocol:(//)?username:password@hostname:port/path/filename?search#hash
		var href = (uri.protocol ? uri.protocol + '//' : '')
				+ (uri.username || uri.password ? uri.username
						+ (uri.password ? ':' + uri.password : '') + '@' : '')
				+ uri.host
				// assert: uri.pathname is encodeURI()-ed.
				+ uri.pathname + uri.search + uri.hash;
		return href;
	}

	// options: 'charset'
	function URI_toString(options) {
		var uri = this;
		// assert: !!Object.defineProperty[KEY_not_native] === true
		uri.search = search_getter.call(uri, options);
		if ((uri.hash = String(uri.hash)) && !uri.hash.startsWith('#')) {
			uri.hash = '#' + uri.hash;
		}
		// console.trace(uri.search);
		return uri.href = URI_href.call(uri);
	}

	_// JSDT:_module_
	.URI = URI;

	function is_URI(value) {
		return value instanceof URI;
	}

	_.is_URI = is_URI;

	// ------------------------------------------------------------------------

	var NO_EQUAL_SIGN = typeof Symbol === 'function' ? Symbol('NO_EQUAL_SIGN')
	//
	: {
		NO_EQUAL_SIGN : true
	};

	function decode_URI_component_no_throw(value, charset) {
		try {
			return decode_URI_component(value, charset);
		} catch (e) {
		}

		// decode_URI_component() should be decodeURIComponent()
		return value.replace(/%([\dA-F]{2})/g, function(encoded, code) {
			return String.fromCharCode(parseInt(code, 16));
		});
	}

	/**
	 * parse_parameters({String}parameter) to hash
	 * 
	 * CeL.net.Search_parameters()
	 * 
	 * 新版本與 charset 編碼無關的話，應該使用 new URLSearchParams(parameters).toString()。
	 * 
	 * @param {String}search_string
	 * @param {Object}[options]
	 *            附加參數/設定選擇性/特殊功能與選項
	 * 
	 * @see https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
	 */
	function Search_parameters(search_string, options) {
		// Similar to:
		// return new URLSearchParams(search_string);
		// but with charset and forward compatibility
		// and re-usable {Object} data structure.

		if (!is_Search_parameters(this)) {
			// `CeL.Search_parameters(search_string)`
			if (is_Search_parameters(search_string))
				return search_string;
			return new Search_parameters(search_string, options);
		}

		options = library_namespace.setup_options(options);
		var parameters = this;
		var data, name, value, matched;
		if (typeof search_string === 'string') {
			// http://stackoverflow.com/questions/14551194/how-are-parameters-sent-in-an-http-post-request
			data = search_string.replace(/\+/g, '%20').split(/&/);
		} else if (Array.isArray(search_string)) {
			data = search_string;
		} else if (typeof search_string === 'object') {
			// https://github.com/whatwg/url/issues/27
			// Creation of URLSearchParams from Object/Map
			if (library_namespace.is_Map(search_string)) {
				// input {Map}.
				Array.from(search_string.entries()).forEach(function(entry) {
					parameters[entry[0]] = entry[1];
				});
			} else if (search_string instanceof URLSearchParams) {
				Array.from(search_string.keys()).unique().forEach(
						function(key) {
							var values = search_string.getAll(key);
							parameters[key] = values.length > 1 ? values
									: values[0];
						});
			} else {
				// {Object}search_string.
				// assert: library_namespace.is_Object(search_string)
				Object.assign(parameters, search_string);
			}
		} else {
			if (search_string) {
				// Invalid search 無法處理之 parameters
				library_namespace.debug({
					// gettext_config:{"id":"enter-a-non-string-parameter-$1"}
					T : [ '輸入了非字串之參數：[%1]', search_string ]
				}, 1, 'Search_parameters');
			}
		}

		// 不可設置 parameters.charset，會加在 request URL 裡面。
		var charset = options.charset;

		for (var i = 0, l = data && data.length || 0; i < l; i++) {
			if (!data[i])
				continue;
			if (library_namespace.is_Object(data[i])) {
				this.set_parameters(data[i], options);
				continue;
			}
			if (typeof data[i] !== 'string') {
				library_namespace
						.error('Must input {String} as search parameter!');
				console.error(data[i]);
				return;
			}

			// Warning: Search_parameters() 僅接受 UTF-8。
			// 欲設定 charset，必須自行先處理 .search！

			// var index = parameter.indexOf('=');
			if (matched = data[i].match(/^([^=]+)=(.*)$/)) {
				name = matched[1];
				value = decode_URI_component_no_throw(matched[2], charset);
			} else {
				name = data[i];
				value = 'default_value' in options ? options.default_value
						: /* name */NO_EQUAL_SIGN;
			}
			try {
				name = decode_URI_component_no_throw(name, charset);
			} catch (e) {
				// TODO: handle exception
			}

			if (ignore_search_properties
			// Warning: for old environment, may need ignore some keys
			&& (name in ignore_search_properties)) {
				continue;
			}

			if (library_namespace.is_debug(2)) {
				try {
					library_namespace.debug('[' + (i + 1) + '/' + l + '] '
					//
					+ (parameters[name] ? '<span style="color:#888;">('
					//
					+ parameters[name].length + ')</span> [' + name
					//
					+ '] += [' + value + ']' : '[' + name + '] = ['
					//
					+ value + ']'));
				} catch (e) {
				}
			}

			if (options.split_pattern && typeof value === 'string'
			//
			&& (matched = value.split(options.split_pattern)).length > 1) {
				if (name in parameters) {
					if (Array.isArray(parameters[name])) {
						Array.prototype.push.apply(parameters[name], matched);
					} else {
						matched.unshift(parameters[name]);
						parameters[name] = matched;
					}
				} else
					parameters[name] = matched;
			} else {
				search_add_1_parameter.call(parameters, name, value);
			}
		}

		if (options.Array_only) {
			Object.keys(parameters).forEach(function(key) {
				if (!ignore_search_properties
				// Warning: for old environment, may need ignore some keys
				|| !(key in ignore_search_properties)) {
					if (!Array.isArray(parameters[name]))
						parameters[name] = [ parameters[name] ];
				}
			});
		}

		if (typeof options.URI === 'object') {
			Object.defineProperty(parameters, KEY_URL, {
				value : options.URI
			});
		}
	}

	function search_add_1_parameter(key, value, options) {
		if (key in this) {
			var original_value = this[key];
			if (Array.isArray(original_value))
				original_value.push(value);
			else
				this[key] = [ original_value, value ];
		} else {
			// Warning: if Array.isArray(value),
			// next value will push to the value!
			this[key] = value;
		}
	}

	/**
	 * set / append these parameters
	 * 
	 * @inner
	 */
	function search_set_parameters(parameters, options) {
		// console.trace([ this, parameters, options ]);
		options = Object.assign({
			charset : this.charset || this[KEY_URL] && this[KEY_URL].charset
		}, options);
		if (!library_namespace.is_Object(parameters))
			parameters = Search_parameters(parameters, options);
		// console.trace([ this, parameters, options ]);
		// Object.keys() 不會取得 Search_parameters.prototype 的屬性。
		Object.keys(parameters).forEach(function(key) {
			if (!ignore_search_properties
			// Warning: for old environment, may need ignore some keys
			|| !(key in ignore_search_properties)) {
				var value = parameters[key];
				if (options.append) {
					search_add_1_parameter.call(this,
					//
					key, value, options);
				} else {
					this[key] = value;
				}
			}
		}, this);
		return this;
	}

	// @inner
	function search_clean_parameters(object) {
		// if (!object)
		object = this;
		if (!object) {
			// @ node.js v0.10.48
			// https://github.com/kanasimi/CeJS/runs/2105831296?check_suite_focus=true
			return this;
		}

		Object.keys(object).forEach(function(key) {
			if (!ignore_search_properties
			// Warning: for old environment, may need ignore some keys
			|| !(key in ignore_search_properties)) {
				delete object[key];
			}
		});
		return this;
	}

	// {Object}this parameter hash to String
	// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
	function parameters_toString(options) {
		var charset;
		if (typeof options === 'string') {
			charset = options;
			options = Object.create(null);
		} else {
			options = library_namespace.setup_options(options);
			charset = options.charset;
		}
		if (charset === undefined) {
			// console.trace([ this, this[KEY_URL] ]);
			charset = this.charset || this[KEY_URL] && this[KEY_URL].charset;
		}

		var search = [], key;
		function append(value) {
			if (library_namespace.is_debug(9) && typeof value !== 'string'
					&& typeof value !== 'number' && value !== NO_EQUAL_SIGN) {
				try {
					library_namespace.debug({
						T : [
								// gettext_config:{"id":"set-$1-to-a-non-string-$2"}
								'設定 %1 成非字串之參數：%2',
								typeof JSON === 'object' ? JSON.stringify(key)
										: String(key),
								typeof JSON === 'object' ? JSON
										.stringify(value) : String(value) ]
					}, 1, 'parameters_toString.append');
				} catch (e) {
					// TypeError: Converting circular structure to JSON
				}
			}

			// console.trace([ key, value ]);
			try {
				search.push(value === NO_EQUAL_SIGN ? key : key + '='
						+ encode_URI_component(String(value), charset));
				// console.trace(search);
			} catch (e) {
				library_namespace.error(e);
				console.error(e);
				console.trace([ key, value ]);
			}
		}

		// console.trace([ this, charset ]);
		for (var index = 0, key_list = Object.keys(this); index < key_list.length; index++) {
			key = key_list[index];
			if (ignore_search_properties && (key in ignore_search_properties)) {
				// Warning: for old environment, may need ignore some keys
				continue;
			}

			var value = this[key];
			key = encode_URI_component(key, charset);
			// console.trace(key + ' = ' + value);
			if (!Array.isArray(value)) {
				append(value);
			} else if (Object.getOwnPropertyDescriptor(value, 'toString')) {
				// assert: 自行定義 {Function}.toString()
				append(value.toString());
			} else {
				value.forEach(append);
			}
		}

		library_namespace.debug([ {
			// gettext_config:{"id":"a-total-of-$1-parameters"}
			T : [ '共%1個參數：', search.length ]
		}, '<br />\n', search.map(function(parameter) {
			return parameter.length > 400 ? parameter.slice(0,
			//
			library_namespace.is_debug(6) ? 2000 : 400) + '...' : parameter;
		}).join('<br />\n') ], 9, 'parameters_toString');

		search = search.join('&');
		if (this[KEY_URL]) {
			// @see URI.prototype.search
			this[KEY_URL].search = search;
		}

		return search;
	}

	// @private
	var KEY_hash = typeof Symbol === 'function' ? Symbol('hash') : '\0hash';
	var KEY_URL = !Object.defineProperty[KEY_not_native]
			&& typeof Symbol === 'function' ? Symbol('URL') : '\0URL';
	// search_properties
	Object.assign(Search_parameters.prototype, {
		clean_parameters : search_clean_parameters,
		set_parameters : search_set_parameters,
		// valueOf
		toString : parameters_toString
	});
	var ignore_search_properties;
	if (Object.defineProperty[KEY_not_native]) {
		// 皆已採用 Object.keys(), Object.entries()
		// Object.keys() 不會取得 Search_parameters.prototype 的屬性。
		// ignore_search_properties = Object.clone(Search_parameters.prototype);
		ignore_search_properties = Object.create(null);

		// @ WScript.exe 會採用 (key in ignore_search_properties) 的方法，
		// 因此 KEY_URL 必須是 {String}。
		if (typeof KEY_URL !== 'string')
			KEY_URL = String(KEY_URL);
		ignore_search_properties[KEY_URL] = true;
		// alert(Object.keys(ignore_search_properties));
	}

	_.Search_parameters = Search_parameters;

	function is_Search_parameters(value) {
		return value instanceof Search_parameters;
	}

	_.is_Search_parameters = is_Search_parameters;

	// --------------------------------

	// 有缺陷的 URL()
	function defective_URL(url) {
		// Object.assign() will not copy toString:URI_toString()
		// Object.assign(this, URI(url));

		return new URI(url, null, {
			// 盡可能模擬 W3C URL()
			as_URL : true
		});
	}

	// 有缺陷的 URLSearchParams()
	function defective_URLSearchParams(search_string, options) {
		// library_namespace.debug(search_string);
		// Warning: new Map() 少了許多必要的功能! 不能完全替代!
		var search = Object.entries(
		//
		new Search_parameters(search_string, options));
		if (ignore_search_properties) {
			search = search.filter(function(entry) {
				return !(entry[0] in ignore_search_properties);
			});
		}
		// library_namespace.info(search.length);

		// alert(Array.isArray(search));
		try {
			Map.call(this, search);
			if (!this.forEach)
				throw 1;
			return;
		} catch (e) {
			// node.js 0.11: Constructor Map requires 'new'
		}

		search = new Map(search);
		// Copy all methods
		Object.assign(search, defective_URLSearchParams.prototype);
		return search;
	}

	// https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Inheritance
	Object.assign(defective_URLSearchParams.prototype = Object
			.create(Map.prototype), {
		constructor : defective_URLSearchParams,

		clean : function clean() {
			var search = this;
			var keys = Array.from(this.keys());
			keys.forEach(function(key) {
				search['delete'](key);
			});
			return this;
		},

		// URLSearchParams() 會存成字串，不會保留原先的資料結構。
		set : function set(key, value) {
			key = String(key);
			value = String(value);
			Map.prototype.set.call(this, key, value);
		},
		append : function append(key, value) {
			key = String(key);
			value = String(value);
			// defective_URLSearchParams.prototype.toString
			if (this.has(key)) {
				var original_value = Map.prototype.get.call(this, key);
				if (Array.isArray(original_value)) {
					original_value.push(value);
				} else {
					Map.prototype.set
							.call(this, key, [ original_value, value ]);
				}
			} else {
				Map.prototype.set.call(this, key, value);
			}
		},

		// Return the first one
		get : function get(key) {
			key = String(key);
			var original_value = Map.prototype.get.call(this, key);
			if (Array.isArray(original_value))
				return original_value[0];
			return original_value;
		},
		getAll : function getAll(key) {
			key = String(key);
			if (!this.has(key))
				return [];
			var original_value = Map.prototype.get.call(this, key);
			if (Array.isArray(original_value))
				return original_value;
			return [ original_value ];
		},

		// 注意: 本 library 模擬之 URLSearchParams.prototype.toString 只能得到等價
		// href，不完全相同。
		toString : function toString() {
			// defective_URLSearchParams.prototype.toString
			var list = [];
			this.forEach(function(value, key) {
				// console.trace([ value, key ]);
				key = encodeURIComponent(key) + '=';
				if (Array.isArray(value)) {
					value.forEach(function(v) {
						list.push(key + encodeURIComponent(String(v)));
					});
				} else {
					list.push(key + encodeURIComponent(String(value)));
				}
			});
			return list.join('&');
		}
	});

	// ------------------------------------------------------------------------

	/**
	 * <code>
	https://pubs.opengroup.org/onlinepubs/007908799/xbd/notation.html
	The following table lists escape sequences and associated actions on display devices capable of the action.

	https://pubs.opengroup.org/onlinepubs/007908799/xcu/printf.html
	the escape sequences listed in the XBD specification, File Format Notation  (\\, \a, \b, \f, \n, \r, \t, \v), which will be converted to the characters they represent
	</code>
	 */
	var to_file_name_escape_sequences = {
		'\n' : '＼n',
		'\r' : '＼r',
		'\t' : '＼t'
	};

	/**
	 * 正規化 file name，排除會導致 error 的字元。 normalize file name
	 * 
	 * @param {String}file_name
	 *            file name
	 * @param {Boolean}do_escape
	 *            是否作 escape
	 * 
	 * @returns {String}正規化 file name
	 * 
	 * @see data.is_matched.string_pre_handler(),
	 *      application.storage.file.get_file_name()
	 * @since 2012/10/13 13:31:21
	 */
	function to_file_name(file_name, do_escape) {
		file_name = file_name.trim();

		// 處理 illegal file name. 去除檔名中不被允許的字元。
		// http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words

		if (do_escape)
			file_name = file_name
			// 若本來就含有這些 functional 字元的情況，須作 escape。
			.replace(/([＼／｜？＊])/g, '＼$1');
		// else: make result readable.

		file_name = file_name.replace(/[\0-\x1f]/g, function($0) {
			if ($0 in to_file_name_escape_sequences)
				return to_file_name_escape_sequences[$0];

			var c = $0.charCodeAt(0).toString(16), l = c.length;
			if (l === 1 || l === 3)
				c = '0' + c;
			else if (4 < l && l < 8)
				c = '000'.slice(l - 5) + c;
			return '＼' + (c.length === 2 ? 'x' : 'u') + c;
		});

		file_name = file_name
		// functional characters in RegExp.
		.replace(/[\\\/|?*]/g, function($0) {
			return {
				'\\' : '＼',
				// Fraction slash '⁄'
				// Division slash '∕'
				'/' : '／',
				'|' : '｜',
				'?' : '？',
				'*' : '＊'
			}[$0];
		});

		file_name = file_name
		// normalize string.
		// 全寬引號（fullwidth quotation mark）[＂]
		.replace(/"([^"'“”＂]+)"/g, '“$1”').replace(/"/g, '”')
				.replace(/:/g, '：').replace(/</g, '＜').replace(/>/g, '＞');

		if (library_namespace.platform.is_Windows()) {
			file_name = file_name
			// 若是以 "." 結尾，在 Windows 7 中會出現問題，無法移動或刪除。
			.replace(/(.)\.$/, '$1._');
		}

		// 限制長度.
		// http://en.wikipedia.org/wiki/Filename#Length_restrictions
		// http://msdn.microsoft.com/en-us/library/aa365247.aspx#maxpath
		// https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
		if (file_name.length > 255) {
			library_namespace
					.warn('to_file_name: The file name will be cutted! ['
							+ file_name.length + '] [' + file_name + ']');
			file_name = file_name.slice(0, 255);
		}

		return file_name;
	}

	_// JSDT:_module_
	.to_file_name = to_file_name;

	var
	/**
	 * 取得副檔名。
	 * 
	 * @type {RegExp}
	 */
	PATTERN_extension = /\.([a-z\d\-]+)$/i,
	/**
	 * 一般字元，非特殊字元之 folder 名。<br />
	 * [...]{1,512}<br />
	 * 
	 * @type {RegExp}
	 */
	PATTERN_ordinary_folder_name = /^[a-z\d ~!@#$%^&()-_+={}[],.]+[\\\/]$/i,

	TARGET_FILE_EXISTS = new Error, NO_EXECUTABLE_FILE = new Error, NOT_YET_IMPLEMENTED = new Error;

	TARGET_FILE_EXISTS.name = 'TARGET_FILE_EXISTS';
	NO_EXECUTABLE_FILE.name = 'NO_EXECUTABLE_FILE';
	NOT_YET_IMPLEMENTED.name = 'NOT_YET_IMPLEMENTED';

	/**
	 * 取得 URI/取得器
	 * 
	 * @param {Function}[module]
	 *            use what module/command to get.
	 * @returns getter
	 * @throws No
	 *             module to use.
	 */
	function URI_accessor(module, setting) {

		if (!module)
			if (URI_accessor.default_module)
				module = URI_accessor.default_module;
			else {
				// detect what module/command to use.
				for (module in URI_accessor.module)
					if (!URI_accessor.test_module(module)) {
						URI_accessor.default_module = module;
						break;
					}
				if (!URI_accessor.default_module)
					module = undefined;
			}

		if ((module in URI_accessor.module)
				&& library_namespace
						.is_Function(module = URI_accessor.module[module]
								(setting)))
			return module;

		throw new Error('No module' + (module ? ' [' + module + ']' : '')
				+ ' to use!');
	}

	// return undefined: OK, others: error.
	URI_accessor.test_module = function(module_name) {
		library_namespace.debug('test module: [' + module_name + ']', 1,
				'URI_accessor.test_module');
		try {
			get_WScript_object().WshShell.Run(module_name, 0, true);
		} catch (e) {
			// 若不存在此執行檔案，將 throw。
			library_namespace.error(e);
			return (e.number & 0xFFFF) === 2 ? NO_EXECUTABLE_FILE : e;
		}
		library_namespace.debug('test module: [' + module_name + ']: OK.', 1,
				'URI_accessor.test_module');
	};

	/**
	 * 從 URI 抽取 file name
	 * 
	 * @param URI
	 *            URI
	 * @returns file name
	 * @throws decodeURIComponent
	 *             error
	 */
	URI_accessor.extract_file_name = function(URI) {
		// 須處理非標準之符號，可能會有 &#x27; 之類的東西。因此對 #hash 之處理得放在 HTML_to_Unicode() 後面。
		var m = URI.replace(/([^&])#.*/, '$1')
		//
		.match(/(([^\/\\]+)[\/\\]+)?([^\/\\]*)$/);
		if (m) {
			return URI_accessor.regularize_file_name(
			// 因為 escape 會多出不必要符號，因此不 escape。
			HTML_to_Unicode(m[3] || m[1]), false);
		}
	};

	// 正規化 file name
	URI_accessor.regularize_file_name = to_file_name;

	URI_accessor.setting = {
		// referer : '',
		window_style : function() {
			// 0: hidden, 1: show, 2: Activate & minimize,
			// 7: Minimize. The active window remains active.
			return library_namespace.is_debug() ? 1 : 0;
		},
		// 指定當檔名具有特殊字元時之暫存檔。
		// temporary_file : 'URI_accessor.tmp',
		// temporary_file : 'C:\\URI_accessor.tmp',
		// temporary_file : function(URI, save_to, FSO) { return
		// temporary_file_path; },
		// temporary_file : function(URI, save_to, FSO) { return save_to +
		// '.unfinished'; },
		temporary_file : function(URI, save_to) {
			var extension = save_to.match(PATTERN_extension),
			// 應該用 save_to 的 md5 值。
			hash_id = Math.ceil(Math.random() * 1e9);
			return 'URI_accessor.'
					+ (extension ? 'temp.' + hash_id + extension[0] : hash_id
							+ '.temp');
		},

		// do not overwrite:
		// target_exist : false

		// when target file exists, save to ..
		// target_exist : function(target, FSO) { return save_to || skip; },

		// when target file exists, rename old to ..
		// target_exist : [ save new to, rename old to ],

		target_exist : [],

		user_agent : 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.29 Safari/537.22'
	};

	URI_accessor.target_exist = {
		rename : function(target, FSO) {
			var proto = target + '.old', move_to = proto, i = 0;
			// TODO: when error occurred..
			while (FSO.FileExists(move_to))
				move_to = proto + ++i;
			try {
				FSO.MoveFile(target, move_to);
			} catch (e) {
			}
			return target;
		},
		// save new to, rename old to
		move : function(save_to, move_to, FSO) {
			if (!move_to)
				move_to = save_to + '.old';
			if (FSO.FileExists(move_to))
				try {
					FSO.DeleteFile(move_to);
				} catch (e) {
				}
			try {
				FSO.MoveFile(save_to, move_to);
			} catch (e) {
			}
			return save_to;
		}
	};

	/**
	 * <code>
	default command modules.
	取得方法：
	wget
	curl
	lftp
	prozilla
	puf
	CuteFTPPro.TEConnection

	XMLHttp
	Msxml2.DOMDocument
	InternetExplorer.Application
	WinHttp.WinHttpRequest.5.1
		深入挖掘Windows腳本技術(5) - 網頁特效代碼 - IT學習者	http://www.itlearner.com/Article/2008/4024_5.shtml
		獲取軟件下載的真實地址！再談獲取Response.redirect重定向的URL-asp教程-asp學習網	http://www.aspxuexi.com/xmlhttp/example/2006-8-8/852.htm
		http://www.360doc.com/content/11/0108/11/597197_84935972.shtml
		從msdn得知，WinHttp.WinHttpRequest.5.1 是 msxml 4.0 的底層對象，也就是說 XMLHTTP/ServerXMLHTTP 也是在它的基礎上封裝而來。

	XMLHTTP組件在處理包含Location頭的302消息時太智能了，直接跳轉到最後的頁面

	TODO:
	先知道 file size
	use $PATH
	</code>
	 */
	URI_accessor.module = {
		curl : function(user_setting) {
			// http://curl.haxx.se/docs/httpscripting.html

			// The ORDINAL 2821 could not be located in the dynamic link library
			// LIBEAY32.dll
			// This is caused by a conflict in the version of LIBEAY32.DLL
			// Solution: install the latest version of Win32 OpenSSL
			// http://www.slproweb.com/products/Win32OpenSSL.html

			if (false) {
				library_namespace.debug('URI_accessor.setting.temporary_file:'
						+ URI_accessor.setting.temporary_file, 2,
						'URI_accessor.module.curl');
				library_namespace.debug('user_setting.temporary_file:'
						+ user_setting.temporary_file, 2,
						'URI_accessor.module.curl');
			}
			var setting = new library_namespace.setting_pair(Object
					.create(null), URI_accessor.setting, user_setting),
			//
			value = setting('user_agent'),
			//
			tmp = setting('cookie') || setting('cookie_file'),
			//
			command_array = [
					'curl --remote-time --insecure --compressed '
							+ (library_namespace.is_debug(2) ? '-v ' : '')
							+ (setting('additional_options') ? setting('additional_options')
									+ ' '
									: '')
							// --cookie STRING/FILE String or file to read
							// cookies from (H)
							+ (tmp ? '--cookie "' + tmp + '" ' : '')
							+ ((tmp = setting('cookie_file')
									|| setting('cookie')) ? '--cookie-jar "'
									+ tmp + '" ' : '') + '--output "', '',
					(value ? '" --user-agent "' + value : '') + '"' ];

			if (setting('POST')) {
				setting('POST_index', command_array.length + 1);
				command_array.push(' --data "', '', '"');
			}

			command_array[command_array.length - 1] += ' --referer "';

			tmp = '" "';
			if (value = setting('referer')) {
				library_namespace.debug([ 'referer: ', {
					a : value,
					href : value
				} ], 2, 'URI_accessor.module.curl');
				command_array[command_array.length - 1] += value + tmp;
			} else
				setting('referer_index', command_array.length), command_array
						.push('', tmp);

			command_array.push('', '"');

			library_namespace.debug('command_array: ' + command_array, 2,
					'URI_accessor.module.curl');
			if (false)
				library_namespace.debug('temporary_file: ['
						+ (typeof setting('temporary_file')) + ']'
						+ setting('temporary_file'), 2,
						'URI_accessor.module.curl');

			return URI_accessor.default_getter(setting, command_array,
					URI_accessor.default_apply_command);
		},

		wget : function(user_setting) {
			var setting = new library_namespace.setting_pair(Object
					.create(null), URI_accessor.setting, user_setting), value = setting('user_agent'), tmp = '" "', command_array = [
					'wget --timestamping --keep-session-cookies --no-check-certificate '
							+ (library_namespace.is_debug(2) ? '-d ' : '')
							+ (setting('additional_options') ? setting('additional_options')
									+ ' '
									: '') + '--output-document="', '',
					(value ? '" --user-agent="' + value : '') + '"' ];

			if (setting('POST')) {
				setting('POST_index', command_array.length + 1);
				command_array.push(' --post-data="', '', '"');
			}

			command_array[command_array.length - 1] += ' --referer="';

			if (value = setting('referer')) {
				library_namespace.debug([ 'referer: ', {
					a : value,
					href : value
				} ], 2, 'URI_accessor.module.wget');
				command_array[command_array.length - 1] += value + tmp;
			} else
				setting('referer_index', command_array.length), command_array
						.push('', tmp);

			command_array.push('', '"');

			library_namespace.debug('command_array: ' + command_array, 2,
					'URI_accessor.module.wget');
			if (false)
				library_namespace.debug('temporary_file: ['
						+ (typeof setting('temporary_file')) + ']'
						+ setting('temporary_file'), 2,
						'URI_accessor.module.wget');

			return URI_accessor.default_getter(setting, command_array,
					URI_accessor.default_apply_command);
		}
	};

	URI_accessor.default_apply_command = function(setting, command_array, URI,
			save_to, temporary_file_used) {
		command_array[1] = temporary_file_used || save_to;
		command_array[command_array.length - 2] = URI;
		var i;
		if (i = setting('referer_index'))
			command_array[i] = URI;
		if (i = setting('POST_index'))
			command_array[i] = setting('POST') || '';
	};

	URI_accessor.default_getter = function(setting, command_array,
			apply_command) {
		if (false)
			library_namespace.debug('get_WScript_object: ['
					+ (typeof get_WScript_object) + ']' + get_WScript_object,
					2, 'URI_accessor.default_getter');
		var WSO = get_WScript_object();
		if (false)
			library_namespace.debug('WSO: [' + (typeof WSO) + ']' + WSO, 2,
					'URI_accessor.default_getter');
		if (!WSO) {
			library_namespace.warn('No WScript objects got!');
			return;
		}

		var WshShell = WSO.WshShell, FSO = WSO.FSO,
		//
		normalize_directory = function(id) {
			var directory = setting(id);
			if (directory && !/[\\\/]$/.test(directory))
				setting(id, directory + library_namespace.env.path_separator);
		}, normalize_function = function(id) {
			if (typeof setting(id) !== 'function')
				setting(id, undefined);
		},
		//
		window_style = setting('window_style'), temporary_file = setting('temporary_file');
		library_namespace.debug('temporary_file: [' + (typeof temporary_file)
				+ ']' + temporary_file, 2, 'URI_accessor.default_getter');

		WSO = null;

		var getter = function(URI, save_to) {
			var start_time = new Date, result, temporary_file_used, tmp;

			if (library_namespace.is_Object(save_to)) {
				setting(save_to);
				normalize_directory('directory');
				normalize_function('callback');
				save_to = setting('save_to');
			}

			// 若沒有輸入 save_to，從 URI 取得。
			if (!save_to)
				save_to = URI_accessor.extract_file_name(URI);

			// 得放在偵測 temporary file 之前，預防 directory
			// 包含非普通的(unordinary)字符。
			if (tmp = setting('directory'))
				save_to = tmp + save_to;

			if (FSO.FileExists(save_to) && ('target_exist' in setting())) {
				if (Array.isArray(tmp = setting('target_exist')))
					tmp = URI_accessor.target_exist.move(tmp[0] || save_to,
							tmp[1], FSO);
				else if (typeof tmp === 'string' && tmp.charAt(0) === '*')
					tmp = URI_accessor.target_exist[tmp.slice(1)];

				if (typeof tmp === 'function')
					tmp = tmp(save_to, FSO);

				if (typeof tmp === 'string' && tmp)
					save_to = tmp;
				else {
					library_namespace
							.debug(
									'Skip ['
											+ URI
											+ ']: target file ['
											+ save_to
											+ '] exists and target_exist of setting refused rename or overwrite.',
									2, 'URI_accessor.default_getter.getter');
					result = TARGET_FILE_EXISTS;
					if (tmp = setting('callback'))
						tmp(save_to, URI, result);
					return result;
				}
			}

			// 只有非常用字母才需要 temporary file。
			if (temporary_file && !/^[\x20-\x7e]+$/.test(save_to)) {
				temporary_file_used = typeof temporary_file === 'function' ? temporary_file(
						URI, save_to, FSO)
						: temporary_file;
				library_namespace.debug('temporary file: ['
						+ temporary_file_used + ']←[' + temporary_file + ']',
						2, 'URI_accessor.default_getter.getter');
			}

			library_namespace.debug('Downloading [<a href="'
					+ URI
					+ '" target="_blank">'
					+ URI
					+ '</a>]'
					+ (temporary_file_used ? '→[' + temporary_file_used + ']'
							: '') + '→[' + save_to + ']..', 1,
					'URI_accessor.default_getter.getter');

			apply_command(setting, command_array, URI, save_to,
					temporary_file_used);
			library_namespace.debug(
					'Execute: [' + command_array.join('') + ']', 2,
					'URI_accessor.default_getter.getter');
			library_namespace.debug('WshShell: [' + (typeof WshShell) + ']'
					+ WshShell, 3, 'URI_accessor.default_getter.getter');

			try {
				// WshShell.Run("cmd.exe /c set > env.txt", 1, true);
				result = WshShell.Run(command_array.join(''),
				// Window Style
				1,
				// typeof window_style === 'function' ? window_style() :
				// window_style,
				// true: 等調用的程序退出後再執行。
				true);
				if (result) {
					// result = EXIT CODE
					this.lastest_errorno = result;
					save_to = '[error] ' + save_to;
				}

				if (temporary_file_used)
					if (FSO.FileExists(temporary_file_used))
						// 出問題還是照搬。
						// 需注意出問題過，原先就存在的情況。
						FSO.MoveFile(temporary_file_used, save_to);
					else
						library_namespace
								.warn('temporary file does not exists: ['
										+ temporary_file_used + ']');

				library_namespace.debug('['
						+ URI
						+ ']→[<a href="'
						+ save_to
						+ '" target="_blank">'
						+ save_to
						+ '</a>] @ '
						+ Math.round((new Date - start_time) / 1000)
						+ ' sec '
						+ (result ? ', <em>error code ' + result + '</em>.'
								: ''));

			} catch (e) {
				// library_namespace.error(e);
				if ((e.number & 0xFFFF) === 2)
					// 若不存在此執行檔案，將 throw。
					// '找不到執行檔: wget。您可能需要安裝此程式後再執行。'
					// http://users.ugent.be/~bpuype/wget/
					result = NO_EXECUTABLE_FILE;
				else {
					library_namespace.error(e);
					result = e;
				}
			}

			if (tmp = setting('callback'))
				tmp(save_to, URI, result, setting);
			return result;
		};

		if (false)
			library_namespace.debug('WshShell: [' + (typeof WshShell) + ']'
					+ WshShell, 2, 'URI_accessor.default_getter');

		// binding prototype
		if (false)
			library_namespace.set_method(URI_accessor.prototype,
					getter.prototype);
		else
			getter.prototype = URI_accessor.prototype;

		normalize_directory('directory');
		normalize_function('callback');

		return getter;
	};

	library_namespace.set_method(URI_accessor.prototype, {
		// TODO
		list : function(URI_array, index_URI, index_save_to) {
			throw NOT_YET_IMPLEMENTED;
		},
		process : function(index) {
			throw NOT_YET_IMPLEMENTED;
		}
	});

	_// JSDT:_module_
	.URI_accessor = URI_accessor;

	/**
	 * get URI / URI 取得器.
	 * 
	 * @example <code>
	 * get_URI('http://lyrics.meicho.com.tw/game/index.htm');
	 * </code>
	 * 
	 * @param {String}URI
	 *            URI to get
	 * @param {String}[save_to]
	 *            path save to
	 * 
	 * @returns error
	 */
	function get_URI(URI, save_to, setting) {
		if (get_URI.getter && (!setting
		// 有可能使用相同的 setting object，但僅改變了部分內容，如 temporary_file。
		// || get_URI.setting === setting
		))
			return get_URI.getter(URI, save_to);

		var i, getter, result;
		for (i in URI_accessor.module) {
			result = (getter = new URI_accessor(i, setting))(URI, save_to);
			if (result !== NO_EXECUTABLE_FILE) {
				// cache default setting
				get_URI.getter = getter;
				get_URI.setting = setting;
				break;
			}
		}

		return result;
	}

	_// JSDT:_module_
	.get_URI = get_URI;

	// @since 2021/2/27 6:29:0 remove get_video() for 下載 Youtube 影片檔案與播放清單:
	// 年久失修且網站改版，無法使用且沒想要維護了。

	/**
	 * 自動組態設定檔/自動設定網址
	 * 
	 * url: 完整的URL字串, host: 在 URL字串中遠端伺服器的網域名稱。該參數祇是為了 方便而設定的，是與URL在 :// 和 /
	 * 中的文字是一模 一樣。但是傳輸阜（The port number）並不包含其中 。當需要的時候可以從URL字串解讀出來。
	 * 
	 * <code>
	http://contest.ks.edu.tw/syshtml/proxy-pac.html
	Proxy Auto-Config File Format	http://lyrics.meicho.com.tw/proxy.pac
	http://openattitude.irixs.org/%E7%BC%96%E5%86%99-pac-proxy-auto-config-%E6%96%87%E4%BB%B6/
	http://www.atmarkit.co.jp/fwin2k/experiments/ieproxy/ieproxy_01.html
	http://www.cses.tcc.edu.tw/~chihwu/proxy-pac.htm
	you should configure your server to map the .pac filename extension to the MIME type:
		application/x-ns-proxy-autoconfig

	網域名稱之長度，經punycode轉碼後，不得超過63字元,大約二十個中文字以內。

	FindProxyForURL 將會傳回一個描寫Proxy組態設定的單一字串。假如該字串為空字串，則表示瀏覽器不使用 Proxy 伺服器。
	假如有多個代理伺服器設定同時存在，則最左邊的設定將第一個使用，直 到瀏覽器無法建立連線才會更換到第二個設定。而瀏覽器將會在30分鐘後 自動對於先前無回應的 PROXY 伺服器重新連線。而瀏覽器將會於一個小時 後自動再連線一次（每一次的重新連線都會增加30分鐘）。
	如果說所有的 PROXY 伺服器都當掉了，也沒有將 DIRECT 設定在 .pac 檔 案，那麼瀏覽器在嘗試建立連線 20 分鐘後將會詢問是否要暫時忽略 Proxy 服器直接存取網路，下一次詢問的時間則是在 40 分鐘後（注意！每一次 詢問都會增加20分鐘)

	http://www.microsoft.com/technet/prodtechnol/ie/ieak/techinfo/deploy/60/en/corpexjs.mspx?mfr=true
	The isInNet, isResolvable, and dnsResolve functions query a DNS server.
	The isPlainHostName function checks to see if there are any dots in the hostname. If so, it returns false; otherwise, the function returns true.
	The localHostOrDomainIs function is executed only for URLs in the local domain.
	The dnsDomainIs function returns true if the domain of the hostname matches the domain given.

	DIRECT - 不調用代理，直接連接
	PROXY host:port - 調用指定代理(host:port)
	SOCKS host:port - 調用指定SOCKS代理(host:port)
	如果是選用由分號分割的多塊設置，按照從左向右，最左邊的代理會被最優先調用，除非瀏覽器無法成功和proxy建立連接，那麼下一個配置就會被調 用。如果瀏覽器遇到不可用的代理服務器，瀏覽器將在30分鐘後自動重試先前無響應的代理服務器，一個小時後會再次進行嘗試，依此類推，每次間隔時間為 30 分鐘。
	</code>
	 */
	function FindProxyForURL(url, host) {
		var lch = host.toLowerCase();

		// isPlainHostName(lch) || isInNet(lch,"192.168.0.0","255.255.0.0") ||
		// isInNet(lch,"127.0.0.0","255.255.0.0") || dnsDomainIs(lch,".tw")
		// ?"DIRECT";
		return (
		/**
		 * <code>
		//dnsDomainIs(lch,"holyseal.net") || dnsDomainIs(lch,".fuzzy2.com") ? "PROXY 211.22.213.114:8000; DIRECT":	//	可再插入第二、三順位的proxy
		http://www.cybersyndrome.net/

		http://www.publicproxyservers.com/page1.html
		curl --connect-timeout 5 -x 219.163.8.163:3128 http://www.getchu.com/ | grep Getchu.com
		curl --connect-timeout 5 -x 64.34.113.100:80 http://www.getchu.com/ | grep Getchu.com
		curl --connect-timeout 5 -x 66.98.238.8:3128 http://www.getchu.com/ | grep Getchu.com

		dnsDomainIs(lch, ".cn") || dnsDomainIs(lch, "pkucn.com") ? "PROXY proxy.hinet.net:80; DIRECT" :	//	2009/8/16 14:20:32	用 HiNet 網際網路 Proxy Server 上大陸網速度還滿快的	http://www.ltivs.ilc.edu.tw/proxy/proxy/hinet.htm
		dnsDomainIs(lch, ".getchu.com") ? "PROXY 219.163.8.163:3128; PROXY 64.34.113.100:80; PROXY 66.98.238.8:3128; DIRECT" :
		dnsDomainIs(lch, ".minori.ph") ? "PROXY 219.94.198.110:3128; PROXY 221.186.108.237:80; DIRECT" :	//	Japan Distorting Open Proxy List	http://www.xroxy.com/proxy--Distorting-JP-nossl.htm
		//	slow:	http://www.cybersyndrome.net/country.html
		dnsDomainIs(lch, ".tactics.ne.jp") ? "PROXY 202.175.95.171:8080; PROXY 203.138.90.141:80; DIRECT" :
		dnsDomainIs(lch,".ys168.com")		? "PROXY 76.29.160.230:8000; DIRECT":	//	永硕E盘专业网络硬盘服务
		</code>
		 */
		dnsDomainIs(lch, "erogamescape.dyndns.org")
		//
		? "PROXY 211.22.213.114:8000; DIRECT"
		// http://www.twnic.net.tw/proxy.pac 將中文網域名稱轉成英文網域名稱
		// :/^[a-z\.\d_\-]+$/.test(lch)?"DIRECT":"PROXY
		// dnsrelay.twnic.net.tw:3127"
		: "DIRECT");
	}

	// http://help.globalscape.com/help/cuteftppro8/
	// setupCuteFTPSite[generateCode.dLK]='parse_URI';
	function setupCuteFTPSite(targetS, site) {
		if (typeof targetS === 'string')
			targetS = new URI(targetS, {
				protocol : 'ftp:'
			});
		if (!targetS)
			return;

		if (site) {
			try {
				site.Disconnect();
			} catch (e) {
			}
			try {
				site.Close();
			} catch (e) {
			}
		}
		try {
			site = null;
			site = WScript.CreateObject("CuteFTPPro.TEConnection");
			site.Host = targetS.host;
			// http://help.globalscape.com/help/cuteftppro8/setting_protocols.htm
			// The default Protocol is FTP, however SFTP (SSH2), FTPS (SSL),
			// HTTP, and HTTPS can also be used.
			site.Protocol = targetS.protocol.replace(/:$/, '').toUpperCase();
			if (targetS.username)
				site.Login = targetS.username;
			if (targetS.password)
				site.Password = targetS.password;

			site.useProxy = "off";
			site.TransferType = 'binary';

			site.Connect();

			// site.TransferURL("http://lyrics.meicho.com.tw/run.js");
		} catch (e) {
			return;
		}
		return site;
	}

	/*
	 * TODO: transferURL(remote URI,remote URI)
	 */
	// transferURL[generateCode.dLK]='parsePath,parse_URI,setupCuteFTPSite';
	function transferURL(from_URI, to_URI) {
		// var connectTo = from_URI.includes('://') ? from_URI : to_URI,
		// CuteFTPSite = setupCuteFTPSite(connectTo);
		var
		// isD: use download (else upload)
		isD, CuteFTPSite,
		// lF: local file
		lF,
		// rP: remote path
		rP;
		if (from_URI.includes('://'))
			isD = 0;
		else if (to_URI.includes('://'))
			isD = 1;
		else
			// local to local?
			return;
		lF = parsePath(isD ? to_URI : from_URI);
		CuteFTPSite = setupCuteFTPSite(rP = new URI(isD ? from_URI : to_URI, {
			protocol : 'ftp:'
		}));
		if (!CuteFTPSite || !CuteFTPSite.IsConnected)
			return;

		// 到這裡之後，就認定 CuteFTPPro.TEConnection 的 initial 沒有問題，接下來若出問題，會嘗試重新
		// initial CuteFTPPro.TEConnection.

		// initial local folder
		try {
			if (!site.LocalExists(site.LocalFolder = lF.directory))
				site.CreateLocalFolder(lF.directory);
		} catch (e) {
			return;
		}
		site.RemoteFolder = rP.pathname;

		if (isD) {
			site.Download(rP.fileName, lF.fileName || rP.fileName);
			if (!site.LocalExists(lF.path))
				return;
		} else {
			site.Upload(lF.fileName, rP.fileName || lF.fileName);
			if (!site.LocalExists(rP.path))
				return;
		}

		// get list
		// site.GetList('/OK', '', '%NAME');
		// var l = site.GetResult().replace(/\r\n?/g, '\n').split('\n');

		// close
		try {
			site.Disconnect();
		} catch (e) {
		}
		site.Close();

		return 1;
	}

	// ---------------------------------------------------------------

	// var globalThis = library_namespace.env.global;
	if (library_namespace.is_WWW(true) || library_namespace.platform.nodejs) {
		library_namespace.set_method(library_namespace.env.global, {
			// defective polyfill for W3C URL API, URLSearchParams()
			URL : defective_URL,
			URLSearchParams : defective_URLSearchParams
		});

		// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
		// URLSearchParams_set_parameters(parameters)
		URLSearchParams.prototype.set_parameters = function set_parameters(
				parameters, options) {
			if (Array.isArray(parameters))
				parameters = parameters.join('&');
			// assert: typeof parameters === 'object'
			// || typeof parameters === 'string'
			parameters = new URLSearchParams(parameters);

			var search = this;
			parameters.forEach(function(value, key) {
				search.append(key, value);
			});
			return this;
		};
	}

	return (_// JSDT:_module_
	);
}
