From 7e640f804cacc2189e6f539b7b8861a2ef1a2461 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Ba=C5=A1ti?= <mbasti@redhat.com>
Date: Thu, 11 Jul 2019 19:01:43 +0200
Subject: [PATCH 01/14] Logging: Handle auth type case insensitively
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

According RFC-7617 (inherited from RFC-2978) schema and parameter names are handled
case insensitively:
```
Note that both scheme and parameter names are matched case-
insensitively.
```

Signed-off-by: Martin Bašti <mbasti@redhat.com>
---
 gunicorn/glogging.py |  2 +-
 tests/test_logger.py | 12 ++++++++++--
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/gunicorn/glogging.py b/gunicorn/glogging.py
index 3f7b4ac79..a096f9679 100644
--- a/gunicorn/glogging.py
+++ b/gunicorn/glogging.py
@@ -445,7 +445,7 @@ def _set_syslog_handler(self, log, cfg, fmt, name):
     def _get_user(self, environ):
         user = None
         http_auth = environ.get("HTTP_AUTHORIZATION")
-        if http_auth and http_auth.startswith('Basic'):
+        if http_auth and http_auth.lower().startswith('basic'):
             auth = http_auth.split(" ", 1)
             if len(auth) == 2:
                 try:
diff --git a/tests/test_logger.py b/tests/test_logger.py
index 5b8c0d42f..54801266c 100644
--- a/tests/test_logger.py
+++ b/tests/test_logger.py
@@ -1,6 +1,8 @@
 import datetime
 from types import SimpleNamespace
 
+import pytest
+
 from gunicorn.config import Config
 from gunicorn.glogging import Logger
 
@@ -47,7 +49,13 @@ def test_atoms_zero_bytes():
     assert atoms['B'] == 0
 
 
-def test_get_username_from_basic_auth_header():
+@pytest.mark.parametrize('auth', [
+    # auth type is case in-sensitive
+    'Basic YnJrMHY6',
+    'basic YnJrMHY6',
+    'BASIC YnJrMHY6',
+])
+def test_get_username_from_basic_auth_header(auth):
     request = SimpleNamespace(headers=())
     response = SimpleNamespace(
         status='200', response_length=1024, sent=1024,
@@ -57,7 +65,7 @@ def test_get_username_from_basic_auth_header():
         'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar',
         'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar',
         'SERVER_PROTOCOL': 'HTTP/1.1',
-        'HTTP_AUTHORIZATION': 'Basic YnJrMHY6',
+        'HTTP_AUTHORIZATION': auth,
     }
     logger = Logger(Config())
     atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1))

From f38f717539b1b7296720805b8ae3969c3509b9c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Ba=C5=A1ti?= <mbasti@redhat.com>
Date: Thu, 11 Jul 2019 19:12:16 +0200
Subject: [PATCH 02/14] Fix pytest 5.0.0 compatibility
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

pytest.raises() returns exception info not the exception itself. They
changed implementation of exception info, so now .value property must be
used to get the exception instance and have proper output from str()
method.

https://github.com/pytest-dev/pytest/issues/5412

Signed-off-by: Martin Bašti <mbasti@redhat.com>
---
 tests/test_util.py | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/tests/test_util.py b/tests/test_util.py
index 3b8688a21..2494d2c54 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -29,15 +29,15 @@ def test_parse_address(test_input, expected):
 
 
 def test_parse_address_invalid():
-    with pytest.raises(RuntimeError) as err:
+    with pytest.raises(RuntimeError) as exc_info:
         util.parse_address('127.0.0.1:test')
-    assert "'test' is not a valid port number." in str(err)
+    assert "'test' is not a valid port number." in str(exc_info.value)
 
 
 def test_parse_fd_invalid():
-    with pytest.raises(RuntimeError) as err:
+    with pytest.raises(RuntimeError) as exc_info:
         util.parse_address('fd://asd')
-    assert "'asd' is not a valid file descriptor." in str(err)
+    assert "'asd' is not a valid file descriptor." in str(exc_info.value)
 
 
 def test_http_date():
@@ -63,24 +63,24 @@ def test_warn(capsys):
 def test_import_app():
     assert util.import_app('support:app')
 
-    with pytest.raises(ImportError) as err:
+    with pytest.raises(ImportError) as exc_info:
         util.import_app('a:app')
-    assert 'No module' in str(err)
+    assert 'No module' in str(exc_info.value)
 
-    with pytest.raises(AppImportError) as err:
+    with pytest.raises(AppImportError) as exc_info:
         util.import_app('support:wrong_app')
     msg = "Failed to find application object 'wrong_app' in 'support'"
-    assert msg in str(err)
+    assert msg in str(exc_info.value)
 
 
 def test_to_bytestring():
     assert util.to_bytestring('test_str', 'ascii') == b'test_str'
     assert util.to_bytestring('test_str®') == b'test_str\xc2\xae'
     assert util.to_bytestring(b'byte_test_str') == b'byte_test_str'
-    with pytest.raises(TypeError) as err:
+    with pytest.raises(TypeError) as exc_info:
         util.to_bytestring(100)
     msg = '100 is not a string'
-    assert msg in str(err)
+    assert msg in str(exc_info.value)
 
 
 @pytest.mark.parametrize('test_input, expected', [

From 40802904ebba5a8b1ab6bdec927bacd09fd1b099 Mon Sep 17 00:00:00 2001
From: Randall Leeds <randall@bleeds.info>
Date: Sun, 16 Jun 2019 23:50:45 -0400
Subject: [PATCH 03/14] Avoid unnecessary chown of temporary files

When Gunicorn is configured to change the effective user or group of the
worker processes, it changes the owner and group fo the the temporary
files used for interprocess communication.

With this change, Gunicorn does not change the owner or group of the
files if the worker processes will run as the current effective user and
gorup. This change avoids calling chown when it is not necessary, which
may allow Gunicorn to be used in environments that restrict use of the
chown syscall.

Relates to #2059.
---
 gunicorn/workers/workertmp.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/gunicorn/workers/workertmp.py b/gunicorn/workers/workertmp.py
index 22aaef34c..a37ed1558 100644
--- a/gunicorn/workers/workertmp.py
+++ b/gunicorn/workers/workertmp.py
@@ -21,11 +21,13 @@ def __init__(self, cfg):
         if fdir and not os.path.isdir(fdir):
             raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir)
         fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir)
-
-        # allows the process to write to the file
-        util.chown(name, cfg.uid, cfg.gid)
         os.umask(old_umask)
 
+        # change the owner and group of the file if the worker will run as
+        # a different user or group, so that the worker can modify the file
+        if cfg.uid != os.geteuid() or cfg.gid != os.getegid():
+            util.chown(name, cfg.uid, cfg.gid)
+
         # unlink the file so we don't leak tempory files
         try:
             if not IS_CYGWIN:

From 40d22ae38d577a6d364730ae8fba669693e87706 Mon Sep 17 00:00:00 2001
From: John Whitlock <jwhitlock@mozilla.com>
Date: Mon, 19 Aug 2019 19:27:59 -0500
Subject: [PATCH 04/14] Add setproctitle to extras_require (#2094)

This allows you to specify that you want setproctitle installed so that
gunicorn can set meaningful process names at install time or in a
requirements file.
---
 docs/source/custom.rst  |  3 ++-
 docs/source/install.rst | 22 ++++++++++++++++++++++
 setup.py                |  1 +
 3 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/docs/source/custom.rst b/docs/source/custom.rst
index 0b8c366c0..0fb392501 100644
--- a/docs/source/custom.rst
+++ b/docs/source/custom.rst
@@ -13,7 +13,8 @@ Here is a small example where we create a very small WSGI app and load it with
 a custom Application:
 
 .. literalinclude:: ../../examples/standalone_app.py
-    :lines: 11-60
+    :start-after: # See the NOTICE for more information
+    :lines: 2-
 
 Direct Usage of Existing WSGI Apps
 ----------------------------------
diff --git a/docs/source/install.rst b/docs/source/install.rst
index 3002a6110..d6d146d22 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -52,6 +52,28 @@ want to consider one of the alternate worker types.
     installed, this is the most likely reason.
 
 
+Extra Packages
+==============
+Some Gunicorn options require additional packages. You can use the ``[extra]``
+syntax to install these at the same time as Gunicorn.
+
+Most extra packages are needed for alternate worker types. See the
+`design docs`_ for more information on when you'll want to consider an
+alternate worker type.
+
+* ``gunicorn[eventlet]`` - Eventlet-based greenlets workers
+* ``gunicorn[gevent]`` - Gevent-based greenlets workers
+* ``gunicorn[gthread]`` - Threaded workers
+* ``gunicorn[tornado]`` - Tornado-based workers, not recommended
+
+If you are running more than one instance of Gunicorn, the :ref:`proc-name`
+setting will help distinguish between them in tools like ``ps`` and ``top``.
+
+* ``gunicorn[setproctitle]`` - Enables setting the process name
+
+Multiple extras can be combined, like
+``pip install gunicorn[gevent,setproctitle]``.
+
 Debian GNU/Linux
 ================
 
diff --git a/setup.py b/setup.py
index ee898d884..31d173f62 100644
--- a/setup.py
+++ b/setup.py
@@ -78,6 +78,7 @@ def run_tests(self):
     'eventlet': ['eventlet>=0.9.7'],
     'tornado': ['tornado>=0.2'],
     'gthread': [],
+    'setproctitle': ['setproctitle'],
 }
 
 setup(

From d765f0d123fff5da0f36da8f087a8dd0da778411 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=9E=A5=EC=A4=80=EC=98=81?= <wnsdud3256@gmail.com>
Date: Tue, 20 Aug 2019 09:34:18 +0900
Subject: [PATCH 05/14] Group exceptions with same body together in
 Arbiter.run() (#2081)

---
 gunicorn/arbiter.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/gunicorn/arbiter.py b/gunicorn/arbiter.py
index 7eaa2c177..bca671d17 100644
--- a/gunicorn/arbiter.py
+++ b/gunicorn/arbiter.py
@@ -223,9 +223,7 @@ def run(self):
                 self.log.info("Handling signal: %s", signame)
                 handler()
                 self.wakeup()
-        except StopIteration:
-            self.halt()
-        except KeyboardInterrupt:
+        except (StopIteration, KeyboardInterrupt):
             self.halt()
         except HaltServer as inst:
             self.halt(reason=inst.reason, exit_status=inst.exit_status)

From 799df751c71c3c4024bfe5d4cb884ca159370a04 Mon Sep 17 00:00:00 2001
From: Leonardo Furtado <srleonardofurtado@gmail.com>
Date: Mon, 19 Aug 2019 21:46:22 -0300
Subject: [PATCH 06/14] Add link to CONTRIBUTING.md from README.rst (#2069)

---
 README.rst | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.rst b/README.rst
index 6b9bcaf19..c9e3ebdff 100644
--- a/README.rst
+++ b/README.rst
@@ -52,6 +52,12 @@ Example with test app::
     $ gunicorn --workers=2 test:app
 
 
+Contributing
+------------
+
+See `our complete contributor's guide <CONTRIBUTING.md>`_ for more details.
+
+
 License
 -------
 

From f35ae584b41b4808a23a689d3168e87328d5ebb1 Mon Sep 17 00:00:00 2001
From: johnthagen <johnthagen@users.noreply.github.com>
Date: Sat, 7 Sep 2019 21:55:26 -0400
Subject: [PATCH 07/14] Add pypy3 to list of tested environments (#2105)

---
 .travis.yml | 3 +++
 setup.py    | 2 ++
 tox.ini     | 2 +-
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 97c578aaf..1d569a76c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,9 @@ matrix:
       env: TOXENV=py37
       dist: xenial
       sudo: true
+    - python: pypy3
+      env: TOXENV=pypy3
+      dist: xenial
     - python: 3.8-dev
       env: TOXENV=py38-dev
       dist: xenial
diff --git a/setup.py b/setup.py
index 31d173f62..8d79fb7a5 100644
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,8 @@
     'Programming Language :: Python :: 3.6',
     'Programming Language :: Python :: 3.7',
     'Programming Language :: Python :: 3 :: Only',
+    'Programming Language :: Python :: Implementation :: CPython',
+    'Programming Language :: Python :: Implementation :: PyPy',
     'Topic :: Internet',
     'Topic :: Utilities',
     'Topic :: Software Development :: Libraries :: Python Modules',
diff --git a/tox.ini b/tox.ini
index 47249d6e6..96388fa74 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py34, py35, py36, py37, py38-dev, pypy, lint
+envlist = py34, py35, py36, py37, py38-dev, pypy3, lint
 skipsdist = True
 
 [testenv]

From 49341f1fb35fec7f0606faa1e3129174e12dd28a Mon Sep 17 00:00:00 2001
From: Tyler Lubeck <tyler@tylerlubeck.com>
Date: Thu, 26 Sep 2019 14:54:54 -0700
Subject: [PATCH 08/14] Terminology changes

---
 CONTRIBUTING.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1a6880bb4..ac93950dc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -141,7 +141,7 @@ The relevant maintainer for a pull request is assigned in 3 steps:
 
 * Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one.
 
-* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to him. He may assign it to other listed maintainers, at his discretion.
+* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to them. They may assign it to other listed maintainers, at their discretion.
 
 
 ### I'm a maintainer, should I make pull requests too?

From ce03c192f4ca312dac93f52e61ccdf50e01b09d8 Mon Sep 17 00:00:00 2001
From: Benoit Chesneau <bchesneau@gmail.com>
Date: Fri, 27 Sep 2019 01:45:03 +0200
Subject: [PATCH 09/14] fix formatting

---
 gunicorn/http/body.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gunicorn/http/body.py b/gunicorn/http/body.py
index e75d72de2..afde36854 100644
--- a/gunicorn/http/body.py
+++ b/gunicorn/http/body.py
@@ -7,7 +7,7 @@
 import sys
 
 from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator,
-        InvalidChunkSize)
+                                  InvalidChunkSize)
 
 
 class ChunkedReader(object):
@@ -187,6 +187,7 @@ def __next__(self):
         if not ret:
             raise StopIteration()
         return ret
+
     next = __next__
 
     def getsize(self, size):

From e6a88dbfcd78052a2f03e741d9e732ecb6c17e22 Mon Sep 17 00:00:00 2001
From: Benoit Chesneau <bchesneau@gmail.com>
Date: Fri, 27 Sep 2019 01:47:03 +0200
Subject: [PATCH 10/14] bump to 20.0.0

---
 gunicorn/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gunicorn/__init__.py b/gunicorn/__init__.py
index 78204797b..7b38ab044 100644
--- a/gunicorn/__init__.py
+++ b/gunicorn/__init__.py
@@ -3,6 +3,6 @@
 # This file is part of gunicorn released under the MIT license.
 # See the NOTICE for more information.
 
-version_info = (19, 9, 0)
+version_info = (20, 0, 0)
 __version__ = ".".join([str(v) for v in version_info])
 SERVER_SOFTWARE = "gunicorn/%s" % __version__

From c6bb90ca827e1566158a00ab26e6cfd86fdedb00 Mon Sep 17 00:00:00 2001
From: Tyler Lubeck <tyler@tylerlubeck.com>
Date: Fri, 27 Sep 2019 11:09:45 -0700
Subject: [PATCH 11/14] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ac93950dc..7bd82abda 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -141,7 +141,7 @@ The relevant maintainer for a pull request is assigned in 3 steps:
 
 * Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one.
 
-* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to them. They may assign it to other listed maintainers, at their discretion.
+* Step 3: The first maintainer listed is the primary maintainer who is assigned the Pull Request. The primary maintainer can reassign a Pull Request to other listed maintainers.
 
 
 ### I'm a maintainer, should I make pull requests too?

From e147feaf8b12267ff9bb3c06ad45a2738a4027df Mon Sep 17 00:00:00 2001
From: Benoit Chesneau <bchesneau@gmail.com>
Date: Fri, 27 Sep 2019 23:15:59 +0200
Subject: [PATCH 12/14] fix echo example on python 3.7

---
 examples/echo.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/examples/echo.py b/examples/echo.py
index 06f616020..e10332d87 100644
--- a/examples/echo.py
+++ b/examples/echo.py
@@ -5,12 +5,9 @@
 #
 # Example code from Eventlet sources
 
-from wsgiref.validate import validator
-
 from gunicorn import __version__
 
 
-@validator
 def app(environ, start_response):
     """Simplest possible application object"""
 

From 54c820feb3f8a7c75d35769504de19a3fdcf04cc Mon Sep 17 00:00:00 2001
From: Jeff Brooks <jeph.brooks@gmail.com>
Date: Thu, 10 Oct 2019 10:41:22 -0500
Subject: [PATCH 13/14] Ensure header value is string before conducting regex
 search on it.

---
 gunicorn/http/wsgi.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/gunicorn/http/wsgi.py b/gunicorn/http/wsgi.py
index 32e7a2acc..b786bc095 100644
--- a/gunicorn/http/wsgi.py
+++ b/gunicorn/http/wsgi.py
@@ -253,10 +253,12 @@ def process_headers(self, headers):
             if HEADER_RE.search(name):
                 raise InvalidHeaderName('%r' % name)
 
+            value = str(value)
+
             if HEADER_VALUE_RE.search(value):
                 raise InvalidHeader('%r' % value)
 
-            value = str(value).strip()
+            value = value.strip()
             lname = name.lower().strip()
             if lname == "content-length":
                 self.response_length = int(value)

From ad6ed3f4c835eb6a86ba61dadfd3896ddcbb48e3 Mon Sep 17 00:00:00 2001
From: Jeff Brooks <jeph.brooks@gmail.com>
Date: Tue, 15 Oct 2019 09:03:44 -0500
Subject: [PATCH 14/14] Implement check and exception for str type on value in
 Response process_headers method.

---
 gunicorn/http/wsgi.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gunicorn/http/wsgi.py b/gunicorn/http/wsgi.py
index b786bc095..3524471fc 100644
--- a/gunicorn/http/wsgi.py
+++ b/gunicorn/http/wsgi.py
@@ -253,7 +253,8 @@ def process_headers(self, headers):
             if HEADER_RE.search(name):
                 raise InvalidHeaderName('%r' % name)
 
-            value = str(value)
+            if not isinstance(value, str):
+                raise TypeError('%r is not a string' % value)
 
             if HEADER_VALUE_RE.search(value):
                 raise InvalidHeader('%r' % value)