├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── ChangeLog ├── LICENSE ├── Makefile ├── Makefile.shared ├── README.md ├── check-sieve.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── docs └── man1 │ └── check-sieve.1 ├── gen ├── Makefile ├── Makefile.env ├── location.hh ├── sieve_parser.tab.cc ├── sieve_parser.tab.hh ├── sieve_scanner.cc └── sieve_scanner.hh ├── scripts └── travis-build ├── snapcraft.yaml ├── src ├── AST │ ├── AST.hh │ ├── ASTBlock.cc │ ├── ASTBlock.hh │ ├── ASTBoolean.cc │ ├── ASTBoolean.hh │ ├── ASTBranch.cc │ ├── ASTBranch.hh │ ├── ASTCommand.cc │ ├── ASTCommand.hh │ ├── ASTCondition.cc │ ├── ASTCondition.hh │ ├── ASTNoOp.cc │ ├── ASTNoOp.hh │ ├── ASTNode.hh │ ├── ASTNumeric.cc │ ├── ASTNumeric.hh │ ├── ASTRequire.cc │ ├── ASTRequire.hh │ ├── ASTSieve.cc │ ├── ASTSieve.hh │ ├── ASTString.cc │ ├── ASTString.hh │ ├── ASTStringList.cc │ ├── ASTStringList.hh │ ├── ASTTag.cc │ ├── ASTTag.hh │ ├── ASTTest.cc │ ├── ASTTest.hh │ ├── ASTTestList.cc │ ├── ASTTestList.hh │ ├── ASTTraceVisitor.cc │ ├── ASTTraceVisitor.hh │ ├── ASTVerificationVisitor.cc │ ├── ASTVerificationVisitor.hh │ ├── ASTVisitor.hh │ ├── Makefile.env │ └── Validation │ │ ├── Command.cc │ │ ├── Command.hh │ │ ├── Tag.cc │ │ ├── Tag.hh │ │ ├── Test.cc │ │ ├── Test.hh │ │ └── Validation.hh ├── Makefile ├── Makefile.env ├── Server │ ├── MailServer.cc │ ├── MailServer.hh │ └── Makefile.env ├── checksieve.cc ├── checksieve.h ├── diagnostic.cc ├── diagnostic.hh ├── python.cc ├── sieve.cc ├── sieve_driver.cc ├── sieve_driver.hh ├── sieve_parser.yy ├── sieve_scanner.l └── webchecksieve.cc └── test ├── 3028 ├── __init__.py ├── comments_test.py ├── if_test.py ├── misc_test.py └── strings_test.py ├── 3894 ├── __init__.py └── basic_test.py ├── 5173 ├── __init__.py ├── body_test.py └── examples_test.py ├── 5183 ├── __init__.py └── environment_test.py ├── 5228 ├── __init__.py ├── commands_test.py ├── examples_test.py ├── semicolon_test.py └── tests_test.py ├── 5229 ├── __init__.py ├── tests_test.py └── variables_test.py ├── 5230 ├── __init__.py ├── commands_test.py └── examples_test.py ├── 5231 ├── __init__.py └── relational_test.py ├── 5232 ├── __init__.py ├── actions_test.py ├── fileinto_test.py ├── keep_test.py └── tests_test.py ├── 5233 ├── __init__.py └── examples_test.py ├── 5235 ├── __init__.py ├── spamtest_test.py └── virustest_test.py ├── 5260 ├── __init__.py ├── currentdate_test.py ├── date_test.py └── index_test.py ├── 5293 ├── __init__.py └── actions_test.py ├── 5429 ├── __init__.py └── basic_test.py ├── 5435 ├── __init__.py └── examples_test.py ├── 5436 ├── __init__.py └── notify_test.py ├── 5463 ├── __init__.py ├── error_test.py └── ihave_test.py ├── 5490 ├── __init__.py ├── fileinto_create_test.py ├── mailboxexists_test.py ├── metadata_test.py └── servermetadata_test.py ├── 5703 ├── __init__.py ├── actions_test.py └── tests_test.py ├── 6009 ├── __init__.py └── examples_test.py ├── 6134 ├── __init__.py ├── examples_test.py └── valid_ext_lists_test.py ├── 6558 ├── __init__.py ├── bogus_test.py └── examples_test.py ├── 6609 ├── __init__.py ├── examples_test.py ├── global_test.py └── return_test.py ├── 6785 ├── __init__.py ├── examples_test.py └── norequire_test.py ├── 7352 ├── __init__.py ├── examples_test.py └── failure_test.py ├── 8579 ├── __init__.py └── examples_test.py ├── 8580 ├── __init__.py └── examples_test.py ├── 9042 ├── __init__.py └── examples_test.py ├── 9671 ├── __init__.py ├── cross_requires_test.py └── examples_test.py ├── AST ├── __init__.py ├── commands_test.py ├── control_test.py ├── mock │ ├── commands │ │ ├── require_list.out │ │ ├── require_list.sieve │ │ ├── require_single.out │ │ ├── require_single.sieve │ │ ├── stop.out │ │ └── stop.sieve │ └── control │ │ ├── if_1.out │ │ ├── if_1.sieve │ │ ├── if_2.out │ │ ├── if_2.sieve │ │ ├── if_3.out │ │ └── if_3.sieve └── util.py ├── __init__.py ├── drafts ├── __init__.py └── regex │ ├── __init__.py │ └── tag_test.py ├── other ├── __init__.py └── options_test.py ├── setup.py └── vendor ├── __init__.py ├── dovecot ├── __init__.py └── examples_test.py └── proton ├── __init__.py ├── eval_test.py └── expire_test.py /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | ubuntu_matrix: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | compiler: [gcc, clang] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v3 14 | with: 15 | python-version: "3.12" 16 | - name: make 17 | env: 18 | CC: ${{ matrix.compiler }} 19 | run: make clean && make 20 | - name: setup test dependencies 21 | run: python3 -m pip install --upgrade setuptools 22 | - name: make test 23 | run: make test 24 | macos_matrix: 25 | runs-on: macos-latest 26 | strategy: 27 | matrix: 28 | compiler: [gcc, clang] 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: actions/setup-python@v3 32 | with: 33 | python-version: "3.12" 34 | - name: make 35 | env: 36 | CC: ${{ matrix.compiler }} 37 | run: make clean && make 38 | - name: setup test dependencies 39 | run: python3 -m pip install --upgrade setuptools 40 | - name: make test 41 | run: make test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.o 3 | *.so 4 | *.a 5 | check-sieve 6 | build 7 | *.pyc 8 | *~ 9 | xcuserdata 10 | __pycache__ 11 | 12 | # Web-related binaries 13 | *.js 14 | *.wasm 15 | 16 | # Snaps 17 | *.snap 18 | *.snap.bak -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Version 0.10: 2 | * Support for Delivering to Special-Use Mailboxes (RFC 8579). 3 | * Support for Detecting Duplicate Deliveries (RFC 7352). 4 | * Support for File Carbon Copy (FCC) (RFC 8580). 5 | * Support for Python 3.12. (@grawlinson) 6 | * Support for building with GCC 13. (@RensOliemans) 7 | * Move to C++17. 8 | 9 | Version 0.9: 10 | * Fixed Xcode project files. 11 | * Add support for Externally Stored Lists (RFC 6134). (@johnlettman) 12 | * Snapcraft support. (@johnlettman) 13 | * Support Proton mail-specific extensions. (@johnlettman) 14 | * Support GCC 13+. (@grawlinson) 15 | * Support CR/LF-style line-endings. 16 | * Fix bug to accept relational operators in header tests. (@fischerling) 17 | 18 | Version 0.8: 19 | * Support compiling on FreeBSD. 20 | * Adds a --server flag to check-sieve, enabling the tool to check capabilities specific to your server. 21 | * Fix bug preventing some test-lists from having a single item. 22 | * Addition of explicit test-list AST object, allowing for improved validation. 23 | 24 | Version 0.7: 25 | * Fix core dump which allowed enabling regex support on Linux. 26 | * Many small improvements to validation and RFC conformance. 27 | * Add support for Internet Message Access Protocol (IMAP) Events (RFC 6785). 28 | * Add support for Converting Messages Before Delivery (RFC 6558). 29 | * Adds emscripten support for check-sieve to allow for embedding 30 | libchecksieve on webpages (see checksieve.com). 31 | * Adds support for FreeBSD to the build system. 32 | 33 | Version 0.6: 34 | * Add manpage for check-sieve(1). 35 | * Add a --max-list-length option to validate list length. 36 | * Update python tests to use Python 3.7. 37 | * Fix various small bugs in the tool. 38 | * Add support for Spamtest Extension (RFC 5235). 39 | * Add support for Mailbox Extension (RFC 5490). 40 | * Add support for Notifications Extension (RFC 5435). 41 | * Add support for Subaddress Extension (RFC 5233). 42 | * Add support for Ihave Extension (RFC 5463). 43 | * Add support for Environment Extension (RFC 5183). 44 | 45 | Version 0.5: 46 | * Implement Editheader Extension (RFC 5239). 47 | * Implement Regex Extension (DRAFT). 48 | * Stricter semicolon conformance. 49 | * Fix for multi-line comment syntax. 50 | * Match type validation for tests. 51 | * Add Xcode project. 52 | 53 | Version 0.4: 54 | * Add usage validation for supported commands. 55 | * Factor out user reporting to a separate API (issue #20). 56 | * Support for the "global" command from RFC 6609. 57 | * Fix broken "foreverypart" syntax. 58 | * Add support for python 3 in unit test C extension. 59 | * Add support for AST command validation. 60 | * Add the ability to trace operation of the AST. 61 | * Implement an Abstract Syntax Tree (AST) 62 | 63 | Version 0.3: 64 | * Renamed the project to check-sieve, for better consistency. 65 | * Fix linking issue on some flavors of linux. 66 | * Update the README.md file with a list of supported RFCs. 67 | * Fix command "keep" validation. 68 | * Refactored much of the library. 69 | * Add version information to the library and binary. 70 | * Switch to using regexes to implement required module matching. 71 | * Add usage to check-sieve, as well as a couple of debugging options. 72 | * Implement "MIME Part Tests, Iteration, Extraction, Replacement, and Enclosure" (RFC 5703). 73 | * Implement the relational extension (RFC 5231) 74 | * Implement Body Extension (RFC 5173) 75 | 76 | Version 0.2: 77 | * Fix a handful of RFC conformance bugs. 78 | * Fix Travis-CI builds. 79 | * Add support for reject and extended reject extensions (RFC 5429). 80 | * Implement "Copying without side effects" (RFC 3894). 81 | * Add support for the Include extension (RFC 6609) 82 | * Add support for the Vacation extension (RFC 5230). 83 | * Add support for the Date and Index Extensions (RFC 5260). 84 | 85 | Version 0.1: 86 | * Python test harness 87 | * Implementation of Variables extension (RFC 5229) 88 | * Basic support for RFC 5228 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 - 2025 Dana Burkart 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BASE = . 2 | 3 | ifeq ($(INSTALL_PREFIX),) 4 | INSTALL_PREFIX := /usr/local 5 | endif 6 | 7 | ifdef DEBUG 8 | CFLAGS += -DDEBUG -g 9 | endif 10 | 11 | include $(BASE)/Makefile.shared 12 | include $(BASE)/gen/Makefile.env 13 | include $(BASE)/src/Makefile.env 14 | include $(BASE)/src/AST/Makefile.env 15 | include $(BASE)/src/Server/Makefile.env 16 | 17 | .PHONY: test 18 | 19 | all: libchecksieve.a check-sieve 20 | 21 | codegen: 22 | $(MAKE) -C $(BASE)/src codegen 23 | 24 | check-sieve: $(BINARY_SRC) libchecksieve.a 25 | $(MAKE) -C $(BASE)/src binary 26 | $(CXX) $(CFLAGS) -o check-sieve $(BINARY_OBJ) libchecksieve.a 27 | 28 | libchecksieve.a: $(GENERATED_SRC) $(LIBCHECKSIEVE_SRC) $(AST_SRC) $(SERVER_SRC) 29 | $(MAKE) -C $(BASE)/gen 30 | $(MAKE) -C $(BASE)/src lib 31 | ar rc libchecksieve.a $(GENERATED_OBJ) $(LIBCHECKSIEVE_OBJ) $(AST_OBJ) $(SERVER_OBJ) 32 | 33 | test: libchecksieve.a check-sieve 34 | rm -Rf checksieve.*.so build || true 35 | @python3 --version 36 | python3 $(BASE)/test/setup.py build_ext -i 37 | python3 -m unittest discover -s test -p '*_test.py' 38 | 39 | install: all 40 | mkdir -p $(INSTALL_PREFIX)/bin 41 | mkdir -p $(INSTALL_PREFIX)/lib 42 | mkdir -p $(INSTALL_PREFIX)/share/man/man1 43 | install check-sieve $(INSTALL_PREFIX)/bin 44 | install libchecksieve.a $(INSTALL_PREFIX)/lib 45 | install ./docs/man1/check-sieve.1 $(INSTALL_PREFIX)/share/man/man1 46 | 47 | wasm: 48 | emcc --bind -o checksieve.js $(CFLAGS) $(GENERATED_SRC) $(LIBCHECKSIEVE_SRC) $(AST_SRC) $(BASE)/src/webchecksieve.cc 49 | 50 | clean: 51 | $(MAKE) -C $(BASE)/gen clean 52 | $(MAKE) -C $(BASE)/src clean 53 | rm -f libchecksieve.a checksieve*.so check-sieve 54 | rm -Rf build 55 | -------------------------------------------------------------------------------- /Makefile.shared: -------------------------------------------------------------------------------- 1 | ifndef $(PROJECT) 2 | 3 | PROJECT = check-sieve 4 | 5 | PLATFORM ?= $(shell uname -s) 6 | ARCH ?= $(shell uname -m) 7 | 8 | LEX = flex -I 9 | YACC = bison -d 10 | MAKE = make 11 | 12 | CXX ?= clang++ -DYYDEBUG=1 13 | CINCLUDES = -I$(BASE)/gen/ -I$(BASE)/src/ -I$(BASE)/src/AST -I$(BASE)/src/Server 14 | CFLAGS += $(CINCLUDES) -std=c++17 -fPIC -Wno-deprecated-register -DPLATFORM=\"$(ARCH)-$(PLATFORM)\" -Werror -Wall 15 | 16 | ifeq ($(PLATFORM), Darwin) 17 | CFLAGS += -mmacosx-version-min=10.9 18 | endif 19 | 20 | ifeq ($(PLATFORM), FreeBSD) 21 | MAKE = gmake 22 | endif 23 | 24 | ifdef DEBUG 25 | CFLAGS += -DDEBUG 26 | endif 27 | 28 | endif 29 | -------------------------------------------------------------------------------- /check-sieve.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/man1/check-sieve.1: -------------------------------------------------------------------------------- 1 | .TH check-sieve 1 "08 Nov 2024" "version 0.10" 2 | . 3 | . 4 | . 5 | .SH NAME 6 | . 7 | check-sieve - Syntax checker for mail sieves. 8 | . 9 | .SH SYNOPSIS 10 | . 11 | .B check-sieve [options] 12 | .I file 13 | .B [ 14 | .I file ... 15 | .B ] 16 | .br 17 | .B check-sieve --help 18 | .br 19 | .B check-sieve --version 20 | . 21 | .SH DESCRIPTION 22 | . 23 | .B check-sieve 24 | is a simple syntax checker for server-side mail sieves. It supports many 25 | mail-sieve RFCs, details of which can be found below. 26 | . 27 | .SH OPTIONS 28 | . 29 | .IP "-h, --help" 30 | Show the program help 31 | .IP "--max-list-length N" 32 | Check length of string lists, and flag any that are longer than the specified length 33 | .IP "--server :" 34 | Connect to the specified server, and only enable advertised capabilities 35 | .IP "--trace-parser" 36 | Trace the operation of the parser 37 | .IP "--trace-scanner" 38 | Trace the operation of the scanner 39 | .IP "--trace-tree" 40 | Trace the abstract-syntax-tree 41 | .IP "--version" 42 | Print out version information 43 | . 44 | .SH BUGS 45 | . 46 | For information on existing issues, and to file a bug report check the following URL: 47 | https://github.com/dburkart/check-sieve/issues 48 | . 49 | .SH AUTHORS 50 | . 51 | Dana Burkart 52 | .br 53 | Stuart Montgomery 54 | . 55 | .SH SUPPORTED RFCs 56 | . 57 | The full list of supported RFCs is: 58 | 59 | .RS 60 | Sieve: An Email Filtering Language (RFC 5228) 61 | .br 62 | Variables Extension (RFC 5229) 63 | .br 64 | Date and Index Extensions (RFC 5260) 65 | .br 66 | Vacation Extension (RFC 5230) 67 | .br 68 | Include Extension (RFC 6609) 69 | .br 70 | Copying Without Side Effects (RFC 3894) 71 | .br 72 | Reject and Extended Reject Extensions (RFC 5429) 73 | .br 74 | Body Extension (RFC 5173) 75 | .br 76 | Relational Extension (RFC 5231) 77 | .br 78 | MIME Part Tests, Iteration, Extraction, Replacement, and Enclosure (RFC 5703) 79 | .br 80 | Imap4flags Extension (RFC 5232) 81 | .br 82 | Editheader Extension (RFC 5293) 83 | .br 84 | Regex Extension (DRAFT) 85 | .br 86 | Spamtest and Virustest Extensions (RFC 5235) 87 | .br 88 | Extensions for Notifications (RFC 5435) 89 | .br 90 | Subaddress Extension (RFC 5233) 91 | .br 92 | Ihave Extension (RFC 5463) 93 | .br 94 | Environment Extension (RFC 5183) 95 | .br 96 | Sieve Notification Mechanism: mailto (RFC 5436) 97 | .br 98 | Internet Message Access Protocol (IMAP) Events (RFC 6785) 99 | .br 100 | Converting Messages before Delivery (RFC 6558) 101 | .br 102 | Detecting Duplicate Deliveries (RFC 7352) 103 | .br 104 | Externally Stored Lists (RFC 6134) 105 | .br 106 | Delivering to Special-Use Mailboxes (RFC 8579) 107 | .br 108 | File Carbon Copy (FCC) (RFC 8580) 109 | .br 110 | Delivery by MAILBOXID (RFC 9042) 111 | .br 112 | Delivery Status Notifications and Deliver-By Extensions (RFC 6009) 113 | .br 114 | Extension for Processing Calendar Attachments (RFC 9671) 115 | .br 116 | Proton Expiration Extension (vendor-specific) 117 | .br 118 | Proton Eval Extension (vendor-specific) 119 | .br 120 | Dovecot Invoking External Programs (vendor-specific) 121 | .RE 122 | -------------------------------------------------------------------------------- /gen/Makefile: -------------------------------------------------------------------------------- 1 | BASE = .. 2 | 3 | include $(BASE)/Makefile.shared 4 | include $(BASE)/gen/Makefile.env 5 | 6 | all: $(GENERATED_OBJ) 7 | 8 | clean: 9 | rm -f $(GENERATED_OBJ) 10 | 11 | $(GENERATED_OBJ): %.o: %.cc 12 | $(CXX) -c $(CINCLUDES) -std=c++17 -fPIC -Wno-deprecated-register -DPLATFORM=\"$(ARCH)-$(PLATFORM)\" $< -o $@ 13 | -------------------------------------------------------------------------------- /gen/Makefile.env: -------------------------------------------------------------------------------- 1 | GENERATED_SRC = $(BASE)/gen/sieve_parser.tab.cc $(BASE)/gen/sieve_scanner.cc 2 | GENERATED_OBJ = $(GENERATED_SRC:%.cc=%.o) -------------------------------------------------------------------------------- /scripts/travis-build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PLATFORM=$(uname -s) 4 | MAKECMD=make 5 | 6 | if [ "$PLATFORM" == "FreeBSD" ]; then 7 | MAKECMD=gmake 8 | fi 9 | 10 | $MAKECMD test 11 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: check-sieve 2 | license: MIT 3 | version: "0.8" # automatically updated during pull 4 | summary: Syntax checker for mail sieves 5 | description: | 6 | Makes checking mail sieve syntax easy and painless. 7 | Because breaking your sieve in production sucks. 8 | 9 | type: app 10 | base: core22 11 | grade: stable 12 | compression: xz 13 | 14 | confinement: strict 15 | adopt-info: check-sieve 16 | 17 | parts: 18 | check-sieve: 19 | plugin: make 20 | source: https://github.com/dburkart/check-sieve.git 21 | build-packages: 22 | - g++ 23 | - gcc 24 | - make 25 | override-pull: | 26 | craftctl default 27 | last_committed_tag="$(git describe --tags --abbrev=0)" 28 | last_committed_version="$(echo "${last_committed_tag}" | grep -Eo '[[:digit:]]+\.[[:digit:]]+')" 29 | last_released_version="$(snap info $CRAFT_PROJECT_NAME | awk '$1 == "latest/beta:" { print $2 }' || true)" 30 | 31 | # If the latest tag from the upstream project has not been released to 32 | # beta, build that tag instead of master. 33 | if [ "${last_committed_version}" != "${last_released_version}" ]; then 34 | git fetch 35 | git checkout "${last_committed_tag}" 36 | echo "Setting version to ${last_committed_version}" 37 | craftctl set version="${last_committed_version}" 38 | else 39 | echo "Setting version to $(git rev-parse --short HEAD)" 40 | craftctl set version="$(git rev-parse --short HEAD)" 41 | fi 42 | override-build: | 43 | CFLAGS='-O3 -pipe' make -j4 44 | cp check-sieve $CRAFT_PART_INSTALL 45 | 46 | apps: 47 | check-sieve: 48 | command: check-sieve 49 | plugs: 50 | - home # access to user's /home directory 51 | -------------------------------------------------------------------------------- /src/AST/AST.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTBlock.hh" 4 | #include "ASTBoolean.hh" 5 | #include "ASTBranch.hh" 6 | #include "ASTCommand.hh" 7 | #include "ASTCondition.hh" 8 | #include "ASTNoOp.hh" 9 | #include "ASTNumeric.hh" 10 | #include "ASTRequire.hh" 11 | #include "ASTSieve.hh" 12 | #include "ASTString.hh" 13 | #include "ASTStringList.hh" 14 | #include "ASTTag.hh" 15 | #include "ASTTest.hh" 16 | #include "ASTTestList.hh" 17 | #include "ASTTraceVisitor.hh" 18 | #include "ASTVerificationVisitor.hh" 19 | -------------------------------------------------------------------------------- /src/AST/ASTBlock.cc: -------------------------------------------------------------------------------- 1 | #include "ASTBlock.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTBlock::ASTBlock( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTBlock::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTBlock.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTBlock : public ASTNode { 11 | public: 12 | ASTBlock() : ASTNode() {} 13 | explicit ASTBlock(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor) override; 16 | 17 | [[nodiscard]] std::string value() const { return "ASTBlock"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | }; 34 | 35 | } // namespace sieve 36 | 37 | -------------------------------------------------------------------------------- /src/AST/ASTBoolean.cc: -------------------------------------------------------------------------------- 1 | #include "ASTBoolean.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTBoolean::ASTBoolean( yy::location location ) 8 | : ASTTest( location ) 9 | { 10 | } 11 | 12 | ASTBoolean::ASTBoolean( bool val) 13 | : _val( val ) 14 | { 15 | } 16 | ASTBoolean::ASTBoolean( yy::location location, bool val) 17 | : ASTTest( location ) 18 | , _val( val ) 19 | { 20 | } 21 | 22 | void ASTBoolean::accept( ASTVisitor &visitor ) { 23 | visitor.visit(this); 24 | } 25 | 26 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTBoolean.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTTest.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTBoolean : public ASTTest { 11 | public: 12 | ASTBoolean() : ASTTest() {} 13 | ASTBoolean(yy::location location); 14 | ASTBoolean(bool val); 15 | ASTBoolean(yy::location location, bool val); 16 | 17 | void accept(ASTVisitor &visitor); 18 | 19 | bool value() const { return this->_val; } 20 | 21 | template 22 | std::vector::const_iterator find(const T& value) const { 23 | for (std::vector::const_iterator it = this->children().begin(); it != this->children().end(); ++it) { 24 | const T* child = dynamic_cast(*it); 25 | 26 | if (child == NULL) 27 | continue; 28 | 29 | if (child->value() == value.value()) 30 | return it; 31 | } 32 | 33 | return this->children().end(); 34 | } 35 | 36 | private: 37 | bool _val; 38 | }; 39 | 40 | } // namespace sieve 41 | 42 | -------------------------------------------------------------------------------- /src/AST/ASTBranch.cc: -------------------------------------------------------------------------------- 1 | #include "ASTBranch.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTBranch::ASTBranch( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTBranch::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTBranch.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTBranch : public ASTNode { 11 | public: 12 | ASTBranch() : ASTNode() {} 13 | explicit ASTBranch(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor) override; 16 | 17 | [[nodiscard]] std::string value() const { return "ASTBranch"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | }; 34 | 35 | } // namespace sieve 36 | -------------------------------------------------------------------------------- /src/AST/ASTCommand.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ASTCommand.hh" 4 | #include "ASTVisitor.hh" 5 | 6 | namespace sieve 7 | { 8 | 9 | ASTCommand::ASTCommand( yy::location location ) 10 | : ASTNode( location ) 11 | { 12 | } 13 | 14 | ASTCommand::ASTCommand( std::string name) 15 | : _name(std::move( name )) 16 | { 17 | } 18 | ASTCommand::ASTCommand( yy::location location, std::string name) 19 | : ASTNode( location ) 20 | , _name(std::move( name )) 21 | { 22 | } 23 | 24 | void ASTCommand::accept( ASTVisitor &visitor ) { 25 | visitor.visit(this); 26 | } 27 | 28 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTCommand.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTCommand : public ASTNode { 11 | public: 12 | ASTCommand() : ASTNode() {} 13 | explicit ASTCommand(yy::location location); 14 | explicit ASTCommand(std::string name); 15 | ASTCommand(yy::location location, std::string name); 16 | 17 | void accept(ASTVisitor &visitor) override; 18 | 19 | [[nodiscard]] std::string value() const { return this->_name; } 20 | 21 | template 22 | std::vector::const_iterator find(const T& value) const { 23 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 24 | const T* child = dynamic_cast(*it); 25 | 26 | if (child == NULL) 27 | continue; 28 | 29 | if (child->value() == value.value()) 30 | return it; 31 | } 32 | 33 | return this->children().end(); 34 | } 35 | 36 | private: 37 | std::string _name; 38 | }; 39 | 40 | } // namespace sieve 41 | 42 | -------------------------------------------------------------------------------- /src/AST/ASTCondition.cc: -------------------------------------------------------------------------------- 1 | #include "ASTCondition.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTCondition::ASTCondition( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTCondition::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTCondition.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTCondition : public ASTNode { 11 | public: 12 | ASTCondition() : ASTNode() {} 13 | explicit ASTCondition(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor) override; 16 | 17 | [[nodiscard]] std::string value() const { return "ASTCondition"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | }; 34 | 35 | } // namespace sieve 36 | -------------------------------------------------------------------------------- /src/AST/ASTNoOp.cc: -------------------------------------------------------------------------------- 1 | #include "ASTNoOp.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTNoOp::ASTNoOp( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTNoOp::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTNoOp.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTNoOp : public ASTNode { 11 | public: 12 | ASTNoOp() : ASTNode() {} 13 | explicit ASTNoOp(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor) override; 16 | 17 | [[nodiscard]] std::string value() const { return "ASTNoOp"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | }; 34 | 35 | } // namespace sieve 36 | -------------------------------------------------------------------------------- /src/AST/ASTNode.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "checksieve.h" 7 | 8 | namespace sieve 9 | { 10 | 11 | class ASTVisitor; 12 | 13 | class ASTNode { 14 | public: 15 | ASTNode() : _children() {} 16 | ASTNode(const yy::location &location) 17 | : _parent(NULL) 18 | , _children() 19 | , _location(location.begin, location.end) {} 20 | 21 | virtual void accept(ASTVisitor& visitor) =0; 22 | 23 | const std::vector &children() const { return this->_children; } 24 | const ASTNode *nextChild(const ASTNode *child) const { 25 | const ASTNode *next = NULL; 26 | std::vector children = _children; 27 | for (std::vector::iterator it = children.begin(); it != children.end(); ++it) { 28 | if (child == *it) { 29 | ++it; 30 | if (it == children.end()) 31 | break; 32 | next = *it; 33 | } 34 | } 35 | return next; 36 | }; 37 | 38 | const ASTNode *parent() const { return _parent; } 39 | void setParent(ASTNode *parent) { _parent = parent; } 40 | 41 | void push(ASTNode *child) { child->setParent(this); this->_children.push_back(child); } 42 | void push(std::vector children) { 43 | for (std::vector::iterator it = children.begin(); it != children.end(); ++it) { 44 | (*it)->setParent(this); 45 | } 46 | this->_children.insert(this->_children.end(), children.begin(), children.end()); 47 | } 48 | 49 | yy::location location() const { return this->_location; } 50 | 51 | private: 52 | ASTNode *_parent; 53 | std::vector _children; 54 | yy::location _location; 55 | }; 56 | 57 | } // namespace sieve 58 | 59 | -------------------------------------------------------------------------------- /src/AST/ASTNumeric.cc: -------------------------------------------------------------------------------- 1 | #include "ASTNumeric.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTNumeric::ASTNumeric( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | ASTNumeric::ASTNumeric( int number) 13 | : _number( number ) 14 | { 15 | } 16 | ASTNumeric::ASTNumeric( yy::location location, int number) 17 | : ASTNode( location ) 18 | , _number( number ) 19 | { 20 | } 21 | 22 | void ASTNumeric::accept( ASTVisitor &visitor ) { 23 | visitor.visit(this); 24 | } 25 | 26 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTNumeric.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTNumeric : public ASTNode { 11 | public: 12 | ASTNumeric() : ASTNode() {} 13 | ASTNumeric(yy::location location); 14 | ASTNumeric(int number); 15 | ASTNumeric(yy::location location, int number); 16 | 17 | void accept(ASTVisitor &visitor); 18 | 19 | int value() const { return this->_number; } 20 | 21 | template 22 | std::vector::const_iterator find(const T& value) const { 23 | for (std::vector::const_iterator it = this->children().begin(); it != this->children().end(); ++it) { 24 | const T* child = dynamic_cast(*it); 25 | 26 | if (child == NULL) 27 | continue; 28 | 29 | if (child->value() == value.value()) 30 | return it; 31 | } 32 | 33 | return this->children().end(); 34 | } 35 | 36 | private: 37 | int _number; 38 | }; 39 | 40 | } // namespace sieve 41 | 42 | -------------------------------------------------------------------------------- /src/AST/ASTRequire.cc: -------------------------------------------------------------------------------- 1 | #include "ASTRequire.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTRequire::ASTRequire( yy::location location ) 8 | : ASTCommand( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTRequire::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTRequire.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTCommand.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTRequire : public ASTCommand { 11 | public: 12 | ASTRequire() : ASTCommand() {} 13 | explicit ASTRequire(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor) override; 16 | 17 | [[nodiscard]] std::string value() const { return "ASTRequire"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | 34 | private: 35 | }; 36 | 37 | } // namespace sieve 38 | -------------------------------------------------------------------------------- /src/AST/ASTSieve.cc: -------------------------------------------------------------------------------- 1 | #include "ASTSieve.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTSieve::ASTSieve( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTSieve::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTSieve.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTSieve : public ASTNode { 11 | public: 12 | ASTSieve() : ASTNode() {} 13 | explicit ASTSieve(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor) override; 16 | 17 | [[nodiscard]] std::string value() const { return "ASTSieve"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | 34 | private: 35 | }; 36 | 37 | } // namespace sieve 38 | -------------------------------------------------------------------------------- /src/AST/ASTString.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ASTString.hh" 4 | #include "ASTVisitor.hh" 5 | 6 | namespace sieve 7 | { 8 | 9 | ASTString::ASTString( yy::location location ) 10 | : ASTNode( location ) 11 | { 12 | } 13 | 14 | ASTString::ASTString( std::string str) 15 | : _str(std::move( str )) 16 | { 17 | } 18 | ASTString::ASTString( yy::location location, std::string str) 19 | : ASTNode( location ) 20 | , _str(std::move( str )) 21 | { 22 | } 23 | 24 | void ASTString::accept( ASTVisitor &visitor ) { 25 | visitor.visit(this); 26 | } 27 | 28 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTString.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ASTNode.hh" 6 | 7 | namespace sieve 8 | { 9 | 10 | class ASTVisitor; 11 | 12 | class ASTString : public ASTNode { 13 | public: 14 | ASTString() : ASTNode() {} 15 | explicit ASTString(yy::location location); 16 | explicit ASTString(std::string str); 17 | ASTString(yy::location location, std::string str); 18 | 19 | void accept(ASTVisitor &visitor) override; 20 | 21 | [[nodiscard]] std::string_view value() const { return std::string_view{ this->_str }; } 22 | 23 | template 24 | std::vector::const_iterator find(const T& value) const { 25 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 26 | const T* child = dynamic_cast(*it); 27 | 28 | if (child == NULL) 29 | continue; 30 | 31 | if (child->value() == value.value()) 32 | return it; 33 | } 34 | 35 | return this->children().end(); 36 | } 37 | 38 | private: 39 | std::string _str; 40 | }; 41 | 42 | } // namespace sieve 43 | -------------------------------------------------------------------------------- /src/AST/ASTStringList.cc: -------------------------------------------------------------------------------- 1 | #include "ASTStringList.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTStringList::ASTStringList( yy::location location ) 8 | : ASTNode( location ) 9 | { 10 | } 11 | 12 | 13 | void ASTStringList::accept( ASTVisitor &visitor ) { 14 | visitor.visit(this); 15 | } 16 | 17 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTStringList.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTStringList : public ASTNode { 11 | public: 12 | ASTStringList() : ASTNode() {} 13 | ASTStringList(yy::location location); 14 | 15 | void accept(ASTVisitor &visitor); 16 | 17 | std::string value() const { return "ASTStringList"; } 18 | 19 | template 20 | std::vector::const_iterator find(const T& value) const { 21 | for (std::vector::const_iterator it = this->children().begin(); it != this->children().end(); ++it) { 22 | const T* child = dynamic_cast(*it); 23 | 24 | if (child == NULL) 25 | continue; 26 | 27 | if (child->value() == value.value()) 28 | return it; 29 | } 30 | 31 | return this->children().end(); 32 | } 33 | 34 | int length() const { 35 | int length = 0; 36 | for (std::vector::const_iterator it = this->children().begin(); it != this->children().end(); ++it) { 37 | length += 1; 38 | } 39 | return length; 40 | } 41 | 42 | private: 43 | }; 44 | 45 | } // namespace sieve 46 | -------------------------------------------------------------------------------- /src/AST/ASTTag.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ASTTag.hh" 4 | #include "ASTVisitor.hh" 5 | 6 | namespace sieve 7 | { 8 | 9 | ASTTag::ASTTag( yy::location location ) 10 | : ASTNode( location ) 11 | { 12 | } 13 | 14 | ASTTag::ASTTag( std::string name) 15 | : _name(std::move( name )) 16 | { 17 | } 18 | ASTTag::ASTTag( yy::location location, std::string name) 19 | : ASTNode( location ) 20 | , _name(std::move( name )) 21 | { 22 | } 23 | 24 | void ASTTag::accept( ASTVisitor &visitor ) { 25 | visitor.visit(this); 26 | } 27 | 28 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTTag.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTNode.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTTag : public ASTNode { 11 | public: 12 | ASTTag() : ASTNode() {} 13 | explicit ASTTag(yy::location location); 14 | explicit ASTTag(std::string name); 15 | ASTTag(yy::location location, std::string name); 16 | 17 | void accept(ASTVisitor &visitor) override; 18 | 19 | [[nodiscard]] std::string value() const { return this->_name; } 20 | 21 | template 22 | std::vector::const_iterator find(const T& value) const { 23 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 24 | const T* child = dynamic_cast(*it); 25 | 26 | if (child == NULL) 27 | continue; 28 | 29 | if (child->value() == value.value()) 30 | return it; 31 | } 32 | 33 | return this->children().end(); 34 | } 35 | 36 | private: 37 | std::string _name; 38 | }; 39 | 40 | } // namespace sieve 41 | -------------------------------------------------------------------------------- /src/AST/ASTTest.cc: -------------------------------------------------------------------------------- 1 | #include "ASTTest.hh" 2 | #include "ASTVisitor.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | ASTTest::ASTTest( yy::location location ) 8 | : ASTCommand( location ) 9 | { 10 | } 11 | 12 | ASTTest::ASTTest( std::string name) 13 | : _name( name ) 14 | { 15 | } 16 | ASTTest::ASTTest( yy::location location, std::string name) 17 | : ASTCommand( location ) 18 | , _name( name ) 19 | { 20 | } 21 | 22 | void ASTTest::accept( ASTVisitor &visitor ) { 23 | visitor.visit(this); 24 | } 25 | 26 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTTest.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTCommand.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTTest : public ASTCommand { 11 | public: 12 | ASTTest() : ASTCommand() {} 13 | explicit ASTTest(yy::location location); 14 | explicit ASTTest(std::string name); 15 | ASTTest(yy::location location, std::string name); 16 | 17 | void accept(ASTVisitor &visitor) override; 18 | 19 | [[nodiscard]] std::string value() const { return this->_name; } 20 | 21 | template 22 | std::vector::const_iterator find(const T& value) const { 23 | for (auto it = this->children().begin(); it != this->children().end(); ++it) { 24 | const T* child = dynamic_cast(*it); 25 | 26 | if (child == NULL) 27 | continue; 28 | 29 | if (child->value() == value.value()) 30 | return it; 31 | } 32 | 33 | return this->children().end(); 34 | } 35 | 36 | private: 37 | std::string _name; 38 | }; 39 | 40 | } // namespace sieve 41 | -------------------------------------------------------------------------------- /src/AST/ASTTestList.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ASTTestList.hh" 4 | #include "ASTVisitor.hh" 5 | 6 | namespace sieve 7 | { 8 | 9 | ASTTestList::ASTTestList( yy::location location ) 10 | : ASTCommand( location ) 11 | { 12 | } 13 | 14 | ASTTestList::ASTTestList( std::string name) 15 | : _name(std::move( name )) 16 | { 17 | } 18 | ASTTestList::ASTTestList( yy::location location, std::string name) 19 | : ASTCommand( location ) 20 | , _name(std::move( name )) 21 | { 22 | } 23 | 24 | void ASTTestList::accept( ASTVisitor &visitor ) { 25 | visitor.visit(this); 26 | } 27 | 28 | } // namespace sieve -------------------------------------------------------------------------------- /src/AST/ASTTestList.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTCommand.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTVisitor; 9 | 10 | class ASTTestList : public ASTCommand { 11 | public: 12 | ASTTestList() : ASTCommand() {} 13 | ASTTestList(yy::location location); 14 | ASTTestList(std::string name); 15 | ASTTestList(yy::location location, std::string name); 16 | 17 | void accept(ASTVisitor &visitor); 18 | 19 | std::string value() const { return this->_name; } 20 | 21 | template 22 | std::vector::const_iterator find(const T& value) const { 23 | for (std::vector::const_iterator it = this->children().begin(); it != this->children().end(); ++it) { 24 | const T* child = dynamic_cast(*it); 25 | 26 | if (child == NULL) 27 | continue; 28 | 29 | if (child->value() == value.value()) 30 | return it; 31 | } 32 | 33 | return this->children().end(); 34 | } 35 | 36 | private: 37 | std::string _name; 38 | }; 39 | 40 | } // namespace sieve 41 | -------------------------------------------------------------------------------- /src/AST/ASTTraceVisitor.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ASTTraceVisitor.hh" 5 | 6 | namespace sieve 7 | { 8 | 9 | void ASTTraceVisitor::walk( ASTSieve *root ) { 10 | this->_traverse_tree( root, 0 ); 11 | } 12 | 13 | void ASTTraceVisitor::_traverse_tree( sieve::ASTNode *node, int indent_level ) { 14 | for (int i = 0; i < indent_level; i++) { 15 | std::cout << " "; 16 | } 17 | 18 | node->accept(*this); 19 | 20 | std::vector children = node->children(); 21 | for (auto child : children) { 22 | _traverse_tree(child, indent_level + 1); 23 | } 24 | } 25 | 26 | void ASTTraceVisitor::visit( ASTBlock* node ) { 27 | std::cout << "Block" << std::endl; 28 | } 29 | 30 | void ASTTraceVisitor::visit( ASTBoolean* node ) { 31 | if (node->value()) { 32 | std::cout << "True" << std::endl; 33 | } else { 34 | std::cout << "False" << std::endl; 35 | } 36 | } 37 | 38 | void ASTTraceVisitor::visit( ASTBranch* node ) { 39 | std::cout << "Branch" << std::endl; 40 | } 41 | 42 | void ASTTraceVisitor::visit( ASTCommand* node ) { 43 | std::cout << "Command (" << node->value() << ")" << std::endl; 44 | } 45 | 46 | void ASTTraceVisitor::visit( ASTCondition* node ) { 47 | std::cout << "Condition" << std::endl; 48 | } 49 | 50 | void ASTTraceVisitor::visit( ASTNoOp* ) { 51 | // No Op 52 | } 53 | 54 | void ASTTraceVisitor::visit( ASTNumeric* node ) { 55 | std::cout << "Numeric (" << node->value() << ")" << std::endl; 56 | } 57 | 58 | void ASTTraceVisitor::visit( ASTRequire* node ) { 59 | std::cout << "Require" << std::endl; 60 | } 61 | 62 | void ASTTraceVisitor::visit( ASTSieve* node ) { 63 | std::cout << "Mail Sieve" << std::endl; 64 | } 65 | 66 | void ASTTraceVisitor::visit( ASTString* node ) { 67 | const auto slice = node->value().substr(0, 10); 68 | if ( slice != node->value() ) { 69 | std::cout << "String (\"" << slice << "..." << "\")" << std::endl; 70 | } else { 71 | std::cout << "String (\"" << slice << "\")" << std::endl; 72 | } 73 | } 74 | 75 | void ASTTraceVisitor::visit( ASTStringList* node ) { 76 | std::cout << "String List" << std::endl; 77 | } 78 | 79 | void ASTTraceVisitor::visit( ASTTag* node ) { 80 | std::cout << "Tag (" << node->value() << ")" << std::endl; 81 | } 82 | 83 | void ASTTraceVisitor::visit( ASTTest* node ) { 84 | std::cout << "Test (" << node->value() << ")" << std::endl; 85 | } 86 | 87 | void ASTTraceVisitor::visit( ASTTestList* node ) { 88 | std::cout << "Test-List" << std::endl; 89 | } 90 | 91 | } // namespace sieve 92 | 93 | -------------------------------------------------------------------------------- /src/AST/ASTTraceVisitor.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTVisitor.hh" 4 | 5 | namespace sieve 6 | { 7 | 8 | class ASTTraceVisitor : public ASTVisitor { 9 | public: 10 | void walk( ASTSieve * ) override; 11 | 12 | void visit( ASTBlock* ) override; 13 | void visit( ASTBoolean* ) override; 14 | void visit( ASTBranch* ) override; 15 | void visit( ASTCommand* ) override; 16 | void visit( ASTCondition* ) override; 17 | void visit( ASTNoOp* ) override; 18 | void visit( ASTNumeric* ) override; 19 | void visit( ASTRequire* ) override; 20 | void visit( ASTSieve* ) override; 21 | void visit( ASTString* ) override; 22 | void visit( ASTStringList* ) override; 23 | void visit( ASTTag* ) override; 24 | void visit( ASTTest* ) override; 25 | void visit( ASTTestList* ) override; 26 | 27 | private: 28 | void _traverse_tree(sieve::ASTNode *node, int indent_level); 29 | }; 30 | 31 | } // namespace sieve 32 | -------------------------------------------------------------------------------- /src/AST/ASTVerificationVisitor.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ASTString.hh" 7 | #include "checksieve.h" 8 | #include "ASTVisitor.hh" 9 | #include "Validation/Command.hh" 10 | #include "Validation/Tag.hh" 11 | #include "Validation/Test.hh" 12 | 13 | namespace sieve 14 | { 15 | 16 | class ASTVerificationVisitor : public ASTVisitor { 17 | public: 18 | ASTVerificationVisitor( struct parse_options options); 19 | void walk( ASTSieve * ); 20 | 21 | void visit( ASTBlock* ); 22 | void visit( ASTBoolean* ); 23 | void visit( ASTBranch* ); 24 | void visit( ASTCommand* ); 25 | void visit( ASTCondition* ); 26 | void visit( ASTNoOp* ); 27 | void visit( ASTNumeric* ); 28 | void visit( ASTRequire* ); 29 | void visit( ASTSieve* ); 30 | void visit( ASTString* ); 31 | void visit( ASTStringList* ); 32 | void visit( ASTTag* ); 33 | void visit( ASTTest* ); 34 | void visit( ASTTestList* ); 35 | 36 | parse_result result() { return _verification_result; } 37 | 38 | bool requires_capability( std::string capability ) const { 39 | try { 40 | return _capability_map.at(capability); 41 | } catch (const std::out_of_range& ex) { 42 | return false; 43 | } 44 | } 45 | bool has_required( std::string require ) const { 46 | return requires_capability(require) || ( 47 | _required_capabilities != nullptr && 48 | _required_capabilities->find(ASTString(require)) != _required_capabilities->children().end()); 49 | } 50 | 51 | private: 52 | void _init(); 53 | void _traverse_tree(sieve::ASTNode *node); 54 | void _enable_capability(std::string_view capability); 55 | 56 | struct parse_options _options; 57 | parse_result _verification_result; 58 | 59 | std::map _capability_map; 60 | std::map _command_map; 61 | std::map _test_map; 62 | std::map _tag_map; 63 | std::map _require_lookup; 64 | 65 | // Validators 66 | Command _command; 67 | Tag _tag; 68 | Test _test; 69 | 70 | // Other state 71 | ASTStringList *_required_capabilities; // TODO: This should really be a vector or map 72 | }; 73 | 74 | } // namespace sieve 75 | -------------------------------------------------------------------------------- /src/AST/ASTVisitor.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ASTBlock.hh" 4 | #include "ASTBoolean.hh" 5 | #include "ASTBranch.hh" 6 | #include "ASTCommand.hh" 7 | #include "ASTCondition.hh" 8 | #include "ASTNoOp.hh" 9 | #include "ASTNumeric.hh" 10 | #include "ASTRequire.hh" 11 | #include "ASTSieve.hh" 12 | #include "ASTString.hh" 13 | #include "ASTStringList.hh" 14 | #include "ASTTag.hh" 15 | #include "ASTTest.hh" 16 | #include "ASTTestList.hh" 17 | 18 | namespace sieve 19 | { 20 | 21 | class ASTVisitor { 22 | public: 23 | virtual void walk( ASTSieve *root ) =0; 24 | 25 | virtual void visit( ASTBlock* ) =0; 26 | virtual void visit( ASTBoolean* ) =0; 27 | virtual void visit( ASTBranch* ) =0; 28 | virtual void visit( ASTCommand* ) =0; 29 | virtual void visit( ASTCondition* ) =0; 30 | virtual void visit( ASTNoOp* ) =0; 31 | virtual void visit( ASTNumeric* ) =0; 32 | virtual void visit( ASTRequire* ) =0; 33 | virtual void visit( ASTSieve* ) =0; 34 | virtual void visit( ASTString* ) =0; 35 | virtual void visit( ASTStringList* ) =0; 36 | virtual void visit( ASTTag* ) =0; 37 | virtual void visit( ASTTest* ) =0; 38 | virtual void visit( ASTTestList* ) =0; 39 | }; 40 | 41 | } // namespace sieve 42 | -------------------------------------------------------------------------------- /src/AST/Makefile.env: -------------------------------------------------------------------------------- 1 | AST_SRC = $(BASE)/src/AST/Validation/Command.cc $(BASE)/src/AST/Validation/Test.cc $(BASE)/src/AST/Validation/Tag.cc $(BASE)/src/AST/ASTTraceVisitor.cc $(BASE)/src/AST/ASTVerificationVisitor.cc \ 2 | $(BASE)/src/AST/ASTBlock.cc $(BASE)/src/AST/ASTBoolean.cc $(BASE)/src/AST/ASTBranch.cc $(BASE)/src/AST/ASTCommand.cc $(BASE)/src/AST/ASTCondition.cc $(BASE)/src/AST/ASTNoOp.cc \ 3 | $(BASE)/src/AST/ASTNumeric.cc $(BASE)/src/AST/ASTRequire.cc $(BASE)/src/AST/ASTSieve.cc $(BASE)/src/AST/ASTString.cc $(BASE)/src/AST/ASTStringList.cc $(BASE)/src/AST/ASTTag.cc \ 4 | $(BASE)/src/AST/ASTTest.cc $(BASE)/src/AST/ASTTestList.cc 5 | AST_OBJ = $(AST_SRC:%.cc=%.o) -------------------------------------------------------------------------------- /src/AST/Validation/Command.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Validation.hh" 7 | 8 | namespace sieve 9 | { 10 | 11 | class Command : public Validator { 12 | public: 13 | Command(); 14 | ~Command() = default; 15 | 16 | ValidationResult validate(const ASTNode *node) override; 17 | std::string usage(const ASTNode *node) override; 18 | 19 | friend class Test; 20 | 21 | private: 22 | // Validation functions 23 | static ValidationResult _validateAddHeadersCommand(const ASTNode *node); 24 | static ValidationResult _validateDeleteHeadersCommand(const ASTNode *node); 25 | static ValidationResult _validateIncludeCommand(const ASTNode *node); 26 | static ValidationResult _validateIMAP4FlagsAction(const ASTNode *node); 27 | static ValidationResult _validateFileintoCommand(const ASTNode *node); 28 | static ValidationResult _validateKeepCommand(const ASTNode *node); 29 | static ValidationResult _validateReplaceCommand(const ASTNode *node); 30 | static ValidationResult _validateEncloseCommand(const ASTNode *node); 31 | static ValidationResult _validateRedirectCommand(const ASTNode *node); 32 | static ValidationResult _validateSetCommand(const ASTNode *node); 33 | static ValidationResult _validateVacationCommand(const ASTNode *node); 34 | static ValidationResult _validateBareCommand(const ASTNode *node); 35 | static ValidationResult _validateSingleArgumentCommand(const ASTNode *node); 36 | static ValidationResult _validateSingleStringArgumentCommand(const ASTNode *node); 37 | static ValidationResult _validateBreakCommand(const ASTNode *node); 38 | static ValidationResult _validateForeverypartCommand(const ASTNode *node); 39 | static ValidationResult _validateExtracttextCommand(const ASTNode *node); 40 | static ValidationResult _validateExpireCommand(const ASTNode *node); 41 | static ValidationResult _validateNotifyCommand(const ASTNode *node); 42 | static ValidationResult _validateConvertCommand(const ASTNode *node); 43 | static ValidationResult _validatePipeCommand(const ASTNode *node); 44 | static ValidationResult _validateFilterCommand(const ASTNode *node); 45 | static ValidationResult _validateExecuteCommand(const ASTNode *node); 46 | static ValidationResult _validateProcesscalendarCommand(const ASTNode *node); 47 | 48 | std::map _validation_fn_map; 49 | std::map _usage_map; 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/AST/Validation/Tag.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Validation.hh" 7 | 8 | namespace sieve 9 | { 10 | 11 | class Tag : public Validator{ 12 | public: 13 | Tag(); 14 | ~Tag() = default; 15 | 16 | ValidationResult validate(const ASTNode *node) override; 17 | std::string usage(const ASTNode *node) override; 18 | 19 | private: 20 | // Validation functions 21 | ValidationResult _validateSingleString(const ASTNode *node); 22 | ValidationResult _validateSingleNumeric(const ASTNode *node); 23 | ValidationResult _validateList(const ASTNode *node); 24 | ValidationResult _validateSpecialUse(const ASTNode *node); 25 | ValidationResult _validateZone(const ASTNode *node); 26 | ValidationResult _validateByMode(const ASTNode *node); 27 | ValidationResult _validateOrganizers(const ASTNode *node); 28 | ValidationResult _validateOutcomeOrReason(const ASTNode *node); 29 | 30 | std::map _validation_fn_map; 31 | std::map _usage_map; 32 | }; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/AST/Validation/Test.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Validation.hh" 7 | 8 | namespace sieve 9 | { 10 | 11 | class Test : public Validator { 12 | public: 13 | Test(); 14 | ~Test() = default; 15 | 16 | ValidationResult validate(const ASTNode *command) override; 17 | std::string usage(const ASTNode *command) override; 18 | 19 | private: 20 | // Validation functions 21 | ValidationResult _validateExists(const ASTNode *node); 22 | ValidationResult _validateHasOnlyTestList(const ASTNode *node); 23 | ValidationResult _validateHasOnlyStringList(const ASTNode *node); 24 | ValidationResult _validateNotTest(const ASTNode *node); 25 | ValidationResult _validateSizeTest(const ASTNode *node); 26 | ValidationResult _validateValidNotifyMethodTest(const ASTNode *node); 27 | ValidationResult _validateHasExpirationTest(const ASTNode *node); 28 | ValidationResult _validateExpirationTest(const ASTNode *node); 29 | ValidationResult _validateHeaderTest(const ASTNode *node); 30 | ValidationResult _validateIhaveTest(const ASTNode *node); 31 | ValidationResult _validateEnvironmentTest(const ASTNode *node); 32 | ValidationResult _validateDuplicateTest(const ASTNode *node); 33 | ValidationResult _validateSpecialUseExistsTest(const ASTNode *node); 34 | ValidationResult _validateFilterTest(const ASTNode *node); 35 | ValidationResult _validateExecuteTest(const ASTNode *node); 36 | ValidationResult _validateEnvelopeTest(const ASTNode *node); 37 | 38 | std::map _validation_fn_map; 39 | std::map _usage_map; 40 | }; 41 | 42 | } -------------------------------------------------------------------------------- /src/AST/Validation/Validation.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ASTNode.hh" 6 | 7 | namespace sieve { 8 | 9 | template 10 | bool nodeIsType(const ASTNode *node) { 11 | const T *t_node = dynamic_cast(node); 12 | return t_node != NULL; 13 | } 14 | 15 | class ValidationResult { 16 | public: 17 | explicit ValidationResult(bool r) : _result(r), _hint_as_error(false), _offset(0) { }; 18 | ValidationResult(bool r, std::string h) : _result(r), _hint(std::move(h)), _hint_as_error(false), _offset(0) { }; 19 | ValidationResult(bool r, std::string h, bool asErr) : _result(r), _hint(std::move(h)), _hint_as_error(asErr), _offset(0) { }; 20 | ValidationResult(bool r, std::string h, bool asErr, int offset) : _result(r), _hint(std::move(h)), _hint_as_error(asErr), _offset(offset) { }; 21 | ~ValidationResult() = default; 22 | 23 | const bool result() { return this->_result; } 24 | const std::string hint() { return this->_hint; } 25 | const bool hint_as_error() { return this->_hint_as_error; } 26 | const int offset() { return this->_offset; } 27 | 28 | private: 29 | const bool _result; 30 | const std::string _hint; 31 | const bool _hint_as_error; 32 | const int _offset; 33 | }; 34 | 35 | class Validator { 36 | public: 37 | Validator() = default; 38 | ~Validator() = default; 39 | 40 | void set_visitor( const ASTVisitor *v ) { _visitor = v; } 41 | 42 | virtual ValidationResult validate( const ASTNode * ) =0; 43 | virtual std::string usage( const ASTNode * ) =0; 44 | 45 | const ASTVisitor *visitor() { return _visitor; } 46 | 47 | private: 48 | const ASTVisitor *_visitor; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | BASE = .. 2 | 3 | include $(BASE)/Makefile.shared 4 | include $(BASE)/src/Makefile.env 5 | include $(BASE)/src/AST/Makefile.env 6 | include $(BASE)/src/Server/Makefile.env 7 | 8 | all: $(BINARY_OBJ) $(LIBCHECKSIEVE_OBJ) 9 | 10 | binary: $(BINARY_OBJ) 11 | 12 | lib: $(LIBCHECKSIEVE_OBJ) $(AST_OBJ) $(SERVER_OBJ) 13 | 14 | codegen: $(BASE)/src/sieve_parser.yy $(BASE)/src/sieve_scanner.l 15 | $(YACC) --file-prefix=$(BASE)/gen/sieve_parser $(BASE)/src/sieve_parser.yy 16 | $(LEX) --header-file=$(BASE)/gen/sieve_scanner.hh --outfile $(BASE)/gen/sieve_scanner.cc $(BASE)/src/sieve_scanner.l 17 | 18 | clean: 19 | rm -f $(BINARY_OBJ) $(LIBCHECKSIEVE_OBJ) $(AST_OBJ) $(SERVER_OBJ) 20 | 21 | $(BINARY_OBJ): %.o: %.cc 22 | $(CXX) -c $(CFLAGS) $< -o $@ 23 | 24 | $(LIBCHECKSIEVE_OBJ): %.o: %.cc 25 | $(CXX) -c $(CFLAGS) $< -o $@ 26 | 27 | $(AST_OBJ): %.o: %.cc 28 | $(CXX) -c $(CFLAGS) $< -o $@ 29 | 30 | $(SERVER_OBJ): %.o: %.cc 31 | $(CXX) -c $(CFLAGS) $< -o $@ 32 | -------------------------------------------------------------------------------- /src/Makefile.env: -------------------------------------------------------------------------------- 1 | LIBCHECKSIEVE_SRC = $(BASE)/src/sieve_driver.cc $(BASE)/src/checksieve.cc $(BASE)/src/diagnostic.cc 2 | LIBCHECKSIEVE_OBJ = $(LIBCHECKSIEVE_SRC:%.cc=%.o) 3 | 4 | BINARY_SRC = $(BASE)/src/sieve.cc 5 | BINARY_OBJ = $(BINARY_SRC:.cc=.o) -------------------------------------------------------------------------------- /src/Server/MailServer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef __FreeBSD__ 14 | #include 15 | #endif 16 | 17 | #include "MailServer.hh" 18 | 19 | namespace sieve { 20 | 21 | MailServer MailServer::create(const std::string& host_with_port) 22 | { 23 | bool in_port = false; 24 | std::string hostname, port; 25 | 26 | for (char const &c: host_with_port) 27 | { 28 | if (c == ':') 29 | { 30 | in_port = true; 31 | continue; 32 | } 33 | 34 | if (in_port) 35 | port += c; 36 | else 37 | hostname += c; 38 | } 39 | 40 | return {hostname, static_cast(atoi(port.c_str()))}; 41 | } 42 | 43 | MailServer::MailServer(std::string hostname, uint32_t port) 44 | : _hostname(std::move(hostname)) 45 | , _port(port) 46 | , _socket(-1) 47 | { 48 | } 49 | 50 | MailServer::~MailServer() 51 | { 52 | close(_socket); 53 | } 54 | 55 | std::map MailServer::capabilities() 56 | { 57 | auto capabilities = std::map(); 58 | 59 | this->_connect(); 60 | 61 | auto hello_dictionary = sieve::MailServer::_parse_response(_greeting); 62 | 63 | std::string capability; 64 | for (char const &c: hello_dictionary["sieve"]) 65 | { 66 | if (c == ' ') 67 | { 68 | capabilities[capability] = true; 69 | capability = ""; 70 | continue; 71 | } 72 | 73 | capability += c; 74 | } 75 | 76 | return capabilities; 77 | } 78 | 79 | void MailServer::_connect() 80 | { 81 | if (_socket >= 0) 82 | return; 83 | 84 | if ((_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 85 | std::cout << "Socket creation error" << std::endl; 86 | abort(); 87 | } 88 | 89 | struct sockaddr_in server_address{}; 90 | server_address.sin_family = AF_INET; 91 | server_address.sin_port = htons(this->_port); 92 | 93 | struct hostent *host_entry; 94 | struct in_addr **address_list; 95 | 96 | if ((host_entry = gethostbyname(this->_hostname.c_str())) == nullptr) { 97 | herror("gethostbyname"); 98 | abort(); 99 | } 100 | 101 | address_list = (struct in_addr **) host_entry->h_addr_list; 102 | 103 | char ip[100] = {}; 104 | if (address_list[0] == nullptr) 105 | { 106 | std::cout << "Could not map hostname: " << this->_hostname << " to an IP address." << std::endl; 107 | abort(); 108 | } 109 | strcpy(ip, inet_ntoa(*address_list[0])); 110 | 111 | if (inet_pton(AF_INET, ip, &server_address.sin_addr) <= 0) 112 | { 113 | std::cout << "Invalid address / Address not supported" << ip << std::endl; 114 | abort(); 115 | } 116 | 117 | if (connect(_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) 118 | { 119 | std::cout << "Connection Failed" << std::endl; 120 | abort(); 121 | } 122 | 123 | char buffer[1024] = {}; 124 | if (send(_socket, "", 0, 0) == -1) { 125 | std::cout << "Could not send to socket: " << strerror(errno) << std::endl; 126 | abort(); 127 | } 128 | 129 | if (read(_socket, buffer, 1024) == -1) { 130 | std::cout << "Could not read from socket: " << strerror(errno) << std::endl; 131 | abort(); 132 | } 133 | 134 | _greeting = std::string(buffer); 135 | } 136 | 137 | std::map MailServer::_parse_response(const std::string& response) 138 | { 139 | auto dictionary = std::map(); 140 | 141 | std::istringstream response_stream(response); 142 | for (std::string line; std::getline(response_stream, line);) 143 | { 144 | bool in_key = false, in_value = false; 145 | bool key_found = false, value_found = false; 146 | std::string key, value; 147 | for (char const &c: line) 148 | { 149 | if (c == '"') 150 | { 151 | if (!in_key && !key_found) { 152 | in_key = true; 153 | continue; 154 | } 155 | 156 | if (in_key) { 157 | in_key = false; 158 | key_found = true; 159 | continue; 160 | } 161 | 162 | if (!in_value && !value_found) { 163 | in_value = true; 164 | continue; 165 | } 166 | 167 | if (in_value) { 168 | in_value = false; 169 | value_found = true; 170 | continue; 171 | } 172 | } else { 173 | if (in_key) 174 | key += (char)tolower(c); 175 | 176 | if (in_value) 177 | value += c; 178 | } 179 | } 180 | 181 | dictionary[key] = value; 182 | } 183 | 184 | return dictionary; 185 | } 186 | 187 | 188 | } //-- namespace sieve -------------------------------------------------------------------------------- /src/Server/MailServer.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace sieve{ 9 | 10 | class MailServer { 11 | public: 12 | static MailServer create(const std::string& host_with_port); 13 | ~MailServer(); 14 | 15 | std::map capabilities(); 16 | 17 | protected: 18 | MailServer(std::string hostname, uint32_t port); 19 | 20 | private: 21 | void _connect(); 22 | static std::map _parse_response(const std::string& response); 23 | 24 | std::string _hostname; 25 | uint32_t _port; 26 | int _socket; 27 | 28 | std::string _greeting; 29 | }; 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/Server/Makefile.env: -------------------------------------------------------------------------------- 1 | SERVER_SRC = $(BASE)/src/Server/MailServer.cc 2 | SERVER_OBJ = $(SERVER_SRC:%.cc=%.o) -------------------------------------------------------------------------------- /src/checksieve.cc: -------------------------------------------------------------------------------- 1 | #include "checksieve.h" 2 | #include "sieve_driver.hh" 3 | 4 | namespace sieve 5 | { 6 | 7 | #define LIBCHECKSIEVE_VERSION "0.11-dev" 8 | 9 | extern const char *version() { 10 | return LIBCHECKSIEVE_VERSION; 11 | } 12 | 13 | struct parse_result sieve_parse_file( const char *filename, struct parse_options options ) { 14 | driver driver(std::move(options)); 15 | return driver.parse_file(filename); 16 | } 17 | 18 | struct parse_result sieve_parse_string( const char *sieve, struct parse_options options ) { 19 | driver driver(std::move(options)); 20 | return driver.parse_string(sieve); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/checksieve.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef PLATFORM 4 | #if defined __APPLE__ && defined __MACH__ 5 | #define PLATFORM "Darwin" 6 | #endif 7 | #endif 8 | 9 | #ifdef DEBUG 10 | #define DEBUGLOG(x) std::cout << "DEBUG: " << x << std::endl; 11 | #else 12 | #define DEBUGLOG(x) 13 | #endif 14 | 15 | #include 16 | #include 17 | #include "location.hh" 18 | 19 | namespace sieve 20 | { 21 | 22 | struct parse_result { 23 | int status; 24 | yy::location location; 25 | std::string error; 26 | std::string hint; 27 | 28 | void set_error( std::string err ) { 29 | status = 1; 30 | error = std::move(err); 31 | } 32 | 33 | void set_error( std::string err, yy::location loc ) { 34 | status = 1; 35 | error = std::move(err); 36 | location = loc; 37 | } 38 | }; 39 | 40 | struct parse_options { 41 | parse_options() : string_list_max_length(0), all_supported_capabilities(true) {} 42 | int string_list_max_length; 43 | std::map capabilities; 44 | bool all_supported_capabilities; 45 | }; 46 | 47 | struct parse_result sieve_parse_file( const char *filename, struct parse_options options ); 48 | struct parse_result sieve_parse_string( const char *sieve, struct parse_options options ); 49 | 50 | const char *version(); 51 | 52 | } // namespace sieve 53 | -------------------------------------------------------------------------------- /src/diagnostic.cc: -------------------------------------------------------------------------------- 1 | #include "diagnostic.hh" 2 | 3 | #include 4 | #include 5 | 6 | namespace sieve { 7 | 8 | std::string Diagnostic::describe( parse_result& result, std::ifstream &input ) const { 9 | std::string line; 10 | std::ostringstream output; 11 | 12 | for (int i = 1; getline(input, line, '\n'); i++) { 13 | if (i == result.location.begin.line) 14 | break; 15 | } 16 | 17 | // Account for tabs in our input 18 | for (int i = 0; i < result.location.begin.column; i++) { 19 | if (line[i] == '\t') { 20 | line.replace(i, 1, std::string(_tab_size, ' ')); 21 | result.location.begin.column += _tab_size - 1; 22 | result.location.end.column += _tab_size - 1; 23 | } 24 | } 25 | 26 | output << result.error << std::endl 27 | << "On line " << result.location.begin.line << ":" << std::endl 28 | << line << std::endl 29 | << std::string(result.location.begin.column - 1, ' ') 30 | << std::string(result.location.end.column - result.location.begin.column, '^') 31 | << std::endl; 32 | 33 | if (!result.hint.empty()) { 34 | output << result.hint << std::endl; 35 | } 36 | 37 | return output.str(); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/diagnostic.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "checksieve.h" 7 | 8 | namespace sieve { 9 | 10 | class Diagnostic { 11 | public: 12 | Diagnostic() : _tab_size( 4 ) { } 13 | explicit Diagnostic(size_t tab_size) : _tab_size( tab_size ) { } 14 | 15 | std::string describe(parse_result& result, std::ifstream &input) const; 16 | 17 | private: 18 | size_t _tab_size; 19 | }; 20 | 21 | } // namespace sieve 22 | 23 | -------------------------------------------------------------------------------- /src/python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * python.cc 3 | * 4 | * This python module lets us easily test libchecksieve using python. The 5 | * 'test' target will automatically compile this against libchecksieve.a 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include "checksieve.h" 12 | #include "sieve_driver.hh" 13 | 14 | static PyObject *parse_string(PyObject *, PyObject *); 15 | static PyObject *parse_string_with_options(PyObject *self, PyObject *args); 16 | 17 | static PyMethodDef checksieve_methods[] = { 18 | {"parse_string", parse_string, METH_VARARGS, "guhhh"}, 19 | {"parse_string_with_options", parse_string_with_options, METH_VARARGS, "Parse a sieve buffer with the specified options"}, 20 | {NULL, NULL} 21 | }; 22 | 23 | #define PY_MODNAME "checksieve" 24 | #define PY_MODDESC "syntax-check a mail sieve" 25 | 26 | #if PY_MAJOR_VERSION >= 3 27 | #define PY_MODINIT(name) PyMODINIT_FUNC PyInit_##name(void) 28 | 29 | static struct PyModuleDef moduledef = { 30 | PyModuleDef_HEAD_INIT, 31 | PY_MODNAME, 32 | PY_MODDESC, 33 | -1, 34 | checksieve_methods, 35 | NULL, 36 | NULL, 37 | NULL, 38 | NULL, 39 | }; 40 | 41 | #define PY_MODCREATE() return PyModule_Create(&moduledef) 42 | #else 43 | #define PY_MODINIT(name) PyMODINIT_FUNC init##name(void) 44 | #define PY_MODCREATE() Py_InitModule3( PY_MODNAME, checksieve_methods, PY_MODDESC ) 45 | #endif // PY_MAJOR_VERSION >= 3 46 | 47 | static PyObject *parse_string(PyObject *self, PyObject *args) { 48 | const char *sieve; 49 | PyObject *quiet; 50 | 51 | if (!PyArg_ParseTuple(args, "sO:parse_string", &sieve, &quiet)) 52 | return NULL; 53 | 54 | sieve::driver driver; 55 | 56 | sieve::parse_result result = driver.parse_string(sieve); 57 | 58 | if (quiet == Py_False && result.status) { 59 | std::cerr << result.error << std::endl; 60 | } 61 | 62 | return Py_BuildValue("i", result.status); 63 | } 64 | 65 | static PyObject *parse_string_with_options(PyObject *self, PyObject *args) { 66 | const char *sieve; 67 | PyObject *options; 68 | PyObject *value; 69 | struct sieve::parse_options opts; 70 | 71 | if (!PyArg_ParseTuple(args, "sO:parse_string", &sieve, &options)) 72 | return NULL; 73 | 74 | // TODO: Set error. 75 | if (!PyDict_Check(options)) 76 | return NULL; 77 | 78 | // Pull out max line length 79 | value = PyDict_GetItem(options, PyUnicode_FromString("string_list_max_length")); 80 | if (value != NULL) { 81 | opts.string_list_max_length = int(PyLong_AsLong(value)); 82 | } 83 | 84 | sieve::driver driver(opts); 85 | return Py_BuildValue("i", driver.parse_string(sieve).status); 86 | } 87 | 88 | PY_MODINIT(checksieve) { 89 | PY_MODCREATE(); 90 | } 91 | -------------------------------------------------------------------------------- /src/sieve.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "AST.hh" 5 | #include "checksieve.h" 6 | #include "diagnostic.hh" 7 | #include "sieve_driver.hh" 8 | #include "MailServer.hh" 9 | 10 | const char *usage_string = 11 | "Usage: check-sieve [options] file1 [file2 ...] \n" 12 | " \n" 13 | "Options: \n" 14 | " -h, --help Show this message \n" 15 | " --max-list-length N Flag lists over a certain length (default: none) \n" 16 | " --server : Only allow capabilities advertised by the specified \n" 17 | " server. \n" 18 | " --trace-parser Trace the operation of the parser \n" 19 | " --trace-scanner Trace the operation of the scanner \n" 20 | " --trace-tree Trace the abstract-syntax-tree \n" 21 | " --version Print out version information \n"; 22 | 23 | void print_version() { 24 | std::cout << "check-sieve, version " << sieve::version() << " (" << PLATFORM << ")" << std::endl; 25 | } 26 | 27 | void print_help() { 28 | std::cout << usage_string << std::endl; 29 | } 30 | 31 | bool file_exists(const char *filename) { 32 | std::ifstream file(filename); 33 | return (bool)file; 34 | } 35 | 36 | int main( int argc, char *argv[] ) { 37 | int result = 0; 38 | bool trace_scanning = false; 39 | bool trace_parsing = false; 40 | bool trace_tree = false; 41 | sieve::Diagnostic diag; 42 | struct sieve::parse_options options; 43 | 44 | if (argc == 1) { 45 | print_help(); 46 | return 1; 47 | } 48 | 49 | for (int i = 1; i < argc; ++i) { 50 | // Long argument 51 | if (argv[i][0] == '-' && argv[i][1] == '-') { 52 | if (strcmp(argv[i], "--max-list-length") == 0) { 53 | if (i + 1 >= argc) { 54 | std::cerr << "Expected a number after --max-list-length." << std::endl; 55 | return 1; 56 | } 57 | options.string_list_max_length = atoi(argv[i+1]); 58 | i++; 59 | continue; 60 | } 61 | 62 | if (strcmp(argv[i], "--trace-scanner") == 0) { 63 | trace_scanning = true; 64 | continue; 65 | } 66 | 67 | if (strcmp(argv[i], "--trace-parser") == 0) { 68 | trace_parsing = true; 69 | continue; 70 | } 71 | 72 | if (strcmp(argv[i], "--trace-tree") == 0) { 73 | trace_tree = true; 74 | continue; 75 | } 76 | 77 | if (strcmp(argv[i], "--help") == 0) { 78 | print_help(); 79 | return 0; 80 | } 81 | 82 | if (strcmp(argv[i], "--version") == 0) { 83 | print_version(); 84 | return 0; 85 | } 86 | 87 | if (strcmp(argv[i], "--server") == 0) { 88 | if (i + 1 >= argc) 89 | { 90 | std::cerr << "Expected a connection string after --server" << std::endl; 91 | return 1; 92 | } 93 | 94 | auto server = sieve::MailServer::create(argv[i+1]); 95 | options.all_supported_capabilities = false; 96 | options.capabilities = server.capabilities(); 97 | i++; 98 | continue; 99 | } 100 | 101 | std::cerr << "Unrecognized argument \"" << argv[i] << "\"" << std::endl; 102 | return 1; 103 | } 104 | // Short argument 105 | else if (argv[i][0] == '-') { 106 | if (strcmp(argv[i], "-h") == 0) { 107 | print_help(); 108 | return 0; 109 | } 110 | } 111 | // Sieve file 112 | else { 113 | if (!file_exists(argv[i])) { 114 | std::cerr << "Could not find file \"" << argv[i] << "\"." << std::endl; 115 | result = 2; 116 | break; 117 | } 118 | 119 | sieve::driver driver(options); 120 | 121 | // Configure driver 122 | driver.trace_scanning = trace_scanning; 123 | driver.trace_parsing = trace_parsing; 124 | driver.trace_tree = trace_tree; 125 | 126 | sieve::parse_result parse_res = driver.parse_file( argv[i] ); 127 | 128 | if ( !parse_res.status ) { 129 | if (driver.trace_tree) { 130 | sieve::ASTTraceVisitor visitor = sieve::ASTTraceVisitor(); 131 | visitor.walk(driver.syntax_tree()); 132 | } else { 133 | std::cout << argv[i] << ": No errors found!" << std::endl; 134 | } 135 | } else { 136 | std::ifstream fin( argv[i] ); 137 | std::cerr << "Errors found in \"" << argv[i] << "\":" << std::endl << std::endl; 138 | std::cerr << diag.describe(parse_res, fin); 139 | result = 1; 140 | } 141 | } 142 | } 143 | 144 | return result; 145 | } 146 | -------------------------------------------------------------------------------- /src/sieve_driver.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "sieve_driver.hh" 6 | #include "sieve_parser.tab.hh" 7 | #include "AST.hh" 8 | 9 | namespace sieve 10 | { 11 | 12 | driver::driver() 13 | : trace_scanning( false ) 14 | , trace_parsing( false ) 15 | , trace_tree( false ) 16 | , result( 0 ) 17 | , _options() 18 | , yyscanner() 19 | , _sieve() {} 20 | 21 | driver::driver( struct parse_options options ) 22 | : trace_scanning( false ) 23 | , trace_parsing( false ) 24 | , trace_tree( false ) 25 | , result( 0 ) 26 | , _options( options ) 27 | , yyscanner() 28 | , _sieve() {} 29 | 30 | driver::~driver() = default; 31 | 32 | parse_result driver::parse_file( const std::string &fp ) { 33 | std::string line; 34 | std::string buffer; 35 | std::ifstream file( fp ); 36 | 37 | filepath = fp; 38 | 39 | while (!file.eof()) { 40 | getline(file, line); 41 | buffer += line + "\n"; 42 | } 43 | 44 | file.seekg(0, std::ifstream::beg); 45 | 46 | return parse_string(buffer); 47 | } 48 | 49 | parse_result driver::parse_string( const std::string &sieve ) { 50 | parse_result res; 51 | 52 | res.status = 0; 53 | sieve_string = sieve + "\n"; 54 | scan_begin( sieve_string ); 55 | yy::sieve_parser parser( yyscanner, *this ); 56 | parser.set_debug_level( trace_parsing ); 57 | parser.parse(); 58 | scan_end(); 59 | 60 | if ( result ) 61 | res = _errors[0]; 62 | 63 | if ( !result ) { 64 | ASTVerificationVisitor visitor = ASTVerificationVisitor( _options ); 65 | visitor.walk(syntax_tree()); 66 | 67 | res = visitor.result(); 68 | result = res.status; 69 | } 70 | 71 | return res; 72 | } 73 | 74 | void driver::push_error( const yy::location &l, const std::string &message ) { 75 | result = 1; 76 | _errors.push_back({ 1, l, message }); 77 | } 78 | 79 | } // namespace sieve 80 | -------------------------------------------------------------------------------- /src/sieve_driver.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "sieve_parser.tab.hh" 10 | #include "checksieve.h" 11 | #include "AST.hh" 12 | 13 | #define YY_DECL \ 14 | yy::sieve_parser::symbol_type yylex( yyscan_t yyscanner, sieve::driver &driver ) 15 | YY_DECL; 16 | 17 | namespace sieve 18 | { 19 | 20 | class driver { 21 | public: 22 | driver(); 23 | driver(struct parse_options options); 24 | virtual ~driver(); 25 | 26 | void scan_begin(); 27 | void scan_begin( const std::string &ifstream); 28 | void scan_end(); 29 | bool trace_scanning; 30 | 31 | parse_result parse_file( const std::string &fp ); 32 | parse_result parse_string( const std::string &sieve ); 33 | 34 | std::string filepath; 35 | std::string sieve_string; 36 | bool trace_parsing; 37 | bool trace_tree; 38 | 39 | int result; 40 | 41 | void push_error( const yy::location &l, const std::string &message ); 42 | 43 | ASTSieve *syntax_tree() { return _sieve; } 44 | ASTSieve *syntax_tree(ASTSieve *sieve) { _sieve = sieve; return _sieve; } 45 | 46 | struct parse_options parse_options() { return _options; } 47 | 48 | private: 49 | struct parse_options _options; 50 | std::istringstream _input_stream; 51 | yyscan_t yyscanner; 52 | 53 | ASTSieve *_sieve; 54 | 55 | std::vector _errors; 56 | }; 57 | 58 | } // namespace sieve 59 | -------------------------------------------------------------------------------- /src/sieve_parser.yy: -------------------------------------------------------------------------------- 1 | %skeleton "lalr1.cc" /* -*- C++ -*- */ 2 | %require "3.2" 3 | %defines 4 | %define api.parser.class {sieve_parser} 5 | 6 | %define api.token.constructor 7 | %define api.value.type variant 8 | 9 | %lex-param {yyscan_t scanner} {sieve::driver &driver} 10 | %parse-param {yyscan_t scanner} {sieve::driver &driver} 11 | 12 | %code requires { 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "AST.hh" 19 | 20 | namespace sieve { 21 | class driver; 22 | } 23 | 24 | typedef void* yyscan_t; 25 | } 26 | 27 | %locations 28 | %initial-action { 29 | @$.begin.filename = @$.end.filename = &driver.filepath; 30 | } 31 | 32 | %define parse.trace 33 | %define parse.error verbose 34 | 35 | %code { 36 | #include "sieve_driver.hh" 37 | #include "sieve_scanner.hh" 38 | } 39 | 40 | %define api.token.prefix {TOK_} 41 | %token 42 | END 0 "end of file" 43 | REQUIRE "require" 44 | STRING "string" 45 | IF "if" 46 | ELSIF "elsif" 47 | ELSE "else" 48 | SEMICOLON ";" 49 | LPAREN "(" 50 | RPAREN ")" 51 | LBRACKET "[" 52 | RBRACKET "]" 53 | LCURLY "{" 54 | RCURLY "}" 55 | COMMA "," 56 | QUANTIFIER "quantifier" 57 | ; 58 | 59 | 60 | %token IDENTIFIER "identifier" 61 | %token FOREVERYPART "foreverypart" 62 | %token TAG ":tag" 63 | %token STRING_LITERAL 64 | %token NUMBER "number" 65 | %token TRUE "true" 66 | %token FALSE "false" 67 | 68 | %type numeric 69 | %type > argument 70 | %type > arguments 71 | %type > test 72 | %type > tests 73 | %type > test_list 74 | 75 | %type > strings 76 | %type > string_list 77 | 78 | %type command 79 | %type > command_list 80 | %type block 81 | %type if_flow 82 | 83 | %type sieve 84 | 85 | %% 86 | %start sieve; 87 | 88 | sieve : END 89 | { 90 | sieve::ASTSieve *sieve = new sieve::ASTSieve(@1); 91 | driver.syntax_tree(sieve); 92 | $$ = sieve; 93 | } 94 | | command_list 95 | { 96 | sieve::ASTSieve *sieve = new sieve::ASTSieve(@1); 97 | sieve->push($1); 98 | driver.syntax_tree(sieve); 99 | $$ = sieve; 100 | } 101 | ; 102 | 103 | command_list : command { $$ = std::vector(1, $1); } 104 | | command_list command { $1.push_back($2); $$ = $1; } 105 | ; 106 | 107 | command : 108 | REQUIRE string_list SEMICOLON 109 | { 110 | sieve::ASTRequire *require = new sieve::ASTRequire(@1); 111 | if ($2.size() > 1) { 112 | sieve::ASTStringList *stringList = new sieve::ASTStringList(@2); 113 | stringList->push($2); 114 | require->push(stringList); 115 | } else { 116 | require->push($2); 117 | } 118 | $$ = dynamic_cast(require); 119 | } 120 | | IDENTIFIER arguments SEMICOLON 121 | { 122 | sieve::ASTCommand *command = new sieve::ASTCommand(@1, $1); 123 | command->push($2); 124 | $$ = dynamic_cast(command); 125 | } 126 | | IDENTIFIER SEMICOLON 127 | { 128 | sieve::ASTCommand *command = new sieve::ASTCommand(@1, $1); 129 | $$ = dynamic_cast(command); 130 | } 131 | | FOREVERYPART block 132 | { 133 | sieve::ASTCommand *command = new sieve::ASTCommand(@1, $1); 134 | command->push($2); 135 | $$ = dynamic_cast(command); 136 | } 137 | | FOREVERYPART TAG STRING_LITERAL block 138 | { 139 | sieve::ASTCommand *command = new sieve::ASTCommand(@1, $1); 140 | command->push(new sieve::ASTTag(@2, $2)); 141 | command->push(new sieve::ASTString(@3, $3)); 142 | command->push($4); 143 | $$ = dynamic_cast(command); 144 | } 145 | | if_flow { $$ = $1; } 146 | | if_flow ELSE block 147 | { 148 | $1->push($3); 149 | $$ = $1; 150 | } 151 | ; 152 | 153 | block : "{" command_list "}" 154 | { 155 | sieve::ASTBlock *block = new sieve::ASTBlock( @1 ); 156 | block->push($2); 157 | $$ = dynamic_cast( block ); 158 | } 159 | | "{" "}" 160 | { 161 | sieve::ASTNoOp *noop = new sieve::ASTNoOp( @1 ); 162 | $$ = noop; 163 | } 164 | ; 165 | 166 | if_flow : IF test block 167 | { 168 | sieve::ASTBranch *branch = new sieve::ASTBranch( @1 ); 169 | sieve::ASTCondition *condition = new sieve::ASTCondition( @2 ); 170 | condition->push($2); 171 | branch->push(condition); 172 | branch->push($3); 173 | $$ = dynamic_cast(branch); 174 | 175 | } 176 | | IF "(" test ")" block 177 | { 178 | sieve::ASTBranch *branch = new sieve::ASTBranch( @1 ); 179 | sieve::ASTCondition *condition = new sieve::ASTCondition( @3 ); 180 | condition->push($3); 181 | branch->push(condition); 182 | branch->push($5); 183 | $$ = dynamic_cast(branch); 184 | 185 | } 186 | | if_flow ELSIF test block 187 | { 188 | sieve::ASTCondition *condition = new sieve::ASTCondition( @3 ); 189 | condition->push($3); 190 | $1->push(condition); 191 | $1->push($4); 192 | $$ = $1; 193 | } 194 | ; 195 | 196 | arguments : argument { $$ = $1; } 197 | | argument test { $1.insert($1.end(), $2.begin(), $2.end()); $$ = $1; } 198 | | argument test_list { $1.insert($1.end(), $2.begin(), $2.end()); $$ = $1; } 199 | | arguments argument { $1.insert($1.end(), $2.begin(), $2.end()); $$ = $1; } 200 | | test { $$ = $1; } 201 | | test_list { $$ = $1; } 202 | ; 203 | 204 | argument : string_list 205 | { 206 | if ($1.size() > 1) { 207 | sieve::ASTStringList *stringList = new sieve::ASTStringList(@1); 208 | stringList->push($1); 209 | $$ = std::vector( 1, dynamic_cast(stringList)); 210 | } else { 211 | $$ = $1; 212 | } 213 | } 214 | | numeric 215 | { 216 | $$ = std::vector( 1, dynamic_cast($1)); 217 | } 218 | | TAG 219 | { 220 | sieve::ASTTag *tag = new sieve::ASTTag(@1, $1); 221 | $$ = std::vector( 1, dynamic_cast(tag)); 222 | } 223 | ; 224 | 225 | test_list : "(" tests ")" 226 | { 227 | sieve::ASTTestList *testList = new sieve::ASTTestList( @1 ); 228 | testList->push($2); 229 | $$ = std::vector(1, testList); 230 | } 231 | ; 232 | 233 | tests : test { $$ = $1; } 234 | | tests "," test { $1.insert($1.end(), $3.begin(), $3.end()); $$ = $1; } 235 | ; 236 | 237 | test : 238 | IDENTIFIER arguments 239 | { 240 | sieve::ASTTest *test = new sieve::ASTTest(@1, $1); 241 | test->push($2); 242 | $$ = std::vector(1, test); 243 | } 244 | | IDENTIFIER { $$ = std::vector(1, new sieve::ASTTest(@1, $1)); } 245 | | TRUE { $$ = std::vector(1, new sieve::ASTBoolean(@1, $1)); } 246 | | FALSE { $$ = std::vector(1, new sieve::ASTBoolean(@1, $1)); } 247 | ; 248 | 249 | string_list : STRING_LITERAL {$$ = std::vector(1, new sieve::ASTString(@1, $1)); } 250 | | "[" strings "]" { $$ = $2; } 251 | ; 252 | 253 | strings : STRING_LITERAL {$$ = std::vector(1, new sieve::ASTString(@1, $1)); } 254 | | strings "," STRING_LITERAL { $1.push_back( new sieve::ASTString(@3, $3)); $$ = $1; } 255 | ; 256 | 257 | numeric : 258 | NUMBER 259 | { 260 | $$ = new sieve::ASTNumeric(@1, $1); 261 | } 262 | | NUMBER QUANTIFIER 263 | { 264 | // TODO: Somehow incorporate the quantifier in here 265 | $$ = new sieve::ASTNumeric(@1, $1); 266 | } 267 | ; 268 | 269 | %% 270 | 271 | void yy::sieve_parser::error( const location_type &l, const std::string &m ) { 272 | driver.push_error(l, m); 273 | } 274 | -------------------------------------------------------------------------------- /src/sieve_scanner.l: -------------------------------------------------------------------------------- 1 | %{ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "sieve_parser.tab.hh" 8 | #include "sieve_driver.hh" 9 | 10 | static yy::location loc; 11 | 12 | std::string multiline_buffer; 13 | %} 14 | 15 | %option nounput batch debug noinput reentrant 16 | 17 | comment #.*$ 18 | id [a-zA-Z_][a-zA-Z0-9_]* 19 | int [0-9]+/[KGM]? 20 | quant [KGM]? 21 | blank [ \t] 22 | tag :[a-zA-Z_][a-zA-Z0-9_]* 23 | 24 | %{ 25 | #define YY_USER_ACTION loc.columns (yyleng); 26 | %} 27 | 28 | %x COMMENT MULTILINE STRING 29 | 30 | %% 31 | 32 | %{ 33 | loc.step(); 34 | %} 35 | 36 | {blank}+ loc.step(); 37 | [\n]+ loc.lines(yyleng); loc.step(); 38 | [\r\n]+ loc.lines(yyleng/2); loc.step(); 39 | 40 | {comment} loc.step(); 41 | 42 | "/*" { BEGIN(COMMENT); loc.step(); } 43 | "*/" { BEGIN(0); loc.step(); } 44 | [^*\r\n]+ { loc.step(); } 45 | "*" { loc.step(); } 46 | (\n|\r\n) { loc.lines(yyleng); loc.step(); } 47 | 48 | ["] { BEGIN(STRING); loc.step(); multiline_buffer = ""; } 49 | \\\" { loc.step(); multiline_buffer += '"'; } 50 | \\ { loc.step(); multiline_buffer += yytext; } 51 | ["] { loc.step(); BEGIN(0); return yy::sieve_parser::make_STRING_LITERAL( multiline_buffer, loc); } 52 | [^"\\\r\n]* { loc.step(); multiline_buffer += yytext; } 53 | (\n|\r\n)+ { loc.lines(yyleng); loc.step(); multiline_buffer += yytext; } 54 | 55 | "text:"[ \t]*(\n|\r\n) { BEGIN(MULTILINE); loc.step(); loc.lines(yyleng); multiline_buffer = ""; } 56 | "text:"[ \t]*#.*(\n|\r\n) { BEGIN(MULTILINE); loc.step(); loc.lines(yyleng); multiline_buffer = ""; } 57 | [^\.][^\r\n]*(\n|\r\n) { loc.lines(yyleng); loc.step(); multiline_buffer += yytext; } 58 | [\.](\n|\r\n) { loc.lines(yyleng); loc.step(); BEGIN(0); return yy::sieve_parser::make_STRING_LITERAL( multiline_buffer, loc); } 59 | 60 | ";" return yy::sieve_parser::make_SEMICOLON( loc ); 61 | "[" return yy::sieve_parser::make_LBRACKET( loc ); 62 | "]" return yy::sieve_parser::make_RBRACKET( loc ); 63 | "(" return yy::sieve_parser::make_LPAREN( loc ); 64 | ")" return yy::sieve_parser::make_RPAREN( loc ); 65 | "{" return yy::sieve_parser::make_LCURLY( loc ); 66 | "}" return yy::sieve_parser::make_RCURLY( loc ); 67 | "," return yy::sieve_parser::make_COMMA( loc ); 68 | "true" return yy::sieve_parser::make_TRUE( true, loc ); 69 | "false" return yy::sieve_parser::make_FALSE( false, loc ); 70 | "require" return yy::sieve_parser::make_REQUIRE( loc ); 71 | "foreverypart" return yy::sieve_parser::make_FOREVERYPART( std::string(yytext), loc ); 72 | 73 | "if" return yy::sieve_parser::make_IF( loc ); 74 | "elsif" return yy::sieve_parser::make_ELSIF( loc ); 75 | "else" return yy::sieve_parser::make_ELSE( loc ); 76 | 77 | [0-9]+/[KGM]? { 78 | errno = 0; 79 | long n = strtol( yytext, NULL, 10 ); 80 | if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE)) 81 | driver.push_error(loc, "integer is out of range"); 82 | return yy::sieve_parser::make_NUMBER( n, loc ); 83 | } 84 | [KGM]? return yy::sieve_parser::make_QUANTIFIER( loc ); 85 | {id} return yy::sieve_parser::make_IDENTIFIER( yytext, loc ); 86 | {tag} return yy::sieve_parser::make_TAG( yytext, loc ); 87 | 88 | . driver.push_error( loc, "invalid character" ); 89 | <> return yy::sieve_parser::make_END( loc ); 90 | %% 91 | 92 | int yywrap(yyscan_t yyscanner) 93 | { 94 | return 1; 95 | } 96 | 97 | 98 | void sieve::driver::scan_begin() { 99 | struct yyguts_t * yyg; 100 | 101 | yylex_init(&yyscanner); 102 | yyg = (struct yyguts_t*)yyscanner; 103 | loc = yy::location(); 104 | 105 | if (!(yyin = fopen(filepath.c_str(), "r"))) { 106 | push_error(yy::location(), "cannot open " + filepath + ": " + strerror(errno)); 107 | exit( EXIT_FAILURE ); 108 | } 109 | } 110 | 111 | void sieve::driver::scan_begin(const std::string &sieve) { 112 | struct yyguts_t *yyg; 113 | 114 | yylex_init(&yyscanner); 115 | yyg = (struct yyguts_t*)yyscanner; 116 | loc = yy::location(); 117 | 118 | yy_scan_string(sieve.c_str(), yyscanner); 119 | } 120 | 121 | void sieve::driver::scan_end() { 122 | yylex_destroy(yyscanner); 123 | } 124 | -------------------------------------------------------------------------------- /src/webchecksieve.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "checksieve.h" 4 | 5 | using namespace emscripten; 6 | 7 | EMSCRIPTEN_BINDINGS(sieve) { 8 | 9 | value_object("position") 10 | .field("column", &yy::position::column) 11 | .field("line", &yy::position::line) 12 | ; 13 | value_object("location") 14 | .field("begin", &yy::location::begin) 15 | .field("end", &yy::location::end) 16 | ; 17 | value_object("parse_result") 18 | .field("location", &sieve::parse_result::location) 19 | .field("status", &sieve::parse_result::status) 20 | .field("error", &sieve::parse_result::error) 21 | .field("hint", &sieve::parse_result::hint) 22 | ; 23 | class_("parse_options") 24 | .constructor<>() 25 | ; 26 | 27 | function("version", optional_override( 28 | [](){ 29 | return std::string(sieve::version()); 30 | })); 31 | function("sieve_parse_string", optional_override( 32 | [](const std::string s, struct sieve::parse_options o) { 33 | return sieve_parse_string(s.c_str(), o); 34 | })); 35 | } 36 | -------------------------------------------------------------------------------- /test/3028/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/3028/__init__.py -------------------------------------------------------------------------------- /test/3028/comments_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestComments(unittest.TestCase): 5 | 6 | def test_single_line(self): 7 | sieve = '# This is a comment' 8 | self.assertFalse(checksieve.parse_string(sieve, False)) 9 | 10 | def test_single_line_with_code(self): 11 | sieve = 'keep; # This is a comment' 12 | self.assertFalse(checksieve.parse_string(sieve, False)) 13 | 14 | def test_multi_line(self): 15 | sieve = ''' 16 | /* This is the first line 17 | This is the second */ 18 | ''' 19 | self.assertFalse(checksieve.parse_string(sieve, False)) 20 | 21 | def test_multi_line_2(self): 22 | sieve = ''' 23 | if exists "In-Reply-To" { 24 | /* Single-line-multi-line-comment */ 25 | } 26 | ''' 27 | self.assertFalse(checksieve.parse_string(sieve, False)) 28 | 29 | def test_multi_line_3(self): 30 | sieve = ''' 31 | if exists "In-Reply-To" { 32 | /* Multi-line comment 33 | with a * in it */ 34 | } 35 | ''' 36 | self.assertFalse(checksieve.parse_string(sieve, False)) 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() -------------------------------------------------------------------------------- /test/3028/if_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestIf(unittest.TestCase): 5 | 6 | def test_simple_if(self): 7 | sieve = 'if header :contains :comparator "i;octet" "Subject" "MAKE MONEY FAST" { discard; }' 8 | self.assertFalse(checksieve.parse_string(sieve, False)) 9 | 10 | def test_bogus_test(self): 11 | sieve = 'if bogus :contains :comparator "i;octet" "Subject" "MAKE MONEY FAST" { discard; }' 12 | self.assertTrue(checksieve.parse_string(sieve, True)) 13 | 14 | def test_elsif(self): 15 | sieve = 'if header :matches "Subject" "MAKE MONEY FAST" { discard; }\nelsif header :matches "Subject" "business opportunity" { discard; }' 16 | self.assertFalse(checksieve.parse_string(sieve, False)) 17 | 18 | def test_elsif_multiple(self): 19 | sieve = '''if header :matches "Subject" "MAKE MONEY FAST" 20 | { 21 | discard; 22 | } 23 | elsif header :matches "Subject" "business opportunity" { 24 | discard; 25 | } 26 | elsif header :matches "To" "someone@example.com" { 27 | keep; 28 | stop; 29 | }''' 30 | self.assertFalse(checksieve.parse_string(sieve, False)) 31 | 32 | def test_else(self): 33 | sieve = ''' 34 | if header :matches "Subject" "MAKE MONEY FAST" { 35 | discard; 36 | } else { 37 | keep; 38 | stop; 39 | }''' 40 | self.assertFalse(checksieve.parse_string(sieve, False)); 41 | 42 | def test_if_elsif_else(self): 43 | sieve = '''if header :matches ["Subject"] ["MAKE MONEY FAST"] 44 | { 45 | discard; 46 | } 47 | elsif header :matches "Subject" "business opportunity" { 48 | discard; 49 | } 50 | else { 51 | keep; 52 | stop; 53 | }''' 54 | self.assertFalse(checksieve.parse_string(sieve, False)) 55 | 56 | def test_if_elsif_elsif_else(self): 57 | sieve = '''if header :matches ["Subject"] ["MAKE MONEY FAST"] 58 | { 59 | discard; 60 | } 61 | elsif header :matches "Subject" "business opportunity" { 62 | discard; 63 | } 64 | elsif header :matches "Subject" "NOT A VIRUS" { 65 | discard; 66 | } 67 | else { 68 | keep; 69 | stop; 70 | }''' 71 | self.assertFalse(checksieve.parse_string(sieve, False)) 72 | 73 | def test_test_list(self): 74 | sieve = ''' 75 | if allof 76 | ( 77 | header :contains "List-Id" "", 78 | header :matches "Subject" [ "*Closure*", "*closure*" ] 79 | ) 80 | { 81 | keep; 82 | stop; 83 | } 84 | ''' 85 | self.assertFalse(checksieve.parse_string(sieve, False)) 86 | 87 | def test_empty_block(self): 88 | sieve = ''' 89 | if header :matches "Subject" "NOT A VIRUS" { 90 | 91 | } 92 | ''' 93 | self.assertFalse(checksieve.parse_string(sieve, False)) 94 | 95 | 96 | if __name__ == '__main__': 97 | unittest.main() 98 | -------------------------------------------------------------------------------- /test/3028/misc_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestMiscellany(unittest.TestCase): 5 | 6 | def test_fileinto_no_require(self): 7 | sieve = 'fileinto "Inbox";' 8 | self.assertTrue(checksieve.parse_string(sieve, True)) 9 | 10 | def test_fileinto(self): 11 | sieve = ''' 12 | require "fileinto"; 13 | fileinto "Inbox";''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | def test_unnecessary_semicolon(self): 17 | sieve = 'if header :matches "Subject" "*" { keep; stop; };' 18 | self.assertTrue(checksieve.parse_string(sieve, True)) 19 | 20 | def test_multiline_string(self): 21 | sieve = ''' 22 | require "fileinto"; 23 | fileinto text: 24 | Inbox 25 | . 26 | ;''' 27 | self.assertFalse(checksieve.parse_string(sieve, False)) 28 | 29 | def test_multiline_string_with_comment(self): 30 | sieve = ''' 31 | require "fileinto"; 32 | fileinto text: #inline comment 33 | Inbox 34 | . 35 | ;''' 36 | self.assertFalse(checksieve.parse_string(sieve, False)) 37 | 38 | def test_multiple_requires(self): 39 | sieve = ''' 40 | require "fileinto"; 41 | require "copy"; 42 | 43 | if header :matches "Subject" "*" { 44 | fileinto :copy "All Mail"; 45 | } 46 | 47 | keep; 48 | stop; 49 | ''' 50 | self.assertFalse(checksieve.parse_string(sieve, False)) 51 | 52 | def test_stop_too_many_args(self): 53 | sieve = ''' 54 | stop "foo"; 55 | ''' 56 | self.assertTrue(checksieve.parse_string(sieve, True)) 57 | 58 | if __name__ == '__main__': 59 | unittest.main() 60 | -------------------------------------------------------------------------------- /test/3028/strings_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestStrings(unittest.TestCase): 5 | 6 | def test_multiline_strings(self): 7 | sieve = ''' 8 | require "vacation"; 9 | vacation :mime 10 | "Content-Type: multipart/alternative; boundary=foo 11 | 12 | --foo 13 | 14 | I'm at the beach relaxing. Mmmm, surf... 15 | 16 | --foo 17 | Content-Type: text/html; charset=us-ascii 18 | 19 | 21 | How to relax 22 | 23 |

I'm at the beach relaxing. 24 | Mmmm, surf... 25 | 26 | 27 | --foo-- 28 | "; 29 | ''' 30 | self.assertFalse(checksieve.parse_string(sieve, False)) 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /test/3894/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/3894/__init__.py -------------------------------------------------------------------------------- /test/3894/basic_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestBasic(unittest.TestCase): 5 | 6 | def test_copy_fileinto (self): 7 | sieve = ''' 8 | require ["copy", "fileinto"]; 9 | 10 | if header :matches "Subject" "*" { 11 | fileinto :copy "All Mail"; 12 | } 13 | 14 | keep; 15 | stop; 16 | ''' 17 | self.assertFalse(checksieve.parse_string(sieve, False)) 18 | 19 | def test_copy_redirect (self): 20 | sieve = ''' 21 | require ["copy"]; 22 | 23 | if header :matches "Subject" "*" { 24 | redirect :copy "foo@example.com"; 25 | } 26 | 27 | keep; 28 | stop; 29 | ''' 30 | self.assertFalse(checksieve.parse_string(sieve, False)) 31 | 32 | def test_copy_no_require (self): 33 | sieve = ''' 34 | require ["fileinto"]; 35 | 36 | if header :matches "Subject" "*" { 37 | fileinto :copy "All Mail"; 38 | } 39 | 40 | keep; 41 | stop; 42 | ''' 43 | self.assertTrue(checksieve.parse_string(sieve, True)) 44 | 45 | if __name__ == '__main__': 46 | unittest.main() -------------------------------------------------------------------------------- /test/5173/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5173/__init__.py -------------------------------------------------------------------------------- /test/5173/body_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestBody(unittest.TestCase): 5 | 6 | def test_body(self): 7 | sieve = ''' 8 | require ["body"]; 9 | 10 | if body :raw :contains "MAKE MONEY FAST" { 11 | discard; 12 | } 13 | ''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | def test_body_no_require(self): 17 | sieve = ''' 18 | if body :raw :contains "MAKE MONEY FAST" { 19 | discard; 20 | } 21 | ''' 22 | self.assertTrue(checksieve.parse_string(sieve, True)) 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /test/5173/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require ["body", "fileinto"]; 9 | 10 | # Save any message with any text MIME part that contains the 11 | # words "missile" or "coordinates" in the "secrets" folder. 12 | 13 | if body :content "text" :contains ["missile", "coordinates"] { 14 | fileinto "secrets"; 15 | } 16 | 17 | # Save any message with an audio/mp3 MIME part in 18 | # the "jukebox" folder. 19 | 20 | if body :content "audio/mp3" :contains "" { 21 | fileinto "jukebox"; 22 | } 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | if __name__ == '__main__': 27 | unittest.main() 28 | -------------------------------------------------------------------------------- /test/5183/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5183/__init__.py -------------------------------------------------------------------------------- /test/5183/environment_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestEnvironment(unittest.TestCase): 5 | 6 | def test_environment_name(self): 7 | sieve = ''' 8 | require ["environment"]; 9 | 10 | if environment :is "name" "check-sieve" { 11 | discard; 12 | } 13 | ''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | def test_environment_domain(self): 17 | sieve = ''' 18 | require ["environment"]; 19 | 20 | if environment :is "domain" ["google.com", "icloud.com"] { 21 | discard; 22 | } 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | def test_environment_host(self): 27 | sieve = ''' 28 | require ["environment"]; 29 | 30 | if environment :is "host" "moo" { 31 | discard; 32 | } 33 | ''' 34 | self.assertFalse(checksieve.parse_string(sieve, False)) 35 | 36 | def test_environment_location(self): 37 | sieve = ''' 38 | require ["environment"]; 39 | 40 | if environment :is "location" "foo" { 41 | discard; 42 | } 43 | ''' 44 | self.assertFalse(checksieve.parse_string(sieve, False)) 45 | 46 | def test_environment_phase(self): 47 | sieve = ''' 48 | require ["environment"]; 49 | 50 | if environment :is "phase" "1" { 51 | discard; 52 | } 53 | ''' 54 | self.assertFalse(checksieve.parse_string(sieve, False)) 55 | 56 | def test_environment_remote_host(self): 57 | sieve = ''' 58 | require ["environment"]; 59 | 60 | if environment :is "remote-host" "moo" { 61 | discard; 62 | } 63 | ''' 64 | self.assertFalse(checksieve.parse_string(sieve, False)) 65 | 66 | def test_environment_remote_ip(self): 67 | sieve = ''' 68 | require ["environment"]; 69 | 70 | if environment :is "remote-ip" "127.0.0.1" { 71 | discard; 72 | } 73 | ''' 74 | self.assertFalse(checksieve.parse_string(sieve, False)) 75 | 76 | def test_environment_version(self): 77 | sieve = ''' 78 | require ["environment"]; 79 | 80 | if environment :is "version" "1.2" { 81 | discard; 82 | } 83 | ''' 84 | self.assertFalse(checksieve.parse_string(sieve, False)) 85 | 86 | def test_environment_bogus(self): 87 | sieve = ''' 88 | require ["environment"]; 89 | 90 | if environment :is "bogus" "check-sieve" { 91 | discard; 92 | } 93 | ''' 94 | self.assertTrue(checksieve.parse_string(sieve, True)) 95 | 96 | if __name__ == '__main__': 97 | unittest.main() 98 | -------------------------------------------------------------------------------- /test/5228/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5228/__init__.py -------------------------------------------------------------------------------- /test/5228/commands_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestCommands(unittest.TestCase): 5 | 6 | def test_discard(self): 7 | sieve = ''' 8 | discard; 9 | ''' 10 | self.assertFalse(checksieve.parse_string(sieve, False)) 11 | 12 | def test_discard_extra_arg(self): 13 | sieve = ''' 14 | discard "foo"; 15 | ''' 16 | self.assertTrue(checksieve.parse_string(sieve, True)) 17 | 18 | def test_redirect(self): 19 | sieve = ''' 20 | redirect "foo@example.org"; 21 | ''' 22 | self.assertFalse(checksieve.parse_string(sieve, False)) 23 | 24 | def test_redirect_no_args(self): 25 | sieve = ''' 26 | redirect; 27 | ''' 28 | self.assertTrue(checksieve.parse_string(sieve, True)) 29 | 30 | def test_multiple_is(self): 31 | sieve = ''' 32 | if header :is "X-Phabricator-Mail-Tags" :is "" 33 | { 34 | discard; 35 | stop; 36 | } 37 | ''' 38 | self.assertTrue(checksieve.parse_string(sieve, True)) 39 | 40 | def test_single_test_parens_01(self): 41 | sieve = ''' 42 | if anyof (not exists ["From", "Date"]) { 43 | discard; 44 | } 45 | ''' 46 | self.assertFalse(checksieve.parse_string(sieve, False)) 47 | 48 | def test_single_test_parens_02(self): 49 | sieve = ''' 50 | if (address :is true) { 51 | discard; 52 | } 53 | ''' 54 | self.assertFalse(checksieve.parse_string(sieve, False)) 55 | 56 | def test_single_test_parens_03(self): 57 | sieve = ''' 58 | if anyof (not true) { 59 | keep; 60 | } else { 61 | discard; 62 | } 63 | ''' 64 | self.assertFalse(checksieve.parse_string(sieve, False)) 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /test/5228/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_extended_example(self): 7 | sieve = ''' 8 | # 9 | # Example Sieve Filter 10 | # Declare any optional features or extension used by the script 11 | # 12 | require ["fileinto"]; 13 | 14 | # 15 | # Handle messages from known mailing lists 16 | # Move messages from IETF filter discussion list to filter mailbox 17 | # 18 | if header :is "Sender" "owner-ietf-mta-filters@imc.org" 19 | { 20 | fileinto "filter"; # move to "filter" mailbox 21 | } 22 | # 23 | # Keep all messages to or from people in my company 24 | # 25 | elsif address :DOMAIN :is ["From", "To"] "example.com" 26 | { 27 | keep; # keep in "In" mailbox 28 | } 29 | 30 | # 31 | # Try and catch unsolicited email. If a message is not to me, 32 | # or it contains a subject known to be spam, file it away. 33 | # 34 | elsif anyof (not address :all :contains 35 | ["To", "Cc", "Bcc"] "me@example.com", 36 | header :matches "subject" 37 | ["*make*money*fast*", "*university*dipl*mas*"]) 38 | { 39 | fileinto "spam"; # move to "spam" mailbox 40 | } 41 | else 42 | { 43 | # Move all other (non-company) mail to "personal" 44 | # mailbox. 45 | fileinto "personal"; 46 | } 47 | ''' 48 | self.assertFalse(checksieve.parse_string(sieve, False)) 49 | 50 | def test_match_variables(self): 51 | sieve = ''' 52 | require ["fileinto", "variables"]; 53 | 54 | if header :matches "List-ID" "*<*@*" { 55 | fileinto "INBOX.lists.${2}"; stop; 56 | } 57 | 58 | # Imagine the header 59 | # Subject: [acme-users] [fwd] version 1.0 is out 60 | if header :matches "Subject" "[*] *" { 61 | # ${1} will hold "acme-users", 62 | # ${2} will hold "[fwd] version 1.0 is out" 63 | fileinto "INBOX.lists.${1}"; stop; 64 | } 65 | 66 | # Imagine the header 67 | # To: coyote@ACME.Example.COM 68 | if address :matches ["To", "Cc"] ["coyote@**.com", 69 | "wile@**.com"] { 70 | # ${0} is the matching address 71 | # ${1} is always the empty string 72 | # ${2} is part of the domain name ("ACME.Example") 73 | fileinto "INBOX.business.${2}"; stop; 74 | } else { 75 | # Control wouldn't reach this block if any match was 76 | # successful, so no match variables are set at this 77 | # point. 78 | } 79 | 80 | if anyof (true, address :domain :matches "To" "*.com") { 81 | # The second test is never evaluated, so there are 82 | # still no match variables set. 83 | stop; 84 | } 85 | ''' 86 | self.assertFalse(checksieve.parse_string(sieve, False)) 87 | 88 | if __name__ == '__main__': 89 | unittest.main() -------------------------------------------------------------------------------- /test/5228/semicolon_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class SemiColonTest(unittest.TestCase): 5 | 6 | def test_dangling_semicolon(self): 7 | sieve = ''' 8 | require ["fileinto"]; 9 | if header :matches "Subject" "*Some Subject:*" { ; fileinto "Folder"; stop; } 10 | ''' 11 | self.assertTrue(checksieve.parse_string(sieve, True)) 12 | 13 | if __name__ == '__main__': 14 | unittest.main() -------------------------------------------------------------------------------- /test/5228/tests_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestForTestList(unittest.TestCase): 5 | 6 | def test_not_01(self): 7 | sieve = ''' 8 | if not (address :is "Foo") { 9 | discard; 10 | } 11 | ''' 12 | self.assertTrue(checksieve.parse_string(sieve, True)) 13 | 14 | if __name__ == '__main__': 15 | unittest.main() -------------------------------------------------------------------------------- /test/5229/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5229/__init__.py -------------------------------------------------------------------------------- /test/5229/tests_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestTests(unittest.TestCase): 5 | 6 | def test_string(self): 7 | sieve = ''' 8 | require ["variables"]; 9 | if string :matches " ${state} " "* pending *" { 10 | # the above test always succeeds 11 | } 12 | ''' 13 | self.assertFalse(checksieve.parse_string(sieve, False)) 14 | 15 | def test_string_no_require(self): 16 | sieve = ''' 17 | if string :matches " ${state} " "* pending *" { 18 | # the above test always succeeds 19 | } 20 | ''' 21 | self.assertTrue(checksieve.parse_string(sieve, True)) 22 | 23 | 24 | if __name__ == '__main__': 25 | unittest.main() -------------------------------------------------------------------------------- /test/5229/variables_test.py: -------------------------------------------------------------------------------- 1 | from sys import platform 2 | 3 | import unittest 4 | import checksieve 5 | 6 | class TestVariables(unittest.TestCase): 7 | 8 | def test_set(self): 9 | sieve = ''' 10 | require "variables"; 11 | set "honorific" "Mr"; 12 | ''' 13 | self.assertFalse(checksieve.parse_string(sieve, False)) 14 | 15 | def test_mod_length(self): 16 | sieve = ''' 17 | require "variables"; 18 | set :length "b" "${a}"; 19 | ''' 20 | self.assertFalse(checksieve.parse_string(sieve, False)) 21 | 22 | def test_wrong_tag(self): 23 | sieve = ''' 24 | require "variables"; 25 | set :mime "b" "c"; 26 | ''' 27 | self.assertTrue(checksieve.parse_string(sieve, True)) 28 | 29 | def test_set_wrong_arg(self): 30 | sieve = ''' 31 | require "variables"; 32 | set "a" "b" "c"; 33 | ''' 34 | self.assertTrue(checksieve.parse_string(sieve, True)) 35 | 36 | def test_too_many_args(self): 37 | sieve = ''' 38 | require "variables"; 39 | set "a" "b" "c" "d"; 40 | ''' 41 | self.assertTrue(checksieve.parse_string(sieve, True)) 42 | 43 | def test_test_string(self): 44 | sieve = ''' 45 | require "variables"; 46 | set "state" "${state} pending"; 47 | if string :matches " ${state} " "* pending *" { 48 | # the above test always succeeds 49 | stop; 50 | } 51 | ''' 52 | self.assertFalse(checksieve.parse_string(sieve, False)) 53 | 54 | def test_numeral_varname(self): 55 | sieve = ''' 56 | require "variables"; 57 | set "1" "${state} pending"; 58 | ''' 59 | self.assertFalse(checksieve.parse_string(sieve, False)) 60 | 61 | def test_bad_varname(self): 62 | sieve = ''' 63 | require "variables"; 64 | set "bad-variable" "no dashes allowed!"; 65 | ''' 66 | self.assertTrue(checksieve.parse_string(sieve, True)) 67 | 68 | 69 | if __name__ == '__main__': 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /test/5230/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5230/__init__.py -------------------------------------------------------------------------------- /test/5230/commands_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestCommands(unittest.TestCase): 5 | 6 | def test_vacation_no_args(self): 7 | sieve = ''' 8 | require "vacation"; 9 | vacation; 10 | ''' 11 | self.assertTrue(checksieve.parse_string(sieve, True)) 12 | 13 | def test_vacation_bad_tag(self): 14 | sieve = ''' 15 | require ["vacation", "copy"]; 16 | vacation :copy "I'm out"; 17 | ''' 18 | self.assertTrue(checksieve.parse_string(sieve, True)) 19 | 20 | 21 | if __name__ == '__main__': 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /test/5230/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require "vacation"; 9 | if header :contains "subject" "cyrus" { 10 | vacation "I'm out -- send mail to cyrus-bugs"; 11 | } else { 12 | vacation "I'm out -- call me at +1 304 555 0123"; 13 | } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | def test_example_2(self): 18 | sieve = ''' 19 | require ["vacation", "variables"]; 20 | if header :matches "subject" "*" { 21 | vacation :subject "Automatic response to: ${1}" 22 | "I'm away -- send mail to foo in my absence"; 23 | } 24 | ''' 25 | self.assertFalse(checksieve.parse_string(sieve, False)) 26 | 27 | def test_example_3(self): 28 | sieve = ''' 29 | require "vacation"; 30 | vacation :days 23 :addresses ["tjs@example.edu", 31 | "ts4z@landru.example.edu"] 32 | "I'm away until October 19. 33 | If it's an emergency, call 911, I guess." ; 34 | ''' 35 | self.assertFalse(checksieve.parse_string(sieve, False)) 36 | 37 | def test_example_4(self): 38 | sieve = ''' 39 | require "vacation"; 40 | if header :contains "from" "boss@example.edu" { 41 | redirect "pleeb@isp.example.org"; 42 | } else { 43 | vacation "Sorry, I'm away, I'll read your 44 | message when I get around to it."; 45 | } 46 | ''' 47 | self.assertFalse(checksieve.parse_string(sieve, False)) 48 | 49 | def test_example_5(self): 50 | sieve = ''' 51 | require "vacation"; 52 | if header :contains ["accept-language", "content-language"] "en" 53 | { 54 | vacation "I am away this week."; 55 | } else { 56 | vacation "Estoy ausente esta semana."; 57 | } 58 | ''' 59 | self.assertFalse(checksieve.parse_string(sieve, False)) 60 | 61 | def test_example_mime(self): 62 | sieve = ''' 63 | require "vacation"; 64 | vacation :mime text: 65 | Content-Type: multipart/alternative; boundary=foo 66 | 67 | --foo 68 | 69 | I'm at the beach relaxing. Mmmm, surf... 70 | 71 | --foo 72 | Content-Type: text/html; charset=us-ascii 73 | 74 | 76 | How to relax 77 | 78 |

I'm at the beach relaxing. 79 | Mmmm, surf... 80 | 81 | 82 | --foo-- 83 | . 84 | ; 85 | ''' 86 | self.assertFalse(checksieve.parse_string(sieve, False)) 87 | 88 | if __name__ == '__main__': 89 | unittest.main() 90 | -------------------------------------------------------------------------------- /test/5231/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5231/__init__.py -------------------------------------------------------------------------------- /test/5231/relational_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestRelational(unittest.TestCase): 5 | 6 | def test_count(self): 7 | sieve = ''' 8 | require ["relational", "fileinto", "comparator-i;ascii-numeric"]; 9 | if address :count "gt" :comparator "i;ascii-numeric" 10 | ["to"] ["5"] 11 | { 12 | # everything with more than 5 recipients in the "to" field 13 | # is considered SPAM 14 | fileinto "SPAM"; 15 | } 16 | ''' 17 | self.assertFalse(checksieve.parse_string(sieve, False)) 18 | 19 | def test_count_no_require(self): 20 | sieve = ''' 21 | require ["fileinto"]; 22 | if address :count "gt" :comparator "i;ascii-numeric" 23 | ["to"] ["5"] 24 | { 25 | # everything with more than 5 recipients in the "to" field 26 | # is considered SPAM 27 | fileinto "SPAM"; 28 | } 29 | ''' 30 | self.assertTrue(checksieve.parse_string(sieve, True)) 31 | 32 | def test_value(self): 33 | sieve = ''' 34 | require ["relational", "fileinto", "comparator-i;ascii-casemap"]; 35 | if address :value "gt" :all :comparator "i;ascii-casemap" 36 | ["from"] ["M"] 37 | { 38 | fileinto "From N-Z"; 39 | } 40 | ''' 41 | self.assertFalse(checksieve.parse_string(sieve, False)) 42 | 43 | def test_header_test_value(self): 44 | sieve = ''' 45 | require ["relational", "comparator-i;ascii-numeric", "fileinto"]; 46 | if header :value "lt" :comparator "i;ascii-numeric" 47 | ["x-priority"] ["3"] 48 | { 49 | fileinto "Priority"; 50 | } 51 | ''' 52 | self.assertFalse(checksieve.parse_string(sieve, False)) 53 | 54 | def test_value_no_require(self): 55 | sieve = ''' 56 | require ["fileinto"]; 57 | if address :value "gt" :all :comparator "i;ascii-casemap" 58 | ["from"] ["M"] 59 | { 60 | fileinto "From N-Z"; 61 | } 62 | ''' 63 | self.assertTrue(checksieve.parse_string(sieve, True)) 64 | 65 | # TODO: Re-enable this test when it passes 66 | # def test_value_no_comparator_require(self): 67 | # sieve = ''' 68 | # require ["fileinto", "relational"]; 69 | # if address :value "gt" :all :comparator "i;ascii-numeric" 70 | # ["from"] ["M"] 71 | # { 72 | # fileinto "From N-Z"; 73 | # } 74 | # ''' 75 | # self.assertTrue(checksieve.parse_string(sieve, True)) 76 | 77 | 78 | if __name__ == '__main__': 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /test/5232/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5232/__init__.py -------------------------------------------------------------------------------- /test/5232/actions_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestSetFlag(unittest.TestCase): 5 | 6 | def test_setflag(self): 7 | sieve = ''' 8 | require ["imap4flags"]; 9 | setflag "\\Seen"; 10 | ''' 11 | self.assertFalse(checksieve.parse_string(sieve, False)) 12 | 13 | def test_setflag_with_variable(self): 14 | sieve = ''' 15 | require ["imap4flags", "fileinto"]; 16 | if header :contains "from" "boss@frobnitzm.example.edu" { 17 | setflag "flagvar" "\\Flagged"; 18 | fileinto :flags "${flagvar}" "INBOX.From Boss"; 19 | } 20 | ''' 21 | self.assertFalse(checksieve.parse_string(sieve, False)) 22 | 23 | def test_setflag_no_args(self): 24 | sieve = ''' 25 | require ["imap4flags"]; 26 | setflag; 27 | ''' 28 | self.assertTrue(checksieve.parse_string(sieve, True)) 29 | 30 | 31 | class TestAddFlag(unittest.TestCase): 32 | 33 | def test_addflag(self): 34 | sieve = ''' 35 | require ["imap4flags"]; 36 | addflag "\\Seen"; 37 | ''' 38 | self.assertFalse(checksieve.parse_string(sieve, False)) 39 | 40 | def test_addflag_with_var(self): 41 | sieve = ''' 42 | require ["imap4flags"]; 43 | addflag "flagvar" ["\\Deleted", "\\Answered"]; 44 | ''' 45 | self.assertFalse(checksieve.parse_string(sieve, False)) 46 | 47 | def test_addflag_no_args(self): 48 | sieve = ''' 49 | require ["imap4flags"]; 50 | addflag; 51 | ''' 52 | self.assertTrue(checksieve.parse_string(sieve, True)) 53 | 54 | 55 | class TestRemoveFlag(unittest.TestCase): 56 | 57 | def test_removeflag(self): 58 | sieve = ''' 59 | require ["imap4flags"]; 60 | removeflag "\\Seen"; 61 | ''' 62 | self.assertFalse(checksieve.parse_string(sieve, False)) 63 | 64 | def test_removeflag_with_var(self): 65 | sieve = ''' 66 | require ["imap4flags"]; 67 | removeflag "flagvar" ["\\Deleted", "\\Answered"]; 68 | ''' 69 | self.assertFalse(checksieve.parse_string(sieve, False)) 70 | 71 | def test_removeflag_no_args(self): 72 | sieve = ''' 73 | require ["imap4flags"]; 74 | removeflag; 75 | ''' 76 | self.assertTrue(checksieve.parse_string(sieve, True)) 77 | 78 | if __name__ == '__main__': 79 | unittest.main() -------------------------------------------------------------------------------- /test/5232/fileinto_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestFileinto(unittest.TestCase): 5 | 6 | def test_fileinto_with_flags(self): 7 | sieve = ''' 8 | require ["fileinto", "imap4flags"]; 9 | fileinto :flags "\\\\Seen" "Mailbox"; 10 | ''' 11 | self.assertFalse(checksieve.parse_string(sieve, False)) 12 | 13 | def test_fileinto_with_flags_list(self): 14 | sieve = ''' 15 | require ["fileinto", "imap4flags"]; 16 | fileinto :flags ["\\\\Seen", "\\\\Deleted"] "Mailbox"; 17 | ''' 18 | self.assertFalse(checksieve.parse_string(sieve, False)) 19 | 20 | def test_fileinto_with_flags_without_arguments(self): 21 | sieve = ''' 22 | require ["fileinto", "imap4flags"]; 23 | fileinto :flags "Mailbox"; 24 | ''' 25 | self.assertTrue(checksieve.parse_string(sieve, True)) 26 | 27 | def test_fileinto_with_flag_and_copy(self): 28 | sieve = ''' 29 | require ["fileinto", "imap4flags", "copy"]; 30 | fileinto :flags "\\\\Seen" :copy "Mailbox"; 31 | ''' 32 | self.assertFalse(checksieve.parse_string(sieve, False)) 33 | 34 | def test_fileinto_with_copy_and_flag(self): 35 | sieve = ''' 36 | require ["fileinto", "imap4flags", "copy"]; 37 | fileinto :copy :flags "\\\\Seen" "Mailbox"; 38 | ''' 39 | self.assertFalse(checksieve.parse_string(sieve, False)) 40 | 41 | def test_fileinto_with_flag_without_arguments_and_copy(self): 42 | sieve = ''' 43 | require ["fileinto", "imap4flags", "copy"]; 44 | fileinto :flags :copy "Mailbox"; 45 | ''' 46 | self.assertTrue(checksieve.parse_string(sieve, True)) 47 | 48 | def test_fileinto_with_copy_and_flag_without_arguments(self): 49 | sieve = ''' 50 | require ["fileinto", "imap4flags", "copy"]; 51 | fileinto :copy :flags "Mailbox"; 52 | ''' 53 | self.assertTrue(checksieve.parse_string(sieve, True)) 54 | 55 | 56 | 57 | if __name__ == '__main__': 58 | unittest.main() -------------------------------------------------------------------------------- /test/5232/keep_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestKeep(unittest.TestCase): 5 | 6 | def test_hasflag(self): 7 | sieve = ''' 8 | require ["imap4flags"]; 9 | if hasflag :contains "MyVar" "Junk" { 10 | keep :flags "\\\\Junk"; 11 | stop; 12 | } 13 | ''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | 17 | 18 | if __name__ == '__main__': 19 | unittest.main() -------------------------------------------------------------------------------- /test/5232/tests_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestTests(unittest.TestCase): 5 | 6 | def test_hasflag(self): 7 | sieve = ''' 8 | require ["imap4flags"]; 9 | if hasflag :contains "MyVar" "Junk" { 10 | discard; 11 | stop; 12 | } 13 | ''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | def test_hasflag_no_require(self): 17 | sieve = ''' 18 | if hasflag :contains "MyVar" "Junk" { 19 | discard; 20 | stop; 21 | } 22 | ''' 23 | self.assertTrue(checksieve.parse_string(sieve, True)) 24 | 25 | 26 | if __name__ == '__main__': 27 | unittest.main() -------------------------------------------------------------------------------- /test/5233/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5233/__init__.py -------------------------------------------------------------------------------- /test/5233/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class ExamplesTest(unittest.TestCase): 5 | 6 | def test_example1(self): 7 | sieve = ''' 8 | require ["envelope", "subaddress", "fileinto"]; 9 | 10 | # In this example the same user account receives mail for both 11 | # "ken@example.com" and "postmaster@example.com" 12 | 13 | # File all messages to postmaster into a single mailbox, 14 | # ignoring the :detail part. 15 | if envelope :user "to" "postmaster" { 16 | fileinto "inbox.postmaster"; 17 | stop; 18 | } 19 | 20 | # File mailing list messages (subscribed as "ken+mta-filters"). 21 | if envelope :detail "to" "mta-filters" { 22 | fileinto "inbox.ietf-mta-filters"; 23 | } 24 | 25 | # Redirect all mail sent to "ken+foo". 26 | if envelope :detail "to" "foo" { 27 | redirect "ken@example.net"; 28 | } 29 | ''' 30 | self.assertFalse(checksieve.parse_string(sieve, False)) 31 | 32 | 33 | if __name__ == '__main__': 34 | unittest.main() -------------------------------------------------------------------------------- /test/5235/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5235/__init__.py -------------------------------------------------------------------------------- /test/5235/spamtest_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestSpamtest(unittest.TestCase): 5 | 6 | def test_without_percent(self): 7 | sieve = ''' 8 | require ["spamtest", "fileinto", "relational", "comparator- 9 | i;ascii-numeric"]; 10 | if spamtest :value "eq" :comparator "i;ascii-numeric" "0" 11 | { 12 | fileinto "INBOX.unclassified"; 13 | } 14 | elsif spamtest :value "ge" :comparator "i;ascii-numeric" "3" 15 | { 16 | fileinto "INBOX.spam-trap"; 17 | } 18 | ''' 19 | self.assertFalse(checksieve.parse_string(sieve, False)) 20 | 21 | def test_with_percent(self): 22 | sieve = ''' 23 | require ["spamtestplus", "fileinto", "relational", 24 | "comparator-i;ascii-numeric"]; 25 | 26 | if spamtest :value "eq" 27 | :comparator "i;ascii-numeric" "0" 28 | { 29 | fileinto "INBOX.unclassified"; 30 | } 31 | elsif spamtest :percent :value "eq" 32 | :comparator "i;ascii-numeric" "0" 33 | { 34 | fileinto "INBOX.not-spam"; 35 | } 36 | elsif spamtest :percent :value "lt" 37 | :comparator "i;ascii-numeric" "37" 38 | { 39 | fileinto "INBOX.spam-trap"; 40 | } 41 | else 42 | { 43 | discard; 44 | } 45 | ''' 46 | self.assertFalse(checksieve.parse_string(sieve, False)) 47 | 48 | def test_with_count(self): 49 | sieve = ''' 50 | require ["spamtestplus", "fileinto", "relational", 51 | "comparator-i;ascii-numeric"]; 52 | if spamtest :percent :count "eq" 53 | :comparator "i;ascii-numeric" "0" 54 | { 55 | fileinto "INBOX.unclassified"; 56 | } 57 | elsif spamtest :percent :value "eq" 58 | :comparator "i;ascii-numeric" "0" 59 | { 60 | fileinto "INBOX.not-spam"; 61 | } 62 | elsif spamtest :percent :value "lt" 63 | :comparator "i;ascii-numeric" "37" 64 | { 65 | fileinto "INBOX.spam-trap"; 66 | } 67 | else 68 | { 69 | discard; 70 | } 71 | ''' 72 | self.assertFalse(checksieve.parse_string(sieve, False)) 73 | 74 | 75 | if __name__ == '__main__': 76 | unittest.main() -------------------------------------------------------------------------------- /test/5235/virustest_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestVirustest(unittest.TestCase): 5 | 6 | def test_virustest(self): 7 | sieve = ''' 8 | require ["virustest", "fileinto", "relational", "comparator- 9 | i;ascii-numeric"]; 10 | 11 | if virustest :value "eq" :comparator "i;ascii-numeric" "0" 12 | { 13 | fileinto "INBOX.unclassified"; 14 | } 15 | if virustest :value "eq" :comparator "i;ascii-numeric" "4" 16 | { 17 | fileinto "INBOX.quarantine"; 18 | } 19 | elsif virustest :value "eq" :comparator "i;ascii-numeric" "5" 20 | { 21 | discard; 22 | } 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | 27 | if __name__ == '__main__': 28 | unittest.main() -------------------------------------------------------------------------------- /test/5260/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5260/__init__.py -------------------------------------------------------------------------------- /test/5260/currentdate_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestCurrentDate(unittest.TestCase): 5 | 6 | def test_current_date(self): 7 | sieve = ''' 8 | require ["date", "relational"]; 9 | if anyof(currentdate :is "weekday" "0", 10 | currentdate :is "weekday" "6", 11 | currentdate :value "lt" "hour" "09", 12 | currentdate :value "ge" "hour" "17") 13 | { redirect "pager@example.com"; } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | def test_current_date_no_require(self): 18 | sieve = ''' 19 | require ["relational"]; 20 | if anyof(currentdate :is "weekday" "0", 21 | currentdate :is "weekday" "6", 22 | currentdate :value "lt" "hour" "09", 23 | currentdate :value "ge" "hour" "17") 24 | { redirect "pager@example.com"; } 25 | ''' 26 | self.assertTrue(checksieve.parse_string(sieve, True)) 27 | 28 | 29 | if __name__ == '__main__': 30 | unittest.main() -------------------------------------------------------------------------------- /test/5260/date_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestDate(unittest.TestCase): 5 | 6 | def test_date_pass(self): 7 | sieve = ''' 8 | require ["date", "relational", "fileinto"]; 9 | if anyof(date :is "received" "weekday" "0", 10 | date :is "received" "weekday" "6") 11 | { fileinto "weekend"; } 12 | ''' 13 | self.assertFalse(checksieve.parse_string(sieve, False)) 14 | 15 | def test_date_no_require(self): 16 | sieve = ''' 17 | require ["relational", "fileinto"]; 18 | if anyof(date :is "received" "weekday" "0", 19 | date :is "received" "weekday" "6") 20 | { fileinto "weekend"; } 21 | ''' 22 | self.assertTrue(checksieve.parse_string(sieve, True)) 23 | 24 | def test_date_2(self): 25 | sieve = ''' 26 | require ["date", "relational", "fileinto"]; 27 | if allof(header :is "from" "boss@example.com", 28 | date :value "ge" :originalzone "date" "hour" "09", 29 | date :value "lt" :originalzone "date" "hour" "17") 30 | { fileinto "urgent"; } 31 | ''' 32 | self.assertFalse(checksieve.parse_string(sieve, False)) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() -------------------------------------------------------------------------------- /test/5260/index_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestIndex(unittest.TestCase): 5 | 6 | def test_index(self): 7 | sieve = ''' 8 | # Implement the Internet-Draft cutoff date check assuming the 9 | # second Received: field specifies when the message first 10 | # entered the local email infrastructure. 11 | require ["date", "relational", "index"]; 12 | if date :value "gt" :index 2 :zone "-0500" "received" 13 | "iso8601" "2007-02-26T09:00:00-05:00" 14 | { redirect "aftercutoff@example.org"; } 15 | ''' 16 | self.assertFalse(checksieve.parse_string(sieve, False)) 17 | 18 | def test_index_no_require(self): 19 | sieve = ''' 20 | # Implement the Internet-Draft cutoff date check assuming the 21 | # second Received: field specifies when the message first 22 | # entered the local email infrastructure. 23 | require ["date", "relational"]; 24 | if date :value "gt" :index 2 :zone "-0500" "received" 25 | "iso8601" "2007-02-26T09:00:00-05:00" 26 | { redirect "aftercutoff@example.org"; } 27 | ''' 28 | self.assertTrue(checksieve.parse_string(sieve, True)) 29 | 30 | 31 | if __name__ == '__main__': 32 | unittest.main() -------------------------------------------------------------------------------- /test/5293/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5293/__init__.py -------------------------------------------------------------------------------- /test/5293/actions_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestAddHeader(unittest.TestCase): 5 | 6 | def test_addheader_correct(self): 7 | sieve = ''' 8 | if not header :contains "X-Sieve-Filtered" 9 | ["", ""] 10 | { 11 | addheader "X-Sieve-Filtered" ""; 12 | redirect "kim@home.example.com"; 13 | } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | def test_addheader_correct_lasttag(self): 18 | sieve = ''' 19 | if not header :contains "X-Sieve-Filtered" 20 | ["", ""] 21 | { 22 | addheader :last "X-Sieve-Filtered" ""; 23 | redirect "kim@home.example.com"; 24 | } 25 | ''' 26 | self.assertFalse(checksieve.parse_string(sieve, False)) 27 | 28 | def test_addheader_incorrect_tag(self): 29 | sieve = ''' 30 | if not header :contains "X-Sieve-Filtered" 31 | ["", ""] 32 | { 33 | addheader :is "X-Sieve-Filtered" ""; 34 | redirect "kim@home.example.com"; 35 | } 36 | ''' 37 | self.assertTrue(checksieve.parse_string(sieve, True)) 38 | 39 | def test_addheader_incorrect_number_of_args(self): 40 | sieve = ''' 41 | if not header :contains "X-Sieve-Filtered" 42 | ["", ""] 43 | { 44 | addheader :is "X-Sieve-Filtered"; 45 | redirect "kim@home.example.com"; 46 | } 47 | ''' 48 | self.assertTrue(checksieve.parse_string(sieve, True)) 49 | 50 | def test_addheader_incorrect_type(self): 51 | sieve = ''' 52 | if not header :contains "X-Sieve-Filtered" 53 | ["", ""] 54 | { 55 | addheader :last :is "X-Sieve-Filtered"; 56 | redirect "kim@home.example.com"; 57 | } 58 | ''' 59 | self.assertTrue(checksieve.parse_string(sieve, True)) 60 | 61 | def test_add_and_delete_1(self): 62 | sieve = ''' 63 | require ["index"]; 64 | addheader "X-Hello" "World"; 65 | deleteheader :index 1 "X-Hello"; 66 | ''' 67 | self.assertFalse(checksieve.parse_string(sieve, False)) 68 | 69 | def test_delete_header(self): 70 | sieve = ''' 71 | require ["index"]; 72 | deleteheader :index 1 :last "X-Hello"; 73 | ''' 74 | self.assertFalse(checksieve.parse_string(sieve, False)) 75 | 76 | def test_delete_header_incorrect_last(self): 77 | sieve = ''' 78 | require "index"; 79 | deleteheader :index 1 "X-Hello" :last; 80 | ''' 81 | self.assertTrue(checksieve.parse_string(sieve, True)) 82 | 83 | 84 | if __name__ == '__main__': 85 | unittest.main() 86 | -------------------------------------------------------------------------------- /test/5429/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5429/__init__.py -------------------------------------------------------------------------------- /test/5429/basic_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestBasic(unittest.TestCase): 5 | 6 | def test_reject(self): 7 | sieve = ''' 8 | require ["reject"]; 9 | reject "I don't like mail"; 10 | ''' 11 | self.assertFalse(checksieve.parse_string(sieve, False)) 12 | 13 | def test_reject_no_arg(self): 14 | sieve = ''' 15 | require ["reject"]; 16 | reject; 17 | ''' 18 | self.assertTrue(checksieve.parse_string(sieve, True)) 19 | 20 | def test_reject_no_require(self): 21 | sieve = ''' 22 | reject "I don't like mail"; 23 | ''' 24 | self.assertTrue(checksieve.parse_string(sieve, True)) 25 | 26 | def test_ereject(self): 27 | sieve = ''' 28 | require ["ereject"]; 29 | ereject "I don't like mail"; 30 | ''' 31 | self.assertFalse(checksieve.parse_string(sieve, False)) 32 | 33 | def test_ereject_no_arg(self): 34 | sieve = ''' 35 | require ["ereject"]; 36 | ereject; 37 | ''' 38 | self.assertTrue(checksieve.parse_string(sieve, True)) 39 | 40 | def test_ereject_no_require(self): 41 | sieve = ''' 42 | ereject "I don't like mail"; 43 | ''' 44 | self.assertTrue(checksieve.parse_string(sieve, True)) 45 | 46 | def test_with_multiline_reason(self): 47 | sieve = ''' 48 | require ["reject"]; 49 | 50 | if size :over 100K { 51 | reject text: 52 | Your message is too big. If you want to send me a big attachment, 53 | put it on a public web site and send me a URL. 54 | . 55 | ; 56 | } 57 | ''' 58 | self.assertFalse(checksieve.parse_string(sieve, False)) 59 | 60 | if __name__ == '__main__': 61 | unittest.main() -------------------------------------------------------------------------------- /test/5435/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5435/__init__.py -------------------------------------------------------------------------------- /test/5435/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestNotify(unittest.TestCase): 5 | 6 | def test_example1(self): 7 | sieve = ''' 8 | require ["enotify", "fileinto", "variables"]; 9 | 10 | if header :contains "from" "boss@example.org" { 11 | notify :importance "1" 12 | :message "This is probably very important" 13 | "mailto:alm@example.com"; 14 | # Don't send any further notifications 15 | stop; 16 | } 17 | 18 | if header :contains "to" "sievemailinglist@example.org" { 19 | # :matches is used to get the value of the Subject header 20 | if header :matches "Subject" "*" { 21 | set "subject" "${1}"; 22 | } 23 | 24 | # :matches is used to get the value of the From header 25 | if header :matches "From" "*" { 26 | set "from" "${1}"; 27 | } 28 | 29 | notify :importance "3" 30 | :message "[SIEVE] ${from}: ${subject}" 31 | "mailto:alm@example.com"; 32 | fileinto "INBOX.sieve"; 33 | } 34 | ''' 35 | self.assertFalse(checksieve.parse_string(sieve, False)) 36 | 37 | def test_example2(self): 38 | sieve = ''' 39 | require ["enotify", "fileinto", "variables", "envelope"]; 40 | 41 | if header :matches "from" "*@*.example.org" { 42 | # :matches is used to get the MAIL FROM address 43 | if envelope :all :matches "from" "*" { 44 | set "env_from" " [really: ${1}]"; 45 | } 46 | 47 | # :matches is used to get the value of the Subject header 48 | if header :matches "Subject" "*" { 49 | set "subject" "${1}"; 50 | } 51 | 52 | # :matches is used to get the address from the From header 53 | if address :matches :all "from" "*" { 54 | set "from_addr" "${1}"; 55 | } 56 | 57 | notify :message "${from_addr}${env_from}: ${subject}" 58 | "mailto:alm@example.com"; 59 | } 60 | ''' 61 | self.assertFalse(checksieve.parse_string(sieve, False)) 62 | 63 | def test_example3(self): 64 | sieve = ''' 65 | require ["enotify", "variables"]; 66 | 67 | set "notif_method" 68 | "xmpp:tim@example.com?message;subject=SIEVE;body=You%20got%20mail"; 69 | 70 | if header :contains "subject" "Your dog" { 71 | set "notif_method" "tel:+14085551212"; 72 | } 73 | 74 | if header :contains "to" "sievemailinglist@example.org" { 75 | set "notif_method" ""; 76 | } 77 | 78 | if not string :is "${notif_method}" "" { 79 | notify "${notif_method}"; 80 | } 81 | 82 | if header :contains "from" "boss@example.org" { 83 | # :matches is used to get the value of the Subject header 84 | if header :matches "Subject" "*" { 85 | set "subject" "${1}"; 86 | } 87 | 88 | # don't need high importance notification for 89 | # a 'for your information' 90 | if not header :contains "subject" "FYI:" { 91 | notify :importance "1" :message "BOSS: ${subject}" 92 | "tel:+14085551212"; 93 | } 94 | } 95 | ''' 96 | self.assertFalse(checksieve.parse_string(sieve, False)) 97 | 98 | def test_example4(self): 99 | sieve = ''' 100 | require ["enotify"]; 101 | if not valid_notify_method ["mailto:", 102 | "http://gw.example.net/notify?test"] { 103 | stop; 104 | } 105 | ''' 106 | self.assertFalse(checksieve.parse_string(sieve, False)) 107 | 108 | def test_example5(self): 109 | sieve = ''' 110 | require ["enotify"]; 111 | 112 | if notify_method_capability 113 | "xmpp:tim@example.com?message;subject=SIEVE" 114 | "Online" 115 | "yes" { 116 | notify :importance "1" :message "You got mail" 117 | "xmpp:tim@example.com?message;subject=SIEVE"; 118 | } else { 119 | notify :message "You got mail" "tel:+14085551212"; 120 | } 121 | ''' 122 | self.assertFalse(checksieve.parse_string(sieve, False)) 123 | 124 | def test_example6(self): 125 | sieve = ''' 126 | require ["enotify", "variables"]; 127 | 128 | set :encodeurl "body_param" "Safe body&evil=evilbody"; 129 | 130 | notify "mailto:tim@example.com?body=${body_param}"; 131 | ''' 132 | self.assertFalse(checksieve.parse_string(sieve, False)) 133 | 134 | def test_example6_no_variables(self): 135 | sieve = ''' 136 | require ["enotify"]; 137 | 138 | set :encodeurl "body_param" "Safe body&evil=evilbody"; 139 | 140 | notify "mailto:tim@example.com?body=${body_param}"; 141 | ''' 142 | self.assertTrue(checksieve.parse_string(sieve, True)) 143 | 144 | if __name__ == '__main__': 145 | unittest.main() -------------------------------------------------------------------------------- /test/5436/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5436/__init__.py -------------------------------------------------------------------------------- /test/5436/notify_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestMailto(unittest.TestCase): 5 | 6 | def test_example1(self): 7 | sieve = ''' 8 | require ["enotify", "variables"]; 9 | 10 | if header :contains "list-id" "knitting.example.com" { 11 | if header :matches "Subject" "[*] *" { 12 | notify :message "From ${1} list: ${2}" 13 | :importance "3" 14 | "mailto:0123456789@sms.example.net?to=backup@example.com"; 15 | } 16 | } 17 | ''' 18 | self.assertFalse(checksieve.parse_string(sieve, False)) 19 | 20 | if __name__ == '__main__': 21 | unittest.main() -------------------------------------------------------------------------------- /test/5463/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5463/__init__.py -------------------------------------------------------------------------------- /test/5463/error_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestError(unittest.TestCase): 5 | 6 | def test_error(self): 7 | sieve = ''' 8 | require "ihave"; 9 | 10 | addheader "X-Hello" "World"; 11 | if ihave "index" { 12 | deleteheader :index 1 "X-Hello"; 13 | } else { 14 | error "Abort!"; 15 | } 16 | ''' 17 | self.assertFalse(checksieve.parse_string(sieve, False)) 18 | 19 | def test_error_no_message(self): 20 | sieve = ''' 21 | require "ihave"; 22 | 23 | addheader "X-Hello" "World"; 24 | if ihave "index" { 25 | deleteheader :index 1 "X-Hello"; 26 | } else { 27 | error; 28 | } 29 | ''' 30 | self.assertTrue(checksieve.parse_string(sieve, True)) 31 | 32 | if __name__ == '__main__': 33 | unittest.main() -------------------------------------------------------------------------------- /test/5463/ihave_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestIHave(unittest.TestCase): 5 | 6 | def test_ihave_index(self): 7 | sieve = ''' 8 | require "ihave"; 9 | 10 | addheader "X-Hello" "World"; 11 | if ihave "index" { 12 | deleteheader :index 1 "X-Hello"; 13 | } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | 18 | 19 | if __name__ == '__main__': 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /test/5490/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5490/__init__.py -------------------------------------------------------------------------------- /test/5490/fileinto_create_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestCreateTag(unittest.TestCase): 5 | 6 | def test_create_tag(self): 7 | sieve = ''' 8 | require ["fileinto", "reject", "mailbox"]; 9 | if mailboxexists "Partners" { 10 | fileinto :create "Partners"; 11 | } else { 12 | reject "This message was not accepted by the Mailstore"; 13 | } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | 18 | if __name__ == '__main__': 19 | unittest.main() -------------------------------------------------------------------------------- /test/5490/mailboxexists_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestMailboxexists(unittest.TestCase): 5 | 6 | def test_mailboxexists(self): 7 | sieve = ''' 8 | require ["fileinto", "reject", "mailbox"]; 9 | if mailboxexists "Partners" { 10 | fileinto "Partners"; 11 | } else { 12 | reject "This message was not accepted by the Mailstore"; 13 | } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | 18 | if __name__ == '__main__': 19 | unittest.main() -------------------------------------------------------------------------------- /test/5490/metadata_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestMetadata(unittest.TestCase): 5 | 6 | def test_metadata(self): 7 | sieve = ''' 8 | require ["mboxmetadata", "vacation"]; 9 | 10 | if metadata :is "INBOX" 11 | "/private/vendor/vendor.isode/auto-replies" "on" { 12 | 13 | vacation text: 14 | I'm away on holidays till March 2009. 15 | Expect a delay. 16 | . 17 | ; 18 | 19 | } 20 | ''' 21 | self.assertFalse(checksieve.parse_string(sieve, False)) 22 | 23 | def test_metadataexists(self): 24 | sieve = ''' 25 | require ["mboxmetadata", "vacation"]; 26 | 27 | if metadataexists "INBOX" 28 | "/private/vendor/vendor.isode/auto-replies" { 29 | 30 | stop; 31 | 32 | } 33 | ''' 34 | self.assertFalse(checksieve.parse_string(sieve, False)) 35 | 36 | 37 | if __name__ == '__main__': 38 | unittest.main() -------------------------------------------------------------------------------- /test/5490/servermetadata_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestServermetadata(unittest.TestCase): 5 | 6 | def test_servermetadata(self): 7 | sieve = ''' 8 | require ["servermetadata", "variables", "envelope"]; 9 | 10 | if servermetadata :matches 11 | "/private/vendor/vendor.isode/notification-uri" "*" { 12 | set "notif_uri" "${0}"; 13 | } 14 | 15 | if not string :is "${notif_uri}" "none" { 16 | # :matches is used to get the MAIL FROM address 17 | if envelope :all :matches "from" "*" { 18 | set "env_from" " [really: ${1}]"; 19 | } 20 | 21 | # :matches is used to get the value of the Subject header 22 | if header :matches "Subject" "*" { 23 | set "subject" "${1}"; 24 | } 25 | # :matches is used to get the address from the From header 26 | if address :matches :all "from" "*" { 27 | set "from_addr" "${1}"; 28 | } 29 | 30 | # notify :message "${from_addr}${env_from}: ${subject}" 31 | # "${notif_uri}"; 32 | stop; 33 | } 34 | ''' 35 | self.assertFalse(checksieve.parse_string(sieve, False)) 36 | 37 | 38 | if __name__ == '__main__': 39 | unittest.main() -------------------------------------------------------------------------------- /test/5703/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/5703/__init__.py -------------------------------------------------------------------------------- /test/5703/tests_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestTests(unittest.TestCase): 5 | 6 | def test_address(self): 7 | sieve = ''' 8 | require ["mime", "fileinto"]; 9 | 10 | if address :mime :is :all "content-from" "tim@example.com" 11 | { 12 | fileinto "INBOX.part-from-tim"; 13 | } 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | def test_exists(self): 18 | sieve = ''' 19 | require ["mime", "fileinto"]; 20 | 21 | if exists :mime :anychild "content-md5" 22 | { 23 | fileinto "INBOX.md5"; 24 | } 25 | ''' 26 | self.assertFalse(checksieve.parse_string(sieve, False)) 27 | 28 | 29 | if __name__ == '__main__': 30 | unittest.main() 31 | -------------------------------------------------------------------------------- /test/6009/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/6009/__init__.py -------------------------------------------------------------------------------- /test/6009/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class ExamplesTest(unittest.TestCase): 5 | 6 | def test_notify_dsn_01(self): 7 | sieve = ''' 8 | require ["envelope", "envelope-dsn"]; 9 | 10 | # Check whether SUCCESS notifications were requested, 11 | # irrespective of any other requests that were made 12 | if envelope "notify" "SUCCESS" 13 | { 14 | # do whatever 15 | } 16 | ''' 17 | self.assertFalse(checksieve.parse_string(sieve, False)) 18 | 19 | def test_notify_dsn_02(self): 20 | sieve = ''' 21 | require ["envelope", "envelope-dsn", "relational", 22 | "comparator-i;ascii-numeric"]; 23 | 24 | # Check whether only FAILURE notifications were requested 25 | if allof ( envelope "notify" "FAILURE", 26 | envelope :comparator "i;ascii-numeric" 27 | :count "eq" "notify" "1" 28 | ) 29 | { 30 | # do whatever 31 | } 32 | ''' 33 | self.assertFalse(checksieve.parse_string(sieve, False)) 34 | 35 | def test_notify_dsn_03(self): 36 | sieve = ''' 37 | require ["envelope", "envelope-dsn"]; 38 | 39 | # See if the orcpt is an RFC822 address in the example.com 40 | # domain 41 | if envelope :matches "orcpt" "rfc822;*@example.com" 42 | { 43 | # do whatever 44 | } 45 | ''' 46 | self.assertFalse(checksieve.parse_string(sieve, False)) 47 | 48 | def test_notify_deliverby_01(self): 49 | sieve = ''' 50 | require ["envelope", "envelope-deliverby", "relational", 51 | "comparator-i;ascii-numeric"]; 52 | 53 | # Check to see if this message didn't make it in the time allotted by 54 | # the originator. 55 | if anyof (envelope :contains "bytimerelative" "-", 56 | envelope :value "eq" :comparator "i;ascii-numeric" 57 | "bytimerelative" "0") 58 | { 59 | # do whatever 60 | } 61 | ''' 62 | self.assertFalse(checksieve.parse_string(sieve, False)) 63 | 64 | def test_notify_deliverby_02(self): 65 | sieve = ''' 66 | require ["envelope", "envelope-deliverby", "relational", "date", 67 | "variables"]; 68 | # Check to see if this message didn't make it in the time allotted by 69 | # the originator. 70 | if currentdate :matches "iso8601" "*" { 71 | set "cdate" "${0}"; 72 | if envelope :value "ge" "bytimeabsolute" "${cdate}" { 73 | # do whatever 74 | } 75 | } 76 | ''' 77 | self.assertFalse(checksieve.parse_string(sieve, False)) 78 | 79 | def test_notify_deliverby_03(self): 80 | sieve = ''' 81 | require ["envelope", "envelope-deliverby", "relational", "date", 82 | "variables", "fileinto"]; 83 | 84 | # If the message didn't make it in time, file it according to when it 85 | # should have been received 86 | if envelope :matches :zone "+0000" "bytimeabsolute" "*T*:*:*" { 87 | set "bdate" "${0}"; 88 | set "bhour" "${2}"; 89 | if currentdate :zone "+0000" :value "lt" "iso8601" "${bdate}" { 90 | fileinto "missed-${bhour}"; 91 | } 92 | } 93 | ''' 94 | self.assertFalse(checksieve.parse_string(sieve, False)) 95 | 96 | def test_notify_deliverby_04(self): 97 | sieve = ''' 98 | require ["envelope", "relational", "date", 99 | "variables", "fileinto"]; 100 | 101 | # If the message didn't make it in time, file it according to when it 102 | # should have been received 103 | if envelope :matches :zone "+0000" "from" "*T*:*:*" { 104 | set "bdate" "${0}"; 105 | set "bhour" "${2}"; 106 | if currentdate :zone "+0000" :value "lt" "iso8601" "${bdate}" { 107 | fileinto "missed-${bhour}"; 108 | } 109 | } 110 | ''' 111 | self.assertTrue(checksieve.parse_string(sieve, True)) 112 | 113 | def test_redirect_dsn_01(self): 114 | sieve = ''' 115 | require ["copy", "redirect-dsn"]; 116 | 117 | # Make a private copy of messages from user@example.com 118 | if address "from" "user@example.com" 119 | { 120 | redirect :copy :notify "NEVER" "elsewhere@example.com"; 121 | } 122 | ''' 123 | self.assertFalse(checksieve.parse_string(sieve, False)) 124 | 125 | def test_redirect_deliverby_01(self): 126 | sieve = ''' 127 | require ["copy", "redirect-deliverby"]; 128 | 129 | # Send a copy to my cell phone, time out after 10 minutes 130 | if address "from" "user@example.com" 131 | { 132 | redirect :copy :bytimerelative 600 "cellphone@example.com"; 133 | } 134 | ''' 135 | self.assertFalse(checksieve.parse_string(sieve, False)) 136 | 137 | def test_redirect_deliverby_02(self): 138 | sieve = ''' 139 | require ["copy", "redirect-deliverby", "date", "variables", 140 | "relational", "comparator-i;ascii-numeric"]; 141 | 142 | # Send a copy to my cell phone to be delivered before 10PM 143 | if currentdate :value "lt" 144 | :comparator "i;ascii-numeric" "hour" "22" 145 | { 146 | if currentdate :matches "date" "*" {set "date" "${0}";} 147 | if currentdate :matches "zone" "*" {set "zone" "${0}";} 148 | redirect :copy :bytimeabsolute "${date}T20:00:00${zone}" 149 | :bymode "return" "cellphone@example.com"; 150 | } 151 | ''' 152 | self.assertFalse(checksieve.parse_string(sieve, False)) 153 | 154 | 155 | if __name__ == '__main__': 156 | unittest.main() 157 | -------------------------------------------------------------------------------- /test/6134/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/6134/__init__.py -------------------------------------------------------------------------------- /test/6134/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | 5 | class TestExamples(unittest.TestCase): 6 | def test_example_1a(self): 7 | sieve = """ 8 | require ["envelope", "extlists", "fileinto", "spamtest", 9 | "relational", "comparator-i;ascii-numeric"]; 10 | if envelope :list "from" ":addrbook:default" 11 | { /* Known: allow high spam score */ 12 | if spamtest :value "ge" :comparator "i;ascii-numeric" "8" 13 | { 14 | fileinto "spam"; 15 | } 16 | } 17 | elsif spamtest :value "ge" :comparator "i;ascii-numeric" "3" 18 | { /* Unknown: less tolerance in spam score */ 19 | fileinto "spam"; 20 | } 21 | """ 22 | self.assertFalse(checksieve.parse_string(sieve, False)) 23 | 24 | def test_example_1b(self): 25 | sieve = """ 26 | require ["envelope", "extlists", "fileinto", "spamtest", 27 | "variables", "relational", "comparator-i;ascii-numeric"]; 28 | if envelope :list "from" ":addrbook:default" { 29 | set "lim" "8"; /* Known: allow high spam score */ 30 | } else { 31 | set "lim" "3"; /* Unknown: less tolerance in spam score */ 32 | } 33 | if spamtest :value "ge" :comparator "i;ascii-numeric" "${lim}" { 34 | fileinto "spam"; 35 | } 36 | """ 37 | self.assertFalse(checksieve.parse_string(sieve, False)) 38 | 39 | def test_example_2(self): 40 | sieve = """ 41 | require ["extlists", "date", "enotify"]; 42 | if currentdate :list "date" 43 | "tag:example.com,2011-01-01:localHolidays" { 44 | notify "xmpp:romeo@im.example.com"; 45 | } 46 | """ 47 | self.assertFalse(checksieve.parse_string(sieve, False)) 48 | 49 | def test_example_3(self): 50 | sieve = """ 51 | require ["extlists", "envelope", "subaddress"]; 52 | 53 | # Submission from list members is sent to all members 54 | if allof (envelope :detail "to" "mylist", 55 | header :list "from" 56 | "tag:example.com,2010-05-28:mylist") { 57 | redirect :list "tag:example.com,2010-05-28:mylist"; 58 | } 59 | """ 60 | self.assertFalse(checksieve.parse_string(sieve, False)) 61 | 62 | def test_example_4(self): 63 | sieve = """ 64 | require ["variables", "extlists", "index", "reject"]; 65 | if header :index 1 :matches "received" "*(* [*.*.*.*])*" { 66 | set "ip" "${3}.${4}.${5}.${6}"; 67 | if string :list "${ip}" 68 | "tag:example.com,2011-04-10:DisallowedIPs" { 69 | reject "Message not allowed from this IP address"; 70 | } 71 | } 72 | """ 73 | self.assertFalse(checksieve.parse_string(sieve, False)) 74 | 75 | def test_example_5(self): 76 | sieve = """ 77 | require [ "extlists", "foreverypart", "mime", "enclose" ]; 78 | 79 | foreverypart 80 | { 81 | if header :mime :param "filename" 82 | :list ["Content-Type", "Content-Disposition"] 83 | "tag:example.com,2011-04-10:BadFileNameExts" 84 | { 85 | # these attachment types are executable 86 | enclose :subject "Warning" text: 87 | WARNING! The enclosed message has attachments that might be unsafe. 88 | These attachment types may contain a computer virus program 89 | that can infect your computer and potentially damage your data. 90 | 91 | Before clicking on these message attachments, you should verify 92 | with the sender that this message was sent intentionally, and 93 | that the attachments are safe to open. 94 | . 95 | ; 96 | break; 97 | } 98 | } 99 | """ 100 | self.assertFalse(checksieve.parse_string(sieve, False)) 101 | 102 | 103 | if __name__ == "__main__": 104 | unittest.main() 105 | -------------------------------------------------------------------------------- /test/6134/valid_ext_lists_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | 5 | class TestValidExtList(unittest.TestCase): 6 | def test_valid_ext_list(self): 7 | sieve = """ 8 | require ["extlists", "reject"]; 9 | if valid_ext_list ":addrbook:default" 10 | { 11 | reject "Sorry, only friends and work!"; 12 | } 13 | elsif valid_ext_list [":addrbook:frens", ":addrbook:work"] 14 | { 15 | stop; 16 | } 17 | """ 18 | self.assertFalse(checksieve.parse_string(sieve, False)) 19 | 20 | 21 | if __name__ == "__main__": 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /test/6558/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/6558/__init__.py -------------------------------------------------------------------------------- /test/6558/bogus_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestBogus(unittest.TestCase): 5 | 6 | def test_bogus_argument(self): 7 | sieve = ''' 8 | require ["convert"]; 9 | convert "image/tiff" 2 ["pix-x=320","pix-y=240"]; 10 | ''' 11 | self.assertTrue(checksieve.parse_string(sieve, True)) 12 | 13 | def test_unquoted_strings(self): 14 | sieve = ''' 15 | require ["convert"]; 16 | convert image/tiff image/jpeg ["pix-x=320","pix-y=240"]; 17 | ''' 18 | self.assertTrue(checksieve.parse_string(sieve, True)) 19 | 20 | 21 | if __name__ == '__main__': 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /test/6558/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require ["convert"]; 9 | convert "image/tiff" "image/jpeg" ["pix-x=320","pix-y=240"]; 10 | ''' 11 | self.assertFalse(checksieve.parse_string(sieve, False)) 12 | 13 | def test_example_2(self): 14 | sieve = ''' 15 | require ["mime", "fileinto", "convert"]; 16 | if header :mime :anychild :contenttype 17 | "Content-Type" "image/tiff" 18 | { 19 | if (convert "image/tiff" "image/jpeg" ["pix-x=320","pix-y=240"]) { 20 | fileinto "INBOX.pics"; 21 | } 22 | } 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | def test_example_3(self): 27 | sieve = ''' 28 | require ["mime", "foreverypart", "fileinto", "convert"]; 29 | foreverypart 30 | { 31 | if header :mime :param "filename" :contains 32 | "Content-Disposition" "inline" 33 | { 34 | if size :over "500K" 35 | { 36 | convert "image/tiff" "image/jpeg" ["pix-x=640","pix-y=480"]; 37 | } else { 38 | convert "image/tiff" "image/jpeg" ["pix-x=320","pix-y=240"]; 39 | } 40 | } 41 | } 42 | ''' 43 | self.assertFalse(checksieve.parse_string(sieve, False)) 44 | 45 | def test_example_4(self): 46 | sieve = ''' 47 | require ["mime", "foreverypart", 48 | "fileinto", "redirect", "convert"]; 49 | 50 | # The first "if" block will convert all image/tiff body parts 51 | # to 640x480 jpegs and will file the message 52 | # into the "INBOX.pics" mailbox as converted at this point. 53 | if header :mime :anychild :contenttype 54 | "Content-Type" "image/tiff" 55 | { 56 | convert "image/tiff" "image/jpeg" ["pix-x=640","pix-y=480"]; 57 | fileinto "INBOX.pics"; 58 | } 59 | 60 | # The second block, the "foreverypart" loop, will convert all 61 | # inline jpegs to 320x240 resolution... including any tiff body 62 | # parts that had been converted in the first block, above. 63 | # Therefore, any tiff that had been converted to a 640x480 jpeg 64 | # will be re-converted to a 320x240 jpeg here if its 65 | # Content-Disposition is specified as "inline". 66 | foreverypart 67 | { 68 | if header :mime :param "filename" :contains 69 | "Content-Disposition" "inline" 70 | { 71 | convert "image/jpeg" "image/jpeg" ["pix-x=320","pix-y=240"]; 72 | } 73 | } 74 | 75 | # The third block will take any message that contains a header 76 | # field called "Mobile-Link" and redirect it to the user's 77 | # mobile address. The redirected message will include both 78 | # conversions above, from block one and block two. 79 | if exists "Mobile-Link" 80 | { 81 | redirect "joe@mobile.example.com"; 82 | } 83 | 84 | # The fourth block will file the message into "Tiff" if it 85 | # contains any tiff body parts. But because of the earlier 86 | # conversion (in the first block), there will never be any 87 | # tiff body parts, so this "fileinto" will never happen. 88 | if header :mime :anychild :contenttype 89 | "Content-Type" "image/tiff" 90 | { 91 | fileinto "Tiff"; 92 | } 93 | 94 | # Now, at the end of the script processing, the Sieve 95 | # processor will perform an implicit keep if none of 96 | # the "fileinto" and "redirect" actions were taken. 97 | # The kept message will include any conversions that 98 | # were done (that is, any from the second block). 99 | ''' 100 | self.assertFalse(checksieve.parse_string(sieve, False)) 101 | 102 | 103 | if __name__ == '__main__': 104 | unittest.main() 105 | -------------------------------------------------------------------------------- /test/6609/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/6609/__init__.py -------------------------------------------------------------------------------- /test/6609/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require ["include"]; 9 | 10 | include :personal "always_allow"; 11 | include :global "spam_tests"; 12 | include :personal "spam_tests"; 13 | include :personal "mailing_lists"; 14 | ''' 15 | self.assertFalse(checksieve.parse_string(sieve, False)) 16 | 17 | def test_example_2(self): 18 | sieve = ''' 19 | require ["variables", "include"]; 20 | 21 | set "global.i_am_on_vacation" "1"; 22 | ''' 23 | self.assertFalse(checksieve.parse_string(sieve, False)) 24 | 25 | def test_return_command_no_require(self): 26 | sieve = ''' 27 | return; 28 | ''' 29 | self.assertTrue(checksieve.parse_string(sieve, True)) 30 | 31 | def test_return_command(self): 32 | sieve = ''' 33 | require ["include"]; 34 | return; 35 | ''' 36 | self.assertFalse(checksieve.parse_string(sieve, False)) 37 | 38 | def test_include_no_arguments(self): 39 | sieve = ''' 40 | require ["include"]; 41 | include; 42 | ''' 43 | self.assertTrue(checksieve.parse_string(sieve, True)) 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /test/6609/global_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class GlobalTest(unittest.TestCase): 5 | 6 | def test_global(self): 7 | sieve = ''' 8 | require ["include", "variables"]; 9 | 10 | global "test"; 11 | ''' 12 | self.assertFalse(checksieve.parse_string(sieve, False)) 13 | 14 | def test_global_no_require(self): 15 | sieve = ''' 16 | global "test"; 17 | ''' 18 | self.assertTrue(checksieve.parse_string(sieve, True)) 19 | 20 | def test_global_no_include_require(self): 21 | sieve = ''' 22 | require ["variables"]; 23 | 24 | global "test"; 25 | ''' 26 | self.assertTrue(checksieve.parse_string(sieve, True)) 27 | 28 | def test_global_no_variables_require(self): 29 | sieve = ''' 30 | require ["include"]; 31 | 32 | global "test"; 33 | ''' 34 | self.assertTrue(checksieve.parse_string(sieve, True)) 35 | 36 | def test_global_list(self): 37 | sieve = ''' 38 | require ["include", "variables"]; 39 | 40 | global ["foo", "bar", "baz"]; 41 | ''' 42 | self.assertFalse(checksieve.parse_string(sieve, False)) 43 | 44 | def test_global_no_args(self): 45 | sieve = ''' 46 | require ["include", "variables"]; 47 | 48 | global; 49 | ''' 50 | self.assertTrue(checksieve.parse_string(sieve, True)) 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /test/6609/return_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class ReturnTest(unittest.TestCase): 5 | 6 | def test_return(self): 7 | sieve = ''' 8 | require ["include"]; 9 | 10 | return; 11 | ''' 12 | self.assertFalse(checksieve.parse_string(sieve, False)) 13 | 14 | def test_return_extra_arg(self): 15 | sieve = ''' 16 | require ["include"]; 17 | 18 | return "foo"; 19 | ''' 20 | self.assertTrue(checksieve.parse_string(sieve, True)) 21 | 22 | if __name__ == '__main__': 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /test/6785/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/6785/__init__.py -------------------------------------------------------------------------------- /test/6785/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require ["copy", "environment", "imapsieve"]; 9 | 10 | if anyof (environment :is "imap.cause" "APPEND", 11 | environment :is "imap.cause" "COPY") { 12 | if environment :is "imap.mailbox" "ActionItems" { 13 | redirect :copy "actionitems@example.com"; 14 | } 15 | } 16 | ''' 17 | self.assertFalse(checksieve.parse_string(sieve, False)) 18 | 19 | def test_example_2(self): 20 | sieve = ''' 21 | require ["enotify", "imap4flags", "variables", 22 | "environment", "imapsieve"]; 23 | 24 | if environment :matches "imap.mailbox" "*" { 25 | set "mailbox" "${1}"; 26 | } 27 | 28 | if allof (hasflag "\\Flagged", 29 | environment :contains "imap.changedflags" "\\Flagged") { 30 | notify :message "Important message in ${mailbox}" 31 | "xmpp:tim@example.com?message;subject=SIEVE"; 32 | } 33 | ''' 34 | self.assertFalse(checksieve.parse_string(sieve, False)) 35 | 36 | 37 | if __name__ == '__main__': 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /test/6785/norequire_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestNoRequire(unittest.TestCase): 5 | 6 | def test_no_require(self): 7 | sieve = ''' 8 | require ["copy", "environment"]; 9 | 10 | if anyof (environment :is "imap.cause" "APPEND", 11 | environment :is "imap.cause" "COPY") { 12 | if environment :is "imap.mailbox" "ActionItems" { 13 | redirect :copy "actionitems@example.com"; 14 | } 15 | } 16 | ''' 17 | self.assertTrue(checksieve.parse_string(sieve, True)) 18 | 19 | 20 | if __name__ == '__main__': 21 | unittest.main() 22 | -------------------------------------------------------------------------------- /test/7352/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/7352/__init__.py -------------------------------------------------------------------------------- /test/7352/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require ["duplicate", "fileinto", "mailbox"]; 9 | 10 | if duplicate { 11 | fileinto :create "Trash/Duplicate"; 12 | } 13 | ''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | def test_example_2(self): 17 | sieve = ''' 18 | require ["duplicate", "variables", "imap4flags", "fileinto"]; 19 | 20 | if header :matches "subject" "ALERT: *" { 21 | if duplicate :seconds 60 :uniqueid "${1}" { 22 | setflag "\\seen"; 23 | } 24 | fileinto "Alerts"; 25 | } 26 | ''' 27 | self.assertFalse(checksieve.parse_string(sieve, False)) 28 | 29 | def test_example_3(self): 30 | sieve = ''' 31 | require ["variables", "envelope", "enotify", "duplicate"]; 32 | 33 | if envelope :matches "from" "*" { set "sender" "${1}"; } 34 | if header :matches "subject" "*" { set "subject" "${1}"; } 35 | 36 | if not duplicate :seconds 1800 :uniqueid "${sender}" 37 | { 38 | notify :message "[SIEVE] ${sender}: ${subject}" 39 | "xmpp:user@im.example.com"; 40 | } 41 | ''' 42 | self.assertFalse(checksieve.parse_string(sieve, False)) 43 | 44 | def test_example_4(self): 45 | sieve = ''' 46 | require ["variables", "envelope", "enotify", "duplicate"]; 47 | 48 | if envelope :matches "from" "*" { set "sender" "${1}"; } 49 | if header :matches "subject" "*" { set "subject" "${1}"; } 50 | 51 | # account for 'Re:' prefix 52 | if string :comparator "i;ascii-casemap" 53 | :matches "${subject}" "Re:*" 54 | { 55 | set "subject" "${1}"; 56 | } 57 | if not duplicate :seconds 1800 58 | :uniqueid "${sender} ${subject}" 59 | { 60 | notify :message "[SIEVE] ${sender}: ${subject}" 61 | "xmpp:user@im.example.com"; 62 | } 63 | ''' 64 | self.assertFalse(checksieve.parse_string(sieve, False)) 65 | 66 | def test_example_5(self): 67 | sieve = ''' 68 | require ["duplicate", "imap4flags"]; 69 | 70 | if duplicate :header "X-Event-ID" :handle "notifier" { 71 | discard; 72 | } 73 | if allof ( 74 | duplicate :header "X-Ticket-ID" :handle "support", 75 | address "to" "support@example.com", 76 | header :contains "subject" "fileserver") 77 | { 78 | setflag "\\seen"; 79 | } 80 | ''' 81 | self.assertFalse(checksieve.parse_string(sieve, False)) 82 | 83 | 84 | if __name__ == '__main__': 85 | unittest.main() 86 | -------------------------------------------------------------------------------- /test/7352/failure_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestFailures(unittest.TestCase): 5 | 6 | def test_failure_1(self): 7 | sieve = ''' 8 | require ["duplicate", "fileinto", "mailbox"]; 9 | 10 | if duplicate :header "foo" :uniqueid "bar" { 11 | fileinto :create "Trash/Duplicate"; 12 | } 13 | ''' 14 | self.assertTrue(checksieve.parse_string(sieve, True)) 15 | 16 | def test_failure_2(self): 17 | sieve = ''' 18 | require ["duplicate", "fileinto", "mailbox"]; 19 | 20 | if duplicate :index 2 { 21 | fileinto :create "Trash/Duplicate"; 22 | } 23 | ''' 24 | self.assertTrue(checksieve.parse_string(sieve, True)) 25 | 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /test/8579/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/8579/__init__.py -------------------------------------------------------------------------------- /test/8579/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require "imapsieve"; 9 | require "special-use"; 10 | require "environment"; 11 | require "variables"; 12 | 13 | if environment :contains "imap.mailbox" "*" { 14 | set "mailbox" "${1}"; 15 | } 16 | 17 | if allof( 18 | environment "imap.cause" "COPY", 19 | specialuse_exists "${mailbox}" "\\Junk") { 20 | redirect "spam-report@example.org"; 21 | } 22 | ''' 23 | self.assertFalse(checksieve.parse_string(sieve, False)) 24 | 25 | def test_example_2(self): 26 | sieve = ''' 27 | require "fileinto"; 28 | require "special-use"; 29 | 30 | fileinto :specialuse "\\Junk" "Spam"; 31 | ''' 32 | self.assertFalse(checksieve.parse_string(sieve, False)) 33 | 34 | def test_example_3(self): 35 | sieve = ''' 36 | require "fileinto"; 37 | require "special-use"; 38 | require "mailbox"; 39 | 40 | fileinto :specialuse "\\Junk" :create "Spam"; 41 | ''' 42 | self.assertFalse(checksieve.parse_string(sieve, False)) 43 | 44 | def test_example_4(self): 45 | sieve = ''' 46 | require "special-use"; 47 | 48 | if (address :specialuse "foo") { 49 | discard; 50 | } 51 | ''' 52 | self.assertTrue(checksieve.parse_string(sieve, True)) 53 | -------------------------------------------------------------------------------- /test/8580/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/8580/__init__.py -------------------------------------------------------------------------------- /test/8580/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require ["vacation", "fcc", "mailbox", "imap4flags"]; 9 | 10 | vacation :days 7 11 | :from "hemingway@example.com" "Gone Fishin'" 12 | :fcc "INBOX.Sent"; 13 | ''' 14 | self.assertFalse(checksieve.parse_string(sieve, False)) 15 | 16 | def test_example_2(self): 17 | sieve = ''' 18 | 19 | require ["enotify", "fcc"]; 20 | 21 | notify :fcc "INBOX.Sent" 22 | :message "You got mail!" 23 | "mailto:ken@example.com"; 24 | 25 | ''' 26 | self.assertFalse(checksieve.parse_string(sieve, False)) 27 | 28 | def test_example_3(self): 29 | sieve = ''' 30 | 31 | require ["enotify", "fcc"]; 32 | 33 | if notify_method_capability "xmpp:" "fcc" "yes" { 34 | notify :fcc "INBOX.Sent" 35 | :message "You got mail" 36 | "xmpp:ken@example.com?message;subject=SIEVE"; 37 | } else { 38 | notify :fcc "INBOX.Sent" 39 | :message "You got mail!" 40 | "mailto:ken@example.com"; 41 | } 42 | ''' 43 | self.assertFalse(checksieve.parse_string(sieve, False)) 44 | 45 | def test_no_reject(self): 46 | sieve = ''' 47 | require ["reject", "fcc"]; 48 | 49 | if size :over 100K { 50 | reject :fcc "foo" text: 51 | Your message is too big. If you want to send me a big attachment, 52 | put it on a public web site and send me a URL. 53 | . 54 | ; 55 | } 56 | ''' 57 | self.assertTrue(checksieve.parse_string(sieve, True)) -------------------------------------------------------------------------------- /test/9042/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/9042/__init__.py -------------------------------------------------------------------------------- /test/9042/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class TestExamples(unittest.TestCase): 5 | 6 | def test_example_1(self): 7 | sieve = ''' 8 | require "fileinto"; 9 | require "mailboxid"; 10 | 11 | if header :contains ["from"] "coyote" { 12 | fileinto :mailboxid "F6352ae03-b7f5-463c-896f-d8b48ee3" 13 | "INBOX.harassment"; 14 | } 15 | ''' 16 | self.assertFalse(checksieve.parse_string(sieve, False)) 17 | 18 | def test_example_2(self): 19 | sieve = ''' 20 | require "fileinto"; 21 | require "mailboxid"; 22 | require "mailbox"; 23 | 24 | fileinto :mailboxid "Fnosuch" 25 | :create 26 | "INBOX.no-such-folder"; 27 | # creates INBOX.no-such-folder, but it doesn't 28 | # get the "Fnosuch" mailboxid. 29 | ''' 30 | self.assertFalse(checksieve.parse_string(sieve, False)) 31 | 32 | def test_example_3(self): 33 | sieve = ''' 34 | require "fileinto"; 35 | require "mailboxid"; 36 | 37 | if header :contains ["from"] "coyote" { 38 | if mailboxidexists "F6352ae03-b7f5-463c-896f-d8b48ee3" { 39 | fileinto :mailboxid "F6352ae03-b7f5-463c-896f-d8b48ee3" 40 | "INBOX.name.will.not.be.used"; 41 | } else { 42 | fileinto "INBOX.harassment"; 43 | } 44 | } 45 | ''' 46 | self.assertFalse(checksieve.parse_string(sieve, False)) 47 | 48 | def test_specialuse_interaction(self): 49 | sieve = ''' 50 | require "fileinto"; 51 | require "special-use"; 52 | require "mailboxid"; 53 | 54 | fileinto :mailboxid "blah" :specialuse "\\Junk" "Spam"; 55 | ''' 56 | self.assertTrue(checksieve.parse_string(sieve, True)) 57 | -------------------------------------------------------------------------------- /test/9671/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/9671/__init__.py -------------------------------------------------------------------------------- /test/9671/cross_requires_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class CrossRequiresTest(unittest.TestCase): 5 | 6 | def test_extlists_organizers_no(self): 7 | sieve = ''' 8 | require [ "processcalendar" ]; 9 | 10 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 11 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a" 12 | :organizers "foo@example.com"; 13 | ''' 14 | self.assertTrue(checksieve.parse_string(sieve, True)) 15 | 16 | def test_extlists_organizers_yes(self): 17 | sieve = ''' 18 | require [ "processcalendar", "extlists" ]; 19 | 20 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 21 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a" 22 | :organizers "foo@example.com"; 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | def test_variables_outcome_no(self): 27 | sieve = ''' 28 | require [ "processcalendar" ]; 29 | 30 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 31 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a" 32 | :outcome "process_outcome"; 33 | ''' 34 | self.assertTrue(checksieve.parse_string(sieve, True)) 35 | 36 | def test_variables_outcome_yes(self): 37 | sieve = ''' 38 | require [ "processcalendar", "variables" ]; 39 | 40 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 41 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a" 42 | :outcome "process_outcome"; 43 | ''' 44 | self.assertFalse(checksieve.parse_string(sieve, False)) 45 | 46 | def test_variables_reason_no(self): 47 | sieve = ''' 48 | require [ "processcalendar" ]; 49 | 50 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 51 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a" 52 | :reason "process_reason"; 53 | ''' 54 | self.assertTrue(checksieve.parse_string(sieve, True)) 55 | 56 | def test_variables_reason_yes(self): 57 | sieve = ''' 58 | require [ "processcalendar", "variables" ]; 59 | 60 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 61 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a" 62 | :reason "process_reason"; 63 | ''' 64 | self.assertFalse(checksieve.parse_string(sieve, False)) 65 | -------------------------------------------------------------------------------- /test/9671/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | class ExamplesTest(unittest.TestCase): 5 | 6 | def test_example_01(self): 7 | sieve = ''' 8 | require [ "processcalendar" ]; 9 | 10 | processcalendar :addresses [ "me@example.com", "alsome@example.com" ] 11 | :calendarid "1ea6d86b-6c7f-48a2-bed3-2a4c40ec281a"; 12 | ''' 13 | self.assertFalse(checksieve.parse_string(sieve, False)) 14 | 15 | def test_example_02(self): 16 | sieve = ''' 17 | require [ "processcalendar" ]; 18 | 19 | if allof (address ["from", "sender"] "airline@example.com", 20 | header :contains "subject" "itinerary") { 21 | processcalendar :allowpublic; 22 | } 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | def test_example_03(self): 27 | sieve = ''' 28 | require [ "processcalendar", "variables", "editheader" ]; 29 | 30 | set "processcal_outcome" "no_action"; 31 | set "processcal_reason" ""; 32 | 33 | processcalendar :outcome "processcal_outcome" 34 | :reason "processcal_reason"; 35 | 36 | if not string :is "${processcal_outcome}" ["added", "updated"] { 37 | addheader "X-ProcessCal-Outcome" "${processcal_outcome}"; 38 | addheader "X-ProcessCal-Reason" "${processcal_reason}"; 39 | } 40 | ''' 41 | self.assertFalse(checksieve.parse_string(sieve, False)) -------------------------------------------------------------------------------- /test/AST/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/AST/__init__.py -------------------------------------------------------------------------------- /test/AST/commands_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | from . import util 4 | 5 | class TestCommandsAST(util.DiffTestCase): 6 | 7 | def test_require(self): 8 | self.assertNoDiff(util.diff(util.run_mock('commands/require_single.sieve'), 'commands/require_single.out')) 9 | 10 | def test_require_list(self): 11 | self.assertNoDiff(util.diff(util.run_mock('commands/require_list.sieve'), 'commands/require_list.out')) 12 | 13 | def test_stop(self): 14 | self.assertNoDiff(util.diff(util.run_mock('commands/stop.sieve'), 'commands/stop.out')) 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /test/AST/control_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | from . import util 4 | 5 | class TestControlAST(util.DiffTestCase): 6 | 7 | def test_simple_if(self): 8 | self.assertNoDiff(util.diff(util.run_mock('control/if_1.sieve'), 'control/if_1.out')) 9 | 10 | def test_if_else(self): 11 | self.assertNoDiff(util.diff(util.run_mock('control/if_2.sieve'), 'control/if_2.out')) 12 | 13 | def test_if_elsif_else(self): 14 | self.assertNoDiff(util.diff(util.run_mock('control/if_3.sieve'), 'control/if_3.out')) 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /test/AST/mock/commands/require_list.out: -------------------------------------------------------------------------------- 1 | Mail Sieve 2 | Require 3 | String List 4 | String ("fileinto") 5 | String ("imap4flags") 6 | -------------------------------------------------------------------------------- /test/AST/mock/commands/require_list.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags"]; 2 | -------------------------------------------------------------------------------- /test/AST/mock/commands/require_single.out: -------------------------------------------------------------------------------- 1 | Mail Sieve 2 | Require 3 | String ("fileinto") 4 | -------------------------------------------------------------------------------- /test/AST/mock/commands/require_single.sieve: -------------------------------------------------------------------------------- 1 | require "fileinto"; 2 | -------------------------------------------------------------------------------- /test/AST/mock/commands/stop.out: -------------------------------------------------------------------------------- 1 | Mail Sieve 2 | Command (stop) 3 | -------------------------------------------------------------------------------- /test/AST/mock/commands/stop.sieve: -------------------------------------------------------------------------------- 1 | stop; -------------------------------------------------------------------------------- /test/AST/mock/control/if_1.out: -------------------------------------------------------------------------------- 1 | Mail Sieve 2 | Branch 3 | Condition 4 | True 5 | Block 6 | Command (keep) 7 | Command (stop) 8 | -------------------------------------------------------------------------------- /test/AST/mock/control/if_1.sieve: -------------------------------------------------------------------------------- 1 | if true { 2 | keep; 3 | stop; 4 | } -------------------------------------------------------------------------------- /test/AST/mock/control/if_2.out: -------------------------------------------------------------------------------- 1 | Mail Sieve 2 | Branch 3 | Condition 4 | True 5 | Block 6 | Command (keep) 7 | Command (stop) 8 | Block 9 | Command (stop) 10 | -------------------------------------------------------------------------------- /test/AST/mock/control/if_2.sieve: -------------------------------------------------------------------------------- 1 | if true { 2 | keep; 3 | stop; 4 | } else { 5 | stop; 6 | } -------------------------------------------------------------------------------- /test/AST/mock/control/if_3.out: -------------------------------------------------------------------------------- 1 | Mail Sieve 2 | Require 3 | String ("fileinto") 4 | Branch 5 | Condition 6 | True 7 | Block 8 | Command (keep) 9 | Command (stop) 10 | Condition 11 | False 12 | Block 13 | Command (fileinto) 14 | String ("\\Seen") 15 | Command (stop) 16 | Block 17 | Command (stop) 18 | -------------------------------------------------------------------------------- /test/AST/mock/control/if_3.sieve: -------------------------------------------------------------------------------- 1 | require "fileinto"; 2 | 3 | if true { 4 | keep; 5 | stop; 6 | } elsif false { 7 | fileinto "\\Seen"; 8 | stop; 9 | } else { 10 | stop; 11 | } -------------------------------------------------------------------------------- /test/AST/util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import unittest 4 | from tempfile import NamedTemporaryFile 5 | 6 | 7 | class DiffTestCase(unittest.TestCase): 8 | def assertNoDiff(self, diff): 9 | self.assertFalse(diff, msg='\n{}'.format(diff)) 10 | 11 | 12 | def run_mock(filename): 13 | current_dir = os.path.dirname(os.path.realpath(__file__)) 14 | test_path = os.path.join(current_dir, 'mock', filename) 15 | check_sieve_path = os.path.join(current_dir, '../..', 'check-sieve') 16 | 17 | return subprocess.check_output([check_sieve_path, '--trace-tree', test_path]) 18 | 19 | def diff(output, filename): 20 | current_dir = os.path.dirname(os.path.realpath(__file__)) 21 | out_path = os.path.join(current_dir, 'mock', filename) 22 | check_sieve_path = os.path.join(current_dir, '../..', 'check-sieve') 23 | 24 | diff = False 25 | 26 | temp = NamedTemporaryFile(delete=False) 27 | temp.write(output) 28 | temp.close() 29 | 30 | diff = subprocess.Popen(["/usr/bin/diff", "-ru", temp.name, out_path], stdout=subprocess.PIPE).communicate()[0] 31 | 32 | return diff 33 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/__init__.py -------------------------------------------------------------------------------- /test/drafts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/drafts/__init__.py -------------------------------------------------------------------------------- /test/drafts/regex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/drafts/regex/__init__.py -------------------------------------------------------------------------------- /test/drafts/regex/tag_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('./') 3 | 4 | import unittest 5 | import checksieve 6 | 7 | class TestIndex(unittest.TestCase): 8 | 9 | def test_regex(self): 10 | sieve = ''' 11 | require ["fileinto", "regex", "variables"]; 12 | 13 | if header :regex "List-ID" "<(.*)@" { 14 | fileinto "lists.${1}"; stop; 15 | } 16 | 17 | # Imagine the header 18 | # Subject: [acme-users] [fwd] version 1.0 is out 19 | if header :regex "Subject" "^[(.*)] (.*)$" { 20 | # ${1} will hold "acme-users] [fwd" 21 | stop; 22 | } 23 | ''' 24 | self.assertFalse(checksieve.parse_string(sieve, False)) 25 | 26 | def test_regex_no_require(self): 27 | sieve = ''' 28 | require ["fileinto", "variables"]; 29 | 30 | if header :regex "List-ID" "<(.*)@" { 31 | fileinto "lists.${1}"; stop; 32 | } 33 | 34 | # Imagine the header 35 | # Subject: [acme-users] [fwd] version 1.0 is out 36 | if header :regex "Subject" "^[(.*)] (.*)$" { 37 | # ${1} will hold "acme-users] [fwd" 38 | stop; 39 | } 40 | ''' 41 | self.assertTrue(checksieve.parse_string(sieve, True)) 42 | 43 | def test_regex_2(self): 44 | sieve = ''' 45 | require "regex"; 46 | 47 | # Try to catch unsolicited email. 48 | if anyof ( 49 | # if a message is not to me (with optional +detail), 50 | not address :regex ["to", "cc", "bcc"] 51 | "me(\\\\+.*)?@company\\\\.com", 52 | 53 | # or the subject is all uppercase (no lowercase) 54 | header :regex :comparator "i;octet" "subject" 55 | "^[^[:lower:]]+$" ) { 56 | 57 | discard; # junk it 58 | } 59 | ''' 60 | self.assertFalse(checksieve.parse_string(sieve, False)) 61 | 62 | def test_quoteregex(self): 63 | sieve = ''' 64 | require ["regex", "variables"]; 65 | set :quoteregex "b" "[a-zA-Z]*"; 66 | ''' 67 | self.assertFalse(checksieve.parse_string(sieve, False)) 68 | 69 | def test_quoteregex_no_require(self): 70 | sieve = ''' 71 | require ["variables"]; 72 | set :quoteregex "b" "[a-zA-Z]*"; 73 | ''' 74 | self.assertTrue(checksieve.parse_string(sieve, True)) 75 | 76 | if __name__ == '__main__': 77 | unittest.main() 78 | -------------------------------------------------------------------------------- /test/other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/other/__init__.py -------------------------------------------------------------------------------- /test/other/options_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('./') 3 | 4 | import unittest 5 | import checksieve 6 | 7 | class OptionsTest(unittest.TestCase): 8 | 9 | def test_max_list_length(self): 10 | sieve = ''' 11 | require ["include", "variables"]; 12 | 13 | global "test"; 14 | ''' 15 | self.assertFalse(checksieve.parse_string_with_options(sieve, {"string_list_max_length": 2})) 16 | 17 | def test_max_list_length(self): 18 | sieve = ''' 19 | require ["include", "variables", "imap4flags"]; 20 | 21 | global "test"; 22 | ''' 23 | self.assertTrue(checksieve.parse_string_with_options(sieve, {"string_list_max_length": 2})) 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /test/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import Extension, setup 2 | 3 | module = Extension('checksieve', 4 | extra_compile_args = ['-std=c++17'], 5 | sources = ['src/python.cc'], 6 | include_dirs = ['src', 'src/AST', 'gen'], 7 | libraries = ['checksieve'], 8 | library_dirs = ['./'] ); 9 | 10 | setup (name = 'CheckSieve', 11 | version = '0.11-dev', 12 | description = 'Python interface to libchecksieve', 13 | author = 'Dana Burkart', 14 | author_email = 'dana.burkart@gmail.com', 15 | ext_modules = [module] ); 16 | -------------------------------------------------------------------------------- /test/vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/vendor/__init__.py -------------------------------------------------------------------------------- /test/vendor/dovecot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/vendor/dovecot/__init__.py -------------------------------------------------------------------------------- /test/vendor/dovecot/examples_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | 5 | class TestExamples(unittest.TestCase): 6 | def test_pipe_01(self): 7 | sieve = ''' 8 | require [ "vnd.dovecot.pipe", "subaddress", "envelope" ]; 9 | 10 | if envelope :detail "to" "request" 11 | { 12 | pipe "request-handler"; 13 | } 14 | 15 | ''' 16 | self.assertFalse(checksieve.parse_string(sieve, False)) 17 | 18 | def test_pipe_02(self): 19 | sieve = '''require [ "vnd.dovecot.pipe", "copy" ]; 20 | 21 | if address "to" "snailmail@example.com" 22 | { 23 | pipe :copy "printer" ["A4", "draft"]; 24 | }''' 25 | self.assertFalse(checksieve.parse_string(sieve, False)) 26 | 27 | def test_filter_01(self): 28 | sieve = '''require [ "vnd.dovecot.filter", "fileinto" ]; 29 | 30 | if header "content-language" "nl" 31 | { 32 | filter "translator" ["nl", "en"]; 33 | fileinto "Translated"; 34 | stop; 35 | }''' 36 | self.assertFalse(checksieve.parse_string(sieve, False)) 37 | 38 | def test_filter_02(self): 39 | sieve = '''require [ "vnd.dovecot.filter", "fileinto" ]; 40 | 41 | if header "content-language" "nl" 42 | { 43 | if filter "translator" ["nl", "en"] 44 | { 45 | fileinto "Translated"; 46 | stop; 47 | } 48 | }''' 49 | self.assertFalse(checksieve.parse_string(sieve, False)) 50 | 51 | def test_execute_01(self): 52 | sieve = '''require [ "vnd.dovecot.execute", "vacation", "variables", 53 | "envelope" ]; 54 | 55 | if envelope :localpart :matches "to" "*" 56 | { 57 | set "recipient" "${1}"; 58 | } 59 | 60 | if execute :output "vacation_message" "onvacation" "${recipient}" 61 | { 62 | vacation "${vacation_message}"; 63 | }''' 64 | self.assertFalse(checksieve.parse_string(sieve, False)) 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /test/vendor/proton/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dburkart/check-sieve/6fef71e4d5b983d3ee6955c627aa3b5f738b195d/test/vendor/proton/__init__.py -------------------------------------------------------------------------------- /test/vendor/proton/eval_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | 5 | class TestProtonEval(unittest.TestCase): 6 | def test_eval(self): 7 | sieve = ''' 8 | require "variables"; 9 | require "fileinto"; 10 | require "vnd.proton.eval"; 11 | # do a match test on the sender address 12 | if header :matches "from" "*" { 13 | # create a variable called length, containing the length of the first 14 | # matching variable 15 | set :length "length" "${1}"; 16 | # Create a variable called fileintovar containing the result of the expression written below 17 | set :eval "fileintovar" "${length} * 25 - 1 / 8+3"; 18 | fileinto "${fileintovar}"; 19 | } 20 | ''' 21 | self.assertFalse(checksieve.parse_string(sieve, False)) 22 | 23 | def test_eval_no_require(self): 24 | sieve = ''' 25 | require "variables"; 26 | require "fileinto"; 27 | # do a match test on the sender address 28 | if header :matches "from" "*" { 29 | # create a variable called length, containing the length of the first 30 | # matching variable 31 | set :length "length" "${1}"; 32 | # Create a variable called fileintovar containing the result of the expression written below 33 | set :eval "fileintovar" "${length} * 25 - 1 / 8+3"; 34 | fileinto "${fileintovar}"; 35 | } 36 | ''' 37 | self.assertTrue(checksieve.parse_string(sieve, True)) 38 | 39 | def test_eval_no_variables_require(self): 40 | sieve = ''' 41 | require "fileinto"; 42 | require "vnd.proton.eval"; 43 | # do a match test on the sender address 44 | if header :matches "from" "*" { 45 | # create a variable called length, containing the length of the first 46 | # matching variable 47 | set :length "length" "${1}"; 48 | # Create a variable called fileintovar containing the result of the expression written below 49 | set :eval "fileintovar" "${length} * 25 - 1 / 8+3"; 50 | fileinto "${fileintovar}"; 51 | } 52 | ''' 53 | self.assertTrue(checksieve.parse_string(sieve, True)) 54 | 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /test/vendor/proton/expire_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import checksieve 3 | 4 | 5 | class TestProtonExpire(unittest.TestCase): 6 | def test_expire(self): 7 | sieve = ''' 8 | require "vnd.proton.expire"; 9 | # permanently delete all incoming and outgoing emails after 10 days 10 | expire "day" "10"; 11 | ''' 12 | self.assertFalse(checksieve.parse_string(sieve, False)) 13 | 14 | def test_expire_no_require(self): 15 | sieve = ''' 16 | # permanently delete all incoming and outgoing emails after 10 days 17 | expire "day" "10"; 18 | ''' 19 | self.assertTrue(checksieve.parse_string(sieve, True)) 20 | 21 | def test_hasexpiration(self): 22 | sieve = ''' 23 | require "vnd.proton.expire"; 24 | 25 | if hasexpiration { 26 | stop; 27 | } 28 | ''' 29 | self.assertFalse(checksieve.parse_string(sieve, False)) 30 | 31 | def test_hasexpiration_no_require(self): 32 | sieve = ''' 33 | if hasexpiration { 34 | stop; 35 | } 36 | ''' 37 | self.assertTrue(checksieve.parse_string(sieve, True)) 38 | 39 | def test_expiration(self): 40 | sieve = ''' 41 | require "comparator-i;ascii-numeric"; 42 | require "vnd.proton.expire"; 43 | 44 | if expiration :comparator "i;ascii-numeric" "ge" "day" "5" { 45 | stop; 46 | } 47 | ''' 48 | self.assertFalse(checksieve.parse_string(sieve, False)) 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | --------------------------------------------------------------------------------