Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/common.py (Python)

Code before:
        if res is None:
            msg = f'Failed to extract {name}: Could not parse value {v!r}'
            if fatal:
                raise ExtractorError(msg)
            else:
                self.report_warning(msg)
        return res

    def _set_cookie(self, domain, name, value, expire_time=None, port=None,
                    path='/', secure=False, discard=False, rest={}, **kwargs):
        cookie = http.cookiejar.Cookie(
            0, name, value, port, port is not None, domain, True,
            domain.startswith('.'), path, True, secure, expire_time,
            discard, None, None, rest)
        self.cookiejar.set_cookie(cookie)

    def _get_cookies(self, url):
        """ Return a http.cookies.SimpleCookie with the cookies for the url """
        raw_cookie = self._downloader._calc_cookies(url)

        cookie = http.cookies.SimpleCookie()
        if raw_cookie is None:
            return cookie

        # Iterate over split chunks to skip invalid values instead of failing fast.
        for chunk in raw_cookie.split(";"):
            key, _, value = chunk.partition("=")
            if not value:
                key, value = "", key
            key = key.strip()
            value = value.strip()
            if key or value:
                cookie[key] = urllib.parse.unquote(value)

        return cookie

    def _apply_first_set_cookie_header(self, url_handle, cookie):
        """
        Apply first Set-Cookie header instead of the last. Experimental.

        Some sites (e.g. [1-3]) may serve two cookies under the same name
        in Set-Cookie header and expect the first (old) one to be set rather
        than second (new). However, as of RFC6265 the newer one cookie
        should be set into cookie store what actually happens.
        We will workaround this issue by resetting the cookie to
        the first one manually.
        1. https://new.vk.com/
        2. https://github.com/ytdl-org/youtube-dl/issues/9841#issuecomment-227871201
        3. https://learning.oreilly.com/
        """
        for header, cookies in url_handle.headers.items():

Review comment (suggestion):
`;` is a valid character in a `quoted-string` cookie value.
```
>>> http.cookies.SimpleCookie('''chips="a;hoy"; vienna=finger''')
<SimpleCookie: chips='a;hoy' vienna='finger'>
>>> 
```
Check https://github.com/python/cpython/blob/3.10/Lib/http/cookies.py for what is currently being done (and has been for 22 years, according to the header comments).

What could additionally be done is to add a parallel pattern to the value match in `_CookiePattern`, s/t like this
```
r"""
(?:
  (?P<val>                         # Start of group 'val'
      "(?:[^\\"]|\\.)*"                  # Any doublequoted string
      |                                  # or
      \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT  # Special case for "expires" attr
      |                                  # or
      [""" + _LegalValueChars + r"""]*      # Any word or empty string
      )|                                # End of group 'val'
  (?P<bad>(?:\\;|[^;])*)           # fallback match to skip bad value 
)
"""
```
Then in a rewrite of the `BaseCookie.__parse_string()` method, add a step to check the `bad` group: if it matches, skip past the match and try to parse the cookie string from there.

Code after:
            msg = f'Failed to extract {name}: Could not parse value {v!r}'
            if fatal:
                raise ExtractorError(msg)
            else:
                self.report_warning(msg)
        return res

    def _set_cookie(self, domain, name, value, expire_time=None, port=None,
                    path='/', secure=False, discard=False, rest={}, **kwargs):
        cookie = http.cookiejar.Cookie(
            0, name, value, port, port is not None, domain, True,
            domain.startswith('.'), path, True, secure, expire_time,
            discard, None, None, rest)
        self.cookiejar.set_cookie(cookie)

    def _get_cookies(self, url):
        """ Return a http.cookies.SimpleCookie with the cookies for the url """
        return LenientSimpleCookie(self._downloader._calc_cookies(url))

    def _apply_first_set_cookie_header(self, url_handle, cookie):
        """
        Apply first Set-Cookie header instead of the last. Experimental.

        Some sites (e.g. [1-3]) may serve two cookies under the same name
        in Set-Cookie header and expect the first (old) one to be set rather
        than second (new). However, as of RFC6265 the newer one cookie
        should be set into cookie store what actually happens.
        We will workaround this issue by resetting the cookie to
        the first one manually.
        1. https://new.vk.com/
        2. https://github.com/ytdl-org/youtube-dl/issues/9841#issuecomment-227871201
        3. https://learning.oreilly.com/
        """
        for header, cookies in url_handle.headers.items():
            if header.lower() != 'set-cookie':
                continue
            cookies = cookies.encode('iso-8859-1').decode('utf-8')
            cookie_value = re.search(
                r'%s=(.+?);.*?\b[Dd]omain=(.+?)(?:[,;]|$)' % cookie, cookies)
            if cookie_value:
                value, domain = cookie_value.groups()
                self._set_cookie(domain, cookie, value)
                break

    @classmethod
    def get_testcases(cls, include_onlymatching=False):
        t = getattr(cls, '_TEST', None)
        if t:
            assert not hasattr(cls, '_TESTS'), f'{cls.ie_key()}IE has _TEST and _TESTS'
            tests = [t]
        else: