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

Code before:
        login_response = self._download_json(
            f'{self._API_BASE}/login.1.json', None, 'Logging in',
            data=urlencode_postdata({
                'account': username,
                'password': password,
                'session_id': session_id
            }))
        if login_response['code'] != 'ok':
            raise ExtractorError('Login failed. Server message: %s' % login_response['message'], expected=True)
        if not self.is_logged_in:
            raise ExtractorError('Login succeeded but did not set etp_rt cookie')

    def _update_auth(self):
        if CrunchyrollBaseIE._AUTH_HEADERS and CrunchyrollBaseIE._AUTH_REFRESH > time_seconds():
            return

        if not CrunchyrollBaseIE._BASIC_AUTH:
            cx_api_param = self._CLIENT_ID[self.is_logged_in]
            self.write_debug(f'Using cxApiParam={cx_api_param}')
            CrunchyrollBaseIE._BASIC_AUTH = 'Basic ' + base64.b64encode(f'{cx_api_param}:'.encode()).decode()

        grant_type = 'etp_rt_cookie' if self.is_logged_in else 'client_id'
        try:
            auth_headers = {'Authorization': CrunchyrollBaseIE._BASIC_AUTH}
            if not self.is_logged_in:
                auth_headers['ETP-Anonymous-ID'] = uuid.uuid4()

            auth_response = self._download_json(
                f'{self._BASE_URL}/auth/v1/token', None, note=f'Authenticating with grant_type={grant_type}',
                headers=auth_headers, data=f'grant_type={grant_type}'.encode())
        except ExtractorError as error:
            if isinstance(error.cause, HTTPError) and error.cause.status == 403:
                raise ExtractorError(
                    'Request blocked by Cloudflare; navigate to Crunchyroll in your browser, '
                    'then pass the fresh cookies (with --cookies-from-browser or --cookies) '
                    'and your browser\'s User-Agent (with --user-agent)', expected=True)
            raise

        CrunchyrollBaseIE._ACCESS_TOKEN = auth_response['access_token']
        CrunchyrollBaseIE._AUTH_HEADERS = {'Authorization': auth_response['token_type'] + ' ' + auth_response['access_token']}
        CrunchyrollBaseIE._AUTH_REFRESH = time_seconds(seconds=traverse_obj(auth_response, ('expires_in', {float_or_none}), default=300) - 10)

    def _locale_from_language(self, language):
        config_locale = self._configuration_arg('metadata', ie_key=CrunchyrollBetaIE, casesense=True)
        return config_locale[0] if config_locale else self._LOCALE_LOOKUP.get(language)

    def _call_base_api(self, endpoint, internal_id, lang, note=None, query={}):
        self._update_auth()

        if not endpoint.startswith('/'):
            endpoint = f'/{endpoint}'

Review comment (security):
This should probably be outside the `try`. Perhaps
```suggestion
        auth_headers = {'Authorization': CrunchyrollBaseIE._BASIC_AUTH}
        if self.is_logged_in:
            grant_type = 'etp_rt_cookie'
        else:
            grant_type = 'client_id'
            auth_headers['ETP-Anonymous-ID'] = uuid.uuid4()
        try:
```

`uuid` is required now?

Code after:
                'password': password,
                'session_id': session_id
            }))
        if login_response['code'] != 'ok':
            raise ExtractorError('Login failed. Server message: %s' % login_response['message'], expected=True)
        if not self.is_logged_in:
            raise ExtractorError('Login succeeded but did not set etp_rt cookie')

    def _update_auth(self):
        if CrunchyrollBaseIE._AUTH_HEADERS and CrunchyrollBaseIE._AUTH_REFRESH > time_seconds():
            return

        if not CrunchyrollBaseIE._BASIC_AUTH:
            cx_api_param = self._CLIENT_ID[self.is_logged_in]
            self.write_debug(f'Using cxApiParam={cx_api_param}')
            CrunchyrollBaseIE._BASIC_AUTH = 'Basic ' + base64.b64encode(f'{cx_api_param}:'.encode()).decode()

        auth_headers = {'Authorization': CrunchyrollBaseIE._BASIC_AUTH}
        if self.is_logged_in:
            grant_type = 'etp_rt_cookie'
        else:
            grant_type = 'client_id'
            auth_headers['ETP-Anonymous-ID'] = uuid.uuid4()
        try:
            auth_response = self._download_json(
                f'{self._BASE_URL}/auth/v1/token', None, note=f'Authenticating with grant_type={grant_type}',
                headers=auth_headers, data=f'grant_type={grant_type}'.encode())
        except ExtractorError as error:
            if isinstance(error.cause, HTTPError) and error.cause.status == 403:
                raise ExtractorError(
                    'Request blocked by Cloudflare; navigate to Crunchyroll in your browser, '
                    'then pass the fresh cookies (with --cookies-from-browser or --cookies) '
                    'and your browser\'s User-Agent (with --user-agent)', expected=True)
            raise

        CrunchyrollBaseIE._IS_PREMIUM = 'cr_premium' in traverse_obj(auth_response, ('access_token', {jwt_decode_hs256}, 'benefits', ...))
        CrunchyrollBaseIE._AUTH_HEADERS = {'Authorization': auth_response['token_type'] + ' ' + auth_response['access_token']}
        CrunchyrollBaseIE._AUTH_REFRESH = time_seconds(seconds=traverse_obj(auth_response, ('expires_in', {float_or_none}), default=300) - 10)

    def _locale_from_language(self, language):
        config_locale = self._configuration_arg('metadata', ie_key=CrunchyrollBetaIE, casesense=True)
        return config_locale[0] if config_locale else self._LOCALE_LOOKUP.get(language)

    def _call_base_api(self, endpoint, internal_id, lang, note=None, query={}):
        self._update_auth()

        if not endpoint.startswith('/'):
            endpoint = f'/{endpoint}'

        query = query.copy()
        locale = self._locale_from_language(lang)