source file: /afs/cern.ch/sw/ganga/external/DQ2Client/20080212/noarch/usr/lib/python2.3/site-packages/dq2/common/client/curl/DQCurl.py
file stats: 159 lines, 60 executed: 37.7% covered
   1. """
   2. The curl implementation to contact the central catalogs.
   3. 
   4. @author:
   5. @contact:
   6. @since: 0.3.0
   7. @version: $Id: DQCurl.py,v 1.10.2.22 2007/11/16 10:11:49 psalgado Exp $
   8. """
   9. 
  10. 
  11. import dq2.common
  12. import commands
  13. import cPickle
  14. import re
  15. import os
  16. import time
  17. import urllib
  18. import warnings
  19. import tempfile
  20. 
  21. # ignore os.tempnam() warning
  22. warnings.filterwarnings('ignore')
  23. 
  24. import dq2.common
  25. from dq2.common.DQException import *
  26. from dq2.common.dao.DQDaoException import *
  27. from dq2.common.client.DQClientException import DQInternalServerException
  28. from StringIO import StringIO
  29. 
  30. 
  31. class DQCurl:
  32.     """
  33.     Class responsible to establish and send HTTP requests to DDM servers using curl.
  34. 
  35.     @since: 0.3.0
  36. 
  37.     @cvar REG_CURL_ERROR: the regular expression to get curl errors.
  38.     @type REG_CURL_ERROR: object
  39.     """
  40. 
  41. 
  42.     REG_CURL_ERROR = re.compile('^curl: \([0-9]+\) (.)*$')
  43. 
  44. 
  45.     def __init__ (self, url, urlsec, proxy_cert, ca_path, timeout, headers, hostname='<unknown>'):
  46.         """
  47.         Constructs an instance of DQCurl.
  48. 
  49.         @since: 0.3.0
  50. 
  51.         @ivar url: is the non-secure URL of the host to be contacted.
  52.         @type url: str
  53.         @ivar urlsec: is the secure URL of the host to be contacted.
  54.         @type urlsec: str
  55.         @ivar proxy_cert: is the proxy certificate.
  56.         @type proxy_cert:
  57.         @ivar ca_path: is the directory where the Certification Authority certificates are located (by default it is /etc/grid-security/certificates but can be overriden by the X509_CERT_DIR environment variable.
  58.         @type ca_path: str
  59.         @ivar timeout : the client timeout (in seconds). (since 0.3)
  60.         @type timeout: str
  61.         @ivar hostname: is the client's hostname.
  62.         @type hostname: str
  63. 
  64.         @raise DQSecurityException: in case of an invalid proxy certificate.
  65.         """
  66. 
  67.         self.url = url
  68.         self.urlsec = urlsec
  69.         self.timeout = timeout
  70.         self.hostname = hostname
  71.         self.headers = headers
  72. 
  73.         self.options = {}
  74.         self.options['--max-redirs'] = 5
  75.         self.secoptions = {}
  76. 
  77.         if proxy_cert is not None:
  78.             if proxy_cert == -1:
  79.                 err_msg = 'Invalid certificate!'
  80.                 raise DQSecurityException(err_msg)
  81. 
  82.             self.secoptions['-k'] = None # VERIFYHOST = 0
  83.             #--capath will try to check the peer
  84.             self.secoptions['--cert'] = proxy_cert
  85.             self.secoptions['--key'] = proxy_cert
  86.             self.secoptions['--sslv3'] = None
  87. 
  88. 
  89.     def __del__ (self):
  90.         """
  91.         Closes DQCurl HTTP connection.
  92. 
  93.         @since: 0.2.0
  94.         """
  95.         pass
  96. 
  97. 
  98. # PRIVATE methods
  99. 
 100. 
 101.     def _process (self, com):
 102.         """
 103.         Process the response of the web service.
 104. 
 105.         @since: 0.3.0
 106. 
 107.         @param com: the curl command.
 108.         @type com: str
 109.         """
 110. 
 111.         curl_exit, tmp_response = commands.getstatusoutput(com)
 112. 
 113.         if curl_exit != 0:
 114.             if curl_exit == 6 or curl_exit == 1536:
 115.                 err_msg = 'Unknown DDM server! Please check your configuration file.'
 116.                 raise DQInternalServerException(err_msg, self.url, self.urlsec, curl_error=curl_exit)
 117.             if curl_exit == 7 or curl_exit == 1792:
 118.                 err_msg = 'The central catalog is not responding! The service is down or bad configuration (host port number)!'
 119.                 raise DQInternalServerException(err_msg, self.url, self.urlsec, curl_error=curl_exit)
 120.             elif curl_exit == 23 or curl_exit == 5888:
 121.                 err_msg = 'The central catalog database is down!'
 122.                 raise DQInternalServerException(err_msg, self.url, self.urlsec, curl_error=curl_exit)
 123.             elif curl_exit == 28 or curl_exit == 7168:
 124.                 err_msg = 'The client timed out!'
 125.                 raise DQInternalServerException(err_msg, self.url, self.urlsec, curl_error=curl_exit)
 126.             elif curl_exit == 35 or curl_exit == 8960:
 127.                 err_msg = 'Proxy certificate expired!'
 128.                 raise DQInternalServerException(err_msg, curl_error=curl_exit)
 129.             elif curl_exit == 52 or curl_exit == 13312:
 130.                 """empty reply from the server """
 131.                 err_msg = 'The central catalog sent an empty response!'
 132.                 raise DQBadServerResponse(err_msg, self.url, self.urlsec, curl_exit)
 133.             elif curl_exit == 8960:
 134.                 """certificates have expired on the central catalog"""
 135.                 err_msg = 'certificates have expired on the central catalog or ServerName does not match the hostname!'
 136.                 raise DQInternalServerException(err_msg, curl_error=curl_exit)
 137.             else:
 138.                 err_msg = 'Unknown curl error! [%s]' % (tmp_response)
 139.                 raise DQInternalServerException(err_msg, self.url, self.urlsec, curl_error=curl_exit)
 140.         elif curl_exit == 0:
 141.             if tmp_response.find('Mod_python error') > 0:
 142.                 raise DQBadServerResponse(tmp_response, self.url, self.urlsec, None)
 143. 
 144.         #parse the response of the server
 145. 
 146.         #check for proxy
 147.         if tmp_response.startswith("HTTP/1.1 200 Connection established") or tmp_response.startswith("HTTP/1.0 200 Connection established"):
 148.             tmp_response = tmp_response[tmp_response.find("\r\n\r\n")+4:]
 149. 
 150.         #check for multiple http response
 151.         if tmp_response.startswith("HTTP/1.1 100 Continue") or tmp_response.startswith("HTTP/1.0 100 Continue"):
 152.             tmp2 = tmp_response[tmp_response.find("\r\n\r\n")+4:]
 153.             response = tmp2[tmp2.find("\r\n\r\n")+4:]
 154.             st = int(tmp2[9:12])
 155.         else:
 156.             response = tmp_response[tmp_response.find("\r\n\r\n")+4:]
 157.             st = int(tmp_response[9:12])
 158. 
 159. 
 160.         if len(response) == 0:
 161.             err_msg = 'The server sent an empty response:\n'
 162.             raise DQBadServerResponse(response, self.url, self.urlsec, None)
 163. 
 164. 
 165.         if st == 200:
 166. 
 167.             if re.findall(DQCurl.REG_CURL_ERROR, response):
 168.                 raise DQInternalServerException(response, self.url, self.urlsec)
 169. 
 170.             # ugly but only way is to grep for error string
 171.             if response.find('Mod_python error') != -1:
 172.                 raise DQBadServerResponse(response, self.url, self.urlsec, None)
 173. 
 174.             # convert non strings to list, dict etc
 175.             try:
 176.                 response = eval(response)
 177.             except:
 178.                 """if it is a string it will come with an extra \n"""
 179.                 response = response[0:len(response)-1]
 180. 
 181.             return response
 182. 
 183.         else:
 184. 
 185.             try:
 186.                 raise cPickle.loads(response)
 187.             except cPickle.UnpicklingError, e:
 188.                 if str(response).find('<!DOCTYPE') == 0:
 189.                     err_msg = 'The server sent an invalid response:\n %s' % (response)
 190.                     raise DQInternalServerException(err_msg, self.url, self.urlsec)
 191.                 err_msg = 'Unknown server error:\n %s' % (response)
 192.                 raise DQInternalServerException(err_msg, self.url, self.urlsec)
 193. 
 194. 
 195. # PUBLIC methods
 196. 
 197. 
 198.     def delete (self, request):
 199.         """
 200.         Does HTTP GET with 'delete=yes' field.
 201. 
 202.         @since: 0.3.0
 203. 
 204.         @param request: the URL.
 205.         @type request: str
 206.         """
 207. 
 208.         if request.find('?') != -1: request += '&delete=yes'
 209.         else: request += '?delete=yes'
 210. 
 211.         return self.get(request, secure=True)
 212. 
 213. 
 214.     def get (self, request, secure=False):
 215.         """
 216.         Does HTTP GET.
 217. 
 218.         @since: 0.3.0
 219. 
 220.         @param request: the URL.
 221.         @type request: str
 222.         @param secure: flag to indicate if the request should be secure.
 223.         @type secure: bool
 224.         """
 225. 
 226.         com = 'curl -i --silent --get '
 227. 
 228.         if self.timeout is not None:
 229.             com += '--max-time %u ' % (self.timeout)
 230. 
 231.         for eachOption in self.options.keys():
 232.             if self.options[eachOption] is None:
 233.                 com += ' %s ' % (eachOption)
 234.             else:
 235.                 com += ' %s %s ' % (eachOption, self.options[eachOption])
 236. 
 237.         for eachHeader in self.headers:
 238.             com += ' -H "%s"' % (eachHeader)
 239. 
 240.         if secure:
 241.             for eachOption in self.secoptions.keys():
 242.                 if self.secoptions[eachOption] is None:
 243.                     com += ' %s ' % (eachOption)
 244.                 else:
 245.                     com += '%s %s ' % (eachOption, self.secoptions[eachOption])
 246.             com += " --url '%s%s' " % (self.urlsec, request)
 247.         else:
 248.             com += " --url '%s%s' " % (self.url, request)
 249. 
 250. 
 251. 
 252.         return self._process(com)
 253. 
 254. 
 255.     def post (self, request, params, secure=True):
 256.         """
 257.         Does HTTP POST. Expects list with fields (format per field is 'field1=value1').
 258. 
 259.         @since: 0.3.0
 260. 
 261.         @param request: the URL.
 262.         @type request: str
 263.         @param params: the parameters.
 264.         @type params: dict
 265.         @param secure: flag to indicate if the request should be secure.
 266.         @type secure: bool
 267.         """
 268.         assert params != []
 269. 
 270.         com = 'curl -i --silent '
 271. 
 272.         if self.timeout is not None:
 273.             com += '--max-time %u ' % (self.timeout)
 274. 
 275.         for eachOption in self.options.keys():
 276.             if self.options[eachOption] is None:
 277.                 com += ' %s ' % (eachOption)
 278.             else:
 279.                 com += ' %s %s ' % (eachOption, self.options[eachOption])
 280. 
 281.         for eachHeader in self.headers:
 282.             com += ' -H "%s"' % (eachHeader)
 283. 
 284.         if secure:
 285.             for eachOption in self.secoptions.keys():
 286.                 if self.secoptions[eachOption] is None:
 287.                     com += ' %s ' % (eachOption)
 288.                 else:
 289.                     com += '%s %s ' % (eachOption, self.secoptions[eachOption])
 290.             com += " --url '%s%s' " % (self.urlsec, request)
 291.         else:
 292.             com += " --url '%s%s' " % (self.url, request)
 293. 
 294.         strData = ''
 295.         for key in params.keys():
 296.             strData += 'data="%s"\n' % urllib.urlencode({key:params[key]})
 297. 
 298. 
 299.         # write data to temporary config file
 300. 
 301.         #proper security fix needs python 2.3: tempfile.mkstemp
 302.         tmpName = tempfile.mktemp()
 303.         tmpFile = open(tmpName,'w')
 304.         tmpFile.write(strData)
 305.         tmpFile.close()
 306.         com += ' --config %s ' % tmpName
 307. 
 308.         try:
 309.             r = self._process(com)
 310.         except Exception, e:
 311.             try:
 312.                 os.remove(tmpName)
 313.             except:
 314.                 pass
 315.             raise e
 316. 
 317.         try:
 318.             os.remove(tmpName)
 319.         except:
 320.             pass
 321.         return r
 322. 
 323. 
 324.     def put (self, request, params):
 325.         """
 326.         Does HTTP POST with 'update=yes' field.
 327. 
 328.         @since: 0.3.0
 329. 
 330.         @param request: the URL.
 331.         @type request: str
 332.         @param params: the parameters.
 333.         @type params: dict
 334.         """
 335.         assert params != {}
 336. 
 337.         # handling parameters
 338.         params['update'] = 'yes'
 339. 
 340.         return self.post(request, params)