import HTML from './html.js';
import CSS from './css.js';
import JS from './js.js';
import setCookie from 'set-cookie-parser';
import { xor, base64, plain } from './codecs.js';
import mimeTypes from './mime.js';
import {
validateCookie,
db,
getCookies,
setCookies,
serialize,
} from './cookie.js';
import {
attributes,
isUrl,
isForbidden,
isHtml,
isSrcset,
isStyle,
text,
injectHead,
createInjection,
} from './rewrite.html.js';
import { importStyle, url } from './rewrite.css.js';
//import { call, destructureDeclaration, dynamicImport, getProperty, importDeclaration, setProperty, sourceMethods, wrapEval, wrapIdentifier } from './rewrite.script.js';
import {
dynamicImport,
identifier,
importDeclaration,
property,
unwrap,
wrapEval,
} from './rewrite.script.js';
import { openDB } from 'idb';
import parsel from './parsel.js';
import UVClient from '../client/index.js';
import Bowser from 'bowser';
import BareClient from '@tomphttp/bare-client';
import EventEmitter from 'events';
const valid_chars =
"!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
const reserved_chars = '%';
class Ultraviolet {
constructor(options = {}) {
this.prefix = options.prefix || '/service/';
//this.urlRegex = /^(#|about:|data:|mailto:|javascript:)/;
this.urlRegex = /^(#|about:|data:|mailto:)/;
this.rewriteUrl = options.rewriteUrl || this.rewriteUrl;
this.sourceUrl = options.sourceUrl || this.sourceUrl;
this.encodeUrl = options.encodeUrl || this.encodeUrl;
this.decodeUrl = options.decodeUrl || this.decodeUrl;
this.vanilla = 'vanilla' in options ? options.vanilla : false;
this.meta = options.meta || {};
this.meta.base ||= undefined;
this.meta.origin ||= '';
this.bundleScript = options.bundleScript || '/uv.bundle.js';
this.handlerScript = options.handlerScript || '/uv.handler.js';
this.configScript = options.handlerScript || '/uv.config.js';
this.meta.url ||= this.meta.base || '';
this.codec = Ultraviolet.codec;
this.html = new HTML(this);
this.css = new CSS(this);
this.js = new JS(this);
this.parsel = parsel;
this.openDB = this.constructor.openDB;
this.Bowser = this.constructor.Bowser;
this.client =
typeof self !== 'undefined'
? new UVClient(options.window || self)
: null;
this.master = '__uv';
this.dataPrefix = '__uv$';
this.attributePrefix = '__uv';
this.createHtmlInject = createInjection;
this.attrs = {
isUrl,
isForbidden,
isHtml,
isSrcset,
isStyle,
};
if (!this.vanilla) this.implementUVMiddleware();
this.cookie = {
validateCookie,
db: () => {
return db(this.constructor.openDB);
},
getCookies,
setCookies,
serialize,
setCookie,
};
}
rewriteUrl(str, meta = this.meta) {
str = new String(str).trim();
if (!str || this.urlRegex.test(str)) return str;
if (str.startsWith('javascript:')) {
return (
'javascript:' + this.js.rewrite(str.slice('javascript:'.length))
);
}
try {
return (
meta.origin +
this.prefix +
this.encodeUrl(new URL(str, meta.base).href)
);
} catch (e) {
return meta.origin + this.prefix + this.encodeUrl(str);
}
}
sourceUrl(str, meta = this.meta) {
if (!str || this.urlRegex.test(str)) return str;
try {
return new URL(
this.decodeUrl(
str.slice(this.prefix.length + meta.origin.length)
),
meta.base
).href;
} catch (e) {
return this.decodeUrl(
str.slice(this.prefix.length + meta.origin.length)
);
}
}
encodeUrl(str) {
return encodeURIComponent(str);
}
decodeUrl(str) {
return decodeURIComponent(str);
}
encodeProtocol(protocol) {
protocol = protocol.toString();
let result = '';
for (let i = 0; i < protocol.length; i++) {
const char = protocol[i];
if (valid_chars.includes(char) && !reserved_chars.includes(char)) {
result += char;
} else {
const code = char.charCodeAt();
result += '%' + code.toString(16).padStart(2, 0);
}
}
return result;
}
decodeProtocol(protocol) {
if (typeof protocol != 'string')
throw new TypeError('protocol must be a string');
let result = '';
for (let i = 0; i < protocol.length; i++) {
const char = protocol[i];
if (char == '%') {
const code = parseInt(protocol.slice(i + 1, i + 3), 16);
const decoded = String.fromCharCode(code);
result += decoded;
i += 2;
} else {
result += char;
}
}
return result;
}
implementUVMiddleware() {
// HTML
attributes(this);
text(this);
injectHead(this);
// CSS
url(this);
importStyle(this);
// JS
importDeclaration(this);
dynamicImport(this);
property(this);
wrapEval(this);
identifier(this);
unwrap(this);
}
get rewriteHtml() {
return this.html.rewrite.bind(this.html);
}
get sourceHtml() {
return this.html.source.bind(this.html);
}
get rewriteCSS() {
return this.css.rewrite.bind(this.css);
}
get sourceCSS() {
return this.css.source.bind(this.css);
}
get rewriteJS() {
return this.js.rewrite.bind(this.js);
}
get sourceJS() {
return this.js.source.bind(this.js);
}
static codec = { xor, base64, plain };
static mime = mimeTypes;
static setCookie = setCookie;
static openDB = openDB;
static Bowser = Bowser;
static BareClient = BareClient;
static EventEmitter = EventEmitter;
}
export default Ultraviolet;
if (typeof self === 'object') self.Ultraviolet = Ultraviolet;