feat: add multi-engine regex flavour support

This commit is contained in:
2026-07-27 11:43:51 +02:00
parent 7079cde15f
commit 7f3a91ad37
340 changed files with 643286 additions and 483 deletions

View File

@@ -0,0 +1,207 @@
This software is Copyright (c) 2000 - 2017 by David Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The Artistic License 2.0
Copyright (c) 2000-2006, The Perl Foundation.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
This license establishes the terms under which a given free software
Package may be copied, modified, distributed, and/or redistributed.
The intent is that the Copyright Holder maintains some artistic
control over the development of that Package while still keeping the
Package available as open source and free software.
You are always permitted to make arrangements wholly outside of this
license directly with the Copyright Holder of a given Package. If the
terms of this license do not permit the full use that you propose to
make of the Package, you should contact the Copyright Holder and seek
a different licensing arrangement.
Definitions
"Copyright Holder" means the individual(s) or organization(s)
named in the copyright notice for the entire Package.
"Contributor" means any party that has contributed code or other
material to the Package, in accordance with the Copyright Holder's
procedures.
"You" and "your" means any person who would like to copy,
distribute, or modify the Package.
"Package" means the collection of files distributed by the
Copyright Holder, and derivatives of that collection and/or of
those files. A given Package may consist of either the Standard
Version, or a Modified Version.
"Distribute" means providing a copy of the Package or making it
accessible to anyone else, or in the case of a company or
organization, to others outside of your company or organization.
"Distributor Fee" means any fee that you charge for Distributing
this Package or providing support for this Package to another
party. It does not mean licensing fees.
"Standard Version" refers to the Package if it has not been
modified, or has been modified only in ways explicitly requested
by the Copyright Holder.
"Modified Version" means the Package, if it has been changed, and
such changes were not explicitly requested by the Copyright
Holder.
"Original License" means this Artistic License as Distributed with
the Standard Version of the Package, in its current version or as
it may be modified by The Perl Foundation in the future.
"Source" form means the source code, documentation source, and
configuration files for the Package.
"Compiled" form means the compiled bytecode, object code, binary,
or any other form resulting from mechanical transformation or
translation of the Source form.
Permission for Use and Modification Without Distribution
(1) You are permitted to use the Standard Version and create and use
Modified Versions for any purpose without restriction, provided that
you do not Distribute the Modified Version.
Permissions for Redistribution of the Standard Version
(2) You may Distribute verbatim copies of the Source form of the
Standard Version of this Package in any medium without restriction,
either gratis or for a Distributor Fee, provided that you duplicate
all of the original copyright notices and associated disclaimers. At
your discretion, such verbatim copies may or may not include a
Compiled form of the Package.
(3) You may apply any bug fixes, portability changes, and other
modifications made available from the Copyright Holder. The resulting
Package will still be considered the Standard Version, and as such
will be subject to the Original License.
Distribution of Modified Versions of the Package as Source
(4) You may Distribute your Modified Version as Source (either gratis
or for a Distributor Fee, and with or without a Compiled form of the
Modified Version) provided that you clearly document how it differs
from the Standard Version, including, but not limited to, documenting
any non-standard features, executables, or modules, and provided that
you do at least ONE of the following:
(a) make the Modified Version available to the Copyright Holder
of the Standard Version, under the Original License, so that the
Copyright Holder may include your modifications in the Standard
Version.
(b) ensure that installation of your Modified Version does not
prevent the user installing or running the Standard Version. In
addition, the Modified Version must bear a name that is different
from the name of the Standard Version.
(c) allow anyone who receives a copy of the Modified Version to
make the Source form of the Modified Version available to others
under
(i) the Original License or
(ii) a license that permits the licensee to freely copy,
modify and redistribute the Modified Version using the same
licensing terms that apply to the copy that the licensee
received, and requires that the Source form of the Modified
Version, and of any works derived from it, be made freely
available in that license fees are prohibited but Distributor
Fees are allowed.
Distribution of Compiled Forms of the Standard Version
or Modified Versions without the Source
(5) You may Distribute Compiled forms of the Standard Version without
the Source, provided that you include complete instructions on how to
get the Source of the Standard Version. Such instructions must be
valid at the time of your distribution. If these instructions, at any
time while you are carrying out such distribution, become invalid, you
must provide new instructions on demand or cease further distribution.
If you provide valid instructions or cease distribution within thirty
days after you become aware that the instructions are invalid, then
you do not forfeit any of your rights under this license.
(6) You may Distribute a Modified Version in Compiled form without
the Source, provided that you comply with Section 4 with respect to
the Source of the Modified Version.
Aggregating or Linking the Package
(7) You may aggregate the Package (either the Standard Version or
Modified Version) with other packages and Distribute the resulting
aggregation provided that you do not charge a licensing fee for the
Package. Distributor Fees are permitted, and licensing fees for other
components in the aggregation are permitted. The terms of this license
apply to the use and Distribution of the Standard or Modified Versions
as included in the aggregation.
(8) You are permitted to link Modified and Standard Versions with
other works, to embed the Package in a larger work of your own, or to
build stand-alone binary or bytecode versions of applications that
include the Package, and Distribute the result without restriction,
provided the result does not expose a direct interface to the Package.
Items That are Not Considered Part of a Modified Version
(9) Works (including, but not limited to, modules and scripts) that
merely extend or make use of the Package, do not, by themselves, cause
the Package to be a Modified Version. In addition, such works are not
considered parts of the Package itself, and are not subject to the
terms of this license.
General Provisions
(10) Any use, modification, and distribution of the Standard or
Modified Versions is governed by this Artistic License. By using,
modifying or distributing the Package, you accept this license. Do not
use, modify, or distribute the Package, if you do not accept this
license.
(11) If your Modified Version has been derived from a Modified
Version made by someone other than you, you are nevertheless required
to ensure that your Modified Version complies with the requirements of
this license.
(12) This license does not grant you the right to use any trademark,
service mark, tradename, or logo of the Copyright Holder.
(13) This license includes the non-exclusive, worldwide,
free-of-charge patent license to make, have made, use, offer to sell,
sell, import and otherwise transfer the Package with respect to any
patent claims licensable by the Copyright Holder that are necessarily
infringed by the Package. If you institute patent litigation
(including a cross-claim or counterclaim) against any party alleging
that the Package constitutes direct or contributory patent
infringement, then this Artistic License to you shall terminate on the
date that such litigation is filed.
(14) Disclaimer of Warranty:
THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,101 @@
Emscripten is available under 2 licenses, the MIT license and the
University of Illinois/NCSA Open Source License.
Both are permissive open source licenses, with little if any
practical difference between them.
The reason for offering both is that (1) the MIT license is
well-known, while (2) the University of Illinois/NCSA Open Source
License allows Emscripten's code to be integrated upstream into
LLVM, which uses that license, should the opportunity arise.
The full text of both licenses follows.
==============================================================================
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
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.
==============================================================================
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal with 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:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimers.
Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimers
in the documentation and/or other materials provided with the
distribution.
Neither the names of Mozilla,
nor the names of its contributors may be used to endorse
or promote products derived from this Software without specific prior
written permission.
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 CONTRIBUTORS 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 WITH THE SOFTWARE.
==============================================================================
This program uses portions of Node.js source code located in src/library_path.js,
in accordance with the terms of the MIT license. Node's license follows:
"""
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
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.
"""
The musl libc project is bundled in this repo, and it has the MIT license, see
system/lib/libc/musl/COPYRIGHT
The third_party/ subdirectory contains code with other licenses. None of it is
used by default, but certain options use it (e.g., the optional closure compiler
flag will run closure compiler from third_party/).

View File

@@ -0,0 +1,158 @@
NAME
perlartistic - the Perl Artistic License
SYNOPSIS
You can refer to this document in Pod via "L<perlartistic>"
Or you can see this document by entering "perldoc perlartistic"
DESCRIPTION
Perl is free software; you can redistribute it and/or modify it under
the terms of either:
a) the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or
b) the "Artistic License" which comes with this Kit.
This is "The Artistic License". It's here so that modules, programs,
etc., that want to declare this as their distribution license can link
to it.
For the GNU General Public License, see perlgpl.
The "Artistic License"
Preamble
The intent of this document is to state the conditions under which a
Package may be copied, such that the Copyright Holder maintains some
semblance of artistic control over the development of the package, while
giving the users of the package the right to use and distribute the
Package in a more-or-less customary fashion, plus the right to make
reasonable modifications.
Definitions
"Package"
refers to the collection of files distributed by the Copyright
Holder, and derivatives of that collection of files created through
textual modification.
"Standard Version"
refers to such a Package if it has not been modified, or has been
modified in accordance with the wishes of the Copyright Holder as
specified below.
"Copyright Holder"
is whoever is named in the copyright or copyrights for the package.
"You"
is you, if you're thinking about copying or distributing this
Package.
"Reasonable copying fee"
is whatever you can justify on the basis of media cost, duplication
charges, time of people involved, and so on. (You will not be
required to justify it to the Copyright Holder, but only to the
computing community at large as a market that must bear the fee.)
"Freely Available"
means that no fee is charged for the item itself, though there may
be fees involved in handling the item. It also means that recipients
of the item may redistribute it under the same conditions they
received it.
Conditions
1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that
you duplicate all of the original copyright notices and associated
disclaimers.
2. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A
Package modified in such a way shall still be considered the
Standard Version.
3. You may otherwise modify your copy of this Package in any way,
provided that you insert a prominent notice in each changed file
stating how and when you changed that file, and provided that you do
at least ONE of the following:
a) place your modifications in the Public Domain or otherwise make
them Freely Available, such as by posting said modifications to
Usenet or an equivalent medium, or placing the modifications on
a major archive site such as uunet.uu.net, or by allowing the
Copyright Holder to include your modifications in the Standard
Version of the Package.
b) use the modified Package only within your corporation or
organization.
c) rename any non-standard executables so the names do not conflict
with standard executables, which must also be provided, and
provide a separate manual page for each non-standard executable
that clearly documents how it differs from the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
4. You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following:
a) distribute a Standard Version of the executables and library
files, together with instructions (in the manual page or
equivalent) on where to get the Standard Version.
b) accompany the distribution with the machine-readable source of
the Package with your modifications.
c) give non-standard executables non-standard names, and clearly
document the differences in manual pages (or equivalent),
together with instructions on where to get the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
5. You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this
Package. You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial)
software distribution provided that you do not advertise this
Package as a product of your own. You may embed this Package's
interpreter within an executable of yours (by linking); this shall
be construed as a mere form of aggregation, provided that the
complete Standard Version of the interpreter is so embedded.
6. The scripts and library files supplied as input to or produced as
output from the programs of this Package do not automatically fall
under the copyright of this Package, but belong to whoever generated
them, and may be sold commercially, and may be aggregated with this
Package. If such scripts or library files are aggregated with this
Package via the so-called "undump" or "unexec" methods of producing
a binary executable image, then distribution of such an image shall
neither be construed as a distribution of this Package nor shall it
fall under the restrictions of Paragraphs 3 and 4, provided that you
do not represent such an executable image as a Standard Version of
this Package.
7. C subroutines (or comparably compiled subroutines in other
languages) supplied by you and linked into this Package in order to
emulate subroutines and variables of the language defined by this
Package shall not be considered part of this Package, but are the
equivalent of input as in Paragraph 6, provided these subroutines do
not change the language in any way that would cause it to fail the
regression tests for the language.
8. Aggregation of this Package with a commercial distribution is always
permitted provided that the use of this Package is embedded; that
is, when no overt attempt is made to make this Package's interfaces
visible to the end user of the commercial distribution. Such use
shall not be construed as a distribution of this Package.
9. The name of the Copyright Holder may not be used to endorse or
promote products derived from this software without specific prior
written permission.
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End

View File

@@ -0,0 +1,281 @@
NAME
perlgpl - the GNU General Public License, version 1
SYNOPSIS
You can refer to this document in Pod via "L<perlgpl>"
Or you can see this document by entering "perldoc perlgpl"
DESCRIPTION
Perl is free software; you can redistribute it and/or modify it under
the terms of either:
a) the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or
b) the "Artistic License" which comes with this Kit.
This is the "GNU General Public License, version 1". It's here so that
modules, programs, etc., that want to declare this as their distribution
license can link to it.
For the Perl Artistic License, see perlartistic.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 1, February 1989
Copyright (C) 1989 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The license agreements of most software companies try to keep users
at the mercy of those companies. By contrast, our General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. The
General Public License applies to the Free Software Foundation's
software and to any other program whose authors commit to using it.
You can use it for your programs, too.
When we speak of free software, we are referring to freedom, not
price. Specifically, the General Public License is designed to make
sure that you have the freedom to give away or sell copies of free
software, 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 make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of a such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must tell them their rights.
We protect your rights with two steps: (1) copyright the software,
and (2) offer you this license which gives you legal permission to
copy, distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on,
we want its recipients to know that what they have is not the original,
so that any problems introduced by others will not reflect on the
original authors' reputations.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any program or other work which
contains a notice placed by the copyright holder saying it may be
distributed under the terms of this General Public License. The
"Program", below, refers to any such program or work, and a "work based
on the Program" means either the Program or any work containing the
Program or a portion of it, either verbatim or with modifications.
Each licensee is addressed as "you".
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
notices that refer to this General Public License and to the absence of
any warranty; and give any other recipients of the Program a copy of
this General Public License along with the Program. You may charge a
fee for the physical act of transferring a copy.
2. You may modify your copy or copies of the Program or any portion
of it, and copy and distribute such modifications under the terms of
Paragraph 1 above, provided that you also do the following:
a) cause the modified files to carry prominent notices stating that
you changed the files and the date of any change; and
b) cause the whole of any work that you distribute or publish, that
in whole or in part contains the Program or any part thereof,
either with or without modifications, to be licensed at no charge
to all third parties under the terms of this General Public License
(except that you may choose to grant warranty protection to some or
all third parties, at your option).
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the simplest and most usual way, to print or
display an announcement including an appropriate copyright notice
and a notice that there is no warranty (or else, saying that you
provide a warranty) and that users may redistribute the program
under these conditions, and telling the user how to view a copy of
this General Public License.
d) You may charge a fee for the physical act of transferring a
copy, and you may at your option offer warranty protection in
exchange for a fee.
Mere aggregation of another independent work with the Program (or its
derivative) on a volume of a storage or distribution medium does not
bring the other work under the scope of these terms.
3. You may copy and distribute the Program (or a portion or
derivative of it, under Paragraph 2) in object code or executable form
under the terms of Paragraphs 1 and 2 above provided that you also do
one of the following:
a) accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of
Paragraphs 1 and 2 above; or,
b) accompany it with a written offer, valid for at least three
years, to give any third party free (except for a nominal charge
for the cost of distribution) a complete machine-readable copy of
the corresponding source code, to be distributed under the terms of
Paragraphs 1 and 2 above; or,
c) accompany it with the information you received as to where the
corresponding source code may be obtained. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form alone.)
Source code for a work means the preferred form of the work for making
modifications to it. For an executable file, complete source code
means all the source code for all modules it contains; but, as a
special exception, it need not include source code for modules which
are standard libraries that accompany the operating system on which the
executable file runs, or for standard header files or definitions files
that accompany that operating system.
4. You may not copy, modify, sublicense, distribute or transfer the
Program except as expressly provided under this General Public License.
Any attempt otherwise to copy, modify, sublicense, distribute or
transfer the Program is void, and will automatically terminate your
rights to use the Program under this License. However, parties who
have received copies, or rights to use copies, from you under this
General Public License will not have their licenses terminated so long
as such parties remain in full compliance.
5. By copying, distributing or modifying the Program (or any work
based on the Program) you indicate your acceptance of this license to
do so, and all its terms and conditions.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
7. The Free Software Foundation may publish revised and/or new
versions of the 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 a version number of the license which applies to it and "any
later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Program does not specify a
version number of the license, you may choose any version ever
published by the Free Software Foundation.
8. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the
author to ask for permission. For software which is copyrighted by the
Free Software Foundation, write to the Free Software Foundation; we
sometimes make exceptions for this. Our decision will be guided by the
two goals of preserving the free status of all derivatives of our free
software and of promoting the sharing and reuse of software generally.
NO WARRANTY
9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE 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.
END OF TERMS AND CONDITIONS
Appendix: 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 humanity, 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
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it
does.>
Copyright (C) 19yy <name of author>
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 1, 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA
02110-1301 USA
Also add information on how to contact you by electronic and paper
mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19xx name of author
Gnomovision 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, the
commands you use may be called something other than 'show w' and 'show
c'; they could even be mouse-clicks or menu items--whatever suits your
program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
program 'Gnomovision' (a program to direct compilers to make passes
at assemblers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -0,0 +1,68 @@
Legacy WebPerl runtime notices
==============================
This directory contains the unmodified WebPerl v0.09-beta prebuilt runtime,
released in 2019 and explicitly treated by regex-tools as legacy beta software.
WebPerl
-------
Project: https://github.com/haukex/webperl
Release: v0.09-beta
Commit: 6f2173d29a2c2e3536e1de75ff5d291ae96ab348
Copyright (c) 2018 Hauke Daempfling, Leibniz Institute of Freshwater Ecology
and Inland Fisheries (IGB), Berlin, Germany.
WebPerl is distributed under the same terms as Perl 5: either the GNU General
Public License version 1 or later, or the Artistic License supplied with Perl 5.
The complete upstream texts are included as LICENSE-WebPerl-GPL.txt and
LICENSE-WebPerl-Artistic.txt.
Perl
----
The binary and data pack contain Perl v5.28.1 and its core library. Perl 5 is
distributed under the same GNU GPL / Artistic dual-license terms described
above. Exact upstream source is available from:
https://github.com/Perl/perl5/tree/v5.28.1
Emscripten
----------
The generated loader and WebAssembly runtime were built by upstream WebPerl
with Emscripten 1.38.28. Emscripten is dual-licensed under the MIT and
University of Illinois/NCSA licenses. The upstream 1.38.28 license text,
including its bundled-code notices, is included as LICENSE-Emscripten.txt.
Bundled CPAN additions
----------------------
The upstream prebuilt data pack also contains the modules selected by
WebPerl's pinned build:
- Cpanel::JSON::XS 4.09 — same terms as Perl 5;
- Devel::StackTrace 2.03 — Artistic License 2.0; its upstream distribution
license is included as LICENSE-Devel-StackTrace-Artistic-2.0.txt; and
- Future 0.39 — same terms as Perl 5.
The Devel::StackTrace license was reproduced byte-for-byte from
https://www.cpan.org/authors/id/D/DR/DROLSKY/Devel-StackTrace-2.03.tar.gz
(SHA-256 7618cd4ebe24e254c17085f4b418784ab503cb4cb3baf8f48a7be894e59ba848).
regex-tools uses Cpanel::JSON::XS only as the fixed bridge's bounded JSON
codec. It does not load Devel::StackTrace, Future, or WebPerl's JavaScript
interop module on the normal engine path. Those files remain present because
emperl.data is the unmodified, checksummed upstream prebuilt pack.
Security boundary
-----------------
The upstream generated emperl.js has one JavaScript eval(code) call in the
optional WebPerl JavaScript-interoperability import. regex-tools neither loads
nor calls that Perl module. User values cross the fixed bridge only as
exact-key, byte-bounded JSON in MEMFS; the sole Perl evaluator invocation is
the constant RegexTools::run_request(). The bounded metadata response is JSON,
while successful replacement UTF-8 bytes use a separate fixed MEMFS binary
file with exact worker-side length and encoding verification. Perl's
runtime-regex eval mode and regex code assertions are disabled.

View File

@@ -0,0 +1,11 @@
e16dd93533bb65e25fad00d06e88840d9b5fd6bb80d551d8866126c52d89e1de LICENSE-Devel-StackTrace-Artistic-2.0.txt
51aea7641f81d560eb039bc97ce35e2517e2656fe8731eb49ce6a18498eb22fe LICENSE-Emscripten.txt
435a788b2de9aaae9c640e7c0ab7b317b41323522e8641bcda000607892abf1f LICENSE-WebPerl-Artistic.txt
0166b8b8e96a6b153da26ece157ebbd9ffad5423670f611fae80fefacb6da7f5 LICENSE-WebPerl-GPL.txt
499559af54694d319697271433262a3324a976061f4537ed1c5210235f9eb348 NOTICE.txt
9529019418cf766a42cf2d25bd3fc97b47c9e689f5666cfc32dc11338d1b1e66 emperl.data
b60e3c04874c6ef5278001257b4c8a9f4c7e69ca3d6b268d9639723234844784 emperl.js
f1d49c4514c7332a57992c4a2444fd6a56ae3b5e6651b4fd484852a641e5e4ec emperl.wasm
c2967eeec6b3eadcd0be3df064797a5d8ce790e43542e9417092af196128dfa7 engine-metadata.json
0c42a7b2a72b886d967aadff0b1bb35017c247e5cad79ad85544fc3a91f17e5b perl-runtime.worker.js
880835d63b2319f72ae4be1db50e2cecda8ebd98b80dca810448c3a665dcc4d3 regex_tools_bridge.pl

567811
public/engines/perl/emperl.data Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,175 @@
{
"schemaVersion": 1,
"engine": "perl",
"engineName": "Perl regular expressions via WebPerl",
"engineVersion": "5.28.1",
"semanticIdentity": "Perl v5.28.1 regular expressions via the legacy WebPerl v0.09-beta prebuilt runtime",
"status": "legacy-beta-compatibility-runtime",
"bridge": {
"abiVersion": 1,
"nativeOffsetUnit": "Unicode code points (Perl character offsets)",
"requestEncoding": "exact-key and byte-bounded JSON in a fixed MEMFS request file; no request value is interpolated into source",
"responseEncoding": "byte-bounded metadata JSON in a fixed MEMFS response file",
"workerTopology": "supervised ES module worker with a nested self-hosted classic WebPerl worker",
"evaluatorInvocation": "RegexTools::run_request()",
"userSourceEvaluation": false,
"regexCodeAssertionsEnabled": false,
"supportedApplicationFlags": [
"g",
"i",
"m",
"s",
"x",
"n",
"a",
"d",
"l",
"u"
],
"replacement": "Pre-tokenized fixed grammar supporting literal text, $$, greedy $n, and ${name}; bounded UTF-8 chunks stream from native spans without a complete capture-value array, expansion, or output materialization",
"replacementParityOracle": "identity self-test compares fixed named and numbered fixtures with native Perl s/// output",
"replacementOutputTransport": "successful UTF-8 output uses a separate fixed MEMFS binary file; the classic worker validates exact length, bound, canonical UTF-8 and injects the decoded string for PortableEngineAdapter",
"captureNames": "Perl-native numbered participation and spans; syntax metadata annotates capture-row names; named replacement lookup uses native %+ and resolves the selected value back to a native span before output",
"limits": {
"maximumPatternCharacters": 65536,
"maximumSubjectBytes": 16777216,
"maximumReplacementCharacters": 65536,
"maximumMatches": 10000,
"maximumCaptureRows": 100000,
"maximumCaptureGroups": 1000,
"maximumOutputBytes": 67108864,
"maximumErrorCharacters": 1024,
"maximumRequestJsonBytes": 101466112,
"maximumResponseJsonBytes": 8388608,
"outputChunkCharacters": 4096
},
"sourceFiles": [
{
"path": "engines/perl/README.md",
"sha256": "bac7fcce7a1e4451ab7f1c7489211114aa52b896aecf45f3ac991b294c736952",
"bytes": 3311
},
{
"path": "engines/perl/perl-runtime.worker.js",
"sha256": "0c42a7b2a72b886d967aadff0b1bb35017c247e5cad79ad85544fc3a91f17e5b",
"bytes": 10926
},
{
"path": "engines/perl/regex_tools_bridge.pl",
"sha256": "880835d63b2319f72ae4be1db50e2cecda8ebd98b80dca810448c3a665dcc4d3",
"bytes": 23415
}
]
},
"securityAudit": {
"generatedLoaderJavaScriptEvalSites": 1,
"generatedLoaderNewFunctionSites": 0,
"evalSitePurpose": "optional WebPerl Perl-to-JavaScript interoperability import; not loaded or invoked by regex-tools",
"normalPathCsp": "script-src 'self' 'wasm-unsafe-eval'; no 'unsafe-eval'",
"runtimeRegexEval": "disabled with no re 'eval'",
"codeAssertionDefense": "fixed scanner rejects (?{...}) and (??{...}) before compilation",
"killability": "the supervised worker tree can be terminated by EngineSupervisor",
"boundedTransport": "request and response JSON have exact field/byte contracts; replacement output is not JSON-escaped"
},
"source": {
"project": "WebPerl",
"repository": "https://github.com/haukex/webperl",
"tag": "v0.09-beta",
"tagObjectSha1": "c5933d11666906328b238d03459707207b6ca2b4",
"commitSha1": "6f2173d29a2c2e3536e1de75ff5d291ae96ab348",
"treeSha1": "20e59a142ad2e355fc3ed03da974bdf183b79c66",
"license": "GPL-1.0-or-later OR Artistic-1.0-Perl",
"perl": {
"version": "5.28.1",
"repository": "https://github.com/Perl/perl5",
"tag": "v5.28.1",
"license": "GPL-1.0-or-later OR Artistic-1.0-Perl"
}
},
"prebuilt": {
"archive": "https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip",
"archiveSha256": "5f441249217e90ab378c666f473d4206ab4f44907f6bb0aa8d70834bc38c40dc",
"archiveBytes": 3936557,
"runtimeAssetsCopiedByteForByte": true
},
"toolchain": {
"emscriptenVersion": "1.38.28",
"target": "wasm32-unknown-emscripten",
"license": "MIT OR NCSA"
},
"bundledModules": [
{
"name": "Cpanel::JSON::XS",
"version": "4.09",
"license": "GPL-1.0-or-later OR Artistic-1.0-Perl",
"normalEnginePath": true
},
{
"name": "Devel::StackTrace",
"version": "2.03",
"license": "Artistic-2.0",
"normalEnginePath": false,
"sourceArchive": "https://www.cpan.org/authors/id/D/DR/DROLSKY/Devel-StackTrace-2.03.tar.gz",
"sourceArchiveSha256": "7618cd4ebe24e254c17085f4b418784ab503cb4cb3baf8f48a7be894e59ba848"
},
{
"name": "Future",
"version": "0.39",
"license": "GPL-1.0-or-later OR Artistic-1.0-Perl",
"normalEnginePath": false
}
],
"sourceDateEpoch": 1551644196,
"files": [
{
"path": "LICENSE-Devel-StackTrace-Artistic-2.0.txt",
"sha256": "e16dd93533bb65e25fad00d06e88840d9b5fd6bb80d551d8866126c52d89e1de",
"bytes": 9048
},
{
"path": "LICENSE-Emscripten.txt",
"sha256": "51aea7641f81d560eb039bc97ce35e2517e2656fe8731eb49ce6a18498eb22fe",
"bytes": 5092
},
{
"path": "LICENSE-WebPerl-Artistic.txt",
"sha256": "435a788b2de9aaae9c640e7c0ab7b317b41323522e8641bcda000607892abf1f",
"bytes": 7587
},
{
"path": "LICENSE-WebPerl-GPL.txt",
"sha256": "0166b8b8e96a6b153da26ece157ebbd9ffad5423670f611fae80fefacb6da7f5",
"bytes": 14468
},
{
"path": "NOTICE.txt",
"sha256": "499559af54694d319697271433262a3324a976061f4537ed1c5210235f9eb348",
"bytes": 2827
},
{
"path": "emperl.data",
"sha256": "9529019418cf766a42cf2d25bd3fc97b47c9e689f5666cfc32dc11338d1b1e66",
"bytes": 12021691
},
{
"path": "emperl.js",
"sha256": "b60e3c04874c6ef5278001257b4c8a9f4c7e69ca3d6b268d9639723234844784",
"bytes": 303013
},
{
"path": "emperl.wasm",
"sha256": "f1d49c4514c7332a57992c4a2444fd6a56ae3b5e6651b4fd484852a641e5e4ec",
"bytes": 3734063
},
{
"path": "perl-runtime.worker.js",
"sha256": "0c42a7b2a72b886d967aadff0b1bb35017c247e5cad79ad85544fc3a91f17e5b",
"bytes": 10926
},
{
"path": "regex_tools_bridge.pl",
"sha256": "880835d63b2319f72ae4be1db50e2cecda8ebd98b80dca810448c3a665dcc4d3",
"bytes": 23415
}
]
}

View File

@@ -0,0 +1,367 @@
"use strict";
/*
* Classic-worker wrapper for the pinned WebPerl 0.09-beta Emscripten runtime.
* User-controlled values cross into Perl only through a JSON file in MEMFS.
* Successful replacement bytes return through a separate fixed binary file;
* the bounded JSON response contains metadata only.
* The sole call through WebPerl's eval export is the fixed source string
* "RegexTools::run_request()"; no request data is interpolated into code.
*/
var BRIDGE_PROTOCOL_VERSION = 1;
var REQUEST_PATH = "/tmp/regex-tools-request.json";
var RESPONSE_PATH = "/tmp/regex-tools-response.json";
var OUTPUT_PATH = "/tmp/regex-tools-output.bin";
var HARNESS_PATH = "/tmp/regex-tools-bridge.pl";
var FIXED_INVOCATION = "RegexTools::run_request()";
var MAXIMUM_PATTERN_CHARACTERS = 64 * 1024;
var MAXIMUM_SUBJECT_BYTES = 16 * 1024 * 1024;
var MAXIMUM_REPLACEMENT_CHARACTERS = 64 * 1024;
var MAXIMUM_MATCHES = 10000;
var MAXIMUM_CAPTURE_ROWS = 100000;
var MAXIMUM_OUTPUT_BYTES = 64 * 1024 * 1024;
var MAXIMUM_REQUEST_JSON_BYTES =
6 *
(MAXIMUM_SUBJECT_BYTES +
MAXIMUM_PATTERN_CHARACTERS +
MAXIMUM_REPLACEMENT_CHARACTERS) +
16 * 1024;
var MAXIMUM_RESPONSE_JSON_BYTES = 8 * 1024 * 1024;
var utf8Encoder = new TextEncoder();
var runtimeRoot = new URL("./", self.location.href);
var runtimeResolve;
var runtimeReject;
var runtimeReady = new Promise(function (resolve, reject) {
runtimeResolve = resolve;
runtimeReject = reject;
});
var stderrTail = "";
function boundedRuntimeError(value) {
var message = value instanceof Error ? value.message : String(value);
if (stderrTail) {
message += " (" + stderrTail.slice(-512) + ")";
}
return message.slice(0, 1024);
}
function captureStderr(value) {
stderrTail = (stderrTail + String(value) + "\n").slice(-4096);
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function owns(value, key) {
return Object.prototype.hasOwnProperty.call(value, key);
}
function isIntegerInRange(value, minimum, maximum) {
return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
}
function hasExactKeys(value, expected) {
var actual = Object.keys(value).sort();
var wanted = expected.slice().sort();
if (actual.length !== wanted.length) return false;
for (var index = 0; index < actual.length; index += 1) {
if (actual[index] !== wanted[index]) return false;
}
return true;
}
function encodeRequest(request) {
if (
!isRecord(request) ||
request.abi !== 1 ||
typeof request.operation !== "string"
) {
throw new Error("The WebPerl bridge request is invalid.");
}
if (request.operation === "identity") {
if (!hasExactKeys(request, ["abi", "operation"])) {
throw new Error("The WebPerl identity request is invalid.");
}
} else {
var replacement = request.operation === "replace";
if (
(!replacement && request.operation !== "execute") ||
!hasExactKeys(
request,
[
"abi",
"operation",
"pattern",
"flags",
"options",
"subject",
"scanAll",
"maximumMatches",
"maximumCaptureRows",
].concat(replacement ? ["replacement", "maximumOutputBytes"] : []),
) ||
typeof request.pattern !== "string" ||
request.pattern.length > MAXIMUM_PATTERN_CHARACTERS ||
typeof request.flags !== "string" ||
request.flags.length > 10 ||
typeof request.subject !== "string" ||
typeof request.scanAll !== "boolean" ||
!isRecord(request.options) ||
Object.keys(request.options).length !== 0 ||
!isIntegerInRange(request.maximumMatches, 1, MAXIMUM_MATCHES) ||
!isIntegerInRange(request.maximumCaptureRows, 1, MAXIMUM_CAPTURE_ROWS)
) {
throw new Error("The WebPerl execution request is invalid.");
}
if (
utf8Encoder.encode(request.subject).byteLength > MAXIMUM_SUBJECT_BYTES
) {
throw new Error("The WebPerl subject exceeds its UTF-8 byte limit.");
}
if (
replacement &&
(typeof request.replacement !== "string" ||
request.replacement.length > MAXIMUM_REPLACEMENT_CHARACTERS ||
!isIntegerInRange(request.maximumOutputBytes, 1, MAXIMUM_OUTPUT_BYTES))
) {
throw new Error("The WebPerl replacement request is invalid.");
}
}
var encoded = utf8Encoder.encode(JSON.stringify(request));
if (encoded.byteLength > MAXIMUM_REQUEST_JSON_BYTES) {
throw new Error("The WebPerl request exceeds its JSON byte limit.");
}
return encoded;
}
function decodeUtf8Exact(encoded, label) {
var decoded;
try {
decoded = new TextDecoder("utf-8", { fatal: true }).decode(encoded);
} catch (_error) {
throw new Error(label + " is not valid UTF-8.");
}
var roundTrip = utf8Encoder.encode(decoded);
if (roundTrip.byteLength !== encoded.byteLength) {
throw new Error(label + " has an inconsistent UTF-8 length.");
}
for (var index = 0; index < encoded.byteLength; index += 1) {
if (roundTrip[index] !== encoded[index]) {
throw new Error(label + " is not canonical UTF-8.");
}
}
return decoded;
}
function readResponse() {
var encoded = FS.readFile(RESPONSE_PATH);
if (
Object.prototype.toString.call(encoded) !== "[object Uint8Array]" ||
encoded.byteLength === 0 ||
encoded.byteLength > MAXIMUM_RESPONSE_JSON_BYTES
) {
throw new Error("The fixed Perl bridge response exceeds its byte limit.");
}
var parsed;
try {
parsed = JSON.parse(decodeUtf8Exact(encoded, "The WebPerl response"));
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error("The fixed Perl bridge returned malformed JSON.");
}
throw error;
}
if (!isRecord(parsed)) {
throw new Error("The fixed Perl bridge returned a non-object response.");
}
return parsed;
}
function injectReplacementOutput(response, maximumOutputBytes) {
if (
owns(response, "output") ||
!isIntegerInRange(response.outputBytes, 0, maximumOutputBytes) ||
typeof response.outputTruncated !== "boolean"
) {
throw new Error("The fixed Perl bridge returned invalid output metadata.");
}
var encoded = FS.readFile(OUTPUT_PATH);
if (
Object.prototype.toString.call(encoded) !== "[object Uint8Array]" ||
encoded.byteLength !== response.outputBytes ||
encoded.byteLength > maximumOutputBytes
) {
throw new Error(
"The fixed Perl bridge output file exceeds its bounded contract.",
);
}
response.output = decodeUtf8Exact(encoded, "The WebPerl output file");
}
function removeFixedFile(path) {
try {
FS.unlink(path);
} catch (_error) {
// The fixed file does not exist before its first use.
}
}
var Perl = {
trace: false,
glue: function () {
throw new Error("The WebPerl JavaScript bridge is disabled.");
},
unglue: function () {
throw new Error("The WebPerl JavaScript bridge is disabled.");
},
};
var Module = {
noInitialRun: true,
noExitRuntime: true,
arguments: ["--version"],
print: function () {},
printErr: captureStderr,
stdout: function () {},
stderr: function (character) {
if (character) {
stderrTail = (stderrTail + String.fromCharCode(character)).slice(-4096);
}
},
stdin: function () {
return null;
},
locateFile: function (file) {
return new URL(file, runtimeRoot).href;
},
preRun: [],
onRuntimeInitialized: function () {
runtimeResolve();
},
onAbort: function (reason) {
runtimeReject(
new Error("The legacy WebPerl runtime aborted: " + String(reason)),
);
},
};
importScripts(new URL("emperl.js", runtimeRoot).href);
var harnessPromise;
async function loadHarness() {
if (harnessPromise) return harnessPromise;
harnessPromise = (async function () {
await runtimeReady;
var response = await fetch(new URL("regex_tools_bridge.pl", runtimeRoot));
if (!response.ok) {
throw new Error(
"The fixed Perl bridge request failed with HTTP " +
response.status +
".",
);
}
var source = await response.text();
FS.writeFile(HARNESS_PATH, source, { encoding: "utf8" });
Module.callMain([HARNESS_PATH]);
return true;
})();
return harnessPromise;
}
async function invokeHarness(request) {
stderrTail = "";
await loadHarness();
if (stderrTail) {
throw new Error("The fixed Perl bridge failed to load: " + stderrTail);
}
removeFixedFile(REQUEST_PATH);
removeFixedFile(RESPONSE_PATH);
removeFixedFile(OUTPUT_PATH);
try {
FS.writeFile(REQUEST_PATH, encodeRequest(request));
var status = Module.ccall(
"webperl_eval_perl",
"string",
["string"],
[FIXED_INVOCATION],
);
if (status !== "ok") {
throw new Error("The fixed Perl bridge returned an invalid status.");
}
var parsed = readResponse();
if (request.operation === "replace" && parsed.ok === true) {
injectReplacementOutput(parsed, request.maximumOutputBytes);
} else if (
owns(parsed, "output") ||
owns(parsed, "outputBytes") ||
owns(parsed, "outputTruncated")
) {
throw new Error(
"The fixed Perl bridge returned output for a non-replacement result.",
);
}
return parsed;
} finally {
removeFixedFile(REQUEST_PATH);
removeFixedFile(RESPONSE_PATH);
removeFixedFile(OUTPUT_PATH);
}
}
async function handleMessage(message) {
if (
!message ||
message.protocolVersion !== BRIDGE_PROTOCOL_VERSION ||
!Number.isSafeInteger(message.requestId)
) {
throw new Error("The WebPerl bridge received an invalid message.");
}
if (message.kind === "load") {
return invokeHarness({ abi: 1, operation: "identity" });
}
if (message.kind === "run") {
if (
!message.request ||
typeof message.request !== "object" ||
Array.isArray(message.request)
) {
throw new Error("The WebPerl bridge received an invalid request.");
}
return invokeHarness(message.request);
}
throw new Error("The WebPerl bridge operation is unsupported.");
}
var operationQueue = Promise.resolve();
self.onmessage = function (event) {
var message = event.data;
var current = operationQueue.then(function () {
return handleMessage(message);
});
operationQueue = current.catch(function () {});
current.then(
function (payload) {
self.postMessage({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
requestId: message && message.requestId,
ok: true,
payload: payload,
});
},
function (error) {
self.postMessage({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
requestId: message && message.requestId,
ok: false,
error: {
name: error instanceof Error ? error.name : "Error",
message: boundedRuntimeError(error),
},
});
},
);
};

View File

@@ -0,0 +1,753 @@
package RegexTools;
use strict;
use warnings;
use utf8;
use Cpanel::JSON::XS ();
no re 'eval';
our $BRIDGE_ABI_VERSION = 1;
our $REQUEST_PATH = '/tmp/regex-tools-request.json';
our $RESPONSE_PATH = '/tmp/regex-tools-response.json';
our $OUTPUT_PATH = '/tmp/regex-tools-output.bin';
my $JSON = Cpanel::JSON::XS->new->utf8->canonical;
my $TRUE = Cpanel::JSON::XS::true;
my $FALSE = Cpanel::JSON::XS::false;
my $MAXIMUM_PATTERN_CHARACTERS = 64 * 1024;
my $MAXIMUM_SUBJECT_BYTES = 16 * 1024 * 1024;
my $MAXIMUM_REPLACEMENT_CHARACTERS = 64 * 1024;
my $MAXIMUM_MATCHES = 10_000;
my $MAXIMUM_CAPTURE_ROWS = 100_000;
my $MAXIMUM_CAPTURE_GROUPS = 1_000;
my $MAXIMUM_OUTPUT_BYTES = 64 * 1024 * 1024;
my $MAXIMUM_ERROR_CHARACTERS = 1_024;
my $OUTPUT_CHUNK_CHARACTERS = 4_096;
my $MAXIMUM_REQUEST_BYTES = 6 * (
$MAXIMUM_SUBJECT_BYTES
+ $MAXIMUM_PATTERN_CHARACTERS
+ $MAXIMUM_REPLACEMENT_CHARACTERS
) + 16 * 1024;
my $MAXIMUM_RESPONSE_BYTES = 8 * 1024 * 1024;
sub _bounded_message {
my ($value) = @_;
$value = defined($value) ? "$value" : 'Unknown Perl bridge error';
$value =~ s/\s+at \Q@{[$0]}\E line \d+\.?\s*\z//;
return length($value) <= $MAXIMUM_ERROR_CHARACTERS
? $value
: substr($value, 0, $MAXIMUM_ERROR_CHARACTERS - 1) . "\x{2026}";
}
sub _utf8_bytes {
my ($value) = @_;
my $encoded = $value;
utf8::encode($encoded);
return $encoded;
}
sub _failure {
my ($phase, $code, $message) = @_;
return {
abi => $BRIDGE_ABI_VERSION,
ok => $FALSE,
phase => $phase,
code => $code,
message => _bounded_message($message),
};
}
sub _is_plain_hash {
my ($value) = @_;
return ref($value) eq 'HASH';
}
sub _has_exact_keys {
my ($value, @expected) = @_;
return 0 if !_is_plain_hash($value);
return join("\0", sort keys %$value)
eq join("\0", sort @expected);
}
sub _is_integer_in_range {
my ($value, $minimum, $maximum) = @_;
return defined($value)
&& !ref($value)
&& "$value" =~ /\A(?:0|[1-9][0-9]*)\z/
&& $value >= $minimum
&& $value <= $maximum;
}
sub _read_request {
open my $input, '<:raw', $REQUEST_PATH
or die "Unable to open the fixed bridge request file: $!";
local $/;
my $encoded = <$input>;
close $input or die "Unable to close the fixed bridge request file: $!";
die 'The fixed bridge request exceeds its byte limit.'
if !defined($encoded) || length($encoded) > $MAXIMUM_REQUEST_BYTES;
return $JSON->decode($encoded);
}
sub _write_response {
my ($response) = @_;
my $encoded = $JSON->encode($response);
die 'The fixed bridge response exceeds its byte limit.'
if length($encoded) > $MAXIMUM_RESPONSE_BYTES;
open my $output, '>:raw', $RESPONSE_PATH
or die "Unable to open the fixed bridge response file: $!";
print {$output} $encoded
or die "Unable to write the fixed bridge response file: $!";
close $output
or die "Unable to close the fixed bridge response file: $!";
}
sub _contains_code_assertion {
my ($pattern) = @_;
my $escaped = 0;
my $character_class = 0;
my $quoted = 0;
for (my $index = 0; $index < length($pattern); $index++) {
my $current = substr($pattern, $index, 1);
if ($quoted) {
if ($current eq '\\'
&& substr($pattern, $index + 1, 1) eq 'E') {
$quoted = 0;
$index++;
}
next;
}
if ($escaped) {
$escaped = 0;
next;
}
if ($current eq '\\') {
if (substr($pattern, $index + 1, 1) eq 'Q') {
$quoted = 1;
$index++;
}
else {
$escaped = 1;
}
next;
}
if ($current eq '[' && !$character_class) {
$character_class = 1;
next;
}
if ($current eq ']' && $character_class) {
$character_class = 0;
next;
}
next if $character_class;
return 1 if substr($pattern, $index, 3) eq '(?{';
return 1 if substr($pattern, $index, 4) eq '(??{';
}
return 0;
}
sub _validate_flags {
my ($flags) = @_;
return 'Flags must be a string.' if !defined($flags) || ref($flags);
my %seen;
for my $flag (split //, $flags) {
return 'The request contains an unsupported or duplicate Perl flag.'
if $flag !~ /\A[gimsxnadlu]\z/ || $seen{$flag}++;
}
my $character_rules = grep { $seen{$_} } qw(a d l u);
return 'Perl character-set flags a, d, l and u are mutually exclusive.'
if $character_rules > 1;
return;
}
sub _validate_request {
my ($request) = @_;
return _failure('bridge', 'invalid-request', 'The request must be a JSON object.')
if !_is_plain_hash($request);
return _failure('bridge', 'abi-mismatch', 'The bridge ABI version is unsupported.')
if !defined($request->{abi})
|| ref($request->{abi})
|| $request->{abi} != $BRIDGE_ABI_VERSION;
if (defined($request->{operation})
&& !ref($request->{operation})
&& $request->{operation} eq 'identity') {
return _failure(
'bridge',
'invalid-request',
'The identity request contains missing or unexpected fields.',
) if !_has_exact_keys($request, qw(abi operation));
return;
}
return _failure('bridge', 'invalid-operation', 'The bridge operation is unsupported.')
if !defined($request->{operation})
|| ref($request->{operation})
|| ($request->{operation} ne 'execute'
&& $request->{operation} ne 'replace');
my @request_fields = qw(
abi
flags
maximumCaptureRows
maximumMatches
operation
options
pattern
scanAll
subject
);
push @request_fields, qw(maximumOutputBytes replacement)
if $request->{operation} eq 'replace';
return _failure(
'bridge',
'invalid-request',
'The request contains missing or unexpected fields.',
) if !_has_exact_keys($request, @request_fields);
return _failure('bridge', 'invalid-request', 'Pattern and subject must be strings.')
if !defined($request->{pattern})
|| ref($request->{pattern})
|| !defined($request->{subject})
|| ref($request->{subject});
return _failure('bridge', 'pattern-limit', 'The pattern exceeds the bridge limit.')
if length($request->{pattern}) > $MAXIMUM_PATTERN_CHARACTERS;
return _failure('bridge', 'subject-limit', 'The subject exceeds the bridge limit.')
if length(_utf8_bytes($request->{subject})) > $MAXIMUM_SUBJECT_BYTES;
return _failure('bridge', 'unsupported-option', 'Perl does not accept engine options.')
if !_is_plain_hash($request->{options})
|| keys(%{$request->{options}});
my $flag_error = _validate_flags($request->{flags});
return _failure('bridge', 'unsupported-flag', $flag_error) if $flag_error;
return _failure(
'bridge',
'result-limit',
'The requested result bounds are outside the bridge contract.',
) if !_is_integer_in_range(
$request->{maximumMatches},
1,
$MAXIMUM_MATCHES,
) || !_is_integer_in_range(
$request->{maximumCaptureRows},
1,
$MAXIMUM_CAPTURE_ROWS,
);
return _failure(
'compile',
'code-assertion-disabled',
'Perl code assertions (?{...}) and (??{...}) are disabled.',
) if _contains_code_assertion($request->{pattern});
if ($request->{operation} eq 'execute') {
return _failure(
'bridge',
'invalid-request',
'Execution requests cannot contain replacement fields.',
) if exists($request->{replacement})
|| exists($request->{maximumOutputBytes});
return;
}
return _failure(
'bridge',
'invalid-request',
'Replacement requests require a string template and output bound.',
) if !defined($request->{replacement})
|| ref($request->{replacement})
|| !_is_integer_in_range(
$request->{maximumOutputBytes},
1,
$MAXIMUM_OUTPUT_BYTES,
);
return _failure(
'bridge',
'replacement-limit',
'The replacement template exceeds the bridge limit.',
) if length($request->{replacement}) > $MAXIMUM_REPLACEMENT_CHARACTERS;
return;
}
sub _compile_pattern {
my ($pattern, $flags) = @_;
my $modifiers = join '', grep { index($flags, $_) >= 0 }
qw(i m s x n a d l u);
my $wrapped = length($modifiers)
? "(?$modifiers:$pattern)"
: "(?:$pattern)";
my $compiled;
my $error;
{
local $SIG{__WARN__} = sub { };
local $@;
$compiled = eval { qr/$wrapped/ };
$error = $@;
}
return ($compiled, $error);
}
sub _capture_group_count {
my ($compiled) = @_;
my $probe = qr/(?:$compiled)|()/;
'' =~ $probe;
return $#- - 1;
}
sub _snapshot_match {
my ($group_count, $starts, $ends) = @_;
my @captures;
for my $group (1 .. $group_count) {
push @captures, defined($starts->[$group])
&& $starts->[$group] >= 0
? [0 + $starts->[$group], 0 + $ends->[$group]]
: undef;
}
return {
span => [0 + $starts->[0], 0 + $ends->[0]],
captures => \@captures,
};
}
sub _push_literal_token {
my ($tokens, $literal) = @_;
return if !length($$literal);
push @$tokens, ['literal', $$literal];
$$literal = '';
}
sub _tokenize_replacement {
my ($template, $group_count) = @_;
my @tokens;
my $literal = '';
my $cursor = 0;
while ($cursor < length($template)) {
my $current = substr($template, $cursor, 1);
if ($current ne '$') {
$literal .= $current;
$cursor++;
next;
}
my $next = substr($template, $cursor + 1, 1);
if ($next eq '$') {
_push_literal_token(\@tokens, \$literal);
push @tokens, ['literal', '$'];
$cursor += 2;
next;
}
if (substr($template, $cursor + 1) =~ /\A([0-9]+)/) {
my $digits = $1;
my $canonical = $digits;
$canonical =~ s/\A0+(?=[0-9])//;
my $number = length($canonical) <= length("$group_count")
&& $canonical <= $group_count
? 0 + $canonical
: undef;
_push_literal_token(\@tokens, \$literal);
push @tokens, ['capture', $number];
$cursor += 1 + length($digits);
next;
}
if (substr($template, $cursor + 1)
=~ /\A\{([A-Za-z_][A-Za-z0-9_]*)\}/) {
my $name = $1;
_push_literal_token(\@tokens, \$literal);
push @tokens, ['named', $name];
$cursor += 3 + length($name);
next;
}
$literal .= '$';
$cursor++;
}
_push_literal_token(\@tokens, \$literal);
return \@tokens;
}
sub _write_encoded {
my ($state, $encoded) = @_;
return if !length($encoded);
print {$state->{handle}} $encoded
or die "Unable to write the fixed bridge output file: $!";
$state->{bytes} += length($encoded);
}
sub _append_piece {
my ($state, $value) = @_;
return 0 if $state->{truncated};
return 1 if !length($value);
my $encoded = _utf8_bytes($value);
my $remaining = $state->{maximum} - $state->{bytes};
if (length($encoded) <= $remaining) {
_write_encoded($state, $encoded);
return 1;
}
my ($low, $high) = (0, length($value));
while ($low < $high) {
my $middle = int(($low + $high + 1) / 2);
my $prefix_bytes = length(_utf8_bytes(substr($value, 0, $middle)));
if ($prefix_bytes <= $remaining) {
$low = $middle;
}
else {
$high = $middle - 1;
}
}
_write_encoded($state, _utf8_bytes(substr($value, 0, $low)))
if $low > 0;
$state->{truncated} = $TRUE;
return 0;
}
sub _append_text {
my ($state, $value) = @_;
return 0 if $state->{truncated};
my $cursor = 0;
while ($cursor < length($value)) {
my $length = length($value) - $cursor;
$length = $OUTPUT_CHUNK_CHARACTERS
if $length > $OUTPUT_CHUNK_CHARACTERS;
return 0
if !_append_piece($state, substr($value, $cursor, $length));
$cursor += $length;
}
return 1;
}
sub _append_span {
my ($state, $subject, $start, $end) = @_;
return 0 if $state->{truncated};
my $cursor = $start;
while ($cursor < $end) {
my $length = $end - $cursor;
$length = $OUTPUT_CHUNK_CHARACTERS
if $length > $OUTPUT_CHUNK_CHARACTERS;
return 0
if !_append_piece($state, substr($subject, $cursor, $length));
$cursor += $length;
}
return 1;
}
sub _resolve_named_spans {
my ($tokens, $subject, $starts, $ends, $named_values) = @_;
my %resolved;
for my $token (@$tokens) {
my ($kind, $name) = @$token;
next if $kind ne 'named'
|| exists($resolved{$name})
|| !exists($named_values->{$name})
|| !defined($named_values->{$name});
my $captured = $named_values->{$name};
for my $group (1 .. $#$ends) {
next if !defined($starts->[$group])
|| $starts->[$group] < 0
|| $ends->[$group] - $starts->[$group] != length($captured)
|| index($subject, $captured, $starts->[$group])
!= $starts->[$group];
$resolved{$name} = [
0 + $starts->[$group],
0 + $ends->[$group],
];
last;
}
die "Unable to resolve native span for named capture $name."
if !exists($resolved{$name});
}
return \%resolved;
}
sub _append_replacement_tokens {
my ($state, $tokens, $subject, $starts, $ends, $named_spans) = @_;
for my $token (@$tokens) {
return 0 if $state->{truncated};
my ($kind, $value) = @$token;
if ($kind eq 'literal') {
_append_text($state, $value);
}
elsif ($kind eq 'capture') {
_append_span(
$state,
$subject,
$starts->[$value],
$ends->[$value],
) if defined($value)
&& defined($starts->[$value])
&& $starts->[$value] >= 0;
}
elsif ($kind eq 'named') {
_append_span(
$state,
$subject,
$named_spans->{$value}->[0],
$named_spans->{$value}->[1],
) if exists($named_spans->{$value});
}
else {
die 'The fixed replacement tokenizer produced an invalid token.';
}
}
return !$state->{truncated};
}
sub _open_output {
my ($maximum, $path) = @_;
open my $handle, '>:raw', $path
or die "Unable to open the fixed bridge output file: $!";
return {
handle => $handle,
bytes => 0,
maximum => 0 + $maximum,
truncated => $FALSE,
};
}
sub _close_output {
my ($state) = @_;
close $state->{handle}
or die "Unable to close the fixed bridge output file: $!";
delete $state->{handle};
}
sub _run_regex {
my ($request) = @_;
my ($compiled, $compile_error) = _compile_pattern(
$request->{pattern},
$request->{flags},
);
return _failure('compile', 'compile-error', $compile_error)
if !defined($compiled);
my $group_count = _capture_group_count($compiled);
return _failure(
'compile',
'capture-group-limit',
"The pattern declares $group_count capture groups; "
. "the bridge limit is $MAXIMUM_CAPTURE_GROUPS.",
) if $group_count > $MAXIMUM_CAPTURE_GROUPS;
my $rows_per_match = $group_count > 0 ? $group_count : 1;
my $allowed_matches = $request->{maximumMatches};
my $row_bound = int($request->{maximumCaptureRows} / $rows_per_match);
$allowed_matches = $row_bound if $row_bound < $allowed_matches;
my $subject = $request->{subject};
my $global = index($request->{flags}, 'g') >= 0;
my $replacement_tokens = $request->{operation} eq 'replace'
? _tokenize_replacement($request->{replacement}, $group_count)
: undef;
my @matches;
my $results_truncated = $FALSE;
my $replacement_state = defined($replacement_tokens)
? _open_output($request->{maximumOutputBytes}, $OUTPUT_PATH)
: undef;
$replacement_state->{cursor} = 0 if defined($replacement_state);
pos($subject) = 0;
while ($subject =~ /$compiled/g) {
my @starts = @-;
my @ends = @+;
if (@matches >= $allowed_matches) {
$results_truncated = $TRUE;
last;
}
push @matches, _snapshot_match(
$group_count,
\@starts,
\@ends,
);
if (defined($replacement_state)) {
_append_span(
$replacement_state,
$subject,
$replacement_state->{cursor},
$starts[0],
);
if (!$replacement_state->{truncated}) {
my $named_spans = _resolve_named_spans(
$replacement_tokens,
$subject,
\@starts,
\@ends,
\%+,
);
_append_replacement_tokens(
$replacement_state,
$replacement_tokens,
$subject,
\@starts,
\@ends,
$named_spans,
);
}
$replacement_state->{cursor} = $ends[0];
if ($replacement_state->{truncated}) {
$results_truncated = $TRUE;
last;
}
}
last if !$global;
}
my $response = {
abi => $BRIDGE_ABI_VERSION,
ok => $TRUE,
groupCount => 0 + $group_count,
groupNames => [],
matches => \@matches,
resultsTruncated => $results_truncated,
};
if (defined($replacement_state)) {
_append_span(
$replacement_state,
$subject,
$replacement_state->{cursor},
length($subject),
);
_close_output($replacement_state);
$response->{outputBytes} = 0 + $replacement_state->{bytes};
$response->{outputTruncated} = $replacement_state->{truncated};
}
return $response;
}
sub _render_self_test_replacement {
my ($compiled, $subject, $template, $maximum) = @_;
my $group_count = _capture_group_count($compiled);
my $tokens = _tokenize_replacement($template, $group_count);
return if $subject !~ /$compiled/;
my @starts = @-;
my @ends = @+;
my $named_spans = _resolve_named_spans(
$tokens,
$subject,
\@starts,
\@ends,
\%+,
);
my $encoded = '';
my $state = _open_output($maximum, \$encoded);
_append_span($state, $subject, 0, $starts[0]);
_append_replacement_tokens(
$state,
$tokens,
$subject,
\@starts,
\@ends,
$named_spans,
);
_append_span($state, $subject, $ends[0], length($subject));
_close_output($state);
my $decoded = $encoded;
return if !utf8::decode($decoded);
return ($decoded, 0 + $state->{bytes}, $state->{truncated});
}
sub _self_test {
return $FALSE if "$^V" ne 'v5.28.1';
my ($compiled, $error) = _compile_pattern(
'(?<word>\x{1F600}+)',
'u',
);
return $FALSE if $error || !defined($compiled);
my $subject = "x\x{1F600}\x{1F600}";
return $FALSE if $subject !~ /$compiled/;
return $FALSE if $-[0] != 1 || $+[0] != 3;
return $FALSE if $+{word} ne "\x{1F600}\x{1F600}";
my ($named, $named_bytes, $named_truncated) =
_render_self_test_replacement(
$compiled,
$subject,
'<${word}>',
1_024,
);
return $FALSE if !defined($named)
|| $named ne "x<\x{1F600}\x{1F600}>"
|| $named_bytes != 11
|| $named_truncated;
my $native_named = $subject;
$native_named =~ s/$compiled/<$+{word}>/;
return $FALSE if $named ne $native_named;
my $numbered_compiled = qr/(a)(b)/;
my ($numbered, $numbered_bytes, $numbered_truncated) =
_render_self_test_replacement(
$numbered_compiled,
'ab',
'$1-$$',
1_024,
);
my $native_numbered = 'ab';
$native_numbered =~ s/$numbered_compiled/$1-\$/;
return $FALSE if !defined($numbered)
|| $numbered ne $native_numbered
|| $numbered_bytes != 3
|| $numbered_truncated;
my ($greedy, $greedy_bytes, $greedy_truncated) =
_render_self_test_replacement(
qr/(a)(b)/,
'ab',
'$12/$1/$$/${missing}/$',
1_024,
);
return $FALSE if !defined($greedy)
|| $greedy ne '/a/$//$'
|| $greedy_bytes != 7
|| $greedy_truncated;
my ($bounded, $bounded_bytes, $bounded_truncated) =
_render_self_test_replacement(
$compiled,
$subject,
'<${word}>',
5,
);
return $FALSE if !defined($bounded)
|| $bounded ne 'x<'
|| $bounded_bytes != 2
|| !$bounded_truncated;
return $TRUE;
}
sub _identity {
return {
abi => $BRIDGE_ABI_VERSION,
ok => $TRUE,
identity => {
bridge => 'regex-tools-webperl-json',
bridgeVersion => $BRIDGE_ABI_VERSION,
engine => 'Perl regular expressions',
engineVersion => '5.28.1',
perlVersion => "$^V",
webPerlVersion => '0.09-beta',
offsetUnit => 'code-point',
replacementGrammar => 'literal + $$ + $n + ${name}',
replacementTransport => 'fixed MEMFS binary output file',
replacementParityOracle => 'fixed native s/// fixtures',
legacy => $TRUE,
beta => $TRUE,
selfTest => _self_test(),
},
};
}
sub run_request {
my $response;
my $ok = eval {
my $request = _read_request();
my $validation_failure = _validate_request($request);
$response = $validation_failure
|| ($request->{operation} eq 'identity'
? _identity()
: _run_regex($request));
1;
};
if (!$ok) {
$response = _failure(
'bridge',
'bridge-error',
$@ || 'Unknown fixed Perl bridge failure',
);
}
eval { _write_response($response); 1 } or do {
my $fallback = _failure(
'bridge',
'serialization-error',
$@ || 'The fixed Perl bridge could not serialize its response.',
);
_write_response($fallback);
};
return 'ok';
}
1;