View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2012  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail.exchange.dav;
20  
21  import davmail.exchange.XMLStreamUtil;
22  import org.apache.commons.httpclient.Header;
23  import org.apache.commons.httpclient.HttpConnection;
24  import org.apache.commons.httpclient.HttpException;
25  import org.apache.commons.httpclient.HttpState;
26  import org.apache.commons.httpclient.HttpStatus;
27  import org.apache.commons.httpclient.methods.PostMethod;
28  import org.apache.commons.httpclient.methods.RequestEntity;
29  import org.apache.jackrabbit.webdav.MultiStatusResponse;
30  import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
31  import org.apache.jackrabbit.webdav.xml.Namespace;
32  import org.apache.log4j.Logger;
33  
34  import javax.xml.stream.XMLStreamConstants;
35  import javax.xml.stream.XMLStreamException;
36  import javax.xml.stream.XMLStreamReader;
37  import java.io.FilterInputStream;
38  import java.io.IOException;
39  import java.io.OutputStream;
40  import java.util.ArrayList;
41  import java.util.List;
42  
43  /**
44   * New stax based implementation to replace DOM based jackrabbit version an support Exchange only extensions.
45   */
46  public abstract class ExchangeDavMethod extends PostMethod {
47      protected static final Logger LOGGER = Logger.getLogger(ExchangeDavMethod.class);
48      List<MultiStatusResponse> responses;
49  
50      /**
51       * Create PROPPATCH method.
52       *
53       * @param path           path
54       */
55      public ExchangeDavMethod(String path) {
56          super(path);
57          setRequestEntity(new RequestEntity() {
58              byte[] content;
59  
60              public boolean isRepeatable() {
61                  return true;
62              }
63  
64              public void writeRequest(OutputStream outputStream) throws IOException {
65                  if (content == null) {
66                      content = generateRequestContent();
67                  }
68                  outputStream.write(content);
69              }
70  
71              public long getContentLength() {
72                  if (content == null) {
73                      content = generateRequestContent();
74                  }
75                  return content.length;
76              }
77  
78              public String getContentType() {
79                  return "text/xml;charset=UTF-8";
80              }
81          });
82      }
83  
84      /**
85       * Generate request content from property values.
86       *
87       * @return request content as byte array
88       */
89      protected abstract byte[] generateRequestContent();
90  
91      @Override
92      protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
93          Header contentTypeHeader = getResponseHeader("Content-Type");
94          if (contentTypeHeader != null && "text/xml".equals(contentTypeHeader.getValue())) {
95              responses = new ArrayList<>();
96              XMLStreamReader reader;
97              try {
98                  reader = XMLStreamUtil.createXMLStreamReader(new FilterInputStream(getResponseBodyAsStream()) {
99                      final byte[] lastbytes = new byte[3];
100 
101                     @Override
102                     public int read(byte[] bytes, int off, int len) throws IOException {
103                         int count = in.read(bytes, off, len);
104                         // patch invalid element name
105                         for (int i = 0; i < count; i++) {
106                             byte currentByte = bytes[off + i];
107                             if ((lastbytes[0] == '<') && (currentByte >= '0' && currentByte <= '9')) {
108                                 // move invalid first tag char to valid range
109                                 bytes[off + i] = (byte) (currentByte + 49);
110                             }
111                             lastbytes[0] = lastbytes[1];
112                             lastbytes[1] = lastbytes[2];
113                             lastbytes[2] = currentByte;
114                         }
115                         return count;
116                     }
117 
118                 });
119                 while (reader.hasNext()) {
120                     reader.next();
121                     if (XMLStreamUtil.isStartTag(reader, "response")) {
122                         handleResponse(reader);
123                     }
124                 }
125 
126             } catch (IOException | XMLStreamException e) {
127                 LOGGER.error("Error while parsing soap response: " + e, e);
128             }
129         }
130     }
131 
132     protected void handleResponse(XMLStreamReader reader) throws XMLStreamException {
133         MultiStatusResponse multiStatusResponse = null;
134         String href = null;
135         String responseStatus = "";
136         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "response")) {
137             reader.next();
138             if (XMLStreamUtil.isStartTag(reader)) {
139                 String tagLocalName = reader.getLocalName();
140                 if ("href".equals(tagLocalName)) {
141                     href = reader.getElementText();
142                 } else if ("status".equals(tagLocalName)) {
143                     responseStatus = reader.getElementText();
144                 } else if ("propstat".equals(tagLocalName)) {
145                      if (multiStatusResponse == null) {
146                          multiStatusResponse = new MultiStatusResponse(href, responseStatus);
147                      }
148                      handlePropstat(reader, multiStatusResponse);
149                 }
150             }
151         }
152         if (multiStatusResponse != null) {
153             responses.add(multiStatusResponse);
154         }
155     }
156 
157     protected void handlePropstat(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
158         int propstatStatus = 0;
159         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "propstat")) {
160             reader.next();
161             if (XMLStreamUtil.isStartTag(reader)) {
162                 String tagLocalName = reader.getLocalName();
163                 if ("status".equals(tagLocalName)) {
164                     if ("HTTP/1.1 200 OK".equals(reader.getElementText())) {
165                         propstatStatus = HttpStatus.SC_OK;
166                     } else {
167                         propstatStatus = 0;
168                     }
169                 } else if ("prop".equals(tagLocalName) && propstatStatus == HttpStatus.SC_OK) {
170                     handleProperty(reader, multiStatusResponse);
171                 }
172             }
173         }
174 
175     }
176 
177     protected void handleProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
178         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "prop")) {
179             reader.next();
180             if (XMLStreamUtil.isStartTag(reader)) {
181                 Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
182                 String tagLocalName = reader.getLocalName();
183                 if (reader.getAttributeCount() > 0 && "mv.string".equals(reader.getAttributeValue(0))) {
184                      handleMultiValuedProperty(reader, multiStatusResponse);
185                 } else {
186                     String tagContent = getTagContent(reader);
187                     if (tagContent != null) {
188                         multiStatusResponse.add(new DefaultDavProperty<>(tagLocalName, tagContent, namespace));
189                     }
190                 }
191             }
192         }
193     }
194 
195     protected void handleMultiValuedProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
196         String tagLocalName = reader.getLocalName();
197         Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
198         ArrayList<String> values = new ArrayList<>();
199         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, tagLocalName)) {
200             reader.next();
201             if (XMLStreamUtil.isStartTag(reader)) {
202                 String tagContent = getTagContent(reader);
203                 if (tagContent != null) {
204                     values.add(tagContent);
205                 }
206             }
207         }
208         multiStatusResponse.add(new DefaultDavProperty<>(tagLocalName, values, namespace));
209     }
210 
211     protected String getTagContent(XMLStreamReader reader) throws XMLStreamException {
212         String value = null;
213         String tagLocalName = reader.getLocalName();
214         while (reader.hasNext() &&
215                 !((reader.getEventType() == XMLStreamConstants.END_ELEMENT) && tagLocalName.equals(reader.getLocalName()))) {
216             reader.next();
217             if (reader.getEventType() == XMLStreamConstants.CHARACTERS) {
218                 value = reader.getText();
219             }
220         }
221         // empty tag
222         if (!reader.hasNext()) {
223             throw new XMLStreamException("End element for " + tagLocalName + " not found");
224         }
225         return value;
226     }
227 
228     /**
229      * Get Multistatus responses.
230      *
231      * @return responses
232      * @throws HttpException on error
233      */
234     public MultiStatusResponse[] getResponses() throws HttpException {
235         if (responses == null) {
236             throw new HttpException(getStatusLine().toString());
237         }
238         return responses.toArray(new MultiStatusResponse[0]);
239     }
240 
241     /**
242      * Get single Multistatus response.
243      *
244      * @return response
245      * @throws HttpException on error
246      */
247     public MultiStatusResponse getResponse() throws HttpException {
248         if (responses == null || responses.size() != 1) {
249             throw new HttpException(getStatusLine().toString());
250         }
251         return responses.get(0);
252     }
253 
254     /**
255      * Return method http status code.
256      *
257      * @return http status code
258      * @throws HttpException on error
259      */
260     public int getResponseStatusCode() throws HttpException {
261         String responseDescription = getResponse().getResponseDescription();
262         if ("HTTP/1.1 201 Created".equals(responseDescription)) {
263             return HttpStatus.SC_CREATED;
264         } else {
265             return HttpStatus.SC_OK;
266         }
267     }
268 }