PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/python/Lib/site-packages/python_ldap-2.4.18-py2.7-win32.egg/ldif.py

https://gitlab.com/pmuontains/Odoo
Python | 453 lines | 399 code | 11 blank | 43 comment | 18 complexity | 33552a9171f8cb6ae2236188b79128b8 MD5 | raw file
  1. """
  2. ldif - generate and parse LDIF data (see RFC 2849)
  3. See http://www.python-ldap.org/ for details.
  4. $Id: ldif.py,v 1.77 2014/10/08 17:16:05 stroeder Exp $
  5. Python compability note:
  6. Tested with Python 2.0+, but should work with Python 1.5.2+.
  7. """
  8. __version__ = '2.4.18'
  9. __all__ = [
  10. # constants
  11. 'ldif_pattern',
  12. # functions
  13. 'AttrTypeandValueLDIF','CreateLDIF','ParseLDIF',
  14. # classes
  15. 'LDIFWriter',
  16. 'LDIFParser',
  17. 'LDIFRecordList',
  18. 'LDIFCopy',
  19. ]
  20. import urlparse,urllib,base64,re,types
  21. try:
  22. from cStringIO import StringIO
  23. except ImportError:
  24. from StringIO import StringIO
  25. attrtype_pattern = r'[\w;.-]+(;[\w_-]+)*'
  26. attrvalue_pattern = r'(([^,]|\\,)+|".*?")'
  27. attrtypeandvalue_pattern = attrtype_pattern + r'[ ]*=[ ]*' + attrvalue_pattern
  28. rdn_pattern = attrtypeandvalue_pattern + r'([ ]*\+[ ]*' + attrtypeandvalue_pattern + r')*[ ]*'
  29. dn_pattern = rdn_pattern + r'([ ]*,[ ]*' + rdn_pattern + r')*[ ]*'
  30. dn_regex = re.compile('^%s$' % dn_pattern)
  31. ldif_pattern = '^((dn(:|::) %(dn_pattern)s)|(%(attrtype_pattern)s(:|::) .*)$)+' % vars()
  32. MOD_OP_INTEGER = {
  33. 'add':0,'delete':1,'replace':2
  34. }
  35. MOD_OP_STR = {
  36. 0:'add',1:'delete',2:'replace'
  37. }
  38. CHANGE_TYPES = ['add','delete','modify','modrdn']
  39. valid_changetype_dict = {}
  40. for c in CHANGE_TYPES:
  41. valid_changetype_dict[c]=None
  42. def is_dn(s):
  43. """
  44. returns 1 if s is a LDAP DN
  45. """
  46. if s=='':
  47. return 1
  48. rm = dn_regex.match(s)
  49. return rm!=None and rm.group(0)==s
  50. SAFE_STRING_PATTERN = '(^(\000|\n|\r| |:|<)|[\000\n\r\200-\377]+|[ ]+$)'
  51. safe_string_re = re.compile(SAFE_STRING_PATTERN)
  52. def list_dict(l):
  53. """
  54. return a dictionary with all items of l being the keys of the dictionary
  55. """
  56. return dict([(i,None) for i in l])
  57. class LDIFWriter:
  58. """
  59. Write LDIF entry or change records to file object
  60. Copy LDIF input to a file output object containing all data retrieved
  61. via URLs
  62. """
  63. def __init__(self,output_file,base64_attrs=None,cols=76,line_sep='\n'):
  64. """
  65. output_file
  66. file object for output
  67. base64_attrs
  68. list of attribute types to be base64-encoded in any case
  69. cols
  70. Specifies how many columns a line may have before it's
  71. folded into many lines.
  72. line_sep
  73. String used as line separator
  74. """
  75. self._output_file = output_file
  76. self._base64_attrs = list_dict([a.lower() for a in (base64_attrs or [])])
  77. self._cols = cols
  78. self._line_sep = line_sep
  79. self.records_written = 0
  80. def _unfoldLDIFLine(self,line):
  81. """
  82. Write string line as one or more folded lines
  83. """
  84. # Check maximum line length
  85. line_len = len(line)
  86. if line_len<=self._cols:
  87. self._output_file.write(line)
  88. self._output_file.write(self._line_sep)
  89. else:
  90. # Fold line
  91. pos = self._cols
  92. self._output_file.write(line[0:min(line_len,self._cols)])
  93. self._output_file.write(self._line_sep)
  94. while pos<line_len:
  95. self._output_file.write(' ')
  96. self._output_file.write(line[pos:min(line_len,pos+self._cols-1)])
  97. self._output_file.write(self._line_sep)
  98. pos = pos+self._cols-1
  99. return # _unfoldLDIFLine()
  100. def _needs_base64_encoding(self,attr_type,attr_value):
  101. """
  102. returns 1 if attr_value has to be base-64 encoded because
  103. of special chars or because attr_type is in self._base64_attrs
  104. """
  105. return self._base64_attrs.has_key(attr_type.lower()) or \
  106. not safe_string_re.search(attr_value) is None
  107. def _unparseAttrTypeandValue(self,attr_type,attr_value):
  108. """
  109. Write a single attribute type/value pair
  110. attr_type
  111. attribute type
  112. attr_value
  113. attribute value
  114. """
  115. if self._needs_base64_encoding(attr_type,attr_value):
  116. # Encode with base64
  117. self._unfoldLDIFLine(':: '.join([attr_type,base64.encodestring(attr_value).replace('\n','')]))
  118. else:
  119. self._unfoldLDIFLine(': '.join([attr_type,attr_value]))
  120. return # _unparseAttrTypeandValue()
  121. def _unparseEntryRecord(self,entry):
  122. """
  123. entry
  124. dictionary holding an entry
  125. """
  126. attr_types = entry.keys()[:]
  127. attr_types.sort()
  128. for attr_type in attr_types:
  129. for attr_value in entry[attr_type]:
  130. self._unparseAttrTypeandValue(attr_type,attr_value)
  131. def _unparseChangeRecord(self,modlist):
  132. """
  133. modlist
  134. list of additions (2-tuple) or modifications (3-tuple)
  135. """
  136. mod_len = len(modlist[0])
  137. if mod_len==2:
  138. changetype = 'add'
  139. elif mod_len==3:
  140. changetype = 'modify'
  141. else:
  142. raise ValueError,"modlist item of wrong length"
  143. self._unparseAttrTypeandValue('changetype',changetype)
  144. for mod in modlist:
  145. if mod_len==2:
  146. mod_type,mod_vals = mod
  147. elif mod_len==3:
  148. mod_op,mod_type,mod_vals = mod
  149. self._unparseAttrTypeandValue(MOD_OP_STR[mod_op],mod_type)
  150. else:
  151. raise ValueError,"Subsequent modlist item of wrong length"
  152. if mod_vals:
  153. for mod_val in mod_vals:
  154. self._unparseAttrTypeandValue(mod_type,mod_val)
  155. if mod_len==3:
  156. self._output_file.write('-'+self._line_sep)
  157. def unparse(self,dn,record):
  158. """
  159. dn
  160. string-representation of distinguished name
  161. record
  162. Either a dictionary holding the LDAP entry {attrtype:record}
  163. or a list with a modify list like for LDAPObject.modify().
  164. """
  165. # Start with line containing the distinguished name
  166. self._unparseAttrTypeandValue('dn',dn)
  167. # Dispatch to record type specific writers
  168. if isinstance(record,types.DictType):
  169. self._unparseEntryRecord(record)
  170. elif isinstance(record,types.ListType):
  171. self._unparseChangeRecord(record)
  172. else:
  173. raise ValueError, "Argument record must be dictionary or list"
  174. # Write empty line separating the records
  175. self._output_file.write(self._line_sep)
  176. # Count records written
  177. self.records_written = self.records_written+1
  178. return # unparse()
  179. def CreateLDIF(dn,record,base64_attrs=None,cols=76):
  180. """
  181. Create LDIF single formatted record including trailing empty line.
  182. This is a compability function. Use is deprecated!
  183. dn
  184. string-representation of distinguished name
  185. record
  186. Either a dictionary holding the LDAP entry {attrtype:record}
  187. or a list with a modify list like for LDAPObject.modify().
  188. base64_attrs
  189. list of attribute types to be base64-encoded in any case
  190. cols
  191. Specifies how many columns a line may have before it's
  192. folded into many lines.
  193. """
  194. f = StringIO()
  195. ldif_writer = LDIFWriter(f,base64_attrs,cols,'\n')
  196. ldif_writer.unparse(dn,record)
  197. s = f.getvalue()
  198. f.close()
  199. return s
  200. class LDIFParser:
  201. """
  202. Base class for a LDIF parser. Applications should sub-class this
  203. class and override method handle() to implement something meaningful.
  204. Public class attributes:
  205. records_read
  206. Counter for records processed so far
  207. """
  208. def _stripLineSep(self,s):
  209. """
  210. Strip trailing line separators from s, but no other whitespaces
  211. """
  212. if s[-2:]=='\r\n':
  213. return s[:-2]
  214. elif s[-1:]=='\n':
  215. return s[:-1]
  216. else:
  217. return s
  218. def __init__(
  219. self,
  220. input_file,
  221. ignored_attr_types=None,
  222. max_entries=0,
  223. process_url_schemes=None,
  224. line_sep='\n'
  225. ):
  226. """
  227. Parameters:
  228. input_file
  229. File-object to read the LDIF input from
  230. ignored_attr_types
  231. Attributes with these attribute type names will be ignored.
  232. max_entries
  233. If non-zero specifies the maximum number of entries to be
  234. read from f.
  235. process_url_schemes
  236. List containing strings with URLs schemes to process with urllib.
  237. An empty list turns off all URL processing and the attribute
  238. is ignored completely.
  239. line_sep
  240. String used as line separator
  241. """
  242. self._input_file = input_file
  243. self._max_entries = max_entries
  244. self._process_url_schemes = list_dict([s.lower() for s in (process_url_schemes or [])])
  245. self._ignored_attr_types = list_dict([a.lower() for a in (ignored_attr_types or [])])
  246. self._line_sep = line_sep
  247. self.records_read = 0
  248. def handle(self,dn,entry):
  249. """
  250. Process a single content LDIF record. This method should be
  251. implemented by applications using LDIFParser.
  252. """
  253. def _unfoldLDIFLine(self):
  254. """
  255. Unfold several folded lines with trailing space into one line
  256. """
  257. unfolded_lines = [ self._stripLineSep(self._line) ]
  258. self._line = self._input_file.readline()
  259. while self._line and self._line[0]==' ':
  260. unfolded_lines.append(self._stripLineSep(self._line[1:]))
  261. self._line = self._input_file.readline()
  262. return ''.join(unfolded_lines)
  263. def _parseAttrTypeandValue(self):
  264. """
  265. Parse a single attribute type and value pair from one or
  266. more lines of LDIF data
  267. """
  268. # Reading new attribute line
  269. unfolded_line = self._unfoldLDIFLine()
  270. # Ignore comments which can also be folded
  271. while unfolded_line and unfolded_line[0]=='#':
  272. unfolded_line = self._unfoldLDIFLine()
  273. if not unfolded_line or unfolded_line=='\n' or unfolded_line=='\r\n':
  274. return None,None
  275. try:
  276. colon_pos = unfolded_line.index(':')
  277. except ValueError:
  278. # Treat malformed lines without colon as non-existent
  279. return None,None
  280. attr_type = unfolded_line[0:colon_pos]
  281. # if needed attribute value is BASE64 decoded
  282. value_spec = unfolded_line[colon_pos:colon_pos+2]
  283. if value_spec=='::':
  284. # attribute value needs base64-decoding
  285. attr_value = base64.decodestring(unfolded_line[colon_pos+2:])
  286. elif value_spec==':<':
  287. # fetch attribute value from URL
  288. url = unfolded_line[colon_pos+2:].strip()
  289. attr_value = None
  290. if self._process_url_schemes:
  291. u = urlparse.urlparse(url)
  292. if self._process_url_schemes.has_key(u[0]):
  293. attr_value = urllib.urlopen(url).read()
  294. elif value_spec==':\r\n' or value_spec=='\n':
  295. attr_value = ''
  296. else:
  297. attr_value = unfolded_line[colon_pos+2:].lstrip()
  298. return attr_type,attr_value
  299. def parse(self):
  300. """
  301. Continously read and parse LDIF records
  302. """
  303. self._line = self._input_file.readline()
  304. while self._line and \
  305. (not self._max_entries or self.records_read<self._max_entries):
  306. # Reset record
  307. version = None; dn = None; changetype = None; modop = None; entry = {}
  308. attr_type,attr_value = self._parseAttrTypeandValue()
  309. while attr_type!=None and attr_value!=None:
  310. if attr_type=='dn':
  311. # attr type and value pair was DN of LDIF record
  312. if dn!=None:
  313. raise ValueError, 'Two lines starting with dn: in one record.'
  314. if not is_dn(attr_value):
  315. raise ValueError, 'No valid string-representation of distinguished name %s.' % (repr(attr_value))
  316. dn = attr_value
  317. elif attr_type=='version' and dn is None:
  318. version = 1
  319. elif attr_type=='changetype':
  320. # attr type and value pair was DN of LDIF record
  321. if dn is None:
  322. raise ValueError, 'Read changetype: before getting valid dn: line.'
  323. if changetype!=None:
  324. raise ValueError, 'Two lines starting with changetype: in one record.'
  325. if not valid_changetype_dict.has_key(attr_value):
  326. raise ValueError, 'changetype value %s is invalid.' % (repr(attr_value))
  327. changetype = attr_value
  328. elif attr_value!=None and \
  329. not self._ignored_attr_types.has_key(attr_type.lower()):
  330. # Add the attribute to the entry if not ignored attribute
  331. if entry.has_key(attr_type):
  332. entry[attr_type].append(attr_value)
  333. else:
  334. entry[attr_type]=[attr_value]
  335. # Read the next line within an entry
  336. attr_type,attr_value = self._parseAttrTypeandValue()
  337. if entry:
  338. # append entry to result list
  339. self.handle(dn,entry)
  340. self.records_read = self.records_read+1
  341. return # parse()
  342. class LDIFRecordList(LDIFParser):
  343. """
  344. Collect all records of LDIF input into a single list.
  345. of 2-tuples (dn,entry). It can be a memory hog!
  346. """
  347. def __init__(
  348. self,
  349. input_file,
  350. ignored_attr_types=None,max_entries=0,process_url_schemes=None
  351. ):
  352. """
  353. See LDIFParser.__init__()
  354. Additional Parameters:
  355. all_records
  356. List instance for storing parsed records
  357. """
  358. LDIFParser.__init__(self,input_file,ignored_attr_types,max_entries,process_url_schemes)
  359. self.all_records = []
  360. def handle(self,dn,entry):
  361. """
  362. Append single record to dictionary of all records.
  363. """
  364. self.all_records.append((dn,entry))
  365. class LDIFCopy(LDIFParser):
  366. """
  367. Copy LDIF input to LDIF output containing all data retrieved
  368. via URLs
  369. """
  370. def __init__(
  371. self,
  372. input_file,output_file,
  373. ignored_attr_types=None,max_entries=0,process_url_schemes=None,
  374. base64_attrs=None,cols=76,line_sep='\n'
  375. ):
  376. """
  377. See LDIFParser.__init__() and LDIFWriter.__init__()
  378. """
  379. LDIFParser.__init__(self,input_file,ignored_attr_types,max_entries,process_url_schemes)
  380. self._output_ldif = LDIFWriter(output_file,base64_attrs,cols,line_sep)
  381. def handle(self,dn,entry):
  382. """
  383. Write single LDIF record to output file.
  384. """
  385. self._output_ldif.unparse(dn,entry)
  386. def ParseLDIF(f,ignore_attrs=None,maxentries=0):
  387. """
  388. Parse LDIF records read from file.
  389. This is a compability function. Use is deprecated!
  390. """
  391. ldif_parser = LDIFRecordList(
  392. f,ignored_attr_types=ignore_attrs,max_entries=maxentries,process_url_schemes=0
  393. )
  394. ldif_parser.parse()
  395. return ldif_parser.all_records