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 00000000..9ee3ec59 Binary files /dev/null and b/views/assets/img/uv.png differ diff --git a/views/assets/img/uv2.png b/views/assets/img/uv2.png new file mode 100644 index 00000000..89a939da Binary files /dev/null and b/views/assets/img/uv2.png differ diff --git a/views/assets/js/common-1644738239.js b/views/assets/js/common-16451543478.js similarity index 68% rename from views/assets/js/common-1644738239.js rename to views/assets/js/common-16451543478.js index 24745fac..13d9355b 100644 --- a/views/assets/js/common-1644738239.js +++ b/views/assets/js/common-16451543478.js @@ -30,7 +30,30 @@ function setAuthCookie(s, lax) { document.cookie = s + "; expires=" + (Date.now() + 259200) + "; SameSite=" + (lax ? "Lax" : "None") + "; domain=." + getDomain() + "; path=/; Secure;"; } -let sxlink = 'https://searx.degenerate.info'; +const sx = 'bing.com' + '/search?q='; + +function omnibox(url) { + if (url.substring(0, 4) == "http") { + return url; + } else if (url.includes("." || "")) { + return "https://" + url; + } else { + return "https://" + sx + url; + } +} + +const xor = { + encode(str) { + if (!str) return str; + return encodeURIComponent(str.toString().split('').map((char, ind) => 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