Compare commits
51 commits
22a120cf0f
...
5241f63256
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5241f63256 | ||
|
|
1800a5e33c | ||
|
|
3310e5d9b2 | ||
|
|
1b1ea68da6 | ||
|
|
f70c1e0c11 | ||
|
|
c7ce5a9a30 | ||
|
|
4214309ee0 | ||
|
|
1511c27140 | ||
|
|
8cf450908f | ||
|
|
cb5a2d64f8 | ||
|
|
07fe4f32b3 | ||
|
|
f517c5bd14 | ||
|
|
d757aaf46d | ||
|
|
1f454956c2 | ||
|
|
af345005df | ||
|
|
8a927fdade | ||
|
|
84bb626e32 | ||
|
|
97d2a377e7 | ||
|
|
51d3c53ed1 | ||
|
|
d3422f2b2b | ||
|
|
df3d378acd | ||
|
|
630e367ca8 | ||
|
|
44586aa92f | ||
|
|
2bba9840b7 | ||
|
|
4969901747 | ||
|
|
a0cdf8f5b1 | ||
|
|
18f1a3350f | ||
|
|
cecaf2c9db | ||
|
|
59dd2c52e9 | ||
|
|
d89db88c58 | ||
|
|
3c3921d61e | ||
|
|
7c5d8a79fb | ||
|
|
3b590f016f | ||
|
|
07f8bd2b25 | ||
|
|
e1885d82ef | ||
|
|
8dfca77ae8 | ||
|
|
b2d231b587 | ||
|
|
6009989f10 | ||
|
|
ff618e27a1 | ||
|
|
ae4a32d426 | ||
|
|
7f188f1c4c | ||
|
|
4151287b0a | ||
|
|
950884bf06 | ||
|
|
7ac9e57893 | ||
|
|
022cece987 | ||
|
|
983d016c7b | ||
|
|
169ceee992 | ||
|
|
79193e2773 | ||
|
|
55d2268150 | ||
|
|
3f822176dd | ||
|
|
cc8c694545 |
10
Makefile
|
|
@ -41,8 +41,6 @@ fetch:
|
||||||
aria2c -x16 -s16 -k1M -o $(ff_source_tarball) "https://archive.mozilla.org/pub/firefox/releases/$(version)/source/firefox-$(version).source.tar.xz"; \
|
aria2c -x16 -s16 -k1M -o $(ff_source_tarball) "https://archive.mozilla.org/pub/firefox/releases/$(version)/source/firefox-$(version).source.tar.xz"; \
|
||||||
|
|
||||||
setup-minimal:
|
setup-minimal:
|
||||||
# Note: Only docker containers are intended to run this directly.
|
|
||||||
# Run this before `make dir` or `make build` to avoid setting up a local git repo.
|
|
||||||
if [ ! -f $(ff_source_tarball) ]; then \
|
if [ ! -f $(ff_source_tarball) ]; then \
|
||||||
make fetch; \
|
make fetch; \
|
||||||
fi
|
fi
|
||||||
|
|
@ -86,9 +84,7 @@ set-target:
|
||||||
mozbootstrap:
|
mozbootstrap:
|
||||||
cd $(of_source_dir) && MOZBUILD_STATE_PATH=$$HOME/.mozbuild ./mach --no-interactive bootstrap --application-choice=browser
|
cd $(of_source_dir) && MOZBUILD_STATE_PATH=$$HOME/.mozbuild ./mach --no-interactive bootstrap --application-choice=browser
|
||||||
|
|
||||||
bootstrap: dir
|
bootstrap: mozbootstrap
|
||||||
(sudo apt-get -y install $(debs) || sudo dnf -y install $(rpms) || sudo pacman -Sy $(pacman))
|
|
||||||
make mozbootstrap
|
|
||||||
|
|
||||||
diff:
|
diff:
|
||||||
@cd $(of_source_dir) && git diff $(_ARGS)
|
@cd $(of_source_dir) && git diff $(_ARGS)
|
||||||
|
|
@ -118,13 +114,9 @@ check-arch:
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build-launcher: check-arch
|
|
||||||
cd legacy/launcher && bash build.sh $(arch) $(os)
|
|
||||||
|
|
||||||
package-linux:
|
package-linux:
|
||||||
python3 scripts/package.py linux \
|
python3 scripts/package.py linux \
|
||||||
--includes \
|
--includes \
|
||||||
settings/omegacfg.jvv \
|
|
||||||
settings/properties.json \
|
settings/properties.json \
|
||||||
bundle/fontconfigs \
|
bundle/fontconfigs \
|
||||||
--version $(version) \
|
--version $(version) \
|
||||||
|
|
|
||||||
14
README.md
|
|
@ -13,14 +13,15 @@
|
||||||
- Removed cursor highlighter
|
- Removed cursor highlighter
|
||||||
- Re-enabled stock Firefox features that do not make outside connections
|
- Re-enabled stock Firefox features that do not make outside connections
|
||||||
- Search engine usage allowed
|
- Search engine usage allowed
|
||||||
- etc.
|
- [etc.](https://forge.fsky.io/oneflux/omegafox/commits/branch/main)
|
||||||
|
|
||||||
- Security enhancements:
|
- Security enhancements:
|
||||||
- Improved process isolation
|
- Improved process isolation
|
||||||
- Weak ciphers and outdated TLS versions disabled by default
|
- Weak ciphers and outdated TLS versions disabled by default
|
||||||
- Hardened compilation configuration
|
- Hardened compilation configuration
|
||||||
- Bundled with [hardened_malloc](https://github.com/GrapheneOS/hardened_malloc)
|
- Bundled with [hardened_malloc](https://github.com/GrapheneOS/hardened_malloc)
|
||||||
- More to come
|
- Uses Mullvad's adblock DNS-over-HTTPS to provide Encrypted Client Hello
|
||||||
|
- JIT disabled by default
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|
@ -31,19 +32,19 @@ git clone --depth 1 https://forge.fsky.io/oneflux/omegafox
|
||||||
cd omegafox
|
cd omegafox
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, build the Omegafox source code with the following command:
|
Next, build the Ωfox source code with the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make dir
|
make dir
|
||||||
```
|
```
|
||||||
|
|
||||||
After that, you have to bootstrap your system to be able to build Omegafox. You only have to do this one time. It is done by running the following command:
|
After that, you have to bootstrap your system to be able to build Ωfox. You only have to do this one time. It is done by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make bootstrap
|
make bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally you can build and package Omegafox the following command:
|
Finally you can build and package Ωfox using the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 multibuild.py --target linux --arch x86_64 arm64 i686
|
python3 multibuild.py --target linux --arch x86_64 arm64 i686
|
||||||
|
|
@ -74,3 +75,6 @@ $ python3 multibuild.py --target linux --arch x86_64 arm64
|
||||||
|
|
||||||
- [Camoufox](https://github.com/daijro/camoufox) - For inspiring this project
|
- [Camoufox](https://github.com/daijro/camoufox) - For inspiring this project
|
||||||
- [GrapheneOS](https://github.com/GrapheneOS) - Hardened memory allocator
|
- [GrapheneOS](https://github.com/GrapheneOS) - Hardened memory allocator
|
||||||
|
- [Mullvad](https://mullvad.net/) - Adblock DNS-over-HTTPS
|
||||||
|
- [Firefox-hardened](https://github.com/boredsquirrel/Firefox-hardened) - Mozconfig options
|
||||||
|
- [Trivalent](https://github.com/secureblue/Trivalent) - Browser hardening reference
|
||||||
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2 MiB |
|
|
@ -1,40 +1,65 @@
|
||||||
|
# COMMON
|
||||||
ac_add_options --enable-application=browser
|
ac_add_options --enable-application=browser
|
||||||
|
ac_add_options --enable-jxl
|
||||||
ac_add_options --allow-addon-sideload
|
ac_add_options --enable-release
|
||||||
|
mk_add_options MOZ_MAKE_FLAGS="-j$(nproc)"
|
||||||
|
export CC=clang
|
||||||
|
export CXX=clang++
|
||||||
|
|
||||||
|
# DEBLOAT
|
||||||
ac_add_options --disable-crashreporter
|
ac_add_options --disable-crashreporter
|
||||||
ac_add_options --disable-backgroundtasks
|
ac_add_options --disable-backgroundtasks
|
||||||
ac_add_options --disable-debug
|
|
||||||
ac_add_options --disable-default-browser-agent
|
ac_add_options --disable-default-browser-agent
|
||||||
|
ac_add_options --disable-parental-controls
|
||||||
ac_add_options --disable-tests
|
ac_add_options --disable-tests
|
||||||
ac_add_options --disable-updater
|
ac_add_options --disable-updater
|
||||||
ac_add_options --enable-hardening
|
ac_add_options --disable-artifact-builds
|
||||||
ac_add_options --enable-jxl
|
|
||||||
# ac_add_options --enable-optimize
|
|
||||||
ac_add_options --enable-release
|
|
||||||
# ac_add_options --enable-rust-simd
|
|
||||||
|
|
||||||
ac_add_options --disable-system-policies
|
ac_add_options --disable-system-policies
|
||||||
# ac_add_options --disable-accessibility
|
# ac_add_options --disable-accessibility
|
||||||
# ac_add_options --disable-webspeech
|
# ac_add_options --disable-webspeech
|
||||||
|
|
||||||
ac_add_options --with-app-name=omegafox
|
|
||||||
ac_add_options --with-branding=browser/branding/omegafox
|
|
||||||
|
|
||||||
ac_add_options --with-unsigned-addon-scopes=app,system
|
|
||||||
|
|
||||||
export MOZ_REQUIRE_SIGNING=
|
|
||||||
|
|
||||||
mk_add_options MOZ_CRASHREPORTER=0
|
mk_add_options MOZ_CRASHREPORTER=0
|
||||||
mk_add_options MOZ_DATA_REPORTING=0
|
mk_add_options MOZ_DATA_REPORTING=0
|
||||||
mk_add_options MOZ_SERVICES_HEALTHREPORT=0
|
mk_add_options MOZ_SERVICES_HEALTHREPORT=0
|
||||||
mk_add_options MOZ_TELEMETRY_REPORTING=0
|
mk_add_options MOZ_TELEMETRY_REPORTING=0
|
||||||
mk_add_options MOZ_INSTALLER=0
|
mk_add_options MOZ_INSTALLER=0
|
||||||
mk_add_options MOZ_AUTOMATION_INSTALLER=0
|
mk_add_options MOZ_AUTOMATION_INSTALLER=0
|
||||||
|
MOZ_CRASHREPORTER=0
|
||||||
|
MOZ_DATA_REPORTING=0
|
||||||
|
export MOZ_CRASHREPORTER=0
|
||||||
|
export MOZ_DATA_REPORTING=0
|
||||||
|
|
||||||
ac_add_options --enable-lto
|
# ADDONS
|
||||||
# ac_add_options --enable-alsa
|
ac_add_options --allow-addon-sideload
|
||||||
|
ac_add_options --with-unsigned-addon-scopes=app,system
|
||||||
|
|
||||||
|
# BRANDING
|
||||||
|
ac_add_options --with-app-name=omegafox
|
||||||
|
ac_add_options --with-branding=browser/branding/omegafox
|
||||||
|
export MOZ_REQUIRE_SIGNING=
|
||||||
|
|
||||||
|
# SPEED
|
||||||
|
ac_add_options --enable-optimize
|
||||||
|
ac_add_options --enable-rust-simd
|
||||||
|
ac_add_options MOZ_THIN_LTO=1
|
||||||
|
export MOZ_THIN_LTO=1
|
||||||
|
# ac_add_options --disable-debug
|
||||||
|
# ac_add_options --disable-debug-symbols
|
||||||
|
# ac_add_options --disable-debug-js-modules
|
||||||
|
# ac_add_options --enable-strip
|
||||||
|
# ac_add_options --enable-install-strip
|
||||||
|
# ac_add_options --disable-profiling
|
||||||
|
mk_add_options MOZ_OPTIMIZE=1
|
||||||
|
MOZ_OPTIMIZE=1
|
||||||
|
export MOZ_OPTIMIZE=1
|
||||||
|
# export STRIP_FLAGS="--strip-debug --strip-unneeded"
|
||||||
|
|
||||||
|
# SECURITY
|
||||||
|
ac_add_options --enable-hardening
|
||||||
|
mk_add_options MOZ_HARDENING=1
|
||||||
ac_add_options --disable-jemalloc
|
ac_add_options --disable-jemalloc
|
||||||
ac_add_options --enable-cfi
|
ac_add_options --enable-sandbox
|
||||||
ac_add_options --enable-safestack
|
ac_add_options --enable-default-toolkit=cairo-gtk3-wayland
|
||||||
ac_add_options --enable-pie
|
CFLAGS="$CFLAGS -fwrapv"
|
||||||
|
CXXFLAGS="$CXXFLAGS -fwrapv"
|
||||||
|
CFLAGS="$CFLAGS -ftrivial-auto-var-init=zero"
|
||||||
|
CXXFLAGS="$CXXFLAGS -ftrivial-auto-var-init=zero"
|
||||||
728
jsonvv/README.md
|
|
@ -1,728 +0,0 @@
|
||||||
# JSONvv
|
|
||||||
|
|
||||||
JSON value validator
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This is a simple JSON schema validator library. It was created for Camoufox to validate passed user configurations. Because I found it useful for other projects, I decided to extract it into a separate library.
|
|
||||||
|
|
||||||
JSONvv's syntax parser is written in pure Python. It does not rely on any dependencies.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th width="50%">Configuration</th>
|
|
||||||
<th width="50%">Validator</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
```python
|
|
||||||
config = {
|
|
||||||
"username": "johndoe",
|
|
||||||
"email": "johndoe@example.com",
|
|
||||||
"age": 30,
|
|
||||||
"chat": "Hello world!",
|
|
||||||
"preferences": {
|
|
||||||
"notifications": True,
|
|
||||||
"theme": "dark"
|
|
||||||
},
|
|
||||||
"allowed_commands": [
|
|
||||||
"/help", "/time", "/weather"
|
|
||||||
],
|
|
||||||
"location": [40.7128, -74.0060],
|
|
||||||
"hobbies": [
|
|
||||||
{
|
|
||||||
"name": "Traveling",
|
|
||||||
"cities": ["Paris", "London"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "reading",
|
|
||||||
"hours": {
|
|
||||||
"Sunday": 2,
|
|
||||||
"Monday": 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
```python
|
|
||||||
validator = {
|
|
||||||
"username": "str", # Basic username
|
|
||||||
"email": "str[/\S+@\S+\.\S+/]", # Validate emails
|
|
||||||
"age": "int[>=18]", # Age must be 18 or older
|
|
||||||
"chat": "str | nil", # Optional chat message
|
|
||||||
"preferences": {
|
|
||||||
"notifications": "bool",
|
|
||||||
"theme": "str[light, dark] | nil", # Optional theme
|
|
||||||
},
|
|
||||||
# Commands must start with "/", but not contain "sudo"
|
|
||||||
"allowed_commands": "array[str[/^//] - str[/sudo/]]",
|
|
||||||
# Validate coordinate ranges
|
|
||||||
"location": "tuple[double[-90 - 90], double[-180 - 180]]",
|
|
||||||
# Handle an array of hobby types
|
|
||||||
"hobbies": "array[@traveling | @other, >=1]",
|
|
||||||
"@traveling": {
|
|
||||||
# Require 1 or more cities/countries iff name is "Traveling"
|
|
||||||
"*name,type": "str[Traveling]",
|
|
||||||
"*cities,countries": "array[str[A-Za-z*], >=1]",
|
|
||||||
},
|
|
||||||
"@other": {
|
|
||||||
"name,type": "str - str[Traveling]", # Non-traveling types
|
|
||||||
# If hour(s) is specified, require days have >0 hours
|
|
||||||
"/hours?/": {
|
|
||||||
"*/day$/": "int[>0]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<hr width=50>
|
|
||||||
|
|
||||||
Then, validate the configuration like this:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from jsonvv import JsonValidator, JvvRuntimeException
|
|
||||||
|
|
||||||
val = JsonValidator(validator)
|
|
||||||
try:
|
|
||||||
val.validate(config)
|
|
||||||
except JvvRuntimeException as exc:
|
|
||||||
print("Failed:", exc)
|
|
||||||
else:
|
|
||||||
print('Config is valid!')
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Key Syntax](#key-syntax)
|
|
||||||
- [Regex patterns](#regex-patterns)
|
|
||||||
- [Lists of possible values](#lists-of-possible-values)
|
|
||||||
- [Required fields (`*`)](#required-fields-)
|
|
||||||
- [Grouping keys (`$`)](#grouping-keys-)
|
|
||||||
- [Supported Types](#supported-types)
|
|
||||||
- [String (`str`)](#string-str)
|
|
||||||
- [Integer (`int`)](#integer-int)
|
|
||||||
- [Double (`double`)](#double-double)
|
|
||||||
- [Boolean (`bool`)](#boolean-bool)
|
|
||||||
- [Array (`array`)](#array-array)
|
|
||||||
- [Tuple (`tuple`)](#tuple-tuple)
|
|
||||||
- [Nested Dictionaries](#nested-dictionaries)
|
|
||||||
- [Nil (`nil`)](#nil-nil)
|
|
||||||
- [Any (`any`)](#any-any)
|
|
||||||
- [Required fields (`*`)](#required-fields-)
|
|
||||||
- [Type References (`@`)](#type-references-)
|
|
||||||
- [Advanced Features](#advanced-features)
|
|
||||||
- [Subtracting Domains (`-`)](#subtracting-domains--)
|
|
||||||
- [Union Types (`|`)](#union-types-)
|
|
||||||
- [Conditional Ranges and Values](#conditional-ranges-and-values)
|
|
||||||
- [Error Handling](#error-handling)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Keys Syntax
|
|
||||||
|
|
||||||
Dictionary keys can be specified in several possible ways:
|
|
||||||
|
|
||||||
- `"key": "type"`
|
|
||||||
- `"key1,key2,key3": "type"`
|
|
||||||
- `"/key\d+/": "type"`
|
|
||||||
- `"*required_key": "type"`
|
|
||||||
|
|
||||||
### Regex patterns
|
|
||||||
|
|
||||||
To use regex in a key, wrap it in `/ ... /`.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"/key\d+/": "type"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lists of possible values
|
|
||||||
|
|
||||||
To specify a list of keys, use a comma-separated string.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"key1,key2,key3": "type"
|
|
||||||
"/k[ey]{2}1/,key2": "type"
|
|
||||||
```
|
|
||||||
|
|
||||||
To escape a comma, use `!`.
|
|
||||||
|
|
||||||
### Required fields (`*`)
|
|
||||||
|
|
||||||
Fields marked with `*` are required. The validation will fail without them.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"*key1": "type"
|
|
||||||
"*/key\d+/": "type"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Grouping keys (`$`)
|
|
||||||
|
|
||||||
Fields that end with `$group_name` are grouped together. If one of the keys is set, all of the keys in the group must also be set as well.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"isEnabled$group1": "bool"
|
|
||||||
"value$group1": "int[>0]"
|
|
||||||
```
|
|
||||||
|
|
||||||
This will require both `value` is set if and only if `isEnabled` is set.
|
|
||||||
|
|
||||||
Multiple `$` can be used to create more complex group dependencies.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Supported Types
|
|
||||||
|
|
||||||
### String (`str`)
|
|
||||||
|
|
||||||
Represents a string value. Optionally, you can specify a regex pattern that the string must match.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
- Basic string: `"str"`
|
|
||||||
- With regex pattern: `"str[regex_pattern]"`
|
|
||||||
- The escape character for regex is `\`, and for commas is `_`.
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
|
|
||||||
- `regex_pattern`: A regular expression that the string must match. If not specified, any string is accepted.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Basic string:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"username": "str"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts any string value for the key `username`.
|
|
||||||
|
|
||||||
2. String with regex pattern:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"fullname": "str[/[A-Z][a-z]+ [A-Z][a-z]+/]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a string that matches the pattern of a first and last name starting with uppercase letters.
|
|
||||||
|
|
||||||
### Integer (`int`)
|
|
||||||
|
|
||||||
Represents an integer value. You can specify conditions like exact values, ranges, and inequalities.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
- Basic integer: `"int"`
|
|
||||||
- With conditions: `"int[conditions]"`
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
|
|
||||||
- `conditions`: A comma-separated list of conditions.
|
|
||||||
|
|
||||||
**Condition Operators:**
|
|
||||||
|
|
||||||
- `==`: Equal to a specific value.
|
|
||||||
- `>=`: Greater than or equal to a value.
|
|
||||||
- `<=`: Less than or equal to a value.
|
|
||||||
- `>`: Greater than a value.
|
|
||||||
- `<`: Less than a value.
|
|
||||||
- `range`: A range between two values (inclusive).
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Basic integer:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"age": "int"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts any integer value for the key `age`.
|
|
||||||
|
|
||||||
2. Integer with conditions:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"userage": "int[>=0, <=120]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts integer values between 0 and 120 inclusive.
|
|
||||||
|
|
||||||
3. Specific values and ranges
|
|
||||||
|
|
||||||
```python
|
|
||||||
"rating": "int[1-5]"
|
|
||||||
"rating": "int[1,2,3,4-5]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts integer values 1, 2, 3, 4, or 5.
|
|
||||||
|
|
||||||
4. Ranges with negative numbers:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"rating": "int[-100 - -90]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts integer values from -100 to -90.
|
|
||||||
|
|
||||||
### Double (`double`)
|
|
||||||
|
|
||||||
Represents a floating-point number. Supports the same conditions as integers.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
- Basic double: `"double"`
|
|
||||||
- With conditions: `"double[conditions]"`
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
|
|
||||||
- `conditions`: A comma-separated list of conditions.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Basic double:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"price": "double"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts any floating-point number for the key `price`.
|
|
||||||
|
|
||||||
2. Double with conditions:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"percentage": "double[>=0.0,<=100.0]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts double values between 0.0 and 100.0 inclusive.
|
|
||||||
|
|
||||||
### Boolean (`bool`)
|
|
||||||
|
|
||||||
Represents a boolean value (`True` or `False`).
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"isActive": "bool"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a boolean value for the key `isActive`.
|
|
||||||
|
|
||||||
### Array (`array`)
|
|
||||||
|
|
||||||
Represents a list of elements of a specified type. You can specify conditions on the length of the array.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
- Basic array: `"array[element_type]"`
|
|
||||||
- With length conditions: `"array[element_type,length_conditions]"`
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
|
|
||||||
- `element_type`: The type of the elements in the array.
|
|
||||||
- `length_conditions`: Conditions on the array length (same as integer conditions).
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Basic array:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"tags": "array[str]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a list of strings for the key `tags`.
|
|
||||||
|
|
||||||
2. Array with length conditions:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"scores": "array[int[>=0,<=100],>=1,<=5]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a list of 1 to 5 integers between 0 and 100 inclusive.
|
|
||||||
|
|
||||||
3. Fixed-length array:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"coordinates": "array[double, 2]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a list of exactly 2 double values.
|
|
||||||
|
|
||||||
4. More complex restraints:
|
|
||||||
```python
|
|
||||||
"coordinates": "array[array[int[>0]] - tuple[1, 1]], 2]"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tuple (`tuple`)
|
|
||||||
|
|
||||||
Represents a fixed-size sequence of elements of specified types.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"tuple[element_type1, element_type2]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
|
|
||||||
- `element_typeN`: The type of the Nth element in the tuple.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Basic tuple:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"point": "tuple[int, int]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a tuple or list of two integers.
|
|
||||||
|
|
||||||
2. Tuple with mixed types:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"userInfo": "tuple[str, int, bool]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a tuple of a string, an integer, and a boolean.
|
|
||||||
|
|
||||||
### Nested Dictionaries
|
|
||||||
|
|
||||||
Represents a nested dictionary structure. Dictionaries are defined using Python's dictionary syntax `{}` in the type definitions.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"settings": {
|
|
||||||
"volume": "int[>=0,<=100]",
|
|
||||||
"brightness": "int[>=0,<=100]",
|
|
||||||
"mode": "str"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
|
|
||||||
- Define the expected keys and their types within the dictionary.
|
|
||||||
- You can use all the supported types for the values.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Nested dictionary:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"user": {
|
|
||||||
"name": "str",
|
|
||||||
"age": "int[>=0]",
|
|
||||||
"preferences": {
|
|
||||||
"theme": "str",
|
|
||||||
"notifications": "bool"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Defines a nested dictionary structure for the key `user`.
|
|
||||||
|
|
||||||
### Nil (`nil`)
|
|
||||||
|
|
||||||
Represents a `None` value.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"optionalValue": "int | nil"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
|
|
||||||
- Use `nil` to allow a value to be `None`.
|
|
||||||
- Often used with union types to specify optional values.
|
|
||||||
|
|
||||||
### Any (`any`)
|
|
||||||
|
|
||||||
Represents any value.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"metadata": "any"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
|
|
||||||
- Use `any` when any value is acceptable.
|
|
||||||
- Useful for keys where the value is not constrained.
|
|
||||||
|
|
||||||
### Type References (`@`)
|
|
||||||
|
|
||||||
Allows you to define reusable types and reference them.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
- Define a named type:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"@typeName": "type_definition"
|
|
||||||
```
|
|
||||||
|
|
||||||
- Reference a named type:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"key": "@typeName"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Defining and using a named type:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"@positiveInt": "int[>0]"
|
|
||||||
"userId": "@positiveInt"
|
|
||||||
```
|
|
||||||
|
|
||||||
Defines a reusable type `@positiveInt` and uses it for the key `userId`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Subtracting Domains (`-`)
|
|
||||||
|
|
||||||
Allows you to specify that a value should not match a certain type or condition.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"typeA - typeB"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
|
|
||||||
- The value must match `typeA` but not `typeB`.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Excluding certain strings:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"message": "str - str[.*error.*]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts any string that does not match the regex pattern `.*error.*`.
|
|
||||||
|
|
||||||
2. Excluding a range of numbers:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"score": "int[0-100] - int[>=90]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts integers between 0 and 100, excluding values greater than or equal to 90.
|
|
||||||
|
|
||||||
3. Excluding multiple types:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"score": "int[>0,<100] - int[>90] - int[<10]"
|
|
||||||
# Union, then subtraction:
|
|
||||||
"score": "int[>0,<100] - int[>90] | int[<10]"
|
|
||||||
"score": "int[>0,<100] - (int[>90] | int[<10])" # same thing
|
|
||||||
# Use parenthesis to run subtraction first
|
|
||||||
"score": "int[>0,<50] | (int[<100] - int[<10])"
|
|
||||||
"score": "(int[<100] - int[<10]) | int[>0,<50]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: Union is handled before subtraction.
|
|
||||||
|
|
||||||
4. Allowing all but a specific value:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"specialNumber": "any - int[0]"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Union Types (`|`)
|
|
||||||
|
|
||||||
Allows you to specify that a value can be one of multiple types.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
"typeA | typeB | typeC"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
|
|
||||||
- The value must match at least one of the specified types.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Multiple possible types:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"data": "int | str | bool"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts an integer, string, or boolean value for the key `data`.
|
|
||||||
|
|
||||||
2. Combining with arrays:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"mixedList": "array[int | str]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts a list of integers or strings.
|
|
||||||
|
|
||||||
### Conditional Ranges and Values
|
|
||||||
|
|
||||||
Specifies conditions that values must satisfy, including ranges and specific values.
|
|
||||||
|
|
||||||
**Syntax:**
|
|
||||||
|
|
||||||
- Greater than: `">value"`
|
|
||||||
- Less than: `"<value"`
|
|
||||||
- Greater than or equal to: `">=value"`
|
|
||||||
- Less than or equal to: `"<="value"`
|
|
||||||
- Range: `"start-end"`
|
|
||||||
- Specific values: `"value1,value2,value3"`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
1. Integer conditions:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"level": "int[>=1,<=10]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts integers from 1 to 10 inclusive.
|
|
||||||
|
|
||||||
2. Double with range:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"latitude": "double[-90.0 - 90.0]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts doubles between -90.0 and 90.0 inclusive.
|
|
||||||
|
|
||||||
3. Specific values:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"status": "int[1,2,3]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepts integers that are either 1, 2, or 3.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
Exception --> JvvException
|
|
||||||
JvvException --> JvvRuntimeException
|
|
||||||
JvvException --> JvvSyntaxError
|
|
||||||
|
|
||||||
JvvRuntimeException --> UnknownProperty["UnknownProperty<br/><small>Raised when a key in config<br/>isn't defined in property types</small>"]
|
|
||||||
JvvRuntimeException --> InvalidPropertyType["InvalidPropertyType<br/><small>Raised when a value doesn't<br/>match its type definition</small>"]
|
|
||||||
InvalidPropertyType --> MissingRequiredKey["MissingRequiredKey<br/><small>Raised when a required key<br/>is missing from config</small>"]
|
|
||||||
MissingRequiredKey --> MissingGroupKey["MissingGroupKey<br/><small>Raised when some keys in a<br/>property group are missing</small>"]
|
|
||||||
|
|
||||||
JvvSyntaxError --> PropertySyntaxError["PropertySyntaxError<br/><small>Raised when property type<br/>definitions have syntax errors</small>"]
|
|
||||||
|
|
||||||
classDef base fill:#eee,stroke:#333,stroke-width:2px;
|
|
||||||
classDef jvv fill:#d4e6f1,stroke:#2874a6,stroke-width:2px;
|
|
||||||
classDef runtime fill:#d5f5e3,stroke:#196f3d,stroke-width:2px;
|
|
||||||
classDef syntax fill:#fdebd0,stroke:#b9770e,stroke-width:2px;
|
|
||||||
classDef error fill:#fadbd8,stroke:#943126,stroke-width:2px;
|
|
||||||
|
|
||||||
class Exception base;
|
|
||||||
class JvvException jvv;
|
|
||||||
class JvvRuntimeException,JvvSyntaxError runtime;
|
|
||||||
class PropertySyntaxError syntax;
|
|
||||||
class UnknownProperty,InvalidPropertyType,MissingRequiredKey,MissingGroupKey error;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Types
|
|
||||||
|
|
||||||
- **str**: Basic string type.
|
|
||||||
|
|
||||||
- Arguments:
|
|
||||||
- `regex_pattern` (optional): A regex pattern the string must match.
|
|
||||||
- Example: `"str[^[A-Za-z]+$]"`
|
|
||||||
|
|
||||||
- **int**: Integer type with conditions.
|
|
||||||
|
|
||||||
- Arguments:
|
|
||||||
- `conditions`: Inequalities (`>=`, `<=`, `>`, `<`), specific values (`value1,value2`), ranges (`start-end`).
|
|
||||||
- Example: `"int[>=0,<=100]"`
|
|
||||||
|
|
||||||
- **double**: Double (floating-point) type with conditions.
|
|
||||||
|
|
||||||
- Arguments:
|
|
||||||
- Same as `int`.
|
|
||||||
- Example: `"double[>0.0]"`
|
|
||||||
|
|
||||||
- **bool**: Boolean type.
|
|
||||||
|
|
||||||
- Arguments: None.
|
|
||||||
- Example: `"bool"`
|
|
||||||
|
|
||||||
- **array**: Array (list) of elements of a specified type.
|
|
||||||
|
|
||||||
- Arguments:
|
|
||||||
- `element_type`: Type of elements in the array.
|
|
||||||
- `length_conditions` (optional): Conditions on the array length.
|
|
||||||
- Example: `"array[int[>=0],>=1,<=10]"`
|
|
||||||
|
|
||||||
- **tuple**: Fixed-size sequence of elements of specified types.
|
|
||||||
|
|
||||||
- Arguments:
|
|
||||||
- List of element types.
|
|
||||||
- Example: `"tuple[str, int, bool]"`
|
|
||||||
|
|
||||||
- **nil**: Represents a `None` value.
|
|
||||||
|
|
||||||
- Arguments: None.
|
|
||||||
- Example: `"nil"`
|
|
||||||
|
|
||||||
- **any**: Accepts any value.
|
|
||||||
|
|
||||||
- Arguments: None.
|
|
||||||
- Example: `"any"`
|
|
||||||
|
|
||||||
- **Type References**: Reusable type definitions.
|
|
||||||
- Arguments:
|
|
||||||
- `@typeName`: Reference to a named type.
|
|
||||||
- Example:
|
|
||||||
- Define: `"@positiveInt": "int[>0]"`
|
|
||||||
- Use: `"userId": "@positiveInt"`
|
|
||||||
|
|
||||||
### Type Combinations
|
|
||||||
|
|
||||||
- **Union Types** (`|`): Value must match one of multiple types.
|
|
||||||
|
|
||||||
- Syntax: `"typeA | typeB"`
|
|
||||||
- Example: `"str | int"`
|
|
||||||
|
|
||||||
- **Subtracting Domains** (`-`): Value must match `typeA` but not `typeB`.
|
|
||||||
- Syntax: `"typeA - typeB"`
|
|
||||||
- Example: `"int - int[13]"` (any integer except 13)
|
|
||||||
|
|
||||||
### Escaping Characters
|
|
||||||
|
|
||||||
- `!`: Escapes commas, slashes, and other jsonvv characters within strings.
|
|
||||||
- `\`: Escapes within a regex pattern.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
from .exceptions import (
|
|
||||||
InvalidPropertyType,
|
|
||||||
JvvException,
|
|
||||||
JvvRuntimeException,
|
|
||||||
JvvSyntaxError,
|
|
||||||
MissingRequiredKey,
|
|
||||||
PropertySyntaxError,
|
|
||||||
UnknownProperty,
|
|
||||||
)
|
|
||||||
from .validator import JsonValidator
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'JvvRuntimeException',
|
|
||||||
'JvvSyntaxError',
|
|
||||||
'PropertySyntaxError',
|
|
||||||
'JsonValidator',
|
|
||||||
'JvvException',
|
|
||||||
'InvalidPropertyType',
|
|
||||||
'UnknownProperty',
|
|
||||||
'MissingRequiredKey',
|
|
||||||
]
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from jsonvv.exceptions import InvalidPropertyType, JvvSyntaxError, UnknownProperty
|
|
||||||
from jsonvv.validator import JsonValidator
|
|
||||||
|
|
||||||
|
|
||||||
def load_json(file_path: Path) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Load and parse a JSON file.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open(file_path) as f:
|
|
||||||
return json.load(f)
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
raise ValueError(f"Invalid JSON in {file_path}: {e}")
|
|
||||||
except FileNotFoundError:
|
|
||||||
raise ValueError(f"File not found: {file_path}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""JSON Value Validator - Validate JSON data against a schema."""
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="JSON Value Validator - Validate JSON data against a schema."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'properties_file', type=Path, help='JSON file containing the property type definitions'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-i', '--input', type=Path, help='JSON file containing the data to validate'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--check', action='store_true', help='Check if the properties file is valid'
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Load property types
|
|
||||||
property_types = load_json(args.properties_file)
|
|
||||||
validator = JsonValidator(property_types)
|
|
||||||
|
|
||||||
if args.check:
|
|
||||||
print("✓ Property types are valid")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not args.input:
|
|
||||||
parser.error("Either --input or --check must be specified")
|
|
||||||
|
|
||||||
# Load and validate data
|
|
||||||
data = load_json(args.input)
|
|
||||||
validator.validate(data)
|
|
||||||
print("✓ Data is valid")
|
|
||||||
|
|
||||||
except (InvalidPropertyType, UnknownProperty) as e:
|
|
||||||
print(f"Validation Error: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
except JvvSyntaxError as e:
|
|
||||||
print(f"Syntax Error: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
except ValueError as e:
|
|
||||||
print(f"File Error: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
"""Exception classes for jsonvv"""
|
|
||||||
|
|
||||||
|
|
||||||
class JvvException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class JvvRuntimeException(JvvException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class JvvSyntaxError(JvvException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnknownProperty(JvvRuntimeException, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidPropertyType(JvvRuntimeException, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MissingRequiredKey(InvalidPropertyType):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MissingGroupKey(MissingRequiredKey):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PropertySyntaxError(JvvSyntaxError):
|
|
||||||
pass
|
|
||||||
|
|
@ -1,309 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
from .exceptions import InvalidPropertyType
|
|
||||||
from .strings import string_validator
|
|
||||||
from .types import (
|
|
||||||
AnyType,
|
|
||||||
ArrayType,
|
|
||||||
BaseType,
|
|
||||||
BoolType,
|
|
||||||
DoubleType,
|
|
||||||
IntType,
|
|
||||||
NilType,
|
|
||||||
StringType,
|
|
||||||
SubtractionType,
|
|
||||||
TupleType,
|
|
||||||
Type,
|
|
||||||
UnionType,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
|
||||||
def __init__(self, type_str: str):
|
|
||||||
self.type_str = type_str
|
|
||||||
self.pos = 0
|
|
||||||
self.length = len(type_str)
|
|
||||||
|
|
||||||
def parse(self) -> Type:
|
|
||||||
"""Main entry point"""
|
|
||||||
result = self.parse_subtraction() # Start with subtraction instead of union
|
|
||||||
self.skip_whitespace()
|
|
||||||
if self.pos < self.length:
|
|
||||||
raise RuntimeError(f"Unexpected character at position {self.pos}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
def parse_union(self) -> Type:
|
|
||||||
"""Handles type1 | type2 | type3"""
|
|
||||||
types = [self.parse_term()] # Parse first term
|
|
||||||
|
|
||||||
while self.pos < self.length:
|
|
||||||
self.skip_whitespace()
|
|
||||||
if not self.match('|'):
|
|
||||||
break
|
|
||||||
types.append(self.parse_term()) # Parse additional terms
|
|
||||||
|
|
||||||
return types[0] if len(types) == 1 else UnionType(types)
|
|
||||||
|
|
||||||
def parse_subtraction(self) -> Type:
|
|
||||||
"""Handles type1 - type2"""
|
|
||||||
left = self.parse_union() # Start with union
|
|
||||||
|
|
||||||
while self.pos < self.length:
|
|
||||||
self.skip_whitespace()
|
|
||||||
if not self.match('-'):
|
|
||||||
break
|
|
||||||
right = self.parse_union() # Parse right side as union
|
|
||||||
left = SubtractionType(left, right)
|
|
||||||
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_term(self) -> Type:
|
|
||||||
"""Handles basic terms and parenthesized expressions"""
|
|
||||||
self.skip_whitespace()
|
|
||||||
|
|
||||||
if self.match('('):
|
|
||||||
type_obj = self.parse_subtraction() # Parse subtraction inside parens
|
|
||||||
if not self.match(')'):
|
|
||||||
raise RuntimeError("Unclosed parenthesis")
|
|
||||||
return type_obj
|
|
||||||
|
|
||||||
return self.parse_basic_type()
|
|
||||||
|
|
||||||
def parse_basic_type(self) -> Type:
|
|
||||||
"""Handles basic types with conditions"""
|
|
||||||
name = self.parse_identifier()
|
|
||||||
|
|
||||||
# Special handling for array type
|
|
||||||
if name == 'array':
|
|
||||||
return self.parse_array_type()
|
|
||||||
|
|
||||||
# Special handling for tuple type
|
|
||||||
if name == 'tuple':
|
|
||||||
# Don't advance position, let parse_tuple_type handle it
|
|
||||||
return self.parse_tuple_type()
|
|
||||||
|
|
||||||
conditions = None
|
|
||||||
self.skip_whitespace()
|
|
||||||
|
|
||||||
if self.match('['):
|
|
||||||
start = self.pos
|
|
||||||
# For all types, just capture everything until the closing bracket
|
|
||||||
bracket_count = 1 # Track nested brackets
|
|
||||||
while self.pos < self.length:
|
|
||||||
if self.type_str[self.pos] == '[':
|
|
||||||
bracket_count += 1
|
|
||||||
elif self.type_str[self.pos] == ']':
|
|
||||||
bracket_count -= 1
|
|
||||||
if bracket_count == 0:
|
|
||||||
break
|
|
||||||
self.pos += 1
|
|
||||||
|
|
||||||
if bracket_count > 0:
|
|
||||||
raise RuntimeError("Unclosed '['")
|
|
||||||
conditions = self.type_str[start : self.pos]
|
|
||||||
|
|
||||||
if not self.match(']'):
|
|
||||||
raise RuntimeError("Expected ']'")
|
|
||||||
|
|
||||||
# Return appropriate type based on name
|
|
||||||
if name == 'str':
|
|
||||||
return StringType(conditions)
|
|
||||||
elif name == 'int':
|
|
||||||
return IntType(conditions)
|
|
||||||
elif name == 'double':
|
|
||||||
return DoubleType(conditions)
|
|
||||||
elif name == 'bool':
|
|
||||||
return BoolType()
|
|
||||||
elif name == 'any':
|
|
||||||
return AnyType()
|
|
||||||
elif name == 'nil':
|
|
||||||
return NilType() # Add this type
|
|
||||||
elif name == 'tuple':
|
|
||||||
return self.parse_tuple_type()
|
|
||||||
elif name.startswith('@'):
|
|
||||||
return ReferenceType(name[1:])
|
|
||||||
return BaseType(name, conditions)
|
|
||||||
|
|
||||||
def peek(self, char: str) -> bool:
|
|
||||||
"""Looks ahead for a character without advancing position"""
|
|
||||||
self.skip_whitespace()
|
|
||||||
return self.pos < self.length and self.type_str[self.pos] == char
|
|
||||||
|
|
||||||
def parse_array_type(self) -> Type:
|
|
||||||
"""Handles array[type, length?]"""
|
|
||||||
if not self.match('['):
|
|
||||||
return ArrayType(AnyType(), None) # Default array type
|
|
||||||
|
|
||||||
# Parse the element type (which could be a complex type)
|
|
||||||
element_type = self.parse_subtraction() # Start with subtraction to handle all cases
|
|
||||||
|
|
||||||
length_conditions = None
|
|
||||||
self.skip_whitespace()
|
|
||||||
|
|
||||||
# Check for length conditions after comma
|
|
||||||
if self.match(','):
|
|
||||||
self.skip_whitespace()
|
|
||||||
start = self.pos
|
|
||||||
while self.pos < self.length and self.type_str[self.pos] != ']':
|
|
||||||
self.pos += 1
|
|
||||||
if self.pos >= self.length:
|
|
||||||
raise RuntimeError("Unclosed array type")
|
|
||||||
length_conditions = self.type_str[start : self.pos].strip()
|
|
||||||
|
|
||||||
if not self.match(']'):
|
|
||||||
raise RuntimeError("Expected ']' in array type")
|
|
||||||
|
|
||||||
return ArrayType(element_type, length_conditions)
|
|
||||||
|
|
||||||
def parse_tuple_type(self) -> Type:
|
|
||||||
"""Handles tuple[type1, type2, ...]"""
|
|
||||||
|
|
||||||
if not self.match('['):
|
|
||||||
raise RuntimeError("Expected '[' after 'tuple'")
|
|
||||||
|
|
||||||
types = []
|
|
||||||
while True:
|
|
||||||
self.skip_whitespace()
|
|
||||||
if self.match(']'):
|
|
||||||
break
|
|
||||||
|
|
||||||
# Parse complex type expressions within tuple arguments
|
|
||||||
type_obj = self.parse_subtraction() # Start with subtraction to handle all operations
|
|
||||||
types.append(type_obj)
|
|
||||||
|
|
||||||
self.skip_whitespace()
|
|
||||||
if not self.match(','):
|
|
||||||
if self.match(']'):
|
|
||||||
break
|
|
||||||
raise RuntimeError("Expected ',' or ']' in tuple type")
|
|
||||||
|
|
||||||
return TupleType(types)
|
|
||||||
|
|
||||||
def parse_identifier(self) -> str:
|
|
||||||
"""Parses an identifier"""
|
|
||||||
self.skip_whitespace()
|
|
||||||
start = self.pos
|
|
||||||
|
|
||||||
# Only consume alphanumeric and underscore characters
|
|
||||||
while self.pos < self.length and (
|
|
||||||
self.type_str[self.pos].isalnum() or self.type_str[self.pos] in '_.@!'
|
|
||||||
):
|
|
||||||
self.pos += 1
|
|
||||||
|
|
||||||
if start == self.pos:
|
|
||||||
raise RuntimeError(f'Expected identifier at position {self.pos}')
|
|
||||||
|
|
||||||
result = self.type_str[start : self.pos]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def skip_whitespace(self) -> None:
|
|
||||||
"""Skips whitespace characters"""
|
|
||||||
while self.pos < self.length and self.type_str[self.pos].isspace():
|
|
||||||
self.pos += 1
|
|
||||||
|
|
||||||
def match(self, char: str) -> bool:
|
|
||||||
"""Tries to match a character, advances position if matched"""
|
|
||||||
self.skip_whitespace()
|
|
||||||
if self.pos < self.length and self.type_str[self.pos] == char:
|
|
||||||
self.pos += 1
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def peek_word(self, word: str) -> bool:
|
|
||||||
"""Looks ahead for a word without advancing position"""
|
|
||||||
self.skip_whitespace()
|
|
||||||
return (
|
|
||||||
self.pos + len(word) <= self.length
|
|
||||||
and self.type_str[self.pos : self.pos + len(word)] == word
|
|
||||||
and (
|
|
||||||
self.pos + len(word) == self.length
|
|
||||||
or not self.type_str[self.pos + len(word)].isalnum()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
Python's import system is a pain,
|
|
||||||
so I'm moving DictType and ReferenceType here.
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DictType(Type):
|
|
||||||
type_dict: Dict[str, Any]
|
|
||||||
type_registry: Dict[str, Any]
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if not isinstance(value, dict):
|
|
||||||
raise InvalidPropertyType(f"Expected dict at {'.'.join(path)}, got {type(value)}")
|
|
||||||
|
|
||||||
# Track matched patterns and required keys
|
|
||||||
any_pattern_matched = False
|
|
||||||
required_patterns = {
|
|
||||||
pattern[1:]: False for pattern in self.type_dict if pattern.startswith('*')
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, val in value.items():
|
|
||||||
pattern_matched = False
|
|
||||||
for pattern, type_def in self.type_dict.items():
|
|
||||||
# Strip * for required patterns when matching
|
|
||||||
match_pattern = pattern[1:] if pattern.startswith('*') else pattern
|
|
||||||
|
|
||||||
if string_validator(key, match_pattern):
|
|
||||||
pattern_matched = True
|
|
||||||
any_pattern_matched = True
|
|
||||||
|
|
||||||
# Mark required pattern as found
|
|
||||||
if pattern.startswith('*'):
|
|
||||||
required_patterns[match_pattern] = True
|
|
||||||
|
|
||||||
# Parse the type definition string into a Type object
|
|
||||||
expected_type = parse_type_def(type_def, type_registry)
|
|
||||||
expected_type.validate(val, path + [key], type_registry)
|
|
||||||
|
|
||||||
if not pattern_matched:
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Key {key} at {'.'.join(path)} does not match any allowed patterns"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if all required patterns were matched
|
|
||||||
missing_required = [pattern for pattern, found in required_patterns.items() if not found]
|
|
||||||
if missing_required:
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Missing required properties matching patterns: {', '.join(missing_required)} at {'.'.join(path)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not any_pattern_matched:
|
|
||||||
raise InvalidPropertyType(f"No properties at {'.'.join(path)} matched any patterns")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ReferenceType(Type):
|
|
||||||
name: str
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if self.name not in type_registry:
|
|
||||||
raise RuntimeError(f"Unknown type reference: @{self.name}")
|
|
||||||
|
|
||||||
ref_type = type_registry[self.name]
|
|
||||||
|
|
||||||
if isinstance(ref_type, dict):
|
|
||||||
# Create a DictType for dictionary references
|
|
||||||
dict_type = DictType(ref_type, type_registry)
|
|
||||||
dict_type.validate(value, path, type_registry)
|
|
||||||
else:
|
|
||||||
# For non-dictionary types
|
|
||||||
ref_type.validate(value, path, type_registry)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"@{self.name}"
|
|
||||||
|
|
||||||
|
|
||||||
def parse_type_def(type_def: Any, type_registry: Dict[str, Type]) -> Type:
|
|
||||||
if isinstance(type_def, str):
|
|
||||||
parser = Parser(type_def)
|
|
||||||
return parser.parse()
|
|
||||||
elif isinstance(type_def, dict):
|
|
||||||
return DictType(type_def, type_registry)
|
|
||||||
raise InvalidPropertyType(f"Invalid type definition: {type_def}")
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import re
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class StringValidator:
|
|
||||||
def __init__(self, pattern: str):
|
|
||||||
self.pattern = pattern
|
|
||||||
self.patterns = self._split_patterns(pattern)
|
|
||||||
|
|
||||||
def _split_patterns(self, p: str) -> List[str]:
|
|
||||||
patterns = []
|
|
||||||
current = []
|
|
||||||
in_regex = False
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while i < len(p):
|
|
||||||
if p[i] == '/' and (i == 0 or p[i - 1] != '!'):
|
|
||||||
in_regex = not in_regex
|
|
||||||
current.append(p[i])
|
|
||||||
elif p[i] == ',' and not in_regex:
|
|
||||||
# Check if comma is escaped
|
|
||||||
if i > 0 and p[i - 1] == '!':
|
|
||||||
current.append(',')
|
|
||||||
else:
|
|
||||||
# End of pattern
|
|
||||||
patterns.append(''.join(current))
|
|
||||||
current = []
|
|
||||||
else:
|
|
||||||
current.append(p[i])
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
if current:
|
|
||||||
patterns.append(''.join(current))
|
|
||||||
|
|
||||||
result = [p.strip() for p in patterns if p.strip()]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _is_regex_pattern(self, p: str) -> bool:
|
|
||||||
is_regex = p.startswith('/') and p.endswith('/') and not p.endswith('!/')
|
|
||||||
return is_regex
|
|
||||||
|
|
||||||
def _clean_literal_pattern(self, p: str) -> str:
|
|
||||||
return re.sub(r'!(.)', r'\1', p)
|
|
||||||
|
|
||||||
def validate(self, value: str) -> bool:
|
|
||||||
for p in self.patterns:
|
|
||||||
p = self._clean_literal_pattern(p)
|
|
||||||
if self._is_regex_pattern(p):
|
|
||||||
regex = p[1:-1]
|
|
||||||
match = bool(re.match(regex, value))
|
|
||||||
if match:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
match = value == p
|
|
||||||
if match:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def string_validator(value: str, pattern: str) -> bool:
|
|
||||||
validator = StringValidator(pattern)
|
|
||||||
result = validator.validate(value)
|
|
||||||
return result
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Any, Dict, List, Optional, Union
|
|
||||||
|
|
||||||
from .exceptions import InvalidPropertyType
|
|
||||||
from .strings import string_validator
|
|
||||||
|
|
||||||
TYPE_NAMES = {'array', 'tuple', 'str', 'int', 'double', 'bool', 'any', 'nil', 'tuple'}
|
|
||||||
|
|
||||||
|
|
||||||
class Type(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, 'Type']) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class BaseType(Type):
|
|
||||||
"""Base class for all types"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
conditions: Optional[str] = None
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
# Raise error early
|
|
||||||
if not self.name.startswith('@') and self.name not in TYPE_NAMES:
|
|
||||||
raise InvalidPropertyType(f'Unknown base type {self.name}')
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if self.name in type_registry:
|
|
||||||
type_registry[self.name].validate(value, path, type_registry)
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f'Unknown base type {self.name}')
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class NilType(Type):
|
|
||||||
"""Represents a nil/null type"""
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if value is not None:
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: expected nil, got {value}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "nil"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class StringType(Type):
|
|
||||||
pattern: Optional[str] = None
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if not isinstance(value, str):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: expected string, got {type(value).__name__}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.pattern:
|
|
||||||
if not string_validator(value, self.pattern):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: {value} does not match pattern '{self.pattern}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"str[{self.pattern}]" if self.pattern else "str"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class NumericalType(Type):
|
|
||||||
conditions: Optional[str] = None
|
|
||||||
numeric_type: Type = float # Default to float
|
|
||||||
type_name: str = "number" # For error messages
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
allowed_types = (int, float) if self.numeric_type is float else (int,)
|
|
||||||
if not isinstance(value, allowed_types):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: expected {self.type_name}, got {type(value).__name__}"
|
|
||||||
)
|
|
||||||
if self.conditions and not self._check_conditions(self.numeric_type(value)):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: {value} does not match conditions '{self.conditions}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_conditions(self, value: Union[int, float]) -> bool:
|
|
||||||
if not self.conditions:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Split by comma and handle each condition
|
|
||||||
conditions = [c.strip() for c in self.conditions.split(',')]
|
|
||||||
|
|
||||||
for condition in conditions:
|
|
||||||
try:
|
|
||||||
# Handle comparisons
|
|
||||||
if '>=' in condition:
|
|
||||||
if value >= self.numeric_type(condition.replace('>=', '')):
|
|
||||||
return True
|
|
||||||
elif '<=' in condition:
|
|
||||||
if value <= self.numeric_type(condition.replace('<=', '')):
|
|
||||||
return True
|
|
||||||
elif '>' in condition:
|
|
||||||
if value > self.numeric_type(condition.replace('>', '')):
|
|
||||||
return True
|
|
||||||
elif '<' in condition:
|
|
||||||
if value < self.numeric_type(condition.replace('<', '')):
|
|
||||||
return True
|
|
||||||
# Handle ranges (e.g., "1.5-5.5")
|
|
||||||
elif '-' in condition[1:]:
|
|
||||||
# split by the -, ignoring the first character
|
|
||||||
range_s, range_e = condition[1:].split('-', 1)
|
|
||||||
range_s = self.numeric_type(condition[0] + range_s)
|
|
||||||
range_e = self.numeric_type(range_e)
|
|
||||||
if range_s <= value <= range_e:
|
|
||||||
return True
|
|
||||||
# Handle single values
|
|
||||||
else:
|
|
||||||
if value == self.numeric_type(condition):
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"{self.type_name}[{self.conditions}]" if self.conditions else self.type_name
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class IntType(NumericalType):
|
|
||||||
def __init__(self, conditions: Optional[str] = None):
|
|
||||||
super().__init__(conditions=conditions, numeric_type=int, type_name="int")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DoubleType(NumericalType):
|
|
||||||
def __init__(self, conditions: Optional[str] = None):
|
|
||||||
super().__init__(conditions=conditions, numeric_type=float, type_name="double")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AnyType(Type):
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
# Any type accepts all values
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "any"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class BoolType(Type):
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if not isinstance(value, bool):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: expected bool, got {type(value).__name__}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ArrayType(Type):
|
|
||||||
element_type: Type
|
|
||||||
length_conditions: Optional[str] = None
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if not isinstance(value, list):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: expected array, got {type(value).__name__}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.length_conditions:
|
|
||||||
array_len = len(value)
|
|
||||||
length_validator = IntType(self.length_conditions)
|
|
||||||
try:
|
|
||||||
length_validator._check_conditions(array_len)
|
|
||||||
except Exception:
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid array length at {'.'.join(path)}: got length {array_len}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, item in enumerate(value):
|
|
||||||
self.element_type.validate(item, path + [str(i)], type_registry)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TupleType(Type):
|
|
||||||
element_types: List[Type]
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
if not isinstance(value, (list, tuple)):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: expected tuple, got {type(value).__name__}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(value) != len(self.element_types):
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid tuple length at {'.'.join(path)}: expected {len(self.element_types)}, got {len(value)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, (item, expected_type) in enumerate(zip(value, self.element_types)):
|
|
||||||
expected_type.validate(item, path + [str(i)], type_registry)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class UnionType(Type):
|
|
||||||
types: List[Type]
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
errors = []
|
|
||||||
for t in self.types:
|
|
||||||
try:
|
|
||||||
t.validate(value, path, type_registry)
|
|
||||||
return # If any type validates successfully, we're done
|
|
||||||
except InvalidPropertyType as e:
|
|
||||||
errors.append(str(e))
|
|
||||||
|
|
||||||
# If we get here, none of the types validated
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {'.'.join(path)}: {value} does not match any of the allowed types"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"({' | '.join(str(t) for t in self.types)})"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SubtractionType(Type):
|
|
||||||
base_type: Type
|
|
||||||
subtracted_type: Type
|
|
||||||
|
|
||||||
def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None:
|
|
||||||
path_str = '.'.join(path)
|
|
||||||
|
|
||||||
# First check if value matches base type
|
|
||||||
matches_base = True
|
|
||||||
try:
|
|
||||||
self.base_type.validate(value, path, type_registry)
|
|
||||||
except InvalidPropertyType:
|
|
||||||
matches_base = False
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Then check if value matches subtracted type
|
|
||||||
matches_subtracted = True
|
|
||||||
try:
|
|
||||||
self.subtracted_type.validate(value, path, type_registry)
|
|
||||||
matches_subtracted = True
|
|
||||||
except InvalidPropertyType:
|
|
||||||
matches_subtracted = False
|
|
||||||
|
|
||||||
# Final validation decision
|
|
||||||
if matches_base and matches_subtracted:
|
|
||||||
raise InvalidPropertyType(f"Invalid value at {path_str}: {value} matches excluded type")
|
|
||||||
elif matches_base and not matches_subtracted:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise InvalidPropertyType(
|
|
||||||
f"Invalid value at {path_str}: {value} does not match base type"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"({self.base_type} - {self.subtracted_type})"
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from .exceptions import (
|
|
||||||
MissingGroupKey,
|
|
||||||
MissingRequiredKey,
|
|
||||||
PropertySyntaxError,
|
|
||||||
UnknownProperty,
|
|
||||||
)
|
|
||||||
from .parser import parse_type_def
|
|
||||||
from .strings import string_validator
|
|
||||||
from .types import Type
|
|
||||||
|
|
||||||
|
|
||||||
class JsonValidator:
|
|
||||||
def __init__(self, property_types):
|
|
||||||
self.property_types = property_types
|
|
||||||
# Create a registry for reference types and parsed type definitions
|
|
||||||
self.type_registry = {}
|
|
||||||
self.parsed_types = {}
|
|
||||||
# Track property groups
|
|
||||||
self.groups: Dict[str, List[str]] = {}
|
|
||||||
# Validate and pre-parse all type definitions
|
|
||||||
self.parse_types(property_types)
|
|
||||||
|
|
||||||
def validate(self, config_map):
|
|
||||||
# First validate groups
|
|
||||||
self.validate_groups(config_map)
|
|
||||||
# Then validate the rest
|
|
||||||
validate_config(config_map, self.property_types, self.type_registry, self.parsed_types)
|
|
||||||
|
|
||||||
def parse_types(self, property_types: Dict[str, Any], path: str = ""):
|
|
||||||
"""Validates and pre-parses all type definitions."""
|
|
||||||
for key, value in property_types.items():
|
|
||||||
current_path = f"{path}.{key}" if path else key
|
|
||||||
|
|
||||||
# Register reference types
|
|
||||||
if key.startswith('@'):
|
|
||||||
if len(key) == 1:
|
|
||||||
raise PropertySyntaxError(
|
|
||||||
f"Invalid key '{current_path}': '@' must be followed by a reference name"
|
|
||||||
)
|
|
||||||
self.type_registry[key[1:]] = value
|
|
||||||
|
|
||||||
# Validate key syntax for required properties
|
|
||||||
if key.startswith('*') and len(key) == 1:
|
|
||||||
raise PropertySyntaxError(
|
|
||||||
f"Invalid key '{current_path}': '*' must be followed by a property name"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Register group dependencies
|
|
||||||
orig_key: Optional[str] = None
|
|
||||||
while (idx := key.rfind('$')) != -1:
|
|
||||||
# Get the original key before all $
|
|
||||||
if orig_key is None:
|
|
||||||
orig_key = key.split('$', 1)[0]
|
|
||||||
# Add to group registry
|
|
||||||
key, group = key[:idx], key[idx + 1 :]
|
|
||||||
if group not in self.groups:
|
|
||||||
self.groups[group] = []
|
|
||||||
self.groups[group].append(orig_key)
|
|
||||||
|
|
||||||
if isinstance(value, dict):
|
|
||||||
# Recursively validate and parse nested dictionaries
|
|
||||||
self.parse_types(value, current_path)
|
|
||||||
elif isinstance(value, str):
|
|
||||||
try:
|
|
||||||
# Pre-parse the type definition and store it
|
|
||||||
self.parsed_types[current_path] = parse_type_def(value, self.type_registry)
|
|
||||||
except Exception as e:
|
|
||||||
raise PropertySyntaxError(
|
|
||||||
f"Invalid type definition for '{current_path}': {str(e)}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise PropertySyntaxError(
|
|
||||||
f"Invalid type definition for '{current_path}': must be a string or dictionary"
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_groups(self, config_map: Dict[str, Any]) -> None:
|
|
||||||
"""Validates that grouped properties are all present or all absent."""
|
|
||||||
group_presence: Dict[str, bool] = {}
|
|
||||||
|
|
||||||
# Check which groups have any properties present
|
|
||||||
for group, props in self.groups.items():
|
|
||||||
group_presence[group] = any(prop in config_map for prop in props)
|
|
||||||
|
|
||||||
# Validate group completeness
|
|
||||||
for group, is_present in group_presence.items():
|
|
||||||
props = self.groups[group]
|
|
||||||
if is_present:
|
|
||||||
# If any property in group exists, all must exist
|
|
||||||
missing = [prop for prop in props if prop not in config_map]
|
|
||||||
if missing:
|
|
||||||
raise MissingGroupKey(
|
|
||||||
f"Incomplete property group ${group}: missing {', '.join(missing)}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# If no property in group exists, none should exist
|
|
||||||
present = [prop for prop in props if prop in config_map]
|
|
||||||
if present:
|
|
||||||
raise MissingGroupKey(
|
|
||||||
f"Incomplete property group ${group}: found {', '.join(present)} but missing {', '.join(set(props) - set(present))}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_config(
|
|
||||||
config_map: Dict[str, Any],
|
|
||||||
property_types: Dict[str, Any],
|
|
||||||
type_registry: Dict[str, Type],
|
|
||||||
parsed_types: Dict[str, Type],
|
|
||||||
parent_registry: Dict[str, Type] = None,
|
|
||||||
path: str = "",
|
|
||||||
) -> None:
|
|
||||||
"""Validates a configuration map against property types."""
|
|
||||||
|
|
||||||
# Create a new registry for this scope, inheriting from parent if it exists
|
|
||||||
local_registry = dict(parent_registry or type_registry)
|
|
||||||
|
|
||||||
# Track required properties
|
|
||||||
required_props = {key[1:]: False for key in property_types if key.startswith('*')}
|
|
||||||
|
|
||||||
# Validate each property in config
|
|
||||||
for key, value in config_map.items():
|
|
||||||
type_def = None
|
|
||||||
current_path = f"{path}.{key}" if path else key
|
|
||||||
|
|
||||||
# Strip group suffix for type lookup
|
|
||||||
lookup_key = key.split('$')[0] if '$' in key else key
|
|
||||||
|
|
||||||
if lookup_key in property_types:
|
|
||||||
type_def = property_types[lookup_key]
|
|
||||||
|
|
||||||
# If the value is a dict and type_def is also a dict, recurse with new scope
|
|
||||||
if isinstance(value, dict) and isinstance(type_def, dict):
|
|
||||||
validate_config(
|
|
||||||
value, type_def, type_registry, parsed_types, local_registry, current_path
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
elif '*' + lookup_key in property_types:
|
|
||||||
type_def = property_types['*' + lookup_key]
|
|
||||||
required_props[lookup_key] = True
|
|
||||||
else:
|
|
||||||
# Check pattern matches
|
|
||||||
for pattern, pattern_type in property_types.items():
|
|
||||||
if pattern.startswith('@') or pattern.startswith('*'):
|
|
||||||
continue
|
|
||||||
pattern_base = pattern.split('$')[0] if '$' in pattern else pattern
|
|
||||||
if string_validator(lookup_key, pattern_base):
|
|
||||||
type_def = pattern_type
|
|
||||||
current_path = f"{path}.{pattern}" if path else pattern
|
|
||||||
break
|
|
||||||
|
|
||||||
if type_def is None:
|
|
||||||
raise UnknownProperty(f"Unknown property: {key}")
|
|
||||||
|
|
||||||
# Use pre-parsed type if available, otherwise parse it
|
|
||||||
expected_type = parsed_types.get(current_path)
|
|
||||||
if expected_type is None:
|
|
||||||
expected_type = parse_type_def(type_def, local_registry)
|
|
||||||
expected_type.validate(value, [key], local_registry)
|
|
||||||
|
|
||||||
# Check for missing required properties
|
|
||||||
missing_required = [key for key, found in required_props.items() if not found]
|
|
||||||
if missing_required:
|
|
||||||
raise MissingRequiredKey(f"Missing required properties: {', '.join(missing_required)}")
|
|
||||||
|
|
||||||
# Check for missing required properties
|
|
||||||
missing_required = [key for key, found in required_props.items() if not found]
|
|
||||||
if missing_required:
|
|
||||||
raise MissingRequiredKey(f"Missing required properties: {', '.join(missing_required)}")
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
rm -rf ./dist
|
|
||||||
|
|
||||||
vermin . --eval-annotations --target=3.8 --violations jsonvv/ || exit 1
|
|
||||||
|
|
||||||
python -m build
|
|
||||||
twine check dist/*
|
|
||||||
|
|
||||||
read -p "Confirm publish? (y/n) " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
twine upload dist/*
|
|
||||||
fi
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core>=1.0.0"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
||||||
[tool.poetry]
|
|
||||||
name = "jsonvv"
|
|
||||||
version = "0.2.2"
|
|
||||||
description = "JSON value validator"
|
|
||||||
authors = ["daijro <daijro.dev@gmail.com>"]
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://github.com/daijro/camoufox"
|
|
||||||
homepage = "https://github.com/daijro/camoufox/tree/main/pythonlib/jsonvv"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = [
|
|
||||||
"json",
|
|
||||||
"validator",
|
|
||||||
"validation",
|
|
||||||
"typing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.8"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
|
||||||
jsonvv = "jsonvv.__main__:main"
|
|
||||||
|
|
@ -120,9 +120,6 @@ def main():
|
||||||
# Run build
|
# Run build
|
||||||
for target in args.target:
|
for target in args.target:
|
||||||
for arch in args.arch:
|
for arch in args.arch:
|
||||||
if (target, arch) in [("windows", "arm64"), ("macos", "i686")]:
|
|
||||||
print(f"Skipping {target} {arch}: Unsuported architecture.")
|
|
||||||
continue
|
|
||||||
run_build(target, arch)
|
run_build(target, arch)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp
|
||||||
index 52f0af76ec..2a7a9ae4fc 100644
|
index 52f0af76ec..2a7a9ae4fc 100644
|
||||||
--- a/dom/base/ChromeUtils.cpp
|
--- a/dom/base/ChromeUtils.cpp
|
||||||
+++ b/dom/base/ChromeUtils.cpp
|
+++ b/dom/base/ChromeUtils.cpp
|
||||||
@@ -5,6 +5,8 @@
|
@@ -5,6 +5,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "ChromeUtils.h"
|
#include "ChromeUtils.h"
|
||||||
|
|
@ -10,7 +10,7 @@ index 52f0af76ec..2a7a9ae4fc 100644
|
||||||
|
|
||||||
#include "JSOracleParent.h"
|
#include "JSOracleParent.h"
|
||||||
#include "ThirdPartyUtil.h"
|
#include "ThirdPartyUtil.h"
|
||||||
@@ -2115,6 +2117,24 @@ bool ChromeUtils::IsDarkBackground(GlobalObject&, Element& aElement) {
|
@@ -2414,6 +2416,24 @@ bool ChromeUtils::IsDarkBackground(GlobalObject&, Element& aElement) {
|
||||||
return nsNativeTheme::IsDarkBackground(f);
|
return nsNativeTheme::IsDarkBackground(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ index 52f0af76ec..2a7a9ae4fc 100644
|
||||||
double ChromeUtils::DateNow(GlobalObject&) { return JS_Now() / 1000.0; }
|
double ChromeUtils::DateNow(GlobalObject&) { return JS_Now() / 1000.0; }
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
@@ -2141,6 +2161,77 @@ void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
|
@@ -2440,6 +2440,65 @@ void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,18 +97,6 @@ index 52f0af76ec..2a7a9ae4fc 100644
|
||||||
+ }
|
+ }
|
||||||
+ aRetVal.Clear();
|
+ aRetVal.Clear();
|
||||||
+}
|
+}
|
||||||
+
|
|
||||||
+/* static */
|
|
||||||
+void ChromeUtils::CamouGetMouseTrajectory(GlobalObject& aGlobal, long aFromX,
|
|
||||||
+ long aFromY, long aToX, long aToY,
|
|
||||||
+ nsTArray<int32_t>& aPoints) {
|
|
||||||
+ HumanizeMouseTrajectory trajectory(std::make_pair(aFromX, aFromY),
|
|
||||||
+ std::make_pair(aToX, aToY));
|
|
||||||
+ std::vector<int> flattenedPoints = trajectory.getPoints();
|
|
||||||
+
|
|
||||||
+ aPoints.Clear();
|
|
||||||
+ aPoints.AppendElements(flattenedPoints.data(), flattenedPoints.size());
|
|
||||||
+}
|
|
||||||
+
|
+
|
||||||
/* static */
|
/* static */
|
||||||
bool ChromeUtils::ShouldResistFingerprinting(
|
bool ChromeUtils::ShouldResistFingerprinting(
|
||||||
|
|
@ -128,7 +116,7 @@ index 138b9c3f80..c7c7ce74bf 100644
|
||||||
static double DateNow(GlobalObject&);
|
static double DateNow(GlobalObject&);
|
||||||
|
|
||||||
static void EnsureJSOracleStarted(GlobalObject&);
|
static void EnsureJSOracleStarted(GlobalObject&);
|
||||||
@@ -314,6 +318,24 @@ class ChromeUtils {
|
@@ -314,6 +314,20 @@ class ChromeUtils {
|
||||||
static void GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
|
static void GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
|
||||||
nsTArray<nsCString>& aNames);
|
nsTArray<nsCString>& aNames);
|
||||||
|
|
||||||
|
|
@ -145,10 +133,6 @@ index 138b9c3f80..c7c7ce74bf 100644
|
||||||
+
|
+
|
||||||
+ static void CamouGetStringList(GlobalObject& aGlobal, const nsAString& aVarName,
|
+ static void CamouGetStringList(GlobalObject& aGlobal, const nsAString& aVarName,
|
||||||
+ nsTArray<nsString>& aRetVal);
|
+ nsTArray<nsString>& aRetVal);
|
||||||
+
|
|
||||||
+ static void CamouGetMouseTrajectory(GlobalObject& aGlobal, long aFromX,
|
|
||||||
+ long aFromY, long aToX, long aToY,
|
|
||||||
+ nsTArray<int32_t>& aPoints);
|
|
||||||
+
|
+
|
||||||
static bool ShouldResistFingerprinting(
|
static bool ShouldResistFingerprinting(
|
||||||
GlobalObject& aGlobal, JSRFPTarget aTarget,
|
GlobalObject& aGlobal, JSRFPTarget aTarget,
|
||||||
|
|
@ -171,7 +155,7 @@ index 6a99703db1..82415eba19 100644
|
||||||
/**
|
/**
|
||||||
* Starts the JSOracle process for ORB JavaScript validation, if it hasn't started already.
|
* Starts the JSOracle process for ORB JavaScript validation, if it hasn't started already.
|
||||||
*/
|
*/
|
||||||
@@ -761,6 +768,36 @@ partial namespace ChromeUtils {
|
@@ -761,6 +768,31 @@ partial namespace ChromeUtils {
|
||||||
[ChromeOnly]
|
[ChromeOnly]
|
||||||
readonly attribute unsigned long aliveUtilityProcesses;
|
readonly attribute unsigned long aliveUtilityProcesses;
|
||||||
|
|
||||||
|
|
@ -199,11 +183,6 @@ index 6a99703db1..82415eba19 100644
|
||||||
+ * Get a list of strings from Omegafox MaskConfig.
|
+ * Get a list of strings from Omegafox MaskConfig.
|
||||||
+ */
|
+ */
|
||||||
+ sequence<DOMString> camouGetStringList(DOMString varName);
|
+ sequence<DOMString> camouGetStringList(DOMString varName);
|
||||||
+
|
|
||||||
+ /**
|
|
||||||
+ * Calculate a human-like mouse trajectory between two points.
|
|
||||||
+ */
|
|
||||||
+ sequence<long> camouGetMouseTrajectory(long fromX, long fromY, long toX, long toY);
|
|
||||||
+
|
+
|
||||||
/**
|
/**
|
||||||
* Get a list of all possible Utility process Actor Names ; mostly useful to
|
* Get a list of all possible Utility process Actor Names ; mostly useful to
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ diff --git a/lw/moz.build b/lw/moz.build
|
||||||
index e69de29bb2..208184547e 100644
|
index e69de29bb2..208184547e 100644
|
||||||
--- a/lw/moz.build
|
--- a/lw/moz.build
|
||||||
+++ b/lw/moz.build
|
+++ b/lw/moz.build
|
||||||
@@ -0,0 +1,14 @@
|
@@ -0,0 +1,13 @@
|
||||||
+FINAL_TARGET_FILES += [
|
+FINAL_TARGET_FILES += [
|
||||||
+ "omegafox.cfg",
|
+ "omegafox.cfg",
|
||||||
+ "properties.json"
|
+ "properties.json"
|
||||||
|
|
|
||||||
100
patches/ostree-mozboot.patch
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
diff --git a/python/mozboot/mozboot/centosfedora.py b/python/mozboot/mozboot/centosfedora.py
|
||||||
|
index 37aa0e8eaa..7e7cf84481 100644
|
||||||
|
--- a/python/mozboot/mozboot/centosfedora.py
|
||||||
|
+++ b/python/mozboot/mozboot/centosfedora.py
|
||||||
|
@@ -21,9 +21,8 @@ class CentOSFedoraBootstrapper(LinuxBootstrapper, BaseBootstrapper):
|
||||||
|
def install_packages(self, packages):
|
||||||
|
if self.version >= 33 and "perl" in packages:
|
||||||
|
packages.append("perl-FindBin")
|
||||||
|
- # watchman is not available on centos/rocky
|
||||||
|
- if self.distro in ("centos", "rocky", "oracle"):
|
||||||
|
- packages = [p for p in packages if p != "watchman"]
|
||||||
|
+ # watchman is not available on centos/rocky/f42
|
||||||
|
+ packages = [p for p in packages if p != "watchman"]
|
||||||
|
self.dnf_install(*packages)
|
||||||
|
|
||||||
|
def upgrade_mercurial(self, current):
|
||||||
|
@@ -33,7 +32,41 @@ class CentOSFedoraBootstrapper(LinuxBootstrapper, BaseBootstrapper):
|
||||||
|
self.dnf_update("mercurial")
|
||||||
|
|
||||||
|
def dnf_install(self, *packages):
|
||||||
|
- if which("dnf"):
|
||||||
|
+ if which("rpm-ostree"):
|
||||||
|
+
|
||||||
|
+ def not_installed(package):
|
||||||
|
+ is_installed = subprocess.run(
|
||||||
|
+ ["rpm", "-q", package],
|
||||||
|
+ stdout=subprocess.DEVNULL,
|
||||||
|
+ stderr=subprocess.DEVNULL,
|
||||||
|
+ )
|
||||||
|
+ return is_installed.returncode !=0
|
||||||
|
+
|
||||||
|
+ packages = list(filter(not_installed, packages))
|
||||||
|
+ if len(packages) == 0:
|
||||||
|
+ return
|
||||||
|
+
|
||||||
|
+ command = ["rpm-ostree", "install"]
|
||||||
|
+ command.extend(packages)
|
||||||
|
+ subprocess.run(command, check=True)
|
||||||
|
+
|
||||||
|
+ reboot_confirmation = (
|
||||||
|
+ input(
|
||||||
|
+ "Packages installed successfully. "
|
||||||
|
+ "A system reboot is required. "
|
||||||
|
+ "Do you want to reboot now? (y/n): "
|
||||||
|
+ ).strip().lower()
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ if reboot_confirmation == 'y':
|
||||||
|
+ print("Rebooting...")
|
||||||
|
+ subprocess.run(["systemctl", "reboot"])
|
||||||
|
+ else:
|
||||||
|
+ raise Exception(
|
||||||
|
+ f'Reboot deferred. Please reboot to continue mozboot.'
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ elif which("dnf"):
|
||||||
|
|
||||||
|
def not_installed(package):
|
||||||
|
# We could check for "Error: No matching Packages to list", but
|
||||||
|
@@ -68,7 +101,39 @@ class CentOSFedoraBootstrapper(LinuxBootstrapper, BaseBootstrapper):
|
||||||
|
self.run_as_root(command)
|
||||||
|
|
||||||
|
def dnf_update(self, *packages):
|
||||||
|
- if which("dnf"):
|
||||||
|
+ if which("rpm-ostree"):
|
||||||
|
+ command = ["rpm-ostree", "upgrade"]
|
||||||
|
+
|
||||||
|
+ result = subprocess.run(
|
||||||
|
+ command,
|
||||||
|
+ stdout=subprocess.PIPE,
|
||||||
|
+ stderr=subprocess.PIPE,
|
||||||
|
+ text=True,
|
||||||
|
+ check=True
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ output = result.stdout + result.stderr
|
||||||
|
+
|
||||||
|
+ if "No upgrade available" in output:
|
||||||
|
+ return
|
||||||
|
+ else:
|
||||||
|
+ reboot_confirmation = (
|
||||||
|
+ input(
|
||||||
|
+ "System upgraded successfully. "
|
||||||
|
+ "A system reboot is required to continue. "
|
||||||
|
+ "Do you want to reboot now? (y/n): "
|
||||||
|
+ ).strip().lower()
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ if reboot_confirmation == 'y':
|
||||||
|
+ print("Rebooting...")
|
||||||
|
+ subprocess.run(["systemctl", "reboot"])
|
||||||
|
+ else:
|
||||||
|
+ raise Exception(
|
||||||
|
+ f'Reboot deferred. Please reboot to continue mozboot.'
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ elif which("dnf"):
|
||||||
|
command = ["dnf", "update"]
|
||||||
|
else:
|
||||||
|
command = ["yum", "update"]
|
||||||
|
|
@ -33,6 +33,7 @@ run 'cp -v ../../settings/omegafox.cfg .'
|
||||||
run 'cp -v ../../settings/distribution/policies.json .'
|
run 'cp -v ../../settings/distribution/policies.json .'
|
||||||
run 'cp -v ../../settings/defaults/pref/local-settings.js .'
|
run 'cp -v ../../settings/defaults/pref/local-settings.js .'
|
||||||
run 'cp -v ../../settings/properties.json .'
|
run 'cp -v ../../settings/properties.json .'
|
||||||
|
run 'cp -v ../../settings/libhardened_malloc.so .'
|
||||||
run 'touch moz.build'
|
run 'touch moz.build'
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@
|
||||||
"amazondotcom@search.mozilla.org",
|
"amazondotcom@search.mozilla.org",
|
||||||
"ebay@search.mozilla.org",
|
"ebay@search.mozilla.org",
|
||||||
"twitter@search.mozilla.org",
|
"twitter@search.mozilla.org",
|
||||||
"webcompat@mozilla.org",
|
|
||||||
"screenshots@mozilla.org",
|
"screenshots@mozilla.org",
|
||||||
"formautofill@mozilla.org"
|
"formautofill@mozilla.org"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
BIN
settings/libhardened_malloc.so
Normal file
|
|
@ -1,7 +1,3 @@
|
||||||
// =================================================================
|
|
||||||
// CAMOUFOX FUNCTIONALITY
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
// Use dark theme
|
// Use dark theme
|
||||||
defaultPref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org");
|
defaultPref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org");
|
||||||
|
|
||||||
|
|
@ -35,6 +31,7 @@ defaultPref("ui.systemUsesDarkTheme", 1);
|
||||||
|
|
||||||
// Enable PDFJS
|
// Enable PDFJS
|
||||||
defaultPref("pdfjs.disabled", false);
|
defaultPref("pdfjs.disabled", false);
|
||||||
|
defaultPref("pdfjs.enableScripting", false);
|
||||||
|
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
|
|
@ -267,10 +264,9 @@ defaultPref("browser.sessionstore.resuming_after_os_restart", false);
|
||||||
defaultPref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 0);
|
defaultPref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 0);
|
||||||
defaultPref("browser.sessionhistory.max_total_viewers", 0);
|
defaultPref("browser.sessionhistory.max_total_viewers", 0);
|
||||||
|
|
||||||
// Force disk cache
|
// Disable disk cache
|
||||||
defaultPref("browser.cache.memory.enable", false); // Disable memory cache
|
defaultPref("browser.cache.disk.enable", false); // Enable disk cache
|
||||||
defaultPref("browser.cache.disk.enable", true); // Enable disk cache
|
defaultPref("browser.cache.disk_cache_ssl", false); // Enable disk cache for SSL
|
||||||
defaultPref("browser.cache.disk_cache_ssl", true); // Enable disk cache for SSL
|
|
||||||
|
|
||||||
// Disable offline cache (commented out to help performance)
|
// Disable offline cache (commented out to help performance)
|
||||||
// defaultPref("browser.cache.offline.enable", false);
|
// defaultPref("browser.cache.offline.enable", false);
|
||||||
|
|
@ -382,10 +378,6 @@ defaultPref("datareporting.policy.dataSubmissionEnabled", false);
|
||||||
defaultPref("datareporting.policy.dataSubmissionPolicyAccepted", false);
|
defaultPref("datareporting.policy.dataSubmissionPolicyAccepted", false);
|
||||||
defaultPref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
|
defaultPref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
|
||||||
|
|
||||||
// -------
|
|
||||||
// CAMOUFOX: Removed pref to disable pdf.js
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Disable BFCache in parent process.
|
// Disable BFCache in parent process.
|
||||||
// We also separately disable BFCache in content via docSchell property.
|
// We also separately disable BFCache in content via docSchell property.
|
||||||
defaultPref("fission.bfcacheInParent", false);
|
defaultPref("fission.bfcacheInParent", false);
|
||||||
|
|
@ -455,10 +447,6 @@ defaultPref("security.enterprise_roots.enabled", true);
|
||||||
|
|
||||||
defaultPref("toolkit.shutdown.fastShutdownStage", 0);
|
defaultPref("toolkit.shutdown.fastShutdownStage", 0);
|
||||||
|
|
||||||
// -------
|
|
||||||
// CAMOUFOX: Removed pref to use light theme by default (make less like headless)
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Do not use system colors - they are affected by themes.
|
// Do not use system colors - they are affected by themes.
|
||||||
defaultPref("ui.use_standins_for_native_colors", true);
|
defaultPref("ui.use_standins_for_native_colors", true);
|
||||||
|
|
||||||
|
|
@ -673,4 +661,47 @@ defaultPref("security.tls.version.max", 4); // 4 = TLS 1.3
|
||||||
defaultPref("security.ssl3.rsa_rc4_128_sha", false);
|
defaultPref("security.ssl3.rsa_rc4_128_sha", false);
|
||||||
defaultPref("security.ssl3.rsa_des_ede3_sha", false);
|
defaultPref("security.ssl3.rsa_des_ede3_sha", false);
|
||||||
defaultPref("security.ssl3.dhe_rsa_aes_128_sha", false);
|
defaultPref("security.ssl3.dhe_rsa_aes_128_sha", false);
|
||||||
defaultPref("security.ssl3.dhe_rsa_aes_256_sha", false);
|
defaultPref("security.ssl3.dhe_rsa_aes_256_sha", false);
|
||||||
|
|
||||||
|
// Enable Encrypted Client Hello
|
||||||
|
defaultPref("network.trr.enabled", true);
|
||||||
|
defaultPref("network.trr.uri", "https://adblock.dns.mullvad.net/dns-query");
|
||||||
|
defaultPref("network.trr.mode", 2);
|
||||||
|
defaultPref("network.dns.echconfig.enabled", true);
|
||||||
|
defaultPref("network.trr.bootstrapAddress", 194.242.2.3);
|
||||||
|
|
||||||
|
// Disable referrers
|
||||||
|
defaultPref("network.http.sendRefererHeader", 0);
|
||||||
|
defaultPref("network.http.referer.trimmingPolicy", 2);
|
||||||
|
defaultPref("network.http.referer.XOriginTrimmingPolicy", 2);
|
||||||
|
defaultPref("network.http.referer.XOriginPolicy", 2);
|
||||||
|
defaultPref("network.http.referer.defaultPolicy", 0);
|
||||||
|
|
||||||
|
// Disable JIT by default
|
||||||
|
defaultPref("javascript.options.baselinejit", false);
|
||||||
|
defaultPref("javascript.options.ion", false);
|
||||||
|
defaultPref("javascript.options.warp", false);
|
||||||
|
|
||||||
|
// Disable Unicode domain names to prevent homograph attacks
|
||||||
|
defaultPref("network.IDN_show_punycode", true);
|
||||||
|
|
||||||
|
// Disable middle-mouse paste
|
||||||
|
defaultPref("middlemouse.paste", false);
|
||||||
|
|
||||||
|
// Clear window.name across contexts
|
||||||
|
defaultPref("privacy.window.name.update.enabled", true);
|
||||||
|
|
||||||
|
// Disable ping in links
|
||||||
|
defaultPref("browser.send_pings", false);
|
||||||
|
|
||||||
|
// Disable domain guessing
|
||||||
|
defaultPref("browser.fixup.alternate.enabled", false);
|
||||||
|
|
||||||
|
// Ensure Fission is working
|
||||||
|
defaultPref("fission.autostart", true);
|
||||||
|
|
||||||
|
// Enable network isolation
|
||||||
|
defaultPref("privacy.firstparty.isolate", true);
|
||||||
|
defaultPref("privacy.partition.network_state", true);
|
||||||
|
defaultPref("privacy.storagePrincipal.enabledForTrackers", true);
|
||||||
|
defaultPref("privacy.partition.serviceWorkers", true);
|
||||||
|
|
@ -1,308 +0,0 @@
|
||||||
{
|
|
||||||
"navigator.userAgent$__UA": "str",
|
|
||||||
"navigator.appVersion$__UA": "str",
|
|
||||||
"navigator.platform$__UA": "str",
|
|
||||||
"navigator.oscpu$__UA": "str",
|
|
||||||
|
|
||||||
"navigator.appCodeName$__PROD_CODE": "str",
|
|
||||||
"navigator.appName$__PROD_CODE": "str",
|
|
||||||
"navigator.product$__PROD_CODE": "str",
|
|
||||||
"navigator.productSub": "str[/^\\d+$/]",
|
|
||||||
"navigator.buildID": "str[/^\\d+$/]",
|
|
||||||
|
|
||||||
"screen.height$__SC": "int[>0]",
|
|
||||||
"screen.width$__SC": "int[>0]",
|
|
||||||
"screen.availHeight$__SC": "int[>=0]",
|
|
||||||
"screen.availWidth$__SC": "int[>=0]",
|
|
||||||
"screen.availTop": "int[>=0]",
|
|
||||||
"screen.availLeft": "int[>=0]",
|
|
||||||
"locale:language$__LOCALE": "str",
|
|
||||||
"locale:region$__LOCALE": "str",
|
|
||||||
"locale:script": "str",
|
|
||||||
"geolocation:latitude$__GEO": "double[-90 - 90]",
|
|
||||||
"geolocation:longitude$__GEO": "double[-180 - 180]",
|
|
||||||
"geolocation:accuracy": "double[>=0]",
|
|
||||||
"timezone": "str[/^[\\w_]+/[\\w_]+$/]",
|
|
||||||
|
|
||||||
"locale:all": "str",
|
|
||||||
"headers.Accept-Language": "str",
|
|
||||||
"navigator.language": "str",
|
|
||||||
"navigator.languages": "array[str]",
|
|
||||||
|
|
||||||
"headers.User-Agent": "str",
|
|
||||||
"headers.Accept-Encoding": "str",
|
|
||||||
"navigator.doNotTrack": "str[0, 1, unspecified]",
|
|
||||||
"navigator.hardwareConcurrency": "int[>0]",
|
|
||||||
"navigator.maxTouchPoints": "int[>=0]",
|
|
||||||
"navigator.cookieEnabled": "bool",
|
|
||||||
"navigator.globalPrivacyControl": "bool",
|
|
||||||
"navigator.onLine": "bool",
|
|
||||||
"window.history.length": "int[>=0]",
|
|
||||||
"pdfViewerEnabled": "bool",
|
|
||||||
|
|
||||||
"window.outerHeight$__W_OUTER": "int[>0]",
|
|
||||||
"window.outerWidth$__W_OUTER": "int[>0]",
|
|
||||||
"window.innerHeight$__W_INNER": "int[>0]",
|
|
||||||
"window.innerWidth$__W_INNER": "int[>0]",
|
|
||||||
|
|
||||||
"screen.colorDepth": "int[>0]",
|
|
||||||
"screen.pixelDepth": "int[>0]",
|
|
||||||
"screen.pageXOffset": "double",
|
|
||||||
"screen.pageYOffset": "double",
|
|
||||||
"window.scrollMinX": "int",
|
|
||||||
"window.scrollMinY": "int",
|
|
||||||
"window.scrollMaxX": "int",
|
|
||||||
"window.scrollMaxY": "int",
|
|
||||||
"window.screenX": "int",
|
|
||||||
"window.screenY": "int",
|
|
||||||
"window.devicePixelRatio": "double[>0]",
|
|
||||||
|
|
||||||
"document.body.clientWidth$__DOC_BODY": "int[>=0]",
|
|
||||||
"document.body.clientHeight$__DOC_BODY": "int[>=0]",
|
|
||||||
"document.body.clientTop": "int",
|
|
||||||
"document.body.clientLeft": "int",
|
|
||||||
|
|
||||||
"webrtc:ipv4": "@IPV4",
|
|
||||||
"webrtc:ipv6": "@IPV6",
|
|
||||||
"webrtc:localipv4": "@IPV4",
|
|
||||||
"webrtc:localipv6": "@IPV6",
|
|
||||||
|
|
||||||
"@IPV4": "str[/^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$/]",
|
|
||||||
"@IPV6": "str[/^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$/]",
|
|
||||||
|
|
||||||
"battery:charging$__BATTERY": "bool",
|
|
||||||
"battery:chargingTime$__BATTERY": "double[>=0]",
|
|
||||||
"battery:dischargingTime$__BATTERY": "double[>=0]",
|
|
||||||
"battery:level$__BATTERY": "double[>0]",
|
|
||||||
|
|
||||||
"fonts": "array[str]",
|
|
||||||
"fonts:spacing_seed": "int[>=0]",
|
|
||||||
|
|
||||||
"AudioContext:sampleRate": "int[>=0]",
|
|
||||||
"AudioContext:outputLatency": "double[>=0]",
|
|
||||||
"AudioContext:maxChannelCount": "int[>=0]",
|
|
||||||
|
|
||||||
"mediaDevices:micros": "int[>=0]",
|
|
||||||
"mediaDevices:webcams": "int[>=0]",
|
|
||||||
"mediaDevices:speakers": "int[>=0]",
|
|
||||||
"mediaDevices:enabled": "bool",
|
|
||||||
|
|
||||||
"webGl:renderer$__WEBGL": "str",
|
|
||||||
"webGl:vendor$__WEBGL": "str",
|
|
||||||
|
|
||||||
"webGl:supportedExtensions": "array[str[/^[\\w_]+$/]]",
|
|
||||||
"webGl2:supportedExtensions": "array[str[/^[\\w_]+$/]]",
|
|
||||||
|
|
||||||
"webGl:parameters": "@WEBGL_PARAMS",
|
|
||||||
"webGl2:parameters": "@WEBGL_PARAMS",
|
|
||||||
"webGl:parameters:blockIfNotDefined": "bool",
|
|
||||||
"webGl2:parameters:blockIfNotDefined": "bool",
|
|
||||||
|
|
||||||
"webGl:shaderPrecisionFormats": "@WEBGL_SHADER_PRECISION_FORMATS",
|
|
||||||
"webGl2:shaderPrecisionFormats": "@WEBGL_SHADER_PRECISION_FORMATS",
|
|
||||||
"webGl:shaderPrecisionFormats:blockIfNotDefined": "bool",
|
|
||||||
"webGl2:shaderPrecisionFormats:blockIfNotDefined": "bool",
|
|
||||||
|
|
||||||
"webGl:contextAttributes": "@WEBGL_CONTEXT_ATTRIBUTES",
|
|
||||||
"webGl2:contextAttributes": "@WEBGL_CONTEXT_ATTRIBUTES",
|
|
||||||
|
|
||||||
"@WEBGL_PARAMS": {
|
|
||||||
"2849": "int",
|
|
||||||
"2884": "bool",
|
|
||||||
"2885": "int",
|
|
||||||
"2886": "int",
|
|
||||||
"2928": "array[int, 2]",
|
|
||||||
"2929": "bool",
|
|
||||||
"2930": "bool",
|
|
||||||
"2931": "int",
|
|
||||||
"2932": "int",
|
|
||||||
"2960": "bool",
|
|
||||||
"2961": "int",
|
|
||||||
"2962": "int",
|
|
||||||
"2963": "int",
|
|
||||||
"2964": "int",
|
|
||||||
"2965": "int",
|
|
||||||
"2966": "int",
|
|
||||||
"2967": "int",
|
|
||||||
"2968": "int",
|
|
||||||
"2978": "array[int, 4]",
|
|
||||||
"3024": "bool",
|
|
||||||
"3042": "bool",
|
|
||||||
"3074": "int | nil",
|
|
||||||
"3088": "array[int, 4]",
|
|
||||||
"3089": "bool",
|
|
||||||
"3106": "array[int, 4]",
|
|
||||||
"3107": "array[bool, 4]",
|
|
||||||
"3314": "int | nil",
|
|
||||||
"3315": "int | nil",
|
|
||||||
"3316": "int | nil",
|
|
||||||
"3317": "int",
|
|
||||||
"3330": "int | nil",
|
|
||||||
"3331": "int | nil",
|
|
||||||
"3332": "int | nil",
|
|
||||||
"3333": "int",
|
|
||||||
"3379": "int",
|
|
||||||
"3386": "array[int, 2]",
|
|
||||||
"3408": "int",
|
|
||||||
"3410": "int",
|
|
||||||
"3411": "int",
|
|
||||||
"3412": "int",
|
|
||||||
"3413": "int",
|
|
||||||
"3414": "int",
|
|
||||||
"3415": "int",
|
|
||||||
"7936": "str",
|
|
||||||
"7937": "str",
|
|
||||||
"7938": "str",
|
|
||||||
"10752": "int",
|
|
||||||
"32773": "array[int, 4]",
|
|
||||||
"32777": "int",
|
|
||||||
"32823": "bool",
|
|
||||||
"32824": "int",
|
|
||||||
"32873": "nil",
|
|
||||||
"32877": "int | nil",
|
|
||||||
"32878": "int | nil",
|
|
||||||
"32883": "int | nil",
|
|
||||||
"32926": "bool",
|
|
||||||
"32928": "bool",
|
|
||||||
"32936": "int",
|
|
||||||
"32937": "int",
|
|
||||||
"32938": "int",
|
|
||||||
"32939": "bool",
|
|
||||||
"32968": "int",
|
|
||||||
"32969": "int",
|
|
||||||
"32970": "int",
|
|
||||||
"32971": "int",
|
|
||||||
"33000": "int | nil",
|
|
||||||
"33001": "int | nil",
|
|
||||||
"33170": "int",
|
|
||||||
"33901": "array[double, 2]",
|
|
||||||
"33902": "array[double, 2]",
|
|
||||||
"34016": "int",
|
|
||||||
"34024": "int",
|
|
||||||
"34045": "int | nil",
|
|
||||||
"34047": "nil",
|
|
||||||
"34068": "nil",
|
|
||||||
"34076": "int",
|
|
||||||
"34467": "nil",
|
|
||||||
"34816": "int",
|
|
||||||
"34817": "int",
|
|
||||||
"34818": "int",
|
|
||||||
"34819": "int",
|
|
||||||
"34852": "int | nil",
|
|
||||||
"34853": "int | nil",
|
|
||||||
"34854": "int | nil",
|
|
||||||
"34855": "int | nil",
|
|
||||||
"34856": "int | nil",
|
|
||||||
"34857": "int | nil",
|
|
||||||
"34858": "int | nil",
|
|
||||||
"34859": "int | nil",
|
|
||||||
"34860": "int | nil",
|
|
||||||
"34877": "int",
|
|
||||||
"34921": "int",
|
|
||||||
"34930": "int",
|
|
||||||
"34964": "nil",
|
|
||||||
"34965": "nil",
|
|
||||||
"35071": "int | nil",
|
|
||||||
"35076": "int | nil",
|
|
||||||
"35077": "int | nil",
|
|
||||||
"35371": "int | nil",
|
|
||||||
"35373": "int | nil",
|
|
||||||
"35374": "int | nil",
|
|
||||||
"35375": "int | nil",
|
|
||||||
"35376": "int | nil",
|
|
||||||
"35377": "int | nil",
|
|
||||||
"35379": "int | nil",
|
|
||||||
"35380": "int | nil",
|
|
||||||
"35657": "int | nil",
|
|
||||||
"35658": "int | nil",
|
|
||||||
"35659": "int | nil",
|
|
||||||
"35660": "int",
|
|
||||||
"35661": "int",
|
|
||||||
"35723": "int | nil",
|
|
||||||
"35724": "str",
|
|
||||||
"35725": "nil",
|
|
||||||
"35738": "int",
|
|
||||||
"35739": "int",
|
|
||||||
"35968": "int | nil",
|
|
||||||
"35977": "bool | nil",
|
|
||||||
"35978": "int | nil",
|
|
||||||
"35979": "int | nil",
|
|
||||||
"36003": "int",
|
|
||||||
"36004": "int",
|
|
||||||
"36005": "int",
|
|
||||||
"36006": "nil",
|
|
||||||
"36007": "nil",
|
|
||||||
"36063": "int | nil",
|
|
||||||
"36183": "int | nil",
|
|
||||||
"36203": "int | nil",
|
|
||||||
"36345": "int | nil",
|
|
||||||
"36347": "int",
|
|
||||||
"36348": "int",
|
|
||||||
"36349": "int",
|
|
||||||
"36387": "bool | nil",
|
|
||||||
"36388": "bool | nil",
|
|
||||||
"36392": "nil",
|
|
||||||
"36795": "nil",
|
|
||||||
"37137": "int | double | nil",
|
|
||||||
"37154": "int | nil",
|
|
||||||
"37157": "int | nil",
|
|
||||||
"37440": "bool",
|
|
||||||
"37441": "bool",
|
|
||||||
"37443": "int",
|
|
||||||
"37444": "nil",
|
|
||||||
"37445": "str",
|
|
||||||
"37446": "str",
|
|
||||||
"37447": "int | nil",
|
|
||||||
"38449": "nil"
|
|
||||||
},
|
|
||||||
|
|
||||||
"@WEBGL_SHADER_PRECISION_FORMATS": {
|
|
||||||
"/^\\d+,\\d+$/": {
|
|
||||||
"*rangeMin": "int[>=0]",
|
|
||||||
"*rangeMax": "int[>=0]",
|
|
||||||
"*precision": "int[>=0]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"@WEBGL_CONTEXT_ATTRIBUTES": {
|
|
||||||
"alpha": "bool",
|
|
||||||
"antialias": "bool",
|
|
||||||
"depth": "bool",
|
|
||||||
"failIfMajorPerformanceCaveat": "bool",
|
|
||||||
"powerPreference": "str[low, high, default]",
|
|
||||||
"premultipliedAlpha": "bool",
|
|
||||||
"preserveDrawingBuffer": "bool",
|
|
||||||
"stencil": "bool"
|
|
||||||
},
|
|
||||||
|
|
||||||
"canvas:aaOffset": "int",
|
|
||||||
"canvas:aaCapOffset": "bool",
|
|
||||||
|
|
||||||
"voices": "array[@VOICE_TYPE]",
|
|
||||||
"voices:blockIfNotDefined": "bool",
|
|
||||||
"voices:fakeCompletion": "bool",
|
|
||||||
"voices:fakeCompletion:charsPerSecond": "double[>0]",
|
|
||||||
|
|
||||||
"@VOICE_TYPE": {
|
|
||||||
"*isLocalService": "bool",
|
|
||||||
"*isDefault": "bool",
|
|
||||||
"*voiceURI": "str",
|
|
||||||
"*name": "str",
|
|
||||||
"*lang": "str"
|
|
||||||
},
|
|
||||||
|
|
||||||
"humanize": "bool",
|
|
||||||
"humanize:maxTime": "double[>=0]",
|
|
||||||
"humanize:minTime": "double[>=0]",
|
|
||||||
"showcursor": "bool",
|
|
||||||
|
|
||||||
"allowMainWorld": "bool",
|
|
||||||
"forceScopeAccess": "bool",
|
|
||||||
"enableRemoteSubframes": "bool",
|
|
||||||
"disableTheming": "bool",
|
|
||||||
"memorysaver": "bool",
|
|
||||||
"addons": "array[str]",
|
|
||||||
"certificatePaths": "array[str]",
|
|
||||||
"certificates": "array[str]",
|
|
||||||
"debug": "bool"
|
|
||||||
}
|
|
||||||