From 53e0c7f7ed866e8e942698b7e36f815ff8e3002e Mon Sep 17 00:00:00 2001 From: QuiteAFancyEmerald <46467239+QuiteAFancyEmerald@users.noreply.github.com> Date: Fri, 25 Feb 2022 22:56:50 -0800 Subject: [PATCH] Massive update (TODO: Add UV locally) --- backend.js | 139 +- bare/.gitignore | 3 + bare/EncodeProtocol.mjs | 57 + bare/Example.mjs | 46 + bare/HeaderUtil.mjs | 33 + bare/LICENSE | 674 + bare/README.md | 48 + bare/Response.mjs | 34 + bare/Server.mjs | 146 + bare/Standalone.mjs | 81 + bare/V1.mjs | 384 + bare/package.json | 30 + ecosystem.config.js | 9 +- package.json | 4 +- src/backend.mjs | 29 + src/charinsert.js | 53 - config.json => src/config.json | 2 +- randomization.json => src/data.json | 2 +- src/examples/proxy.js | 25 - src/examples/static.js | 29 - src/proxy.mjs | 24 + src/randomization.mjs | 34 + src/routes.mjs | 59 + views/ads.txt | 1 + views/assets/img/uv.png | Bin 0 -> 20873 bytes views/assets/img/uv2.png | Bin 0 -> 28980 bytes ...on-1644738239.js => common-16451543478.js} | 41 +- views/assets/js/csel.js | 16 +- views/index.html | 2 + views/index.js | 6 + views/pages/frame.html | 2 + views/pages/proxnav/corrosion.html | 5 +- views/pages/proxnav/preset/discord.html | 6 +- views/pages/proxnav/preset/reddit.html | 6 +- views/pages/proxnav/preset/searx.html | 3 +- views/pages/proxnav/preset/youtube.html | 8 +- views/pages/proxnav/ultraviolet.html | 71 + views/pages/proxnav/womginx.html | 2 +- views/pages/surf.html | 21 +- views/uv.bundle.js | 39204 ++++++++++++++++ views/uv.config.js | 10 + views/uv.handler.js | 1092 + views/uv.sw.js | 278 + 43 files changed, 42435 insertions(+), 284 deletions(-) create mode 100644 bare/.gitignore create mode 100644 bare/EncodeProtocol.mjs create mode 100644 bare/Example.mjs create mode 100644 bare/HeaderUtil.mjs create mode 100644 bare/LICENSE create mode 100644 bare/README.md create mode 100644 bare/Response.mjs create mode 100644 bare/Server.mjs create mode 100644 bare/Standalone.mjs create mode 100644 bare/V1.mjs create mode 100644 bare/package.json create mode 100644 src/backend.mjs delete mode 100644 src/charinsert.js rename config.json => src/config.json (87%) rename randomization.json => src/data.json (87%) delete mode 100644 src/examples/proxy.js delete mode 100644 src/examples/static.js create mode 100644 src/proxy.mjs create mode 100644 src/randomization.mjs create mode 100644 src/routes.mjs create mode 100644 views/ads.txt create mode 100644 views/assets/img/uv.png create mode 100644 views/assets/img/uv2.png rename views/assets/js/{common-1644738239.js => common-16451543478.js} (68%) create mode 100644 views/index.js create mode 100644 views/pages/proxnav/ultraviolet.html create mode 100644 views/uv.bundle.js create mode 100644 views/uv.config.js create mode 100644 views/uv.handler.js create mode 100644 views/uv.sw.js diff --git a/backend.js b/backend.js index 9ec14bf1..8710d894 100644 --- a/backend.js +++ b/backend.js @@ -1,136 +1,5 @@ -/* ----------------------------------------------- - * Authors: QuiteAFancyEmerald, BinBashBanana (OlyB), YÖCTDÖNALD'S and the lime - * Additional help from Divide and SexyDuceDuce >:D test aaaa - * ----------------------------------------------- */ -const fs = require('fs'); -const path = require('path'); -const http = require('http'); -const https = require('https'); -const express = require('express'); -const corrosion = require('corrosion'); -const config = require('./config.json'); -const insert = require('./randomization.json'); -const app = express(); -const port = process.env.PORT || config.port; -const server = http.createServer(app); +(async() => { + await + import ('./src/backend.mjs'); -btoa = (str) => { - return new Buffer.from(str).toString('base64'); -} - -atob = (str) => { - return new Buffer.from(str, 'base64').toString('utf-8'); -} - -const text404 = fs.readFileSync(path.normalize(__dirname + '/views/404.html'), 'utf8'); -const pages = { - 'index': 'index.html', - /* Main */ - 'in': 'docs.html', - 'faq': 'faq.html', - 'j': 'hidden.html', - 's': 'pages/frame.html', - 'z': 'pages/surf.html', - 'c': 'pages/nav/credits.html', - 'x': 'pages/nav/bookmarklets.html', - 'i': 'pages/nav/icons.html', - 't': 'pages/nav/terms.html', - /* Games */ - 'g': 'pages/nav/gtools.html', - 'h': 'pages/nav/games5.html', - 'el': 'pages/nav/emulators.html', - 'f': 'pages/nav/flash.html', - 'm': 'pages/nav/emulibrary.html', - /* Proxies */ - 'q': 'pages/proxnav/corrosion.html', - 'rh': 'pages/proxnav/rammerhead.html', - 'w': 'pages/proxnav/womginx.html', - /* Proxy Presets */ - 'sx': 'pages/proxnav/preset/searx.html', - 'y': 'pages/proxnav/preset/youtube.html', - 'd': 'pages/proxnav/preset/discord.html', - 'r': 'pages/proxnav/preset/reddit.html', - /* Misc */ - 'fg': 'archive/gfiles/flash/index.html', - 'eg': 'archive/gfiles/rarch/index.html', - 'vos': 'archive/vibeOS/index.html' -}; - -const cookingInserts = insert.content; -const vegetables = insert.keywords; -const charRandom = insert.chars; -const splashRandom = insert.splash; -const cacheBustList = { - "styles.css": "styles-1644738239.css", - "h5-nav.js": "h5-nav-1644738239.js", - "desc.js": "desc-1644738239.js", - "header.js": "header-1644738239.js", - "footer.js": "footer-1644738239.js", - "common.js": "common-1644738239.js", - "links.js": "links-1644738239.js" -}; - -function randomListItem(lis) { - return lis[Math.floor(Math.random() * lis.length)]; -} - -function insertCharset(str) { - return str.replace(/­|​|­|/g, function() { return randomListItem(charRandom); }); // this needs to be inside a function, so that not every string is the same -} - -function hutaoInsert(str) { - return str.replace(//g, function() { return randomListItem(splashRandom); }); // this needs to be inside a function, so that not every string is the same -} - -function insertCooking(str) { - return str.replace(//g, function() { return '' + randomListItem(cookingInserts) + ''; }); // this needs to be inside a function, so that not every string is the same -} - -function cacheBusting(str) { - for (var item of Object.entries(cacheBustList)) { - str = str.replace(new RegExp(item[0], "g"), item[1]); - } - return str; -} - -function insertAll(str) { - return insertCharset(hutaoInsert(insertCooking(cacheBusting(str)))); -} - -function tryReadFile(file) { - return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : text404; -} - -let blacklist; - -const fetch = (...args) => - import ('node-fetch').then(({ default: fetch }) => fetch(...args)); - -fetch("https://blocklistproject.github.io/Lists/alt-version/everything-nl.txt").then(response => response.text()).then(data => { - blacklist = data.split("\n") && config.blacklist; -}); - -const proxy = new corrosion({ - title: config.title, - prefix: config.prefix || '/search/', - codec: config.codec || 'xor', - ws: config.ws, - requestMiddleware: [ - corrosion.middleware.blacklist(blacklist, 'Service not allowed due to bot protection! Make sure you are not trying to verify on a proxy.'), - ], -}); - -proxy.bundleScripts(); - -/* Querystring Navigation */ -app.get('/', async(req, res) => res.send(insertAll(tryReadFile(path.normalize(__dirname + '/views/' + (['/', '/?'].includes(req.url) ? pages.index : pages[Object.keys(req.query)[0]])))))); - -/* Static Files Served */ -app.use(express.static(path.normalize(__dirname + '/views'))); -app.use((req, res) => { - if (req.url.startsWith(proxy.prefix)) return proxy.request(req, res); - res.status(404).send(insertAll(text404)); -}); - -server.listen(port); -console.log('Holy Unblocker is listening on port ' + port + '. This is simply a public for Holy Unblocker. Certain functions may not work properly.'); \ No newline at end of file +})(); \ No newline at end of file diff --git a/bare/.gitignore b/bare/.gitignore new file mode 100644 index 00000000..8a775b3b --- /dev/null +++ b/bare/.gitignore @@ -0,0 +1,3 @@ +/package-lock.json +/node_modules/ +/tls/ \ No newline at end of file diff --git a/bare/EncodeProtocol.mjs b/bare/EncodeProtocol.mjs new file mode 100644 index 00000000..ccaccbea --- /dev/null +++ b/bare/EncodeProtocol.mjs @@ -0,0 +1,57 @@ +const valid_chars = "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~"; +const reserved_chars = "%"; + +export function valid_protocol(protocol){ + protocol = protocol.toString(); + + for(let i = 0; i < protocol.length; i++){ + const char = protocol[i]; + + if(!valid_chars.includes(char)){ + return false; + } + } + + return true; +} + +export function encode_protocol(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; +} + +export function decode_protocol(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; +} \ No newline at end of file diff --git a/bare/Example.mjs b/bare/Example.mjs new file mode 100644 index 00000000..d29caad4 --- /dev/null +++ b/bare/Example.mjs @@ -0,0 +1,46 @@ +import { Server as HTTPServer } from 'node:http'; +import { Server as BareServer } from './Server.mjs'; + +const bare_server = new BareServer({ + prefix: '/bare/', +}); + +const my_server = new HTTPServer(); + +my_server.on('request', (request, response) => { + // .route_request() will return true if the request's URL points to the bare server's prefix. + if(bare_server.route_request(request, response)){ + return; + } + + // send a response for web crawlers/discoverers + + const message = Buffer.from(`This server handles TOMP bare requests on the prefix: ${bare_server.prefix}`); + + response.writeHead(200, { + 'content-type': 'text/plain', + 'content-length': message.byteLength, + }); + + response.end(message); +}); + +my_server.on('upgrade', (request, socket, head) => { + if(bare_server.route_upgrade(request, socket, head)){ + return; + } + + // All upgrade sockets should go to TOMP + // Because we have nothing to do with the socket, we will close it. + + socket.end(); +}); + +my_server.on('listening', () => { + console.log('Listening on localhost:80'); +}); + +my_server.listen({ + host: 'localhost', + port: 80, +}); \ No newline at end of file diff --git a/bare/HeaderUtil.mjs b/bare/HeaderUtil.mjs new file mode 100644 index 00000000..b9d1be66 --- /dev/null +++ b/bare/HeaderUtil.mjs @@ -0,0 +1,33 @@ +export function ObjectFromRawHeaders(raw){ + const result = Object.setPrototypeOf({}, null); + + for(let i = 0; i < raw.length; i += 2){ + let [header,value] = raw.slice(i, i + 2); + if (result[header] != void[]) result[header] = [].concat(result[header], value); + else result[header] = value; + } + + return result; +} + +export function RawHeaderNames(raw){ + const result = []; + + for(let i = 0; i < raw.length; i += 2){ + if(!result.includes(i))result.push(raw[i]); + } + + return result; +} + +export function MapHeaderNamesFromArray(/*Array*/ from, /*Object*/ to){ + for(let header of from) { + if(to[header.toLowerCase()] != void[]){ + const value = to[header.toLowerCase()]; + delete to[header.toLowerCase()]; + to[header] = value; + } + } + + return to; +}; \ No newline at end of file diff --git a/bare/LICENSE b/bare/LICENSE new file mode 100644 index 00000000..e62ec04c --- /dev/null +++ b/bare/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/bare/README.md b/bare/README.md new file mode 100644 index 00000000..2d244ab6 --- /dev/null +++ b/bare/README.md @@ -0,0 +1,48 @@ +# TOMP Bare Server + +This repository implements the TompHTTP bare server. See the specification [here](https://github.com/tomphttp/specifications/blob/master/BareServerV1.md). + +## Usage + +We provide a command-line interface for creating a server. + +For more features, specify the `--help` option when running the CLI. + +### Quickstart + +1. Clone the repository locally +```sh +git clone https:/github.com/tomphttp/bare-server-node.git +``` + +2. Enter the folder +```sh +cd bare-server-node +``` + +3. Install dependencies +```sh +npm install +``` + +3. Start the server +```sh +node ./Standalone.mjs --port 80 --host localhost +``` + +### TLS + +In the cloned repository (See [quickstart](#quickstart)) + +1. Generate OpenSSL certificates (Unless you're bringing your own) +```sh +mkdir tls +openssl genrsa -out tls/key.pem +openssl req -new -key tls/key.pem -out tls/csr.pem +openssl x509 -req -days 9999 -in tls/csr.pem -signkey tls/key.pem -out tls/cert.pem +``` + +2. Start the server +```sh +node ./Standalone.mjs --port 443 --host localhost --tls --cert tls/cert.pem --key tls/key.pem +``` diff --git a/bare/Response.mjs b/bare/Response.mjs new file mode 100644 index 00000000..cc86e6de --- /dev/null +++ b/bare/Response.mjs @@ -0,0 +1,34 @@ +import { OutgoingMessage } from 'node:http'; +import { Stream } from 'node:stream'; + +export class Response { + headers = Object.setPrototypeOf({}, null); + status = 200; + constructor(body, status, headers){ + this.body = body; + + if(typeof status === 'number'){ + this.status = status; + } + + if(typeof headers === 'object' && headers !== undefined && headers !== null){ + Object.assign(this.headers, headers); + } + } + send(request){ + if(!(request instanceof OutgoingMessage))throw new TypeError('Request must be an OutgoingMessage'); + + request.writeHead(this.status, this.headers); + + if(this.body instanceof Stream){ + this.body.pipe(request); + }else if(this.body instanceof Buffer){ + request.write(this.body); + request.end(); + }else{ + request.end(); + } + + return true; + } +}; \ No newline at end of file diff --git a/bare/Server.mjs b/bare/Server.mjs new file mode 100644 index 00000000..e4b3bbcd --- /dev/null +++ b/bare/Server.mjs @@ -0,0 +1,146 @@ +import { v1, v1socket, v1wsmeta, v1wsnewmeta } from './V1.mjs'; +import { Response } from './Response.mjs'; + +export class Server { + prefix = '/'; + fof = this.json(404, { message: 'Not found.' }); + maintainer = undefined; + project = { + name: 'TOMPHTTP NodeJS Bare Server', + repository: 'https://github.com/tomphttp/bare-server-node', + }; + log_error = false; + constructor(directory, log_error, maintainer){ + if(typeof log_error === 'boolean'){ + this.log_error = true; + } + + if(typeof maintainer === 'object' && maintainer === null){ + this.maintainer = maintainer; + } + + if(typeof directory !== 'string'){ + throw new Error('Directory must be specified.') + } + + if(!directory.startsWith('/') || !directory.endsWith('/')){ + throw new RangeError('Directory must start and end with /'); + } + + this.directory = directory; + } + error(...args){ + if(this.log_error){ + console.error(...args); + } + } + json(status, json){ + const send = Buffer.from(JSON.stringify(json, null, '\t')); + + return new Response(send, status, { + 'content-type': 'application/json', + 'content-length': send.byteLength, + }); + } + route_request(request, response){ + if(request.url.startsWith(this.directory)){ + this.request(request, response); + return true; + }else{ + return false; + } + } + route_upgrade(request, socket, head){ + if(request.url.startsWith(this.directory)){ + this.upgrade(request, socket, head); + return true; + }else{ + return false; + } + } + get instance_info(){ + return { + versions: [ 'v1' ], + language: 'NodeJS', + memoryUsage: Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100, + maintainer: this.maintainer, + developer: this.project, + }; + } + async upgrade(request, socket, head){ + const service = request.url.slice(this.directory.length - 1); + + try{ + switch(service){ + case'/v1/': + await v1socket(this, request, socket, head); + break; + default: + socket.end(); + break; + } + }catch(err){ + this.error(err); + socket.end(); + } + } + async request(server_request, server_response){ + const service = server_request.url.slice(this.directory.length - 1); + let response; + + try{ + switch(service){ + case'/': + + if(server_request.method != 'GET')response = this.json(405, { message: 'This route only accepts the GET method.' }); + else response = this.json(200, this.instance_info); + + break; + case'/v1/': + + response = await v1(this, server_request); + + break; + case'/v1/ws-meta': + + response = await v1wsmeta(this, server_request); + + break; + case'/v1/ws-new-meta': + + response = await v1wsnewmeta(this, server_request); + + break; + default: + + response = this.fof; + + } + }catch(err){ + this.error(err); + + if(err instanceof Error){ + response = this.json(500, { + code: 'UNKNOWN', + id: `error.${err.name}`, + message: err.message, + stack: err.stack, + }); + }else{ + response = this.json(500, { + code: 'UNKNOWN', + id: 'error.Exception', + message: err, + stack: new Error(err).stack, + }); + } + } + + if(!(response instanceof Response)){ + this.error('Response to', server_request.url, 'was not a response.'); + response = this.fof; + } + + response.send(server_response); + } +}; \ No newline at end of file diff --git a/bare/Standalone.mjs b/bare/Standalone.mjs new file mode 100644 index 00000000..b87dc9ef --- /dev/null +++ b/bare/Standalone.mjs @@ -0,0 +1,81 @@ +import { Server as BareServer } from './Server.mjs'; +import { Server as HTTPServer, Server } from 'node:http'; +import { Server as TLSHTTPServer } from 'node:https'; +import { readFile } from 'node:fs/promises'; +import { cwd } from 'node:process'; + +import { program, Option } from 'commander'; +import { resolve } from 'node:path'; + +const default_port = Symbol(); + +program +.addOption(new Option('--d, --directory ', 'Bare directory').default('/')) +.addOption(new Option('--h, --host ', 'Listening host').default('localhost')) +.addOption(new Option('--p, --port ', 'Listening port').default(default_port).env('PORT')) +.addOption(new Option('--e, --errors', 'Error logging').default(false)) +.addOption(new Option('--tls', 'use HTTPS (TLS/SSL)')) +.addOption(new Option('--cert ', 'TLS certificate').default('')) +.addOption(new Option('--key ', 'TLS key').default('')) +; + +program.parse(process.argv); + +const options = program.opts(); + +const bare = new BareServer(options.directory, options.errors); +console.info('Created Bare Server on directory:', options.directory); +console.info('Error logging is', options.errors ? 'enabled.' : 'disabled.'); + +let http; + +if(options.tls){ + const tls = {}; + + if(options.key !== ''){ + options.key = resolve(cwd(), options.key); + console.info('Reading key from file:', options.key); + tls.key = await readFile(options.key); + } + + if(options.cert !== ''){ + options.cert = resolve(cwd(), options.cert); + console.info('Reading certificate from file:', options.cert); + tls.cert = await readFile(options.cert); + } + + http = new TLSHTTPServer(tls); + console.info('Created TLS HTTP server.'); +}else{ + http = new HTTPServer(); + console.info('Created HTTP server.'); +} + +http.on('request', (req, res) => { + if(bare.route_request(req, res))return; + + res.writeHead(400); + res.send('Not found'); +}); + +http.on('upgrade', (req, socket, head) => { + if(bare.route_upgrade(req, socket, head))return; + socket.end(); +}); + +if(options.port === default_port){ + if(options.tls){ + options.port = 443; + }else{ + options.port = 80; + } +} + +http.on('listening', () => { + console.log(`HTTP server listening. View live at ${options.tls ? 'https:' : 'http:'}//${options.host}:${options.port}${options.directory}`); +}); + +http.listen({ + host: options.host, + port: options.port, +}); \ No newline at end of file diff --git a/bare/V1.mjs b/bare/V1.mjs new file mode 100644 index 00000000..75c809ff --- /dev/null +++ b/bare/V1.mjs @@ -0,0 +1,384 @@ +import http from 'node:http'; +import https from 'node:https'; +import { MapHeaderNamesFromArray, RawHeaderNames } from './HeaderUtil.mjs'; +import { decode_protocol } from './EncodeProtocol.mjs'; +import { Response } from './Response.mjs'; +import { randomBytes } from 'node:crypto'; +import { promisify } from 'node:util'; + +const randomBytesAsync = promisify(randomBytes); + +// max of 4 concurrent sockets, rest is queued while busy? set max to 75 +// const http_agent = http.Agent(); +// const https_agent = https.Agent(); + +async function Fetch(server_request, request_headers, url){ + const options = { + host: url.host, + port: url.port, + path: url.path, + method: server_request.method, + headers: request_headers, + }; + + let outgoing; + + if(url.protocol === 'https:'){ + outgoing = https.request(options); + }else if(url.protocol === 'http:'){ + outgoing = http.request(options); + }else{ + throw new RangeError(`Unsupported protocol: '${url.protocol}'`); + } + + server_request.pipe(outgoing); + + return await new Promise((resolve, reject) => { + outgoing.on('response', resolve); + outgoing.on('error', reject); + }); +} + +function load_forwarded_headers(request, forward, target){ + const raw = RawHeaderNames(request.rawHeaders); + + for(let header of forward){ + for(let cap of raw){ + if(cap.toLowerCase() == header){ + // header exists and real capitalization was found + target[cap] = request.headers[header]; + } + } + } +} + +function read_headers(server_request, request_headers){ + const remote = Object.setPrototypeOf({}, null); + const headers = Object.setPrototypeOf({}, null); + + for(let remote_prop of ['host','port','protocol','path']){ + const header = `x-bare-${remote_prop}`; + + if(header in request_headers){ + let value = request_headers[header]; + + if(remote_prop === 'port'){ + value = parseInt(value); + if(isNaN(value))return { + error: { + code: 'INVALID_BARE_HEADER', + id: `request.headers.${header}`, + message: `Header was not a valid integer.`, + }, + }; + } + + remote[remote_prop] = value; + }else{ + return { + error: { + code: 'MISSING_BARE_HEADER', + id: `request.headers.${header}`, + message: `Header was not specified.`, + }, + }; + } + } + + if('x-bare-headers' in request_headers){ + let json; + + try{ + json = JSON.parse(request_headers['x-bare-headers']); + + for(let header in json){ + if(typeof json[header] !== 'string' && !Array.isArray(json[header])){ + return { + error: { + code: 'INVALID_BARE_HEADER', + id: `bare.headers.${header}`, + message: `Header was not a String or Array.`, + }, + }; + } + } + }catch(err){ + return { + error: { + code: 'INVALID_BARE_HEADER', + id: `request.headers.${header}`, + message: `Header contained invalid JSON. (${err.message})`, + }, + }; + } + + Object.assign(headers, json); + }else{ + return { + error: { + code: 'MISSING_BARE_HEADER', + id: `request.headers.x-bare-headers`, + message: `Header was not specified.`, + }, + }; + } + + if('x-bare-forward-headers' in request_headers){ + let json; + + try{ + json = JSON.parse(request_headers['x-bare-forward-headers']); + }catch(err){ + return { + error: { + code: 'INVALID_BARE_HEADER', + id: `request.headers.x-bare-forward-headers`, + message: `Header contained invalid JSON. (${err.message})`, + }, + }; + } + + load_forwarded_headers(server_request, json, headers); + }else{ + return { + error: { + code: 'MISSING_BARE_HEADER', + id: `request.headers.x-bare-forward-headers`, + message: `Header was not specified.`, + }, + }; + } + + return { remote, headers }; +} + +export async function v1(server, server_request){ + const response_headers = Object.setPrototypeOf({}, null); + + response_headers['x-robots-tag'] = 'noindex'; + response_headers['access-control-allow-headers'] = '*'; + response_headers['access-control-allow-origin'] = '*'; + response_headers['access-control-expose-headers'] = '*'; + + const { error, remote, headers } = read_headers(server_request, server_request.headers); + + if(error){ + // sent by browser, not client + if(server_request.method === 'OPTIONS'){ + return new Response(undefined, 200, response_headers); + }else{ + return server.json(400, error); + } + } + + let response; + + try{ + response = await Fetch(server_request, headers, remote); + }catch(err){ + if(err instanceof Error){ + switch(err.code){ + case'ENOTFOUND': + return server.json(500, { + code: 'HOST_NOT_FOUND', + id: 'request', + message: 'The specified host could not be resolved.', + }); + case'ECONNREFUSED': + return server.json(500, { + code: 'CONNECTION_REFUSED', + id: 'response', + message: 'The remote rejected the request.', + }); + case'ECONNRESET': + return server.json(500, { + code: 'CONNECTION_RESET', + id: 'response', + message: 'The request was forcibly closed.', + }); + case'ETIMEOUT': + return server.json(500, { + code: 'CONNECTION_TIMEOUT', + id: 'response', + message: 'The response timed out.', + }); + } + } + + throw err; + } + + for(let header in response.headers){ + if(header === 'content-encoding' || header === 'x-content-encoding'){ + response_headers['content-encoding'] = response.headers[header]; + }else if(header === 'content-length'){ + response_headers['content-length'] = response.headers[header]; + } + } + + response_headers['x-bare-headers'] = JSON.stringify(MapHeaderNamesFromArray(RawHeaderNames(response.rawHeaders), {...response.headers})); + response_headers['x-bare-status'] = response.statusCode + response_headers['x-bare-status-text'] = response.statusMessage; + + return new Response(response, 200, response_headers); +} + +// prevent users from specifying id=__proto__ or id=constructor +const temp_meta = Object.setPrototypeOf({}, null); + +setInterval(() => { + for(let id in temp_meta){ + if(temp_meta[id].expires < Date.now()){ + delete temp_meta[id]; + } + } +}, 1e3); + +export async function v1wsmeta(server, server_request){ + if(!('x-bare-id' in server_request.headers)){ + return server.json(400, { + code: 'MISSING_BARE_HEADER', + id: 'request.headers.x-bare-id', + message: 'Header was not specified', + }); + } + + const id = server_request.headers['x-bare-id']; + + if(!(id in temp_meta)){ + return server.json(400, { + code: 'INVALID_BARE_HEADER', + id: 'request.headers.x-bare-id', + message: 'Unregistered ID', + }); + } + + const { meta } = temp_meta[id]; + + delete temp_meta[id]; + + return server.json(200, meta); +} + +export async function v1wsnewmeta(server, server_request){ + const id = (await randomBytesAsync(32)).toString('hex'); + + temp_meta[id] = { + expires: Date.now() + 30e3, + }; + + return new Response(Buffer.from(id.toString('hex'))) +} + +export async function v1socket(server, server_request, server_socket, server_head){ + if(!server_request.headers['sec-websocket-protocol']){ + server_socket.end(); + return; + } + + const [ first_protocol, data ] = server_request.headers['sec-websocket-protocol'].split(/,\s*/g); + + if(first_protocol !== 'bare'){ + server_socket.end(); + return; + } + + const { + remote, + headers, + forward_headers, + id, + } = JSON.parse(decode_protocol(data)); + + load_forwarded_headers(server_request, forward_headers, headers); + + const options = { + host: remote.host, + port: remote.port, + path: remote.path, + headers, + method: server_request.method, + }; + + let request_stream; + + let response_promise = new Promise((resolve, reject) => { + try{ + if(remote.protocol === 'wss:'){ + request_stream = https.request(options, res => { + reject(`Remote didn't upgrade the request`); + }); + }else if(remote.protocol === 'ws:'){ + request_stream = http.request(options, res => { + reject(`Remote didn't upgrade the request`); + }); + }else{ + return reject(new RangeError(`Unsupported protocol: '${remote.protocol}'`)); + } + + request_stream.on('upgrade', (...args) => { + resolve(args) + }); + + request_stream.on('error', reject); + request_stream.write(server_head); + request_stream.end(); + }catch(err){ + reject(err); + } + }); + + const [ response, socket, head ] = await response_promise; + + if('id' in temp_meta){ + if(typeof id !== 'string'){ + socket.end(); + return; + } + + const meta = { + headers: MapHeaderNamesFromArray(RawHeaderNames(response.rawHeaders), {...response.headers}), + }; + + temp_meta[id].meta = meta; + } + + + const response_headers = [ + `HTTP/1.1 101 Switching Protocols`, + `Upgrade: websocket`, + `Connection: Upgrade`, + `Sec-WebSocket-Protocol: bare`, + `Sec-WebSocket-Accept: ${response.headers['sec-websocket-accept']}`, + ]; + + if('sec-websocket-extensions' in response.headers){ + response_headers.push(`Sec-WebSocket-Extensions: ${response.headers['sec-websocket-extensions']}`); + } + + server_socket.write(response_headers.concat('', '').join('\r\n')); + server_socket.write(head); + + socket.on('close', () => { + // console.log('Remote closed'); + server_socket.end(); + }); + + server_socket.on('close', () => { + // console.log('Serving closed'); + socket.end(); + }); + + socket.on('error', err => { + server.error('Remote socket error:', err); + server_socket.end(); + }); + + server_socket.on('error', err => { + server.error('Serving socket error:', err); + socket.end(); + }); + + socket.pipe(server_socket); + server_socket.pipe(socket); +} diff --git a/bare/package.json b/bare/package.json new file mode 100644 index 00000000..4c1b2a19 --- /dev/null +++ b/bare/package.json @@ -0,0 +1,30 @@ +{ + "name": "tomp-bare-server", + "description": "Back.", + "version": "0.0.1", + "homepage": "https://github.com/tomphttp", + "bugs": { + "url": "https://github.com/tomphttp/bare-server-node/issues", + "email": "tomp@sys32.dev" + }, + "repository": { + "type": "git", + "url": "https://github.com/tomphttp/bare-server-node.git" + }, + "author": { + "name": "TOMP Development", + "email": "tomp@sys32.dev", + "url": "https://github.com/tomphttp" + }, + "keywords": [ + "proxy", + "tomp", + "tomphttp", + "sys32" + ], + "license": "GPL-3.0", + "type": "module", + "dependencies": { + "commander": "^9.0.0" + } +} diff --git a/ecosystem.config.js b/ecosystem.config.js index ecdffd90..6dcb8491 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,17 +1,18 @@ module.exports = { apps: [{ - name: 'HolyUnblockerPublic', + name: 'HolyUB', script: './backend.js', env: { - PORT: 8080, + PORT: 8078, NODE_ENV: "development", }, env_production: { - PORT: 8080, + PORT: 8078, NODE_ENV: "production", }, instances: "1", - exec_mode: "cluster", + exec_interpreter: "babel-node", + exec_mode: "fork", autorestart: true, exp_backoff_restart_delay: 100, cron_restart: "*/10 * * * *", diff --git a/package.json b/package.json index df93977a..3a331fa0 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,11 @@ "author": "Titanium Network", "license": "MIT", "dependencies": { + "babel": "^6.23.0", "corrosion": "^1.0.0", "express": "^4.17.1", "mime-types": "^2.1.27", "node-fetch": "^3.2.0", "ws": "^7.5.3" - }, - "engines": { - "node": ">=12.20.0" } } diff --git a/src/backend.mjs b/src/backend.mjs new file mode 100644 index 00000000..6fbbbe20 --- /dev/null +++ b/src/backend.mjs @@ -0,0 +1,29 @@ +import http from 'http'; +import path from 'path'; +import express from 'express'; +import { readFile } from 'fs/promises'; +import pkg from './routes.mjs'; +import { paintSource, tryReadFile } from './randomization.mjs'; +import { proxy } from './proxy.mjs'; + +const config = JSON.parse(await readFile(new URL('./config.json', + import.meta.url))); +const { pages, text404 } = pkg; +const __dirname = path.resolve(); +const port = process.env.PORT || config.port; +const app = express(); +const router = express.Router(); +const server = http.createServer(app); +proxy.bundleScripts(); + +router.get('/', async(req, res) => res.send(paintSource(tryReadFile(path.normalize(__dirname + '/views/' + (['/', '/?'].includes(req.url) ? pages.index : pages[Object.keys(req.query)[0]])))))); +app.use(router); +app.use(express.static(path.normalize(__dirname + '/views'))); +app.disable('x-powered-by'); +app.use((req, res) => { + if (req.url.startsWith(proxy.prefix)) return proxy.request(req, res); + res.status(404).send(paintSource(text404)); +}); + +server.listen(port); +console.log('Holy Unblocker is listening on port ' + port + '.'); \ No newline at end of file diff --git a/src/charinsert.js b/src/charinsert.js deleted file mode 100644 index 68017372..00000000 --- a/src/charinsert.js +++ /dev/null @@ -1,53 +0,0 @@ -/* ----------------------------------------------- -/* Author : Divide -/* MIT license: http://opensource.org/licenses/MIT -/* ----------------------------------------------- */ - -var fs = require('fs'), - path = require('path'), - mime = require('mime-types'), - char_insert = (str, hash) => { - var output = ''; - - str.split(' ').forEach((word, word_index) => (word.split('').forEach((chr, chr_index) => output += (!chr_index || chr_index == word.length) ? '&#' + chr.charCodeAt() + '' : '​' + hash + '&#' + chr.charCodeAt() + '​'), output += word_index != str.split(' ').length - 1 ? ' ' : '')); - - return output - }, - hash = s => { for (var i = 0, h = 9; i < s.length;) h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9); return h ^ h >>> 9 }, - express_ip = req => { - var ip = null, - methods = [req.headers['cf-connecting-ip'], req.headers['x-real-ip'], req.headers['x-forwarded-for']]; - - methods.filter(method => method).forEach(method => { - if (ip) return; - - ip = method; - - if (ip.includes(',')) { - ip = ip.split(',')[ip.split(',').length - 1].replace(' ', ''); - if (ip.length > 15) ip = ip.split(',')[0].replace(' ', ''); - } - }); - - return ip || '127.0.0.1'; - }; - -module.exports = { - static: public_path => (req, res, next) => { - var pub_file = path.join(public_path, req.url); - - if (fs.existsSync(pub_file)) { - if (fs.statSync(pub_file).isDirectory()) pub_file = path.join(pub_file, 'index.html'); - if (!fs.existsSync(pub_file)) return next(); - - var mime_type = mime.lookup(pub_file), - data = fs.readFileSync(pub_file), - ext = path.extname(pub_file); - - if (ext == '.html') data = data.toString('utf8').replace(/char_?insert{([\s\S]*?)}/gi, (match, str) => char_insert(str, hash(express_ip(req)))); - - res.contentType(mime_type).send(data); - } else next(); - }, - parse: string => char_insert(string, '-1231'), -} \ No newline at end of file diff --git a/config.json b/src/config.json similarity index 87% rename from config.json rename to src/config.json index ecd29f8a..de8279bc 100644 --- a/config.json +++ b/src/config.json @@ -3,7 +3,7 @@ "port": "8080", "ssl": false, "ws": true, - "prefix": "/search/", + "prefix": "/service/", "codec": "xor", "blacklist": [ "accounts.google.com", diff --git a/randomization.json b/src/data.json similarity index 87% rename from randomization.json rename to src/data.json index 702ce88d..a4ad5909 100644 --- a/randomization.json +++ b/src/data.json @@ -3,7 +3,7 @@ "­", "​", "­" ], "keywords": [ - "Example1", "Example2" + "Example1", "Example2", "Example3", "Example4", "Example5", "Example6", "Example7", "Example8", "Example9" ], "content": [ "Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1Example1", diff --git a/src/examples/proxy.js b/src/examples/proxy.js deleted file mode 100644 index d8337abf..00000000 --- a/src/examples/proxy.js +++ /dev/null @@ -1,25 +0,0 @@ -const http = require('http'); -const express = require('express'); -const corrosion = require('corrosion'); -const app = express(); -const port = process.env.PORT || 8443; -const server = http.createServer(app); -const examples = ['accounts.google.com', 'example.com']; - -let blacklist; -const fetch = (...t) => - import ("node-fetch").then(({ default: e }) => e(...t)); -fetch("https://blocklistproject.github.io/Lists/alt-version/everything-nl.txt").then(t => t.text()).then(t => { blacklist = t.split("\n") && examples }); - -const proxy = new corrosion({ - title: 'Untitled Document', - prefix: '/search/', - codec: 'xor', - ws: true, - requestMiddleware: [ - corrosion.middleware.blacklist(blacklist, 'Service not allowed due to bot protection! Make sure you are not trying to verify on a proxy.'), - ], -}); - -proxy.bundleScripts(); -server.listen(port); \ No newline at end of file diff --git a/src/examples/static.js b/src/examples/static.js deleted file mode 100644 index cb006d5b..00000000 --- a/src/examples/static.js +++ /dev/null @@ -1,29 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const http = require('http'); -const express = require('express'); -const app = express(); -const port = process.env.PORT || 8080; -const server = http.createServer(app); -const text404 = fs.readFileSync(path.normalize(__dirname + '/views/404.html'), 'utf8'); -const pages = { index: "index.html", in: "docs.html", faq: "faq.html", j: "hidden.html", s: "pages/frame.html", z: "pages/surf.html", c: "pages/nav/credits.html", x: "pages/nav/bookmarklets.html", i: "pages/nav/icons.html", t: "pages/nav/terms.html", g: "pages/nav/gtools.html", h: "pages/nav/games5.html", el: "pages/nav/emulators.html", f: "pages/nav/flash.html", m: "pages/nav/emulibrary.html", q: "pages/proxnav/corrosion.html", w: "pages/proxnav/womginx.html", y: "pages/proxnav/preset/youtube.html", d: "pages/proxnav/preset/discord.html", r: "pages/proxnav/preset/reddit.html", fg: "archive/gfiles/flash/index.html", eg: "archive/gfiles/rarch/index.html", vos: "archive/vibeOS/index.html" }; -const cacheBustList = { "common.js": "common-1643838852.js", "links.js": "links-1642900360.js" }; - -function cacheBusting(str) { - for (var item of Object.entries(cacheBustList)) { - str = str.replace(new RegExp(item[0], "g"), item[1]); - } - return str; -} - -function insertAll(str) { - return cacheBusting(str); -} - -function tryReadFile(file) { - return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : text404; -} - -app.get('/', async(req, res) => res.send(insertAll(tryReadFile(path.normalize(__dirname + '/views/' + (['/', '/?'].includes(req.url) ? pages.index : pages[Object.keys(req.query)[0]])))))); -app.use(express.static(path.normalize(__dirname + "/views"))), app.use((e, s) => { s.status(404).send(insertAll(text404)) }); -server.listen(port); \ No newline at end of file diff --git a/src/proxy.mjs b/src/proxy.mjs new file mode 100644 index 00000000..2a1184e9 --- /dev/null +++ b/src/proxy.mjs @@ -0,0 +1,24 @@ +import { readFile } from 'fs/promises'; +import corrosion from 'corrosion'; + +const config = JSON.parse(await readFile(new URL('./config.json', + import.meta.url))); + +let blacklist; + +const fetch = (...args) => + import ('node-fetch').then(({ default: fetch }) => fetch(...args)); + +fetch("https://blocklistproject.github.io/Lists/alt-version/everything-nl.txt").then(response => response.text()).then(data => { + blacklist = data.split("\n") && config.blacklist; +}); + +export const proxy = new corrosion({ + title: config.title, + prefix: config.prefix || '/service/', + codec: config.codec || 'xor', + ws: config.ws, + requestMiddleware: [ + corrosion.middleware.blacklist(blacklist, 'Service not allowed due to bot protection! Make sure you are not trying to verify on a proxy.'), + ], +}); \ No newline at end of file diff --git a/src/randomization.mjs b/src/randomization.mjs new file mode 100644 index 00000000..3629c45c --- /dev/null +++ b/src/randomization.mjs @@ -0,0 +1,34 @@ +import pkg from './routes.mjs'; +import { existsSync, readFileSync } from 'fs'; +const { cookingInserts, vegetables, charRandom, splashRandom, cacheBustList, text404 } = pkg; + +function randomListItem(lis) { + return lis[Math.floor(Math.random() * lis.length)]; +} + +function insertCharset(str) { + return str.replace(/­|​|­|/g, function() { return randomListItem(charRandom); }); +} + +function hutaoInsert(str) { + return str.replace(//g, function() { return randomListItem(splashRandom); }); +} + +function insertCooking(str) { + return str.replace(//g, function() { return '' + randomListItem(cookingInserts) + ''; }); // this needs to be inside a function, so that not every string is the same +} + +function cacheBusting(str) { + for (var item of Object.entries(cacheBustList)) { + str = str.replace(new RegExp(item[0], "g"), item[1]); + } + return str; +} + +export function paintSource(str) { + return insertCharset(hutaoInsert(insertCooking(cacheBusting(str)))); +} + +export function tryReadFile(file) { + return existsSync(file) ? readFileSync(file, 'utf8') : text404; +} \ No newline at end of file diff --git a/src/routes.mjs b/src/routes.mjs new file mode 100644 index 00000000..dfffa06d --- /dev/null +++ b/src/routes.mjs @@ -0,0 +1,59 @@ +import { readFileSync } from 'fs'; +import path from 'path'; +import { readFile } from 'fs/promises'; + + +const insert = JSON.parse(await readFile(new URL('./data.json', + import.meta.url))); + +const __dirname = path.resolve(); + +const text404 = readFileSync(path.normalize(__dirname + '/views/404.html'), 'utf8'); +const pages = { + 'index': 'index.html', + /* Main */ + 'in': 'docs.html', + 'faq': 'faq.html', + 'j': 'hidden.html', + 's': 'pages/frame.html', + 'z': 'pages/surf.html', + 'c': 'pages/nav/credits.html', + 'x': 'pages/nav/bookmarklets.html', + 'i': 'pages/nav/icons.html', + 't': 'pages/nav/terms.html', + /* Games */ + 'g': 'pages/nav/gtools.html', + 'h': 'pages/nav/games5.html', + 'el': 'pages/nav/emulators.html', + 'f': 'pages/nav/flash.html', + 'm': 'pages/nav/emulibrary.html', + /* Proxies */ + 'q': 'pages/proxnav/ultraviolet.html', + 'rh': 'pages/proxnav/rammerhead.html', + 'w': 'pages/proxnav/womginx.html', + /* Proxy Presets */ + 'sx': 'pages/proxnav/preset/searx.html', + 'y': 'pages/proxnav/preset/youtube.html', + 'd': 'pages/proxnav/preset/discord.html', + 'r': 'pages/proxnav/preset/reddit.html', + /* Misc */ + 'fg': 'archive/gfiles/flash/index.html', + 'eg': 'archive/gfiles/rarch/index.html', + 'vos': 'archive/vibeOS/index.html' +}; + +const cookingInserts = insert.content; +const vegetables = insert.keywords; +const charRandom = insert.chars; +const splashRandom = insert.splash; +const cacheBustList = { + "styles.css": "styles-1644738239.css", + "h5-nav.js": "h5-nav-1644738239.js", + "desc.js": "desc-1644738239.js", + "header.js": "header-1644738239.js", + "footer.js": "footer-1644738239.js", + "common.js": "common-16451543478.js", + "links.js": "links-1644738239.js" +}; + +export default { pages, text404, cookingInserts, vegetables, charRandom, splashRandom, cacheBustList }; \ No newline at end of file diff --git a/views/ads.txt b/views/ads.txt new file mode 100644 index 00000000..59bfdc25 --- /dev/null +++ b/views/ads.txt @@ -0,0 +1 @@ +google.com, pub-1493380855147376, DIRECT, f08c47fec0942fa0 \ No newline at end of file diff --git a/views/assets/img/uv.png b/views/assets/img/uv.png new file mode 100644 index 0000000000000000000000000000000000000000..9ee3ec593488bffd247a9db92a8d559b887ad67f GIT binary patch literal 20873 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 ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join('')); + }, + decode(str) { + if (!str) return str; + let [input, ...search] = str.split('?'); + + return decodeURIComponent(input).split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char).join('') + (search.length ? '?' + search.join('?') : ''); + }, +}; /* To use: * goProx.proxy(url-string, stealth-boolean-optional) @@ -41,13 +64,13 @@ let sxlink = 'https://searx.degenerate.info'; */ window.goProx = { - corrosion: function(url, stealth) { + ultraviolet: function(url, stealth) { setAuthCookie("__cor_auth=1", true); - goToUrl("https://" + getDomain() + "/search/gateway?url=" + url, stealth); + goToUrl("https://" + getDomain() + "/service/" + xor.encode(omnibox(url)), stealth); }, womginx: function(url, stealth) { setAuthCookie("wgauth=yes", false); - goToUrl("https://a." + getDomain() + "/main/" + url, stealth); + goToUrl("https://a." + getDomain() + "/main/" + omnibox(url), stealth); }, searx: function(stealth) { setAuthCookie("oldsmobile=badcar", true); @@ -66,23 +89,23 @@ window.goProx = { }, mcnow: function(stealth) { setAuthCookie("__cor_auth=1", false); - goToUrl("https://cdn." + getDomain() + "/search/gateway?url=" + ('https://now.gg/play/mojang/2534/minecraft-trial'), stealth); + goToUrl("https://cdn." + getDomain() + "/sw/" + xor.encode('https://now.gg/play/mojang/2534/minecraft-trial'), stealth); }, glife: function(stealth) { setAuthCookie("__cor_auth=1", false); - goToUrl("https://cdn." + getDomain() + "/search/gateway?url=" + ('https://now.gg/play/lunime/5767/gacha-life'), stealth); + goToUrl("https://cdn." + getDomain() + "/sw/" + xor.encode('https://now.gg/play/lunime/5767/gacha-life'), stealth); }, roblox: function(stealth) { setAuthCookie("__cor_auth=1", false); - goToUrl("https://cdn." + getDomain() + "/search/gateway?url=" + ('https://now.gg/play/roblox-corporation/5349/roblox'), stealth); + goToUrl("https://cdn." + getDomain() + "/sw/" + xor.encode('https://now.gg/play/roblox-corporation/5349/roblox'), stealth); }, amongus: function(stealth) { setAuthCookie("__cor_auth=1", false); - goToUrl("https://cdn." + getDomain() + "/search/gateway?url=" + ('https://now.gg/play/innersloth-llc/4047/among-us'), stealth); + goToUrl("https://cdn." + getDomain() + "/sw/" + xor.encode('https://now.gg/play/innersloth-llc/4047/among-us'), stealth); }, pubg: function(stealth) { setAuthCookie("__cor_auth=1", false); - goToUrl("https://cdn." + getDomain() + "/search/gateway?url=" + ('https://now.gg/play/proxima-beta/2609/pubg-mobile-resistance'), stealth); + goToUrl("https://cdn." + getDomain() + "/sw/" + xor.encode('https://now.gg/play/proxima-beta/2609/pubg-mobile-resistance'), stealth); }, train: function(stealth) { setAuthCookie("wgauth=yes", false); diff --git a/views/assets/js/csel.js b/views/assets/js/csel.js index fb1a7dc4..49ed0281 100644 --- a/views/assets/js/csel.js +++ b/views/assets/js/csel.js @@ -5,11 +5,11 @@ /* ----------------------------------------------- */ (function() { - var date = new Date(); + let date = new Date(); date.setFullYear(date.getFullYear() + 100); date = date.toUTCString(); - var csel = document.getElementById("csel"); + let csel = document.getElementById("csel"); function setCookie(name, value) { document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + date + "; "; @@ -20,23 +20,23 @@ } async function readCookie(name) { - var cookie = document.cookie.split("; "); - var cookies = {}; - for (var i = 0; i < cookie.length; i++) { - var p = cookie[i].split("="); + let cookie = document.cookie.split("; "); + let cookies = {}; + for (let i = 0; i < cookie.length; i++) { + let p = cookie[i].split("="); cookies[p[0]] = p[1]; } return decodeURIComponent(cookies[name]); } function pageTitle(value) { - var tag = document.getElementsByTagName("title")[0] || document.createElement("title"); + let tag = document.getElementsByTagName("title")[0] || document.createElement("title"); tag.innerHTML = value; document.head.appendChild(tag); } function pageIcon(value) { - var tag = document.querySelector("link[rel*='icon']") || document.createElement("link"); + let tag = document.querySelector("link[rel*='icon']") || document.createElement("link"); tag.rel = "icon"; tag.href = value; document.head.appendChild(tag); diff --git a/views/index.html b/views/index.html index 3c1bbae6..4b1e2c51 100644 --- a/views/index.html +++ b/views/index.html @@ -13,6 +13,7 @@ + @@ -37,6 +38,7 @@ + ADSENSEID diff --git a/views/index.js b/views/index.js new file mode 100644 index 00000000..29e0b431 --- /dev/null +++ b/views/index.js @@ -0,0 +1,6 @@ +importScripts('./uv.bundle.js'); +importScripts('./uv.handler.js'); + +__uv.client.location.overrideWorkerLocation(() => new URL('https://www.google.com')); + +console.log(postMessage.__uv$string); \ No newline at end of file diff --git a/views/pages/frame.html b/views/pages/frame.html index 68c67822..41cb910a 100644 --- a/views/pages/frame.html +++ b/views/pages/frame.html @@ -37,6 +37,8 @@ } */ + + diff --git a/views/pages/proxnav/corrosion.html b/views/pages/proxnav/corrosion.html index 53bcbf21..15aa5050 100644 --- a/views/pages/proxnav/corrosion.html +++ b/views/pages/proxnav/corrosion.html @@ -12,6 +12,9 @@ + + + @@ -28,7 +31,7 @@

Th­e mai­n pro­xy f­or Ti­tan­ium Ne­two­rk wi­th en­hanc­ed suppo­rt for a lar­ge major­ity o­f si­tes wi­th su­ppo­rt for hCA­PTCH­A.

diff --git a/views/pages/proxnav/preset/discord.html b/views/pages/proxnav/preset/discord.html index 29ed114a..7ce677cf 100644 --- a/views/pages/proxnav/preset/discord.html +++ b/views/pages/proxnav/preset/discord.html @@ -49,15 +49,17 @@ + + diff --git a/views/pages/proxnav/preset/reddit.html b/views/pages/proxnav/preset/reddit.html index f55abd05..84e3b8ab 100644 --- a/views/pages/proxnav/preset/reddit.html +++ b/views/pages/proxnav/preset/reddit.html @@ -37,13 +37,15 @@ Local Libre­dd­it In­stance (Classic) Local Libre­dd­it In­stance (Stealth)
- Cor­ros­ion (Classic) - Cor­ros­ion (Stealth) + U­V (Classic) + U­V (Stealth) + + diff --git a/views/pages/proxnav/preset/searx.html b/views/pages/proxnav/preset/searx.html index 87e4c5a8..3986c1be 100644 --- a/views/pages/proxnav/preset/searx.html +++ b/views/pages/proxnav/preset/searx.html @@ -35,8 +35,7 @@
Sometimes the pro­xie­s are under high load so things may be s­low, sorry. In that case simply wait for the pa­ge to load.

diff --git a/views/pages/proxnav/preset/youtube.html b/views/pages/proxnav/preset/youtube.html index cd9315a2..c90eee42 100644 --- a/views/pages/proxnav/preset/youtube.html +++ b/views/pages/proxnav/preset/youtube.html @@ -36,15 +36,15 @@
Sometimes the pro­xie­s are under high load so things may be s­low, sorry. In that case simply wait for the pa­ge to load.

+ + diff --git a/views/pages/proxnav/ultraviolet.html b/views/pages/proxnav/ultraviolet.html new file mode 100644 index 00000000..5ad08887 --- /dev/null +++ b/views/pages/proxnav/ultraviolet.html @@ -0,0 +1,71 @@ + + + + + + + U­ltrav­iolet (Be­ta) + + + + + + + + + + + + +
+
+
+
+ +
+

Ultraviolet (Beta)

+ +

T­he hi­ghly inno­vati­ve pr­ox­y of Tit­aniu­m Netw­ork usi­ng tec­hnologi­es such as servi­ce wor­kers and soph­isticated rewr­iting techniq­ues wit­h + CAPT­CHA suppor­t.

+ +

More Information:

+
+

+ Focusing on speed UV works with Y­ouT­ube, no­w.gg, Spoti­fy, Co­ol­Mat­hGam­es and various .i­o sites! +
CAP­TCHA is supp­ort­ed! +
Ultr­a­viol­et is highly rec­ommended due to its speed and enh­anced su­pport for almost ev­ery site ma­king it one of the m­ost advanc­ed web pr­o­xi­es. +
Sites with given support for logging in are Tw­itter, Spo­tify, Red­dit, and Disco­rd. YouTu­be allow­s for ne­arly f­ull fun­ctiona­lity. +
D­isco­rd support on Ram­merhe­ad is a KNO­WN bug. It is being worked on. +

+

+ Comm­on Errors with Soluti­ons: +
- Having issues with CAPTC­HAs? It will take trial and error but try to go slow when it comes to solving them. CAP­TCHA is a g­iven but will take a f­ew atte­mpts. +
- Try using Classic mode or doing 'Reload Frame'. ("cdn.example.com" cannot be reached.) +
- You may not be able to ­lo­gin normally into a number of sites. Phone verification on a select number of sites may occur also with no real sol­uion. +

+

GitHub: https://github.com/titaniumnetwork-dev

+
+
+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/views/pages/proxnav/womginx.html b/views/pages/proxnav/womginx.html index 83ab62de..3e09748c 100644 --- a/views/pages/proxnav/womginx.html +++ b/views/pages/proxnav/womginx.html @@ -27,7 +27,7 @@

Inc­red­i­bly fa­st p­r­oxy us­ing only N­GIN­X as the ba­cke­nd se­rv­er.

diff --git a/views/pages/surf.html b/views/pages/surf.html index 531a9ee2..304ad8cf 100644 --- a/views/pages/surf.html +++ b/views/pages/surf.html @@ -20,22 +20,35 @@
-
-
+
+ + +
+
+ + +

W­eb P­ro­xi­es

Choo­se which pr­ox­y you would like to use.

St­ea­lth mod­e h­ides y­our bro­ws­er histor­y whi­le Cla­ssic mo­de is th­e sta­nda­rd f­orm of na­viga­tion.

More Information:

+

U­ltr­a­vi­ole­t: T­he hi­ghly inno­vati­ve pr­ox­y of Tit­aniu­m Netw­ork usi­ng tec­hnologi­es such as servi­ce wor­kers and soph­isticated + rewr­iting techniq­ues wit­h CAPT­CHA suppor­t.

Ram­m­erhe­ad: Ram­merhe­ad is one of the mo­st adva­nced fr­ee se­cure w­eb prox­ies fe­aturi­ng ses­sio­ns and gre­at s­uppo­rt for a la­r­ge n­umb­er of s­it­es.

-

Cor­rosion: Of­fici­al pro­xy o­f Ti­tan­ium Ne­two­rk wi­th en­hanc­ed suppo­rt for a lar­ge major­ity o­f si­tes and Y­ouTub­e.

Wo­mgi­­nx: Useful for sites which use reC­APTC­HA or hCaptcha. Also faster.

If you get any errors please check the FAQ page!
Join the T­N disc­or­d for upd­ated H­ol­y Un­blo­cke­r sit­e li­nks and b­et­ter ser­vic­es.

diff --git a/views/uv.bundle.js b/views/uv.bundle.js new file mode 100644 index 00000000..e1b50164 --- /dev/null +++ b/views/uv.bundle.js @@ -0,0 +1,39204 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _events_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var parse5__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3); + + + +class HTML extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { + constructor(ctx) { + super(); + this.ctx = ctx; + this.rewriteUrl = ctx.rewriteUrl; + this.sourceUrl = ctx.sourceUrl; + }; + rewrite(str, options = {}) { + if (!str) return str; + return this.recast(str, node => { + if (node.tagName) this.emit('element', node, 'rewrite'); + if (node.attr) this.emit('attr', node, 'rewrite'); + if (node.nodeName === '#text') this.emit('text', node, 'rewrite'); + }, options) + }; + source(str, options = {}) { + if (!str) return str; + return this.recast(str, node => { + if (node.tagName) this.emit('element', node, 'source'); + if (node.attr) this.emit('attr', node, 'source'); + if (node.nodeName === '#text') this.emit('text', node, 'source'); + }, options) + }; + recast(str, fn, options = {}) { + try { + const ast = (options.document ? parse5__WEBPACK_IMPORTED_MODULE_1__.parse : parse5__WEBPACK_IMPORTED_MODULE_1__.parseFragment)(new String(str).toString()); + this.iterate(ast, fn, options); + return (0,parse5__WEBPACK_IMPORTED_MODULE_1__.serialize)(ast); + } catch(e) { + return str; + }; + }; + iterate(ast, fn, fnOptions) { + if (!ast) return ast; + + if (ast.tagName) { + const element = new P5Element(ast, false, fnOptions); + fn(element); + if (ast.attrs) { + for (const attr of ast.attrs) { + if (!attr.skip) fn(new AttributeEvent(element, attr, fnOptions)); + }; + }; + }; + + if (ast.childNodes) { + for (const child of ast.childNodes) { + if (!child.skip) this.iterate(child, fn, fnOptions); + }; + }; + + if (ast.nodeName === '#text') { + fn(new TextEvent(ast, new P5Element(ast.parentNode), false, fnOptions)); + }; + + return ast; + }; + wrapSrcset(str, meta = this.ctx.meta) { + return str.split(',').map(src => { + const parts = src.trimStart().split(' '); + if (parts[0]) parts[0] = this.ctx.rewriteUrl(parts[0], meta); + return parts.join(' '); + }).join(', '); + }; + unwrapSrcset(str, meta = this.ctx.meta) { + return str.split(',').map(src => { + const parts = src.trimStart().split(' '); + if (parts[0]) parts[0] = this.ctx.sourceUrl(parts[0], meta); + return parts.join(' '); + }).join(', '); + }; + static parse = parse5__WEBPACK_IMPORTED_MODULE_1__.parse; + static parseFragment = parse5__WEBPACK_IMPORTED_MODULE_1__.parseFragment; + static serialize = parse5__WEBPACK_IMPORTED_MODULE_1__.serialize; +}; + +class P5Element extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { + constructor(node, stream = false, options = {}) { + super(); + this.stream = stream; + this.node = node; + this.options = options; + }; + setAttribute(name, value) { + for (const attr of this.attrs) { + if (attr.name === name) { + attr.value = value; + return true; + }; + }; + + this.attrs.push( + { + name, + value, + } + ); + }; + getAttribute(name) { + const attr = this.attrs.find(attr => attr.name === name) || {}; + return attr.value; + }; + hasAttribute(name) { + return !!this.attrs.find(attr => attr.name === name); + }; + removeAttribute(name) { + const i = this.attrs.findIndex(attr => attr.name === name); + if (typeof i !== 'undefined') this.attrs.splice(i, 1); + }; + get tagName() { + return this.node.tagName; + }; + set tagName(val) { + this.node.tagName = val; + }; + get childNodes() { + return !this.stream ? this.node.childNodes : null; + }; + get innerHTML() { + return !this.stream ? (0,parse5__WEBPACK_IMPORTED_MODULE_1__.serialize)( + { + nodeName: '#document-fragment', + childNodes: this.childNodes, + } + ) : null; + }; + set innerHTML(val) { + if (!this.stream) this.node.childNodes = (0,parse5__WEBPACK_IMPORTED_MODULE_1__.parseFragment)(val).childNodes; + }; + get outerHTML() { + return !this.stream ? (0,parse5__WEBPACK_IMPORTED_MODULE_1__.serialize)( + { + nodeName: '#document-fragment', + childNodes: [ this ], + } + ) : null; + }; + set outerHTML(val) { + if (!this.stream) this.parentNode.childNodes.splice(this.parentNode.childNodes.findIndex(node => node === this.node), 1, ...(0,parse5__WEBPACK_IMPORTED_MODULE_1__.parseFragment)(val).childNodes); + }; + get textContent() { + if (this.stream) return null; + + let str = ''; + iterate(this.node, node => { + if (node.nodeName === '#text') str += node.value; + }); + + return str; + }; + set textContent(val) { + if (!this.stream) this.node.childNodes = [ + { + nodeName: '#text', + value: val, + parentNode: this.node + } + ]; + }; + get nodeName() { + return this.node.nodeName; + } + get parentNode() { + return this.node.parentNode ? new P5Element(this.node.parentNode) : null; + }; + get attrs() { + return this.node.attrs; + } + get namespaceURI() { + return this.node.namespaceURI; + } +}; + +class AttributeEvent { + constructor(node, attr, options = {}) { + this.attr = attr; + this.attrs = node.attrs; + this.node = node; + this.options = options; + }; + delete() { + const i = this.attrs.findIndex(attr => attr === this.attr); + + this.attrs.splice(i, 1); + + Object.defineProperty(this, 'deleted', { + get: () => true, + }); + + return true; + }; + get name() { + return this.attr.name; + }; + + set name(val) { + this.attr.name = val; + }; + get value() { + return this.attr.value; + }; + + set value(val) { + this.attr.value = val; + }; + get deleted() { + return false; + }; +}; + +class TextEvent { + constructor(node, element, stream = false, options = {}) { + this.stream = stream; + this.node = node; + this.element = element; + this.options = options; + }; + get nodeName() { + return this.node.nodeName; + } + get parentNode() { + return this.element; + }; + get value() { + return this.stream ? this.node.text : this.node.value; + }; + set value(val) { + + if (this.stream) this.node.text = val; + else this.node.value = val; + }; +}; + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (HTML); + +/***/ }), +/* 2 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (EventEmitter); + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +function checkListener(listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } +} + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + checkListener(listener); + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function once(emitter, name) { + return new Promise(function (resolve, reject) { + function errorListener(err) { + emitter.removeListener(name, resolver); + reject(err); + } + + function resolver() { + if (typeof emitter.removeListener === 'function') { + emitter.removeListener('error', errorListener); + } + resolve([].slice.call(arguments)); + }; + + eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); + if (name !== 'error') { + addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); + } + }); +} + +function addErrorHandlerIfEventEmitter(emitter, handler, flags) { + if (typeof emitter.on === 'function') { + eventTargetAgnosticAddListener(emitter, 'error', handler, flags); + } +} + +function eventTargetAgnosticAddListener(emitter, name, listener, flags) { + if (typeof emitter.on === 'function') { + if (flags.once) { + emitter.once(name, listener); + } else { + emitter.on(name, listener); + } + } else if (typeof emitter.addEventListener === 'function') { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we do not listen for `error` events here. + emitter.addEventListener(name, function wrapListener(arg) { + // IE does not have builtin `{ once: true }` support so we + // have to do it manually. + if (flags.once) { + emitter.removeEventListener(name, wrapListener); + } + listener(arg); + }); + } else { + throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); + } +} + +/***/ }), +/* 3 */ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + + +const Parser = __webpack_require__(4); +const Serializer = __webpack_require__(26); + +// Shorthands +exports.parse = function parse(html, options) { + const parser = new Parser(options); + + return parser.parse(html); +}; + +exports.parseFragment = function parseFragment(fragmentContext, html, options) { + if (typeof fragmentContext === 'string') { + options = html; + html = fragmentContext; + fragmentContext = null; + } + + const parser = new Parser(options); + + return parser.parseFragment(html, fragmentContext); +}; + +exports.serialize = function(node, options) { + const serializer = new Serializer(node, options); + + return serializer.serialize(); +}; + + +/***/ }), +/* 4 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; + + +const Tokenizer = __webpack_require__(5); +const OpenElementStack = __webpack_require__(10); +const FormattingElementList = __webpack_require__(12); +const LocationInfoParserMixin = __webpack_require__(13); +const ErrorReportingParserMixin = __webpack_require__(18); +const Mixin = __webpack_require__(14); +const defaultTreeAdapter = __webpack_require__(22); +const mergeOptions = __webpack_require__(23); +const doctype = __webpack_require__(24); +const foreignContent = __webpack_require__(25); +const ERR = __webpack_require__(8); +const unicode = __webpack_require__(7); +const HTML = __webpack_require__(11); + +//Aliases +const $ = HTML.TAG_NAMES; +const NS = HTML.NAMESPACES; +const ATTRS = HTML.ATTRS; + +const DEFAULT_OPTIONS = { + scriptingEnabled: true, + sourceCodeLocationInfo: false, + onParseError: null, + treeAdapter: defaultTreeAdapter +}; + +//Misc constants +const HIDDEN_INPUT_TYPE = 'hidden'; + +//Adoption agency loops iteration count +const AA_OUTER_LOOP_ITER = 8; +const AA_INNER_LOOP_ITER = 3; + +//Insertion modes +const INITIAL_MODE = 'INITIAL_MODE'; +const BEFORE_HTML_MODE = 'BEFORE_HTML_MODE'; +const BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE'; +const IN_HEAD_MODE = 'IN_HEAD_MODE'; +const IN_HEAD_NO_SCRIPT_MODE = 'IN_HEAD_NO_SCRIPT_MODE'; +const AFTER_HEAD_MODE = 'AFTER_HEAD_MODE'; +const IN_BODY_MODE = 'IN_BODY_MODE'; +const TEXT_MODE = 'TEXT_MODE'; +const IN_TABLE_MODE = 'IN_TABLE_MODE'; +const IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE'; +const IN_CAPTION_MODE = 'IN_CAPTION_MODE'; +const IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE'; +const IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE'; +const IN_ROW_MODE = 'IN_ROW_MODE'; +const IN_CELL_MODE = 'IN_CELL_MODE'; +const IN_SELECT_MODE = 'IN_SELECT_MODE'; +const IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE'; +const IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE'; +const AFTER_BODY_MODE = 'AFTER_BODY_MODE'; +const IN_FRAMESET_MODE = 'IN_FRAMESET_MODE'; +const AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE'; +const AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE'; +const AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE'; + +//Insertion mode reset map +const INSERTION_MODE_RESET_MAP = { + [$.TR]: IN_ROW_MODE, + [$.TBODY]: IN_TABLE_BODY_MODE, + [$.THEAD]: IN_TABLE_BODY_MODE, + [$.TFOOT]: IN_TABLE_BODY_MODE, + [$.CAPTION]: IN_CAPTION_MODE, + [$.COLGROUP]: IN_COLUMN_GROUP_MODE, + [$.TABLE]: IN_TABLE_MODE, + [$.BODY]: IN_BODY_MODE, + [$.FRAMESET]: IN_FRAMESET_MODE +}; + +//Template insertion mode switch map +const TEMPLATE_INSERTION_MODE_SWITCH_MAP = { + [$.CAPTION]: IN_TABLE_MODE, + [$.COLGROUP]: IN_TABLE_MODE, + [$.TBODY]: IN_TABLE_MODE, + [$.TFOOT]: IN_TABLE_MODE, + [$.THEAD]: IN_TABLE_MODE, + [$.COL]: IN_COLUMN_GROUP_MODE, + [$.TR]: IN_TABLE_BODY_MODE, + [$.TD]: IN_ROW_MODE, + [$.TH]: IN_ROW_MODE +}; + +//Token handlers map for insertion modes +const TOKEN_HANDLERS = { + [INITIAL_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInInitialMode, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInInitialMode, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: doctypeInInitialMode, + [Tokenizer.START_TAG_TOKEN]: tokenInInitialMode, + [Tokenizer.END_TAG_TOKEN]: tokenInInitialMode, + [Tokenizer.EOF_TOKEN]: tokenInInitialMode + }, + [BEFORE_HTML_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHtml, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHtml, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagBeforeHtml, + [Tokenizer.END_TAG_TOKEN]: endTagBeforeHtml, + [Tokenizer.EOF_TOKEN]: tokenBeforeHtml + }, + [BEFORE_HEAD_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHead, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHead, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagBeforeHead, + [Tokenizer.END_TAG_TOKEN]: endTagBeforeHead, + [Tokenizer.EOF_TOKEN]: tokenBeforeHead + }, + [IN_HEAD_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInHead, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHead, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagInHead, + [Tokenizer.END_TAG_TOKEN]: endTagInHead, + [Tokenizer.EOF_TOKEN]: tokenInHead + }, + [IN_HEAD_NO_SCRIPT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInHeadNoScript, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHeadNoScript, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagInHeadNoScript, + [Tokenizer.END_TAG_TOKEN]: endTagInHeadNoScript, + [Tokenizer.EOF_TOKEN]: tokenInHeadNoScript + }, + [AFTER_HEAD_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenAfterHead, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterHead, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagAfterHead, + [Tokenizer.END_TAG_TOKEN]: endTagAfterHead, + [Tokenizer.EOF_TOKEN]: tokenAfterHead + }, + [IN_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInBody, + [Tokenizer.END_TAG_TOKEN]: endTagInBody, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [TEXT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.NULL_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: ignoreToken, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: ignoreToken, + [Tokenizer.END_TAG_TOKEN]: endTagInText, + [Tokenizer.EOF_TOKEN]: eofInText + }, + [IN_TABLE_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTable, + [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInTable, + [Tokenizer.END_TAG_TOKEN]: endTagInTable, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_TABLE_TEXT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTableText, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInTableText, + [Tokenizer.COMMENT_TOKEN]: tokenInTableText, + [Tokenizer.DOCTYPE_TOKEN]: tokenInTableText, + [Tokenizer.START_TAG_TOKEN]: tokenInTableText, + [Tokenizer.END_TAG_TOKEN]: tokenInTableText, + [Tokenizer.EOF_TOKEN]: tokenInTableText + }, + [IN_CAPTION_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInCaption, + [Tokenizer.END_TAG_TOKEN]: endTagInCaption, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_COLUMN_GROUP_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInColumnGroup, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInColumnGroup, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInColumnGroup, + [Tokenizer.END_TAG_TOKEN]: endTagInColumnGroup, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_TABLE_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTable, + [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInTableBody, + [Tokenizer.END_TAG_TOKEN]: endTagInTableBody, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_ROW_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTable, + [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInRow, + [Tokenizer.END_TAG_TOKEN]: endTagInRow, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_CELL_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInCell, + [Tokenizer.END_TAG_TOKEN]: endTagInCell, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_SELECT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInSelect, + [Tokenizer.END_TAG_TOKEN]: endTagInSelect, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_SELECT_IN_TABLE_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInSelectInTable, + [Tokenizer.END_TAG_TOKEN]: endTagInSelectInTable, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_TEMPLATE_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInTemplate, + [Tokenizer.END_TAG_TOKEN]: endTagInTemplate, + [Tokenizer.EOF_TOKEN]: eofInTemplate + }, + [AFTER_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenAfterBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterBody, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendCommentToRootHtmlElement, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterBody, + [Tokenizer.END_TAG_TOKEN]: endTagAfterBody, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [IN_FRAMESET_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInFrameset, + [Tokenizer.END_TAG_TOKEN]: endTagInFrameset, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [AFTER_FRAMESET_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterFrameset, + [Tokenizer.END_TAG_TOKEN]: endTagAfterFrameset, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [AFTER_AFTER_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenAfterAfterBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterAfterBody, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterBody, + [Tokenizer.END_TAG_TOKEN]: tokenAfterAfterBody, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [AFTER_AFTER_FRAMESET_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterFrameset, + [Tokenizer.END_TAG_TOKEN]: ignoreToken, + [Tokenizer.EOF_TOKEN]: stopParsing + } +}; + +//Parser +class Parser { + constructor(options) { + this.options = mergeOptions(DEFAULT_OPTIONS, options); + + this.treeAdapter = this.options.treeAdapter; + this.pendingScript = null; + + if (this.options.sourceCodeLocationInfo) { + Mixin.install(this, LocationInfoParserMixin); + } + + if (this.options.onParseError) { + Mixin.install(this, ErrorReportingParserMixin, { onParseError: this.options.onParseError }); + } + } + + // API + parse(html) { + const document = this.treeAdapter.createDocument(); + + this._bootstrap(document, null); + this.tokenizer.write(html, true); + this._runParsingLoop(null); + + return document; + } + + parseFragment(html, fragmentContext) { + //NOTE: use