2018-12-15 00:08:54 +00:00
# Copyright 2011-2018, Damian Johnson and The Tor Project
2015-11-23 21:13:53 +00:00
# See LICENSE for licensing information
"""
2018-12-15 00:08:54 +00:00
Supports communication with sockets speaking Tor protocols . This
2015-11-23 21:13:53 +00:00
allows us to send messages as basic strings , and receive responses as
: class : ` ~ stem . response . ControlMessage ` instances .
* * This module only consists of low level components , and is not intended for
users . * * See our ` tutorials < . . / tutorials . html > ` _ and ` Control Module
< control . html > ` _ if you ' re new to Stem and looking to get started.
With that aside , these can still be used for raw socket communication with
Tor . . .
: :
import stem
import stem . connection
import stem . socket
if __name__ == ' __main__ ' :
try :
control_socket = stem . socket . ControlPort ( port = 9051 )
stem . connection . authenticate ( control_socket )
except stem . SocketError as exc :
print ' Unable to connect to tor on port 9051: %s ' % exc
sys . exit ( 1 )
except stem . connection . AuthenticationFailure as exc :
print ' Unable to authenticate: %s ' % exc
sys . exit ( 1 )
print " Issuing ' GETINFO version ' query... \\ n "
control_socket . send ( ' GETINFO version ' )
print control_socket . recv ( )
: :
% python example . py
Issuing ' GETINFO version ' query . . .
version = 0.2 .4 .10 - alpha - dev ( git - 8 be6058d8f31e578 )
OK
* * Module Overview : * *
: :
2018-12-15 00:08:54 +00:00
BaseSocket - Thread safe socket .
| - RelaySocket - Socket for a relay ' s ORPort.
| | - send - sends a message to the socket
| + - recv - receives a response from the socket
2015-11-23 21:13:53 +00:00
|
2018-12-15 00:08:54 +00:00
| - ControlSocket - Socket wrapper that speaks the tor control protocol .
| | - ControlPort - Control connection via a port .
| | - ControlSocketFile - Control connection via a local file socket .
| |
| | - send - sends a message to the socket
| + - recv - receives a ControlMessage from the socket
2015-11-23 21:13:53 +00:00
|
| - is_alive - reports if the socket is known to be closed
| - is_localhost - returns if the socket is for the local system or not
2018-12-15 00:08:54 +00:00
| - connection_time - timestamp when socket last connected or disconnected
2015-11-23 21:13:53 +00:00
| - connect - connects a new socket
| - close - shuts down the socket
+ - __enter__ / __exit__ - manages socket connection
send_message - Writes a message to a control socket .
recv_message - Reads a ControlMessage from a control socket .
send_formatting - Performs the formatting expected from sent messages .
"""
from __future__ import absolute_import
import re
import socket
2018-12-15 00:08:54 +00:00
import ssl
2015-11-23 21:13:53 +00:00
import threading
import time
import stem . prereq
import stem . response
import stem . util . str_tools
from stem . util import log
2018-12-15 00:08:54 +00:00
MESSAGE_PREFIX = re . compile ( b ' ^[a-zA-Z0-9] {3} [-+ ] ' )
ERROR_MSG = ' Error while receiving a control message ( %s ): %s '
2015-11-23 21:13:53 +00:00
2018-12-15 00:08:54 +00:00
# lines to limit our trace logging to, you can disable this by setting it to None
2015-11-23 21:13:53 +00:00
2018-12-15 00:08:54 +00:00
TRUNCATE_LOGS = 10
# maximum number of bytes to read at a time from a relay socket
MAX_READ_BUFFER_LEN = 10 * 1024 * 1024
class BaseSocket ( object ) :
"""
Thread safe socket , providing common socket functionality .
2015-11-23 21:13:53 +00:00
"""
def __init__ ( self ) :
self . _socket , self . _socket_file = None , None
self . _is_alive = False
self . _connection_time = 0.0 # time when we last connected or disconnected
# Tracks sending and receiving separately. This should be safe, and doing
# so prevents deadlock where we block writes because we're waiting to read
# a message that isn't coming.
self . _send_lock = threading . RLock ( )
self . _recv_lock = threading . RLock ( )
def is_alive ( self ) :
"""
Checks if the socket is known to be closed . We won ' t be aware if it is
until we either use it or have explicitily shut it down .
In practice a socket derived from a port knows about its disconnection
2018-12-15 00:08:54 +00:00
after failing to receive data , whereas socket file derived connections
know after either sending or receiving data .
2015-11-23 21:13:53 +00:00
This means that to have reliable detection for when we ' re disconnected
you need to continually pull from the socket ( which is part of what the
: class : ` ~ stem . control . BaseController ` does ) .
2018-12-15 00:08:54 +00:00
: returns : * * bool * * that ' s **True** if our socket is connected and **False**
otherwise
2015-11-23 21:13:53 +00:00
"""
return self . _is_alive
def is_localhost ( self ) :
"""
Returns if the connection is for the local system or not .
2018-12-15 00:08:54 +00:00
: returns : * * bool * * that ' s **True** if the connection is for the local host
and * * False * * otherwise
2015-11-23 21:13:53 +00:00
"""
return False
def connection_time ( self ) :
"""
Provides the unix timestamp for when our socket was either connected or
disconnected . That is to say , the time we connected if we ' re currently
connected and the time we disconnected if we ' re not connected.
. . versionadded : : 1.3 .0
: returns : * * float * * for when we last connected or disconnected , zero if
we ' ve never connected
"""
return self . _connection_time
def connect ( self ) :
"""
Connects to a new socket , closing our previous one if we ' re already
attached .
: raises : : class : ` stem . SocketError ` if unable to make a socket
"""
with self . _send_lock :
# Closes the socket if we're currently attached to one. Once we're no
# longer alive it'll be safe to acquire the recv lock because recv()
# calls no longer block (raising SocketClosed instead).
if self . is_alive ( ) :
self . close ( )
with self . _recv_lock :
self . _socket = self . _make_socket ( )
self . _socket_file = self . _socket . makefile ( mode = ' rwb ' )
self . _is_alive = True
self . _connection_time = time . time ( )
# It's possible for this to have a transient failure...
# SocketError: [Errno 4] Interrupted system call
#
# It's safe to retry, so give it another try if it fails.
try :
self . _connect ( )
except stem . SocketError :
self . _connect ( ) # single retry
def close ( self ) :
"""
Shuts down the socket . If it ' s already closed then this is a no-op.
"""
with self . _send_lock :
# Function is idempotent with one exception: we notify _close() if this
# is causing our is_alive() state to change.
is_change = self . is_alive ( )
if self . _socket :
# if we haven't yet established a connection then this raises an error
# socket.error: [Errno 107] Transport endpoint is not connected
try :
self . _socket . shutdown ( socket . SHUT_RDWR )
except socket . error :
pass
# Suppressing unexpected exceptions from close. For instance, if the
# socket's file has already been closed then with python 2.7 that raises
# with...
# error: [Errno 32] Broken pipe
try :
self . _socket . close ( )
except :
pass
if self . _socket_file :
try :
self . _socket_file . close ( )
except :
pass
self . _socket = None
self . _socket_file = None
self . _is_alive = False
self . _connection_time = time . time ( )
if is_change :
self . _close ( )
2018-12-15 00:08:54 +00:00
def _send ( self , message , handler ) :
"""
Send message in a thread safe manner . Handler is expected to be of the form . . .
: :
my_handler ( socket , socket_file , message )
"""
with self . _send_lock :
try :
if not self . is_alive ( ) :
raise stem . SocketClosed ( )
handler ( self . _socket , self . _socket_file , message )
except stem . SocketClosed :
# if send_message raises a SocketClosed then we should properly shut
# everything down
if self . is_alive ( ) :
self . close ( )
raise
def _recv ( self , handler ) :
"""
Receives a message in a thread safe manner . Handler is expected to be of the form . . .
: :
my_handler ( socket , socket_file )
"""
with self . _recv_lock :
try :
# makes a temporary reference to the _socket_file because connect()
# and close() may set or unset it
my_socket , my_socket_file = self . _socket , self . _socket_file
if not my_socket or not my_socket_file :
raise stem . SocketClosed ( )
return handler ( my_socket , my_socket_file )
except stem . SocketClosed :
# If recv_message raises a SocketClosed then we should properly shut
# everything down. However, there's a couple cases where this will
# cause deadlock...
#
# * This SocketClosed was *caused by* a close() call, which is joining
# on our thread.
#
# * A send() call that's currently in flight is about to call close(),
# also attempting to join on us.
#
# To resolve this we make a non-blocking call to acquire the send lock.
# If we get it then great, we can close safely. If not then one of the
# above are in progress and we leave the close to them.
if self . is_alive ( ) :
if self . _send_lock . acquire ( False ) :
self . close ( )
self . _send_lock . release ( )
raise
2015-11-23 21:13:53 +00:00
def _get_send_lock ( self ) :
"""
The send lock is useful to classes that interact with us at a deep level
because it ' s used to lock :func:`stem.socket.ControlSocket.connect` /
2018-12-15 00:08:54 +00:00
: func : ` stem . socket . BaseSocket . close ` , and by extension our
: func : ` stem . socket . BaseSocket . is_alive ` state changes .
2015-11-23 21:13:53 +00:00
: returns : * * threading . RLock * * that governs sending messages to our socket
and state changes
"""
return self . _send_lock
def __enter__ ( self ) :
return self
def __exit__ ( self , exit_type , value , traceback ) :
self . close ( )
def _connect ( self ) :
"""
Connection callback that can be overwritten by subclasses and wrappers .
"""
pass
def _close ( self ) :
"""
Disconnection callback that can be overwritten by subclasses and wrappers .
"""
pass
def _make_socket ( self ) :
"""
Constructs and connects new socket . This is implemented by subclasses .
: returns : * * socket . socket * * for our configuration
: raises :
* : class : ` stem . SocketError ` if unable to make a socket
* * * NotImplementedError * * if not implemented by a subclass
"""
2018-12-15 00:08:54 +00:00
raise NotImplementedError ( ' Unsupported Operation: this should be implemented by the BaseSocket subclass ' )
class RelaySocket ( BaseSocket ) :
"""
` Link - level connection
< https : / / gitweb . torproject . org / torspec . git / tree / tor - spec . txt > ` _ to a Tor
relay .
. . versionadded : : 1.7 .0
: var str address : address our socket connects to
: var int port : ORPort our socket connects to
"""
def __init__ ( self , address = ' 127.0.0.1 ' , port = 9050 , connect = True ) :
"""
RelaySocket constructor .
: param str address : ip address of the relay
: param int port : orport of the relay
: param bool connect : connects to the socket if True , leaves it unconnected otherwise
: raises : : class : ` stem . SocketError ` if connect is * * True * * and we ' re
unable to establish a connection
"""
super ( RelaySocket , self ) . __init__ ( )
self . address = address
self . port = port
if connect :
self . connect ( )
def send ( self , message ) :
"""
Sends a message to the relay ' s ORPort.
: param str message : message to be formatted and sent to the socket
: raises :
* : class : ` stem . SocketError ` if a problem arises in using the socket
* : class : ` stem . SocketClosed ` if the socket is known to be shut down
"""
self . _send ( message , lambda s , sf , msg : _write_to_socket ( sf , msg ) )
def recv ( self ) :
"""
Receives a message from the relay .
: returns : bytes for the message received
: raises :
* : class : ` stem . ProtocolError ` the content from the socket is malformed
* : class : ` stem . SocketClosed ` if the socket closes before we receive a complete message
"""
# TODO: Is MAX_READ_BUFFER_LEN defined in the spec? Not sure where it came
# from.
return self . _recv ( lambda s , sf : s . recv ( MAX_READ_BUFFER_LEN ) )
def is_localhost ( self ) :
return self . address == ' 127.0.0.1 '
def _make_socket ( self ) :
try :
relay_socket = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
relay_socket . connect ( ( self . address , self . port ) )
return ssl . wrap_socket ( relay_socket )
except socket . error as exc :
raise stem . SocketError ( exc )
class ControlSocket ( BaseSocket ) :
"""
Wrapper for a socket connection that speaks the Tor control protocol . To the
better part this transparently handles the formatting for sending and
receiving complete messages .
Callers should not instantiate this class directly , but rather use subclasses
which are expected to implement the * * _make_socket ( ) * * method .
"""
def __init__ ( self ) :
super ( ControlSocket , self ) . __init__ ( )
def send ( self , message ) :
"""
Formats and sends a message to the control socket . For more information see
the : func : ` ~ stem . socket . send_message ` function .
. . deprecated : : 1.7 .0
The * * raw * * argument was unhelpful and be removed . Use
: func : ` stem . socket . send_message ` if you need this level of control
instead .
: param str message : message to be formatted and sent to the socket
: raises :
* : class : ` stem . SocketError ` if a problem arises in using the socket
* : class : ` stem . SocketClosed ` if the socket is known to be shut down
"""
self . _send ( message , lambda s , sf , msg : send_message ( sf , msg ) )
def recv ( self ) :
"""
Receives a message from the control socket , blocking until we ' ve received
one . For more information see the : func : ` ~ stem . socket . recv_message ` function .
: returns : : class : ` ~ stem . response . ControlMessage ` for the message received
: raises :
* : class : ` stem . ProtocolError ` the content from the socket is malformed
* : class : ` stem . SocketClosed ` if the socket closes before we receive a complete message
"""
return self . _recv ( lambda s , sf : recv_message ( sf ) )
2015-11-23 21:13:53 +00:00
class ControlPort ( ControlSocket ) :
"""
Control connection to tor . For more information see tor ' s ControlPort torrc
option .
2018-12-15 00:08:54 +00:00
: var str address : address our socket connects to
: var int port : ControlPort our socket connects to
2015-11-23 21:13:53 +00:00
"""
def __init__ ( self , address = ' 127.0.0.1 ' , port = 9051 , connect = True ) :
"""
ControlPort constructor .
: param str address : ip address of the controller
: param int port : port number of the controller
: param bool connect : connects to the socket if True , leaves it unconnected otherwise
: raises : : class : ` stem . SocketError ` if connect is * * True * * and we ' re
unable to establish a connection
"""
super ( ControlPort , self ) . __init__ ( )
2018-12-15 00:08:54 +00:00
self . address = address
self . port = port
2015-11-23 21:13:53 +00:00
if connect :
self . connect ( )
def get_address ( self ) :
"""
Provides the ip address our socket connects to .
2018-12-15 00:08:54 +00:00
. . deprecated : : 1.7 .0
Use the * * address * * attribute instead .
2015-11-23 21:13:53 +00:00
: returns : str with the ip address of our socket
"""
2018-12-15 00:08:54 +00:00
return self . address
2015-11-23 21:13:53 +00:00
def get_port ( self ) :
"""
Provides the port our socket connects to .
2018-12-15 00:08:54 +00:00
. . deprecated : : 1.7 .0
Use the * * port * * attribute instead .
2015-11-23 21:13:53 +00:00
: returns : int with the port of our socket
"""
2018-12-15 00:08:54 +00:00
return self . port
2015-11-23 21:13:53 +00:00
def is_localhost ( self ) :
2018-12-15 00:08:54 +00:00
return self . address == ' 127.0.0.1 '
2015-11-23 21:13:53 +00:00
def _make_socket ( self ) :
try :
control_socket = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
2018-12-15 00:08:54 +00:00
control_socket . connect ( ( self . address , self . port ) )
2015-11-23 21:13:53 +00:00
return control_socket
except socket . error as exc :
raise stem . SocketError ( exc )
class ControlSocketFile ( ControlSocket ) :
"""
Control connection to tor . For more information see tor ' s ControlSocket torrc
option .
2018-12-15 00:08:54 +00:00
: var str path : filesystem path of the socket we connect to
2015-11-23 21:13:53 +00:00
"""
def __init__ ( self , path = ' /var/run/tor/control ' , connect = True ) :
"""
ControlSocketFile constructor .
: param str socket_path : path where the control socket is located
: param bool connect : connects to the socket if True , leaves it unconnected otherwise
: raises : : class : ` stem . SocketError ` if connect is * * True * * and we ' re
unable to establish a connection
"""
super ( ControlSocketFile , self ) . __init__ ( )
2018-12-15 00:08:54 +00:00
self . path = path
2015-11-23 21:13:53 +00:00
if connect :
self . connect ( )
def get_socket_path ( self ) :
"""
Provides the path our socket connects to .
2018-12-15 00:08:54 +00:00
. . deprecated : : 1.7 .0
Use the * * path * * attribute instead .
2015-11-23 21:13:53 +00:00
: returns : str with the path for our control socket
"""
2018-12-15 00:08:54 +00:00
return self . path
2015-11-23 21:13:53 +00:00
def is_localhost ( self ) :
return True
def _make_socket ( self ) :
try :
control_socket = socket . socket ( socket . AF_UNIX , socket . SOCK_STREAM )
2018-12-15 00:08:54 +00:00
control_socket . connect ( self . path )
2015-11-23 21:13:53 +00:00
return control_socket
except socket . error as exc :
raise stem . SocketError ( exc )
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 )
2018-12-15 00:08:54 +00:00
_write_to_socket ( control_file , message )
2015-11-23 21:13:53 +00:00
2018-12-15 00:08:54 +00:00
if log . is_tracing ( ) :
2015-11-23 21:13:53 +00:00
log_message = message . replace ( ' \r \n ' , ' \n ' ) . rstrip ( )
2018-12-15 00:08:54 +00:00
msg_div = ' \n ' if ' \n ' in log_message else ' '
log . trace ( ' Sent to tor: %s %s ' % ( msg_div , log_message ) )
def _write_to_socket ( socket_file , message ) :
try :
socket_file . write ( stem . util . str_tools . _to_bytes ( message ) )
socket_file . flush ( )
2015-11-23 21:13:53 +00:00
except socket . error as exc :
2018-12-15 00:08:54 +00:00
log . info ( ' Failed to send: %s ' % exc )
2015-11-23 21:13:53 +00:00
# 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'
2018-12-15 00:08:54 +00:00
log . info ( ' Failed to send: file has been closed ' )
2015-11-23 21:13:53 +00:00
raise stem . SocketClosed ( ' file has been closed ' )
def recv_message ( control_file ) :
"""
Pulls from a control socket until we either have a complete message or
encounter a problem .
: param file control_file : file derived from the control socket ( see the
socket ' s makefile() method for more information)
: returns : : class : ` ~ stem . response . ControlMessage ` read from the socket
: raises :
* : class : ` stem . ProtocolError ` the content from the socket is malformed
* : class : ` stem . SocketClosed ` if the socket closes before we receive
a complete message
"""
2018-12-15 00:08:54 +00:00
parsed_content , raw_content , first_line = None , None , True
2015-11-23 21:13:53 +00:00
while True :
try :
2018-12-15 00:08:54 +00:00
line = control_file . readline ( )
2015-11-23 21:13:53 +00:00
except AttributeError :
# if the control_file has been closed then we will receive:
# AttributeError: 'NoneType' object has no attribute 'recv'
2018-12-15 00:08:54 +00:00
log . info ( ERROR_MSG % ( ' SocketClosed ' , ' socket file has been closed ' ) )
2015-11-23 21:13:53 +00:00
raise stem . SocketClosed ( ' socket file has been closed ' )
except ( socket . error , ValueError ) as exc :
# When disconnected we get...
#
# Python 2:
# socket.error: [Errno 107] Transport endpoint is not connected
#
# Python 3:
# ValueError: I/O operation on closed file.
2018-12-15 00:08:54 +00:00
log . info ( ERROR_MSG % ( ' SocketClosed ' , ' received exception " %s " ' % exc ) )
2015-11-23 21:13:53 +00:00
raise stem . SocketClosed ( exc )
# Parses the tor control lines. These are of the form...
# <status code><divider><content>\r\n
2018-12-15 00:08:54 +00:00
if not line :
2015-11-23 21:13:53 +00:00
# if the socket is disconnected then the readline() method will provide
# empty content
2018-12-15 00:08:54 +00:00
log . info ( ERROR_MSG % ( ' SocketClosed ' , ' empty socket content ' ) )
2015-11-23 21:13:53 +00:00
raise stem . SocketClosed ( ' Received empty socket content. ' )
2018-12-15 00:08:54 +00:00
elif not MESSAGE_PREFIX . match ( line ) :
log . info ( ERROR_MSG % ( ' ProtocolError ' , ' malformed status code/divider, " %s " ' % log . escape ( line ) ) )
2015-11-23 21:13:53 +00:00
raise stem . ProtocolError ( ' Badly formatted reply line: beginning is malformed ' )
elif not line . endswith ( b ' \r \n ' ) :
2018-12-15 00:08:54 +00:00
log . info ( ERROR_MSG % ( ' ProtocolError ' , ' no CRLF linebreak, " %s " ' % log . escape ( line ) ) )
2015-11-23 21:13:53 +00:00
raise stem . ProtocolError ( ' All lines should end with CRLF ' )
2018-12-15 00:08:54 +00:00
status_code , divider , content = line [ : 3 ] , line [ 3 : 4 ] , line [ 4 : - 2 ] # strip CRLF off content
2015-11-23 21:13:53 +00:00
if stem . prereq . is_python_3 ( ) :
status_code = stem . util . str_tools . _to_unicode ( status_code )
divider = stem . util . str_tools . _to_unicode ( divider )
2018-12-15 00:08:54 +00:00
# Most controller responses are single lines, in which case we don't need
# so much overhead.
if first_line :
if divider == ' ' :
_log_trace ( line )
return stem . response . ControlMessage ( [ ( status_code , divider , content ) ] , line )
else :
parsed_content , raw_content , first_line = [ ] , bytearray ( ) , False
raw_content + = line
2015-11-23 21:13:53 +00:00
if divider == ' - ' :
# mid-reply line, keep pulling for more content
parsed_content . append ( ( status_code , divider , content ) )
elif divider == ' ' :
# end of the message, return the message
parsed_content . append ( ( status_code , divider , content ) )
2018-12-15 00:08:54 +00:00
_log_trace ( bytes ( raw_content ) )
return stem . response . ControlMessage ( parsed_content , bytes ( raw_content ) )
2015-11-23 21:13:53 +00:00
elif divider == ' + ' :
# data entry, all of the following lines belong to the content until we
# get a line with just a period
2018-12-15 00:08:54 +00:00
content_block = bytearray ( content )
2015-11-23 21:13:53 +00:00
while True :
try :
2018-12-15 00:08:54 +00:00
line = control_file . readline ( )
raw_content + = line
2015-11-23 21:13:53 +00:00
except socket . error as exc :
2018-12-15 00:08:54 +00:00
log . info ( ERROR_MSG % ( ' SocketClosed ' , ' received an exception while mid-way through a data reply (exception: " %s " , read content: " %s " ) ' % ( exc , log . escape ( bytes ( raw_content ) ) ) ) )
2015-11-23 21:13:53 +00:00
raise stem . SocketClosed ( exc )
if not line . endswith ( b ' \r \n ' ) :
2018-12-15 00:08:54 +00:00
log . info ( ERROR_MSG % ( ' ProtocolError ' , ' CRLF linebreaks missing from a data reply, " %s " ' % log . escape ( bytes ( raw_content ) ) ) )
2015-11-23 21:13:53 +00:00
raise stem . ProtocolError ( ' All lines should end with CRLF ' )
elif line == b ' . \r \n ' :
break # data block termination
line = line [ : - 2 ] # strips off the CRLF
# lines starting with a period are escaped by a second period (as per
# section 2.4 of the control-spec)
if line . startswith ( b ' .. ' ) :
line = line [ 1 : ]
2018-12-15 00:08:54 +00:00
content_block + = b ' \n ' + line
2015-11-23 21:13:53 +00:00
2018-12-15 00:08:54 +00:00
# joins the content using a newline rather than CRLF separator (more
# conventional for multi-line string content outside the windows world)
2015-11-23 21:13:53 +00:00
2018-12-15 00:08:54 +00:00
parsed_content . append ( ( status_code , divider , bytes ( content_block ) ) )
2015-11-23 21:13:53 +00:00
else :
# this should never be reached due to the prefix regex, but might as well
# be safe...
2018-12-15 00:08:54 +00:00
log . warn ( ERROR_MSG % ( ' ProtocolError ' , " \" %s \" isn ' t a recognized divider type " % divider ) )
2015-11-23 21:13:53 +00:00
raise stem . ProtocolError ( " Unrecognized divider type ' %s ' : %s " % ( divider , stem . util . str_tools . _to_unicode ( line ) ) )
def send_formatting ( message ) :
"""
Performs the formatting expected from sent control messages . For more
information see the : func : ` ~ stem . socket . send_message ` function .
: param str message : message to be formatted
: returns : * * str * * of the message wrapped by the formatting expected from
controllers
"""
# From control-spec section 2.2...
# Command = Keyword OptArguments CRLF / "+" Keyword OptArguments CRLF CmdData
# Keyword = 1*ALPHA
# OptArguments = [ SP *(SP / VCHAR) ]
#
# A command is either a single line containing a Keyword and arguments, or a
# multiline command whose initial keyword begins with +, and whose data
# section ends with a single "." on a line of its own.
# if we already have \r\n entries then standardize on \n to start with
message = message . replace ( ' \r \n ' , ' \n ' )
if ' \n ' in message :
return ' + %s \r \n . \r \n ' % message . replace ( ' \n ' , ' \r \n ' )
else :
return message + ' \r \n '
2018-12-15 00:08:54 +00:00
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 ' ) )