Пример #1
0
 def save_test(self) -> None:
     """Save a previously run test_id."""
     try:
         # touch the save file so it isn't deleted.
         os.utime(
             os.path.join(self.config["save_dir"], self.test_id),
             (
                 thor.time(),
                 thor.time() +
                 (int(self.config["save_days"]) * 24 * 60 * 60),
             ),
         )
         location = b"?id=%s" % self.test_id.encode("ascii")
         if self.descend:
             location = b"%s&descend=True" % location
         self.response_start(b"303", b"See Other",
                             [(b"Location", location)])
         self.response_body("Redirecting to the saved test page...".encode(
             self.config["charset"]))
     except (OSError, IOError):
         self.response_start(
             b"500",
             b"Internal Server Error",
             [(b"Content-Type",
               b"text/html; charset=%s" % self.charset_bytes)],
         )
         self.response_body(self.show_error("Sorry, I couldn't save that."))
     self.response_done([])
Пример #2
0
 def save_test(self):
     """Save a previously run test_id."""
     try:
         # touch the save file so it isn't deleted.
         os.utime(
             os.path.join(save_dir, self.test_id), 
             (
                 thor.time(), 
                 thor.time() + (save_days * 24 * 60 * 60)
             )
         )
         location = "?id=%s" % self.test_id
         if self.descend:
             location = "%s&descend=True" % location
         self.response_start(
             "303", "See Other", [
             ("Location", location)
         ])
         self.response_body("Redirecting to the saved test page...")
     except (OSError, IOError):
         self.response_start(
             "500", "Internal Server Error", [
             ("Content-Type", "text/html; charset=%s" % charset), 
         ])
         # TODO: better error message (through formatter?)
         self.response_body(
             error_template % "Sorry, I couldn't save that."
         )
     self.response_done([])
Пример #3
0
def extend_saved_test(webui: "RedWebUi") -> None:
    """Extend the expiry time of a previously run test_id."""
    try:
        # touch the save file so it isn't deleted.
        os.utime(
            os.path.join(webui.config["save_dir"], webui.test_id),
            (
                thor.time(),
                thor.time() + (int(webui.config["save_days"]) * 24 * 60 * 60),
            ),
        )
        location = b"?id=%s" % webui.test_id.encode("ascii")
        if webui.descend:
            location = b"%s&descend=True" % location
        webui.exchange.response_start(b"303", b"See Other",
                                      [(b"Location", location)])
        webui.output("Redirecting to the saved test page...")
    except OSError:
        webui.exchange.response_start(
            b"500",
            b"Internal Server Error",
            [(b"Content-Type", b"text/html; charset=%s" % webui.charset_bytes)
             ],
        )
        webui.output("Sorry, I couldn't save that.")
    webui.exchange.response_done([])
Пример #4
0
 def write(self, content: bytes, lifetime: int) -> None:
     """
     Write content to the file, marking it fresh for lifetime seconds.
     Discard errors silently.
     """
     try:
         fd = gzip.open(self.path, 'w')
         fd.write(content)
         os.utime(self.path, (thor.time(), thor.time() + lifetime))
     except (OSError, IOError, zlib.error):
         return
     finally:
         if 'fd' in locals():
             fd.close()
Пример #5
0
 def write(self, content: bytes, lifetime: int) -> None:
     """
     Write content to the file, marking it fresh for lifetime seconds.
     Discard errors silently.
     """
     try:
         fd = gzip.open(self.path, "w")
         fd.write(content)
         os.utime(self.path, (thor.time(), thor.time() + lifetime))
     except (OSError, IOError, zlib.error):
         return
     finally:
         if "fd" in locals():
             fd.close()
Пример #6
0
 def __init__(self, *args: Any, **kw: Any) -> None:
     Formatter.__init__(self, *args, **kw)
     self.templates = Environment(
         loader=PackageLoader("redbot.formatter"),
         trim_blocks=True,
         autoescape=select_autoescape(
             enabled_extensions=("html", "xml"), default_for_string=True,
         ),
     )
     self.templates.filters.update(
         {
             "f_num": f_num,
             "relative_time": relative_time,
             "redbot_link": self.redbot_link,
         }
     )
     self.templates.globals.update(
         {
             "formatter": self,
             "version": __version__,
             "baseuri": self.config["ui_uri"],
             "static": self.config["static_root"],
             "hcaptcha": self.config.get("hcaptcha_sitekey", "") != ""
             and self.config.get("hcaptcha_secret", "") != "",
         }
     )
     self.start = thor.time()
Пример #7
0
    def _response_done(self, trailers):
        "Finish anaylsing the response, handling any parse errors."
        self._st.append('_response_done()')
        state = self.state
        state.res_complete = True
        state.res_done_ts = thor.time()
        state.transfer_length = self.exchange.input_transfer_length
        state.header_length = self.exchange.input_header_length
        # TODO: check trailers
        if self.status_cb and state.type:
            self.status_cb("fetched %s (%s)" % (state.uri, state.type))
        state.res_body_md5 = self._md5_processor.digest()
        state.res_body_post_md5 = self._md5_post_processor.digest()
        checkCaching(state)

        if state.method not in ['HEAD'] and state.res_status not in ['304']:
            # check payload basics
            if state.parsed_hdrs.has_key('content-length'):
                if state.res_body_len == state.parsed_hdrs['content-length']:
                    state.set_message('header-content-length', rs.CL_CORRECT)
                else:
                    state.set_message('header-content-length', 
                                    rs.CL_INCORRECT,
                                    body_length=f_num(state.res_body_len)
                    )
            if state.parsed_hdrs.has_key('content-md5'):
                c_md5_calc = base64.encodestring(state.res_body_md5)[:-1]
                if state.parsed_hdrs['content-md5'] == c_md5_calc:
                    state.set_message('header-content-md5', rs.CMD5_CORRECT)
                else:
                    state.set_message('header-content-md5', rs.CMD5_INCORRECT,
                                     calc_md5=c_md5_calc)
        self.done()
        self.finish_task()
Пример #8
0
    def read(self):
        """
        Read the file, returning its contents. If it does not exist or
        cannot be read, returns None.
        """
        if not path.exists(self.path):
            return None

        try:
            fd = gzip.open(self.path)
        except (OSError, IOError, zlib.error):
            self.delete()
            return None

        try:
            mtime = os.fstat(fd.fileno()).st_mtime
            is_fresh = mtime > thor.time()
            if not is_fresh:
                self.delete()
                return None
            content = fd.read()
        except IOError:
            self.delete()
            return None
        finally:
            fd.close()
        return content
Пример #9
0
    def body_done(self,
                  complete: bool,
                  trailers: RawHeaderListType = None) -> None:
        """
        Signal that the body is done. Complete should be True if we
        know it's complete (e.g., final chunk, Content-Length).
        """
        self.complete = complete
        self.complete_time = thor.time()
        self.trailers = trailers or []
        self.payload_md5 = self._md5_processor.digest()
        self.decoded_md5 = self._md5_post_processor.digest()

        if self.is_request or \
          (not self.is_head_response and self.status_code not in ['304']):
            # check payload basics
            if 'content-length' in self.parsed_headers:
                if self.payload_len == self.parsed_headers['content-length']:
                    self.add_note('header-content-length', CL_CORRECT)
                else:
                    self.add_note('header-content-length',
                                  CL_INCORRECT,
                                  body_length=f_num(self.payload_len))
            if 'content-md5' in self.parsed_headers:
                c_md5_calc = base64.encodebytes(self.payload_md5)[:-1]
                if self.parsed_headers['content-md5'] == c_md5_calc:
                    self.add_note('header-content-md5', CMD5_CORRECT)
                else:
                    self.add_note('header-content-md5',
                                  CMD5_INCORRECT,
                                  calc_md5=c_md5_calc)
        self.emit('content_available')
Пример #10
0
 def run(self, done_cb=None):
     """
     Make an asynchronous HTTP request to uri, calling status_cb as it's
     updated and done_cb when it's done. Reason is used to explain what the
     request is in the status callback.
     """
     self.outstanding_tasks += 1
     self._st.append('run(%s)' % str(done_cb))
     self.done_cb = done_cb
     state = self.state
     if not self.preflight() or state.uri == None:
         # generally a good sign that we're not going much further.
         self.finish_task()
         return
     if 'user-agent' not in [i[0].lower() for i in state.req_hdrs]:
         state.req_hdrs.append(
             (u"User-Agent", u"RED/%s (http://redbot.org/)" % __version__))    
     self.exchange = self.client.exchange()
     self.exchange.on('response_start', self._response_start)
     self.exchange.on('response_body', self._response_body)
     self.exchange.on('response_done', self._response_done)
     self.exchange.on('error', self._response_error)
     if self.status_cb and state.type:
         self.status_cb("fetching %s (%s)" % (state.uri, state.type))
     req_hdrs = [
         (k.encode('ascii', 'replace'), v.encode('latin-1', 'replace')) \
         for (k, v) in state.req_hdrs
     ]
     self.exchange.request_start(state.method, state.uri, req_hdrs)
     state.req_ts = thor.time()
     if state.req_body != None:
         self.exchange.request_body(state.req_body)
     self.exchange.request_done([])
Пример #11
0
    def final_status(self):
#        See issue #51
#        self.status("RED made %(reqs)s requests in %(elapse)2.3f seconds." % {
#            'reqs': fetch.total_requests,
        self.status("RED finished in %(elapse)2.3f seconds." % {
           'elapse': thor.time() - self.start
        })
Пример #12
0
    def run_continue(self, allowed: bool) -> None:
        """
        Continue after getting the robots file.
        """
        if not allowed:
            self.response.http_error = RobotsTxtError()
            self._fetch_done()
            return

        self.fetch_started = True

        if 'user-agent' not in [i[0].lower() for i in self.request.headers]:
            self.request.headers.append(("User-Agent", UA_STRING))
        self.exchange = self.client.exchange()
        self.exchange.on('response_nonfinal', self._response_nonfinal)
        self.exchange.once('response_start', self._response_start)
        self.exchange.on('response_body', self._response_body)
        self.exchange.once('response_done', self._response_done)
        self.exchange.on('error', self._response_error)
        self.emit("status", "fetching %s (%s)" % (self.request.uri, self.check_name))
        req_hdrs = [(k.encode('ascii', 'replace'), v.encode('ascii', 'replace'))
                    for (k, v) in self.request.headers] # FIXME: should complain
        self.exchange.request_start(
            self.request.method.encode('ascii'), self.request.uri.encode('ascii'), req_hdrs)
        self.request.start_time = thor.time()
        if self.request.payload != None:
            self.exchange.request_body(self.request.payload)
            self.transfer_out += len(self.request.payload)
        self.exchange.request_done([])
Пример #13
0
    def load_saved_test(self) -> None:
        """Load a saved test by test_id."""
        try:
            fd = gzip.open(
                os.path.join(self.config.save_dir,
                             os.path.basename(self.test_id)))
            mtime = os.fstat(fd.fileno()).st_mtime
        except (OSError, IOError, TypeError, zlib.error):
            self.response_start(
                b"404", b"Not Found",
                [(b"Content-Type",
                  b"text/html; charset=%s" % self.charset_bytes),
                 (b"Cache-Control", b"max-age=600, must-revalidate")])
            self.response_body(
                self.show_error(
                    "I'm sorry, I can't find that saved response."))
            self.response_done([])
            return
        is_saved = mtime > thor.time()
        try:
            top_resource = pickle.load(fd)
        except (pickle.PickleError, IOError, EOFError):
            self.response_start(
                b"500", b"Internal Server Error",
                [(b"Content-Type",
                  b"text/html; charset=%s" % self.charset_bytes),
                 (b"Cache-Control", b"max-age=600, must-revalidate")])
            self.response_body(
                self.show_error("I'm sorry, I had a problem loading that."))
            self.response_done([])
            return
        finally:
            fd.close()

        if self.check_name:
            display_resource = top_resource.subreqs.get(
                self.check_name, top_resource)
        else:
            display_resource = top_resource

        formatter = find_formatter(self.format, 'html', top_resource.descend)(
            self.ui_uri,
            self.config.lang,
            self.output,
            allow_save=(not is_saved),
            is_saved=True,
            test_id=self.test_id)
        content_type = "%s; charset=%s" % (formatter.media_type,
                                           self.config.charset)

        self.response_start(
            b"200", b"OK",
            [(b"Content-Type", content_type.encode('ascii')),
             (b"Cache-Control", b"max-age=3600, must-revalidate")])

        @thor.events.on(formatter)
        def formatter_done() -> None:
            self.response_done([])

        formatter.bind_resource(display_resource)
Пример #14
0
 def save_test(self) -> None:
     """Save a previously run test_id."""
     try:
         # touch the save file so it isn't deleted.
         os.utime(os.path.join(self.config.save_dir, self.test_id), (
             thor.time(), thor.time() + (self.config.save_days * 24 * 60 * 60)))
         location = "?id=%s" % self.test_id
         if self.descend:
             location = "%s&descend=True" % location
         self.response_start("303", "See Other", [("Location", location)])
         self.response_body("Redirecting to the saved test page...".encode(self.config.charset))
     except (OSError, IOError):
         self.response_start(b"500", b"Internal Server Error",
                             [(b"Content-Type", b"text/html; charset=%s" % self.charset_bytes),])
         self.response_body(self.show_error("Sorry, I couldn't save that."))
     self.response_done([])
Пример #15
0
    def _response_done(self, trailers):
        "Finish analysing the response, handling any parse errors."
        self._st.append('_response_done()')
        state = self.state
        state.res_complete = True
        state.res_done_ts = thor.time()
        state.transfer_length = self.exchange.input_transfer_length
        state.header_length = self.exchange.input_header_length
        # TODO: check trailers
        if self.status_cb and state.type:
            self.status_cb("fetched %s (%s)" % (state.uri, state.type))
        state.res_body_md5 = self._md5_processor.digest()
        state.res_body_post_md5 = self._md5_post_processor.digest()
        checkCaching(state)

        if state.method not in ['HEAD'] and state.res_status not in ['304']:
            # check payload basics
            if state.parsed_hdrs.has_key('content-length'):
                if state.res_body_len == state.parsed_hdrs['content-length']:
                    state.set_message('header-content-length', rs.CL_CORRECT)
                else:
                    state.set_message('header-content-length',
                                      rs.CL_INCORRECT,
                                      body_length=f_num(state.res_body_len))
            if state.parsed_hdrs.has_key('content-md5'):
                c_md5_calc = base64.encodestring(state.res_body_md5)[:-1]
                if state.parsed_hdrs['content-md5'] == c_md5_calc:
                    state.set_message('header-content-md5', rs.CMD5_CORRECT)
                else:
                    state.set_message('header-content-md5',
                                      rs.CMD5_INCORRECT,
                                      calc_md5=c_md5_calc)
        self.done()
        self.finish_task()
Пример #16
0
 def run(self, done_cb=None):
     """
     Make an asynchronous HTTP request to uri, calling status_cb as it's
     updated and done_cb when it's done. Reason is used to explain what the
     request is in the status callback.
     """
     self.outstanding_tasks += 1
     self._st.append('run(%s)' % str(done_cb))
     self.done_cb = done_cb
     state = self.state
     if not self.preflight() or state.uri == None:
         # generally a good sign that we're not going much further.
         self.finish_task()
         return
     if 'user-agent' not in [i[0].lower() for i in state.req_hdrs]:
         state.req_hdrs.append(
             (u"User-Agent", u"RED/%s (http://redbot.org/)" % __version__))
     self.exchange = self.client.exchange()
     self.exchange.on('response_start', self._response_start)
     self.exchange.on('response_body', self._response_body)
     self.exchange.on('response_done', self._response_done)
     self.exchange.on('error', self._response_error)
     if self.status_cb and state.type:
         self.status_cb("fetching %s (%s)" % (state.uri, state.type))
     req_hdrs = [
         (k.encode('ascii', 'replace'), v.encode('latin-1', 'replace')) \
         for (k, v) in state.req_hdrs
     ]
     self.exchange.request_start(state.method, state.uri, req_hdrs)
     state.req_ts = thor.time()
     if state.req_body != None:
         self.exchange.request_body(state.req_body)
     self.exchange.request_done([])
Пример #17
0
    def body_done(self, complete: bool, trailers: RawHeaderListType = None) -> None:
        """
        Signal that the body is done. Complete should be True if we
        know it's complete (e.g., final chunk, Content-Length).
        """
        self.complete = complete
        self.complete_time = thor.time()
        self.trailers = trailers or []
        self.payload_md5 = self._md5_processor.digest()
        self.decoded_md5 = self._md5_post_processor.digest()

        if self.is_request or \
          (not self.is_head_response and self.status_code not in ['304']):
            # check payload basics
            if 'content-length' in self.parsed_headers:
                if self.payload_len == self.parsed_headers['content-length']:
                    self.add_note('header-content-length', CL_CORRECT)
                else:
                    self.add_note('header-content-length',
                                  CL_INCORRECT,
                                  body_length=f_num(self.payload_len))
            if 'content-md5' in self.parsed_headers:
                c_md5_calc = base64.encodebytes(self.payload_md5)[:-1]
                if self.parsed_headers['content-md5'] == c_md5_calc:
                    self.add_note('header-content-md5', CMD5_CORRECT)
                else:
                    self.add_note('header-content-md5',
                                  CMD5_INCORRECT, calc_md5=c_md5_calc)
        self.emit('content_available')
Пример #18
0
    def read(self) -> bytes:
        """
        Read the file, returning its contents. If it does not exist or
        cannot be read, returns None.
        """
        if not path.exists(self.path):
            return None

        try:
            fd = gzip.open(self.path)
        except (OSError, IOError, zlib.error):
            self.delete()
            return None

        try:
            mtime = os.fstat(fd.fileno()).st_mtime
            is_fresh = mtime > thor.time()
            if not is_fresh:
                self.delete()
                return None
            content = fd.read()
        except IOError:
            self.delete()
            return None
        finally:
            if "fd" in locals():
                fd.close()
        return content
Пример #19
0
    def run_continue(self, allowed: bool) -> None:
        """
        Continue after getting the robots file.
        """
        if not allowed:
            self.response.http_error = RobotsTxtError()
            self._fetch_done()
            return

        self.fetch_started = True

        if 'user-agent' not in [i[0].lower() for i in self.request.headers]:
            self.request.headers.append(("User-Agent", UA_STRING))
        self.exchange = self.client.exchange()
        self.exchange.on('response_nonfinal', self._response_nonfinal)
        self.exchange.once('response_start', self._response_start)
        self.exchange.on('response_body', self._response_body)
        self.exchange.once('response_done', self._response_done)
        self.exchange.on('error', self._response_error)
        self.emit("status", "fetching %s (%s)" % (self.request.uri, self.check_name))
        req_hdrs = [(k.encode('ascii'), v.encode('ascii')) for (k, v) in self.request.headers]
        self.exchange.request_start(
            self.request.method.encode('ascii'), self.request.uri.encode('ascii'), req_hdrs)
        self.request.start_time = thor.time()
        if self.request.payload != None:
            self.exchange.request_body(self.request.payload)
            self.transfer_out += len(self.request.payload)
        self.exchange.request_done([])
Пример #20
0
    def final_status(self) -> None:
        #        See issue #51
        #        self.status("REDbot made %(reqs)s requests in %(elapse)2.3f seconds." % {
        #            'reqs': fetch.total_requests,
        self.status("")
        self.output("""
<div id="final_status">%(elapse)2.2f seconds</div>
""" % {'elapse': thor.time() - self.start})
Пример #21
0
 def _response_start(self, status: bytes, phrase: bytes,
                     res_headers: RawHeaderListType) -> None:
     "Process the response start-line and headers."
     self.response.start_time = thor.time()
     self.response.process_top_line(self.exchange.res_version, status, phrase)
     self.response.process_raw_headers(res_headers)
     StatusChecker(self.response, self.request)
     checkCaching(self.response, self.request)
Пример #22
0
 def _response_start(self, status: bytes, phrase: bytes,
                     res_headers: RawHeaderListType) -> None:
     "Process the response start-line and headers."
     self.response.start_time = thor.time()
     self.response.process_top_line(self.exchange.res_version, status, phrase)
     self.response.process_raw_headers(res_headers)
     StatusChecker(self.response, self.request)
     checkCaching(self.response, self.request)
Пример #23
0
    def final_status(self) -> None:
#        See issue #51
#        self.status("REDbot made %(reqs)s requests in %(elapse)2.3f seconds." % {
#            'reqs': fetch.total_requests,
        self.status("")
        self.output("""
<div id="final_status">%(elapse)2.2f seconds</div>
""" % {'elapse': thor.time() - self.start})
Пример #24
0
    def status(self, message):
        "Update the status bar of the browser"
        self.output(u"""
<script>
<!-- %3.3f
window.status="%s";
-->
</script>
        """ % (thor.time() - self.start, e_html(message)))
Пример #25
0
    def debug(self, message: str) -> None:
        "Debug to console."
        self.output("""
<script>
<!--
console.log("%3.3f %s");
-->
</script>
""" % (thor.time() - self.start, e_js(message)))
Пример #26
0
    def status(self, message: str) -> None:
        "Update the status bar of the browser"
        self.output("""
<script>
<!-- %3.3f
$('#red_status').text("%s");
-->
</script>
""" % (thor.time() - self.start, e_html(message)))
Пример #27
0
    def status(self, message: str) -> None:
        "Update the status bar of the browser"
        self.output("""
<script>
<!-- %3.3f
$('#red_status').text("%s");
-->
</script>
""" % (thor.time() - self.start, e_html(message)))
Пример #28
0
 def save_test(self):
     """Save a previously run test_id."""
     try:
         # touch the save file so it isn't deleted.
         os.utime(os.path.join(save_dir, self.test_id),
                  (thor.time(), thor.time() + (save_days * 24 * 60 * 60)))
         location = "?id=%s" % self.test_id
         if self.descend:
             location = "%s&descend=True" % location
         self.response_start("303", "See Other", [("Location", location)])
         self.response_body("Redirecting to the saved test page...")
     except (OSError, IOError):
         self.response_start("500", "Internal Server Error", [
             ("Content-Type", "text/html; charset=%s" % charset),
         ])
         # TODO: better error message (through formatter?)
         self.response_body(error_template % "Sorry, I couldn't save that.")
     self.response_done([])
Пример #29
0
    def debug(self, message: str) -> None:
        "Debug to console."
        self.output("""
<script>
<!--
console.log("%3.3f %s");
-->
</script>
""" % (thor.time() - self.start, e_js(message)))
Пример #30
0
    def load_saved_test(self):
        """Load a saved test by test_id."""
        try:
            fd = gzip.open(os.path.join(
                save_dir, os.path.basename(self.test_id)
            ))
            mtime = os.fstat(fd.fileno()).st_mtime
        except (OSError, IOError, TypeError, zlib.error):
            self.response_start(
                "404", "Not Found", [
                ("Content-Type", "text/html; charset=%s" % charset), 
                ("Cache-Control", "max-age=600, must-revalidate")
            ])
            # TODO: better error page (through formatter?)
            self.response_body(error_template % 
                "I'm sorry, I can't find that saved response."
            )
            self.response_done([])
            return
        is_saved = mtime > thor.time()
        try:
            state = pickle.load(fd)
        except (pickle.PickleError, EOFError):
            self.response_start(
                "500", "Internal Server Error", [
                ("Content-Type", "text/html; charset=%s" % charset), 
                ("Cache-Control", "max-age=600, must-revalidate")
            ])
            # TODO: better error page (through formatter?)
            self.response_body(error_template % 
                "I'm sorry, I had a problem reading that response."
            )
            self.response_done([])
            return
        finally:
            fd.close()
            
        formatter = find_formatter(self.format, 'html', self.descend)(
            self.base_uri, state.request.uri, state.orig_req_hdrs, lang,
            self.output, allow_save=(not is_saved), is_saved=True,
            test_id=self.test_id
        )
        self.response_start(
            "200", "OK", [
            ("Content-Type", "%s; charset=%s" % (
                formatter.media_type, charset)), 
            ("Cache-Control", "max-age=3600, must-revalidate")
        ])
        if self.check_type:
        # TODO: catch errors
            state = state.subreqs.get(self.check_type, None)

        formatter.start_output()
        formatter.set_state(state)
        formatter.finish_output()
        self.response_done([])
Пример #31
0
 def _response_start(self, status, phrase, res_headers):
     "Process the response start-line and headers."
     self._st.append('_response_start(%s, %s)' % (status, phrase))
     self.response.start_time = thor.time()
     self.response.version = self.exchange.res_version
     self.response.status_code = status.decode('iso-8859-1', 'replace')
     self.response.status_phrase = phrase.decode('iso-8859-1', 'replace')
     self.response.set_headers(res_headers)
     StatusChecker(self.response, self.request)
     checkCaching(self.response, self.request)
Пример #32
0
    def load_saved_test(self):
        """Load a saved test by test_id."""
        try:
            fd = gzip.open(
                os.path.join(save_dir, os.path.basename(self.test_id)))
            mtime = os.fstat(fd.fileno()).st_mtime
        except (OSError, IOError, TypeError, zlib.error):
            self.response_start(
                "404", "Not Found",
                [("Content-Type", "text/html; charset=%s" % charset),
                 ("Cache-Control", "max-age=600, must-revalidate")])
            # TODO: better error page (through formatter?)
            self.response_body(error_template %
                               "I'm sorry, I can't find that saved response.")
            self.response_done([])
            return
        is_saved = mtime > thor.time()
        try:
            state = pickle.load(fd)
        except (pickle.PickleError, EOFError):
            self.response_start(
                "500", "Internal Server Error",
                [("Content-Type", "text/html; charset=%s" % charset),
                 ("Cache-Control", "max-age=600, must-revalidate")])
            # TODO: better error page (through formatter?)
            self.response_body(
                error_template %
                "I'm sorry, I had a problem reading that response.")
            self.response_done([])
            return
        finally:
            fd.close()

        formatter = find_formatter(self.format, 'html',
                                   self.descend)(self.base_uri,
                                                 state.uri,
                                                 state.orig_req_hdrs,
                                                 lang,
                                                 self.output,
                                                 allow_save=(not is_saved),
                                                 is_saved=True,
                                                 test_id=self.test_id)
        self.response_start(
            "200", "OK", [("Content-Type", "%s; charset=%s" %
                           (formatter.media_type, charset)),
                          ("Cache-Control", "max-age=3600, must-revalidate")])
        if self.req_type:
            # TODO: catch errors
            state = state.subreqs.get(self.req_type, None)

        formatter.start_output()
        formatter.set_red(state)
        formatter.finish_output()
        self.response_done([])
Пример #33
0
 def verify_human(self, human_time: int, human_hmac: str) -> bool:
     """
     Check the user's human HMAC.
     """
     computed_hmac = hmac.new(self.secret, bytes(str(human_time), "ascii"),
                              "sha512")
     is_valid = human_hmac == computed_hmac.hexdigest()
     if is_valid and human_time >= thor.time():
         return True
     else:
         return False
Пример #34
0
 def _response_done(self, trailers):
     "Finish analysing the response, handling any parse errors."
     self._st.append('_response_done()')
     self.response.complete_time = thor.time()
     self.response.transfer_length = self.exchange.input_transfer_length
     self.response.header_length = self.exchange.input_header_length
     self.response.body_done(True, trailers)
     if self.status_cb and self.name:
         self.status_cb("fetched %s (%s)" % (
             self.request.uri, self.name
         ))
     self.done()
     self.finish_task()
Пример #35
0
 def _response_start(self, status, phrase, res_headers):
     "Process the response start-line and headers."
     state = self.state
     state.res_ts = thor.time()
     state.res_version = self.exchange.res_version
     state.res_status = status.decode('iso-8859-1', 'replace')
     state.res_phrase = phrase.decode('iso-8859-1', 'replace')
     state.res_hdrs = res_headers
     redbot.headers.process_headers(state)
     redbot.status_check.ResponseStatusChecker(state)
     state.res_body_enc = state.parsed_hdrs.get(
         'content-type', (None, {})
     )[1].get('charset', 'utf-8') # default isn't UTF-8, but oh well
Пример #36
0
 def robot(results: Tuple[str, bool]) -> None:
     url, robot_ok = results
     if robot_ok:
         self.continue_test(top_resource, formatter)
     else:
         valid_till = str(int(thor.time()) + 60)
         robot_hmac = hmac.new(self._robot_secret, bytes(valid_till, 'ascii'))
         self.response_start(b"403", b"Forbidden", [
             (b"Content-Type", formatter.content_type()),
             (b"Cache-Control", b"no-cache")])
         formatter.start_output()
         formatter.error_output("This site doesn't allow robots. If you are human, please <a href='?uri=%s&robot_time=%s&robot_hmac=%s'>click here</a>." % (self.test_uri, valid_till, robot_hmac.hexdigest()) )
         self.response_done([])
Пример #37
0
 def _response_start(self, status, phrase, res_headers):
     "Process the response start-line and headers."
     state = self.state
     state.res_ts = thor.time()
     state.res_version = self.exchange.res_version
     state.res_status = status.decode('iso-8859-1', 'replace')
     state.res_phrase = phrase.decode('iso-8859-1', 'replace')
     state.res_hdrs = res_headers
     redbot.headers.process_headers(state)
     redbot.status_check.ResponseStatusChecker(state)
     state.res_body_enc = state.parsed_hdrs.get(
         'content-type',
         (None, {}))[1].get('charset',
                            'utf-8')  # default isn't UTF-8, but oh well
Пример #38
0
    def _response_error(self, error):
        state = self.state
        state.res_done_ts = thor.time()
        state.res_error = error
        if isinstance(error, httperr.BodyForbiddenError):
            state.set_message('header-none', rs.BODY_NOT_ALLOWED)
#        elif isinstance(error, httperr.ExtraDataErr):
#            state.res_body_len += len(err.get('detail', ''))
        elif isinstance(error, httperr.ChunkError):
            err_msg = error.detail[:20] or ""
            state.set_message('header-transfer-encoding', rs.BAD_CHUNK,
                chunk_sample=e(err_msg.encode('string_escape'))
            )
        self.done()
        self.finish_task()
Пример #39
0
    def _response_error(self, error):
        "Handle an error encountered while fetching the response."
        self._st.append('_response_error(%s)' % (str(error)))
        self.response.complete_time = thor.time()
        self.response.http_error = error
        if isinstance(error, httperr.BodyForbiddenError):
            self.add_note('header-none', rs.BODY_NOT_ALLOWED)
#        elif isinstance(error, httperr.ExtraDataErr):
#            res.payload_len += len(err.get('detail', ''))
        elif isinstance(error, httperr.ChunkError):
            err_msg = error.detail[:20] or ""
            self.add_note('header-transfer-encoding', rs.BAD_CHUNK,
                chunk_sample=err_msg.encode('string_escape')
            )
        self.done()
        self.finish_task()
Пример #40
0
    def _response_error(self, error):
        state = self.state
        state.res_done_ts = thor.time()
        state.res_error = error
        if isinstance(error, httperr.BodyForbiddenError):
            state.set_message('header-none', rs.BODY_NOT_ALLOWED)


#        elif isinstance(error, httperr.ExtraDataErr):
#            state.res_body_len += len(err.get('detail', ''))
        elif isinstance(error, httperr.ChunkError):
            err_msg = error.detail[:20] or ""
            state.set_message('header-transfer-encoding',
                              rs.BAD_CHUNK,
                              chunk_sample=e(err_msg.encode('string_escape')))
        self.done()
        self.finish_task()
Пример #41
0
 def issue_human(self) -> RawHeaderListType:
     """
     Return cookie headers for later verification that this is a human.
     """
     human_time = str(int(thor.time()) + self.token_lifetime)
     human_hmac = hmac.new(self.secret, bytes(human_time, "ascii"),
                           "sha512").hexdigest()
     return [
         (
             b"Set-Cookie",
             f"human_time={human_time}; Max-Age={self.token_lifetime}; SameSite=Strict"
             .encode("ascii"),
         ),
         (
             b"Set-Cookie",
             f"human_hmac={human_hmac}; Max-Age={self.token_lifetime}; SameSite=Strict"
             .encode("ascii"),
         ),
     ]
Пример #42
0
def verify_slack_secret(webui: "RedWebUi") -> bool:
    """Verify the slack secret."""
    slack_signing_secret = webui.config.get("slack_signing_secret",
                                            fallback="").encode("utf-8")
    timestamp = get_header(webui.req_headers, b"x-slack-request-timestamp")
    if not timestamp or not timestamp[0].isdigit():
        return False
    timestamp = timestamp[0]
    if abs(thor.time() - int(timestamp)) > 60 * 5:
        return False
    sig_basestring = b"v0:" + timestamp + b":" + webui.req_body
    signature = (
        f"v0={hmac.new(slack_signing_secret, sig_basestring, 'sha256').hexdigest()}"
    )
    presented_signature = get_header(webui.req_headers, b"x-slack-signature")
    if not presented_signature:
        return False
    presented_sig = presented_signature[0].decode("utf-8")
    return hmac.compare_digest(signature, presented_sig)
Пример #43
0
    def load_saved_test(self) -> None:
        """Load a saved test by test_id."""
        try:
            fd = gzip.open(os.path.join(self.config.save_dir, os.path.basename(self.test_id)))
            mtime = os.fstat(fd.fileno()).st_mtime
        except (OSError, IOError, TypeError, zlib.error):
            self.response_start(b"404", b"Not Found", [
                (b"Content-Type", b"text/html; charset=%s" % self.charset_bytes),
                (b"Cache-Control", b"max-age=600, must-revalidate")])
            self.response_body(self.show_error("I'm sorry, I can't find that saved response."))
            self.response_done([])
            return
        is_saved = mtime > thor.time()
        try:
            top_resource = pickle.load(fd)
        except (pickle.PickleError, IOError, EOFError):
            self.response_start(b"500", b"Internal Server Error", [
                (b"Content-Type", b"text/html; charset=%s" % self.charset_bytes),
                (b"Cache-Control", b"max-age=600, must-revalidate")])
            self.response_body(self.show_error("I'm sorry, I had a problem loading that."))
            self.response_done([])
            return
        finally:
            fd.close()

        if self.check_name:
            display_resource = top_resource.subreqs.get(self.check_name, top_resource)
        else:
            display_resource = top_resource

        formatter = find_formatter(self.format, 'html', top_resource.descend)(
            self.ui_uri, self.config.lang, self.output,
            allow_save=(not is_saved), is_saved=True, test_id=self.test_id)
        content_type = "%s; charset=%s" % (formatter.media_type, self.config.charset)

        self.response_start(b"200", b"OK", [
            (b"Content-Type", content_type.encode('ascii')),
            (b"Cache-Control", b"max-age=3600, must-revalidate")])
        @thor.events.on(formatter)
        def formatter_done() -> None:
            self.response_done([])
        formatter.bind_resource(display_resource)
Пример #44
0
 def robot(results: Tuple[str, bool]) -> None:
     url, robot_ok = results
     if robot_ok:
         self.continue_test(top_resource, formatter)
     else:
         valid_till = str(int(thor.time()) + 60)
         robot_hmac = hmac.new(self._robot_secret,
                               bytes(valid_till, "ascii"))
         self.response_start(
             b"403",
             b"Forbidden",
             [
                 (b"Content-Type", formatter.content_type()),
                 (b"Cache-Control", b"no-cache"),
             ],
         )
         formatter.start_output()
         formatter.error_output(
             "This site doesn't allow robots. If you are human, please <a href='?uri=%s&robot_time=%s&robot_hmac=%s'>click here</a>."
             % (self.test_uri, valid_till, robot_hmac.hexdigest()))
         self.response_done([])
Пример #45
0
    def check(self) -> None:
        """
        Make an asynchronous HTTP request to uri, emitting 'status' as it's
        updated and 'fetch_done' when it's done. Reason is used to explain what the
        request is in the status callback.
        """
        if not self.preflight() or self.request.uri is None:
            # generally a good sign that we're not going much further.
            self._fetch_done()
            return

        self.fetch_started = True

        if "user-agent" not in [i[0].lower() for i in self.request.headers]:
            self.request.headers.append(("User-Agent", UA_STRING))
        self.exchange = self.client.exchange()
        self.exchange.on("response_nonfinal", self._response_nonfinal)
        self.exchange.once("response_start", self._response_start)
        self.exchange.on("response_body", self._response_body)
        self.exchange.once("response_done", self._response_done)
        self.exchange.on("error", self._response_error)
        self.emit("status",
                  "fetching %s (%s)" % (self.request.uri, self.check_name))
        self.emit("debug",
                  "fetching %s (%s)" % (self.request.uri, self.check_name))
        req_hdrs = [(k.encode("ascii",
                              "replace"), v.encode("ascii", "replace"))
                    for (k, v) in self.request.headers]
        self.exchange.request_start(
            self.request.method.encode("ascii"),
            self.request.uri.encode("ascii"),
            req_hdrs,
        )
        self.request.start_time = thor.time()
        if not self.fetch_done:  # the request could have immediately failed.
            if self.request.payload is not None:
                self.exchange.request_body(self.request.payload)
                self.transfer_out += len(self.request.payload)
        if not self.fetch_done:  # the request could have immediately failed.
            self.exchange.request_done([])
Пример #46
0
    def run_continue(self, robots_txt):
        """
        Continue after getting the robots file.
        TODO: refactor callback style into events.
        """
        if robots_txt == "": # empty or non-200
            pass
        else:
            checker = RobotFileParser()
            checker.parse(robots_txt.decode('ascii', 'replace').encode('ascii', 'replace').splitlines())
            if not checker.can_fetch(UA_STRING, self.request.uri):
                self.response.http_error = RobotsTxtError()
                self.finish_task()
                return # TODO: show error?

        if 'user-agent' not in [i[0].lower() for i in self.request.headers]:
            self.request.headers.append(
                (u"User-Agent", UA_STRING))
        self.exchange = self.client.exchange()
        self.exchange.on('response_start', self._response_start)
        self.exchange.on('response_body', self._response_body)
        self.exchange.on('response_done', self._response_done)
        self.exchange.on('error', self._response_error)
        if self.status_cb and self.name:
            self.status_cb("fetching %s (%s)" % (
                self.request.uri, self.name
            ))
        req_hdrs = [
            (k.encode('ascii', 'replace'), v.encode('latin-1', 'replace')) \
            for (k, v) in self.request.headers
        ]
        self.exchange.request_start(
            self.request.method, self.request.uri, req_hdrs
        )
        self.request.start_time = thor.time()
        if self.request.payload != None:
            self.exchange.request_body(self.request.payload)
            self.transfer_out += len(self.request.payload)
        self.exchange.request_done([])
Пример #47
0
    def run_continue(self, allowed: bool) -> None:
        """
        Continue after getting the robots file.
        """
        if not allowed:
            self.response.http_error = RobotsTxtError()
            self._fetch_done()
            return

        self.fetch_started = True

        if "user-agent" not in [i[0].lower() for i in self.request.headers]:
            self.request.headers.append(("User-Agent", UA_STRING))
        self.exchange = self.client.exchange()
        self.exchange.on("response_nonfinal", self._response_nonfinal)
        self.exchange.once("response_start", self._response_start)
        self.exchange.on("response_body", self._response_body)
        self.exchange.once("response_done", self._response_done)
        self.exchange.on("error", self._response_error)
        self.emit("status",
                  "fetching %s (%s)" % (self.request.uri, self.check_name))
        self.emit("debug",
                  "fetching %s (%s)" % (self.request.uri, self.check_name))
        req_hdrs = [
            (k.encode("ascii", "replace"), v.encode("ascii", "replace"))
            for (k, v) in self.request.headers
        ]  # FIXME: should complain
        self.exchange.request_start(
            self.request.method.encode("ascii"),
            self.request.uri.encode("ascii"),
            req_hdrs,
        )
        self.request.start_time = thor.time()
        if not self.fetch_done:  # the request could have immediately failed.
            if self.request.payload is not None:
                self.exchange.request_body(self.request.payload)
                self.transfer_out += len(self.request.payload)
        if not self.fetch_done:  # the request could have immediately failed.
            self.exchange.request_done([])
Пример #48
0
    def run_test(self) -> None:
        """Test a URI."""
        # try to initialise stored test results
        if self.config.get("save_dir", "") and os.path.exists(
                self.config["save_dir"]):
            try:
                fd, self.save_path = tempfile.mkstemp(
                    prefix="", dir=self.config["save_dir"])
                self.test_id = os.path.split(self.save_path)[1]
            except (OSError, IOError):
                # Don't try to store it.
                self.test_id = None  # should already be None, but make sure

        top_resource = HttpResource(self.config, descend=self.descend)
        self.timeout = thor.schedule(
            int(self.config["max_runtime"]),
            self.timeoutError,
            top_resource.show_task_map,
        )
        top_resource.set_request(self.test_uri, req_hdrs=self.req_hdrs)
        formatter = find_formatter(self.format, "html", self.descend)(
            self.config,
            self.output,
            allow_save=self.test_id,
            is_saved=False,
            test_id=self.test_id,
            descend=self.descend,
        )

        # referer limiting
        referers = []
        for hdr, value in self.req_hdrs:
            if hdr.lower() == "referer":
                referers.append(value)
        referer_error = None
        if len(referers) > 1:
            referer_error = "Multiple referers not allowed."
        if referers and urlsplit(
                referers[0]).hostname in self.referer_spam_domains:
            referer_error = "Referer not allowed."
        if referer_error:
            self.response_start(
                b"403",
                b"Forbidden",
                [
                    (b"Content-Type", formatter.content_type()),
                    (b"Cache-Control", b"max-age=360, must-revalidate"),
                ],
            )
            formatter.start_output()
            formatter.error_output(referer_error)
            self.response_done([])
            return

        # robot human check
        if self.robot_time and self.robot_time.isdigit() and self.robot_hmac:
            valid_till = int(self.robot_time)
            computed_hmac = hmac.new(self._robot_secret,
                                     bytes(self.robot_time, "ascii"))
            is_valid = self.robot_hmac == computed_hmac.hexdigest()
            if is_valid and valid_till >= thor.time():
                self.continue_test(top_resource, formatter)
                return
            else:
                self.response_start(
                    b"403",
                    b"Forbidden",
                    [
                        (b"Content-Type", formatter.content_type()),
                        (b"Cache-Control", b"max-age=60, must-revalidate"),
                    ],
                )
                formatter.start_output()
                formatter.error_output("Naughty.")
                self.response_done([])
                self.error_log("Naughty robot key.")

        # enforce client limits
        if self.config.getint("limit_client_tests", fallback=0):
            client_id = self.get_client_id()
            if client_id:
                if self._client_counts.get(
                        client_id,
                        0) > self.config.getint("limit_client_tests"):
                    self.response_start(
                        b"429",
                        b"Too Many Requests",
                        [
                            (b"Content-Type", formatter.content_type()),
                            (b"Cache-Control", b"max-age=60, must-revalidate"),
                        ],
                    )
                    formatter.start_output()
                    formatter.error_output(
                        "Your client is over limit. Please try later.")
                    self.response_done([])
                    self.error_log("client over limit: %s" %
                                   client_id.decode("idna"))
                    return
                self._client_counts[client_id] += 1

        # enforce origin limits
        if self.config.getint("limit_origin_tests", fallback=0):
            origin = url_to_origin(self.test_uri)
            if origin:
                if self._origin_counts.get(
                        origin, 0) > self.config.getint("limit_origin_tests"):
                    self.response_start(
                        b"429",
                        b"Too Many Requests",
                        [
                            (b"Content-Type", formatter.content_type()),
                            (b"Cache-Control", b"max-age=60, must-revalidate"),
                        ],
                    )
                    formatter.start_output()
                    formatter.error_output(
                        "Origin is over limit. Please try later.")
                    self.response_done([])
                    self.error_log("origin over limit: %s" % origin)
                    return
                self._origin_counts[origin] += 1

        # check robots.txt
        robot_fetcher = RobotFetcher(self.config)

        @thor.events.on(robot_fetcher)
        def robot(results: Tuple[str, bool]) -> None:
            url, robot_ok = results
            if robot_ok:
                self.continue_test(top_resource, formatter)
            else:
                valid_till = str(int(thor.time()) + 60)
                robot_hmac = hmac.new(self._robot_secret,
                                      bytes(valid_till, "ascii"))
                self.response_start(
                    b"403",
                    b"Forbidden",
                    [
                        (b"Content-Type", formatter.content_type()),
                        (b"Cache-Control", b"no-cache"),
                    ],
                )
                formatter.start_output()
                formatter.error_output(
                    "This site doesn't allow robots. If you are human, please <a href='?uri=%s&robot_time=%s&robot_hmac=%s'>click here</a>."
                    % (self.test_uri, valid_till, robot_hmac.hexdigest()))
                self.response_done([])

        robot_fetcher.check_robots(HttpRequest.iri_to_uri(self.test_uri))
Пример #49
0
 def __init__(self, *args: Any, **kw: Any) -> None:
     Formatter.__init__(self, *args, **kw)
     self.hidden_text = []  # type: List[Tuple[str, str]]
     self.start = thor.time()
Пример #50
0
def load_saved_test(webui: "RedWebUi") -> None:
    """Load a saved test by test_id."""
    try:
        with gzip.open(
                os.path.join(webui.config["save_dir"],
                             os.path.basename(webui.test_id))) as fd:
            mtime = os.fstat(fd.fileno()).st_mtime
            is_saved = mtime > thor.time()
            top_resource = pickle.load(fd)
    except (OSError, TypeError):
        webui.exchange.response_start(
            b"404",
            b"Not Found",
            [
                (b"Content-Type",
                 b"text/html; charset=%s" % webui.charset_bytes),
                (b"Cache-Control", b"max-age=600, must-revalidate"),
            ],
        )
        webui.output("I'm sorry, I can't find that saved response.")
        webui.exchange.response_done([])
        return
    except (pickle.PickleError, zlib.error, EOFError):
        webui.exchange.response_start(
            b"500",
            b"Internal Server Error",
            [
                (b"Content-Type",
                 b"text/html; charset=%s" % webui.charset_bytes),
                (b"Cache-Control", b"max-age=600, must-revalidate"),
            ],
        )
        webui.output("I'm sorry, I had a problem loading that.")
        webui.exchange.response_done([])
        return

    if webui.check_name:
        display_resource = top_resource.subreqs.get(webui.check_name,
                                                    top_resource)
    else:
        display_resource = top_resource

    formatter = find_formatter(webui.format, "html", top_resource.descend)(
        webui.config,
        display_resource,
        webui.output,
        allow_save=(not is_saved),
        is_saved=True,
        test_id=webui.test_id,
    )

    webui.exchange.response_start(
        b"200",
        b"OK",
        [
            (b"Content-Type", formatter.content_type()),
            (b"Cache-Control", b"max-age=3600, must-revalidate"),
        ],
    )

    @thor.events.on(formatter)
    def formatter_done() -> None:
        webui.exchange.response_done([])

    formatter.bind_resource(display_resource)
Пример #51
0
    def run_test(self) -> None:
        """Test a URI."""
        # try to initialise stored test results
        if self.config.get('save_dir', "") and os.path.exists(self.config['save_dir']):
            try:
                fd, self.save_path = tempfile.mkstemp(prefix='', dir=self.config['save_dir'])
                self.test_id = os.path.split(self.save_path)[1]
            except (OSError, IOError):
                # Don't try to store it.
                self.test_id = None # should already be None, but make sure

        top_resource = HttpResource(self.config, descend=self.descend)
        self.timeout = thor.schedule(int(self.config['max_runtime']), self.timeoutError,
                                     top_resource.show_task_map)
        top_resource.set_request(self.test_uri, req_hdrs=self.req_hdrs)
        formatter = find_formatter(self.format, 'html', self.descend)(
            self.config, self.output, allow_save=self.test_id, is_saved=False,
            test_id=self.test_id, descend=self.descend)

        # referer limiting
        referers = []
        for hdr, value in self.req_hdrs:
            if hdr.lower() == 'referer':
                referers.append(value)
        referer_error = None
        if len(referers) > 1:
            referer_error = "Multiple referers not allowed."
        if referers and urlsplit(referers[0]).hostname in self.referer_spam_domains:
            referer_error = "Referer not allowed."
        if referer_error:
            self.response_start(b"403", b"Forbidden", [
                (b"Content-Type", formatter.content_type()),
                (b"Cache-Control", b"max-age=360, must-revalidate")])
            formatter.start_output()
            formatter.error_output(referer_error)
            self.response_done([])
            return

        # robot human check
        if self.robot_time and self.robot_time.isdigit() and self.robot_hmac:
            valid_till = int(self.robot_time)
            computed_hmac = hmac.new(self._robot_secret, bytes(self.robot_time, 'ascii'))
            is_valid = self.robot_hmac == computed_hmac.hexdigest()
            if is_valid and valid_till >= thor.time():
                self.continue_test(top_resource, formatter)
                return
            else:
                self.response_start(b"403", b"Forbidden", [
                    (b"Content-Type", formatter.content_type()),
                    (b"Cache-Control", b"max-age=60, must-revalidate")])
                formatter.start_output()
                formatter.error_output("Naughty.")
                self.response_done([])
                self.error_log("Naughty robot key.")

        # enforce client limits
        if self.config.getint('limit_client_tests', fallback=0):
            client_id = self.get_client_id()
            if client_id:
                if self._client_counts.get(client_id, 0) > \
                  self.config.getint('limit_client_tests'):
                    self.response_start(b"429", b"Too Many Requests", [
                        (b"Content-Type", formatter.content_type()),
                        (b"Cache-Control", b"max-age=60, must-revalidate")])
                    formatter.start_output()
                    formatter.error_output("Your client is over limit. Please try later.")
                    self.response_done([])
                    self.error_log("client over limit: %s" % client_id.decode('idna'))
                    return
                self._client_counts[client_id] += 1

        # enforce origin limits
        if self.config.getint('limit_origin_tests', fallback=0):
            origin = url_to_origin(self.test_uri)
            if origin:
                if self._origin_counts.get(origin, 0) > \
                  self.config.getint('limit_origin_tests'):
                    self.response_start(b"429", b"Too Many Requests", [
                        (b"Content-Type", formatter.content_type()),
                        (b"Cache-Control", b"max-age=60, must-revalidate")])
                    formatter.start_output()
                    formatter.error_output("Origin is over limit. Please try later.")
                    self.response_done([])
                    self.error_log("origin over limit: %s" % origin)
                    return
                self._origin_counts[origin] += 1

        # check robots.txt
        robot_fetcher = RobotFetcher(self.config)
        @thor.events.on(robot_fetcher)
        def robot(results: Tuple[str, bool]) -> None:
            url, robot_ok = results
            if robot_ok:
                self.continue_test(top_resource, formatter)
            else:
                valid_till = str(int(thor.time()) + 60)
                robot_hmac = hmac.new(self._robot_secret, bytes(valid_till, 'ascii'))
                self.response_start(b"403", b"Forbidden", [
                    (b"Content-Type", formatter.content_type()),
                    (b"Cache-Control", b"no-cache")])
                formatter.start_output()
                formatter.error_output("This site doesn't allow robots. If you are human, please <a href='?uri=%s&robot_time=%s&robot_hmac=%s'>click here</a>." % (self.test_uri, valid_till, robot_hmac.hexdigest()) )
                self.response_done([])

        robot_fetcher.check_robots(HttpRequest.iri_to_uri(self.test_uri))
Пример #52
0
 def __init__(self, *args: Any, **kw: Any) -> None:
     Formatter.__init__(self, *args, **kw)
     self.hidden_text = []  # type: List[Tuple[str, str]]
     self.start = thor.time()
Пример #53
0
 def __init__(self, *args, **kw):
     Formatter.__init__(self, *args, **kw)
     self.hidden_text = []
     self.start = thor.time()