diff --git a/.gitignore b/.gitignore index 2c194fa6..d3d20557 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ cover htmlcov dist build + proxy/public +docs/_build diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 84e4f570..6634e375 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at mailsforabhinav@gmail.com. All +reported by contacting the project team at mailsforabhinav+proxy@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/Makefile b/Makefile index 791eef17..9eebd4ac 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,8 @@ SHELL := /bin/bash NS ?= abhinavsingh IMAGE_NAME ?= proxy.py -VERSION ?= v$(shell ./scm-version.sh) LATEST_TAG := $(NS)/$(IMAGE_NAME):latest -IMAGE_TAG := $(NS)/$(IMAGE_NAME):$(VERSION) +IMAGE_TAG := $(NS)/$(IMAGE_NAME):$(shell ./write-scm-version.sh) HTTPS_KEY_FILE_PATH := https-key.pem HTTPS_CERT_FILE_PATH := https-cert.pem @@ -17,12 +16,14 @@ CA_SIGNING_KEY_FILE_PATH := ca-signing-key.pem .PHONY: all https-certificates sign-https-certificates ca-certificates .PHONY: lib-check lib-clean lib-test lib-package lib-coverage lib-lint lib-pytest -.PHONY: lib-release-test lib-release lib-profile -.PHONY: lib-dep, lib-flake8, lib-mypy, lib-scm-version +.PHONY: lib-release-test lib-release lib-profile lib-doc +.PHONY: lib-dep lib-flake8 lib-mypy .PHONY: container container-run container-release .PHONY: devtools dashboard dashboard-clean -all: lib-test +all: + echo $(IMAGE_TAG) + # lib-test https-certificates: # Generate server key @@ -82,14 +83,13 @@ lib-clean: rm -rf .hypothesis lib-dep: + pip install --upgrade pip && \ pip install \ -r requirements.txt \ -r requirements-testing.txt \ -r requirements-release.txt \ - -r requirements-tunnel.txt - -lib-scm-version: - @echo "version = '$(VERSION)'" > proxy/common/_scm_version.py + -r requirements-tunnel.txt \ + -r docs/requirements.txt lib-lint: python -m tox -e lint @@ -114,6 +114,16 @@ lib-release-test: lib-package lib-release: lib-package twine upload dist/* +lib-doc: + pushd docs && \ + python -m sphinx \ + --keep-going \ + -b dirhtml \ + -d _build/doctrees \ + -D language=en . _build/html && \ + popd && \ + open docs/_build/html/index.html + lib-coverage: pytest --cov=proxy --cov=tests --cov-report=html tests/ open htmlcov/index.html diff --git a/README.md b/README.md index 94695dd7..59d68392 100644 --- a/README.md +++ b/README.md @@ -379,7 +379,7 @@ To start `proxy.py` from source code follow these instructions: - Install deps ```console - ❯ make lib-dep lib-scm-version + ❯ make lib-dep ``` - Optionally, run tests diff --git a/proxy/http/methods.py b/proxy/http/methods.py index dbf1f2f7..30c9b71a 100644 --- a/proxy/http/methods.py +++ b/proxy/http/methods.py @@ -16,28 +16,91 @@ from typing import NamedTuple +# Ref: https://www.iana.org/assignments/http-methods/http-methods.xhtml HttpMethods = NamedTuple( 'HttpMethods', [ + ('ACL', bytes), + ('BASELINE_CONTROL', bytes), + ('BIND', bytes), + ('CHECKIN', bytes), + ('CHECKOUT', bytes), + ('CONNECT', bytes), + ('COPY', bytes), + ('DELETE', bytes), ('GET', bytes), ('HEAD', bytes), - ('POST', bytes), - ('PUT', bytes), - ('DELETE', bytes), - ('CONNECT', bytes), + ('LABEL', bytes), + ('LINK', bytes), + ('LOCK', bytes), + ('MERGE', bytes), + ('MKACTIVITY', bytes), + ('MKCALENDAR', bytes), + ('MKCOL', bytes), + ('MKREDIRECTREF', bytes), + ('MKWORKSPACE', bytes), + ('MOVE', bytes), ('OPTIONS', bytes), - ('TRACE', bytes), + ('ORDERPATCH', bytes), ('PATCH', bytes), + ('POST', bytes), + ('PRI', bytes), + ('PROPFIND', bytes), + ('PROPPATCH', bytes), + ('PUT', bytes), + ('REBIND', bytes), + ('REPORT', bytes), + ('SEARCH', bytes), + ('TRACE', bytes), + ('UNBIND', bytes), + ('UNCHECKOUT', bytes), + ('UNLINK', bytes), + ('UNLOCK', bytes), + ('UPDATE', bytes), + ('UPDATEREDIRECTREF', bytes), + ('VERSION_CONTROL', bytes), + ('STAR', bytes), ], ) httpMethods = HttpMethods( + b'ACL', + b'BASELINE-CONTROL', + b'BIND', + b'CHECKIN', + b'CHECKOUT', + b'CONNECT', + b'COPY', + b'DELETE', b'GET', b'HEAD', - b'POST', - b'PUT', - b'DELETE', - b'CONNECT', + b'LABEL', + b'LINK', + b'LOCK', + b'MERGE', + b'MKACTIVITY', + b'MKCALENDAR', + b'MKCOL', + b'MKREDIRECTREF', + b'MKWORKSPACE', + b'MOVE', b'OPTIONS', - b'TRACE', + b'ORDERPATCH', b'PATCH', + b'POST', + b'PRI', + b'PROPFIND', + b'PROPPATCH', + b'PUT', + b'REBIND', + b'REPORT', + b'SEARCH', + b'TRACE', + b'UNBIND', + b'UNCHECKOUT', + b'UNLINK', + b'UNLOCK', + b'UPDATE', + b'UPDATEREDIRECTREF', + b'VERSION-CONTROL', + b'*', ) diff --git a/proxy/http/parser/parser.py b/proxy/http/parser/parser.py index 151e037f..cff81abd 100644 --- a/proxy/http/parser/parser.py +++ b/proxy/http/parser/parser.py @@ -304,16 +304,12 @@ class HttpParser: def _process_line(self, raw: bytes) -> None: if self.type == httpParserTypes.REQUEST_PARSER: - # Ref: - # https://datatracker.ietf.org/doc/html/rfc2616#section-5.1 - # https://greenbytes.de/tech/webdav/rfc7230.html#request.line - # https://greenbytes.de/tech/webdav/rfc7231.html#methods - # http://www.iana.org/assignments/http-methods/http-methods.xhtml if self.protocol is not None and self.protocol.version is None: # We expect to receive entire proxy protocol v1 line # in one network read and don't expect partial packets self.protocol.parse(raw) else: + # Ref: https://datatracker.ietf.org/doc/html/rfc2616#section-5.1 line = raw.split(WHITESPACE) if len(line) == 3: self.method = line[0].upper() @@ -321,10 +317,12 @@ class HttpParser: self.version = line[2] self.state = httpParserStates.LINE_RCVD else: - # raise exception - # TODO, it would be better to use raise HttpProtocolException, - # but we should solve circular import problem first - raise NotImplementedError('Invalid request line') + # To avoid a possible attack vector, we raise exception + # if parser receives an invalid request line. + # + # TODO: Better to use raise HttpProtocolException, + # but we should solve circular import problem first. + raise ValueError('Invalid request line') else: line = raw.split(WHITESPACE) self.version = line[0] diff --git a/scm-version.sh b/scm-version.sh deleted file mode 100755 index 50957913..00000000 --- a/scm-version.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# Guessed Version 2.3.2.dev146+gad54132.d20211114 -python -m setuptools_scm --version | \ - # 2.3.2.dev146+gad54132.d20211114 - awk '{print $3}' | \ - # 2.3.2.dev146-gad54132.d20211114 - sed 's/\+/-/' | \ - # 2.3.2.dev146-gad54132-d20211114 - sed -E 's/(.*)\./\1-/' | \ - # 2.3.2-dev146-gad54132-d20211114 - sed -E 's/(.*)\./\1-/' | \ - # 2.3.2-dev146-gad54132.d20211114 - sed -E 's/(.*)-/\1\./' | \ - # 2.3.2-dev146.gad54132.d20211114 - sed -E 's/(.*)-/\1\./' diff --git a/setup.cfg b/setup.cfg index 499337f8..db77dbb0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ description = ⚡⚡⚡Fast, Lightweight, Pluggable, TLS interception capable pr long_description = file: README.md long_description_content_type = text/markdown author = Abhinav Singh -author_email = mailsforabhinav@gmail.com +author_email = mailsforabhinav+proxy@gmail.com license = 'BSD' license_files = LICENSE.md diff --git a/tests/http/test_http_parser.py b/tests/http/test_http_parser.py index 4ef44123..8d97a8fa 100644 --- a/tests/http/test_http_parser.py +++ b/tests/http/test_http_parser.py @@ -24,10 +24,10 @@ class TestHttpParser(unittest.TestCase): self.parser = HttpParser(httpParserTypes.REQUEST_PARSER) def test_issue_127(self) -> None: - with self.assertRaises(NotImplementedError): + with self.assertRaises(ValueError): self.parser.parse(CRLF) - with self.assertRaises(NotImplementedError): + with self.assertRaises(ValueError): raw = b'qwqrqw!@!#@!#ad adfad\r\n' while True: self.parser.parse(raw) diff --git a/tests/http/test_protocol_handler.py b/tests/http/test_protocol_handler.py index 7ef30b7c..c37740a4 100644 --- a/tests/http/test_protocol_handler.py +++ b/tests/http/test_protocol_handler.py @@ -397,7 +397,8 @@ class TestHttpProtocolHandler(unittest.TestCase): b'Via: 1.1 proxy.py v%s' % bytes_(__version__), CRLF, ]) - server.queue.assert_called_once_with(pkt) + server.queue.assert_called_once() + self.assertEqual(server.queue.call_args_list[0][0][0].tobytes(), pkt) server.buffer_size.return_value = len(pkt) def assert_data_queued_to_server(self, server: mock.Mock) -> None: diff --git a/write-scm-version.sh b/write-scm-version.sh new file mode 100755 index 00000000..320e8ca6 --- /dev/null +++ b/write-scm-version.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# +# write-scm-version.sh exists because `proxy.py`` +# auto-detects it's next version from git. Hence, +# for `proxy.py` to work `proxy/common/_scm_version.py` +# file must be auto-generated with necessary information. +# +# For CI/CD, this file is generated via `tox` integration. +# For local development (without editable install), you +# must run this script to pre-populate `_scm_version.py`. +# +# This file is integrated by default within `Makefile`. +# For every make target invocation, `_scm_version.py` file +# will be re-written. + +# Guessed Version 2.3.2.dev146+gad54132.d20211114 +VERSION=$(python -m setuptools_scm --version | \ + # 2.3.2.dev146+gad54132.d20211114 + awk '{print $3}') + +# Store default IFS +OLDIFS=$IFS + +IFS="+" +set -- $VERSION +SEMVER=$1 +DATE_AND_HASH=$2 + +IFS="." +set -- $SEMVER +MAJOR=$1 +MINOR=$2 +PATCH=$3 +DISTANCE=$4 + +# Reset IFS +IFS=$OLDIFS + +echo "# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '${VERSION}' +version_tuple = (${MAJOR}, ${MINOR}, ${PATCH}, '${DISTANCE}', '${DATE_AND_HASH}')" > \ + proxy/common/_scm_version.py + +echo $MAJOR.$MINOR.$PATCH.$DISTANCE