diff --git a/contributing.md b/contributing.md
deleted file mode 100644
index ad0ae99..0000000
--- a/contributing.md
+++ /dev/null
@@ -1,111 +0,0 @@
-Contributing
-============
-
-All projects under the Pylons Projects, including this one, follow the
-guidelines established at [How to
-Contribute](http://www.pylonsproject.org/community/how-to-contribute) and
-[Coding Style and
-Standards](http://docs.pylonsproject.org/en/latest/community/codestyle.html).
-
-You can contribute to this project in several ways.
-
-* [File an Issue on GitHub](https://github.com/Pylons/webob/issues)
-* Fork this project and create a branch with your suggested change. When ready,
-  submit a pull request for consideration. [GitHub
-  Flow](https://guides.github.com/introduction/flow/index.html) describes the
-  workflow process and why it's a good practice.
-* Join the IRC channel #pyramid on irc.freenode.net.
-
-
-Git Branches
-------------
-Git branches and their purpose and status at the time of this writing are
-listed below.
-
-* [master](https://github.com/Pylons/webob/) - The branch on which further
-development takes place. The default branch on GitHub.
-* [1.5-branch](https://github.com/Pylons/webob/tree/1.5-branch) - The branch
-classified as "stable" or "latest". Actively maintained. 
-* [1.4-branch](https://github.com/Pylons/webob/tree/1.4-branch) - The oldest
-actively maintained and stable branch.
-
-Older branches are not actively maintained. In general, two stable branches and
-one or two development branches are actively maintained.
-
-
-Running Tests
--------------
-
-*Note:* This section needs better instructions.
-
-Run `tox` from within your checkout. This will run the tests across all
-supported systems and attempt to build the docs.
-
-To run the tests for Python 2.x only:
-
-    $ tox py2-cover
-
-To build the docs for Python 3.x only:
-
-    $ tox py3-docs
-
-See the `tox.ini` file for details.
-
-
-Building documentation for a Pylons Project project
----------------------------------------------------
-
-*Note:* These instructions might not work for Windows users. Suggestions to
-improve the process for Windows users are welcome by submitting an issue or a
-pull request.
-
-1.  Fork the repo on GitHub by clicking the [Fork] button.
-2.  Clone your fork into a workspace on your local machine.
-
-         git clone git@github.com:<username>/webob.git
-
-3.  Add a git remote "upstream" for the cloned fork.
-
-         git remote add upstream git@github.com:Pylons/webob.git
-
-4.  Set an environment variable to your virtual environment.
-
-         # Mac and Linux
-         $ export VENV=~/hack-on-webob/env
-
-         # Windows
-         set VENV=c:\hack-on-webob\env
-
-5.  Try to build the docs in your workspace.
-
-         # Mac and Linux
-         $ make clean html SPHINXBUILD=$VENV/bin/sphinx-build
-
-         # Windows
-         c:\> make clean html SPHINXBUILD=%VENV%\bin\sphinx-build
-
-     If successful, then you can make changes to the documentation. You can
-     load the built documentation in the `/_build/html/` directory in a web
-     browser.
-
-6.  From this point forward, follow the typical [git
-    workflow](https://help.github.com/articles/what-is-a-good-git-workflow/).
-    Start by pulling from the upstream to get the most current changes.
-
-         git pull upstream master
-
-7.  Make a branch, make changes to the docs, and rebuild them as indicated in
-    step 5.  To speed up the build process, you can omit `clean` from the above
-    command to rebuild only those pages that depend on the files you have
-    changed.
-
-8.  Once you are satisfied with your changes and the documentation builds
-    successfully without errors or warnings, then git commit and push them to
-    your "origin" repository on GitHub.
-
-         git commit -m "commit message"
-         git push -u origin --all # first time only, subsequent can be just 'git push'.
-
-9.  Create a [pull request](https://help.github.com/articles/using-pull-requests/).
-
-10.  Repeat the process starting from Step 6.
\ No newline at end of file
diff --git a/docs/do-it-yourself.txt b/docs/do-it-yourself.txt
index 3b65c7d..381051c 100644
--- a/docs/do-it-yourself.txt
+++ b/docs/do-it-yourself.txt
@@ -324,7 +324,7 @@ Now we'll show a basic application.  Just a hello world application for now.  No
     ...         return 'Hello %s!' % req.params['name']
     ...     elif req.method == 'GET':
     ...         return '''<form method="POST">
-    ...             Your name: <input type="text" name="name">
+    ...             You're name: <input type="text" name="name">
     ...             <input type="submit">
     ...             </form>'''
     >>> hello_world = Router()
@@ -342,7 +342,7 @@ Now let's test that application:
     Content-Length: 131
     <BLANKLINE>
     <form method="POST">
-                Your name: <input type="text" name="name">
+                You're name: <input type="text" name="name">
                 <input type="submit">
                 </form>
     >>> req.method = 'POST'
@@ -421,7 +421,7 @@ Here's the hello world:
     ...         self.request = req
     ...     def get(self):
     ...         return '''<form method="POST">
-    ...             Your name: <input type="text" name="name">
+    ...             You're name: <input type="text" name="name">
     ...             <input type="submit">
     ...             </form>'''
     ...     def post(self):
@@ -442,7 +442,7 @@ We'll run the same test as before:
     Content-Length: 131
     <BLANKLINE>
     <form method="POST">
-                Your name: <input type="text" name="name">
+                You're name: <input type="text" name="name">
                 <input type="submit">
                 </form>
     >>> req.method = 'POST'
@@ -462,7 +462,7 @@ You can use hard-coded links in your HTML, but this can have problems.  Relative
 
 The base URL using SCRIPT_NAME is ``req.application_url``.  So, if we have access to the request we can make a URL.  But what if we don't have access?
 
-We can use thread-local variables to make it easy for any function to get access to the current request.  A "thread-local" variable is a variable whose value is tracked separately for each thread, so if there are multiple requests in different threads, their requests won't clobber each other.
+We can use thread-local variables to make it easy for any function to get access to the currect request.  A "thread-local" variable is a variable whose value is tracked separately for each thread, so if there are multiple requests in different threads, their requests won't clobber each other.
 
 The basic means of using a thread-local variable is ``threading.local()``.  This creates a blank object that can have thread-local attributes assigned to it.  I find the best way to get *at* a thread-local value is with a function, as this makes it clear that you are fetching the object, as opposed to getting at some global object.
 
diff --git a/docs/index.txt b/docs/index.txt
index 49575a8..d96240b 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -1,5 +1,3 @@
-.. _index:
-
 WebOb
 +++++
 
diff --git a/webob/descriptors.py b/webob/descriptors.py
index 505a2b6..5fd26eb 100644
--- a/webob/descriptors.py
+++ b/webob/descriptors.py
@@ -138,6 +138,9 @@ def header_getter(header, rfc_section):
     def fset(r, value):
         fdel(r)
         if value is not None:
+            if '\n' in value or '\r' in value:
+                raise ValueError('Header value may not contain control characters')
+
             if isinstance(value, text_type) and not PY3:
                 value = value.encode('latin-1')
             r._headerlist.append((header, value))
diff --git a/webob/exc.py b/webob/exc.py
index a67a867..57a81b5 100644
--- a/webob/exc.py
+++ b/webob/exc.py
@@ -481,6 +481,9 @@ ${html_comment}''')
             detail=detail, headers=headers, comment=comment,
             body_template=body_template)
         if location is not None:
+            if '\n' in location or '\r' in location:
+                raise ValueError('Control characters are not allowed in location')
+
             self.location = location
             if add_slash:
                 raise TypeError(

Possible HTTP Response Splitting Vulnerability
Hi,

Please review the published advisory, probably it's in the API WebOb which is not documented here: http://docs.webob.org/en/latest/api/exceptions.html

Probably there are other WebOb applications with similar issues.

Here is the advisory: http://www.zeroscience.mk/en/vulnerabilities/ZSL-2015-5267.php

Thanks
diff --git a/tests/test_descriptors.py b/tests/test_descriptors.py
index 7bf229f..eb3d316 100644
--- a/tests/test_descriptors.py
+++ b/tests/test_descriptors.py
@@ -155,6 +155,14 @@ def test_header_getter_fset_text():
     desc.fset(resp, text_('avalue'))
     eq_(desc.fget(resp), 'avalue')
 
+def test_header_getter_fset_text_control_chars():
+    from webob.compat import text_
+    from webob.descriptors import header_getter
+    from webob import Response
+    resp = Response('aresp')
+    desc = header_getter('AHEADER', '14.3')
+    assert_raises(ValueError, desc.fset, resp, text_('\n'))
+
 def test_header_getter_fdel():
     from webob.descriptors import header_getter
     from webob import Response
diff --git a/tests/test_exc.py b/tests/test_exc.py
index 4f7c238..dcb1fed 100644
--- a/tests/test_exc.py
+++ b/tests/test_exc.py
@@ -259,6 +259,17 @@ def test_HTTPMove_location_not_none():
     m = webob_exc._HTTPMove(location='http://example.com')
     assert_equal( m( environ, start_response ), [] )
 
+def test_HTTPMove_location_newlines():
+    environ = {
+       'wsgi.url_scheme': 'HTTP',
+       'SERVER_NAME': 'localhost',
+       'SERVER_PORT': '80',
+       'REQUEST_METHOD': 'HEAD',
+       'PATH_INFO': '/',
+    }
+    assert_raises(ValueError, webob_exc._HTTPMove,
+            location='http://example.com\r\nX-Test: false')
+
 def test_HTTPMove_add_slash_and_location():
     def start_response(status, headers, exc_info=None):
         pass

pip install -e .[dev]
parse_log_pytest
pytest
nose coverage pytest
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
coverage==7.8.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
nose==1.3.7
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
-e git+https://github.com/Pylons/webob.git@87a1254c1818859c066268755621254d2ab086a0#egg=WebOb

name: webob
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - nose==1.3.7
prefix: /opt/conda/envs/webob

tests/test_descriptors.py::test_header_getter_fset_text_control_chars tests/test_exc.py::test_HTTPMove_location_newlines
tests/test_descriptors.py::test_environ_getter_docstring tests/test_descriptors.py::test_environ_getter_nodefault_keyerror tests/test_descriptors.py::test_environ_getter_nodefault_fget tests/test_descriptors.py::test_environ_getter_nodefault_fdel tests/test_descriptors.py::test_environ_getter_default_fget tests/test_descriptors.py::test_environ_getter_default_fset tests/test_descriptors.py::test_environ_getter_default_fset_none tests/test_descriptors.py::test_environ_getter_default_fdel tests/test_descriptors.py::test_environ_getter_rfc_section tests/test_descriptors.py::test_upath_property_fget tests/test_descriptors.py::test_upath_property_fset tests/test_descriptors.py::test_header_getter_doc tests/test_descriptors.py::test_header_getter_fget tests/test_descriptors.py::test_header_getter_fset tests/test_descriptors.py::test_header_getter_fset_none tests/test_descriptors.py::test_header_getter_fset_text tests/test_descriptors.py::test_header_getter_fdel tests/test_descriptors.py::test_header_getter_unicode_fget_none tests/test_descriptors.py::test_header_getter_unicode_fget tests/test_descriptors.py::test_header_getter_unicode_fset_none tests/test_descriptors.py::test_header_getter_unicode_fset tests/test_descriptors.py::test_header_getter_unicode_fdel tests/test_descriptors.py::test_converter_not_prop tests/test_descriptors.py::test_converter_with_name_docstring tests/test_descriptors.py::test_converter_with_name_fget tests/test_descriptors.py::test_converter_with_name_fset tests/test_descriptors.py::test_converter_without_name_fget tests/test_descriptors.py::test_converter_without_name_fset tests/test_descriptors.py::test_converter_none_for_wrong_type tests/test_descriptors.py::test_converter_delete tests/test_descriptors.py::test_list_header tests/test_descriptors.py::test_parse_list_single tests/test_descriptors.py::test_parse_list_multiple tests/test_descriptors.py::test_parse_list_none tests/test_descriptors.py::test_parse_list_unicode_single tests/test_descriptors.py::test_parse_list_unicode_multiple tests/test_descriptors.py::test_serialize_list tests/test_descriptors.py::test_serialize_list_string tests/test_descriptors.py::test_serialize_list_unicode tests/test_descriptors.py::test_converter_date tests/test_descriptors.py::test_converter_date_docstring tests/test_descriptors.py::test_date_header_fget_none tests/test_descriptors.py::test_date_header_fset_fget tests/test_descriptors.py::test_date_header_fdel tests/test_descriptors.py::test_deprecated_property tests/test_descriptors.py::test_parse_etag_response tests/test_descriptors.py::test_parse_etag_response_quoted tests/test_descriptors.py::test_parse_etag_response_is_none tests/test_descriptors.py::test_serialize_etag_response tests/test_descriptors.py::test_serialize_if_range_string tests/test_descriptors.py::test_serialize_if_range_unicode tests/test_descriptors.py::test_serialize_if_range_datetime tests/test_descriptors.py::test_serialize_if_range_other tests/test_descriptors.py::test_parse_range_none tests/test_descriptors.py::test_parse_range_type tests/test_descriptors.py::test_parse_range_values tests/test_descriptors.py::test_serialize_range_none tests/test_descriptors.py::test_serialize_range tests/test_descriptors.py::test_parse_int_none tests/test_descriptors.py::test_parse_int_emptystr tests/test_descriptors.py::test_parse_int tests/test_descriptors.py::test_parse_int_invalid tests/test_descriptors.py::test_parse_int_safe_none tests/test_descriptors.py::test_parse_int_safe_emptystr tests/test_descriptors.py::test_parse_int_safe tests/test_descriptors.py::test_parse_int_safe_invalid tests/test_descriptors.py::test_serialize_int tests/test_descriptors.py::test_parse_content_range_none tests/test_descriptors.py::test_parse_content_range_emptystr tests/test_descriptors.py::test_parse_content_range_length tests/test_descriptors.py::test_parse_content_range_start tests/test_descriptors.py::test_parse_content_range_stop tests/test_descriptors.py::test_serialize_content_range_none tests/test_descriptors.py::test_serialize_content_range_emptystr tests/test_descriptors.py::test_serialize_content_range_invalid tests/test_descriptors.py::test_serialize_content_range_asterisk tests/test_descriptors.py::test_serialize_content_range_defined tests/test_descriptors.py::test_parse_auth_params_leading_capital_letter tests/test_descriptors.py::test_parse_auth_params_trailing_capital_letter tests/test_descriptors.py::test_parse_auth_params_doublequotes tests/test_descriptors.py::test_parse_auth_params_multiple_values tests/test_descriptors.py::test_parse_auth_params_truncate_on_comma tests/test_descriptors.py::test_parse_auth_params_emptystr tests/test_descriptors.py::test_parse_auth_params_bad_whitespace tests/test_descriptors.py::test_authorization2 tests/test_descriptors.py::test_parse_auth_none tests/test_descriptors.py::test_parse_auth_emptystr tests/test_descriptors.py::test_parse_auth_basic tests/test_descriptors.py::test_parse_auth_basic_quoted tests/test_descriptors.py::test_parse_auth_basic_quoted_multiple_unknown tests/test_descriptors.py::test_parse_auth_basic_quoted_known_multiple tests/test_descriptors.py::test_serialize_auth_none tests/test_descriptors.py::test_serialize_auth_emptystr tests/test_descriptors.py::test_serialize_auth_basic_quoted tests/test_descriptors.py::test_serialize_auth_digest_multiple tests/test_descriptors.py::test_serialize_auth_digest_tuple tests/test_descriptors.py::TestEnvironDecoder::test_default_fdel tests/test_descriptors.py::TestEnvironDecoder::test_default_fget tests/test_descriptors.py::TestEnvironDecoder::test_default_fset tests/test_descriptors.py::TestEnvironDecoder::test_default_fset_none tests/test_descriptors.py::TestEnvironDecoder::test_docstring tests/test_descriptors.py::TestEnvironDecoder::test_fget_nonascii tests/test_descriptors.py::TestEnvironDecoder::test_fset_nonascii tests/test_descriptors.py::TestEnvironDecoder::test_nodefault_fdel tests/test_descriptors.py::TestEnvironDecoder::test_nodefault_fget tests/test_descriptors.py::TestEnvironDecoder::test_nodefault_keyerror tests/test_descriptors.py::TestEnvironDecoder::test_rfc_section tests/test_descriptors.py::TestEnvironDecoderLegacy::test_default_fdel tests/test_descriptors.py::TestEnvironDecoderLegacy::test_default_fget tests/test_descriptors.py::TestEnvironDecoderLegacy::test_default_fget_nonascii tests/test_descriptors.py::TestEnvironDecoderLegacy::test_default_fset tests/test_descriptors.py::TestEnvironDecoderLegacy::test_default_fset_none tests/test_descriptors.py::TestEnvironDecoderLegacy::test_docstring tests/test_descriptors.py::TestEnvironDecoderLegacy::test_fget_nonascii tests/test_descriptors.py::TestEnvironDecoderLegacy::test_fset_nonascii tests/test_descriptors.py::TestEnvironDecoderLegacy::test_nodefault_fdel tests/test_descriptors.py::TestEnvironDecoderLegacy::test_nodefault_fget tests/test_descriptors.py::TestEnvironDecoderLegacy::test_nodefault_keyerror tests/test_descriptors.py::TestEnvironDecoderLegacy::test_rfc_section tests/test_exc.py::test_noescape_null tests/test_exc.py::test_noescape_not_basestring tests/test_exc.py::test_noescape_unicode tests/test_exc.py::test_strip_tags_empty tests/test_exc.py::test_strip_tags_newline_to_space tests/test_exc.py::test_strip_tags_zaps_carriage_return tests/test_exc.py::test_strip_tags_br_to_newline tests/test_exc.py::test_strip_tags_zaps_comments tests/test_exc.py::test_strip_tags_zaps_tags tests/test_exc.py::test_HTTPException tests/test_exc.py::test_exception_with_unicode_data tests/test_exc.py::test_WSGIHTTPException_headers tests/test_exc.py::test_WSGIHTTPException_w_body_template tests/test_exc.py::test_WSGIHTTPException_w_empty_body tests/test_exc.py::test_WSGIHTTPException___str__ tests/test_exc.py::test_WSGIHTTPException_plain_body_no_comment tests/test_exc.py::test_WSGIHTTPException_html_body_w_comment tests/test_exc.py::test_WSGIHTTPException_generate_response tests/test_exc.py::test_WSGIHTTPException_call_w_body tests/test_exc.py::test_WSGIHTTPException_wsgi_response tests/test_exc.py::test_WSGIHTTPException_exception_newstyle tests/test_exc.py::test_WSGIHTTPException_exception_no_newstyle tests/test_exc.py::test_HTTPOk_head_of_proxied_head tests/test_exc.py::test_HTTPMove tests/test_exc.py::test_HTTPMove_location_not_none tests/test_exc.py::test_HTTPMove_add_slash_and_location tests/test_exc.py::test_HTTPMove_call_add_slash tests/test_exc.py::test_HTTPMove_call_query_string tests/test_exc.py::test_HTTPExceptionMiddleware_ok tests/test_exc.py::test_HTTPExceptionMiddleware_exception tests/test_exc.py::test_HTTPExceptionMiddleware_exception_exc_info_none tests/test_exc.py::test_status_map_is_deterministic
docs/do-it-yourself.txt webob/descriptors.py docs/index.txt contributing.md webob/exc.py
docs/do-it-yourself.txt webob/descriptors.py docs/index.txt contributing.md webob/exc.py