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)