Adjust for new recaptcha
parent
6478569537
commit
2dac52930b
|
@ -9,6 +9,7 @@
|
|||
],
|
||||
"rules": {
|
||||
"semi": [2, "always"],
|
||||
"no-trailing-spaces": [0],
|
||||
"no-multi-spaces": [1, {
|
||||
"exceptions": {
|
||||
"VariableDeclarator": true
|
||||
|
|
71
index.js
71
index.js
|
@ -251,9 +251,23 @@ function onCloudflareResponse (options, response, body) {
|
|||
onRequestComplete(options, response, body);
|
||||
}
|
||||
|
||||
function detectRecaptchaVersion (body) {
|
||||
// New version > Dec 2019
|
||||
if (/__cf_chl_captcha_tk__=(.*)/i.test(body)) { // Test for ver2 first, as it also has ver2 fields
|
||||
return 'ver2';
|
||||
// Old version < Dec 2019
|
||||
} else if (body.indexOf('why_captcha') !== -1 || /cdn-cgi\/l\/chk_captcha/i.test(body)) {
|
||||
return 'ver1';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function validateResponse (options, response, body) {
|
||||
// Finding captcha
|
||||
if (body.indexOf('why_captcha') !== -1 || /cdn-cgi\/l\/chk_captcha/i.test(body)) {
|
||||
// Old version < Dec 2019
|
||||
let recaptchaVer = detectRecaptchaVersion(body);
|
||||
if (recaptchaVer) {
|
||||
// Convenience boolean
|
||||
response.isCaptcha = true;
|
||||
throw new CaptchaError('captcha', options, response);
|
||||
|
@ -383,11 +397,13 @@ function onChallenge (options, response, body) {
|
|||
|
||||
// Parses the reCAPTCHA form and hands control over to the user
|
||||
function onCaptcha (options, response, body) {
|
||||
let recaptchaVer = detectRecaptchaVersion(body);
|
||||
let isRecaptchaVer2 = recaptchaVer === 'ver2';
|
||||
const callback = options.callback;
|
||||
// UDF that has the responsibility of returning control back to cloudscraper
|
||||
const handler = options.onCaptcha;
|
||||
// The form data to send back to Cloudflare
|
||||
const payload = { /* s, g-re-captcha-response */ };
|
||||
const payload = { /* r|s, g-re-captcha-response */ };
|
||||
|
||||
let cause;
|
||||
let match;
|
||||
|
@ -399,7 +415,18 @@ function onCaptcha (options, response, body) {
|
|||
}
|
||||
|
||||
const form = match[1];
|
||||
|
||||
let siteKey;
|
||||
let rayId; // only for ver 2
|
||||
|
||||
if (isRecaptchaVer2) {
|
||||
match = body.match(/\sdata-ray=["']?([^\s"'<>&]+)/);
|
||||
if (!match) {
|
||||
cause = 'Unable to find cloudflare ray id';
|
||||
return callback(new ParserError(cause, options, response));
|
||||
}
|
||||
rayId = match[1];
|
||||
}
|
||||
|
||||
match = body.match(/\sdata-sitekey=["']?([^\s"'<>&]+)/);
|
||||
if (match) {
|
||||
|
@ -434,9 +461,24 @@ function onCaptcha (options, response, body) {
|
|||
response.captcha = {
|
||||
siteKey,
|
||||
uri: response.request.uri,
|
||||
form: payload
|
||||
form: payload,
|
||||
version: recaptchaVer
|
||||
};
|
||||
|
||||
if (isRecaptchaVer2) {
|
||||
response.rayId = rayId;
|
||||
|
||||
match = body.match(/id="challenge-form" action="(.+?)" method="(.+?)"/);
|
||||
if (!match) {
|
||||
cause = 'Challenge form action and method extraction failed';
|
||||
return callback(new ParserError(cause, options, response));
|
||||
}
|
||||
response.captcha.formMethod = match[2];
|
||||
match = match[1].match(/\/(.*)/);
|
||||
response.captcha.formActionUri = match[0];
|
||||
payload.id = rayId;
|
||||
}
|
||||
|
||||
Object.defineProperty(response.captcha, 'url', {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
|
@ -465,7 +507,7 @@ function onCaptcha (options, response, body) {
|
|||
}
|
||||
|
||||
// Sanity check
|
||||
if (!payload.s) {
|
||||
if (!payload.s && !payload.r) {
|
||||
cause = 'Challenge form is missing secret input';
|
||||
return callback(new ParserError(cause, options, response));
|
||||
}
|
||||
|
@ -506,19 +548,34 @@ function onCaptcha (options, response, body) {
|
|||
function onSubmitCaptcha (options, response) {
|
||||
const callback = options.callback;
|
||||
const uri = response.request.uri;
|
||||
const isRecaptchaVer2 = response.captcha.version === 'ver2';
|
||||
|
||||
if (!response.captcha.form['g-recaptcha-response']) {
|
||||
const cause = 'Form submission without g-recaptcha-response';
|
||||
return callback(new CaptchaError(cause, options, response));
|
||||
}
|
||||
|
||||
options.method = 'GET';
|
||||
options.qs = response.captcha.form;
|
||||
if (isRecaptchaVer2) {
|
||||
options.qs = {
|
||||
__cf_chl_captcha_tk__: response.captcha.formActionUri.match(/__cf_chl_captcha_tk__=(.*)/)[1]
|
||||
};
|
||||
|
||||
options.form = response.captcha.form;
|
||||
} else {
|
||||
options.qs = response.captcha.form;
|
||||
}
|
||||
|
||||
options.method = response.captcha.formMethod || 'GET';
|
||||
|
||||
// Prevent reusing the headers object to simplify unit testing.
|
||||
options.headers = Object.assign({}, options.headers);
|
||||
// Use the original uri as the referer and to construct the form action.
|
||||
options.headers.Referer = uri.href;
|
||||
options.uri = uri.protocol + '//' + uri.host + '/cdn-cgi/l/chk_captcha';
|
||||
if (isRecaptchaVer2) {
|
||||
options.uri = uri.protocol + '//' + uri.host + response.captcha.formActionUri;
|
||||
} else {
|
||||
options.uri = uri.protocol + '//' + uri.host + '/cdn-cgi/l/chk_captcha';
|
||||
}
|
||||
|
||||
performRequest(options, false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en-US"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js ie7 oldie" lang="en-US"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js ie8 oldie" lang="en-US"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js" lang="en-US"> <!--<![endif]-->
|
||||
<head>
|
||||
<title>Attention Required! | Cloudflare</title>
|
||||
<meta name="captcha-bypass" id="captcha-bypass" />
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />
|
||||
<link rel="stylesheet" id="cf_styles-css" href="/cdn-cgi/styles/cf.errors.css" type="text/css" media="screen,projection" />
|
||||
<!--[if lt IE 9]><link rel="stylesheet" id='cf_styles-ie-css' href="/cdn-cgi/styles/cf.errors.ie.css" type="text/css" media="screen,projection" /><![endif]-->
|
||||
<style type="text/css">body{margin:0;padding:0}</style>
|
||||
|
||||
|
||||
<!--[if gte IE 10]><!--><script type="text/javascript" src="/cdn-cgi/scripts/zepto.min.js"></script><!--<![endif]-->
|
||||
<!--[if gte IE 10]><!--><script type="text/javascript" src="/cdn-cgi/scripts/cf.common.js"></script><!--<![endif]-->
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="cf-wrapper">
|
||||
<div class="cf-alert cf-alert-error cf-cookie-error" id="cookie-alert" data-translate="enable_cookies">Please enable cookies.</div>
|
||||
<div id="cf-error-details" class="cf-error-details-wrapper">
|
||||
<div class="cf-wrapper cf-header cf-error-overview">
|
||||
<h1 data-translate="challenge_headline">One more step</h1>
|
||||
<h2 class="cf-subheadline"><span data-translate="complete_sec_check">Please complete the security check to access</span> www.cloudflare.com</h2>
|
||||
</div><!-- /.header -->
|
||||
|
||||
<div class="cf-section cf-highlight cf-captcha-container">
|
||||
<div class="cf-wrapper">
|
||||
<div class="cf-columns two">
|
||||
<div class="cf-column">
|
||||
|
||||
<div class="cf-highlight-inverse cf-form-stacked">
|
||||
<form class="challenge-form" id="challenge-form" action="/?__cf_chl_captcha_tk__=e8844bdff35ae5e" method="POST" enctype="application/x-www-form-urlencoded">
|
||||
<input type="hidden" name="r" value="0bd666f149acf02bbc05bba3b1bb">
|
||||
<script type="text/javascript" src="/cdn-cgi/scripts/cf.challenge.js" data-type="normal" data-ray="53dfe8147d2a9e73" async data-sitekey="6LfBixYUAAAAABhdHynFUIMA_sa4s-XsJvnjtgB0"></script>
|
||||
<div class="g-recaptcha"></div>
|
||||
<noscript id="cf-captcha-bookmark" class="cf-captcha-info">
|
||||
<div><div style="width: 302px">
|
||||
<div>
|
||||
<iframe src="https://www.google.com/recaptcha/api/fallback?k=6LfBixYUAAAAABhdHynFUIMA_sa4s-XsJvnjtgB0" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
|
||||
</div>
|
||||
<div style="width: 300px; border-style: none; bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
|
||||
<textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 40px; border: 1px solid #c1c1c1; margin: 10px 25px; padding: 0px; resize: none;"></textarea>
|
||||
<input type="submit" value="Submit"></input>
|
||||
</div>
|
||||
</div></div>
|
||||
</noscript>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cf-column">
|
||||
<div class="cf-screenshot-container">
|
||||
|
||||
<span class="cf-no-screenshot"></span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.columns -->
|
||||
</div>
|
||||
</div><!-- /.captcha-container -->
|
||||
|
||||
<div class="cf-section cf-wrapper">
|
||||
<div class="cf-columns two">
|
||||
<div class="cf-column">
|
||||
<h2 data-translate="why_captcha_headline">Why do I have to complete a CAPTCHA?</h2>
|
||||
|
||||
<p data-translate="why_captcha_detail">Completing the CAPTCHA proves you are a human and gives you temporary access to the web property.</p>
|
||||
</div>
|
||||
|
||||
<div class="cf-column">
|
||||
<h2 data-translate="resolve_captcha_headline">What can I do to prevent this in the future?</h2>
|
||||
|
||||
|
||||
<p data-translate="resolve_captcha_antivirus">If you are on a personal connection, like at home, you can run an anti-virus scan on your device to make sure it is not infected with malware.</p>
|
||||
|
||||
<p data-translate="resolve_captcha_network">If you are at an office or shared network, you can ask the network administrator to run a scan across the network looking for misconfigured or infected devices.</p>
|
||||
|
||||
|
||||
<p data-translate="resolve_captcha_privacy_pass"> Another way to prevent getting this page in the future is to use Privacy Pass. You may need to download version 2.0 now from the <a href="https://chrome.google.com/webstore/detail/privacy-pass/ajhmfdgkijocedmfjonnpjfojldioehi">Chrome Web Store</a>.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.section -->
|
||||
|
||||
|
||||
<div class="cf-error-footer cf-wrapper">
|
||||
<p>
|
||||
<span class="cf-footer-item">Cloudflare Ray ID: <strong>53dfe8147d2a9e73</strong></span>
|
||||
<span class="cf-footer-separator">•</span>
|
||||
<span class="cf-footer-item"><span>Your IP</span>: 185.4.132.135</span>
|
||||
<span class="cf-footer-separator">•</span>
|
||||
<span class="cf-footer-item"><span>Performance & security by</span> <a href="https://www.cloudflare.com/5xx-error-landing?utm_source=error_footer" id="brand_link" target="_blank">Cloudflare</a></span>
|
||||
|
||||
</p>
|
||||
</div><!-- /.error-footer -->
|
||||
|
||||
|
||||
</div><!-- /#cf-error-details -->
|
||||
</div><!-- /#cf-wrapper -->
|
||||
|
||||
<script type="text/javascript">
|
||||
window._cf_translation = {};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script src="https://ajax.cloudflare.com/cdn-cgi/scripts/f8ce4a63/cloudflare-static/pic-chl.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
var a = function() {try{return !!window.addEventListener} catch(e) {return !1} },
|
||||
b = function(b, c) {a() ? document.addEventListener("DOMContentLoaded", b, c) : document.attachEvent("onreadystatechange", b)};
|
||||
b(function(){
|
||||
var f = document.getElementById('challenge-form');
|
||||
if (f) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'cv_chal_result';
|
||||
input.value = window.__CF$cv$chal([0xf6a7adf9fb,0xa4950f3586]);
|
||||
f.appendChild(input);
|
||||
try {
|
||||
if (window.__CF$cv$fp) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'cv_chal_fp';
|
||||
input.value = window.__CF$cv$fp();
|
||||
f.appendChild(input);
|
||||
}
|
||||
} catch (e) { }
|
||||
}
|
||||
}, false);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -64,111 +64,224 @@ describe('Cloudscraper', function () {
|
|||
expect(promise).to.be.rejectedWith(errors.CaptchaError).and.notify(done);
|
||||
});
|
||||
});
|
||||
describe('reCAPTCHA (version as on 10.04.2019)', () => {
|
||||
for (let stage = 0; stage < 4; stage++) {
|
||||
const desc = {
|
||||
0: 'should resolve when user calls captcha.submit()',
|
||||
1: 'should callback with an error if user calls captcha.submit(error)',
|
||||
2: 'should resolve when the onCaptcha promise resolves',
|
||||
3: 'should callback with an error if the onCaptcha promise is rejected'
|
||||
};
|
||||
|
||||
// Run this test 4 times
|
||||
it(desc[stage], function (done) {
|
||||
const secret = '6b132d85d185a8255f2451d48fe6a8bee7154ea2-1555377580-1800-AQ1azEkeDOnQP5ByOpwUU/RdbKrmMwHYpkaenRvjPXtB0w8Vbjn/Ceg62tfpp/lT799kjDLEMMuDkEMqQ7iO51kniWCQm00BQvDGl+D0h/WvXDWO96YXOUD3qrqUTuzO7QbUOinc8y8kedvOQkr4c0o=';
|
||||
const siteKey = '6LfBixYUAAAAABhdHynFUIMA_sa4s-XsJvnjtgB0';
|
||||
const expectedError = new Error('anti-captcha failed!');
|
||||
|
||||
helper.router
|
||||
.get('/test', function (req, res) {
|
||||
res.sendCaptcha('cf_recaptcha_15_04_2019.html');
|
||||
})
|
||||
.get('/cdn-cgi/l/chk_captcha', function (req, res) {
|
||||
res.send(requestedPage);
|
||||
});
|
||||
|
||||
const onCaptcha = sinon.spy(function (options, response, body) {
|
||||
expect(options).to.be.an('object');
|
||||
expect(response).to.be.instanceof(http.IncomingMessage);
|
||||
expect(body).to.be.a('string');
|
||||
|
||||
sinon.assert.match(response, {
|
||||
isCloudflare: true,
|
||||
isHTML: true,
|
||||
isCaptcha: true,
|
||||
captcha: sinon.match.object
|
||||
});
|
||||
|
||||
sinon.assert.match(response.captcha, {
|
||||
url: uri, // <-- Deprecated
|
||||
uri: sinon.match.same(response.request.uri),
|
||||
form: { s: secret },
|
||||
siteKey: siteKey,
|
||||
submit: sinon.match.func
|
||||
});
|
||||
|
||||
// Simulate what the user should do here
|
||||
response.captcha.form['g-recaptcha-response'] = 'foobar';
|
||||
|
||||
switch (stage) {
|
||||
case 0:
|
||||
// User green lights form submission
|
||||
response.captcha.submit();
|
||||
break;
|
||||
case 1:
|
||||
// User reports an error when solving the reCAPTCHA
|
||||
response.captcha.submit(expectedError);
|
||||
break;
|
||||
case 2:
|
||||
// User green lights form submission by resolving the returned promise
|
||||
return Promise.resolve();
|
||||
case 3:
|
||||
// User reports an error by rejecting the returned promise
|
||||
return Promise.reject(expectedError);
|
||||
}
|
||||
});
|
||||
|
||||
const firstParams = helper.extendParams({ onCaptcha, uri });
|
||||
const secondParams = helper.extendParams({
|
||||
onCaptcha,
|
||||
method: 'GET',
|
||||
uri: helper.resolve('/cdn-cgi/l/chk_captcha'),
|
||||
headers: {
|
||||
Referer: uri
|
||||
},
|
||||
qs: {
|
||||
s: secret,
|
||||
'g-recaptcha-response': 'foobar'
|
||||
}
|
||||
});
|
||||
|
||||
const options = { onCaptcha, uri };
|
||||
|
||||
const promise = cloudscraper.get(options, function (error, response, body) {
|
||||
switch (stage) {
|
||||
case 0:
|
||||
case 2:
|
||||
expect(error).to.be.null;
|
||||
|
||||
expect(onCaptcha).to.be.calledOnce;
|
||||
|
||||
expect(Request).to.be.calledTwice;
|
||||
expect(Request.firstCall).to.be.calledWithExactly(firstParams);
|
||||
expect(Request.secondCall).to.be.calledWithExactly(secondParams);
|
||||
|
||||
expect(body).to.be.equal(requestedPage);
|
||||
expect(promise).to.eventually.equal(requestedPage).and.notify(done);
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
expect(error).to.be.instanceOf(errors.CaptchaError);
|
||||
expect(error.error).to.be.an('error');
|
||||
expect(error).to.have.property('errorType', 1);
|
||||
expect(error.message).to.include(expectedError.message);
|
||||
expect(promise).to.be.rejectedWith(errors.CaptchaError).and.notify(done);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (let stage = 0; stage < 4; stage++) {
|
||||
const desc = {
|
||||
0: 'should resolve reCAPTCHA (version as on 10.04.2019) when user calls captcha.submit()',
|
||||
1: 'should callback with an error if user calls captcha.submit(error)',
|
||||
2: 'should resolve reCAPTCHA (version as on 10.04.2019) when the onCaptcha promise resolves',
|
||||
3: 'should callback with an error if the onCaptcha promise is rejected'
|
||||
describe('reCAPTCHA (version as on 01.12.2019)', () => {
|
||||
for (let stage = 0; stage < 4; stage++) {
|
||||
const desc = {
|
||||
0: 'should resolve when user calls captcha.submit()',
|
||||
1: 'should callback with an error if user calls captcha.submit(error)',
|
||||
2: 'should resolve when the onCaptcha promise resolves',
|
||||
3: 'should callback with an error if the onCaptcha promise is rejected'
|
||||
};
|
||||
|
||||
// Run this test 4 times
|
||||
it(desc[stage], function (done) {
|
||||
const secret = '0bd666f149acf02bbc05bba3b1bb';
|
||||
const siteKey = '6LfBixYUAAAAABhdHynFUIMA_sa4s-XsJvnjtgB0';
|
||||
const rayId = '53dfe8147d2a9e73';
|
||||
const expectedError = new Error('anti-captcha failed!');
|
||||
|
||||
helper.router
|
||||
.get('/test', function (req, res) {
|
||||
res.sendCaptcha('cf_recaptcha_01_12_2019.html');
|
||||
})
|
||||
.post('/', function (req, res) {
|
||||
res.send(requestedPage);
|
||||
});
|
||||
|
||||
const onCaptcha = sinon.spy(function (options, response, body) {
|
||||
expect(options).to.be.an('object');
|
||||
expect(response).to.be.instanceof(http.IncomingMessage);
|
||||
expect(body).to.be.a('string');
|
||||
|
||||
sinon.assert.match(response, {
|
||||
isCloudflare: true,
|
||||
isHTML: true,
|
||||
isCaptcha: true,
|
||||
captcha: sinon.match.object
|
||||
});
|
||||
|
||||
sinon.assert.match(response.captcha, {
|
||||
url: uri, // <-- Deprecated
|
||||
uri: sinon.match.same(response.request.uri),
|
||||
form: { r: secret, id: rayId },
|
||||
siteKey: siteKey,
|
||||
submit: sinon.match.func
|
||||
});
|
||||
|
||||
// Simulate what the user should do here
|
||||
response.captcha.form['g-recaptcha-response'] = 'foobar';
|
||||
|
||||
switch (stage) {
|
||||
case 0:
|
||||
// User green lights form submission
|
||||
response.captcha.submit();
|
||||
break;
|
||||
case 1:
|
||||
// User reports an error when solving the reCAPTCHA
|
||||
response.captcha.submit(expectedError);
|
||||
break;
|
||||
case 2:
|
||||
// User green lights form submission by resolving the returned promise
|
||||
return Promise.resolve();
|
||||
case 3:
|
||||
// User reports an error by rejecting the returned promise
|
||||
return Promise.reject(expectedError);
|
||||
}
|
||||
});
|
||||
|
||||
const firstParams = helper.extendParams({ onCaptcha, uri });
|
||||
const secondParams = helper.extendParams({
|
||||
onCaptcha,
|
||||
method: 'POST',
|
||||
uri: helper.resolve('/?__cf_chl_captcha_tk__=e8844bdff35ae5e'),
|
||||
qs: { __cf_chl_captcha_tk__: 'e8844bdff35ae5e' },
|
||||
headers: {
|
||||
Referer: helper.resolve('/test')
|
||||
},
|
||||
form: {
|
||||
r: secret,
|
||||
id: rayId,
|
||||
'g-recaptcha-response': 'foobar'
|
||||
}
|
||||
});
|
||||
|
||||
const options = { onCaptcha, uri };
|
||||
|
||||
const promise = cloudscraper.get(options, function (error, response, body) {
|
||||
switch (stage) {
|
||||
case 0:
|
||||
case 2:
|
||||
expect(error).to.be.null;
|
||||
|
||||
expect(onCaptcha).to.be.calledOnce;
|
||||
|
||||
expect(Request).to.be.calledTwice;
|
||||
expect(Request.firstCall).to.be.calledWithExactly(firstParams);
|
||||
expect(Request.secondCall).to.be.calledWithExactly(secondParams);
|
||||
|
||||
expect(body).to.be.equal(requestedPage);
|
||||
expect(promise).to.eventually.equal(requestedPage).and.notify(done);
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
expect(error).to.be.instanceOf(errors.CaptchaError);
|
||||
expect(error.error).to.be.an('error');
|
||||
expect(error).to.have.property('errorType', 1);
|
||||
expect(error.message).to.include(expectedError.message);
|
||||
expect(promise).to.be.rejectedWith(errors.CaptchaError).and.notify(done);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Run this test 4 times
|
||||
it(desc[stage], function (done) {
|
||||
const secret = '6b132d85d185a8255f2451d48fe6a8bee7154ea2-1555377580-1800-AQ1azEkeDOnQP5ByOpwUU/RdbKrmMwHYpkaenRvjPXtB0w8Vbjn/Ceg62tfpp/lT799kjDLEMMuDkEMqQ7iO51kniWCQm00BQvDGl+D0h/WvXDWO96YXOUD3qrqUTuzO7QbUOinc8y8kedvOQkr4c0o=';
|
||||
const siteKey = '6LfBixYUAAAAABhdHynFUIMA_sa4s-XsJvnjtgB0';
|
||||
const expectedError = new Error('anti-captcha failed!');
|
||||
|
||||
helper.router
|
||||
.get('/test', function (req, res) {
|
||||
res.sendCaptcha('cf_recaptcha_15_04_2019.html');
|
||||
})
|
||||
.get('/cdn-cgi/l/chk_captcha', function (req, res) {
|
||||
res.send(requestedPage);
|
||||
});
|
||||
|
||||
const onCaptcha = sinon.spy(function (options, response, body) {
|
||||
expect(options).to.be.an('object');
|
||||
expect(response).to.be.instanceof(http.IncomingMessage);
|
||||
expect(body).to.be.a('string');
|
||||
|
||||
sinon.assert.match(response, {
|
||||
isCloudflare: true,
|
||||
isHTML: true,
|
||||
isCaptcha: true,
|
||||
captcha: sinon.match.object
|
||||
});
|
||||
|
||||
sinon.assert.match(response.captcha, {
|
||||
url: uri, // <-- Deprecated
|
||||
uri: sinon.match.same(response.request.uri),
|
||||
form: { s: secret },
|
||||
siteKey: siteKey,
|
||||
submit: sinon.match.func
|
||||
});
|
||||
|
||||
// Simulate what the user should do here
|
||||
response.captcha.form['g-recaptcha-response'] = 'foobar';
|
||||
|
||||
switch (stage) {
|
||||
case 0:
|
||||
// User green lights form submission
|
||||
response.captcha.submit();
|
||||
break;
|
||||
case 1:
|
||||
// User reports an error when solving the reCAPTCHA
|
||||
response.captcha.submit(expectedError);
|
||||
break;
|
||||
case 2:
|
||||
// User green lights form submission by resolving the returned promise
|
||||
return Promise.resolve();
|
||||
case 3:
|
||||
// User reports an error by rejecting the returned promise
|
||||
return Promise.reject(expectedError);
|
||||
}
|
||||
});
|
||||
|
||||
const firstParams = helper.extendParams({ onCaptcha, uri });
|
||||
const secondParams = helper.extendParams({
|
||||
onCaptcha,
|
||||
method: 'GET',
|
||||
uri: helper.resolve('/cdn-cgi/l/chk_captcha'),
|
||||
headers: {
|
||||
Referer: uri
|
||||
},
|
||||
qs: {
|
||||
s: secret,
|
||||
'g-recaptcha-response': 'foobar'
|
||||
}
|
||||
});
|
||||
|
||||
const options = { onCaptcha, uri };
|
||||
|
||||
const promise = cloudscraper.get(options, function (error, response, body) {
|
||||
switch (stage) {
|
||||
case 0:
|
||||
case 2:
|
||||
expect(error).to.be.null;
|
||||
|
||||
expect(onCaptcha).to.be.calledOnce;
|
||||
|
||||
expect(Request).to.be.calledTwice;
|
||||
expect(Request.firstCall).to.be.calledWithExactly(firstParams);
|
||||
expect(Request.secondCall).to.be.calledWithExactly(secondParams);
|
||||
|
||||
expect(body).to.be.equal(requestedPage);
|
||||
expect(promise).to.eventually.equal(requestedPage).and.notify(done);
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
expect(error).to.be.instanceOf(errors.CaptchaError);
|
||||
expect(error.error).to.be.an('error');
|
||||
expect(error).to.have.property('errorType', 1);
|
||||
expect(error.message).to.include(expectedError.message);
|
||||
expect(promise).to.be.rejectedWith(errors.CaptchaError).and.notify(done);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue