Files
infocom-systems-design/node_modules/bare-url/index.js
2025-10-03 22:27:28 +03:00

392 lines
8.2 KiB
JavaScript

const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')
const kind = Symbol.for('bare.url.kind')
const isWindows = Bare.platform === 'win32'
module.exports = exports = class URL {
static get [kind]() {
return 0 // Compatibility version
}
constructor(input, base, opts = {}) {
if (arguments.length === 0) throw errors.INVALID_URL()
input = String(input)
if (base !== undefined) base = String(base)
this._components = new Uint32Array(8)
this._parse(input, base, opts.throw !== false)
if (this._href) this._params = new URLSearchParams(this.search, this)
}
get [kind]() {
return URL[kind]
}
// https://url.spec.whatwg.org/#dom-url-href
get href() {
return this._href
}
set href(value) {
this._update(value)
this._params._parse(this.search)
}
// https://url.spec.whatwg.org/#dom-url-protocol
get protocol() {
return this._slice(0, this._components[0]) + ':'
}
set protocol(value) {
this._update(
this._replace(value.replace(/:+$/, ''), 0, this._components[0])
)
}
// https://url.spec.whatwg.org/#dom-url-username
get username() {
return this._slice(this._components[0] + 3 /* :// */, this._components[1])
}
set username(value) {
if (cannotHaveCredentialsOrPort(this)) {
return
}
if (this.username === '') value += '@'
this._update(
this._replace(
value,
this._components[0] + 3 /* :// */,
this._components[1]
)
)
}
// https://url.spec.whatwg.org/#dom-url-password
get password() {
return this._href.slice(
this._components[1] + 1 /* : */,
this._components[2] - 1 /* @ */
)
}
set password(value) {
if (cannotHaveCredentialsOrPort(this)) {
return
}
let start = this._components[1] + 1 /* : */
let end = this._components[2] - 1 /* @ */
if (this.password === '') {
value = ':' + value
start--
}
if (this.username === '') {
value += '@'
end++
}
this._update(this._replace(value, start, end))
}
// https://url.spec.whatwg.org/#dom-url-host
get host() {
return this._slice(this._components[2], this._components[5])
}
set host(value) {
if (hasOpaquePath(this)) {
return
}
this._update(
this._replace(
value,
this._components[2],
this._components[value.includes(':') ? 5 : 3]
)
)
}
// https://url.spec.whatwg.org/#dom-url-hostname
get hostname() {
return this._slice(this._components[2], this._components[3])
}
set hostname(value) {
if (hasOpaquePath(this)) {
return
}
this._update(this._replace(value, this._components[2], this._components[3]))
}
// https://url.spec.whatwg.org/#dom-url-port
get port() {
return this._slice(this._components[3] + 1 /* : */, this._components[5])
}
set port(value) {
if (cannotHaveCredentialsOrPort(this)) {
return
}
let start = this._components[3] + 1 /* : */
if (this.port === '') {
value = ':' + value
start--
}
this._update(this._replace(value, start, this._components[5]))
}
// https://url.spec.whatwg.org/#dom-url-pathname
get pathname() {
return this._slice(this._components[5], this._components[6] - 1 /* ? */)
}
set pathname(value) {
if (hasOpaquePath(this)) {
return
}
if (value[0] !== '/' && value[0] !== '\\') {
value = '/' + value
}
this._update(
this._replace(value, this._components[5], this._components[6] - 1 /* ? */)
)
}
// https://url.spec.whatwg.org/#dom-url-search
get search() {
return this._slice(
this._components[6] - 1 /* ? */,
this._components[7] - 1 /* # */
)
}
set search(value) {
if (value && value[0] !== '?') value = '?' + value
this._update(
this._replace(
value,
this._components[6] - 1 /* ? */,
this._components[7] - 1 /* # */
)
)
this._params._parse(this.search)
}
// https://url.spec.whatwg.org/#dom-url-searchparams
get searchParams() {
return this._params
}
// https://url.spec.whatwg.org/#dom-url-hash
get hash() {
return this._slice(this._components[7] - 1 /* # */)
}
set hash(value) {
if (value && value[0] !== '#') value = '#' + value
this._update(this._replace(value, this._components[7] - 1 /* # */))
}
toString() {
return this._href
}
toJSON() {
return this._href
}
[Symbol.for('bare.inspect')]() {
return {
__proto__: { constructor: URL },
href: this.href,
protocol: this.protocol,
username: this.username,
password: this.password,
host: this.host,
hostname: this.hostname,
port: this.port,
pathname: this.pathname,
search: this.search,
searchParams: this.searchParams,
hash: this.hash
}
}
_slice(start, end = this._href.length) {
return this._href.slice(start, end)
}
_replace(replacement, start, end = this._href.length) {
return this._slice(0, start) + replacement + this._slice(end)
}
_parse(input, base, shouldThrow) {
try {
this._href = binding.parse(
String(input),
base ? String(base) : null,
this._components,
shouldThrow
)
} catch (err) {
if (err instanceof TypeError) throw err
throw errors.INVALID_URL()
}
}
_update(input) {
try {
this._parse(input, null, true)
} catch (err) {
if (err instanceof TypeError) throw err
}
}
}
// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
return url.pathname[0] !== '/'
}
// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
return url.hostname === '' || url.protocol === 'file:'
}
const URL = exports
exports.URL = URL
exports.URLSearchParams = URLSearchParams
exports.errors = errors
exports.isURL = function isURL(value) {
if (value instanceof URL) return true
return (
typeof value === 'object' && value !== null && value[kind] === URL[kind]
)
}
// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
const url = new URL(input, base, { throw: false })
return url._href ? url : null
}
// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
return binding.canParse(String(input), base ? String(base) : null)
}
exports.fileURLToPath = function fileURLToPath(url) {
if (typeof url === 'string') {
url = new URL(url)
}
if (url.protocol !== 'file:') {
throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
}
if (isWindows) {
if (/%2f|%5c/i.test(url.pathname)) {
throw errors.INVALID_FILE_URL_PATH(
'The file: URL path must not include encoded \\ or / characters'
)
}
} else {
if (url.hostname) {
throw errors.INVALID_FILE_URL_HOST(
"The file: URL host must be 'localhost' or empty"
)
}
if (/%2f/i.test(url.pathname)) {
throw errors.INVALID_FILE_URL_PATH(
'The file: URL path must not include encoded / characters'
)
}
}
const pathname = path.normalize(decodeURIComponent(url.pathname))
if (isWindows) {
if (url.hostname) return '\\\\' + url.hostname + pathname
const letter = pathname.charCodeAt(1) | 0x20
if (
letter < 0x61 /* a */ ||
letter > 0x7a /* z */ ||
pathname.charCodeAt(2) !== 0x3a /* : */
) {
throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
}
return pathname.slice(1)
}
return pathname
}
exports.pathToFileURL = function pathToFileURL(pathname) {
let resolved = path.resolve(pathname)
if (pathname[pathname.length - 1] === '/') {
resolved += '/'
} else if (isWindows && pathname[pathname.length - 1] === '\\') {
resolved += '\\'
}
resolved = resolved
.replaceAll('%', '%25') // Must be first
.replaceAll('#', '%23')
.replaceAll('?', '%3f')
.replaceAll('\n', '%0a')
.replaceAll('\r', '%0d')
.replaceAll('\t', '%09')
if (!isWindows) {
resolved = resolved.replaceAll('\\', '%5c')
}
return new URL('file:' + resolved)
}