├── .gitignore ├── .pylintrc ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE.txt ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── dharma ├── __init__.py ├── __main__.py ├── __version__.py ├── core │ ├── __init__.py │ ├── dharma.py │ ├── extensions.py │ └── websocket.py ├── dharma.py ├── grammars │ ├── canvas2d.dg │ ├── common.dg │ ├── json.dg │ ├── nodejs │ │ └── buffer.dg │ ├── svg.dg │ ├── test.dg │ ├── url.dg │ ├── var │ │ ├── index.html │ │ ├── js │ │ │ └── help.js │ │ └── templates │ │ │ └── html5 │ │ │ └── default.html │ ├── wasm.dg │ └── xss.dg └── settings.py ├── docs ├── Makefile └── source │ ├── conf.py │ ├── dharma.core.rst │ ├── dharma.rst │ ├── index.rst │ └── modules.rst ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Python byte-compiled 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # Python distribution 6 | dist/ 7 | build/ 8 | sdist/ 9 | *.egg 10 | *.egg-info/ 11 | 12 | # Sphinx documentation 13 | docs/_build/ 14 | 15 | # Platform 16 | .DS_Store 17 | 18 | # Testing / Coverage 19 | .tox 20 | .pytest_cache/ 21 | .coverage 22 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | # http://pylint.pycqa.org/en/latest/technical_reference/features.html 2 | [MASTER] 3 | ignore=CVS .git .hg 4 | 5 | [MESSAGES CONTROL] 6 | # Pointless whinging. 7 | # W0613 = Unused argument 8 | # R0201 = Method could be a function 9 | # R0801 = Similar lines https://github.com/PyCQA/pylint/issues/214 10 | # R0903 = Too few public methods 11 | # R0904 = Too many public methods 12 | # R0911 = Too many return statements 13 | # R0913 = Too many arguments 14 | # R0914 = Too many local variables 15 | # 16 | # Sometimes disabled depending on how bad a module is. 17 | # C0111 = Missing docstring 18 | # C0112 = Empty docstring 19 | 20 | disable=C0111,C0112,W0613,R0201,R0801,R0903,R0904,R0911,R0913,R0914 21 | 22 | [MISCELLANEOUS] 23 | # List of note tags to take into consideration, separated by a comma. 24 | notes=FIXME 25 | 26 | [FORMAT] 27 | max-line-length=120 28 | 29 | [REPORTS] 30 | output-format=colorized 31 | 32 | [BASIC] 33 | good-names=a,b,i,j,k,m,n,o,t,v,x,fo,_,logger -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "3.8" 5 | matrix: 6 | include: 7 | - env: TOXENV=py3 8 | before_install: 9 | - pip install pipenv 10 | install: 11 | - pipenv install --dev 12 | script: 13 | - pipenv run tox -e py3 14 | after_success: 15 | - make docs 16 | deploy: 17 | - provider: pypi 18 | user: mozillasecurity 19 | password: 20 | secure: j2+f2KdEIkNJrH5mMo0v72gAgsYmbv4189+asRXS+cluCQfkX5+IMEvGqp2c8LeCrgYgMnE02W3Xuy5FEIQZWrTEtvJSVokNBA+0prztgUAkZ9D1TytRsYF86T9mdMGWROp04G7WGZRsCmicGmCLq+rBfkH7Piobtfxxr5B3E9k= 21 | distributions: sdist bdist_wheel 22 | on: 23 | tags: true 24 | branches: 25 | only: 26 | - master 27 | skip_cleanup: true 28 | - provider: pages 29 | keep-history: true 30 | skip_cleanup: true 31 | github_token: 32 | secure: DKaV3TW4LABnFElBO7FqV8M0WRBtkZ90Sr1rNuokK1SD/oT0FSRhlmmXKQc5+raXWb6tk+hvlMCAKds6DsIZQNF35PB8EvkYszWm27jBzSbYoo7940G7SU10XW+lfNE8CgeTrgNtvFP1TYr4uJL8PYFTQTEUxGRPHi4BSWbPF6Y= 33 | on: 34 | branch: master 35 | local_dir: docs/build/html/ 36 | notifications: 37 | slack: 38 | secure: MnEEIkScGFHvI61pQNQiEA8UjwMOCY9qZqpNt5XA8xC3J7vQ0KrGdm5NMbCKzqMqymibGZ0RH5qu4d6o1cfAV4a9PaEqpaxdAagvY9P5XnqiHxmknEV+voAk0ZEaw4KUlEUxuTp052BPAUrPjXkPZtlG/H9upAmwS1Y3N9kpFCM= 39 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Module", 6 | "type": "python", 7 | "request": "launch", 8 | "module": "dharma", 9 | "console": "integratedTerminal", 10 | "pythonPath": "${config:python.pythonPath}", 11 | "env": { "PYTHONPATH": "${workspaceRoot}" }, 12 | "args": ["-grammars", "dharma/grammars/test.dg"], 13 | "cwd": "${workspaceFolder}", 14 | "stopOnEntry": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/usr/local/bin/python3", 3 | "python.linting.enabled": true, 4 | "python.linting.pylintEnabled": true, 5 | "python.jediEnabled": false, 6 | "python.autoComplete.addBrackets": true, 7 | "python.formatting.provider": "autopep8", 8 | "python.analysis.openFilesOnly": true, 9 | "python.linting.pylintUseMinimalCheckers": false, 10 | "python.linting.ignorePatterns": [ 11 | ".tox/**/*.py", 12 | ".vscode/*.py", 13 | "**/site-packages/**/*.py" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Dharma Test Run", 6 | "type": "shell", 7 | "command": "python3 -m dharma -grammars dharma/grammars/test.dg" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-slim 2 | LABEL maintainer Christoph Diehl 3 | ENV PYTHONUNBUFFERED 1 4 | COPY . /app 5 | WORKDIR /app 6 | RUN python setup.py install 7 | ENTRYPOINT [ "dharma" ] 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | 364 | 365 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build tag docs lint help 2 | .DEFAULT_GOAL := help 3 | 4 | VERSION=`python3 -c 'import dharma.__version__; print(dharma.__version__.__version__)'` 5 | 6 | define PRINT_HELP_PYSCRIPT 7 | import re, sys 8 | for line in sys.stdin: 9 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 10 | if match: 11 | target, help = match.groups() 12 | print("%-20s %s" % (target, help)) 13 | endef 14 | export PRINT_HELP_PYSCRIPT 15 | 16 | help: 17 | @python3 -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 18 | 19 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 20 | 21 | clean-build: ## remove build artifacts 22 | rm -fr build/ 23 | rm -fr dist/ 24 | rm -fr .eggs/ 25 | find . -name '*.egg-info' -exec rm -fr {} + 26 | find . -name '*.egg' -exec rm -f {} + 27 | 28 | clean-pyc: ## remove Python file artifacts 29 | find . -name '*.pyc' -exec rm -f {} + 30 | find . -name '*.pyo' -exec rm -f {} + 31 | find . -name '*~' -exec rm -f {} + 32 | find . -name '__pycache__' -exec rm -fr {} + 33 | 34 | clean-test: ## remove test and coverage artifacts 35 | rm -fr .tox/ 36 | rm -f .coverage 37 | rm -fr htmlcov/ 38 | rm -fr .pytest_cache 39 | 40 | tag: ## tag version for new release 41 | git tag -a v$(VERSION) 42 | git push origin v$(VERSION) 43 | 44 | docs: ## generate Sphinx HTML documentation, including API docs 45 | sphinx-apidoc -E -f -o docs/source dharma/ 46 | $(MAKE) -C docs clean 47 | $(MAKE) -C docs html 48 | 49 | lint: ## run Pylint 50 | python3 -m pylint \ 51 | --rcfile=.pylintrc \ 52 | --msg-template='{path}:{line}: [{msg_id}|{obj}] {msg} [pylint: disable={symbol}]' \ 53 | dharma/ 54 | 55 | servedocs: docs ## watch for changes and compile the docs 56 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 57 | 58 | release: dist ## package and upload a release 59 | twine upload dist/* 60 | 61 | dist: clean ## builds source and wheel package 62 | python3 setup.py sdist 63 | python3 setup.py bdist_wheel 64 | ls -l dist 65 | 66 | install: clean ## install the package to the active Python's site-packages 67 | python3 setup.py install 68 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | tox = "==3.1.2" 10 | twine = "*" 11 | watchdog = "*" 12 | sphinx = "*" 13 | sphinx-rtd-theme = "*" 14 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "1e07eed4b51008ae286178a5fae79f5b161af27e7da159081bc76a9147d01759" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": {}, 17 | "develop": { 18 | "alabaster": { 19 | "hashes": [ 20 | "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", 21 | "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" 22 | ], 23 | "version": "==0.7.12" 24 | }, 25 | "argh": { 26 | "hashes": [ 27 | "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3", 28 | "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65" 29 | ], 30 | "version": "==0.26.2" 31 | }, 32 | "babel": { 33 | "hashes": [ 34 | "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", 35 | "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" 36 | ], 37 | "version": "==2.6.0" 38 | }, 39 | "bleach": { 40 | "hashes": [ 41 | "sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718", 42 | "sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9" 43 | ], 44 | "version": "==3.0.2" 45 | }, 46 | "certifi": { 47 | "hashes": [ 48 | "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", 49 | "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" 50 | ], 51 | "version": "==2018.11.29" 52 | }, 53 | "chardet": { 54 | "hashes": [ 55 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 56 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 57 | ], 58 | "version": "==3.0.4" 59 | }, 60 | "docutils": { 61 | "hashes": [ 62 | "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", 63 | "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", 64 | "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" 65 | ], 66 | "version": "==0.14" 67 | }, 68 | "idna": { 69 | "hashes": [ 70 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 71 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 72 | ], 73 | "version": "==2.8" 74 | }, 75 | "imagesize": { 76 | "hashes": [ 77 | "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", 78 | "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" 79 | ], 80 | "version": "==1.1.0" 81 | }, 82 | "jinja2": { 83 | "hashes": [ 84 | "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", 85 | "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" 86 | ], 87 | "version": "==2.10" 88 | }, 89 | "markupsafe": { 90 | "hashes": [ 91 | "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", 92 | "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", 93 | "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", 94 | "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", 95 | "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", 96 | "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", 97 | "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", 98 | "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", 99 | "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", 100 | "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", 101 | "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", 102 | "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", 103 | "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", 104 | "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", 105 | "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", 106 | "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", 107 | "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", 108 | "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", 109 | "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", 110 | "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", 111 | "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", 112 | "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", 113 | "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", 114 | "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", 115 | "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", 116 | "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", 117 | "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", 118 | "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" 119 | ], 120 | "version": "==1.1.0" 121 | }, 122 | "packaging": { 123 | "hashes": [ 124 | "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", 125 | "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9" 126 | ], 127 | "version": "==18.0" 128 | }, 129 | "pathtools": { 130 | "hashes": [ 131 | "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0" 132 | ], 133 | "version": "==0.1.2" 134 | }, 135 | "pkginfo": { 136 | "hashes": [ 137 | "sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", 138 | "sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee" 139 | ], 140 | "version": "==1.4.2" 141 | }, 142 | "pluggy": { 143 | "hashes": [ 144 | "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", 145 | "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" 146 | ], 147 | "version": "==0.8.0" 148 | }, 149 | "py": { 150 | "hashes": [ 151 | "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", 152 | "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" 153 | ], 154 | "version": "==1.7.0" 155 | }, 156 | "pygments": { 157 | "hashes": [ 158 | "sha256:6301ecb0997a52d2d31385e62d0a4a4cf18d2f2da7054a5ddad5c366cd39cee7", 159 | "sha256:82666aac15622bd7bb685a4ee7f6625dd716da3ef7473620c192c0168aae64fc" 160 | ], 161 | "version": "==2.3.0" 162 | }, 163 | "pyparsing": { 164 | "hashes": [ 165 | "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b", 166 | "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592" 167 | ], 168 | "version": "==2.3.0" 169 | }, 170 | "pytz": { 171 | "hashes": [ 172 | "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", 173 | "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" 174 | ], 175 | "version": "==2018.7" 176 | }, 177 | "pyyaml": { 178 | "hashes": [ 179 | "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", 180 | "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", 181 | "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", 182 | "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", 183 | "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", 184 | "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", 185 | "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", 186 | "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", 187 | "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", 188 | "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", 189 | "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" 190 | ], 191 | "version": "==3.13" 192 | }, 193 | "readme-renderer": { 194 | "hashes": [ 195 | "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", 196 | "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d" 197 | ], 198 | "version": "==24.0" 199 | }, 200 | "requests": { 201 | "hashes": [ 202 | "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", 203 | "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" 204 | ], 205 | "version": "==2.21.0" 206 | }, 207 | "requests-toolbelt": { 208 | "hashes": [ 209 | "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", 210 | "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5" 211 | ], 212 | "version": "==0.8.0" 213 | }, 214 | "six": { 215 | "hashes": [ 216 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 217 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 218 | ], 219 | "version": "==1.12.0" 220 | }, 221 | "snowballstemmer": { 222 | "hashes": [ 223 | "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", 224 | "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" 225 | ], 226 | "version": "==1.2.1" 227 | }, 228 | "sphinx": { 229 | "hashes": [ 230 | "sha256:120732cbddb1b2364471c3d9f8bfd4b0c5b550862f99a65736c77f970b142aea", 231 | "sha256:b348790776490894e0424101af9c8413f2a86831524bd55c5f379d3e3e12ca64" 232 | ], 233 | "index": "pypi", 234 | "version": "==1.8.2" 235 | }, 236 | "sphinx-rtd-theme": { 237 | "hashes": [ 238 | "sha256:02f02a676d6baabb758a20c7a479d58648e0f64f13e07d1b388e9bb2afe86a09", 239 | "sha256:d0f6bc70f98961145c5b0e26a992829363a197321ba571b31b24ea91879e0c96" 240 | ], 241 | "index": "pypi", 242 | "version": "==0.4.2" 243 | }, 244 | "sphinxcontrib-websupport": { 245 | "hashes": [ 246 | "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", 247 | "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" 248 | ], 249 | "version": "==1.1.0" 250 | }, 251 | "tox": { 252 | "hashes": [ 253 | "sha256:4df108a1fcc93a7ee4ac97e1a3a1fc3d41ddd22445d518976604e2ef05025280", 254 | "sha256:9f0cbcc36e08c2c4ae90d02d3d1f9a62231f974bcbc1df85e8045946d8261059" 255 | ], 256 | "index": "pypi", 257 | "version": "==3.1.2" 258 | }, 259 | "tqdm": { 260 | "hashes": [ 261 | "sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392", 262 | "sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb" 263 | ], 264 | "version": "==4.28.1" 265 | }, 266 | "twine": { 267 | "hashes": [ 268 | "sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c", 269 | "sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c" 270 | ], 271 | "index": "pypi", 272 | "version": "==1.12.1" 273 | }, 274 | "urllib3": { 275 | "hashes": [ 276 | "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", 277 | "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" 278 | ], 279 | "version": "==1.24.1" 280 | }, 281 | "virtualenv": { 282 | "hashes": [ 283 | "sha256:686176c23a538ecc56d27ed9d5217abd34644823d6391cbeb232f42bf722baad", 284 | "sha256:f899fafcd92e1150f40c8215328be38ff24b519cd95357fa6e78e006c7638208" 285 | ], 286 | "version": "==16.1.0" 287 | }, 288 | "watchdog": { 289 | "hashes": [ 290 | "sha256:965f658d0732de3188211932aeb0bb457587f04f63ab4c1e33eab878e9de961d" 291 | ], 292 | "index": "pypi", 293 | "version": "==0.9.0" 294 | }, 295 | "webencodings": { 296 | "hashes": [ 297 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 298 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 299 | ], 300 | "version": "==0.5.1" 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **

Archived — please see https://github.com/posidron/dharma for maintained version.

** 2 | 3 |

4 | Logo 5 |

6 | 7 |

8 | Generation-based, context-free grammar fuzzer. 9 |

10 | 11 |

12 | Build Status 13 | IRC 14 |

15 | 16 |

17 | 18 |

19 | 20 |

Table of Contents

21 | 22 | - [Run](#Run) 23 | - [pip](#pip) 24 | - [pipenv](#pipenv) 25 | - [package](#package) 26 | - [Docker](#Docker) 27 | - [Examples](#Examples) 28 | - [Development](#Development) 29 | - [Dharma Grammar Cheatsheet](#Dharma-Grammar-Cheatsheet) 30 | - [API Documentation](#API-Documentation) 31 | - [Dharma in the Public](#Dharma-in-the-Public) 32 | 33 | ## Run 34 | 35 | All roads lead to Rome but Python 3.x is the prefered vehicle. 36 | 37 | ### pip 38 | 39 | ```bash 40 | pip install dharma 41 | dharma --help 42 | ``` 43 | 44 | ### pipenv 45 | 46 | ```bash 47 | pipenv install --dev 48 | pipenv run dharma --help 49 | ``` 50 | 51 | ### package 52 | 53 | ```bash 54 | python -m dharma --help 55 | ``` 56 | 57 | ### Docker 58 | 59 | ```bash 60 | docker build -t dharma . 61 | docker run --rm -it dharma -grammars dharma/grammars/canvas2d.dg 62 | ``` 63 | 64 | ## Examples 65 | 66 | Generate a single test-case and print it to `stdout`. Multiple grammars can be appended to the `-grammars` argument. 67 | 68 | ```bash 69 | dharma -grammars dharma/grammars/canvas2d.dg 70 | ``` 71 | 72 | Generating multiple test-cases and save the result to disk. 73 | 74 | ```bash 75 | dharma -grammars dharma/grammars/canvas2d.dg -storage . -count 5 76 | ``` 77 | 78 | Generate test-cases and serve them in a template via WebSocket. 79 | Launch `dharma/grammars/var/index.html` in the browser after Dharma launched. 80 | 81 | ```bash 82 | dharma -grammars dharma/grammars/canvas2d.dg -server -template dharma/grammars/var/templates/html5/default.html 83 | ``` 84 | 85 | Benchmark the generator. 86 | 87 | ```bash 88 | time dharma -grammars dharma/grammars/canvas2d.dg -count 10000 > /dev/null 89 | ``` 90 | 91 | ## Development 92 | 93 | ### PyLint 94 | 95 | In case you run PyLint 1.9.2 and Python 3.7 you need to upgrade PyLint. 96 | 97 | ```bash 98 | pip3 install pylint astroid --pre -U 99 | ``` 100 | 101 | ## Dharma Grammar Cheatsheet 102 | 103 | ### Comments 104 | 105 | ``` 106 | %%% comment 107 | ``` 108 | 109 | ### Controls 110 | 111 | ``` 112 | %const% name := value 113 | ``` 114 | 115 | ### Sections 116 | 117 | ``` 118 | %section% := value 119 | %section% := variable 120 | %section% := variance 121 | ``` 122 | 123 | ### Extension Methods 124 | 125 | Refer to `extensions.py` in `dharma/core/` and to the `xref_registry` in the `DharmaMachine` class to add further extensions. 126 | 127 | ``` 128 | %range%(0-9) 129 | %range%(0.0-9.0) 130 | %range%(a-z) 131 | %range%(!-~) 132 | %range%(0x100-0x200) 133 | 134 | %repeat%(+variable+) 135 | %repeat%(+variable+, ", ") 136 | 137 | %uri%(path) 138 | %uri%(lookup_key) 139 | 140 | %block%(path) 141 | 142 | %choice%(foo, "bar", 1) 143 | ``` 144 | 145 | ### Assigning Values 146 | 147 | ``` 148 | digit := 149 | %range%(0-9) 150 | 151 | sign := 152 | + 153 | - 154 | 155 | value := 156 | +sign+%repeat%(+digit+) 157 | ``` 158 | 159 | ### Using Values 160 | 161 | ``` 162 | +value+ 163 | ``` 164 | 165 | ### Assigning Variables 166 | 167 | ``` 168 | variable := 169 | @variable@ = new Foo(); 170 | ``` 171 | 172 | ### Using Variables 173 | 174 | ``` 175 | value := 176 | !variable!.bar(); 177 | ``` 178 | 179 | ### Referencing values from `common.dg` 180 | 181 | ``` 182 | value := 183 | attribute=+common:number+ 184 | ``` 185 | 186 | ### Calling JavaScript library methods 187 | 188 | ``` 189 | foo := 190 | Random.pick([0,1]); 191 | ``` 192 | 193 | ## API Documentation 194 | 195 | - https://mozillasecurity.github.io/dharma 196 | 197 | ## Dharma in the Public 198 | 199 | Dharma mentionings in the news. 200 | 201 | - https://webassembly-security.com/fuzzing-wasm-javascript-dharma-chrome-v8/ 202 | - https://www.zerodayinitiative.com/blog/2019/1/31/implementing-fuzz-logics-with-dharma 203 | - http://blog.ret2.io/2018/06/13/pwn2own-2018-vulnerability-discovery/ 204 | - https://blog.mozilla.org/security/2015/06/29/dharma/ 205 | - https://www.redpacketsecurity.com/dharma-generation-based-context-free-grammar-fuzzing-tool/ 206 | -------------------------------------------------------------------------------- /dharma/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | "dharma module" 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | from .dharma import * # noqa pylint: disable=wildcard-import 8 | -------------------------------------------------------------------------------- /dharma/__main__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | "dharma module main" 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | import sys 7 | 8 | from .dharma import DharmaCommandLine 9 | 10 | sys.exit(DharmaCommandLine.main()) 11 | -------------------------------------------------------------------------------- /dharma/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'dharma' 2 | __version__ = '1.3.2' 3 | __license__ = 'MPL 2.0' 4 | __description__ = 'Generation-based, context-free grammar fuzzer.' 5 | __keywords__ = 'fuzzer fuzzing security testing generator' 6 | __author__ = 'Christoph Diehl' 7 | __author_email__ = 'cdiehl@mozilla.com' 8 | __maintainer__ = 'Mozilla Fuzzing Team' 9 | __maintainer_email__ = 'fuzzing@mozilla.com' 10 | __url__ = 'https://github.com/MozillaSecurity/dharma' 11 | __project_urls__ = { 12 | 'Bug Tracker': 'https://github.com/MozillaSecurity/dharma/issues' 13 | } 14 | __download_url__ = "https://github.com/MozillaSecurity/dharma/tarball/" + __version__ 15 | -------------------------------------------------------------------------------- /dharma/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaSecurity/dharma/6b1e5119646064a80122ca18944a98238d5eadb1/dharma/core/__init__.py -------------------------------------------------------------------------------- /dharma/core/dharma.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import re 6 | import sys 7 | import logging 8 | from string import Template 9 | from itertools import chain 10 | from collections import OrderedDict 11 | 12 | if sys.version_info[0] == 2: 13 | from extensions import * # pylint: disable=E0401,W0401 14 | else: 15 | from dharma.core.extensions import * # pylint: disable=W0401,W0614 16 | 17 | 18 | class GenState: 19 | def __init__(self): 20 | self.leaf_mode = False 21 | self.leaf_trigger = 0 22 | 23 | 24 | class String: 25 | """Generator class for basic strings which need no further evaluation.""" 26 | 27 | def __init__(self, value, parent): 28 | self.parent = parent 29 | self.value = value 30 | 31 | def generate(self, state): 32 | return self.value 33 | 34 | 35 | class ValueXRef: 36 | """Generator class for +value+ cross references.""" 37 | 38 | def __init__(self, value, parent): 39 | self.value = ("%s:%s" % (parent.namespace, value)) if ":" not in value else value 40 | self.parent = parent 41 | self.parent.value_xref[self.value] = None 42 | 43 | def generate(self, state): 44 | try: 45 | ref = self.parent.value_xref[self.value] 46 | except KeyError: 47 | logging.error("Value xref inconsistency in %s looking for %s", self.parent.ident, self.value) 48 | sys.exit(-1) 49 | return ref.generate(state) 50 | 51 | 52 | class VariableXRef: 53 | """Generator class for !variable! cross references.""" 54 | 55 | def __init__(self, value, parent): 56 | self.value = ("%s:%s" % (parent.namespace, value)) if ":" not in value else value 57 | self.parent = parent 58 | self.parent.variable_xref[self.value] = None 59 | 60 | def generate(self, state): 61 | try: 62 | ref = self.parent.variable_xref[self.value] 63 | except KeyError: 64 | logging.error("Variable xref inconsistency in %s looking for %s", self.parent.ident, self.value) 65 | sys.exit(-1) 66 | return ref.generate(state) 67 | 68 | 69 | class ElementXRef: 70 | """Generator class for @value@ cross references.""" 71 | 72 | def __init__(self, value, parent): 73 | self.value = ("%s:%s" % (parent.namespace, value)) if ":" not in value else value 74 | self.parent = parent 75 | self.parent.element_xref[self.value] = None 76 | 77 | def generate(self, state): 78 | try: 79 | ref = self.parent.element_xref[self.value] 80 | except KeyError: 81 | logging.error("Element xref inconsistency in %s looking for %s", self.parent.ident, self.value) 82 | sys.exit(-1) 83 | return ref.generate(state) 84 | 85 | 86 | class DharmaObject(list): 87 | """Base object of which Dharma section classes inherit from.""" 88 | 89 | def __init__(self, ident, machine): 90 | list.__init__(self) 91 | self.ident = "%s:%s" % (machine.namespace, ident) 92 | self.machine = machine 93 | self.value_xref = {} 94 | self.variable_xref = {} 95 | self.element_xref = {} 96 | self.namespace = machine.namespace 97 | self.lineno = machine.lineno 98 | 99 | def id(self): # pylint: disable=invalid-name 100 | return "Line %d [%s]" % (self.lineno, self.namespace) 101 | 102 | def __hash__(self): 103 | return hash(self.ident) 104 | 105 | @staticmethod 106 | def eval(tokens, state): 107 | return "".join(token.generate(state) for token in tokens) 108 | 109 | 110 | class DharmaValue(DharmaObject): 111 | """Dharma class which manages the |value| section of a grammar.""" 112 | 113 | def __init__(self, ident, machine): 114 | DharmaObject.__init__(self, ident, machine) 115 | self.leaf = [] 116 | self.leaf_path = [] 117 | self.path_idents = set() 118 | self.minimized = None 119 | 120 | def n_xrefs(self, value): 121 | repeats, n = False, 0 122 | for t in value: 123 | if isinstance(t, ValueXRef): 124 | n += 1 125 | if t.value not in self.path_idents: 126 | return False, None, None 127 | elif isinstance(t, MetaRepeat): 128 | repeats = True 129 | n += self.n_xrefs(t.repeat)[2] 130 | return True, repeats, max(1, min(n, 8)) # constrain within [1, 8] 131 | 132 | def append(self, value): 133 | list.append(self, value) 134 | for t in value: 135 | if isinstance(t, (MetaRepeat, ValueXRef)): 136 | return 137 | self.leaf.append(value) 138 | 139 | def generate(self, state): # pylint: disable=too-many-branches 140 | if not state.leaf_mode: 141 | state.leaf_trigger += 1 142 | if state.leaf_trigger > DharmaConst.LEAF_TRIGGER: 143 | state.leaf_mode = True 144 | if not self: 145 | return "" 146 | if state.leaf_mode and self.leaf: 147 | value = random.choice(self.leaf) 148 | elif state.leaf_mode: # favour non-repeating 149 | if self.minimized is None: 150 | n_refs_groups = {} 151 | have_non_repeats = False 152 | for v in self: 153 | is_leaf_path, repeats, n_xrefs = self.n_xrefs(v) 154 | if not is_leaf_path: 155 | continue 156 | if not repeats: 157 | if not have_non_repeats: 158 | n_refs_groups = {} 159 | have_non_repeats = True 160 | if not repeats or not have_non_repeats: 161 | n_refs_groups.setdefault(n_xrefs, []).append(v) 162 | for _, v in sorted(n_refs_groups.items()): 163 | self.minimized = v 164 | break 165 | if not self.minimized: 166 | logging.error("No path to leaf in force-leaf mode in value %s", self.ident) 167 | sys.exit(-1) 168 | value = random.choice(self.minimized) 169 | else: 170 | value = random.choice(self) 171 | return self.eval(value, state) 172 | 173 | 174 | class DharmaVariable(DharmaObject): 175 | """Dharma class which manages the |variable| section of a grammar.""" 176 | 177 | def __init__(self, ident, machine): 178 | DharmaObject.__init__(self, ident, machine) 179 | self.var = ident 180 | self.count = 0 181 | self.default = "" 182 | 183 | def clear(self): 184 | self.count = 0 185 | self.default = "" 186 | 187 | def generate(self, state): 188 | """Return a random variable if any, otherwise create a new default variable.""" 189 | if self.count >= random.randint(DharmaConst.VARIABLE_MIN, DharmaConst.VARIABLE_MAX): 190 | return "%s%d" % (self.var, random.randint(1, self.count)) 191 | var = random.choice(self) 192 | prefix = self.eval(var[0], state) 193 | suffix = self.eval(var[1], state) 194 | self.count += 1 195 | element_name = "%s%d" % (self.var, self.count) 196 | self.default += "%s%s%s\n" % (prefix, element_name, suffix) 197 | return element_name 198 | 199 | 200 | class DharmaVariance(DharmaObject): 201 | """Dharma class which manages the |variance| section of a grammar.""" 202 | 203 | def generate(self, state): 204 | return self.eval(random.choice(self), state) 205 | 206 | 207 | class DharmaMachine: # pylint: disable=too-many-instance-attributes 208 | def __init__(self, prefix="", suffix="", template=""): 209 | self.section = None 210 | self.level = "top" 211 | self.namespace = "" 212 | self.lineno = 0 213 | self.current_obj = None 214 | self.value = {} 215 | self.variable = OrderedDict() 216 | self.variance = {} 217 | self.prefix = prefix 218 | self.suffix = suffix 219 | self.template = template 220 | self.consts_set = {} 221 | self.default_grammars = ["../grammars/common.dg"] 222 | self.grammar_level_registry = r"""^( 223 | (?P%%%).*| 224 | %const%\s*(?P[A-Z_]+)\s*:=\s*(?P.*)| 225 | %section%\s*:=\s*(?P
value|variable|variance)| 226 | (?P[a-zA-Z0-9_]+)\s*:=\s*| 227 | (?P\s*)| 228 | (\t|[ ]+)(?P.*) 229 | )$""" 230 | self.xref_registry = r"""( 231 | (?P\+|!|@)(?P[a-zA-Z0-9:_]+)(?P=type)| 232 | %uri%\(\s*(?P.*?)\s*\)| 233 | %repeat%\(\s*(?P.+?)\s*(,\s*"(?P.*?)")?\s*(,\s*(?Pnodups))?\s*\)| 234 | %block%\(\s*(?P.*?)\s*\)| 235 | %range%\((?P.+?)-(?P.+?)\)| 236 | %choice%\(\s*(?P.+?)\s*\) 237 | )""" 238 | 239 | def process_settings(self, settings): 240 | """A lazy way of feeding Dharma with configuration settings.""" 241 | logging.debug("Using configuration from: %s", settings.name) 242 | exec(compile(settings.read(), settings.name, 'exec'), globals(), locals()) # pylint: disable=exec-used 243 | 244 | def set_namespace(self, name): 245 | self.namespace = name 246 | self.lineno = 0 247 | 248 | def id(self): # pylint: disable=invalid-name 249 | return "Line %d [%s]" % (self.lineno, self.namespace) 250 | 251 | def parse_line(self, line): 252 | self.lineno += 1 253 | m = re.match(self.grammar_level_registry, line, re.VERBOSE | re.IGNORECASE) 254 | if m is None: 255 | pass 256 | elif m.group("comment"): 257 | return 258 | elif m.group("const"): 259 | self.handle_const(*m.group("const", "value")) 260 | return 261 | elif m.group("section"): 262 | self.handle_empty_line() 263 | self.section = m.group("section").lower() 264 | return 265 | elif m.group("empty") is not None: 266 | self.handle_empty_line() 267 | return 268 | elif self.section is None: 269 | logging.error("%s: Non-empty line in void section", self.id()) 270 | sys.exit(-1) 271 | elif self.level == "top": 272 | self.handle_top_level(m.group("ident")) 273 | return 274 | elif self.level == "assign": 275 | self.handle_assign_level(m.group("assign")) 276 | return 277 | logging.error("%s: Unhandled line", self.id()) 278 | sys.exit(-1) 279 | 280 | def handle_const(self, const, value): 281 | if not hasattr(DharmaConst, const): 282 | logging.error("%s: Trying to set non-existent constant", self.id()) 283 | sys.exit(-1) 284 | orig = self.consts_set.get(const) 285 | if value[0] == '"': 286 | assert value[-1] == '"' 287 | value = value[1:-1] 288 | setattr(DharmaConst, const, value) 289 | else: 290 | setattr(DharmaConst, const, float(value) if "." in value else int(value)) 291 | if orig is not None and getattr(DharmaConst, const) != orig: 292 | logging.warning("%s: Overriding constant %s defined by previous grammar", self.id(), const) 293 | self.consts_set[const] = getattr(DharmaConst, const) 294 | 295 | def handle_empty_line(self): 296 | if self.current_obj is None: 297 | pass 298 | elif not self.current_obj: 299 | logging.error("%s: Empty assignment", self.id()) 300 | sys.exit(-1) 301 | else: 302 | self.add_section_object() 303 | self.level = "top" 304 | self.current_obj = None 305 | 306 | def handle_top_level(self, ident): 307 | if ident is None: 308 | logging.error("%s: Top level syntax error", self.id()) 309 | sys.exit(-1) 310 | try: 311 | assign_type = {"value": DharmaValue, 312 | "variable": DharmaVariable, 313 | "variance": DharmaVariance}[self.section] 314 | except KeyError: 315 | logging.error("%s: Invalid state for top-level", self.id()) 316 | sys.exit(-1) 317 | self.current_obj = assign_type(ident, self) 318 | self.level = "assign" 319 | 320 | def handle_assign_level(self, assign): 321 | if assign is None: 322 | logging.error("%s: Assign level syntax error", self.id()) 323 | sys.exit(-1) 324 | try: 325 | parse_assign = {"value": self.parse_assign_value, 326 | "variable": self.parse_assign_variable, 327 | "variance": self.parse_assign_variance}[self.section] 328 | except KeyError: 329 | logging.error("%s: Invalid state for assignment", self.id()) 330 | sys.exit(-1) 331 | parse_assign(self.parse_xrefs(assign)) 332 | 333 | def parse_xrefs(self, token): 334 | """Search token for +value+ and !variable! style references. Be careful to not xref a new variable. 335 | """ 336 | out, end = [], 0 337 | token = token.replace("\\n", "\n") 338 | for m in re.finditer(self.xref_registry, token, re.VERBOSE | re.DOTALL): 339 | if m.start(0) > end: 340 | out.append(String(token[end:m.start(0)], self.current_obj)) 341 | end = m.end(0) 342 | if m.group("type"): 343 | xref_type = {"+": ValueXRef, 344 | "!": VariableXRef, 345 | "@": ElementXRef}[m.group("type")] 346 | out.append(xref_type(m.group("xref"), self.current_obj)) 347 | elif m.group("uri") is not None: 348 | path = m.group("uri") 349 | out.append(MetaURI(path, self.current_obj)) 350 | elif m.group("repeat") is not None: 351 | repeat, separator, nodups = m.group("repeat", "separator", "nodups") 352 | if separator is None: 353 | separator = "" 354 | if nodups is None: 355 | nodups = "" 356 | out.append(MetaRepeat(self.parse_xrefs(repeat), separator, nodups, self.current_obj)) 357 | elif m.group("block") is not None: 358 | path = m.group("block") 359 | out.append(MetaBlock(path, self.current_obj)) 360 | elif m.group("choices") is not None: 361 | choices = m.group("choices") 362 | out.append(MetaChoice(choices, self.current_obj)) 363 | else: 364 | startval, endval = m.group("start", "end") 365 | out.append(MetaRange(startval, endval, self.current_obj)) 366 | if end < len(token): 367 | out.append(String(token[end:], self.current_obj)) 368 | return out 369 | 370 | def parse_assign_value(self, tokens): 371 | if not isinstance(self.current_obj, DharmaValue): 372 | logging.error("%s: Normal value found in non-normal assignment", self.id()) 373 | sys.exit(-1) 374 | self.current_obj.append(tokens) 375 | 376 | def parse_assign_variable(self, tokens): 377 | """ 378 | Example: 379 | tokens 380 | dharma.String: 'let ', 381 | dharma.ElementXRef: 'GrammarNS:', 382 | dharma.String: '= new ', 383 | dharma.ValueXRef: 'GrammarNS:' 384 | """ 385 | for i, token in enumerate(tokens): 386 | if isinstance(token, ElementXRef): 387 | variable = token.value 388 | break 389 | else: 390 | logging.error("%s: Variable assignment syntax error", self.id()) 391 | sys.exit(-1) 392 | if variable != self.current_obj.ident: 393 | logging.error("%s: Variable name mismatch", self.id()) 394 | sys.exit(-1) 395 | if not isinstance(self.current_obj, DharmaVariable): 396 | logging.error("%s: Inconsistent object for variable assignment", self.id()) 397 | sys.exit(-1) 398 | prefix, suffix = tokens[:i], tokens[i + 1:] # pylint: disable=undefined-loop-variable 399 | self.current_obj.append((prefix, suffix)) 400 | 401 | def parse_assign_variance(self, tokens): 402 | if not isinstance(self.current_obj, DharmaVariance): 403 | logging.error("%s: Inconsistent object for variance assignment", self.id()) 404 | sys.exit(-1) 405 | self.current_obj.append(tokens) 406 | 407 | def add_section_object(self): 408 | try: 409 | section_dict = getattr(self, self.section) 410 | except AttributeError: 411 | logging.error("%s: Inconsistent section value, fatal", self.id()) 412 | sys.exit(-1) 413 | if self.current_obj.ident in section_dict: 414 | logging.error("%s(%s): '%s' gets redefined", self.id(), self.section, self.current_obj.ident) 415 | sys.exit(-1) 416 | section_dict[self.current_obj.ident] = self.current_obj 417 | 418 | def resolve_xref(self): 419 | for obj in chain(self.value.values(), 420 | self.variable.values(), 421 | self.variance.values()): 422 | try: 423 | msg = "%s: Undefined value reference from %s to %s" 424 | obj.value_xref.update((x, self.value[x]) for x in obj.value_xref) 425 | msg = "%s: Undefined variable reference from %s to %s" 426 | obj.variable_xref.update((x, self.variable[x]) for x in obj.variable_xref) 427 | msg = "%s: Element reference without a default variable from %s to %s" 428 | obj.element_xref.update((x, self.variable[x]) for x in obj.element_xref) 429 | except KeyError as error: 430 | logging.error(msg, self.id(), obj.ident, error.args[0]) 431 | sys.exit(-1) 432 | 433 | def calculate_leaf_paths(self): 434 | """Build map of reverse xrefs then traverse backwards marking path to leaf for all leaves. 435 | """ 436 | reverse_xref = {} 437 | leaves = set() 438 | for v in self.value.values(): 439 | if v.leaf: 440 | leaves.add(v) 441 | for xref in v.value_xref: 442 | reverse_xref.setdefault(xref, []).append(v.ident) 443 | for leaf in leaves: 444 | self.calculate_leaf_path(leaf, reverse_xref) 445 | 446 | def calculate_leaf_path(self, leaf, reverse_xref): 447 | if leaf.ident not in reverse_xref: 448 | return 449 | for name in reverse_xref[leaf.ident]: 450 | xref = self.value[name] 451 | xref.leaf_path.append((leaf.ident, leaf.ident, 0)) 452 | xref.path_idents.add(leaf.ident) 453 | self.propagate_leaf(leaf.ident, xref, {xref}, 1, reverse_xref) 454 | 455 | def propagate_leaf(self, leaf, obj, node_seen, depth, reverse_xref): 456 | if obj.ident not in reverse_xref: 457 | return 458 | for name in reverse_xref[obj.ident]: 459 | xref = self.value[name] 460 | xref.leaf_path.append((leaf, obj.ident, depth)) 461 | xref.path_idents.add(obj.ident) 462 | if xref in node_seen: 463 | continue 464 | node_seen.add(xref) 465 | self.propagate_leaf(leaf, xref, node_seen, depth + 1, reverse_xref) 466 | 467 | def generate_content(self): 468 | """Generates a test case as a string.""" 469 | # Setup pre-conditions. 470 | if not self.variance: 471 | logging.error("%s: No variance information %s", self.id(), self.variance) 472 | sys.exit(-1) 473 | 474 | for var in self.variable.values(): 475 | var.clear() 476 | 477 | # Handle variances 478 | variances = [] 479 | for _ in range(random.randint(DharmaConst.VARIANCE_MIN, DharmaConst.VARIANCE_MAX)): 480 | var = random.choice(list(self.variance.values())) 481 | variances.append(DharmaConst.VARIANCE_TEMPLATE % var.generate(GenState())) 482 | variances.append("\n") 483 | 484 | # Handle variables 485 | variables = [] 486 | for var in self.variable.values(): 487 | if var.default: 488 | variables.append(DharmaConst.VARIANCE_TEMPLATE % var.default) 489 | variables.append("\n") 490 | 491 | # Build content 492 | content = "".join(chain([self.prefix], variables, variances, [self.suffix])) 493 | if self.template: 494 | return Template(self.template).safe_substitute(testcase_content=content) 495 | return content 496 | 497 | def generate_testcases(self, path, filetype, count): 498 | """Writes out generated test cases to the provided path.""" 499 | path = path.rstrip("/") 500 | try: 501 | os.makedirs(path, exist_ok=True) 502 | except OSError as error: 503 | logging.error("Unable to create folder for test cases: %s", error) 504 | sys.exit(-1) 505 | for n in range(count): 506 | filename = os.path.join(path, "%d.%s" % (n + 1, filetype)) 507 | try: 508 | with open(filename, "w") as fo: 509 | fo.write(self.generate_content()) 510 | except IOError: 511 | logging.error("Failed in writing test case %s", filename) 512 | sys.exit(-1) 513 | 514 | def process_grammars(self, grammars): 515 | """Process provided grammars by parsing them into Python objects.""" 516 | for path in self.default_grammars: 517 | grammars.insert(0, open(os.path.relpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), 518 | os.path.normcase(path))))) 519 | for fo in grammars: 520 | logging.debug("Processing grammar content of %s", fo.name) 521 | self.set_namespace(os.path.splitext(os.path.basename(fo.name))[0]) 522 | for line in fo: 523 | self.parse_line(line) 524 | self.handle_empty_line() 525 | self.resolve_xref() 526 | self.calculate_leaf_paths() 527 | -------------------------------------------------------------------------------- /dharma/core/extensions.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import sys 6 | import math 7 | import random 8 | import logging 9 | 10 | 11 | class DharmaConst: 12 | """Configuration settings for the Dharma generator.""" 13 | URI_TABLE = {} 14 | LEAF_TRIGGER = 256 15 | MAX_REPEAT_POWER = 12 16 | VARIANCE_TEMPLATE = "%s" 17 | VARIANCE_MIN = 1 18 | VARIANCE_MAX = 8 19 | VARIABLE_MIN = 1 20 | VARIABLE_MAX = 4 21 | 22 | 23 | class MetaBlock: 24 | """Grammar extension which loads code fragments from a file into the grammar.""" 25 | 26 | def __init__(self, path, parent): 27 | self.parent = parent 28 | path = os.path.expanduser(path) 29 | if os.path.exists(path): 30 | with open(path) as fo: 31 | self.content = fo.read() 32 | else: 33 | logging.warning('%s: Unable to load resource for block() "%s"', parent.id(), path) 34 | self.content = path 35 | 36 | def generate(self, state): 37 | return self.content 38 | 39 | 40 | class MetaURI: 41 | """Grammar extension which loads a random file URI into the generated code.""" 42 | 43 | def __init__(self, path, parent): 44 | self.parent = parent 45 | if path in DharmaConst.URI_TABLE: 46 | path = DharmaConst.URI_TABLE[path] 47 | path = os.path.expanduser(path) 48 | if os.path.isdir(path): 49 | self.path = [p for p in (os.path.join(path, f) for f in os.listdir(path)) if os.path.isfile(p)] 50 | elif os.path.exists(path): 51 | self.path = [path] 52 | else: 53 | logging.warning('%s: Unable to identify argument of uri() "%s"', parent.id(), path) 54 | self.path = [path] 55 | 56 | def generate(self, state): 57 | return random.choice(self.path) 58 | 59 | 60 | class MetaRepeat: 61 | """Grammar extension method which repeats an arbitrary expression.""" 62 | 63 | def __init__(self, repeat, separator, nodups, parent): 64 | self.parent = parent 65 | self.repeat, self.separator, self.nodups = repeat, separator, nodups 66 | 67 | def generate(self, state): 68 | count = random.randint(1, math.pow(2, random.randint(1, DharmaConst.MAX_REPEAT_POWER))) 69 | strings = [self.parent.eval(self.repeat, state) for _ in range(count)] 70 | if self.nodups: 71 | strings = list(set(strings)) 72 | return self.separator.join(strings) 73 | 74 | 75 | class MetaChoice: 76 | """Grammar extension method which chooses an item out of a list randomly.""" 77 | 78 | def __init__(self, choices, parent): 79 | self.parent = parent 80 | self.choices = choices 81 | self.choices = [x.strip() for x in self.choices.split(",")] 82 | 83 | def generate(self, state): 84 | return random.choice(self.choices) 85 | 86 | 87 | class MetaRange: 88 | """Grammar extension method which generates a random value between a range of values |a| and |b|.""" 89 | 90 | def __init__(self, a, b, parent): 91 | self.parent = parent 92 | self.base = None 93 | # Type identification 94 | if a is None or b is None: 95 | logging.error("%s: Malformed 'range' meta", parent.id()) 96 | sys.exit(-1) 97 | if self._is_char(a) and self._is_char(b): 98 | self.a, self.b = ord(a), ord(b) 99 | self.fmt = "c" 100 | return 101 | if self._is_float(a) and self._is_float(b): 102 | type_a, type_b = float, float 103 | self.fmt = "f" 104 | else: 105 | type_a, type_b = int, int 106 | self.fmt = "i" 107 | if self._is_hex(a) and self._is_hex(b): 108 | self.base = 16 109 | else: 110 | self.base = 0 111 | # Type verification 112 | if type_a != type_b: 113 | logging.error("%s: Mismatch in 'range' meta %s/%s in %s", 114 | parent.id(), type_a.__name__, type_b.__name__, parent.ident) 115 | sys.exit(-1) 116 | # Type construction 117 | try: 118 | if self.base: 119 | self.a, self.b = type_a(a, self.base), type_b(b, self.base) 120 | else: 121 | self.a, self.b = type_a(a), type_b(b) 122 | except ValueError: 123 | logging.error("%d: Conversion error %s in 'range' meta", parent.id(), type_b.__name__) 124 | sys.exit(-1) 125 | 126 | def _is_char(self, x): 127 | return len(x) == 1 128 | 129 | def _is_float(self, x): 130 | return "." in x 131 | 132 | def _is_hex(self, x): 133 | return "0x" in x 134 | 135 | def generate(self, state): 136 | if self.fmt == "c": 137 | return "%c" % random.randint(self.a, self.b) 138 | if self.fmt == "f": 139 | return "%g" % random.uniform(self.a, self.b) 140 | if self.fmt == "i": 141 | if self.base == 16: 142 | return "%x" % random.randint(self.a, self.b) 143 | return "%d" % random.randint(self.a, self.b) 144 | return None 145 | -------------------------------------------------------------------------------- /dharma/core/websocket.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import base64 5 | import email.message 6 | import email.parser 7 | import hashlib 8 | import logging 9 | import socket 10 | import struct 11 | import sys 12 | import json 13 | 14 | try: 15 | # python 3 16 | from socketserver import BaseRequestHandler, TCPServer, ThreadingMixIn 17 | except ImportError: 18 | # python 2 19 | from SocketServer import BaseRequestHandler, TCPServer, ThreadingMixIn 20 | 21 | 22 | class BaseWebSocketHandler(BaseRequestHandler): 23 | """Base class for WebSocket server.""" 24 | 25 | REQUEST_TIMEOUT = 0.01 26 | _OPCODES = { 27 | 0: 'continue', 28 | 1: 'text', 29 | 2: 'binary', 30 | 8: 'close', 31 | 9: 'ping', 32 | 10: 'pong' 33 | } 34 | 35 | def handle(self): # pylint: disable=too-many-branches,too-many-statements 36 | self.request.settimeout(self.REQUEST_TIMEOUT) 37 | str_t = str if sys.version_info[0] == 3 else lambda a, b: str(a).encode(b) 38 | while not self.should_close(): 39 | try: 40 | _, headers = str_t(self.request.recv(1024), 'ascii').split('\r\n', 1) 41 | break 42 | except socket.timeout: 43 | # time.sleep(0.01) 44 | # print 'timeout 29' 45 | continue 46 | headers = email.parser.HeaderParser().parsestr(headers) 47 | # TODO(jschwartzentruber): validate request/headers 48 | hresponse = hashlib.sha1(headers['sec-websocket-key'].encode('ascii')) 49 | hresponse.update(b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11') 50 | resp = email.message.Message() 51 | resp.add_header('Upgrade', 'websocket') 52 | resp.add_header('Connection', 'Upgrade') 53 | resp.add_header('Sec-WebSocket-Accept', str_t(base64.b64encode(hresponse.digest()), 'ascii')) 54 | resp = resp.as_string(unixfrom=False).replace('\n', '\r\n') 55 | self.request.sendall('HTTP/1.1 101 Switching Protocols\r\n{}'.format(resp).encode('ascii')) 56 | self.open() 57 | buf = None 58 | buf_op = None 59 | try: 60 | while not self.should_close(): 61 | try: 62 | data = struct.unpack('BB', self.request.recv(2)) 63 | except socket.timeout: 64 | # no data 65 | # time.sleep(0.01) 66 | # print 'timeout 51' 67 | continue 68 | except struct.error: 69 | break # chrome doesn't send a close-frame 70 | fin, mask = bool(data[0] & 0x80), bool(data[1] & 0x80) 71 | opcode = self._OPCODES[data[0] & 0xF] 72 | if opcode == 'close': 73 | break 74 | if opcode == 'pong': 75 | self.on_pong() 76 | continue 77 | length = data[1] & 0x7F 78 | if length == 126: 79 | length = struct.unpack('!H', self.request.recv(2))[0] 80 | elif length == 127: 81 | length = struct.unpack('!Q', self.request.recv(8))[0] 82 | mask = bytearray(self.request.recv(4)) if mask else None 83 | data = bytearray(self.request.recv(length)) 84 | if mask is not None: 85 | data = bytearray((b ^ mask[i % 4]) for (i, b) in enumerate(data)) 86 | if opcode == 'continue': 87 | assert buf is not None 88 | opcode = buf_op 89 | elif opcode == 'ping': 90 | self._send(10, data) 91 | continue 92 | elif buf is not None: 93 | logging.warning('Received a new frame while waiting for another to finish, ' 94 | 'discarding %u bytes of %u', len(buf), buf_op) 95 | buf = buf_op = None 96 | if opcode == 'text': 97 | data = str_t(data, 'utf8') 98 | elif opcode != 'binary': 99 | logging.warning('Unknown websocket opcode "%s"', opcode) 100 | continue 101 | if buf is None: 102 | buf = data 103 | buf_op = opcode 104 | else: 105 | buf += data 106 | if fin: 107 | self.on_message(buf) 108 | buf = buf_op = None 109 | finally: 110 | self.on_close() 111 | 112 | def finish(self): 113 | pass 114 | 115 | def _send(self, opcode, data): 116 | length = len(data) 117 | out = bytearray() 118 | out.append(0x80 | opcode) 119 | if length <= 125: 120 | out.append(length) 121 | elif length <= 65535: 122 | out.append(126) 123 | out.extend(struct.pack('!H', length)) 124 | else: 125 | out.append(127) 126 | out.extend(struct.pack('!Q', length)) 127 | if length: 128 | out.extend(data) 129 | self.request.sendall(out) 130 | 131 | # Below is the partial API from tornado.websocket.WebSocketHandler 132 | def ping(self): 133 | self._send(9, '') 134 | 135 | def should_close(self): 136 | """When this returns true, the message loop will exit.""" 137 | return False 138 | 139 | def write_message(self, message, binary=False): 140 | if binary: 141 | self._send(2, message) 142 | else: 143 | self._send(1, message.encode('utf8')) 144 | 145 | # Event handlers 146 | def on_pong(self): 147 | pass 148 | 149 | def open(self): 150 | pass 151 | 152 | def on_close(self): 153 | pass 154 | 155 | def on_message(self, message): 156 | raise NotImplementedError('Required method on_message() not implemented.') 157 | 158 | 159 | class DharmaTCPServer(ThreadingMixIn, TCPServer): 160 | daemon_threads = True 161 | allow_reuse_address = True 162 | 163 | 164 | class DharmaWebSocketServer: 165 | def __init__(self, machine, address=("127.0.0.1", 9090)): 166 | self.server = None 167 | self.machine = machine 168 | self.address = address 169 | 170 | def start(self): 171 | machine = self.machine 172 | 173 | class DharmaWebSocketHandler(BaseWebSocketHandler): 174 | def on_message(self, message): 175 | msg = json.loads(message) 176 | if msg.get("status") == "open": 177 | logging.info("WebSocket connection opened.") 178 | if msg.get("status") in ("open", "success"): 179 | self.write_message(machine.generate_content()) 180 | elif msg.get("status") == "closed": 181 | logging.info("WebSocket connection closed.") 182 | else: 183 | logging.error("WebSocket received unexpected message %r", msg) 184 | 185 | try: 186 | self.server = DharmaTCPServer(self.address, DharmaWebSocketHandler) 187 | except Exception as error: # pylint: disable=broad-except 188 | logging.error("Unable to start WebSocket server: %s", error) 189 | return 190 | logging.info("Socket server is listening at %s:%d", *self.address) 191 | self.server.serve_forever() 192 | 193 | def stop(self): 194 | if self.server is None: 195 | return 196 | try: 197 | logging.info("Stopping WebSocket server.") 198 | self.server.shutdown() 199 | except Exception as error: # pylint: disable=broad-except 200 | logging.error("Unable to shutdown WebSocket server: %s", error) 201 | -------------------------------------------------------------------------------- /dharma/dharma.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | import argparse 6 | import logging 7 | import os 8 | import random 9 | import struct 10 | import sys 11 | 12 | from .__version__ import __version__, __title__ 13 | from .core.dharma import DharmaMachine 14 | from .core.websocket import DharmaWebSocketServer 15 | 16 | 17 | class DharmaCommandLine: 18 | @staticmethod 19 | def parse_args(): 20 | parser = argparse.ArgumentParser( 21 | add_help=False, 22 | description='Dharma Runtime', 23 | epilog='The exit status is 0 for non-failures and -1 for failures.', 24 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 25 | prog=__title__ 26 | ) 27 | 28 | m = parser.add_argument_group('mandatory arguments') 29 | m.add_argument('-grammars', metavar='file', type=argparse.FileType(), nargs='+', required=True, 30 | help='input grammars') 31 | 32 | o = parser.add_argument_group('optional arguments') 33 | o.add_argument('-count', metavar='#', type=int, default=1, help='number of test cases') 34 | o.add_argument('-format', metavar='ext', default='html', help='format of test cases') 35 | o.add_argument('-h', '-help', '--help', action='help', help=argparse.SUPPRESS) 36 | o.add_argument('-logging', metavar='#', default=10, type=int, choices=range(10, 60, 10), 37 | help='verbosity level of logging') 38 | o.add_argument('-prefix', metavar='file', type=argparse.FileType(), help='prefix data') 39 | o.add_argument('-recursion-limit', metavar='#', type=int, default=20000, 40 | help='max python recursion limit') 41 | o.add_argument('-seed', metavar='#', type=int, 42 | help='seed value for random, os.urandom will be used if not specified') 43 | o.add_argument('-server', action='store_true', help='run in server mode') 44 | o.add_argument('-server-host', metavar='host', type=str, default='127.0.0.1', help='server address') 45 | o.add_argument('-server-port', metavar='#', type=int, default=9090, help='server port') 46 | o.add_argument('-settings', metavar='file', type=argparse.FileType(), 47 | default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.py'), 48 | help='') 49 | o.add_argument('-storage', metavar='path', help='folder for test cases') 50 | o.add_argument('-suffix', metavar='file', type=argparse.FileType(), help='suffix data') 51 | o.add_argument('-template', metavar='file', type=argparse.FileType(), help='template data') 52 | o.add_argument('-version', action='version', version='%(prog)s {}'.format(__version__), 53 | help=argparse.SUPPRESS) 54 | 55 | return parser.parse_args() 56 | 57 | @classmethod 58 | def main(cls): 59 | args = cls.parse_args() 60 | sys.setrecursionlimit(args.recursion_limit) 61 | logging.basicConfig(format='[Dharma] %(asctime)s %(levelname)s: %(message)s', level=args.logging) 62 | if args.seed is None: 63 | args.seed = struct.unpack('q', os.urandom(8))[0] 64 | random.seed(args.seed) 65 | logging.info('Machine random seed: %d', args.seed) 66 | prefix_data = '' if not args.prefix else args.prefix.read() 67 | suffix_data = '' if not args.suffix else args.suffix.read() 68 | template_data = '' if not args.template else args.template.read() 69 | dharma = DharmaMachine(prefix_data, suffix_data, template_data) 70 | dharma.process_settings(args.settings) 71 | dharma.process_grammars(args.grammars) 72 | if args.storage: 73 | dharma.generate_testcases(args.storage, args.format, args.count) 74 | elif args.server: 75 | server = DharmaWebSocketServer(dharma, (args.server_host, args.server_port)) 76 | try: 77 | server.start() 78 | except KeyboardInterrupt: 79 | pass 80 | finally: 81 | server.stop() 82 | else: 83 | for _ in range(args.count): 84 | print(dharma.generate_content()) 85 | return 0 86 | -------------------------------------------------------------------------------- /dharma/grammars/canvas2d.dg: -------------------------------------------------------------------------------- 1 | %%% Canvas2D API 2 | 3 | %%% Specification: http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas/ 4 | %%% Firefox WebIDL: https://github.com/mozilla/gecko-dev/blob/inbound/dom/webidl/CanvasRenderingContext2D.webidl 5 | %%% Blink WebIDL: https://github.com/mirrors/blink/tree/master/Source/core/html/canvas 6 | 7 | %%% ########################################################################### 8 | 9 | %const% VARIANCE_MAX := 10 10 | %const% VARIANCE_TEMPLATE := "try { %s } catch (e) { }" 11 | %const% MAX_REPEAT_POWER := 4 12 | 13 | %%% ########################################################################### 14 | 15 | %section% := value 16 | 17 | Canvas2DContextAttributeOptions := 18 | alpha:+common:bool+ 19 | willReadFrequently:+common:bool+ 20 | 21 | Canvas2DContextAttributes := 22 | { +Canvas2DContextAttributeOptions+ } 23 | { %repeat%(+Canvas2DContextAttributeOptions+, ", ") } 24 | 25 | canvasWindingRule := 26 | nonzero 27 | evenodd 28 | 29 | globalCompositeOperation := 30 | source-atop 31 | source-in 32 | source-out 33 | source-over 34 | destination-atop 35 | destination-in 36 | destination-out 37 | destination-over 38 | lighter 39 | copy 40 | exclusion 41 | 42 | lineJoin := 43 | butt 44 | round 45 | square 46 | 47 | lineCap := 48 | round 49 | bevel 50 | miter 51 | 52 | textAlign := 53 | start 54 | end 55 | left 56 | right 57 | center 58 | 59 | textBaseline := 60 | top 61 | hanging 62 | middle 63 | alphabetic 64 | ideographic 65 | bottom 66 | 67 | dash_array_values := 68 | 0 69 | 1 70 | 71 | createPattern_params := 72 | repeat 73 | repeat-x 74 | repeat-y 75 | no-repeat 76 | 77 | hitRegionOptionValues := 78 | id:"!hitRegion!" 79 | control: null 80 | 81 | hitRegionOptions := 82 | { +hitRegionOptionValues+ } 83 | { %repeat%(+hitRegionOptionValues+, ", ") } 84 | 85 | canvasAttributes := 86 | !context!.canvas.width=+common:number+; 87 | !context!.canvas.height=+common:number+; 88 | !context!.canvas.mozOpaque=+common:bool+; 89 | 90 | contextAttributes := 91 | !context!.globalAlpha=+common:number+; 92 | !context!.globalCompositeOperation="+globalCompositeOperation+"; 93 | !context!.strokeStyle="+common:color+"; 94 | !context!.fillStyle="+common:color+"; 95 | !context!.shadowOffsetX=+common:number+; 96 | !context!.shadowOffsetY=+common:number+; 97 | !context!.shadowBlur="+common:number+"; 98 | !context!.shadowColor="+common:color+"; 99 | !context!.lineWidth="+common:number+"; 100 | !context!.lineCap="+lineCap+"; 101 | !context!.lineJoin="+lineJoin+"; 102 | !context!.lineDashOffset=+common:number+; 103 | !context!.miterLimit=+common:number+; 104 | !context!.font="+common:font+"; 105 | !context!.textAlign="+textAlign+"; 106 | !context!.textBaseline="+textBaseline+"; 107 | 108 | Gecko_contextAttributes := 109 | !context!.mozImageSmoothingEnabled=+common:bool+; 110 | !context!.mozDashOffset=+common:number+; 111 | !context!.mozFillRule="+canvasWindingRule+"; 112 | !context!.mozDash=!dash_array!; 113 | !context!.mozCurrentTransform=!currentTransform_array!; 114 | !context!.mozCurrentTransformInverse=!currentTransformInverse_array!; 115 | 116 | WebKit_contextAttributes := 117 | !context!.webkitImageSmoothingEnabled=+common:bool+; 118 | 119 | textMetricReadAttributes := 120 | !textMetrics!.fontBoundingBoxAscent 121 | !textMetrics!.fontBoundingBoxDescent 122 | !textMetrics!.actualBoundingBoxAscent 123 | !textMetrics!.actualBoundingBoxDescent 124 | !textMetrics!.emHeightAscent 125 | !textMetrics!.emHeightDescent 126 | !textMetrics!.hangingBaseline 127 | !textMetrics!.alphabeticBaseline 128 | !textMetrics!.ideographicBaseline 129 | 130 | contextMethods := 131 | !context!.save(); 132 | !context!.restore(); 133 | !context!.scale(+common:number+, +common:number+); 134 | !context!.rotate(+common:number+); 135 | !context!.translate(+common:number+, +common:number+); 136 | !context!.transform(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 137 | !context!.setTransform(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 138 | !context!.clearRect(+common:number+, +common:number+, +common:number+, +common:number+); 139 | !context!.fillRect(+common:number+, +common:number+, +common:number+, +common:number+); 140 | !context!.strokeRect(+common:number+, +common:number+, +common:number+, +common:number+); 141 | !context!.beginPath(); 142 | !context!.stroke(); 143 | !context!.stroke(!path2D!); 144 | !context!.fill("+canvasWindingRule+"); 145 | !context!.fill(!path2D!, "+canvasWindingRule+"); 146 | !context!.clip("+canvasWindingRule+"); 147 | !context!.clip(!path2D!, "+canvasWindingRule+"); 148 | !context!.isPointInPath(+common:number+, +common:number+, "+canvasWindingRule+"); 149 | !context!.isPointInPath(!path2D!, +common:number+, +common:number+, "+canvasWindingRule+"); 150 | !context!.isPointInStroke(+common:number+, +common:number+); 151 | !context!.isPointInStroke(!path2D!, +common:number+, +common:number+); 152 | !context!.fillText("+common:text+", +common:number+, +common:number+); 153 | !context!.strokeText("+common:text+", +common:number+, +common:number+); 154 | !context!.strokeText("+common:text+", +common:number+, +common:number+, +common:number+); 155 | !context!.measureText("+common:text+"); 156 | !context!.closePath(); 157 | !context!.moveTo(+common:number+, +common:number+); 158 | !context!.lineTo(+common:number+, +common:number+); 159 | !context!.quadraticCurveTo(+common:number+, +common:number+, +common:number+, +common:number+); 160 | !context!.bezierCurveTo(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 161 | !context!.rect(+common:number+, +common:number+, +common:number+, +common:number+); 162 | !context!.arcTo(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 163 | !context!.arc(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:bool+); 164 | !context!.setLineDash(!dash_array!); 165 | !context!.getLineDash(); 166 | !context!.addHitRegion(!hitRegion!); 167 | !context!.removeHitRegion(!hitRegion!.id); 168 | !context!.drawFocusIfNeeded(document.documentElement); 169 | !context!.drawCustomFocusRing(document.documentElement); 170 | !canvasGradient!.addColorStop(+common:decimal_number+, "+common:color+"); 171 | !context!.drawImage(!imageElement!, +common:number+, +common:number+); 172 | !context!.drawImage(!imageElement!, +common:number+, +common:number+, +common:number+, +common:number+); 173 | !context!.drawImage(!imageElement!, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 174 | !context!.putImageData(!imageData!, +common:number+, +common:number+); 175 | !context!.putImageData(!imageData!, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 176 | !context!.canvas.toDataURL(+common:mime_type+); 177 | !context!.canvas.toBlob(function(b){n=document.createElement("img"),u=URL.createObjectURL(b);n.onload=function(){URL.revokeObjectURL(u);};n.src=u;document.body.appendChild(n);}, +common:mime_type+, +common:number+); 178 | 179 | Gecko_contextMethods := 180 | !context!.canvas.mozGetAsFile("+common:text+", +common:mime_type+); 181 | 182 | Blink_contextMethods := 183 | !context!.setAlpha(+common:number+); 184 | !context!.setCompositeOperation("+globalCompositeOperation+"); 185 | !context!.setLineWidth(+common:number+); 186 | !context!.setLineCap("+lineCap+"); 187 | !context!.setLineJoin("+lineJoin+"); 188 | !context!.setMiterLine(+common:number+); 189 | !context!.setStrokeColor("+common:color+", +common:number+); 190 | !context!.setStrokeColor(+common:number+, +common:number+); 191 | !context!.setStrokeColor(+common:number+, +common:number+, +common:number+, +common:number+); 192 | !context!.setStrokeColor(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 193 | !context!.setFillColor("+common:color+", +common:number+); 194 | !context!.setFillColor(+common:number+, +common:number+); 195 | !context!.setFillColor(+common:number+, +common:number+, +common:number+, +common:number+); 196 | !context!.setFillColor(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 197 | !context!.clearShadow(); 198 | !context!.setShadow(+common:number+, +common:number+, +common:number+, "+common:color+", +common:number+); 199 | !context!.setShadow(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 200 | !context!.setShadow(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 201 | !context!.setShadow(+common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+, +common:number+); 202 | !context!.webkitGetImageDataHD(+common:number+, +common:number+, +common:number+, +common:number+); 203 | !context!.webkitPutImageDataHD(); 204 | !context!.getContextAttributes(); 205 | 206 | Canvas2D := 207 | +canvasAttributes+ 208 | +contextAttributes+ 209 | +contextMethods+ 210 | +Gecko_contextAttributes+ 211 | +Gecko_contextMethods+ 212 | +textMetricReadAttributes+; 213 | %%% +WebKit_contextAttributes+ 214 | %%% +Blink_contextMethods+ 215 | 216 | wrapper := 217 | try { +Canvas2D+ } catch(e) {} 218 | 219 | %%% ########################################################################### 220 | 221 | %section% := variable 222 | 223 | context := 224 | canvas=document.createElement("canvas"); (document.body || document.documentElement).appendChild(canvas); @context@=canvas.getContext('2d'); 225 | canvas=document.createElement("canvas"); (document.body || document.documentElement).appendChild(canvas); @context@=canvas.getContext('2d', +Canvas2DContextAttributes+); 226 | 227 | imageElement := 228 | x=document.createElement("img"); x.src="file://%uri%(images)"; @imageElement@=x; 229 | 230 | dash_array := 231 | %%% Prevent static values in the grammar by using undefiend.js library functions. 232 | x=new Array(%range%(0-32)); for(var i=0; i Create a Buffer 298 | %%% Permutation -> Functions from Buffer to Buffer 299 | %%% Operation -> Functions from Buffer to other types 300 | 301 | main := 302 | var smalloc = require('smalloc'); +definition+ +permutation+ +operation+ 303 | -------------------------------------------------------------------------------- /dharma/grammars/test.dg: -------------------------------------------------------------------------------- 1 | %const% VARIANCE_MIN := 4 2 | %const% VARIANCE_MAX := 12 3 | 4 | %section% := value 5 | 6 | dictContent := 7 | a: null 8 | b: {} 9 | 10 | dictObject := 11 | {+dictContent+} 12 | 13 | testStmnt := 14 | !pc_l! = !pc_r! 15 | !pc_r! = !pc_l! 16 | 17 | %section% := variable 18 | 19 | pc_l := 20 | let @pc_l@ = new +dictObject+; 21 | 22 | pc_r := 23 | let @pc_r@ = new +dictObject+; 24 | 25 | %section% := variance 26 | 27 | main := 28 | +testStmnt+ 29 | 30 | -------------------------------------------------------------------------------- /dharma/grammars/url.dg: -------------------------------------------------------------------------------- 1 | %%% Uniform Resource Locator grammar 2 | 3 | %%% Based on the official URL BNF: 4 | %%% http://www.w3.org/Addressing/URL/5_BNF.html 5 | %%% http://www.w3.org/Protocols/rfc822/#z7 6 | 7 | %%% ############################################ 8 | %section% := value 9 | 10 | url := 11 | +httpaddress+ 12 | +ftpaddress+ 13 | +newsaddress+ 14 | +nntpaddress+ 15 | +prosperoaddress+ 16 | +telnetaddress+ 17 | +gopheraddress+ 18 | +waisaddress+ 19 | +mailtoaddress+ 20 | +midaddress+ 21 | +cidaddress+ 22 | 23 | scheme := 24 | +ialpha+ 25 | 26 | httpaddress := 27 | http://+hostport+ 28 | http://+hostport+/+path+ 29 | http://+hostport+/+path+?+search+ 30 | 31 | ftpaddress := 32 | ftp://+login+/+path+ 33 | ftp://+login+/+path+;+ftptype+ 34 | 35 | afsaddress := 36 | afs://+cellname+/+path+ 37 | 38 | newsaddress := 39 | news:+groupart+ 40 | 41 | nntpaddress := 42 | nntp:+group+/+digit+ 43 | 44 | midaddress := 45 | mid:+addrspec+ 46 | 47 | cidaddress := 48 | cid:+addrspec+ 49 | 50 | mailtoaddress := 51 | mailto:+xalphas+@+hostname+ 52 | 53 | waisaddress := 54 | +waisindex+ 55 | +waisdoc+ 56 | 57 | waisindex := 58 | wais://+hostport+/+database+ 59 | wais://+hostport+/+database+?+search+ 60 | 61 | waisdoc := 62 | wais://+hostport+/+database+/+wtype+/+wpath+ 63 | 64 | wpath := 65 | +digits+=+path+; 66 | +digits+=+path+;+wpath+ 67 | 68 | groupart := 69 | * 70 | +group+ 71 | +article+ 72 | 73 | group := 74 | +ialpha+ 75 | +ialpha+.+group+ 76 | 77 | article := 78 | +xalphas+@+host+ 79 | 80 | database := 81 | +xalphas+ 82 | 83 | wtype := 84 | +xalphas+ 85 | 86 | prosperoaddress := 87 | +prosperolink+ 88 | 89 | prosperolink := 90 | prospero://+hostport+/+hsoname+ 91 | prospero://+hostport+/+hsoname+%00+version+ 92 | prospero://+hostport+/+hsoname+%00+version++attributes+ 93 | 94 | hsoname := 95 | +path+ 96 | 97 | version := 98 | +digits+ 99 | 100 | attributes := 101 | +attribute+ 102 | +attribute++attributes+ 103 | 104 | attribute := 105 | +alphanums+ 106 | 107 | telnetaddress := 108 | telnet://+login+ 109 | 110 | gopheraddress := 111 | gopher://+hostport+ 112 | gopher://+hostport+/+gtype+ 113 | gopher://+hostport+/+gtype++gcommand+ 114 | 115 | login := 116 | +hostport+ 117 | +user+@+hostport+ 118 | +user+:+password+@+hostport+ 119 | 120 | hostport := 121 | +host+ 122 | +host+:+port+ 123 | 124 | host := 125 | +hostname+ 126 | +hostnumber+ 127 | 128 | ftptype := 129 | A+formcode+ 130 | E+formcode+ 131 | I 132 | L+digits+ 133 | 134 | formcode := 135 | N 136 | T 137 | C 138 | 139 | cellname := 140 | +hostname+ 141 | 142 | hostname := 143 | +ialpha+ 144 | +ialpha+.+hostname+ 145 | 146 | hostnumber := 147 | +digits+.+digits+.+digits+.+digits+ 148 | 149 | port := 150 | +digits+ 151 | 152 | gcommand := 153 | +path+ 154 | 155 | path := 156 | +segment+ 157 | +segment+/+path+ 158 | 159 | segment := 160 | +xpalphas+ 161 | 162 | search := 163 | +xalphas+ 164 | +xalphas+++search+ 165 | 166 | user := 167 | +alphanum2+ 168 | +alphanum2++user+ 169 | 170 | password := 171 | +alphanum2+ 172 | +alphanum2++password+ 173 | 174 | fragmentid := 175 | +xalphas+ 176 | 177 | gtype := 178 | +xalpha+ 179 | 180 | alphanum2 := 181 | +alpha+ 182 | +digit+ 183 | - 184 | _ 185 | . 186 | + 187 | 188 | xalpha := 189 | +alpha+ 190 | +digit+ 191 | +safe+ 192 | +extra+ 193 | +escape+ 194 | 195 | xalphas := 196 | +xalpha+ 197 | +xalpha++xalphas+ 198 | 199 | xpalpha := 200 | +xalpha+ 201 | + 202 | 203 | xpalphas := 204 | +xpalpha+ 205 | +xpalpha++xpalphas+ 206 | 207 | ialpha := 208 | +alpha+ 209 | +alpha++xalphas+ 210 | 211 | alpha := 212 | +common:character+ 213 | 214 | digit := 215 | +common:digit+ 216 | 217 | safe := 218 | $ 219 | - 220 | _ 221 | @ 222 | . 223 | & 224 | + 225 | - 226 | 227 | extra := 228 | ! 229 | * 230 | " 231 | ' 232 | ( 233 | ) 234 | , 235 | 236 | reserved := 237 | = 238 | ; 239 | / 240 | # 241 | ? 242 | : 243 | 244 | escape := 245 | %+common:hex++common:hex+ 246 | 247 | national := 248 | { 249 | } 250 | | 251 | [ 252 | ] 253 | \ 254 | ^ 255 | ~ 256 | 257 | punctuation := 258 | < 259 | > 260 | 261 | digits := 262 | +digit+ 263 | +digit++digits+ 264 | 265 | alphanum := 266 | +alpha+ 267 | +digit+ 268 | 269 | alphanums := 270 | +alphanum+ 271 | +alphanum++alphanums+ 272 | 273 | addrspec := 274 | +localpart+@+domain+ 275 | 276 | localpart := 277 | +words+ 278 | 279 | domain := 280 | +subdomains+ 281 | 282 | subdomains := 283 | +subdomain+ 284 | +subdomain++subdomains+ 285 | 286 | subdomain := 287 | +domainref+ 288 | +domainliteral+ 289 | 290 | domainref := 291 | +atom+ 292 | 293 | words := 294 | +word+ 295 | +word++words+ 296 | 297 | word := 298 | +atom+ 299 | +quotedpair+ 300 | 301 | atom := 302 | %range%(!-') 303 | %range%(*-+) 304 | %range%(/-9) 305 | %range%(A-Z) 306 | %range%(^-~) 307 | - 308 | = 309 | ? 310 | 311 | domainliteral := 312 | [+domaincon+] 313 | 314 | domaincon := 315 | +atom+ 316 | +quotedpair+ 317 | +domaincon+ 318 | 319 | quotedpair := 320 | "+common:asciichar+" 321 | 322 | %%% ############################################ 323 | %section% := variance 324 | 325 | main := 326 | +url+ 327 | -------------------------------------------------------------------------------- /dharma/grammars/var/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dharma WebSocket Publisher 5 | 6 | 7 | 8 | 9 | 58 | 59 | -------------------------------------------------------------------------------- /dharma/grammars/var/js/help.js: -------------------------------------------------------------------------------- 1 | // Various ready-made functions for grammars using JavaScript 2 | 3 | var helper = { 4 | // ArrayBuffer to String 5 | ab2str: function (buf) { 6 | return String.fromCharCode.apply(null, new Uint8Array(buf)); 7 | }, 8 | 9 | // String to ArrayBuffer 10 | str2ab: function (str) { 11 | var buf = new ArrayBuffer(str.length), 12 | bufView = new Uint8Array(buf); 13 | for (var i=0, strLen=str.length; i 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dharma/grammars/wasm.dg: -------------------------------------------------------------------------------- 1 | %%% ## Author: Patrick Ventuzelo 2 | %%% ## Twitter: https://twitter.com/Pat_Ventuzelo 3 | %%% ## Blogpost title: Fuzzing JavaScript WebAssembly APIs using Dharma/Domato (Chrome/V8) 4 | %%% ## Blogpost link: https://webassembly-security.com/fuzzing-wasm-javascript-dharma-chrome-v8/ 5 | %%% ## Description: Simple grammar for fuzzing WebAssembly JavaScript APIs in JS engines. 6 | 7 | %%% ########################################################### 8 | %%% ############# WebAssembly JavaScript APIs ################# 9 | %%% ## APIs Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly 10 | %%% ########################################################### 11 | 12 | %const% VARIANCE_MIN := 200 13 | %const% VARIANCE_MAX := 1000 14 | %const% VARIABLE_MIN := 4 15 | %const% VARIABLE_MAX := 15 16 | 17 | %%% #################################################### 18 | %%% #################################################### 19 | %%% ####### VALUE SECTION 20 | %%% ####### only definition of value 21 | %%% ####### you can use & assign value: +value+ 22 | %%% ####### you can use variable: !variable!.bar(); 23 | %%% #################################################### 24 | %%% #################################################### 25 | 26 | %section% := value 27 | 28 | %%% ######## WebAssembly.Global() values ########### 29 | %%% ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global 30 | 31 | WasmTypeInt := 32 | "i32" 33 | "i64" 34 | 35 | WasmTypeFloat := 36 | "f32" 37 | "f64" 38 | 39 | GlobalDescriptorFloat := 40 | {value: +WasmTypeFloat+, mutable: +common:bool+} 41 | 42 | GlobalDescriptorInt := 43 | {value: +WasmTypeInt+, mutable: +common:bool+} 44 | 45 | GlobalParameters := 46 | +GlobalDescriptorFloat+, +common:decimal_number+ 47 | +GlobalDescriptorInt+, +common:integer+ 48 | 49 | GlobalWasmMethods := 50 | !globalwasm!.value = !number!; 51 | !globalwasm!.value = +common:number+; 52 | !number! = !globalwasm!.value; 53 | !number! = !globalwasm!.valueOf(); 54 | !string! = !globalwasm!.toString(); 55 | %%% delete !globalwasm!; 56 | 57 | %%% ############# WebAssembly.Table() ################# 58 | %%% ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table 59 | 60 | TableDescriptor := 61 | {element: "anyfunc", initial: %range%(0-42)} 62 | {element: "anyfunc", initial: %range%(0-42), maximum: %range%(43-99)} 63 | 64 | TableWasmMethods := 65 | !number! = !tablewasm!.length - 1; 66 | !funcRef! = !tablewasm!.get(%range%(0-42)); 67 | !funcRef! = !tablewasm!.get(!number!); 68 | !number! = !tablewasm!.grow(%range%(0-42)); 69 | !number! = !tablewasm!.grow(!number!); 70 | !tablewasm!.set(%range%(0-42), !funcRef!); 71 | !tablewasm!.set(!number!, !funcRef!); 72 | %%% delete !tablewasm!; 73 | 74 | %%% ############# WebAssembly.Memory() ################# 75 | %%% ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory 76 | 77 | memoryDescriptor := 78 | {initial: %range%(0-9)} 79 | {initial: %range%(0-9), maximum: %range%(9-999)} 80 | 81 | MemoryWasmMethods := 82 | !number! = !memorywasm!.buffer.length - 1; 83 | !array! = !memorywasm!.buffer; 84 | !number! = !memorywasm!.grow(!number!); 85 | !number! = !memorywasm!.grow(%range%(0-9)); 86 | !memorywasm!.buffer[%range%(0-9)] = %range%(0-9); 87 | !memorywasm!.buffer[!number!] = %range%(0-9); 88 | !memorywasm!.buffer[!number!] = !number!; 89 | for (var i = 0; i < !memorywasm!.buffer.length; i++) {!memorywasm!.buffer[i] = %range%(0-9);} 90 | for (var i = 0; i < !memorywasm!.buffer.length; i++) {!memorywasm!.buffer[i] = +common:number+;} 91 | %%% delete !memorywasm!; 92 | 93 | %%% ############# WebAssembly.Module() ################# 94 | %%% ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module 95 | 96 | %%% hello world module with main function exported 97 | bufferSource := 98 | new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]) 99 | 100 | SectionName := 101 | "name" 102 | "" 103 | "debug" 104 | 105 | ModuleWasmMethods := 106 | !array! = WebAssembly.Module.customSections(!modulewasm!, +SectionName+); 107 | !array! = WebAssembly.Module.customSections(!modulewasm!, !string!); 108 | !array! = WebAssembly.Module.exports(!modulewasm!); 109 | !string! = WebAssembly.Module.exports(!modulewasm!).toString(); 110 | !array! = WebAssembly.Module.imports(!modulewasm!); 111 | !string! = WebAssembly.Module.imports(!modulewasm!).toString(); 112 | !modulewasm! = new WebAssembly.Module(+bufferSource+); 113 | %%% delete !modulewasm!; 114 | 115 | %%% ############# WebAssembly.Instance() ################# 116 | %%% ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance 117 | 118 | importObject := 119 | {} 120 | { js: { !globalwasm! } } 121 | { js: { tbl: !tablewasm! } } 122 | { js: { mem: !memorywasm! } } 123 | 124 | InstanceWasmMethods := 125 | !instancewasm!.exports.main(); 126 | !memorywasm! = !instancewasm!.exports.memory; 127 | !instancewasm! = new WebAssembly.Instance(!modulewasm!, +importObject+); 128 | %%% delete !instancewasm!; 129 | %%% TODO instantiateStreaming 130 | 131 | %%% ############# WebAssembly Methods ################# 132 | %%% ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly#Methods 133 | 134 | WasmMethods := 135 | !bool! = WebAssembly.validate(+bufferSource+); 136 | WebAssembly.compile(+bufferSource+); 137 | %%% TODO compileStreaming 138 | 139 | 140 | %%% ############# Wrapper ################# 141 | 142 | wrapper := 143 | +GlobalWasmMethods+ 144 | +TableWasmMethods+ 145 | +MemoryWasmMethods+ 146 | +ModuleWasmMethods+ 147 | +InstanceWasmMethods+ 148 | %%% +WasmMethods+ 149 | 150 | %%% #################################################### 151 | %%% #################################################### 152 | %%% ####### VARIABLE SECTION 153 | %%% ####### only assignement of variable here 154 | %%% ####### ex: @MyVariable@= 5; 155 | %%% #################################################### 156 | %%% #################################################### 157 | 158 | %section% := variable 159 | 160 | bool := 161 | try { var @bool@ = true; } catch(e) {} 162 | try { var @bool@ = false; } catch(e) {} 163 | 164 | number := 165 | try { var @number@ = %range%(0-42); } catch(e) {} 166 | 167 | string := 168 | try { var @string@ = "main"; } catch(e) {} 169 | 170 | array := 171 | try { var @array@ = []; } catch(e) {} 172 | 173 | funcRef := 174 | try { var @funcRef@ = 0; } catch(e) {} 175 | 176 | globalwasm := 177 | try { var @globalwasm@ = new WebAssembly.Global(+GlobalParameters+); } catch(e) {} 178 | 179 | tablewasm := 180 | try { var @tablewasm@ = new WebAssembly.Table(+TableDescriptor+); } catch(e) {} 181 | 182 | memorywasm := 183 | try { var @memorywasm@ = new WebAssembly.Memory(+memoryDescriptor+); } catch(e) {} 184 | 185 | modulewasm := 186 | try { var @modulewasm@ = new WebAssembly.Module(+bufferSource+); } catch(e) {} 187 | 188 | instancewasm := 189 | try { var @instancewasm@ = new WebAssembly.Instance(!modulewasm!, +importObject+); } catch(e) {} 190 | 191 | %%% #################################################### 192 | %%% #################################################### 193 | %%% ####### VARIANCE SECTION 194 | %%% #################################################### 195 | %%% #################################################### 196 | %section% := variance 197 | 198 | main := 199 | try { +wrapper+ } catch(e) {} 200 | -------------------------------------------------------------------------------- /dharma/grammars/xss.dg: -------------------------------------------------------------------------------- 1 | %section% := value 2 | 3 | payload := 4 | +attack+ 5 | 6 | attack := 7 | +attacks_js+ 8 | +outbreak++attacks_html+ 9 | 10 | outbreak := 11 | +quote++bracket_gt+ 12 | +bracket_lt+/+tag_html++bracket_gt+ 13 | +outbreak++bracket_lt+/+tag_html++bracket_gt+ 14 | +bracket_lt+img++quote++quote++quote++bracket_gt+ 15 | 16 | attacks_js := 17 | +quote++space++event+=+quote++payload_attack++quote++bracket_gt+ 18 | +bracket_lt++tag_script++bracket_gt++space++payload_attack++bracket_lt+/+tag_script++bracket_gt+ 19 | +outbreak++bracket_lt++tag_script++bracket_gt++space++payload_attack++bracket_lt+/+tag_script++bracket_gt+ 20 | +bracket_lt++tag_script+/+random_text++space++src_attribute+=+quote+http://xss.rocks/xss.js+quote++bracket_gt++bracket_lt+/+tag_script++bracket_gt+ 21 | +bracket_lt++bracket_lt++tag_script++bracket_gt++payload_attack+;//+bracket_lt++bracket_lt+/+tag_script++bracket_gt+ 22 | +bracket_lt++space++src_attribute+=http://xss.rocks/xss.js?+bracket_lt++tag_html++bracket_gt+ 23 | \+quote++payload_attack+;// 24 | 25 | attacks_html := 26 | +bracket_lt++tag_html++space++event+=+quote++payload_attack++quote++bracket_gt+xss+bracket_lt+/+tag_html++bracket_gt+ 27 | +bracket_lt+img+space++src_attribute+=+src_value++space++event+=+quote++payload_attack++quote++bracket_gt+xss+bracket_lt+/img+bracket_gt+ 28 | +bracket_lt+img+space++src_attribute+=+quote++tag_js+:+payload_attack+;+quote++bracket_gt+ 29 | +bracket_lt+input+space+type=+quote+image+quote++space++src_attribute+=+quote++tag_script+:+payload_attack++quote++bracket_gt+ 30 | +bracket_lt++tag_html++space+background=+quote++tag_js+:+payload_attack++quote++bracket_gt+ 31 | +bracket_lt+style+bracket_gt+li+space+{list-style-image:+space+url(+quote++tag_js+:+payload_attack++quote+}+bracket_lt+/style+bracket_gt++bracket_lt+ul+bracket_gt++bracket_lt+li+bracket_gt++random_text++bracket_lt+/br+bracket_gt+ 32 | +bracket_lt++tag_html++space++event++space+=+payload_attack++bracket_gt+ 33 | +bracket_lt+/+tag_html++space++event++space+=+payload_attack++bracket_gt+ 34 | +bracket_lt++tag_html++space+=+quote+&{+payload_attack+}+quote++bracket_gt+ 35 | +bracket_lt+link+space+rel=+quote+stylesheet+quote++space+href=+quote++tag_js+:+payload_attack+;+quote++bracket_gt+ 36 | +bracket_lt+img+space+style=+quote++random_text+:expr/*+random_text+*/ession(+payload_attack+)+quote++bracket_gt+ 37 | +bracket_lt+style+space+.xss{background-image:url(+quote++tag_js+:+payload_attack++quote+);}+bracket_lt+/style+bracket_gt++bracket_lt++tag_html++space+class=xss+bracket_gt+ 38 | +bracket_lt+style+spacetype=+quote+text/css+quote++bracket_gt+body{background:url(+quote++tag_js+:+payload_attack++quote+)}+bracket_lt+/style+bracket_gt+ 39 | +bracket_lt+meta+space+http-equiv=+quote+refresh+quote++space+content=+quote+0;url=+tag_js+:+payload_attack+;+quote++bracket_gt+ 40 | 41 | src_value := 42 | +space+ 43 | # 44 | / 45 | +random_text+ 46 | 47 | src_attribute := 48 | src 49 | dynsrc 50 | lowsrc 51 | 52 | tag_html := 53 | a 54 | abbr 55 | acronym 56 | address 57 | applet 58 | area 59 | article 60 | aside 61 | audio 62 | b 63 | base 64 | basefont 65 | bdi 66 | bdo 67 | big 68 | blockquote 69 | body 70 | br 71 | button 72 | canvas 73 | caption 74 | center 75 | cite 76 | code 77 | col 78 | colgroup 79 | datalist 80 | dd 81 | del 82 | details 83 | dfn 84 | dialog 85 | dir 86 | div 87 | dl 88 | dt 89 | em 90 | embed 91 | fieldset 92 | figcaption 93 | figure 94 | font 95 | footer 96 | form 97 | frame 98 | frameset 99 | h1 100 | head 101 | header 102 | hr 103 | html 104 | i 105 | iframe 106 | img 107 | input 108 | ins 109 | kbd 110 | keygen 111 | label 112 | legend 113 | li 114 | link 115 | main 116 | map 117 | mark 118 | menu 119 | menuitem 120 | meta 121 | meter 122 | nav 123 | noframes 124 | noscript 125 | object 126 | ol 127 | optgroup 128 | option 129 | output 130 | p 131 | param 132 | picture 133 | pre 134 | progress 135 | q 136 | rp 137 | rt 138 | ruby 139 | s 140 | samp 141 | script 142 | section 143 | select 144 | small 145 | source 146 | span 147 | strike 148 | strong 149 | style 150 | sub 151 | summary 152 | sup 153 | table 154 | tbody 155 | td 156 | textarea 157 | tfoot 158 | th 159 | thead 160 | time 161 | title 162 | tr 163 | track 164 | tt 165 | u 166 | ul 167 | var 168 | video 169 | wbr 170 | 171 | tag_script := 172 | script 173 | 174 | tag_js := 175 | javascript 176 | jav+space+ascript 177 | javas+space+cript 178 | 179 | event := 180 | onclick 181 | oncontextmenu 182 | ondblclick 183 | onmousedown 184 | onmouseenter 185 | onmouseleave 186 | onmousemove 187 | onmouseover 188 | onmouseout 189 | onmouseup 190 | onkeydown 191 | onkeypress 192 | onkeyup 193 | onabort 194 | onbeforeunload 195 | onerror 196 | onhashchange 197 | onload 198 | onpageshow 199 | onpagehide 200 | onresize 201 | onscroll 202 | onunload 203 | onblur 204 | onchange 205 | onfocus 206 | onfocusin 207 | onfocusout 208 | oninput 209 | oninvalid 210 | onreset 211 | onsearch 212 | onselect 213 | onsubmit 214 | ondrag 215 | ondragend 216 | ondragenter 217 | ondragleave 218 | ondragover 219 | ondragstart 220 | ondrop 221 | oncopy 222 | oncut 223 | onpaste 224 | onafterprint 225 | onbeforeprint 226 | onabort 227 | oncanplay 228 | oncanplaythrough 229 | ondurationchange 230 | onemptied 231 | onended 232 | onerror 233 | onloadeddata 234 | onloadedmetadata 235 | onloadstart 236 | onpause 237 | onplay 238 | onplaying 239 | onprogress 240 | onratechange 241 | onseeked 242 | onseeking 243 | onstalled 244 | onsuspend 245 | ontimeupdate 246 | onvolumechange 247 | onwaiting 248 | animationend 249 | animationiteration 250 | animationstart 251 | transitionend 252 | onerror 253 | onmessage 254 | onopen 255 | onmessage 256 | onmousewheel 257 | ononline 258 | onoffline 259 | onpopstate 260 | onshow 261 | onstorage 262 | ontoggle 263 | onwheel 264 | ontouchcancel 265 | ontouchend 266 | ontouchmove 267 | ontouchstart 268 | 269 | quote := 270 | " 271 | ' 272 | ` 273 | \" 274 | \' 275 | \` 276 | 277 | space := 278 | %0A 279 | %07 280 | %0C 281 | %0B 282 | %2F 283 | %09 284 | %26nbsp%3B 285 | + 286 | 287 | bracket_gt := 288 | > 289 | \x3e 290 | \x3E 291 | \u003c 292 | \u003C 293 | %26%23x3e 294 | %26%23x+zero+3e 295 | %26%23x3E 296 | %26%23x+zero+3E 297 | %26lt 298 | %26LT 299 | %26LT%3B 300 | %26lt%3B 301 | %3E 302 | > 303 | &#+zero+62 304 | 305 | bracket_lt := 306 | < 307 | \x3c 308 | \x3C 309 | \u003c 310 | \u003C 311 | %26%23x3c 312 | %26%23x+zero+3c 313 | %26lt 314 | %26LT 315 | %26LT%3B 316 | %26lt%3B 317 | %3C 318 | < 319 | &#+zero+60 320 | 321 | zero := 322 | 0 323 | 0+zero+ 324 | 00+zero+ 325 | 326 | payload_attack := 327 | document.body.innerHTML=+payload_text+ 328 | alert(+payload_text+) 329 | promt(+payload_text+) 330 | confirm(+payload_text+) 331 | +tag_script+:alert(+payload_text+) 332 | 333 | payload_text := 334 | +quote+dharma+random_text++quote+ 335 | +quote+dharma+random_text++quote+ 336 | 337 | random_text := 338 | %range%(a-z) 339 | %range%(A-Z) 340 | %range%(0-9) 341 | %range%(a-z)+random_text+ 342 | %range%(A-Z)+random_text+ 343 | %range%(0-9)+random_text+ 344 | 345 | %section% := variance 346 | 347 | main := 348 | +payload+ 349 | -------------------------------------------------------------------------------- /dharma/settings.py: -------------------------------------------------------------------------------- 1 | # pylint: disable = undefined-variable 2 | DharmaConst.VARIANCE_MIN = 1 3 | DharmaConst.VARIANCE_MAX = 8 4 | DharmaConst.VARIABLE_MIN = 1 5 | DharmaConst.VARIABLE_MAX = 4 6 | DharmaConst.VARIANCE_TEMPLATE = "%s" 7 | DharmaConst.MAX_REPEAT_POWER = 12 8 | DharmaConst.LEAF_TRIGGER = 256 9 | DharmaConst.URI_TABLE = { 10 | "images": "fuzzdata/samples/jpg/", 11 | "videos": "fuzzdata/samples/mp4/", 12 | "audios": "fuzzdata/samples/mp3/", 13 | } 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Dharma 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../..')) 18 | 19 | import dharma.__version__ 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'Dharma' 24 | copyright = '2018, Mozilla Fuzzing Team' 25 | author = 'Mozilla Fuzzing Team' 26 | 27 | # The short X.Y version 28 | version = dharma.__version__.__version__ 29 | # The full version, including alpha/beta/rc tags 30 | release = dharma.__version__.__version__ 31 | 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.coverage', 45 | 'sphinx.ext.githubpages', 46 | 'sphinx.ext.autosummary' 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = '.rst' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path . 71 | exclude_patterns = [] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = 'sphinx' 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | html_theme = 'sphinx_rtd_theme' 83 | 84 | # Theme options are theme-specific and customize the look and feel of a theme 85 | # further. For a list of options available for each theme, see the 86 | # documentation. 87 | # 88 | # html_theme_options = {} 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | html_static_path = ['_static'] 94 | 95 | # Custom sidebar templates, must be a dictionary that maps document names 96 | # to template names. 97 | # 98 | # The default sidebars (for documents that don't match any pattern) are 99 | # defined by theme itself. Builtin themes are using these templates by 100 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 101 | # 'searchbox.html']``. 102 | # 103 | # html_sidebars = {} 104 | 105 | 106 | # -- Options for HTMLHelp output --------------------------------------------- 107 | 108 | # Output file base name for HTML help builder. 109 | htmlhelp_basename = 'Dharmadoc' 110 | 111 | 112 | # -- Options for LaTeX output ------------------------------------------------ 113 | 114 | latex_elements = { 115 | # The paper size ('letterpaper' or 'a4paper'). 116 | # 117 | # 'papersize': 'letterpaper', 118 | 119 | # The font size ('10pt', '11pt' or '12pt'). 120 | # 121 | # 'pointsize': '10pt', 122 | 123 | # Additional stuff for the LaTeX preamble. 124 | # 125 | # 'preamble': '', 126 | 127 | # Latex figure (float) alignment 128 | # 129 | # 'figure_align': 'htbp', 130 | } 131 | 132 | # Grouping the document tree into LaTeX files. List of tuples 133 | # (source start file, target name, title, 134 | # author, documentclass [howto, manual, or own class]). 135 | latex_documents = [ 136 | (master_doc, 'Dharma.tex', 'Dharma Documentation', 137 | 'Mozilla Fuzzing Team', 'manual'), 138 | ] 139 | 140 | 141 | # -- Options for manual page output ------------------------------------------ 142 | 143 | # One entry per manual page. List of tuples 144 | # (source start file, name, description, authors, manual section). 145 | man_pages = [ 146 | (master_doc, 'dharma', 'Dharma Documentation', 147 | [author], 1) 148 | ] 149 | 150 | 151 | # -- Options for Texinfo output ---------------------------------------------- 152 | 153 | # Grouping the document tree into Texinfo files. List of tuples 154 | # (source start file, target name, title, author, 155 | # dir menu entry, description, category) 156 | texinfo_documents = [ 157 | (master_doc, 'Dharma', 'Dharma Documentation', 158 | author, 'Dharma', 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | # -- Extension configuration ------------------------------------------------- 164 | -------------------------------------------------------------------------------- /docs/source/dharma.core.rst: -------------------------------------------------------------------------------- 1 | dharma.core package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. automodule:: dharma.core.dharma 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | .. automodule:: dharma.core.extensions 13 | :members: 14 | :undoc-members: 15 | :show-inheritance: 16 | 17 | .. automodule:: dharma.core.websocket 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: dharma.core 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/source/dharma.rst: -------------------------------------------------------------------------------- 1 | dharma package 2 | ============== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | dharma.core 10 | 11 | Submodules 12 | ---------- 13 | 14 | .. automodule:: dharma.dharma 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | .. automodule:: dharma.settings 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | 25 | Module contents 26 | --------------- 27 | 28 | .. automodule:: dharma 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Dharma Documentation 2 | ==================== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | Dharma 8 | 9 | 10 | Indices and Tables 11 | ================== 12 | 13 | * :ref:`modindex` -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | dharma 2 | ====== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | dharma 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf-8 3 | """setuptools install script""" 4 | # This Source Code Form is subject to the terms of the Mozilla Public 5 | # License, v. 2.0. If a copy of the MPL was not distributed with this 6 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | import os 8 | import sys 9 | 10 | from setuptools import setup, find_packages, Command 11 | 12 | 13 | HERE = os.path.abspath(os.path.dirname(__file__)) 14 | 15 | ABOUT = {} 16 | with open(os.path.join(HERE, 'dharma', '__version__.py')) as fo: 17 | exec(fo.read(), ABOUT) # pylint: disable=exec-used 18 | 19 | 20 | def README(): 21 | with open("README.md") as fo: 22 | return fo.read() 23 | 24 | 25 | class PublishCommand(Command): 26 | """Command class for: setup.py publish""" 27 | 28 | description = 'Build and publish the package.' 29 | user_options = [] 30 | 31 | @staticmethod 32 | def status(text): 33 | """Prints things in bold.""" 34 | print('\033[1m{0}\033[0m'.format(text)) 35 | 36 | def initialize_options(self): 37 | pass 38 | 39 | def finalize_options(self): 40 | pass 41 | 42 | def run(self): 43 | try: 44 | self.status('Removing previous builds…') 45 | os.system('rm -rf {}'.format(os.path.join(HERE, 'dist'))) 46 | except OSError: 47 | pass 48 | 49 | self.status('Building Source and Wheel distribution ...') 50 | os.system('{0} setup.py sdist bdist_wheel'.format(sys.executable)) 51 | 52 | self.status('Pushing git tags ...') 53 | os.system('git tag v{0}'.format(ABOUT['__version__'])) 54 | os.system('git push origin v{0}'.format(ABOUT['__version__'])) 55 | 56 | self.status('Uploading the package to PyPI via Twine ...') 57 | os.system('twine upload dist/*') 58 | 59 | sys.exit() 60 | 61 | 62 | if __name__ == "__main__": 63 | setup( 64 | version=ABOUT['__version__'], 65 | name=ABOUT['__title__'], 66 | license=ABOUT['__license__'], 67 | keywords=ABOUT['__keywords__'], 68 | description=ABOUT['__description__'], 69 | long_description=README(), 70 | long_description_content_type='text/markdown', 71 | author=ABOUT['__author__'], 72 | author_email=ABOUT['__author_email__'], 73 | maintainer=ABOUT['__maintainer__'], 74 | maintainer_email=ABOUT['__maintainer_email__'], 75 | url=ABOUT['__url__'], 76 | project_urls=ABOUT['__project_urls__'], 77 | download_url=ABOUT['__download_url__'], 78 | classifiers=[ 79 | 'Topic :: Software Development :: Testing', 80 | "Topic :: Security", 81 | 'Intended Audience :: Developers', 82 | 'Operating System :: OS Independent', 83 | "Programming Language :: Python :: 3", 84 | 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)' 85 | ], 86 | entry_points={ 87 | "console_scripts": [ 88 | "dharma = dharma:DharmaCommandLine.main" 89 | ] 90 | }, 91 | packages=find_packages(), 92 | package_data={ 93 | 'dharma': [ 94 | 'grammars/*', 95 | 'grammars/**/*' 96 | ] 97 | }, 98 | cmdclass={ 99 | 'publish': PublishCommand 100 | } 101 | ) 102 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py3 3 | skip_missing_interpreters=True 4 | 5 | [testenv] 6 | usedevelop = True 7 | 8 | deps = 9 | pipenv 10 | pylint 11 | pytest 12 | pytest-cov 13 | 14 | commands = 15 | pipenv install --dev --skip-lock 16 | pipenv run pylint dharma 17 | pipenv run pipenv check 18 | #pipenv run pytest -v --durations 10 --cov dharma --cov-report term 19 | --------------------------------------------------------------------------------