Ejemplo n.º 1
0
    def test_is_tracing(self):
        logger = log.get_logger()
        original_handlers = logger.handlers
        logger.handlers = [log._NullHandler()]

        try:
            self.assertFalse(log.is_tracing())
            logger.addHandler(log.LogBuffer(log.DEBUG))
            self.assertFalse(log.is_tracing())
            logger.addHandler(log.LogBuffer(log.TRACE))
            self.assertTrue(log.is_tracing())
        finally:
            logger.handlers = original_handlers
Ejemplo n.º 2
0
def send_message(control_file, message, raw=False):
    """
  Sends a message to the control socket, adding the expected formatting for
  single verses multi-line messages. Neither message type should contain an
  ending newline (if so it'll be treated as a multi-line message with a blank
  line at the end). If the message doesn't contain a newline then it's sent
  as...

  ::

    <message>\\r\\n

  and if it does contain newlines then it's split on ``\\n`` and sent as...

  ::

    +<line 1>\\r\\n
    <line 2>\\r\\n
    <line 3>\\r\\n
    .\\r\\n

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)
  :param str message: message to be sent on the control socket
  :param bool raw: leaves the message formatting untouched, passing it to the
    socket as-is

  :raises:
    * :class:`stem.SocketError` if a problem arises in using the socket
    * :class:`stem.SocketClosed` if the socket is known to be shut down
  """

    if not raw:
        message = send_formatting(message)

    try:
        control_file.write(stem.util.str_tools._to_bytes(message))
        control_file.flush()

        if log.is_tracing():
            log_message = message.replace('\r\n', '\n').rstrip()
            msg_div = '\n' if '\n' in log_message else ' '
            log.trace('Sent to tor:%s%s' % (msg_div, log_message))
    except socket.error as exc:
        log.info('Failed to send message: %s' % exc)

        # When sending there doesn't seem to be a reliable method for
        # distinguishing between failures from a disconnect verses other things.
        # Just accounting for known disconnection responses.

        if str(exc) == '[Errno 32] Broken pipe':
            raise stem.SocketClosed(exc)
        else:
            raise stem.SocketError(exc)
    except AttributeError:
        # if the control_file has been closed then flush will receive:
        # AttributeError: 'NoneType' object has no attribute 'sendall'

        log.info('Failed to send message: file has been closed')
        raise stem.SocketClosed('file has been closed')
Ejemplo n.º 3
0
def _log_trace(response):
  if not log.is_tracing():
    return

  log_message = stem.util.str_tools._to_unicode(response.replace(b'\r\n', b'\n').rstrip())
  log_message_lines = log_message.split('\n')

  if TRUNCATE_LOGS and len(log_message_lines) > TRUNCATE_LOGS:
    log_message = '\n'.join(log_message_lines[:TRUNCATE_LOGS] + ['... %i more lines...' % (len(log_message_lines) - TRUNCATE_LOGS)])

  if len(log_message_lines) > 2:
    log.trace('Received from tor:\n%s' % log_message)
  else:
    log.trace('Received from tor: %s' % log_message.replace('\n', '\\n'))
Ejemplo n.º 4
0
async def send_message(writer: asyncio.StreamWriter,
                       message: Union[bytes, str],
                       raw: bool = False) -> None:
    """
  Sends a message to the control socket, adding the expected formatting for
  single verses multi-line messages. Neither message type should contain an
  ending newline (if so it'll be treated as a multi-line message with a blank
  line at the end). If the message doesn't contain a newline then it's sent
  as...

  ::

    <message>\\r\\n

  and if it does contain newlines then it's split on ``\\n`` and sent as...

  ::

    +<line 1>\\r\\n
    <line 2>\\r\\n
    <line 3>\\r\\n
    .\\r\\n

  :param writer: writer object
  :param message: message to be sent on the control socket
  :param raw: leaves the message formatting untouched, passing it to the
    socket as-is

  :raises:
    * :class:`stem.SocketError` if a problem arises in using the socket
    * :class:`stem.SocketClosed` if the socket is known to be shut down
  """

    message = stem.util.str_tools._to_unicode(message)

    if not raw:
        message = send_formatting(message)

    await _write_to_socket(writer, message)

    if log.is_tracing():
        log_message = message.replace('\r\n', '\n').rstrip()
        msg_div = '\n' if '\n' in log_message else ' '
        log.trace('Sent to tor:%s%s' % (msg_div, log_message))
Ejemplo n.º 5
0
def send_message(control_file, message, raw = False):
  """
  Sends a message to the control socket, adding the expected formatting for
  single verses multi-line messages. Neither message type should contain an
  ending newline (if so it'll be treated as a multi-line message with a blank
  line at the end). If the message doesn't contain a newline then it's sent
  as...

  ::

    <message>\\r\\n

  and if it does contain newlines then it's split on ``\\n`` and sent as...

  ::

    +<line 1>\\r\\n
    <line 2>\\r\\n
    <line 3>\\r\\n
    .\\r\\n

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)
  :param str message: message to be sent on the control socket
  :param bool raw: leaves the message formatting untouched, passing it to the
    socket as-is

  :raises:
    * :class:`stem.SocketError` if a problem arises in using the socket
    * :class:`stem.SocketClosed` if the socket is known to be shut down
  """

  if not raw:
    message = send_formatting(message)

  _write_to_socket(control_file, message)

  if log.is_tracing():
    log_message = message.replace('\r\n', '\n').rstrip()
    msg_div = '\n' if '\n' in log_message else ' '
    log.trace('Sent to tor:%s%s' % (msg_div, log_message))
Ejemplo n.º 6
0
def call(command, default = UNDEFINED, ignore_exit_status = False, timeout = None, cwd = None, env = None):
  """
  call(command, default = UNDEFINED, ignore_exit_status = False)

  Issues a command in a subprocess, blocking until completion and returning the
  results. This is not actually ran in a shell so pipes and other shell syntax
  are not permitted.

  .. versionchanged:: 1.5.0
     Providing additional information upon failure by raising a CallError. This
     is a subclass of OSError, providing backward compatibility.

  .. versionchanged:: 1.5.0
     Added env argument.

  .. versionchanged:: 1.6.0
     Added timeout and cwd arguments.

  :param str,list command: command to be issued
  :param object default: response if the query fails
  :param bool ignore_exit_status: reports failure if our command's exit status
    was non-zero
  :param float timeout: maximum seconds to wait, blocks indefinitely if
    **None**
  :param dict env: environment variables

  :returns: **list** with the lines of output from the command

  :raises:
    * **CallError** if this fails and no default was provided
    * **CallTimeoutError** if the timeout is reached without a default
  """

  # TODO: in stem 2.x return a struct with stdout, stderr, and runtime instead

  global SYSTEM_CALL_TIME

  if isinstance(command, str):
    command_list = command.split(' ')
  else:
    command_list = list(map(str, command))

  exit_status, runtime, stdout, stderr = None, None, None, None
  start_time = time.time()

  try:
    is_shell_command = command_list[0] in SHELL_COMMANDS

    process = subprocess.Popen(command_list, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = is_shell_command, cwd = cwd, env = env)

    if timeout:
      while process.poll() is None:
        if time.time() - start_time > timeout:
          raise CallTimeoutError("Process didn't finish after %0.1f seconds" % timeout, ' '.join(command_list), None, timeout, '', '', timeout)

        time.sleep(0.001)

    stdout, stderr = process.communicate()
    stdout, stderr = stdout.strip(), stderr.strip()
    runtime = time.time() - start_time

    log.debug('System call: %s (runtime: %0.2f)' % (command, runtime))

    if log.is_tracing():
      trace_prefix = 'Received from system (%s)' % command

      if stdout and stderr:
        log.trace(trace_prefix + ', stdout:\n%s\nstderr:\n%s' % (stdout, stderr))
      elif stdout:
        log.trace(trace_prefix + ', stdout:\n%s' % stdout)
      elif stderr:
        log.trace(trace_prefix + ', stderr:\n%s' % stderr)

    exit_status = process.poll()

    if not ignore_exit_status and exit_status != 0:
      raise OSError('%s returned exit status %i' % (command, exit_status))

    if stdout:
      return stdout.decode('utf-8', 'replace').splitlines()
    else:
      return []
  except CallTimeoutError:
    log.debug('System call (timeout): %s (after %0.4fs)' % (command, timeout))

    if default != UNDEFINED:
      return default
    else:
      raise
  except OSError as exc:
    log.debug('System call (failed): %s (error: %s)' % (command, exc))

    if default != UNDEFINED:
      return default
    else:
      raise CallError(str(exc), ' '.join(command_list), exit_status, runtime, stdout, stderr)
  finally:
    with SYSTEM_CALL_TIME_LOCK:
      SYSTEM_CALL_TIME += time.time() - start_time