mirror of
https://github.com/soxoj/maigret.git
synced 2026-09-29 05:04:54 +02:00
Stop reporting blocked LinkedIn responses as free usernames (#3173)
LinkedIn's 999 and its reCAPTCHA interstitial both came out as confident results, one as a free name, the other as a claimed one. Both are errors now.
This commit is contained in:
@@ -40,11 +40,12 @@ def detect_error_page(
|
||||
return CheckError("Rate limited", "429 status code")
|
||||
if status_code == 999:
|
||||
# LinkedIn's "Request denied", served to blocked clients for existing
|
||||
# and non-existing profiles alike, so it is not really a not-found.
|
||||
# Classifying it as an error would flip LinkedIn to UNKNOWN for every
|
||||
# blocked IP, which reads as "LinkedIn is broken" to users. Kept as a
|
||||
# pass-through on purpose: the caller's checkType branch decides.
|
||||
return None
|
||||
# and non-existing profiles alike. Falling through to the checkType
|
||||
# branch turned every profile into AVAILABLE, so a blocked client was
|
||||
# told that williamhgates has no LinkedIn. "We could not check" is the
|
||||
# honest answer here; the cost is that a blocked IP now sees an error
|
||||
# instead of a confident wrong "Not found!".
|
||||
return CheckError("Access denied", "999 status code")
|
||||
if status_code >= 500:
|
||||
return CheckError("Server", f"{status_code} status code")
|
||||
return None
|
||||
|
||||
@@ -84,6 +84,12 @@ COMMON_ERRORS = {
|
||||
'Captcha', 'Google rate-limit / captcha'
|
||||
),
|
||||
'id="gs_captcha_f"': CheckError('Captcha', 'Google rate-limit / captcha'),
|
||||
# Google reCAPTCHA interstitial: answers HTTP 200 on every path, so on a
|
||||
# status_code site every username reads as claimed (linkedin.com). Matches
|
||||
# the challenge page itself, not a login form that merely embeds a widget.
|
||||
'google.com/recaptcha/challengepage/': CheckError(
|
||||
'Captcha', 'Google reCAPTCHA interstitial'
|
||||
),
|
||||
}
|
||||
|
||||
PROXY_RECOMMENDATION = (
|
||||
|
||||
@@ -149,9 +149,13 @@ def test_detect_error_page_403_ignored():
|
||||
|
||||
|
||||
def test_detect_error_page_999_linkedin():
|
||||
# LinkedIn returns 999 on bot suspicion. Deliberately not an error: making
|
||||
# it one turns LinkedIn UNKNOWN for every blocked IP.
|
||||
assert detect_error_page("", 999, {}, ignore_403=False) is None
|
||||
# LinkedIn returns 999 on bot suspicion, for real and made-up profiles
|
||||
# alike. It has to be an error: passing it through made every profile
|
||||
# look free to a blocked client.
|
||||
err = detect_error_page("", 999, {}, ignore_403=False)
|
||||
assert err is not None
|
||||
assert err.type == "Access denied"
|
||||
assert "999" in err.desc
|
||||
|
||||
|
||||
def test_detect_error_page_500():
|
||||
|
||||
@@ -40,10 +40,6 @@ def test_http_429_is_rate_limit():
|
||||
assert err.type == "Rate limited"
|
||||
|
||||
|
||||
def test_ignore_linkedin_999_status():
|
||||
# 999 stays a pass-through on purpose, see detect_error_page.
|
||||
assert detect_error_page("", 999, {}, ignore_403=False) is None
|
||||
|
||||
def test_pow_challenge_pages_are_bot_protection():
|
||||
# Both serve a 2xx on every path, so without a marker every username
|
||||
# would read as claimed.
|
||||
@@ -59,3 +55,34 @@ def test_pow_challenge_pages_are_bot_protection():
|
||||
|
||||
assert anubis.type == "Bot protection"
|
||||
assert pow_js.type == "Bot protection"
|
||||
|
||||
|
||||
def test_linkedin_999_is_an_error_not_an_absence():
|
||||
# LinkedIn serves 999 to blocked clients for existing and non-existing
|
||||
# profiles alike. Passing it through to the status_code branch reported
|
||||
# every profile as free, so a blocked user was told that a real account
|
||||
# does not exist.
|
||||
err = detect_error_page("", 999, {}, ignore_403=False)
|
||||
assert err is not None
|
||||
assert err.type == "Access denied"
|
||||
|
||||
|
||||
def test_google_recaptcha_interstitial_is_a_captcha():
|
||||
# Answers 200 on every path, so without a marker every username reads as
|
||||
# claimed on a status_code site.
|
||||
err = detect_error_page(
|
||||
'<title>Checking your browser - reCAPTCHA</title>'
|
||||
'<script src="https://www.google.com/recaptcha/challengepage/x"></script>',
|
||||
200, {}, ignore_403=False,
|
||||
)
|
||||
assert err is not None
|
||||
assert err.type == "Captcha"
|
||||
|
||||
|
||||
def test_page_embedding_a_recaptcha_widget_is_not_an_error():
|
||||
# A login form that loads the reCAPTCHA script is an ordinary page; only
|
||||
# the challenge interstitial counts as a block.
|
||||
assert detect_error_page(
|
||||
'<form><script src="https://www.google.com/recaptcha/enterprise.js"></script></form>',
|
||||
200, {}, ignore_403=False,
|
||||
) is None
|
||||
|
||||
Reference in New Issue
Block a user