blautenb    2003/11/15 00:48:21

  Modified:    src/org/apache/xml/security/encryption XMLCipher.java
  Added:       src/org/apache/xml/security/encryption XMLCipherInput.java
  Log:
  Moved de-referencing of CipherData octets to XMLCipherInput.java
  
  Revision  Changes    Path
  1.13      +144 -186  
xml-security/src/org/apache/xml/security/encryption/XMLCipher.java
  
  Index: XMLCipher.java
  ===================================================================
  RCS file: 
/home/cvs/xml-security/src/org/apache/xml/security/encryption/XMLCipher.java,v
  retrieving revision 1.12
  retrieving revision 1.13
  diff -u -r1.12 -r1.13
  --- XMLCipher.java    14 Nov 2003 21:05:41 -0000      1.12
  +++ XMLCipher.java    15 Nov 2003 08:48:21 -0000      1.13
  @@ -586,95 +586,7 @@
                        throw new XMLEncryptionException("XMLCipher instance 
without transformation specified");
                }
   
  -        String serializedOctets = _serializer.serialize(element);
  -        logger.debug("Serialized octets:\n" + serializedOctets);
  -
  -        byte[] encryptedBytes = null;
  -             // Now create the working cipher
  -
  -             String jceAlgorithm =
  -                     
JCEMapper.translateURItoJCEID(_algorithm).getAlgorithmID();
  -             String provider;
  -
  -             if (_requestedJCEProvider == null)
  -                     provider =
  -                             
JCEMapper.translateURItoJCEID(_algorithm).getProviderId();
  -             else
  -                     provider = _requestedJCEProvider;
  -
  -             logger.debug("provider = " + provider + "alg = " + 
jceAlgorithm);
  -
  -             Cipher c;
  -             try {
  -                     c = Cipher.getInstance(jceAlgorithm, provider);
  -             } catch (NoSuchAlgorithmException nsae) {
  -                     throw new XMLEncryptionException("empty", nsae);
  -             } catch (NoSuchProviderException nspre) {
  -                     throw new XMLEncryptionException("empty", nspre);
  -             } catch (NoSuchPaddingException nspae) {
  -                     throw new XMLEncryptionException("empty", nspae);
  -             }
  -
  -             // Now perform the encryption
  -
  -             try {
  -                     // Should internally generate an IV
  -                     // todo - allow user to set an IV
  -                     c.init(_cipherMode, _key);
  -             } catch (InvalidKeyException ike) {
  -                     throw new XMLEncryptionException("empty", ike);
  -             }
  -
  -        try {
  -            encryptedBytes =
  -                c.doFinal(serializedOctets.getBytes("UTF-8"));
  -
  -            logger.debug("Expected cipher.outputSize = " +
  -                Integer.toString(c.getOutputSize(
  -                    serializedOctets.getBytes().length)));
  -            logger.debug("Actual cipher.outputSize = " +
  -                Integer.toString(encryptedBytes.length));
  -        } catch (IllegalStateException ise) {
  -            throw new XMLEncryptionException("empty", ise);
  -        } catch (IllegalBlockSizeException ibse) {
  -            throw new XMLEncryptionException("empty", ibse);
  -        } catch (BadPaddingException bpe) {
  -            throw new XMLEncryptionException("empty", bpe);
  -        } catch (UnsupportedEncodingException uee) {
  -                     throw new XMLEncryptionException("empty", uee);
  -             }
  -
  -             // Now build up to a properly XML Encryption encoded octet 
stream
  -             // IvParameterSpec iv;
  -
  -             byte[] iv = c.getIV();
  -             byte[] finalEncryptedBytes = 
  -                     new byte[iv.length + encryptedBytes.length];
  -             System.arraycopy(iv, 0, finalEncryptedBytes, 0,
  -                                              iv.length);
  -             System.arraycopy(encryptedBytes, 0, finalEncryptedBytes, 
  -                                              iv.length,
  -                                              encryptedBytes.length);
  -
  -        String base64EncodedEncryptedOctets = new BASE64Encoder().encode(
  -            finalEncryptedBytes);
  -
  -        logger.debug("Encrypted octets:\n" + base64EncodedEncryptedOctets);
  -        logger.debug("Encrypted octets length = " +
  -            base64EncodedEncryptedOctets.length());
  -
  -        try {
  -                     CipherData cd = _ed.getCipherData();
  -                     CipherValue cv = cd.getCipherValue();
  -                     cv.setValue(base64EncodedEncryptedOctets.getBytes());
  -
  -            _ed.setType(new 
URI(EncryptionConstants.TYPE_ELEMENT).toString());
  -            EncryptionMethod method = _factory.newEncryptionMethod(
  -                new URI(_algorithm).toString());
  -            _ed.setEncryptionMethod(method);
  -        } catch (URI.MalformedURIException mfue) {
  -            throw new XMLEncryptionException("empty", mfue);
  -        }
  +             encryptData(_contextDocument, element);
   
           Element encryptedElement = _factory.toElement(_ed);
   
  @@ -946,6 +858,8 @@
        * Returns an <code>EncryptedData</code> interface. Use this operation if
        * you want to have full control over the contents of the
        * <code>EncryptedData</code> structure.
  +      *
  +      * This does not change the source document in any way.
        *
        * @param context the context <code>Document</code>.
        * @param element the <code>Element</code> that will be encrypted.
  @@ -972,27 +886,74 @@
           logger.debug("Serialized octets:\n" + serializedOctets);
   
           byte[] encryptedBytes = null;
  +             // Now create the working cipher
  +
  +             String jceAlgorithm =
  +                     
JCEMapper.translateURItoJCEID(_algorithm).getAlgorithmID();
  +             String provider;
  +
  +             if (_requestedJCEProvider == null)
  +                     provider =
  +                             
JCEMapper.translateURItoJCEID(_algorithm).getProviderId();
  +             else
  +                     provider = _requestedJCEProvider;
  +
  +             logger.debug("provider = " + provider + "alg = " + 
jceAlgorithm);
  +
  +             Cipher c;
  +             try {
  +                     c = Cipher.getInstance(jceAlgorithm, provider);
  +             } catch (NoSuchAlgorithmException nsae) {
  +                     throw new XMLEncryptionException("empty", nsae);
  +             } catch (NoSuchProviderException nspre) {
  +                     throw new XMLEncryptionException("empty", nspre);
  +             } catch (NoSuchPaddingException nspae) {
  +                     throw new XMLEncryptionException("empty", nspae);
  +             }
  +
  +             // Now perform the encryption
  +
  +             try {
  +                     // Should internally generate an IV
  +                     // todo - allow user to set an IV
  +                     c.init(_cipherMode, _key);
  +             } catch (InvalidKeyException ike) {
  +                     throw new XMLEncryptionException("empty", ike);
  +             }
  +
           try {
               encryptedBytes =
  -                _contextCipher.doFinal(serializedOctets.getBytes("UTF-8"));
  +                c.doFinal(serializedOctets.getBytes("UTF-8"));
   
               logger.debug("Expected cipher.outputSize = " +
  -                Integer.toString(_contextCipher.getOutputSize(
  +                Integer.toString(c.getOutputSize(
                       serializedOctets.getBytes().length)));
               logger.debug("Actual cipher.outputSize = " +
                   Integer.toString(encryptedBytes.length));
           } catch (IllegalStateException ise) {
               throw new XMLEncryptionException("empty", ise);
  -             } catch (UnsupportedEncodingException uee) {
  -                     throw new XMLEncryptionException("empty", uee);
           } catch (IllegalBlockSizeException ibse) {
               throw new XMLEncryptionException("empty", ibse);
           } catch (BadPaddingException bpe) {
               throw new XMLEncryptionException("empty", bpe);
  -        }
  +        } catch (UnsupportedEncodingException uee) {
  +                     throw new XMLEncryptionException("empty", uee);
  +             }
  +
  +             // Now build up to a properly XML Encryption encoded octet 
stream
  +             // IvParameterSpec iv;
  +
  +             byte[] iv = c.getIV();
  +             byte[] finalEncryptedBytes = 
  +                     new byte[iv.length + encryptedBytes.length];
  +             System.arraycopy(iv, 0, finalEncryptedBytes, 0,
  +                                              iv.length);
  +             System.arraycopy(encryptedBytes, 0, finalEncryptedBytes, 
  +                                              iv.length,
  +                                              encryptedBytes.length);
   
           String base64EncodedEncryptedOctets = new BASE64Encoder().encode(
  -            encryptedBytes);
  +            finalEncryptedBytes);
   
           logger.debug("Encrypted octets:\n" + base64EncodedEncryptedOctets);
           logger.debug("Encrypted octets length = " +
  @@ -1189,26 +1150,9 @@
                        throw new XMLEncryptionException("Unable to decrypt 
without a KEK");
                }
   
  -             CipherData cipherData = encryptedKey.getCipherData();
  -        String base64EncodedEncryptedOctets = null;
  -
  -        if (cipherData.getDataType() == CipherData.REFERENCE_TYPE) {
  -            // retrieve the cipher text
  -        } else if (cipherData.getDataType() == CipherData.VALUE_TYPE) {
  -            CipherValue cipherValue = cipherData.getCipherValue();
  -            base64EncodedEncryptedOctets = new 
String(cipherValue.getValue());
  -        } else {
  -            // complain...
  -        }
  -        logger.debug("Encrypted octets:\n" + base64EncodedEncryptedOctets);
  -
  -        byte[] encryptedBytes = null;
  -
  -        try {
  -                     encryptedBytes = 
Base64.decode(base64EncodedEncryptedOctets);
  -        } catch (Base64DecodingException bde) {
  -            throw new XMLEncryptionException("empty", bde);
  -        }
  +             // Obtain the encrypted octets 
  +             XMLCipherInput cipherInput = new XMLCipherInput(encryptedKey);
  +             byte [] encryptedBytes = cipherInput.getBytes();
   
                // Now create the working cipher
   
  @@ -1228,6 +1172,8 @@
   
                String jceKeyAlgorithm = 
                        JCEMapper.getJCEKeyAlgorithmFromURI(algorithm, 
provider);
  +             logger.debug("JCE Provider = " + provider);
  +             logger.debug("JCE Algorithm = " + jceAlgorithm);
   
                Cipher c;
                try {
  @@ -1295,28 +1241,87 @@
                        throw new XMLEncryptionException("Unable to decrypt 
without a key");
                }
   
  -        EncryptedData encryptedData = _factory.newEncryptedData(element);
  +             String octets;
  +             try {
  +                     octets = new String(decryptToByteArray(element), 
"UTF-8");
  +             } catch (UnsupportedEncodingException uee) {
  +                     throw new XMLEncryptionException("empty", uee);
  +             }
   
  -        CipherData cipherData = encryptedData.getCipherData();
  -        String base64EncodedEncryptedOctets = null;
   
  -        if (cipherData.getDataType() == CipherData.REFERENCE_TYPE) {
  -            // retrieve the cipher text
  -        } else if (cipherData.getDataType() == CipherData.VALUE_TYPE) {
  -            CipherValue cipherValue = cipherData.getCipherValue();
  -            base64EncodedEncryptedOctets = new 
String(cipherValue.getValue());
  -        } else {
  -            // complain...
  -        }
  -        logger.debug("Encrypted octets:\n" + base64EncodedEncryptedOctets);
  +        logger.debug("Decrypted octets:\n" + octets);
   
  -        byte[] encryptedBytes = null;
  +        Node sourceParent =  element.getParentNode();
   
  -        try {
  -                     encryptedBytes = 
Base64.decode(base64EncodedEncryptedOctets);
  -        } catch (Base64DecodingException bde) {
  -            throw new XMLEncryptionException("empty", bde);
  -        }
  +        DocumentFragment decryptedFragment = 
  +                     _serializer.deserialize(octets, sourceParent);
  +
  +
  +             // The de-serialiser returns a fragment whose children we need 
to
  +             // take on.
  +
  +             if (sourceParent instanceof Document) {
  +                     
  +                     // If this is a content decryption, this may have 
problems
  +
  +                     
_contextDocument.removeChild(_contextDocument.getDocumentElement());
  +                     _contextDocument.appendChild(decryptedFragment);
  +             }
  +             else {
  +
  +                     sourceParent.replaceChild(decryptedFragment, element);
  +
  +             }
  +
  +        return (_contextDocument);
  +    }
  +    
  +
  +     /**
  +      * 
  +      * @param element
  +      */
  +    private Document decryptElementContent(Element element) throws 
  +             XMLEncryptionException {
  +     Element e = (Element) element.getElementsByTagNameNS(
  +             EncryptionConstants.EncryptionSpecNS, 
  +             EncryptionConstants._TAG_ENCRYPTEDDATA).item(0);
  +     
  +     if (null == e) {
  +             throw new XMLEncryptionException("No EncryptedData child 
element.");
  +     }
  +     
  +     return (decryptElement(e));
  +    }
  +
  +     /**
  +      * Decrypt an EncryptedData element to a byte array
  +      *
  +      * When passed in an EncryptedData node, returns the decryption
  +      * as a byte array.
  +      *
  +      * Does not modify the source document
  +      */
  +
  +     public byte[] decryptToByteArray(Element element) 
  +             throws XMLEncryptionException {
  +             
  +        logger.debug("Decrypting to ByteArray...");
  +
  +        if(_cipherMode != DECRYPT_MODE)
  +            logger.error("XMLCipher unexpectedly not in DECRYPT_MODE...");
  +
  +             if (_key == null) {
  +                     // For now take the easy apprach and just throw
  +                     logger.error("XMLCipher::decryptToByteArray called 
without a key");
  +                     throw new XMLEncryptionException("Unable to decrypt 
without a key");
  +             }
  +
  +        EncryptedData encryptedData = _factory.newEncryptedData(element);
  +
  +             // Obtain the encrypted octets 
  +             XMLCipherInput cipherInput = new XMLCipherInput(encryptedData);
  +             byte [] encryptedBytes = cipherInput.getBytes();
   
                // Now create the working cipher
   
  @@ -1345,7 +1350,6 @@
                        throw new XMLEncryptionException("empty", nspae);
                }
   
  -
                // Calculate the IV length and copy out
   
                // For now, we only work with Block ciphers, so this will work.
  @@ -1374,65 +1378,19 @@
                byte[] plainBytes;
   
           try {
  -            octets = new String(c.doFinal(encryptedBytes, ivLen, 
  -                                                                             
  encryptedBytes.length - ivLen),
  -                                                             "UTF-8");
  +            plainBytes = c.doFinal(encryptedBytes, 
  +                                                                ivLen, 
  +                                                                
encryptedBytes.length - ivLen);
  +
           } catch (IllegalBlockSizeException ibse) {
               throw new XMLEncryptionException("empty", ibse);
  -             } catch (UnsupportedEncodingException uee) {
  -                     throw new XMLEncryptionException("empty", uee);
           } catch (BadPaddingException bpe) {
               throw new XMLEncryptionException("empty", bpe);
           }
                
  -             // Now remove any padding
  -             //octets = new String(padder.dePad(plainBytes));
  -         
  -
  -        logger.debug("Decrypted octets:\n" + octets);
  -
  -        Node sourceParent =  element.getParentNode();
  -
  -        DocumentFragment decryptedFragment = 
  -                     _serializer.deserialize(octets, sourceParent);
  -
  -
  -             // The de-serialiser returns a fragment whose children we need 
to
  -             // take on.
  -
  -             if (sourceParent instanceof Document) {
  -                     
  -                     // If this is a content decryption, this may have 
problems
  -
  -                     
_contextDocument.removeChild(_contextDocument.getDocumentElement());
  -                     _contextDocument.appendChild(decryptedFragment);
  -             }
  -             else {
  -
  -                     sourceParent.replaceChild(decryptedFragment, element);
  -
  -             }
  -
  -        return (_contextDocument);
  -    }
  -    
  -
  -     /**
  -      * 
  -      * @param element
  -      */
  -    private Document decryptElementContent(Element element) throws 
  -             XMLEncryptionException {
  -     Element e = (Element) element.getElementsByTagNameNS(
  -             EncryptionConstants.EncryptionSpecNS, 
  -             EncryptionConstants._TAG_ENCRYPTEDDATA).item(0);
  -     
  -     if (null == e) {
  -             throw new XMLEncryptionException("No EncryptedData child 
element.");
  -     }
  -     
  -     return (decryptElement(e));
  +        return (plainBytes);
       }
  +             
   
   
       /**
  
  
  
  1.1                  
xml-security/src/org/apache/xml/security/encryption/XMLCipherInput.java
  
  Index: XMLCipherInput.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "<WebSig>" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation and was
   * originally based on software copyright (c) 2001, Institute for
   * Data Communications Systems, <http://www.nue.et-inf.uni-siegen.de/>.
   * The development of this software was partly funded by the European
   * Commission in the <WebSig> project in the ISIS Programme.
   * For more information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  package org.apache.xml.security.encryption;
  
  //import java.io.IOException;
  //import java.io.StringReader;
  //import java.io.StringWriter;
  //import java.io.UnsupportedEncodingException;
  //import java.io.ByteArrayOutputStream;
  //import java.lang.Integer;
  //import java.security.InvalidKeyException;
  //import java.security.Key;
  //import java.security.InvalidAlgorithmParameterException;
  //import java.security.NoSuchAlgorithmException;
  //import java.security.NoSuchProviderException;
  //import java.util.HashMap;
  //import java.util.Iterator;
  //import java.util.LinkedList;
  //import java.util.List;
  //import java.util.Map;
  //import javax.crypto.BadPaddingException;
  //import javax.crypto.Cipher;
  //import javax.crypto.IllegalBlockSizeException;
  //import javax.crypto.NoSuchPaddingException;
  //import javax.crypto.spec.IvParameterSpec;
  //import javax.xml.parsers.DocumentBuilder;
  //import javax.xml.parsers.DocumentBuilderFactory;
  //import javax.xml.parsers.ParserConfigurationException;
  //import org.apache.xml.security.keys.KeyInfo;
  //import org.apache.xml.security.utils.Constants;
  //import org.apache.xml.security.utils.EncryptionConstants;
  //import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
  //import org.apache.xml.security.algorithms.JCEMapper;
  //import org.apache.xml.security.c14n.Canonicalizer;
  //import org.apache.xml.security.transforms.Transform;
  //import org.apache.xml.security.utils.ElementProxy;
  import org.apache.xml.security.exceptions.Base64DecodingException;
  //import org.apache.xml.security.exceptions.XMLSecurityException;
  //import org.apache.xml.serialize.OutputFormat;
  //import org.apache.xml.serialize.XMLSerializer;
  //import org.apache.xml.utils.URI;
  //import org.w3c.dom.Document;
  //import org.w3c.dom.DocumentFragment;
  //import org.w3c.dom.NamedNodeMap;
  //import org.w3c.dom.Element;
  //import org.w3c.dom.Node;
  //import org.w3c.dom.NodeList;
  //import org.xml.sax.InputSource;
  //import org.xml.sax.SAXException;
  //import sun.misc.BASE64Encoder;
  import org.apache.xml.security.utils.Base64;
  
  
  /**
   * <code>XMLCipherInput</code> is used to wrap input passed into the
   * XMLCipher encryption operations.
   *
   * In decryption mode, it takes a <code>CipherData</code> object and allows
   * callers to dereference the CipherData into the encrypted bytes that it
   * actually represents.  This takes care of all base64 encoding etc.
   *
   * While primarily an internal class, this can be used by applications to
   * quickly and easily retrieve the encrypted bytes from an EncryptedType
   * object
   *
   * @author Berin Lautenbach
   */
  public class XMLCipherInput {
  
      private static org.apache.commons.logging.Log logger = 
          
org.apache.commons.logging.LogFactory.getLog(XMLCipher.class.getName());
  
        /** The data we are working with */
        private CipherData _cipherData;
  
        /** MODES */
        private int _mode;
  
        /**
         * Constructor for processing encrypted octets
         *
         * @param data The <code>CipherData</code> object to read the bytes from
         * @throws [EMAIL PROTECTED] XMLEncryptionException}
         */
  
        public XMLCipherInput(CipherData data) throws XMLEncryptionException {
  
                _cipherData = data;
                _mode = XMLCipher.DECRYPT_MODE;
                if (_cipherData == null) {
                        throw new XMLEncryptionException("CipherData is null");
                }
  
        }
  
        /**
         * Constructor for processing encrypted octets
         *
         * @param input The <code>EncryptedType</code> object to read 
         * the bytes from.
         * @throws [EMAIL PROTECTED] XMLEncryptionException}
         */
  
        public XMLCipherInput(EncryptedType input) throws 
XMLEncryptionException {
  
                _cipherData = ((input == null) ? null : input.getCipherData());
                _mode = XMLCipher.DECRYPT_MODE;
                if (_cipherData == null) {
                        throw new XMLEncryptionException("CipherData is null");
                }
  
        }
  
        /**
         * Dereferences the input and returns it as a single byte array.
         *
         * @throws XMLEncryption Exception
         */
  
        public byte[] getBytes() throws XMLEncryptionException {
  
                if (_mode == XMLCipher.DECRYPT_MODE) {
                        return getDecryptBytes();
                }
                return null;
        }
  
        /**
         * Internal method to get bytes in decryption mode
         */
  
        private byte[] getDecryptBytes() throws XMLEncryptionException {
  
                String base64EncodedEncryptedOctets = null;
  
          if (_cipherData.getDataType() == CipherData.REFERENCE_TYPE) {
              // retrieve the cipher text
          } else if (_cipherData.getDataType() == CipherData.VALUE_TYPE) {
              CipherValue cipherValue = _cipherData.getCipherValue();
              base64EncodedEncryptedOctets = new String(cipherValue.getValue());
          } else {
                        throw new 
XMLEncryptionException("CipherData.getDataType() returned unexpected value");
                }
  
          logger.debug("Encrypted octets:\n" + base64EncodedEncryptedOctets);
  
          byte[] encryptedBytes = null;
  
          try {
                        encryptedBytes = 
Base64.decode(base64EncodedEncryptedOctets);
          } catch (Base64DecodingException bde) {
              throw new XMLEncryptionException("empty", bde);
          }
  
                return (encryptedBytes);
  
        }
  
  }
  
  
  
  
  

Reply via email to