Package ndg :: Package httpsclient :: Module ssl_socket
[hide private]

Source Code for Module ndg.httpsclient.ssl_socket

  1  """PyOpenSSL utilities including HTTPSSocket class which wraps PyOpenSSL 
  2  SSL connection into a httplib-like interface suitable for use with urllib2 
  3   
  4  """ 
  5  __author__ = "P J Kershaw" 
  6  __date__ = "21/12/10" 
  7  __copyright__ = "(C) 2012 Science and Technology Facilities Council" 
  8  __license__ = "BSD - see LICENSE file in top-level directory" 
  9  __contact__ = "Philip.Kershaw@stfc.ac.uk" 
 10  __revision__ = '$Id$' 
 11   
 12  from datetime import datetime 
 13  import logging 
 14  import socket 
 15  from cStringIO import StringIO 
 16   
 17  from OpenSSL import SSL 
 18   
 19  log = logging.getLogger(__name__) 
20 21 22 -class SSLSocket(object):
23 """SSL Socket class wraps pyOpenSSL's SSL.Connection class implementing 24 the makefile method so that it is compatible with the standard socket 25 interface and usable with httplib. 26 27 @cvar default_buf_size: default buffer size for recv operations in the 28 makefile method 29 @type default_buf_size: int 30 """ 31 default_buf_size = 8192 32
33 - def __init__(self, ctx, sock=None):
34 """Create SSL socket object 35 36 @param ctx: SSL context 37 @type ctx: OpenSSL.SSL.Context 38 @param sock: underlying socket object 39 @type sock: socket.socket 40 """ 41 if sock is not None: 42 self.socket = sock 43 else: 44 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 45 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 46 47 self.__ssl_conn = SSL.Connection(ctx, self.socket) 48 self.buf_size = self.__class__.default_buf_size 49 self._makefile_refs = 0
50
51 - def __del__(self):
52 """Close underlying socket when this object goes out of scope 53 """ 54 self.close()
55 56 @property
57 - def buf_size(self):
58 """Buffer size for makefile method recv() operations""" 59 return self.__buf_size
60 61 @buf_size.setter
62 - def buf_size(self, value):
63 """Buffer size for makefile method recv() operations""" 64 if not isinstance(value, (int, long)): 65 raise TypeError('Expecting int or long type for "buf_size"; ' 66 'got %r instead' % type(value)) 67 self.__buf_size = value
68
69 - def close(self):
70 """Shutdown the SSL connection and call the close method of the 71 underlying socket""" 72 # try: 73 # self.__ssl_conn.shutdown() 74 # except SSL.Error: 75 # # Make errors on shutdown non-fatal 76 # pass 77 78 if self._makefile_refs < 1: 79 self.__ssl_conn.shutdown() 80 else: 81 self._makefile_refs -= 1
82
83 - def set_shutdown(self, mode):
84 """Set the shutdown state of the Connection. 85 @param mode: bit vector of either or both of SENT_SHUTDOWN and 86 RECEIVED_SHUTDOWN 87 """ 88 self.__ssl_conn.set_shutdown(mode)
89
90 - def get_shutdown(self):
91 """Get the shutdown state of the Connection. 92 @return: bit vector of either or both of SENT_SHUTDOWN and 93 RECEIVED_SHUTDOWN 94 """ 95 return self.__ssl_conn.get_shutdown()
96
97 - def bind(self, addr):
98 """bind to the given address - calls method of the underlying socket 99 @param addr: address/port number tuple 100 @type addr: tuple""" 101 self.__ssl_conn.bind(addr)
102
103 - def listen(self, backlog):
104 """Listen for connections made to the socket. 105 106 @param backlog: specifies the maximum number of queued connections and 107 should be at least 1; the maximum value is system-dependent (usually 5). 108 @param backlog: int 109 """ 110 self.__ssl_conn.listen(backlog)
111
112 - def set_accept_state(self):
113 """Set the connection to work in server mode. The handshake will be 114 handled automatically by read/write""" 115 self.__ssl_conn.set_accept_state()
116
117 - def accept(self):
118 """Accept an SSL connection. 119 120 @return: pair (ssl, addr) where ssl is a new SSL connection object and 121 addr is the address bound to the other end of the SSL connection. 122 @rtype: tuple 123 """ 124 return self.__ssl_conn.accept()
125
126 - def set_connect_state(self):
127 """Set the connection to work in client mode. The handshake will be 128 handled automatically by read/write""" 129 self.__ssl_conn.set_connect_state()
130
131 - def connect(self, addr):
132 """Call the connect method of the underlying socket and set up SSL on 133 the socket, using the Context object supplied to this Connection object 134 at creation. 135 136 @param addr: address/port number pair 137 @type addr: tuple 138 """ 139 self.__ssl_conn.connect(addr)
140
141 - def shutdown(self, how):
142 """Send the shutdown message to the Connection. 143 144 @param how: for socket.socket this flag determines whether read, write 145 or both type operations are supported. OpenSSL.SSL.Connection doesn't 146 support this so this parameter is IGNORED 147 @return: true if the shutdown message exchange is completed and false 148 otherwise (in which case you call recv() or send() when the connection 149 becomes readable/writeable. 150 @rtype: bool 151 """ 152 return self.__ssl_conn.shutdown()
153
154 - def renegotiate(self):
155 """Renegotiate this connection's SSL parameters.""" 156 return self.__ssl_conn.renegotiate()
157
158 - def pending(self):
159 """@return: numbers of bytes that can be safely read from the SSL 160 buffer. 161 @rtype: int 162 """ 163 return self.__ssl_conn.pending()
164
165 - def send(self, data, *flags_arg):
166 """Send data to the socket. Nb. The optional flags argument is ignored. 167 - retained for compatibility with socket.socket interface 168 169 @param data: data to send down the socket 170 @type data: string 171 """ 172 return self.__ssl_conn.send(data)
173
174 - def sendall(self, data):
175 self.__ssl_conn.sendall(data)
176
177 - def recv(self, size=default_buf_size):
178 """Receive data from the Connection. 179 180 @param size: The maximum amount of data to be received at once 181 @type size: int 182 @return: data received. 183 @rtype: string 184 """ 185 return self.__ssl_conn.recv(size)
186
187 - def setblocking(self, mode):
188 """Set this connection's underlying socket blocking _mode_. 189 190 @param mode: blocking mode 191 @type mode: int 192 """ 193 self.__ssl_conn.setblocking(mode)
194
195 - def fileno(self):
196 """ 197 @return: file descriptor number for the underlying socket 198 @rtype: int 199 """ 200 return self.__ssl_conn.fileno()
201
202 - def getsockopt(self, *args):
203 """See socket.socket.getsockopt 204 """ 205 return self.__ssl_conn.getsockopt(*args)
206
207 - def setsockopt(self, *args):
208 """See socket.socket.setsockopt 209 210 @return: value of the given socket option 211 @rtype: int/string 212 """ 213 return self.__ssl_conn.setsockopt(*args)
214
215 - def state_string(self):
216 """Return the SSL state of this connection.""" 217 return self.__ssl_conn.state_string()
218
219 - def makefile(self, *args):
220 """Specific to Python socket API and required by httplib: convert 221 response into a file-like object. This implementation reads using recv 222 and copies the output into a StringIO buffer to simulate a file object 223 for consumption by httplib 224 225 Nb. Ignoring optional file open mode (StringIO is generic and will 226 open for read and write unless a string is passed to the constructor) 227 and buffer size - httplib set a zero buffer size which results in recv 228 reading nothing 229 230 @return: file object for data returned from socket 231 @rtype: cStringIO.StringO 232 """ 233 self._makefile_refs += 1 234 235 # Optimisation 236 _buf_size = self.buf_size 237 238 i=0 239 stream = StringIO() 240 startTime = datetime.utcnow() 241 try: 242 dat = self.__ssl_conn.recv(_buf_size) 243 while dat: 244 i+=1 245 stream.write(dat) 246 dat = self.__ssl_conn.recv(_buf_size) 247 248 except (SSL.ZeroReturnError, SSL.SysCallError): 249 # Connection is closed - assuming here that all is well and full 250 # response has been received. httplib will catch an error in 251 # incomplete content since it checks the content-length header 252 # against the actual length of data received 253 pass 254 255 if log.getEffectiveLevel() <= logging.DEBUG: 256 log.debug("Socket.makefile %d recv calls completed in %s", i, 257 datetime.utcnow() - startTime) 258 259 # Make sure to rewind the buffer otherwise consumers of the content will 260 # read from the end of the buffer 261 stream.seek(0) 262 263 return stream
264 265 # def makefile(self, mode='r', bufsize=-1): 266 # 267 # """Make and return a file-like object that 268 # works with the SSL connection. Just use the code 269 # from the socket module.""" 270 # 271 # self._makefile_refs += 1 272 # # close=True so as to decrement the reference count when done with 273 # # the file-like object. 274 # return socket._fileobject(self.socket, mode, bufsize, close=True) 275
276 - def getsockname(self):
277 """ 278 @return: the socket's own address 279 @rtype: 280 """ 281 return self.__ssl_conn.getsockname()
282
283 - def getpeername(self):
284 """ 285 @return: remote address to which the socket is connected 286 """ 287 return self.__ssl_conn.getpeername()
288
289 - def get_context(self):
290 '''Retrieve the Context object associated with this Connection. ''' 291 return self.__ssl_conn.get_context()
292
293 - def get_peer_certificate(self):
294 '''Retrieve the other side's certificate (if any) ''' 295 return self.__ssl_conn.get_peer_certificate()
296