XMLSyncUtils.py 70 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

import smtplib
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Subscription import Signature
32
from AccessControl.SecurityManagement import newSecurityManager
33
from StringIO import StringIO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34
from xml.dom.ext import PrettyPrint
Nicolas Delaby's avatar
Nicolas Delaby committed
35
from ERP5Diff import ERP5Diff
Nicolas Delaby's avatar
Nicolas Delaby committed
36
from zLOG import LOG, INFO
Nicolas Delaby's avatar
Nicolas Delaby committed
37

38 39 40
try:
  from Ft.Xml import Parse
except ImportError:
41
  LOG('XMLSyncUtils', INFO, "Can't import Parse")
42 43 44
  class Parse:
    def __init__(self, *args, **kw):
      raise ImportError, "Sorry, it was not possible to import Ft library"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45

46 47 48 49 50
try:
      from base64 import b16encode, b16decode
except ImportError:
      from base64 import encodestring as b16encode, decodestring as b16decode

51
class XMLSyncUtilsMixin(SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
52

53 54
  def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None,
      source_name=None, dataCred=None, authentication_format='b64',
55
      authentication_type='syncml:auth-basic'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56 57 58 59
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
60 61 62 63 64 65 66 67 68
    xml_list = []
    xml = xml_list.append
    xml(' <SyncHdr>\n')
    xml('  <VerDTD>1.1</VerDTD>\n')
    xml('  <VerProto>SyncML/1.1</VerProto>\n')
    xml('  <SessionID>%s</SessionID>\n' % session_id)
    xml('  <MsgID>%s</MsgID>\n' % msg_id)
    xml('  <Target>\n')
    xml('   <LocURI>%s</LocURI>\n' % target)
69
    if target_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
70 71 72 73
      xml('   <LocName>%s</LocName>\n' %target_name)
    xml('  </Target>\n')
    xml('  <Source>\n')
    xml('   <LocURI>%s</LocURI>\n' % source) 
74
    if source_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
75 76
      xml('   <LocName>%s</LocName>\n' % source_name)
    xml('  </Source>\n')
77 78 79
    if dataCred not in (None, ''):
      xml('  <Cred>\n')
      xml('   <Meta>\n')
80 81
      xml("    <Format xmlns='syncml:metinf'>%s</Format>\n" % authentication_format)
      xml("    <Type xmlns='syncml:metinf'>%s</Type>\n" % authentication_type)
82 83 84
      xml('   </Meta>\n')
      xml('   <Data>%s</Data>\n' % dataCred)
      xml('  </Cred>\n')
Sebastien Robin's avatar
Sebastien Robin committed
85 86 87
    xml(' </SyncHdr>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
88

Sebastien Robin's avatar
Sebastien Robin committed
89 90
  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
91 92 93 94
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
95 96 97 98 99 100 101 102 103 104 105 106 107
    xml_list = []
    xml = xml_list.append
    xml('  <Alert>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <Data>%s</Data>\n' % sync_code)
    xml('   <Item>\n')
    xml('    <Target>\n')
    xml('     <LocURI>%s</LocURI>\n' % target)
    xml('    </Target>\n')
    xml('    <Source>\n')
    xml('     <LocURI>%s</LocURI>\n' % source)
    xml('    </Source>\n')
    xml('    <Meta>\n')
108
    xml('     <Anchor>\n')
Sebastien Robin's avatar
Sebastien Robin committed
109 110 111 112 113 114 115 116
    xml('      <Last>%s</Last>\n' % last_anchor)
    xml('      <Next>%s</Next>\n' % next_anchor)
    xml('     </Anchor>\n')
    xml('    </Meta>\n')
    xml('   </Item>\n')
    xml('  </Alert>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
117

118 119
  def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor, 
      subscription=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
120
    """
121 122
    return a status bloc with all status corresponding to the syncml
    commands in remote_xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
123
    """
124 125 126 127

    #list of element in the SyncBody bloc
    syncbody_element_list = remote_xml.xpath('//SyncBody/*')
    message_id = self.getMessageId(remote_xml)
Sebastien Robin's avatar
Sebastien Robin committed
128 129
    xml_list = []
    xml = xml_list.append
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

    if data_code != self.AUTH_REQUIRED:#because for AUTH_REQUIRED, SyncMLChal is                                       #called
      # status for SyncHdr
      message_id = self.getMessageId(remote_xml)
      xml('  <Status>\n')
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
      cmd_id += 1
      xml('   <MsgRef>%s</MsgRef>\n' % self.getMessageId(remote_xml))
      xml('   <CmdRef>0</CmdRef>\n') #to make reference to the SyncHdr, it's 0
      xml('   <Cmd>SyncHdr</Cmd>\n')
      xml('   <TargetRef>%s</TargetRef>\n' \
        % remote_xml.xpath('string(//SyncHdr/Target/LocURI)').encode('utf-8'))
      xml('   <SourceRef>%s</SourceRef>\n' \
        % remote_xml.xpath('string(//SyncHdr/Source/LocURI)').encode('utf-8'))
      if isinstance(data_code, int):
        data_code = str(data_code)
      xml('   <Data>%s</Data>\n' % data_code)
      xml('  </Status>\n')
    #add the status bloc corresponding to the receive command
    for syncbody_element in syncbody_element_list:
150
      #LOG('SyncMLStatus : ', DEBUG, "command:%s, subscription:%s" % (str(syncbody_element.nodeName), subscription))
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
      if str(syncbody_element.nodeName) not in ('Status', 'Final', 'Get'):
        xml('  <Status>\n')
        xml('   <CmdID>%s</CmdID>\n' % cmd_id)
        cmd_id += 1
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
        xml('   <CmdRef>%s</CmdRef>\n' \
            % syncbody_element.xpath('string(.//CmdID)').encode('utf-8'))
        xml('   <Cmd>%s</Cmd>\n' % syncbody_element.nodeName.encode('utf-8'))

        target_ref = syncbody_element.xpath('string(.//Target/LocURI)').encode('utf-8')
        if target_ref not in (None, ''):
          xml('   <TargetRef>%s</TargetRef>\n' % target_ref )
        source_ref = syncbody_element.xpath('string(.//Source/LocURI)').encode('utf-8')
        if source_ref not in (None, ''):
          xml('   <SourceRef>%s</SourceRef>\n' % source_ref )
        if syncbody_element.nodeName.encode('utf-8') == 'Add':
          xml('   <Data>%s</Data>\n' % str(self.ITEM_ADDED))
        elif syncbody_element.nodeName.encode('utf-8') == 'Alert' and \
            syncbody_element.xpath('string(.//Data)').encode('utf-8') == \
            str(self.SLOW_SYNC):
          xml('   <Data>%s</Data>\n' % str(self.REFRESH_REQUIRED))
        else:
          xml('   <Data>%s</Data>\n' % str(self.SUCCESS))

        if str(syncbody_element.nodeName) == 'Alert':
          xml('   <Item>\n')
          xml('    <Data>\n')
          xml('     <Anchor>\n')
          xml('      <Next>%s</Next>\n' % next_anchor)
          xml('     </Anchor>\n')
          xml('    </Data>\n')
          xml('   </Item>\n')
        xml('  </Status>\n')

      if str(syncbody_element.nodeName) == 'Get' and subscription != None:
        cmd_ref = syncbody_element.xpath('string(.//CmdID)').encode('utf-8')
187 188 189 190 191 192
        syncml_result = self.SyncMLPut(
                                  cmd_id,
                                  subscription,
                                  markup='Results',
                                  cmd_ref=cmd_ref,
                                  message_id=self.getMessageId(remote_xml))
193 194
        xml(syncml_result)
        cmd_id += 1
Sebastien Robin's avatar
Sebastien Robin committed
195
    xml_a = ''.join(xml_list)
196
    return {'xml':xml_a, 'cmd_id':cmd_id}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
197

198 199
  def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None,
      sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None,
200
      remote_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
201
    """
Sebastien Robin's avatar
Sebastien Robin committed
202
    This is used in order to confirm that an object was correctly
Jean-Paul Smets's avatar
Jean-Paul Smets committed
203 204
    synchronized
    """
205 206 207 208 209 210
    if remote_xml is not None :
      msg_ref=remote_xml.xpath("string(//MsgID)").encode('utf-8')
      cmd_ref=remote_xml.xpath("string(.//CmdID)").encode('utf-8')
      target_ref=remote_xml.xpath("string(.//Target/LocURI)").encode('utf-8')
      source_ref=remote_xml.xpath("string(.//Source/LocURI)").encode('utf-8')

Sebastien Robin's avatar
Sebastien Robin committed
211 212 213
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    #here there is a lot of test to keep compatibility with older call
    if cmd_id not in (None,'') :
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    if msg_ref not in (None,''):
      xml('   <MsgRef>%s</MsgRef>\n' % msg_ref)
    if cmd_ref not in (None,''):
      xml('   <CmdRef>%s</CmdRef>\n' %cmd_ref)
    if cmd not in (None,''):
      xml('   <Cmd>%s</Cmd>\n' % cmd)
    if target_ref not in (None,''):
      xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    if source_ref not in (None,''):
      xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    if sync_code not in (None,''):
      xml('   <Data>%s</Data>\n' % sync_code)
Sebastien Robin's avatar
Sebastien Robin committed
229 230 231
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
232 233

  def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format,
Sebastien Robin's avatar
Sebastien Robin committed
234 235 236 237 238 239 240 241
      auth_type, data_code):
    """
    This is used in order to ask crendentials
    """
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
242 243
    xml('   <MsgRef>1</MsgRef>\n')
    xml('   <CmdRef>0</CmdRef>\n')
Sebastien Robin's avatar
Sebastien Robin committed
244 245 246 247 248
    xml('   <Cmd>%s</Cmd>\n' % cmd)
    xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    xml('   <Chal>\n')
    xml('    <Meta>\n')
249 250
    xml("     <Format xmlns='syncml:metinf'>%s</Format>\n" % auth_format)
    xml("     <Type xmlns='syncml:metinf'>%s</Type>\n" % auth_type)
Sebastien Robin's avatar
Sebastien Robin committed
251 252 253 254 255 256
    xml('    </Meta>\n')
    xml('   </Chal>\n')
    xml('   <Data>%s</Data>\n' % str(data_code))
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
257

258
  def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None,
259
      message_id=None):
260 261
    """
    this is used to inform the server of the CTType version supported
262 263
    but if the server use it to respond to a Get request, it's a <Result> markup
    instead of <Put>
264 265
    """
    conduit_name = subscription.getConduit()
266
    conduit = self.getConduitByName(conduit_name)
267
    #if the conduit support the SyncMLPut :
268
    if hasattr(conduit, 'getCapabilitiesCTTypeList') and \
269 270 271 272
        hasattr(conduit, 'getCapabilitiesVerCTList') and \
        hasattr(conduit, 'getPreferedCapabilitieVerCT'):
      xml_list = []
      xml = xml_list.append
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
      xml('  <%s>\n' % markup)
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
      if message_id not in (None, ''):
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
      if cmd_ref not in (None, '') :
        xml('   <CmdRef>%s</CmdRef>\n' % cmd_ref)
      xml('   <Meta>\n')
      xml('    <Type>application/vnd.syncml-devinf+xml</Type>\n');
      xml('   </Meta>\n')
      xml('   <Item>\n')
      xml('    <Source>\n')
      xml('     <LocURI>./devinf11</LocURI>\n')
      xml('    </Source>\n')
      xml('    <Data>\n')
      xml('     <DevInf>\n')
      xml('      <VerDTD>1.1</VerDTD>\n')
      xml('      <Man>Nexedi</Man>\n')
      xml('      <Mod>ERP5SyncML</Mod>\n')
      xml('      <OEM>Open Source</OEM>\n')
      xml('      <SwV>0.1</SwV>\n')
      xml('      <DevID>%s</DevID>\n' % subscription.getSubscriptionUrl())
      xml('      <DevTyp>workstation</DevTyp>\n')
      xml('      <UTC/>\n')
      xml('      <DataStore>\n')
      xml('       <SourceRef>%s</SourceRef>\n' % subscription.getSourceURI())
      xml('       <Rx-Pref>\n')
      xml('        <CTType>%s</CTType>\n' % \
          conduit.getPreferedCapabilitieCTType())
      xml('        <VerCT>%s</VerCT>\n' % \
          conduit.getPreferedCapabilitieVerCT())
      xml('       </Rx-Pref>\n')
      for type in conduit.getCapabilitiesCTTypeList():
        if type != self.MEDIA_TYPE['TEXT_XML']:
          for rx_version in conduit.getCapabilitiesVerCTList(type):
            xml('       <Rx>\n')
            xml('        <CTType>%s</CTType>\n' % type)
            xml('        <VerCT>%s</VerCT>\n' % rx_version)
            xml('       </Rx>\n')

      xml('       <Tx-Pref>\n')
      xml('        <CTType>%s</CTType>\n' % \
          conduit.getPreferedCapabilitieCTType())
      xml('        <VerCT>%s</VerCT>\n' % \
          conduit.getPreferedCapabilitieVerCT())
      xml('       </Tx-Pref>\n')
      for type in conduit.getCapabilitiesCTTypeList():
        if type != self.MEDIA_TYPE['TEXT_XML']:
          for tx_version in conduit.getCapabilitiesVerCTList(type):
            xml('       <Tx>\n')
            xml('        <CTType>%s</CTType>\n' % type)
            xml('        <VerCT>%s</VerCT>\n' % tx_version)
            xml('       </Tx>\n')

      xml('       <SyncCap>\n')
      xml('        <SyncType>2</SyncType>\n')
      xml('        <SyncType>1</SyncType>\n')
      xml('        <SyncType>4</SyncType>\n')
      xml('        <SyncType>6</SyncType>\n')
      xml('       </SyncCap>\n')

      xml('      </DataStore>\n')
      xml('     </DevInf>\n')
      xml('    </Data>\n')
      xml('   </Item>\n')
      xml('  </%s>\n' % markup)
338 339 340 341 342
      xml_a = ''.join(xml_list)
      return xml_a
    return ''


Jean-Paul Smets's avatar
Jean-Paul Smets committed
343 344 345 346 347 348 349 350 351
  def sendMail(self, fromaddr, toaddr, id_sync, msg):
    """
      Send a message via email
      - sync_object : this is a publication or a subscription
      - message : what we want to send
    """
    header = "Subject: %s\n" % id_sync
    header += "To: %s\n\n" % toaddr
    msg = header + msg
352
    #LOG('SubSendMail', DEBUG,'from: %s, to: %s' % (fromaddr,toaddr))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
353 354 355
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
356
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
357 358
    server.quit()

359
  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
360
                  more_data=0, gid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
361 362 363
    """
      Add an object with the SyncML protocol
    """
Sebastien Robin's avatar
Sebastien Robin committed
364 365 366 367
    xml_list = []
    xml = xml_list.append
    xml('   <Add>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
368
    xml('    <Meta>\n')
369
    xml('     <Type>%s</Type>\n' % media_type)
370
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
371 372 373 374
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % gid)
    xml('     </Source>\n')
375 376 377 378 379 380 381 382
    if media_type == self.MEDIA_TYPE['TEXT_XML']:
      xml('     <Data>')
      xml(xml_string)
      xml('</Data>\n')
    else:
      xml('     <Data><![CDATA[')
      xml(xml_string)
      xml('\n]]></Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
383
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
384 385 386 387 388
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Add>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
389

390
  def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
391
    """
Sebastien Robin's avatar
Sebastien Robin committed
392 393 394 395 396 397 398
      Delete an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Delete>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Item>\n')
399 400 401 402 403 404 405 406
    if rid not in (None, ''):
      xml('     <Target>\n')
      xml('      <LocURI>%s</LocURI>\n' % rid)
      xml('     </Target>\n')
    else:
      xml('     <Source>\n')
      xml('      <LocURI>%s</LocURI>\n' % object_gid)
      xml('     </Source>\n')
Sebastien Robin's avatar
Sebastien Robin committed
407 408 409 410
    xml('    </Item>\n')
    xml('   </Delete>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
411

412
  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
413
                       more_data=0, gid=None, rid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
414
    """
Sebastien Robin's avatar
Sebastien Robin committed
415 416 417 418 419 420
      Replace an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Replace>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
421
    xml('    <Meta>\n')
422
    xml('     <Type>%s</Type>\n' % media_type)
423
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
424
    xml('    <Item>\n')
425 426 427 428 429 430 431 432
    if rid is not None:
      xml('     <Target>\n')
      xml('      <LocURI>%s</LocURI>\n' % str(rid))
      xml('     </Target>\n')
    else:
      xml('     <Source>\n')
      xml('      <LocURI>%s</LocURI>\n' % str(gid))
      xml('     </Source>\n')
433
    xml('     <Data>')
Sebastien Robin's avatar
Sebastien Robin committed
434 435
    xml(xml_string)
    xml('     </Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
436
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
437 438 439 440 441
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Replace>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
442

Nicolas Delaby's avatar
Nicolas Delaby committed
443
  def getXupdateObject(self, object_xml=None, old_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
444 445
    """
    Generate the xupdate with the new object and the old xml
Nicolas Delaby's avatar
Nicolas Delaby committed
446 447 448 449 450 451 452 453
    """
    erp5diff = ERP5Diff()
    erp5diff.compare(old_xml, object_xml)
    xupdate_doc = erp5diff._result
    #minidom is buggy, add namespace declaration, and version
    attr_ns = xupdate_doc.createAttribute('xmlns:xupdate')
    attr_ns.value = 'http://www.xmldb.org/xupdate'
    attr_version = xupdate_doc.createAttribute('version')
454
    attr_version.value = '1.0'
Nicolas Delaby's avatar
Nicolas Delaby committed
455 456
    xupdate_doc.documentElement.setAttributeNode(attr_ns)
    xupdate_doc.documentElement.setAttributeNode(attr_version)
457
    xupdate = xupdate_doc.toxml('utf-8')
Nicolas Delaby's avatar
Nicolas Delaby committed
458
    #omit xml declaration
Jean-Paul Smets's avatar
Jean-Paul Smets committed
459 460 461
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
    return xupdate

462 463 464 465 466
  def getSessionId(self, xml):
    """
    We will retrieve the session id of the message
    """
    session_id = 0
467 468
    session_id = xml.xpath('string(/SyncML/SyncHdr/SessionID)')
    session_id = int(session_id)
469
    return session_id
470

Sebastien Robin's avatar
Sebastien Robin committed
471 472 473 474 475
  def getMessageId(self, xml):
    """
    We will retrieve the message id of the message
    """
    message_id = 0
476 477
    message_id = xml.xpath('string(/SyncML/SyncHdr/MsgID)')
    message_id = int(message_id)
Sebastien Robin's avatar
Sebastien Robin committed
478 479 480 481 482 483 484
    return message_id

  def getTarget(self, xml):
    """
    return the target in the SyncHdr section
    """
    url = ''
485 486
    url = xml.xpath('string(/SyncML/SyncHdr/Target/LocURI)')
    url = url.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
487 488
    return url

Jean-Paul Smets's avatar
Jean-Paul Smets committed
489 490 491 492 493
  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
494
    last_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Last)')
495
    last_anchor = last_anchor.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
496
    return last_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
497 498 499 500 501 502

  def getAlertNextAnchor(self, xml_stream):
    """
      Return the value of the next anchor, in the
      alert section of the xml_stream
    """
503
    next_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Next)')
504 505
    next_anchor = next_anchor.encode('utf-8')
    return next_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
506

507
  def getSourceURI(self, xml):
508 509 510 511 512 513 514
    """
    return the source uri of the data base
    """
    source_uri = xml.xpath('string(//SyncBody/Alert/Item/Source/LocURI)')
    if isinstance(source_uri, unicode):
      source_uri = source_uri.encode('utf-8')
    return source_uri
515

516 517 518 519 520 521 522 523 524
  def getTargetURI(self, xml):
    """
    return the target uri of the data base
    """
    target_uri = xml.xpath('string(//SyncBody/Alert/Item/Target/LocURI)')
    if isinstance(target_uri, unicode):
      target_uri = target_uri.encode('utf-8')
    return target_uri

525
  def getSubscriptionUrlFromXML(self, xml):
526 527 528 529 530 531 532
    """
    return the source URI of the syncml header
    """
    subscription_url = xml.xpath('string(//SyncHdr/Source/LocURI)')
    if isinstance(subscription_url, unicode):
      subscription_url = subscription_url.encode('utf-8')
    return subscription_url
533

Jean-Paul Smets's avatar
Jean-Paul Smets committed
534 535 536 537
  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
538 539 540 541
    status = xml.xpath('string(TargetRef)')
    if isinstance(status, unicode):
      status = status.encode('utf-8')
    return status
Jean-Paul Smets's avatar
Jean-Paul Smets committed
542 543 544 545 546

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
547 548 549
    status_code = xml.xpath('string(Data)')
    if status_code not in ('', None, []):
      return int(status_code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
550 551
    return None

552 553 554 555
  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
556
    cmd = None
557
    if xml.nodeName=='Status':
558 559 560
      cmd = xml.xpath('string(//Status/Cmd)')
      if isinstance(cmd, unicode):
        cmd = cmd.encode('utf-8')
561
    return cmd
562

563 564 565
  def getCred(self, xml):
    """
      return the credential information : type, format and data
Nicolas Delaby's avatar
Nicolas Delaby committed
566
    """
567 568 569 570 571
    format=''
    type=''
    data=''

    first_node = xml.childNodes[0]
572 573
    format = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Format'])")
    type = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Type'])")
574
    data = first_node.xpath('string(/SyncML/SyncHdr/Cred/Data)')
575

576 577 578
    format = format.encode('utf-8')
    type = type.encode('utf-8')
    data = data.encode('utf-8')
579 580
    return (format, type, data)

581
  def checkCred(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
582
    """
583
      Check if there's a Cred section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
584
    """
585
    return xml_stream.xpath('string(SyncML/SyncHdr/Cred)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
586

587
  def getChal(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
588
    """
589
      return the chalenge information : format and type
590
    """
591 592
    format=None
    type=None
593 594

    first_node = xml.childNodes[0]
595 596
    format = first_node.xpath("string(//*[local-name() = 'Format'])")
    type = first_node.xpath("string(//*[local-name() = 'Type'])")
597 598 599 600 601 602

    format = format.encode('utf-8')
    type = type.encode('utf-8')
    return (format, type)

  def checkChal(self, xml_stream):
Sebastien Robin's avatar
Sebastien Robin committed
603
    """
604 605
      Check if there's a Chal section in the xml_stream
    """
606
    return xml_stream.xpath('string(SyncML/SyncBody/Status/Chal)') not in ('', None, [])
607

608 609 610 611
  def checkMap(self, xml_stream):
    """
      Check if there's a Map section in the xml_stream
    """
612
    return xml_stream.xpath('string(SyncML/SyncBody/Map)') not in ('', None, [])
613 614 615 616 617 618 619 620 621 622 623

  def setRidWithMap(self, xml_stream, subscriber):
    """
      get all the local objects of the given target id and set them the rid with
      the given source id (in the Map section)
    """
    item_list = xml_stream.xpath('SyncML/SyncBody/Map/MapItem')
    for map_item in item_list:
      gid = map_item.xpath('string(.//Target/LocURI)').encode('utf-8')
      signature = subscriber.getSignatureFromGid(gid)
      rid = map_item.xpath('string(.//Source/LocURI)').encode('utf-8')
624
      signature.setRid(rid)
625

626
  def getAlertCodeFromXML(self, xml_stream):
627 628 629 630 631 632 633 634
    """
      Return the value of the alert code inside the full syncml message
    """
    alert_code = xml_stream.xpath('string(SyncML/SyncBody/Alert/Data)')
    if alert_code not in (None, ''):
      return int(alert_code)
    else:
      return None
Sebastien Robin's avatar
Sebastien Robin committed
635

Jean-Paul Smets's avatar
Jean-Paul Smets committed
636 637
  def checkAlert(self, xml_stream):
    """
Sebastien Robin's avatar
Sebastien Robin committed
638
      Check if there's an Alert section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
639
    """
640
    return xml_stream.xpath('string(SyncML/SyncBody/Alert)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
641 642 643 644 645

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
646
    return xml_stream.xpath('string(SyncML/SyncBody/Sync)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
647

648
  def checkStatus(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
649 650 651
    """
      Check if there's a Status section in the xml_xtream
    """
652
    return xml_stream.xpath('string(SyncML/SyncBody/Status)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
653

654
  def getSyncActionList(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
655
    """
656
    return the list of the action (could be "add", "replace", "delete").
Jean-Paul Smets's avatar
Jean-Paul Smets committed
657
    """
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
    return xml_stream.xpath('//Add|//Delete|//Replace')

  def getSyncBodyStatusList(self, xml_stream):
    """
    return the list of dictionary corredponding to the data of each status bloc
    the data are : cmd, code and source
    """
    status_list = []
    xml = xml_stream.xpath('//Status')
    for status in xml:
      tmp_dict = {}
      tmp_dict['cmd']     = status.xpath('string(./Cmd)').encode('utf-8')
      tmp_dict['code']    = status.xpath('string(./Data)').encode('utf-8')
      tmp_dict['source']  = status.xpath('string(./SourceRef)').encode('utf-8')
      tmp_dict['target']  = status.xpath('string(./TargetRef)').encode('utf-8')
      status_list.append(tmp_dict)
    return status_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
675

676 677 678 679
  def getDataText(self, action):
    """
    return the section data in text form, it's usefull for the VCardConduit
    """
680 681 682 683
    data = action.xpath('string(Item/Data)')
    if isinstance(data, unicode):
      data = data.encode('utf-8')
    return data
684

Jean-Paul Smets's avatar
Jean-Paul Smets committed
685 686 687 688
  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
689 690 691 692
    if action.xpath('.//Item/Data') not in ([], None):
      data_node = action.xpath('.//Item/Data')[0]
      if data_node.hasChildNodes():
        return data_node.childNodes[0]
693
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
694 695 696 697 698

  def getPartialData(self, action):
    """
      Return the node starting with <object....> of the action
    """
699 700 701
    comment_list = action.xpath('.//Item/Data[comment()]')
    if comment_list != []:
      return comment_list[0].childNodes[0].data.encode('utf-8')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
702 703
    return None

704
  def getActionId(self, action):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
705 706 707
    """
      Return the rid of the object described by the action
    """
708 709 710 711 712 713
    id = action.xpath('string(.//Item/Source/LocURI)')
    if id in (None, ''):
      id = action.xpath('string(.//Item/Target/LocURI)')
    if isinstance(id, unicode):
      id = id.encode('utf-8')
    return id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
714 715 716 717 718

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
719
    return action.xpath('Item/MoreData') not in ([],None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
720 721 722 723 724

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
725 726 727 728
    action_type = action.xpath('string(Meta/Type)')
    if isinstance(action_type, unicode):
      action_type = action_type.encode('utf-8')
    return action_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
729

730 731 732 733
  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
734
    return node.xpath('*')
735

736 737 738 739 740
  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
741
    for subnode in node.childNodes or []:
742 743 744
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list
745

746 747 748 749
  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
750
    return node.xpath('@*')
751

752 753
  def getSyncMLData(self, domain=None, remote_xml=None, cmd_id=0,
                    subscriber=None, xml_confirmation=None, conduit=None,
Nicolas Delaby's avatar
Nicolas Delaby committed
754
                    max=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
755
    """
756 757
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
758 759 760

    if object is not None, this usually means we want to set the
    actual xupdate on the signature.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
761
    """
762
    #LOG('getSyncMLData starting...', DEBUG, domain.getId())
Sebastien Robin's avatar
Sebastien Robin committed
763 764
    if isinstance(conduit, str):
      conduit = self.getConduitByName(conduit)
765
    local_gid_list = []
766
    syncml_data = kw.get('syncml_data','')
Nicolas Delaby's avatar
Nicolas Delaby committed
767
    result = {'finished':1}
768 769
    if isinstance(remote_xml, str) or isinstance(remote_xml, unicode):
      remote_xml = Parse(remote_xml)
770
    if subscriber.getRemainingObjectPathList() is None:
771
      object_list = domain.getObjectList()
772
      object_path_list = [x.getPhysicalPath() for x in object_list]
773
      subscriber.setRemainingObjectPathList(object_path_list)
774 775 776 777 778 779
      if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_VCARD']:
        #here the method getGidFromObject don't return the good gid because
        #the conduit use the catalog to determine it and object are not yet
        #cataloged so if there is many times the same gid, we must count it
        gid_not_encoded_list = []
        for object in object_list:
780
          #LOG('getSyncMLData :', DEBUG, 'object:%s,  objectTitle:%s, local_gid_list:%s' % (object, object.getTitle(), local_gid_list))
781 782 783 784 785 786 787
          gid = b16decode(domain.getGidFromObject(object))
          if gid in gid_not_encoded_list:
            number = len([item for item in gid_not_encoded_list if item.startswith(gid)])
            if number > 0:
              gid = gid+'__'+str(number+1)
          gid_not_encoded_list.append(gid)
          local_gid_list.append(b16encode(gid))
788
          #LOG('getSyncMLData :', DEBUG,'gid_not_encoded_list:%s, local_gid_list:%s, gid:%s' % (gid_not_encoded_list, local_gid_list, gid))
789 790
      else:
        local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list)
791 792

      # Objects to remove
793
      #LOG('getSyncMLData remove object to remove ...', DEBUG, '')
794
      object_gid_deleted = []
795
      for object_gid in subscriber.getGidList():
Nicolas Delaby's avatar
Nicolas Delaby committed
796
        if object_gid not in local_gid_list:
797
          # This is an object to remove
798
          signature = subscriber.getSignatureFromGid(object_gid)
799 800
          if signature.getStatus() != self.PARTIAL:
            # If partial, then we have a signature but no local object
801
            xml_object = signature.getXML()
802
            if xml_object is not None: # This prevent to delete an object that
803
                                       # we were not able to create
804
              rid = signature.getRid()
805
              object_gid_deleted.append(object_gid)
806 807 808 809 810
              syncml_data += self.deleteXMLObject(
                                      xml_object=signature.getXML() or '',
                                      object_gid=object_gid,
                                      rid=rid,
                                      cmd_id=cmd_id)
811
              cmd_id += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
812 813 814 815
      #delete Signature if object does not exist anymore
      for known_gid in subscriber.getGidList():
        if known_gid not in local_gid_list:
          subscriber.delSignature(known_gid)
816
    local_gid_list = []
817
    loop = 0
818
    for object_path in subscriber.getRemainingObjectPathList():
Nicolas Delaby's avatar
Nicolas Delaby committed
819 820 821
      if max is not None and loop >= max:
        result['finished'] = 0
        break
822
      #LOG('getSyncMLData object_path', DEBUG, object_path)
823
      object = self.unrestrictedTraverse(object_path)
824
      status = self.SENT
825
      object_gid = domain.getGidFromObject(object)
826
      if object_gid in ('', None):
Nicolas Delaby's avatar
Nicolas Delaby committed
827
        continue
828
      local_gid_list += [object_gid]
829
      force = 0
Nicolas Delaby's avatar
Nicolas Delaby committed
830
      if syncml_data.count('\n') < self.MAX_LINES and not \
831 832
          object.id.startswith('.'):
        # If not we have to cut
833 834 835 836 837 838 839
        #LOG('getSyncMLData', DEBUG, 'object_path: %s' % '/'.join(object_path))
        #LOG('getSyncMLData', DEBUG, 'xml_mapping: %s' % str(domain.getXMLMapping()))
        #LOG('getSyncMLData', DEBUG, 'code: %s' % str(self.getAlertCodeFromXML(remote_xml)))
        #LOG('getSyncMLData', DEBUG, 'gid_list: %s' % str(local_gid_list))
        #LOG('getSyncMLData', DEBUG, 'subscriber.getGidList: %s' % subscriber.getGidList())
        #LOG('getSyncMLData', DEBUG, 'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
        #LOG('getSyncMLData', DEBUG, 'alert_code == slowsync: %s' % str(self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC))
840

Nicolas Delaby's avatar
Nicolas Delaby committed
841 842 843
        signature = subscriber.getSignatureFromGid(object_gid)
        ## Here we first check if the object was modified or not by looking at dates
        #if signature is not None:
844
          #LOG('getSyncMLData', DEBUG, 'signature.getStatus: %s' % signature.getStatus())
845 846 847
        status = self.SENT
        more_data=0
        # For the case it was never synchronized, we have to send everything
Nicolas Delaby's avatar
Nicolas Delaby committed
848
        if signature is not None and signature.getXMLMapping() is None:
849
          pass
Nicolas Delaby's avatar
Nicolas Delaby committed
850
        elif signature is None or (signature.getXML() is None and \
851
            signature.getStatus() != self.PARTIAL) or \
852
            self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC:
853
          #LOG('getSyncMLData', DEBUG, 'Current object.getPath: %s' % object.getPath())
854
          xml_object = domain.getXMLFromObject(object)
855
          xml_string = xml_object
856 857 858
          if isinstance(xml_string, unicode):
            xml_string = xml_object.encode('utf-8')
          gid = subscriber.getGidFromObject(object)
859
          signature = Signature(id=gid, object=object)
860 861
          signature.setTempXML(xml_object)
          if xml_string.count('\n') > self.MAX_LINES:
862
            if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
863
              xml_string = xml_string.replace('--','@-@@-@')
864 865 866 867 868 869 870 871
            more_data=1
            i = 0
            short_string = ''
            rest_string = xml_string
            while i < self.MAX_LINES:
              short_string += rest_string[:rest_string.find('\n')+1]
              rest_string = xml_string[len(short_string):]
              i += 1
872
            #LOG('getSyncMLData', DEBUG, 'setPartialXML with: %s' % str(rest_string))
873
            signature.setPartialXML(rest_string)
874
            status = self.PARTIAL
875 876
            signature.setAction('Add')
            xml_string = '<!--' + short_string + '-->'
877 878 879
          gid = signature.getRid()#in fisrt, we try with rid if there is one
          if gid == None:
            gid = signature.getGid()
880 881 882 883 884 885 886
          syncml_data += self.addXMLObject(
                                  cmd_id=cmd_id,
                                  object=object,
                                  gid=gid,
                                  xml_string=xml_string,
                                  more_data=more_data,
                                  media_type=subscriber.getMediaType())
887 888 889
          cmd_id += 1
          signature.setStatus(status)
          subscriber.addSignature(signature)
890 891 892
        elif signature.getStatus() == self.NOT_SYNCHRONIZED \
            or signature.getStatus() == self.PUB_CONFLICT_MERGE:
          # We don't have synchronized this object yet
893
          xml_object = domain.getXMLFromObject(object)
894 895
          #LOG('getSyncMLData', DEBUG, 'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          #LOG('getSyncMLData', DEBUG, 'getStatus: %s' % str(signature.getStatus()))
896 897 898 899 900 901
          if signature.getStatus() == self.PUB_CONFLICT_MERGE:
            xml_confirmation += self.SyncMLConfirmation(
                                            cmd_id=cmd_id,
                                            source_ref=signature.getGid(),
                                            sync_code=self.CONFLICT_MERGE,
                                            cmd='Replace')
902
          set_synchronized = 1
903
          if not signature.checkMD5(xml_object):
904
            set_synchronized = 0
905
            # This object has changed on this side, we have to generate some xmldiff
906
            if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
907
              xml_string = self.getXupdateObject(xml_object, signature.getXML())
908
            else: #if there is no xml, we re-send all the object
909
              xml_string = xml_object
910
            if xml_string.count('\n') > self.MAX_LINES:
911 912
              # This make comment fails, so we need to replace
              if xml_string.find('--') >= 0:
913
                xml_string = xml_string.replace('--', '@-@@-@')
914
              i = 0
915 916 917
              more_data = 1
              short_string_list = []
              short_string_list_ap = short_string_list.append
918 919
              rest_string = xml_string
              while i < self.MAX_LINES:
920 921
                short_string_list_ap(rest_string[:rest_string.find('\n')+1])
                rest_string = xml_string[len(''.join(short_string_list)):]
922 923 924 925
                i += 1
              signature.setPartialXML(rest_string)
              status = self.PARTIAL
              signature.setAction('Replace')
926
              xml_string = '<!--' + ''.join(short_string_list) + '-->'
927
            signature.setStatus(status)
928
            if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
929
              xml_string = xml_object
930 931
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
932 933 934 935 936 937
            syncml_data += self.replaceXMLObject(
                                        cmd_id=cmd_id, object=object,
                                        gid=gid, rid=rid,
                                        xml_string=xml_string,
                                        more_data=more_data,
                                        media_type=subscriber.getMediaType())
938 939
            cmd_id += 1
            signature.setTempXML(xml_object)
940 941
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
942
          #LOG('getSyncMLData subscriber_xupdate', DEBUG, subscriber_xupdate)
943 944
          if subscriber_xupdate is not None:
            old_xml = signature.getXML()
945 946 947 948 949 950
            conduit.updateNode(
                        xml=subscriber_xupdate,
                        object=object,
                        previous_xml=old_xml,
                        force=(domain.getDomainType() == self.SUB),
                        simulate=0)
951
            xml_object = domain.getXMLFromObject(object)
952 953 954
            signature.setTempXML(xml_object)
          if set_synchronized: # We have to do that after this previous update
            # We should not have this case when we are in CONFLICT_MERGE
955
            signature.setStatus(self.SYNCHRONIZED)
956
        elif signature.getStatus() == self.PUB_CONFLICT_CLIENT_WIN:
957
          # We have decided to apply the update
958
          # XXX previous_xml will be geXML instead of getTempXML because
959 960
          # some modification was already made and the update
          # may not apply correctly
961
          xml_update = signature.getPartialXML()
962 963 964 965 966 967 968 969 970 971
          conduit.updateNode(
                      xml=signature.getPartialXML(),
                      object=object,
                      previous_xml=signature.getXML(),
                      force=1)
          xml_confirmation += self.SyncMLConfirmation(
                                          cmd_id=cmd_id,
                                          target_ref=object_gid,
                                          sync_code=self.CONFLICT_CLIENT_WIN,
                                          cmd='Replace')
972
          signature.setStatus(self.SYNCHRONIZED)
973
        elif signature.getStatus() == self.PARTIAL:
974 975 976 977
          xml_string = signature.getPartialXML()
          if xml_string.count('\n') > self.MAX_LINES:
            i = 0
            more_data=1
978 979
            short_string_list = []
            short_string_list_ap = short_string_list.append
980 981
            rest_string = xml_string
            while i < self.MAX_LINES:
982 983
              short_string_list_ap(rest_string[:rest_string.find('\n')+1])
              rest_string = xml_string[len(''.join(short_string_list)):]
984 985
              i += 1
            signature.setPartialXML(rest_string)
986
            xml_string = ''.join(short_string_list)
987
            status = self.PARTIAL
988
          if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
989
            xml_string = xml_string.replace('--','@-@@-@')
990 991
          xml_string = '<!--' + xml_string + '-->'
          signature.setStatus(status)
992
          if(subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']):
993
            xml_string = domain.getXMLFromObject(object)
994
          if signature.getAction() == 'Replace':
995 996
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
997 998 999 1000 1001 1002 1003 1004 1005
            syncml_data += self.replaceXMLObject(
                                       cmd_id=cmd_id,
                                       object=object,
                                       gid=gid,
                                       rid=rid,
                                       xml_string=xml_string,
                                       more_data=more_data,
                                       media_type=subscriber.getMediaType())
          elif signature.getAction() == 'Add':
1006 1007 1008
            gid = signature.getRid()#in fisrt, we try with rid if there is one
            if gid == None:
              gid = signature.getGid()
1009 1010 1011 1012 1013 1014 1015
            syncml_data += self.addXMLObject(
                                  cmd_id=cmd_id,
                                  object=object,
                                  gid=gid,
                                  xml_string=xml_string,
                                  more_data=more_data,
                                  media_type=subscriber.getMediaType())
Sebastien Robin's avatar
Sebastien Robin committed
1016 1017
        if not more_data:
          subscriber.removeRemainingObjectPath(object_path)
1018
      else:
Nicolas Delaby's avatar
Nicolas Delaby committed
1019
        result['finished'] = 1
1020
        break
1021 1022 1023 1024 1025
      loop += 1
    result['syncml_data'] = syncml_data
    result['xml_confirmation'] = xml_confirmation
    result['cmd_id'] = cmd_id
    return result
1026

1027
  def applyActionList(self, domain=None, subscriber=None, cmd_id=0,
1028
                      remote_xml=None, conduit=None, simulate=0):
1029 1030 1031
    """
    This just look to a list of action to do, then id applies
    each action one by one, thanks to a conduit
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1032
    """
1033
    xml_confirmation = ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1034
    has_next_action = 0
1035
    gid_from_xml_list = []
1036
    destination = self.unrestrictedTraverse(domain.getDestinationPath())
1037 1038
    #LOG('applyActionList args', DEBUG, 'domain : %s\n subscriber : %s\n cmd_id : %s' % (domain.getPath(), subscriber.getPath(), cmd_id))
    #LOG('applyActionList', DEBUG, self.getSyncActionList(remote_xml))
1039
    for action in self.getSyncActionList(remote_xml):
1040 1041
      if isinstance(action, unicode):
        action = action.encode('utf-8')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1042 1043 1044
      conflict_list = []
      status_code = self.SUCCESS
      # Thirst we have to check the kind of action it is
1045
      partial_data = self.getPartialData(action)
1046
      rid = self.getActionId(action)
1047
      if action.nodeName != 'Delete':
1048 1049 1050
        if hasattr(conduit, 'getGidFromXML') and \
            conduit.getGidFromXML(self.getDataText(action),
            gid_from_xml_list) not in ('', None):
1051
          gid = conduit.getGidFromXML(self.getDataText(action),
1052 1053 1054
              gid_from_xml_list)
          gid_from_xml_list.append(gid)
          gid = b16encode(gid)
1055 1056 1057 1058
        else:
          gid=rid
      else:
        gid=rid
1059
      object_id = domain.generateNewIdWithGenerator(object=destination, gid=gid)
1060
      signature = subscriber.getSignatureFromGid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1061
      if signature is not None and rid != gid:
1062 1063 1064
        #in this case, the object was created on another subscriber than erp5
        # and we should save it's remote id
        signature.setRid(rid)
1065
      #LOG('gid == rid ?', DEBUG, 'gid=%s, rid=%s' % (gid, rid))
1066
      object = subscriber.getObjectFromGid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1067
      if object is None and not domain.getSynchronizeWithERP5Sites():
1068 1069 1070 1071 1072
        #if the object is None, that could mean two things :
        # - the object to synchronize don't exists
        # - the id is not a gid but a rid
        #here we try to find an object with the rid
        #LOG('applyActionList, try to find an object with rid', DEBUG, '')
1073
        object = subscriber.getObjectFromRid(rid)
1074 1075 1076
        signature = subscriber.getSignatureFromRid(rid)
        if signature not in ('', None):
          gid = signature.getId()
1077
      #LOG('applyActionList subscriber.getObjectFromGid %s' % gid, DEBUG, object)
Nicolas Delaby's avatar
Nicolas Delaby committed
1078
      if signature is None:
1079
        #LOG('applyActionList, signature is None', DEBUG, signature)
1080
        if gid == rid:
Nicolas Delaby's avatar
Nicolas Delaby committed
1081
          signature = Signature(id=gid, status=self.NOT_SYNCHRONIZED,
1082 1083 1084 1085 1086
              object=object).__of__(subscriber)
        else:
          signature = Signature(rid=rid, id=gid, status=self.NOT_SYNCHRONIZED,
              object=object).__of__(subscriber)
        signature.setObjectId(object_id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1087 1088
        subscriber.addSignature(signature)
      force = signature.getForce()
1089
      if self.checkActionMoreData(action) == 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1090 1091
        data_subnode = None
        if partial_data != None:
1092 1093 1094 1095 1096
          signature_partial_xml = signature.getPartialXML()
          if signature_partial_xml is not None:
            data_subnode = signature.getPartialXML() + partial_data
          else:
            data_subnode = partial_data
1097
          #LOG('applyActionList', DEBUG, 'data_subnode: %s' % data_subnode)
1098
          if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
1099 1100
            data_subnode = Parse(data_subnode)
            data_subnode = data_subnode.childNodes[0] # Because we just created a new xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1101 1102
          # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
        else:
1103
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
1104
            data_subnode = self.getDataText(action)
1105
          else:
1106 1107
            data_subnode = self.getDataSubNode(action)
        if action.nodeName == 'Add':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1108
          # Then store the xml of this new subobject
1109
          reset = 0
1110
          if object is None:
1111 1112 1113
            add_data = conduit.addNode(xml=data_subnode,
                                       object=destination,
                                       object_id=object_id)
1114 1115
            if add_data['conflict_list'] not in ('', None, []):
              conflict_list += add_data['conflict_list']
1116 1117
            # Retrieve directly the object from addNode
            object = add_data['object']
Nicolas Delaby's avatar
Nicolas Delaby committed
1118 1119
            if object is not None:
              signature.setPath(object.getPhysicalPath())
1120
              signature.setObjectId(object.getId())
1121
          else:
1122
            reset = 1
1123 1124
            #Object was retrieve but need to be updated without recreated
            #usefull when an object is only deleted by workflow.
1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
            actual_xml = subscriber.getXMLFromObject(object = object, force=1)
            if data_subnode is not None:
              if type(data_subnode) != type(''):
                string_io = StringIO()
                PrettyPrint(data_subnode, stream=string_io)
                xml_string = string_io.getvalue()
              data_subnode = self.getXupdateObject(xml_string, actual_xml)
            conflict_list += conduit.updateNode(
                                        xml=data_subnode,
                                        object=object,
                                        previous_xml=signature.getXML(),
                                        force=force,
                                        simulate=simulate)
            xml_object = domain.getXMLFromObject(object)
            signature.setTempXML(xml_object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1140
          if object is not None:
1141
            #LOG('applyActionList', DEBUG, 'addNode, found the object')
1142 1143 1144 1145 1146 1147 1148 1149
            if reset:
              #After a reset we want copy the LAST XML view on Signature.
              #this implementation is not sufficient, need to be improved.
              string_io = StringIO()
              PrettyPrint(data_subnode, stream=string_io)
              xml_object = string_io.getvalue()
            else:
              xml_object = domain.getXMLFromObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1150
            signature.setStatus(self.SYNCHRONIZED)
1151
            #signature.setId(object.getId())
1152
            signature.setPath(object.getPhysicalPath())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1153
            signature.setXML(xml_object)
1154
            xml_confirmation += self.SyncMLConfirmation(
1155 1156
                cmd_id=cmd_id,
                cmd='Add',
1157 1158
                sync_code=self.ITEM_ADDED,
                remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1159
            cmd_id +=1
1160
        elif action.nodeName == 'Replace':
1161
          #LOG('applyActionList', DEBUG, 'object: %s will be updated...' % str(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1162
          if object is not None:
1163
            #LOG('applyActionList', DEBUG, 'object: %s will be updated...' % object.id)
1164
            signature = subscriber.getSignatureFromGid(gid)
1165 1166
            if signature is None:
              signature = subscriber.getSignatureFromRid(gid)
1167
            #LOG('applyActionList', DEBUG, 'previous signature: %s' % str(signature))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1168
            previous_xml = signature.getXML()
1169 1170 1171 1172 1173 1174
            conflict_list += conduit.updateNode(
                                        xml=data_subnode,
                                        object=object,
                                        previous_xml=signature.getXML(),
                                        force=force,
                                        simulate=simulate)
1175
            xml_object = domain.getXMLFromObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1176 1177 1178 1179
            signature.setTempXML(xml_object)
            if conflict_list != []:
              status_code = self.CONFLICT
              signature.setStatus(self.CONFLICT)
1180 1181
              signature.setConflictList(signature.getConflictList() \
                  + conflict_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1182
              string_io = StringIO()
1183
              PrettyPrint(data_subnode, stream=string_io)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1184 1185
              data_subnode_string = string_io.getvalue()
              signature.setPartialXML(data_subnode_string)
1186
            elif not simulate:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1187
              signature.setStatus(self.SYNCHRONIZED)
1188 1189 1190 1191 1192
            xml_confirmation += self.SyncMLConfirmation(
                                              cmd_id=cmd_id,
                                              cmd='Replace',
                                              sync_code=status_code,
                                              remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1193
            cmd_id +=1
1194
            if simulate:
Sebastien Robin's avatar
Sebastien Robin committed
1195
              # This means we are on the publisher side and we want to store
1196 1197 1198
              # the xupdate from the subscriber and we also want to generate
              # the current xupdate from the last synchronization
              string_io = StringIO()
1199
              PrettyPrint(data_subnode, stream=string_io)
1200
              data_subnode_string = string_io.getvalue()
1201
              #LOG('applyActionList, subscriber_xupdate:', TRACE, data_subnode_string)
1202 1203
              signature.setSubscriberXupdate(data_subnode_string)

1204
        elif action.nodeName == 'Delete':
1205
          object_id = signature.getId()
1206
          #LOG('applyActionList Delete on : ', DEBUG, (signature.getId(), subscriber.getObjectFromGid(object_id)))
1207
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
1208
            data_subnode = self.getDataText(action)
1209
          else:
1210
            data_subnode = self.getDataSubNode(action)
1211
          #LOG('applyActionList, object gid to delete :', 0, subscriber.getObjectFromGid(object_id))
1212 1213
          if subscriber.getObjectFromGid(object_id) not in (None, ''):
          #if the object exist:
1214 1215 1216 1217
            conduit.deleteNode(
                        xml=data_subnode,
                        object=destination,
                        object_id=subscriber.getObjectFromGid(object_id).getId())
1218 1219
            subscriber.delSignature(gid)
          xml_confirmation += self.SyncMLConfirmation(
1220 1221 1222 1223
                                            cmd_id=cmd_id,
                                            cmd='Delete',
                                            sync_code=status_code,
                                            remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1224 1225 1226
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        previous_partial = signature.getPartialXML() or ''
1227
        previous_partial += partial_data
1228
        #LOG('applyActionList', DEBUG, 'setPartialXML: %s' % str(previous_partial))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1229
        signature.setPartialXML(previous_partial)
1230 1231
        #LOG('applyActionList', DEBUG, 'previous_partial: %s' % str(previous_partial))
        #LOG('applyActionList', DEBUG, 'waiting more data for :%s' % signature.getId())
1232 1233 1234 1235 1236
        xml_confirmation += self.SyncMLConfirmation(
                                          cmd_id=cmd_id,
                                          cmd=action.nodeName,
                                          sync_code=self.WAITING_DATA,
                                          remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1237 1238 1239
      if conflict_list != [] and signature is not None:
        # We had a conflict
        signature.setStatus(self.CONFLICT)
1240

1241
    return (xml_confirmation, has_next_action, cmd_id)
1242

1243
  def applyStatusList(self, subscriber=None, remote_xml=None):
1244 1245 1246 1247
    """
    This read a list of status list (ie syncml confirmations).
    This method have to change status codes on signatures
    """
1248
    status_list = self.getSyncBodyStatusList(remote_xml)
1249 1250
    has_status_list = 0
    destination_waiting_more_data = 0
1251 1252 1253 1254
    if status_list != []:
      for status in status_list:
        status_cmd = status['cmd']
        object_gid = status['source']
1255 1256
        if object_gid in ('', None, []):
          object_gid = status['target']
1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
        status_code = int(status['code'])
        if status_cmd in ('Add','Replace'):
          has_status_list = 1
          signature = subscriber.getSignatureFromGid(object_gid)
          if signature == None:
            signature = subscriber.getSignatureFromRid(object_gid)
          if status_code == self.CHUNK_OK:
            destination_waiting_more_data = 1
            signature.setStatus(self.PARTIAL)
          elif status_code == self.CONFLICT:
            signature.setStatus(self.CONFLICT)
          elif status_code == self.CONFLICT_MERGE:
            # We will have to apply the update, and we should not care 
            # about conflicts, so we have to force the update
            signature.setStatus(self.NOT_SYNCHRONIZED)
            signature.setForce(1)
          elif status_code == self.CONFLICT_CLIENT_WIN:
            # The server was agree to apply our updates, nothing to do
            signature.setStatus(self.SYNCHRONIZED)
          elif status_code in (self.SUCCESS, self.ITEM_ADDED):
            signature.setStatus(self.SYNCHRONIZED)
        elif status_cmd == 'Delete':
Fabien Morin's avatar
Fabien Morin committed
1279
          has_status_list = 1
1280
          if status_code == self.SUCCESS:
1281 1282 1283 1284
            signature = subscriber.getSignatureFromGid(object_gid)
            if signature is None and \
            not(subscriber.getSynchronizeWithERP5Sites()):
              signature = subscriber.getSignatureFromRid(object_gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1285 1286
            if signature is not None:
              subscriber.delSignature(signature.getGid())
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
    return (destination_waiting_more_data, has_status_list)


class XMLSyncUtils(XMLSyncUtilsMixin):

  def Sync(self, id, msg=None, RESPONSE=None):
    """
    This is the main method for synchronization
    """
    pass

  def SyncInit(self, domain):
    """
    Initialization of a synchronization, this is
    used for the first message of every synchronization
    """
    pass

1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323
  def getConduitByName(self, conduit_name):
    """
    Get Conduit Object by given name.
    The Conduit can be located in Any Products according to naming Convention
    Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name.
    By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module>
    """
    from Products.ERP5SyncML import Conduit
    if conduit_name.startswith('Products'):
      path = conduit_name
      conduit_name = conduit_name.split('.')[-1]
      conduit_module = __import__(path, globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
    else:
      conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
                                  globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
    return conduit

1324 1325 1326 1327
  def SyncModif(self, domain, remote_xml):
    """
    Modification Message, this is used after the first
    message in order to send modifications.
1328 1329
    Send the server modification, this happens after the Synchronization
    initialization
1330
    """
1331
    has_response = 0 #check if syncmodif replies to this messages
1332
    cmd_id = 1 # specifies a SyncML message-unique command identifier
1333
    #LOG('SyncModif', DEBUG, 'Starting... domain: %s' % domain.getId())
Sebastien Robin's avatar
Sebastien Robin committed
1334
    first_node = remote_xml.childNodes[0]
1335 1336 1337
    # Get informations from the header
    xml_header = first_node.childNodes[1]
    if xml_header.nodeName != "SyncHdr":
1338
      LOG('SyncModif', INFO, 'This is not a SyncML Header')
Sebastien Robin's avatar
Sebastien Robin committed
1339
      raise ValueError, "Sorry, This is not a SyncML Header"
1340 1341

    subscriber = domain # If we are the client, this is fine
1342
    simulate = 0 # used by applyActionList, should be 0 for client
1343
    if domain.domain_type == self.PUB:
1344
      simulate = 1
1345
      subscription_url = self.getSubscriptionUrlFromXML(xml_header)
1346
      subscriber = domain.getSubscriber(subscription_url)
1347

1348 1349
    # We have to check if this message was not already, this can be dangerous
    # to update two times the same object
Sebastien Robin's avatar
Sebastien Robin committed
1350 1351 1352
    message_id = self.getMessageId(remote_xml)
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
1353
      LOG('SyncModif, no correct message:', INFO, "sending again...")
1354
      last_xml = subscriber.getLastSentMessage()
1355
      LOG("SyncModif last_xml :", INFO, last_xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
1356 1357 1358 1359
      string_io = StringIO()
      PrettyPrint(remote_xml, stream=string_io)
      remote_xml = string_io.getvalue()
      LOG("SyncModif remote_xml :", INFO, remote_xml)
1360 1361 1362
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
1363 1364 1365 1366 1367 1368
          self.sendResponse(
                    from_url=domain.publication_url,
                    to_url=subscriber.subscription_url,
                    sync_id=domain.getTitle(),
                    xml=last_xml,domain=domain,
                    content_type=domain.getSyncContentType())
1369
        elif domain.domain_type == self.SUB:
1370 1371 1372 1373 1374 1375 1376 1377
          self.sendResponse(
                    from_url=domain.subscription_url,
                    to_url=domain.publication_url,
                    sync_id=domain.getTitle(),
                    xml=last_xml,
                    domain=domain,
                    content_type=domain.getSyncContentType())
      return {'has_response':has_response, 'xml':last_xml}
1378 1379
    subscriber.setLastSentMessage('')

1380 1381
    # First apply the list of status codes
    (destination_waiting_more_data,has_status_list) = self.applyStatusList(
1382 1383
                                                      subscriber=subscriber,
                                                      remote_xml=remote_xml)
1384

1385
    alert_code = self.getAlertCodeFromXML(remote_xml)
Kevin Deldycke's avatar
Kevin Deldycke committed
1386
    # Import the conduit and get it
1387
    conduit = self.getConduitByName(subscriber.getConduit())
1388
    # Then apply the list of actions
1389
    (xml_confirmation, has_next_action, cmd_id) = self.applyActionList(
1390 1391 1392 1393 1394
                                          cmd_id=cmd_id,
                                          domain=domain,
                                          subscriber=subscriber,
                                          remote_xml=remote_xml,
                                          conduit=conduit, simulate=simulate)
Sebastien Robin's avatar
Sebastien Robin committed
1395 1396 1397
    xml_list = []
    xml = xml_list.append
    xml('<SyncML>\n')
1398

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1399 1400
    # syncml header
    if domain.domain_type == self.PUB:
1401 1402 1403 1404 1405
      xml(self.SyncMLHeader(
                  subscriber.getSessionId(),
                  subscriber.incrementMessageId(),
                  subscriber.getSubscriptionUrl(),
                  domain.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1406
    elif domain.domain_type == self.SUB:
1407 1408 1409 1410
      xml(self.SyncMLHeader(
                  domain.getSessionId(), domain.incrementMessageId(),
                  domain.getPublicationUrl(),
                  domain.getSubscriptionUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1411 1412 1413 1414
    # Add or replace objects
    syncml_data = ''

    # syncml body
Sebastien Robin's avatar
Sebastien Robin committed
1415
    xml(' <SyncBody>\n')
1416

1417 1418 1419 1420 1421 1422
    xml_status, cmd_id = self.SyncMLStatus(
                                    remote_xml,
                                    self.SUCCESS,
                                    cmd_id,
                                    subscriber.getNextAnchor(),
                                    subscription=subscriber).values()
1423
    xml(xml_status)
1424

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1425 1426 1427
    destination_url = ''
    # alert message if we want more data
    if destination_waiting_more_data == 1:
1428 1429 1430 1431 1432 1433 1434
      xml(self.SyncMLAlert(
                    cmd_id,
                    self.WAITING_DATA,
                    subscriber.getTargetURI(),
                    subscriber.getSourceURI(),
                    subscriber.getLastAnchor(),
                    subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1435
    # Now we should send confirmations
1436
    cmd_id_before_getsyncmldata = cmd_id
1437
    cmd_id = cmd_id+1
1438
    if domain.getActivityEnabled():
1439 1440 1441 1442 1443
      #use activities to get SyncML data.
      if not (isinstance(remote_xml, str) or isinstance(remote_xml, unicode)):
        string_io = StringIO()
        PrettyPrint(remote_xml,stream=string_io)
        remote_xml = string_io.getvalue()
1444 1445
      domain.activate(activity='SQLQueue',
                           tag=domain.getId()).activateSyncModif(
1446 1447 1448 1449 1450 1451 1452 1453 1454 1455
                      domain_relative_url = domain.getRelativeUrl(),
                      remote_xml = remote_xml,
                      subscriber_relative_url = subscriber.getRelativeUrl(),
                      cmd_id = cmd_id,
                      xml_confirmation = xml_confirmation,
                      syncml_data = '',
                      cmd_id_before_getsyncmldata = cmd_id_before_getsyncmldata,
                      xml_list = xml_list,
                      has_status_list = has_status_list,
                      has_response = has_response )
Nicolas Delaby's avatar
Nicolas Delaby committed
1456
      return {'has_response':1, 'xml':''}
1457 1458
    else:
      result = self.getSyncMLData(domain=domain,
1459 1460
                             remote_xml=remote_xml,
                             subscriber=subscriber,
1461 1462
                             cmd_id=cmd_id,
                             xml_confirmation=xml_confirmation,
1463
                             conduit=conduit,
1464
                             max=None)
1465 1466 1467 1468 1469
      syncml_data = result['syncml_data']
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      return self.sendSyncModif(syncml_data, cmd_id_before_getsyncmldata,
                                subscriber, domain, xml_confirmation,
1470
                                remote_xml, xml_list, has_status_list,
1471
                                has_response)
1472

1473
  def activateSyncModif(self, **kw):
1474 1475 1476
    domain = self.unrestrictedTraverse(kw['domain_relative_url'])
    subscriber = self.unrestrictedTraverse(kw['subscriber_relative_url'])
    conduit = subscriber.getConduit()
1477 1478 1479 1480 1481 1482
    result = self.getSyncMLData(
                        domain = domain,
                        subscriber = subscriber,
                        conduit = conduit,
                        max = self.MAX_OBJECTS,
                        **kw)
1483
    syncml_data = result['syncml_data']
1484
    cmd_id = result['cmd_id']
1485
    kw['syncml_data'] = syncml_data
1486
    kw['cmd_id'] = cmd_id
1487 1488
    finished = result['finished']
    if not finished:
1489 1490
      domain.activate(activity='SQLQueue',
                           tag=domain.getId()).activateSyncModif(**kw)
1491 1492 1493 1494 1495 1496 1497 1498
    else:
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      cmd_id_before_getsyncmldata = kw['cmd_id_before_getsyncmldata']
      remote_xml = Parse(kw['remote_xml'])
      xml_list = kw['xml_list']
      has_status_list = kw['has_status_list']
      has_response = kw['has_response']
1499 1500 1501 1502 1503 1504 1505 1506 1507 1508
      return self.sendSyncModif(
                        syncml_data,
                        cmd_id_before_getsyncmldata,
                        subscriber,
                        domain,
                        xml_confirmation,
                        remote_xml,
                        xml_list,
                        has_status_list,
                        has_response)
1509 1510 1511 1512 1513

  def sendSyncModif(self, syncml_data, cmd_id_before_getsyncmldata, subscriber,
                    domain, xml_confirmation, remote_xml, xml_list,
                    has_status_list, has_response):
    xml = xml_list.append
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1514
    if syncml_data != '':
Sebastien Robin's avatar
Sebastien Robin committed
1515
      xml('  <Sync>\n')
1516
      xml('   <CmdID>%s</CmdID>\n' % cmd_id_before_getsyncmldata)
1517 1518 1519 1520 1521 1522 1523 1524
      if subscriber.getTargetURI() not in ('', None):
        xml('   <Target>\n')
        xml('    <LocURI>%s</LocURI>\n' % subscriber.getTargetURI())
        xml('   </Target>\n')
      if subscriber.getSourceURI() not in ('', None):
        xml('   <Source>\n')
        xml('    <LocURI>%s</LocURI>\n' % subscriber.getSourceURI())
        xml('   </Source>\n')
Sebastien Robin's avatar
Sebastien Robin committed
1525 1526
      xml(syncml_data)
      xml('  </Sync>\n')
1527
    xml(xml_confirmation)
Sebastien Robin's avatar
Sebastien Robin committed
1528 1529 1530 1531
    xml('  <Final/>\n')
    xml(' </SyncBody>\n')
    xml('</SyncML>\n')
    xml_a = ''.join(xml_list)
1532

1533
    if domain.domain_type == self.PUB: # We always reply
Sebastien Robin's avatar
Sebastien Robin committed
1534
      subscriber.setLastSentMessage(xml_a)
1535 1536 1537 1538 1539 1540 1541
      self.sendResponse(
                from_url=domain.publication_url,
                to_url=subscriber.subscription_url,
                sync_id=domain.getTitle(),
                xml=xml_a,
                domain=domain,
                content_type=domain.getSyncContentType())
Fabien Morin's avatar
Fabien Morin committed
1542
      if syncml_data == '':
Nicolas Delaby's avatar
Nicolas Delaby committed
1543
        LOG('this is the end of the synchronisation session !!!', INFO, domain.getId())
Fabien Morin's avatar
Fabien Morin committed
1544 1545
        subscriber.setAuthenticated(False)
        domain.setAuthenticated(False)
1546 1547 1548
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
Nicolas Delaby's avatar
Nicolas Delaby committed
1549
          (xml_confirmation, syncml_data) != ('', ''):
Sebastien Robin's avatar
Sebastien Robin committed
1550
        subscriber.setLastSentMessage(xml_a)
1551 1552 1553 1554
        self.sendResponse(
                  from_url=domain.subscription_url,
                  to_url=domain.publication_url,
                  sync_id=domain.getTitle(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1555
                  xml=xml_a, domain=domain,
1556
                  content_type=domain.getSyncContentType())
1557
        has_response = 1
Fabien Morin's avatar
Fabien Morin committed
1558
      else:
Nicolas Delaby's avatar
Nicolas Delaby committed
1559
        LOG('this is the end of the synchronisation session !!!', INFO, domain.getId())
Fabien Morin's avatar
Fabien Morin committed
1560
        domain.setAuthenticated(False)
Nicolas Delaby's avatar
Nicolas Delaby committed
1561
    return {'has_response':has_response, 'xml':xml_a}
1562 1563 1564 1565 1566

  def xml2wbxml(self, xml):
    """
    convert xml string to wbxml using a temporary file
    """
1567
    #LOG('xml2wbxml starting ...', DEBUG, '')
1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581
    import os
    f = open('/tmp/xml2wbxml', 'w')
    f.write(xml)
    f.close()
    os.system('/usr/bin/xml2wbxml -o /tmp/xml2wbxml /tmp/xml2wbxml')
    f = open('/tmp/xml2wbxml', 'r')
    wbxml = f.read()
    f.close()
    return wbxml

  def wbxml2xml(self, wbxml):
    """
    convert wbxml string to xml using a temporary file
    """
1582
    #LOG('wbxml2xml starting ...', DEBUG, '')
1583 1584 1585 1586 1587 1588 1589 1590 1591
    import os
    f = open('/tmp/wbxml2xml', 'w')
    f.write(wbxml)
    f.close()
    os.system('/usr/bin/wbxml2xml -o /tmp/wbxml2xml /tmp/wbxml2xml')
    f = open('/tmp/wbxml2xml', 'r')
    xml = f.read()
    f.close()
    return xml
1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623

  def PubSync(self, publication_path, msg=None, RESPONSE=None, subscriber=None):
    """
      This is the synchronization method for the server
    """
    from Products.ERP5SyncML.Publication import Subscriber
    #LOG('PubSync', DEBUG, 'Starting... publication: %s' % (publication_path))
    # Read the request from the client
    publication = self.unrestrictedTraverse(publication_path)
    xml_client = msg
    if xml_client is None:
      xml_client = self.readResponse(from_url=publication.getPublicationUrl())
    #LOG('PubSync', DEBUG, 'Starting... msg: %s' % str(xml_client))
    result = None

    if xml_client is not None:
      if isinstance(xml_client, str) or isinstance(xml_client, unicode):
        xml_client = Parse(xml_client)
      first_node = xml_client.childNodes[0]

      if first_node.nodeName != "SyncML":
        LOG('PubSync', INFO, 'This is not a SyncML Message')
        raise ValueError, "Sorry, This is not a SyncML Message"
      alert_code = self.getAlertCodeFromXML(xml_client)
      # Get informations from the header
      client_header = first_node.childNodes[1]
      if client_header.nodeName != "SyncHdr":
        LOG('PubSync', INFO, 'This is not a SyncML Header')
        raise ValueError, "Sorry, This is not a SyncML Header"
      subscription_url = self.getSubscriptionUrlFromXML(client_header)
      # Get the subscriber or create it if not already in the list
      subscriber = publication.getSubscriber(subscription_url)
1624
      if subscriber is None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1625
        subscriber = Subscriber(publication.generateNewId(), subscription_url)
1626 1627 1628
        subscriber.setXMLMapping(publication.getXMLMapping())
        subscriber.setConduit(publication.getConduit())
        publication.addSubscriber(subscriber)
1629
        subscriber = subscriber.__of__(publication)
1630
        # first synchronization
Nicolas Delaby's avatar
Nicolas Delaby committed
1631 1632 1633 1634
        result = self.PubSyncInit(publication=publication,
                                  xml_client=xml_client,
                                  subscriber=subscriber,
                                  sync_type=self.SLOW_SYNC)
1635 1636 1637 1638 1639
      elif self.checkAlert(xml_client) and \
          alert_code in (self.TWO_WAY, self.SLOW_SYNC, \
          self.ONE_WAY_FROM_SERVER):
        subscriber.setXMLMapping(publication.getXMLMapping())
        subscriber.setConduit(publication.getConduit())
Nicolas Delaby's avatar
Nicolas Delaby committed
1640 1641 1642 1643
        result = self.PubSyncInit(publication=publication,
                                  xml_client=xml_client,
                                  subscriber=subscriber,
                                  sync_type=alert_code)
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657
      else:
        #we log the user authenticated to do the synchronization with him
        if self.checkMap(xml_client) :
          self.setRidWithMap(xml_client, subscriber)
        if subscriber.isAuthenticated():
            uf = self.getPortalObject().acl_users
            user = uf.getUserById(subscriber.getUser()).__of__(uf)
            newSecurityManager(None, user)
            result = self.PubSyncModif(publication, xml_client)
        else:
          result = self.PubSyncModif(publication, xml_client)
    elif subscriber is not None:
      # This looks like we are starting a synchronization after
      # a conflict resolution by the user
Nicolas Delaby's avatar
Nicolas Delaby committed
1658 1659 1660 1661
      result = self.PubSyncInit(publication=publication,
                                xml_client=None,
                                subscriber=subscriber,
                                sync_type=self.TWO_WAY)
1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')
    elif result is not None:
      return result

  def SubSync(self, subscription_path, msg=None, RESPONSE=None):
    """
      This is the synchronization method for the client
    """
    response = None #check if subsync replies to this messages
    subscription = self.unrestrictedTraverse(subscription_path)
    if msg==None and (subscription.getSubscriptionUrl()).find('file')>=0:
      msg = self.readResponse(sync_id=subscription.getSubscriptionUrl(),
          from_url=subscription.getSubscriptionUrl())
    if msg==None:
      response = self.SubSyncInit(subscription)
    else:
      xml_client = msg
      if isinstance(xml_client, str) or isinstance(xml_client, unicode):
        xml_client = Parse(xml_client)
        status_list = self.getSyncBodyStatusList(xml_client)
        if status_list not in (None, []):
          status_code_syncHdr = status_list[0]['code']
          if status_code_syncHdr.isdigit():
            status_code_syncHdr = int(status_code_syncHdr)
          #LOG('SubSync status code : ', DEBUG, status_code_syncHdr)
          if status_code_syncHdr == self.AUTH_REQUIRED:
            if self.checkChal(xml_client):
              authentication_format, authentication_type = self.getChal(xml_client)
              #LOG('SubSync auth_required :', DEBUG, 'format:%s, type:%s' % (authentication_format, authentication_type))
              if authentication_format is not None and \
                  authentication_type is not None:
                subscription.setAuthenticationFormat(authentication_format)
                subscription.setAuthenticationType(authentication_type)
            else:
              raise ValueError, "Sorry, the server chalenge for an \
                  authentication, but the authentication format is not find"

            LOG('SubSync', INFO, 'Authentication required')
            response = self.SubSyncCred(subscription, xml_client)
          elif status_code_syncHdr == self.UNAUTHORIZED:
            LOG('SubSync', INFO, 'Bad authentication')
            return {'has_response':0, 'xml':xml_client}
          else:
            response = self.SubSyncModif(subscription, xml_client)
        else:
            response = self.SubSyncModif(subscription, xml_client)

    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')
    else:
      return response

  def getActivityType(self, domain):
    if domain.getActivityEnabled():
1717
      return 'SQLQueue'
1718
    return 'RAMQueue'