Repository: hikari-py/hikari
Pull request #989 (2022-01-29 16:13:03)

Strip the token in GatewayBot, RESTBot and RESTApp
### Summary
Strip the token in GatewayBot, RESTBot and RESTApp to help avoid confusing errors which most bot devs won't understand (which are similar to the following) whenever they accidentally leave a trailing newline at the end of their header (I don't like having to explain this to ppl lol)

```py
Traceback (most recent call last):
  File "C:\Users\Lucina\PycharmProjects\hikari\test.py", line 9, in <module>
    asyncio.run(run())
  File "C:\Users\Lucina\.pyenv\pyenv-win\versions\3.10.0\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Users\Lucina\.pyenv\pyenv-win\versions\3.10.0\lib\asyncio\base_events.py", line 641, in run_until_complete
    return future.result()
  File "C:\Users\Lucina\PycharmProjects\hikari\test.py", line 7, in run
    await session.get("https://google.com", headers={"ok":"no\n\r"})
  File "C:\Users\Lucina\PycharmProjects\hikari\venv\lib\site-packages\aiohttp\client.py", line 557, in _request
    resp = await req.send(conn)
  File "C:\Users\Lucina\PycharmProjects\hikari\venv\lib\site-packages\aiohttp\client_reqrep.py", line 669, in send
    await writer.write_headers(status_line, self.headers)
  File "C:\Users\Lucina\PycharmProjects\hikari\venv\lib\site-packages\aiohttp\http_writer.py", line 130, in write_headers
    buf = _serialize_headers(status_line, headers)
  File "aiohttp\_http_writer.pyx", line 132, in aiohttp._http_writer._serialize_headers
  File "aiohttp\_http_writer.pyx", line 116, in aiohttp._http_writer._safe_header
ValueError: Newline or carriage return character detected in HTTP status message or header. This is a potential security issue.
```
But yeah for ppl who are used to being abstracted away from http semantics this won't be understandable

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [ ] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->



Patch:
diff --git a/changes/989.feature.md b/changes/989.feature.md
new file mode 100644
index 00000000..220f5bfd
--- /dev/null
+++ b/changes/989.feature.md
@@ -0,0 +1,3 @@
+Strip tokens in the standard bot impls and RESTApp.
+
+This helps avoids a common mistake with trailing new-lines which leads to confusing errors on request.
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index d31213ad..f7f625b8 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -337,7 +337,7 @@ def __init__(
         self._http_settings = http_settings if http_settings is not None else config.HTTPSettings()
         self._intents = intents
         self._proxy_settings = proxy_settings if proxy_settings is not None else config.ProxySettings()
-        self._token = token
+        self._token = token.strip()
 
         # Caching
         cache_settings = cache_settings if cache_settings is not None else config.CacheSettings()
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index b41f5d01..1e8a98c9 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -404,8 +404,11 @@ def acquire(
         provider = _RESTProvider(lambda: entity_factory, self._executor, lambda: rest_client)
         entity_factory = entity_factory_impl.EntityFactoryImpl(provider)
 
-        if token_type is None and isinstance(token, str):
-            token_type = applications.TokenType.BEARER
+        if isinstance(token, str):
+            token = token.strip()
+
+            if token_type is None:
+                token_type = applications.TokenType.BEARER
 
         rest_client = RESTClientImpl(
             cache=None,
diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py
index 428e6241..7bbc0ad1 100644
--- a/hikari/impl/rest_bot.py
+++ b/hikari/impl/rest_bot.py
@@ -252,6 +252,9 @@ def __init__(
         if isinstance(public_key, str):
             public_key = bytes.fromhex(public_key)
 
+        if isinstance(token, str):
+            token = token.strip()
+
         # Beautification and logging
         ux.init_logging(logs, allow_color, force_color)
         self.print_banner(banner, allow_color, force_color)
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index b71db85b..bdefd07b 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -22,8 +22,7 @@
 set -e
 
 if [ $(ls -1 changes/*.*.md 2>/dev/null | wc -l) != 0 ]; then
-    echo "Cannot create release if CHANGELOG fragment files exist under 'changes/'!"
-    exit 1
+    echo "Cannot create release if CHANGELOG fragment files exist under 'changes/'!" && exit 1
 fi
 
 echo "Defined environment variables"
@@ -42,6 +41,15 @@ if [ -z "${TWINE_USERNAME}" ]; then echo '$TWINE_USERNAME environment variable i
 if [ -z ${TWINE_PASSWORD+x} ]; then echo '$TWINE_PASSWORD environment variable is missing' && exit 1; fi
 if [ -z "${TWINE_PASSWORD}" ]; then echo '$TWINE_PASSWORD environment variable is empty' && exit 1; fi
 
+regex='__version__: typing\.Final\[str\] = "([^"]*)"'
+if [[ $(cat hikari/_about.py) =~ $regex ]]; then
+  if [ "${BASH_REMATCH[1]}" != "${VERSION}" ]; then
+    echo "Variable '__version__' does not match the version this deploy is for!" && exit 1
+  fi
+else
+  echo "Variable '__version__' not found in about!" && exit 1
+fi
+
 echo "===== INSTALLING DEPENDENCIES ====="
 # Note: We install each of these separately due to issues with the new PIP resolver
 # https://github.com/pypa/pip/issues/9187
@@ -53,7 +61,7 @@ pip install -r requirements.txt
 
 echo "===== DEPLOYING TO PYPI ====="
 echo "-- Setting __git_sha1__ (ref: ${REF}) --"
-sed "s|^__git_sha1__.*|__git_sha1__: typing.Final[str] = \"${REF}\"|g" -i hikari/_about.py
+sed "/^__git__sha1__.*/, \${s||__git__sha1__: typing.Final[str] = \"${REF}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__git__sha1__' not found in about!" && exit 1)
 echo "=========================================================================="
 cat hikari/_about.py
 echo "=========================================================================="
@@ -85,7 +93,7 @@ git fetch origin
 git checkout -f master
 
 echo "-- Bumping to development version (${NEW_VERSION}) --"
-sed "s|^__version__.*|__version__: typing.Final[str] = \"${NEW_VERSION}\"|g" -i hikari/_about.py
+sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${NEW_VERSION}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
 
 echo "-- Pushing to repository --"
 git commit -am "Bump to development version (${NEW_VERSION})"
diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh
index ce861762..858ee922 100644
--- a/scripts/prepare-release.sh
+++ b/scripts/prepare-release.sh
@@ -38,7 +38,7 @@ echo "-- Checkout branch --"
 git checkout -b "task/prepare-release-${VERSION}"
 
 echo "-- Bumping repository version to ${VERSION} --"
-sed "s|^__version__.*|__version__: typing.Final[str] = \"${VERSION}\"|g" -i hikari/_about.py
+sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${VERSION}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
 
 echo "-- Running towncrier --"
 towncrier --yes


Test patch:
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index 9a0d8920..8d1cc85e 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -252,6 +252,18 @@ def test_init_when_no_settings(self):
         cache.assert_called_once_with(bot, cache_settings.return_value)
         cache_settings.assert_called_once_with()
 
+    def test_init_strips_token(self):
+        stack = contextlib.ExitStack()
+        stack.enter_context(mock.patch.object(ux, "init_logging"))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "print_banner"))
+
+        with stack:
+            bot = bot_impl.GatewayBot(
+                "\n\r token yeet \r\n", cache_settings=None, http_settings=None, proxy_settings=None
+            )
+
+        assert bot._token == "token yeet"
+
     def test_cache(self, bot, cache):
         assert bot.cache is cache
 
diff --git a/tests/hikari/impl/test_rest_bot.py b/tests/hikari/impl/test_rest_bot.py
index 25f7ae3b..cee02a7c 100644
--- a/tests/hikari/impl/test_rest_bot.py
+++ b/tests/hikari/impl/test_rest_bot.py
@@ -168,6 +168,31 @@ def test___init___parses_string_public_key(self):
                 entity_factory=result.entity_factory, public_key=b"ofdododoo", rest_client=result.rest
             )
 
+    def test___init___strips_token(self):
+        cls = hikari_test_helpers.mock_class_namespace(rest_bot_impl.RESTBot, print_banner=mock.Mock())
+
+        stack = contextlib.ExitStack()
+        stack.enter_context(mock.patch.object(ux, "init_logging"))
+        rest_client = stack.enter_context(mock.patch.object(rest_impl, "RESTClientImpl"))
+        http_settings = stack.enter_context(mock.patch.object(config, "HTTPSettings"))
+        proxy_settings = stack.enter_context(mock.patch.object(config, "ProxySettings"))
+
+        with stack:
+            result = cls("\n\r sddsa tokenoken \n", "token_type")
+
+        rest_client.assert_called_once_with(
+            cache=None,
+            entity_factory=result.entity_factory,
+            executor=None,
+            http_settings=http_settings.return_value,
+            max_rate_limit=300.0,
+            max_retries=3,
+            proxy_settings=proxy_settings.return_value,
+            rest_url=None,
+            token="sddsa tokenoken",
+            token_type="token_type",
+        )
+
     def test___init___generates_default_settings(self):
         cls = hikari_test_helpers.mock_class_namespace(rest_bot_impl.RESTBot, print_banner=mock.Mock())
         stack = contextlib.ExitStack()