def renderAnswer(self, request, bridgeLines=None): """Generate a response for a client which includes **bridgesLines**. .. note: The generated response can be plain or HTML. A plain response looks like:: voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567 That is, there is no HTML, what you see is what you get, and what you get is suitable for pasting directly into Tor Launcher (or into a torrc, if you prepend ``"Bridge "`` to each line). The plain format can be requested from BridgeDB's web service by adding an ``&format=plain`` HTTP GET parameter to the URL. Also note that you won't get a QRCode, usage instructions, error messages, or any other fanciness if you use the plain format. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object containing the HTTP method, full URI, and any URL/POST arguments and headers present. :type bridgeLines: list or None :param bridgeLines: A list of strings used to configure a Tor client to use a bridge. If ``None``, then the returned page will instead explain that there were no bridges of the type they requested, with instructions on how to proceed. :rtype: str :returns: A plaintext or HTML response to serve. """ rtl = False format = self.getResponseFormat(request) if format == 'plain': request.setHeader("Content-Type", "text/plain") try: rendered = bytes('\n'.join(bridgeLines)) except Exception as err: rendered = replaceErrorPage(request, err, html=False) else: request.setHeader("Content-Type", "text/html; charset=utf-8") qrcode = None qrjpeg = generateQR(bridgeLines) if qrjpeg: qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg) try: langs = translations.getLocaleFromHTTPRequest(request) rtl = translations.usingRTLLang(langs) template = lookup.get_template('bridges.html') rendered = template.render(strings, rtl=rtl, lang=langs[0], answer=bridgeLines, qrcode=qrcode) except Exception as err: rendered = replaceErrorPage(request, err) return rendered
def test_generateQR_bridgeSchema(self): """Calling generateQR() with bridgeSchema=True should prepend ``'bridge://`` to each of the QR encoded bridge lines. """ # If we were to install the python-qrtools Debian package, we'd be # able to decode the resulting QRCode to check that it contains the # 'bridge://' prefix for each bridge line… but that would add another # Debian dependency just to unittest 5 lines of code. # # Instead: self.assertTrue(qrcodes.generateQR(self.bridgelines, bridgeSchema=True))
def renderAnswer(self, request, bridgeLines=None, format=None): """Generate a response for a client which includes **bridges**. The generated response can be plaintext or HTML. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object containing the HTTP method, full URI, and any URL/POST arguments and headers present. :type bridgeLines: list or None :param bridgeLines: A list of strings used to configure a Tor client to use a bridge. :type format: str or None :param format: If ``'plain'``, return a plaintext response. Otherwise, use the :file:`bridgedb/templates/bridges.html` template to render an HTML response page which includes the **bridges**. :rtype: str :returns: A plaintext or HTML response to serve. """ rtl = False if format == 'plain': request.setHeader("Content-Type", "text/plain") rendered = bridgeLines else: request.setHeader("Content-Type", "text/html; charset=utf-8") qrcode = None qrjpeg = generateQR(bridgeLines) if qrjpeg: qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg) try: langs = translations.getLocaleFromHTTPRequest(request) rtl = translations.usingRTLLang(langs) template = lookup.get_template('bridges.html') rendered = template.render(strings, rtl=rtl, lang=langs[0], answer=bridgeLines, qrcode=qrcode) except Exception as err: rendered = replaceErrorPage(err) return rendered
def test_generateQR_save_nonexistent_format(self): """Calling generateQR() with imageFormat=u'FOOBAR' should return None. """ self.assertIsNone(qrcodes.generateQR(self.bridgelines, imageFormat=u'FOOBAR'))
def test_generateQR_no_qrcode_module(self): """Calling generateQR() without the qrcode module installed should return None. """ qrcodes.qrcode = None self.assertIsNone(qrcodes.generateQR(self.bridgelines))
def test_generateQR_no_bridgelines(self): """Calling generateQR() without bridgelines should return None.""" self.assertIsNone(qrcodes.generateQR(""))
def test_generateQR_bad_bridgelines(self): """Calling generateQR() with a bad type for the bridgelines should return None. """ self.assertIsNone(qrcodes.generateQR(list()))
def test_generateQR(self): """Calling generateQR() should generate an image.""" self.assertTrue(qrcodes.generateQR(self.bridgelines))
def render_POST(self, request): """Process a client's CAPTCHA solution. If the client's CAPTCHA solution is valid (according to :meth:`checkSolution`), process and serve their original request. Otherwise, redirect them back to a new CAPTCHA page. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object, including POST arguments which should include two key/value pairs: one key being ``'captcha_challenge_field'``, and the other, ``'captcha_response_field'``. These POST arguments should be obtained from :meth:`render_GET`. :rtype: str :returns: A rendered HTML page containing a ReCaptcha challenge image for the client to solve. """ valid = False error = self.checkRequestHeaders(request) if error: # pragma: no cover logging.debug("Error while checking moat request headers.") metrix.recordInvalidMoatRequest(request) return error.render(request) data = { "data": [{ "id": '3', "type": 'moat-bridges', "version": MOAT_API_VERSION, "bridges": None, "qrcode": None, }] } try: pos = request.content.tell() encoded_client_data = request.content.read() # We rewind the stream to its previous position to allow the # metrix module to read the request's content too. request.content.seek(pos) client_data = json.loads(encoded_client_data)["data"][0] clientIP = self.getClientIP(request) (include_qrcode, transport, challenge, solution) = self.extractClientSolution(client_data) valid = self.checkSolution(challenge, solution, clientIP) except captcha.CaptchaExpired: logging.debug("The challenge had timed out") metrix.recordInvalidMoatRequest(request) return self.failureResponse(5, request) except Exception as impossible: logging.warn( "Unhandled exception while processing a POST /fetch request!") logging.error(impossible) metrix.recordInvalidMoatRequest(request) return self.failureResponse(4, request) if valid: qrcode = None bridgeRequest = self.createBridgeRequest(clientIP, client_data) bridgeLines = self.getBridgeLines(bridgeRequest) metrix.recordValidMoatRequest(request) # If we can only return less than the configured # MOAT_BRIDGES_PER_ANSWER then log a warning. if len(bridgeLines) < self.nBridgesToGive: logging.warn( ("Not enough bridges of the type specified to " "fulfill the following request: %s") % bridgeRequest) if antibot.isRequestFromBot(request): ttype = transport or "vanilla" bridgeLines = antibot.getDecoyBridge(ttype, bridgeRequest.ipVersion) # If we have no bridges at all to give to the client, then # return a JSON API 404 error. if not bridgeLines: return self.failureResponse(6, request) if include_qrcode: qrjpeg = generateQR(bridgeLines) if qrjpeg: qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode( qrjpeg) data["data"][0]["qrcode"] = qrcode data["data"][0]["bridges"] = bridgeLines return self.formatDataForResponse(data, request) else: metrix.recordInvalidMoatRequest(request) return self.failureResponse(4, request)
def render_POST(self, request): """Process a client's CAPTCHA solution. If the client's CAPTCHA solution is valid (according to :meth:`checkSolution`), process and serve their original request. Otherwise, redirect them back to a new CAPTCHA page. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object, including POST arguments which should include two key/value pairs: one key being ``'captcha_challenge_field'``, and the other, ``'captcha_response_field'``. These POST arguments should be obtained from :meth:`render_GET`. :rtype: str :returns: A rendered HTML page containing a ReCaptcha challenge image for the client to solve. """ valid = False error = self.checkRequestHeaders(request) if error: # pragma: no cover return error.render(request) data = { "data": [{ "id": '3', "type": 'moat-bridges', "version": MOAT_API_VERSION, "bridges": None, "qrcode": None, }] } try: encoded_client_data = request.content.read() client_data = json.loads(encoded_client_data)["data"][0] clientIP = self.getClientIP(request) (include_qrcode, transport, challenge, solution) = self.extractClientSolution(client_data) valid = self.checkSolution(challenge, solution, clientIP) except captcha.CaptchaExpired: logging.debug("The challenge had timed out") return self.failureResponse(5, request) except Exception as impossible: logging.warn("Unhandled exception while processing a POST /fetch request!") logging.error(impossible) return self.failureResponse(4, request) if valid: qrcode = None bridgeRequest = self.createBridgeRequest(clientIP, client_data) bridgeLines = self.getBridgeLines(bridgeRequest) # If we can only return less than the configured # MOAT_BRIDGES_PER_ANSWER then log a warning. if len(bridgeLines) < self.nBridgesToGive: logging.warn(("Not enough bridges of the type specified to " "fulfill the following request: %s") % bridgeRequest) # If we have no bridges at all to give to the client, then # return a JSON API 404 error. if not bridgeLines: return self.failureResponse(6, request) if include_qrcode: qrjpeg = generateQR(bridgeLines) if qrjpeg: qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg) data["data"][0]["qrcode"] = qrcode data["data"][0]["bridges"] = bridgeLines return self.formatDataForResponse(data, request) else: return self.failureResponse(4, request)