Anonymous avatar Anonymous committed f1f028a

new class API in ldap.controls, not backwards-compatible\!

Comments (0)

Files changed (4)

 * Generic support for LDAPv3 extended operations (thanks to Rich)
 
 Lib/
+* new class API in ldap.controls, not backwards-compatible!
 * New methods LDAPObject.result4() and LDAPObject.extop_result()
 * New (optional) class ldap.controls.AssertionControl
 * New helper module ldap.logger contains file-like object which

Demo/page_control.py

 l.protocol_version = 3
 l.simple_bind_s("", "")
 
-lc = SimplePagedResultsControl(
-  ldap.LDAP_CONTROL_PAGE_OID,True,(page_size,'')
-)
+req_ctrl = SimplePagedResultsControl(True,size=page_size,cookie='')
+
+known_ldap_resp_ctrls = {
+  SimplePagedResultsControl.controlType:SimplePagedResultsControl,
+}
 
 # Send search request
 msgid = l.search_ext(
   ldap.SCOPE_SUBTREE,
   search_flt,
   attrlist=searchreq_attrlist,
-  serverctrls=[lc]
+  serverctrls=[req_ctrl]
 )
 
 pages = 0
 while True:
     pages += 1
-    print "Getting page %d" % (pages,)
-    rtype, rdata, rmsgid, serverctrls = l.result3(msgid)
+    print "Getting page %d" % (pages)
+    rtype, rdata, rmsgid, serverctrls = l.result3(msgid,resp_ctrl_classes=known_ldap_resp_ctrls)
     print '%d results' % len(rdata)
+    print 'serverctrls=',pprint.pprint(serverctrls)
 #    pprint.pprint(rdata)
     pctrls = [
       c
       for c in serverctrls
-      if c.controlType == ldap.LDAP_CONTROL_PAGE_OID
+      if c.controlType == SimplePagedResultsControl.controlType
     ]
     if pctrls:
-        est, cookie = pctrls[0].controlValue
-        if cookie:
-            lc.controlValue = (page_size, cookie)
+        print 'pctrls[0].size',repr(pctrls[0].size)
+        print 'pctrls[0].cookie',repr(pctrls[0].cookie)
+        if pctrls[0].cookie:
+            # Copy cookie from response control to request control
+            req_ctrl.cookie = pctrls[0].cookie
             msgid = l.search_ext(
               base,
               ldap.SCOPE_SUBTREE,
               search_flt,
               attrlist=searchreq_attrlist,
-              serverctrls=[lc]
+              serverctrls=[req_ctrl]
             )
         else:
             break
     else:
         print "Warning:  Server ignores RFC 2696 control."
         break
+
+l.unbind_s()

Lib/ldap/controls.py

 from ldap import __version__
 
 __all__ = [
+  'RequestControl',
+  'ResponseControl',
   'LDAPControl',
+  'ValueLessRequestControl',
+  'ManageDSAITControl',
+  'RelaxRulesControl',
+  'BooleanControl',
+  'SimplePagedResultsControl',
+  'MatchedValuesControl',
+  'AssertionControl',
+  'EncodedControlTuples',
 ]
 
 
 import _ldap,ldap
 
 
-class LDAPControl:
+class RequestControl:
   """
-  Base class for all LDAP controls
+  Base class for all request controls
   """
 
-  def __init__(self,controlType,criticality,controlValue=None,encodedControlValue=None):
+  def __init__(self,controlType=None,criticality=False,encodedControlValue=None):
     self.controlType = controlType
     self.criticality = criticality
-    self.controlValue = controlValue or self.decodeControlValue(encodedControlValue)
+    self.encodedControlValue = encodedControlValue
 
-  def __repr__(self):
-    return '%s(%s,%s,%s)' % (self.__class__.__name__,self.controlType,self.criticality,self.controlValue)
+  def encodeControlValue(self):
+    return self.encodedControlValue
 
-  def encodeControlValue(self,value):
-    return value
 
-  def decodeControlValue(self,encodedValue):
-    return encodedValue
+class ResponseControl:
+  """
+  Base class for all response controls
+  """
 
-  def getEncodedTuple(self):
-    return (self.controlType,self.criticality,self.encodeControlValue(self.controlValue))
+  def __init__(self,controlType=None,criticality=False,encodedControlValue=None):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.decodeControlValue(encodedControlValue)
+
+  def decodeControlValue(self,encodedControlValue):
+    self.encodedControlValue = encodedControlValue
+
+
+class LDAPControl(RequestControl,ResponseControl):
+
+  def __init__(self,controlType=None,criticality=False,controlValue=None,encodedControlValue=None):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.controlValue = controlValue
+    self.encodedControlValue = encodedControlValue
+
+
+class ValueLessRequestControl(RequestControl):
+
+  def __init__(self,controlType=None,criticality=False):
+    self.controlType = controlType
+    self.criticality = criticality
+
+  def encodeControlValue(self):
+    return None
+
+
+class ManageDSAITControl(ValueLessRequestControl):
+
+  def __init__(self,criticality=False):
+    ValueLessRequestControl.__init__(self,ldap.CONTROL_MANAGEDSAIT,criticality=False)
+
+
+class RelaxRulesControl(ValueLessRequestControl):
+
+  def __init__(self,criticality=False):
+    ValueLessRequestControl.__init__(self,"1.3.6.1.4.1.4203.666.5.12",criticality=False)
 
 
 class BooleanControl(LDAPControl):
   """
-  Base class for simple controls with booelan control value
+  Base class for simple request controls with booelan control value
 
   In this base class controlValue has to be passed as
   boolean type (True/False or 1/0).
   boolean2ber = { 1:'\x01\x01\xFF', 0:'\x01\x01\x00' }
   ber2boolean = { '\x01\x01\xFF':1, '\x01\x01\x00':0 }
 
-  def encodeControlValue(self,value):
-    return self.boolean2ber[int(value)]
+  def __init__(self,controlType=None,criticality=False,booleanValue=False):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.booleanValue = booleanValue
 
-  def decodeControlValue(self,encodedValue):
-    return self.ber2boolean[encodedValue]
+  def encodeControlValue(self):
+    return self.boolean2ber[int(self.booleanValue)]
+
+  def decodeControlValue(self,encodedControlValue):
+    self.booleanValue = self.ber2boolean[encodedControlValue]
 
 
 class SimplePagedResultsControl(LDAPControl):
 
   see RFC 2696
   """
-  controlType = ldap.LDAP_CONTROL_PAGE_OID
+  controlType = ldap.CONTROL_PAGEDRESULTS
 
-  def __init__(self,controlType,criticality,controlValue=None,encodedControlValue=None):
-    LDAPControl.__init__(self,ldap.LDAP_CONTROL_PAGE_OID,criticality,controlValue,encodedControlValue)
+  def __init__(self,criticality=False,size=None,cookie=None):
+    self.criticality = criticality
+    self.size,self.cookie = size,cookie
 
-  def encodeControlValue(self,value):
-    size,cookie = value
-    return _ldap.encode_page_control(size,cookie)
+  def encodeControlValue(self):
+    return _ldap.encode_page_control(self.size,self.cookie)
 
-  def decodeControlValue(self,encodedValue):
-    size,cookie = _ldap.decode_page_control(encodedValue)
-    return (size,cookie)
+  def decodeControlValue(self,encodedControlValue):
+    self.size,self.cookie = _ldap.decode_page_control(encodedControlValue)
 
 
-class MatchedValuesControl(LDAPControl):
+class MatchedValuesControl(RequestControl):
   """
   LDAP Matched Values control, as defined in RFC 3876
-
-  from ldap.controls import MatchedValuesControl
-  control = MatchedValuesControl(criticality, filter)
   """
   
-  controlType = ldap.LDAP_CONTROL_VALUESRETURNFILTER
+  controlType = ldap.CONTROL_VALUESRETURNFILTER
   
-  def encodeControlValue(self, value):
-    return _ldap.encode_valuesreturnfilter_control(value)
+  def __init__(self,criticality=False,filterstr='(objectClass=*)'):
+    self.criticality = criticality
+    self.filterstr = filterstr
+
+  def encodeControlValue(self):
+    return _ldap.encode_valuesreturnfilter_control(self.filterstr)
 
 try:
   # Check whether support for assertion control is compiled into
 
   class AssertionControl(LDAPControl):
     """
-    LDAP Assertion control, as defined in 
-
-    from ldap.controls import AssertionControl
-    control = AssertionControl('1.3.6.1.1.12',criticality,filter)
+    LDAP Assertion control, as defined in RFC 4528
     """
     
-    controlType = '1.3.6.1.1.12'
-    
-    def __init__(self,controlType,criticality,controlValue=None,encodedControlValue=None):
-      LDAPControl.__init__(self,self.controlType,criticality,controlValue,encodedControlValue)
+    controlType = ldap.CONTROL_ASSERT    
+    def __init__(self,criticality=True,filterstr='(objectClass=*)'):
+      self.criticality = criticality
+      self.filterstr = filterstr
 
-    def encodeControlValue(self, value):
-      return _ldap.encode_assertion_control(value)
+    def encodeControlValue(self):
+      return _ldap.encode_assertion_control(self.filterstr)
 
 
-def EncodeControlTuples(ldapControls):
+def RequestControlTuples(ldapControls):
   """
   Return list of readily encoded 3-tuples which can be directly
   passed to C module _ldap
     return None
   else:
     result = [
-      c.getEncodedTuple()
+      (c.controlType,c.criticality,c.encodeControlValue())
       for c in ldapControls
     ]
     return result
 
 
-def DecodeControlTuples(ldapControlTuples):
+def DecodeControlTuples(ldapControlTuples,knownLDAPControls):
   """
   Return list of readily encoded 3-tuples which can be directly
   passed to C module _ldap
   """
-  return [
-    knownLDAPControls.get(controlType,LDAPControl)
-      (controlType,criticality,encodedControlValue=encodedControlValue)
-    for controlType,criticality,encodedControlValue in ldapControlTuples or []
-  ]
-
-# Build a dictionary of known LDAPControls
-knownLDAPControls = {}
-for symbol_name in dir():
-  c = eval(symbol_name)
-  if type(c) is ClassType and hasattr(c,'controlType'):
-    knownLDAPControls[c.controlType] = c
+  print '***knownLDAPControls',knownLDAPControls
+  knownLDAPControls = knownLDAPControls or {}
+  result = []
+  for controlType,criticality,encodedControlValue in ldapControlTuples or []:
+    control = knownLDAPControls.get(controlType,LDAPControl)()
+    control.controlType,control.criticality = controlType,criticality
+    control.decodeControlValue(encodedControlValue)
+    result.append(control)  
+  return result

Lib/ldap/ldapobject.py

 import sys,time,_ldap,ldap,ldap.functions
 
 from ldap.schema import SCHEMA_ATTRS
-from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
+from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples
 from ldap.extop import ExtendedRequest,ExtendedResponse
 
 from ldap import LDAPError
         can expect that the result of an abandoned operation will not be
         returned from a future call to result().
     """
-    return self._ldap_call(self._l.abandon_ext,msgid,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.abandon_ext,msgid,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def abandon(self,msgid):
     return self.abandon_ext(msgid,None,None)
 	In opposite to abandon() this extended operation gets an result from
 	the server and thus should be preferred if the server supports it.
     """
-    return self._ldap_call(self._l.cancel,cancelid,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.cancel,cancelid,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def cancel_s(self,cancelid,serverctrls=None,clientctrls=None):
     msgid = self.cancel(cancelid,serverctrls,clientctrls)
         The parameter modlist is similar to the one passed to modify(),
         except that no operation integer need be included in the tuples.
     """
-    return self._ldap_call(self._l.add_ext,dn,modlist,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.add_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def add_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
     msgid = self.add_ext(dn,modlist,serverctrls,clientctrls)
-    return self.result(msgid,all=1,timeout=self.timeout)
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+    return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def add(self,dn,modlist):
     """
     """
     simple_bind([who='' [,cred='']]) -> int
     """
-    return self._ldap_call(self._l.simple_bind,who,cred,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def simple_bind_s(self,who='',cred='',serverctrls=None,clientctrls=None):
     """
     simple_bind_s([who='' [,cred='']]) -> None
     """
     msgid = self.simple_bind(who,cred,serverctrls,clientctrls)
-    return self.result(msgid,all=1,timeout=self.timeout)
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+    return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def bind(self,who,cred,method=ldap.AUTH_SIMPLE):
     """
     """
     sasl_interactive_bind_s(who, auth) -> None
     """
-    return self._ldap_call(self._l.sasl_interactive_bind_s,who,auth,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls),sasl_flags)
+    return self._ldap_call(self._l.sasl_interactive_bind_s,who,auth,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls),sasl_flags)
 
   def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None):
     """
         A design bug in the library prevents value from containing
         nul characters.
     """
-    return self._ldap_call(self._l.compare_ext,dn,attr,value,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.compare_ext,dn,attr,value,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None):
     msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls)
     try:
-      self.result(msgid,all=1,timeout=self.timeout)
+      resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
     except ldap.COMPARE_TRUE:
       return 1
     except ldap.COMPARE_FALSE:
         form returns the message id of the initiated request, and the
         result can be obtained from a subsequent call to result().
     """
-    return self._ldap_call(self._l.delete_ext,dn,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.delete_ext,dn,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def delete_ext_s(self,dn,serverctrls=None,clientctrls=None):
     msgid = self.delete_ext(dn,serverctrls,clientctrls)
-    return self.result(msgid,all=1,timeout=self.timeout)
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+    return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def delete(self,dn):
     return self.delete_ext(dn,None,None)
         ldap.extop.ExtendedResponse this class is used to return an
         object of this class instead of a raw BER value in respvalue.
     """
-    return self._ldap_call(self._l.extop,extreq.requestName,extreq.encodedRequestValue(),EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.extop,extreq.requestName,extreq.encodedRequestValue(),RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def extop_result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
     resulttype,msg,msgid,respctrls,respoid,respvalue = self.result4(msgid,all=1,timeout=self.timeout,add_ctrls=1,add_intermediates=1,add_extop=1)
     """
     modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
     """
-    return self._ldap_call(self._l.modify_ext,dn,modlist,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def modify_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
     msgid = self.modify_ext(dn,modlist,serverctrls,clientctrls)
-    return self.result(msgid,all=1,timeout=self.timeout)
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+    return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def modify(self,dn,modlist):
     """
     return self.rename_s(dn,newrdn,None,delold)
 
   def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
-    return self._ldap_call(self._l.passwd,user,oldpw,newpw,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
     msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls)
-    return self.result(msgid,all=1,timeout=self.timeout)
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+    return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
     """
         This actually corresponds to the rename* routines in the
         LDAP-EXT C API library.
     """
-    return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def rename_s(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
     msgid = self.rename(dn,newrdn,newsuperior,delold,serverctrls,clientctrls)
-    return self.result(msgid,all=1,timeout=self.timeout)
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+    return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
     """
         If a timeout occurs, a TIMEOUT exception is raised, unless
         polling (timeout = 0), in which case (None, None) is returned.
     """
-    res_type,res_data,res_msgid = self.result2(msgid,all,timeout)
-    return res_type,res_data
+    resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout)
+    return resp_type, resp_data
 
   def result2(self,msgid=ldap.RES_ANY,all=1,timeout=None):
-    res_type, res_data, res_msgid, srv_ctrls = self.result3(msgid,all,timeout)
-    return res_type, res_data, res_msgid
+    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout)
+    return resp_type, resp_data, resp_msgid
 
-  def result3(self,msgid=ldap.RES_ANY,all=1,timeout=None):
-    res_type, res_data, res_msgid, srv_ctrls, retoid, retval = self.result4(msgid,all,timeout,add_ctrls=0,add_intermediates=0,add_extop=0)
-    return res_type, res_data, res_msgid, srv_ctrls
+  def result3(self,msgid=ldap.RES_ANY,all=1,timeout=None,resp_ctrl_classes=None):
+    resp_type, resp_data, resp_msgid, decoded_resp_ctrls, retoid, retval = self.result4(
+      msgid,all,timeout,
+      add_ctrls=0,add_intermediates=0,add_extop=0,
+      resp_ctrl_classes=resp_ctrl_classes
+    )
+    return resp_type, resp_data, resp_msgid, decoded_resp_ctrls
  
-  def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermediates=0,add_extop=0):
+  def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermediates=0,add_extop=0,resp_ctrl_classes=None):
     if timeout is None:
       timeout = self.timeout
     ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
     if ldap_result is None:
-        res_type, res_data, res_msgid, srv_ctrls, resp_name, resp_value = (None,None,None,None,None,None)
+        resp_type, resp_data, resp_msgid, resp_ctrls, resp_name, resp_value = (None,None,None,None,None,None)
     else:
       if len(ldap_result)==4:
-        res_type, res_data, res_msgid, srv_ctrls = ldap_result
+        resp_type, resp_data, resp_msgid, resp_ctrls = ldap_result
         resp_name, resp_value = None,None
       else:
-        res_type, res_data, res_msgid, srv_ctrls, resp_name, resp_value = ldap_result
-    return res_type, res_data, res_msgid, srv_ctrls, resp_name, resp_value
+        resp_type, resp_data, resp_msgid, resp_ctrls, resp_name, resp_value = ldap_result
+    decoded_resp_ctrls = DecodeControlTuples(resp_ctrls,resp_ctrl_classes)
+    return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value
  
   def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
     """
       self._l.search_ext,
       base,scope,filterstr,
       attrlist,attrsonly,
-      EncodeControlTuples(serverctrls),
-      EncodeControlTuples(clientctrls),
+      RequestControlTuples(serverctrls),
+      RequestControlTuples(clientctrls),
       timeout,sizelimit,
     )
 
         The unbind and unbind_s methods are identical, and are
         synchronous in nature
     """
-    return self._ldap_call(self._l.unbind_ext,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
+    return self._ldap_call(self._l.unbind_ext,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
 
   def unbind_ext_s(self,serverctrls=None,clientctrls=None):
     msgid = self.unbind_ext(serverctrls,clientctrls)
     if msgid!=None:
-      return self.result(msgid,all=1,timeout=self.timeout)
+      resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
+      return resp_type, resp_data, resp_msgid, resp_ctrls
 
   def unbind(self):
     return self.unbind_ext(None,None)
 
   def set_option(self,option,invalue):
     if option==ldap.OPT_SERVER_CONTROLS or option==ldap.OPT_CLIENT_CONTROLS:
-      invalue = EncodeControlTuples(invalue)
+      invalue = RequestControlTuples(invalue)
     return self._ldap_call(self._l.set_option,option,invalue)
 
   def search_subschemasubentry_s(self,dn=''):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.