Repository: yt-dlp/yt-dlp
Pull request #7648 (2023-07-20 00:25:19)

[networking] Fix POST requests with 0-byte payloads
Bugfix for 227bf1a33be7b89cd7d44ad046844c4ccba104f4

Extractor requests with `data=b''` were being sent as GET requests

<details open><summary>Template</summary> <!-- OPEN is intentional -->

<!--

# PLEASE FOLLOW THE GUIDE BELOW

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
- Use *Preview* tab to see how your *pull request* will actually look like

-->

### Before submitting a *pull request* make sure you have:
- [x] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [x] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
- [x] Checked the code with [flake8](https://pypi.python.org/pypi/flake8) and [ran relevant tests](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions)

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check all of the following options that apply:
- [x] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [ ] Fix or improvement to an extractor (Make sure to add/update tests)
- [ ] New extractor ([Piracy websites will not be accepted](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy))
- [x] Core bug fix/improvement
- [ ] New feature (It is strongly [recommended to open an issue first](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-new-feature-or-making-overarching-changes))


<!-- Do NOT edit/remove anything below this! -->
</details><details><summary>Copilot Summary</summary>  

<!--
copilot:all
-->
### <samp>🤖 Generated by Copilot at 99927ab</samp>

### Summary
🐛🧪🚧

<!--
1.  🐛 - This emoji represents a bug fix, which is the main purpose of the pull request. The bug fix affects the networking module and some extractors that rely on empty POST requests.
2.  🧪 - This emoji represents a test, which is added to verify the bug fix and prevent future regressions. The test covers a new edge case for the `Request` class and its `update` method.
3.  🚧 - This emoji represents a refactor, which is done to improve the readability and robustness of the code. The refactor replaces the `or` operator with an explicit `is not None` check, which avoids potential confusion and errors.
-->
Fix a bug where some extractors failed to send empty POST requests by updating the `Request` class and its related functions in `yt_dlp/networking` and `test/test_networking.py`. Modify the `EttuTvIE` extractor in `yt_dlp/extractor/ettutv.py` to send an empty POST request as required by the website.

> _`Request` data fix_
> _Empty POSTs in winter cold_
> _`EttuTvIE` works_

### Walkthrough
* Fix a bug where some extractors failed to send empty POST requests by checking for `None` instead of falsy values for the `data` parameter ([link](https://github.com/yt-dlp/yt-dlp/pull/7648/files?diff=unified&w=0#diff-c7b2bfc3ad326c23db07694e9137f86e58e526681dab17e7680a968a7190e232L318-R318), [link](https://github.com/yt-dlp/yt-dlp/pull/7648/files?diff=unified&w=0#diff-ead7fca4af365bb8c89c1a0257a027147d0533eb2e0dfb395d08a9a12e58d4abL428-R428))
* Update the `EttuTvIE` extractor to send an empty POST request to the stream access URL as required by the website ([link](https://github.com/yt-dlp/yt-dlp/pull/7648/files?diff=unified&w=0#diff-b6afebe100acb0f789ac9f22ecf428f7e2cf9f09a55e2064b31fe1dfbe4f488cL44-R44))
* Add a test case to the `TestRequest` class in `test_networking.py` to verify that the `update` method of the `Request` class can handle 0-byte payloads and set the appropriate content-type header ([link](https://github.com/yt-dlp/yt-dlp/pull/7648/files?diff=unified&w=0#diff-e92498eb46ad8369b16aed0d65fa7c8d56c81b2d595f6510981c6cfb0c0f7e78R1283-R1291))



</details>


bashonly: Suggestions for commit msg welcome
pukkandan: > Suggestions for commit msg welcome

PR title looks fine

Patch:
diff --git a/yt_dlp/extractor/ettutv.py b/yt_dlp/extractor/ettutv.py
index 46d725543..133b52555 100644
--- a/yt_dlp/extractor/ettutv.py
+++ b/yt_dlp/extractor/ettutv.py
@@ -41,7 +41,7 @@ def _real_extract(self, url):
                 'device': 'desktop',
             })
 
-        stream_response = self._download_json(player_settings['streamAccess'], video_id, data={})
+        stream_response = self._download_json(player_settings['streamAccess'], video_id, data=b'')
 
         formats, subtitles = self._extract_m3u8_formats_and_subtitles(
             stream_response['data']['stream'], video_id, 'mp4')
diff --git a/yt_dlp/networking/_urllib.py b/yt_dlp/networking/_urllib.py
index 2c5f09872..8a76676d9 100644
--- a/yt_dlp/networking/_urllib.py
+++ b/yt_dlp/networking/_urllib.py
@@ -315,7 +315,7 @@ def get_method(self):
 def update_Request(req, url=None, data=None, headers=None, query=None):
     req_headers = req.headers.copy()
     req_headers.update(headers or {})
-    req_data = data or req.data
+    req_data = data if data is not None else req.data
     req_url = update_url_query(url or req.get_full_url(), query)
     req_get_method = req.get_method()
     if req_get_method == 'HEAD':
diff --git a/yt_dlp/networking/common.py b/yt_dlp/networking/common.py
index 458eca39f..61196406d 100644
--- a/yt_dlp/networking/common.py
+++ b/yt_dlp/networking/common.py
@@ -425,7 +425,7 @@ def headers(self, new_headers: Mapping):
             raise TypeError('headers must be a mapping')
 
     def update(self, url=None, data=None, headers=None, query=None):
-        self.data = data or self.data
+        self.data = data if data is not None else self.data
         self.headers.update(headers or {})
         self.url = update_url_query(url or self.url, query or {})
 


Test patch:
diff --git a/test/test_networking.py b/test/test_networking.py
index b60ed283b..3cf587a63 100644
--- a/test/test_networking.py
+++ b/test/test_networking.py
@@ -1280,6 +1280,17 @@ def test_content_type_header(self):
         req.data = b'test3'
         assert req.headers.get('Content-Type') == 'application/x-www-form-urlencoded'
 
+    def test_update_req(self):
+        req = Request('http://example.com')
+        assert req.data is None
+        assert req.method == 'GET'
+        assert 'Content-Type' not in req.headers
+        # Test that zero-byte payloads will be sent
+        req.update(data=b'')
+        assert req.data == b''
+        assert req.method == 'POST'
+        assert req.headers.get('Content-Type') == 'application/x-www-form-urlencoded'
+
     def test_proxies(self):
         req = Request(url='http://example.com', proxies={'http': 'http://127.0.0.1:8080'})
         assert req.proxies == {'http': 'http://127.0.0.1:8080'}