View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  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  
20  package davmail.http.request;
21  
22  import davmail.exchange.XMLStreamUtil;
23  import org.apache.http.Header;
24  import org.apache.http.HttpResponse;
25  import org.apache.http.HttpStatus;
26  import org.apache.http.client.HttpResponseException;
27  import org.apache.http.client.ResponseHandler;
28  import org.apache.http.client.methods.HttpPost;
29  import org.apache.http.entity.AbstractHttpEntity;
30  import org.apache.jackrabbit.webdav.MultiStatusResponse;
31  import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
32  import org.apache.jackrabbit.webdav.xml.Namespace;
33  import org.apache.log4j.Logger;
34  
35  import javax.xml.stream.XMLStreamConstants;
36  import javax.xml.stream.XMLStreamException;
37  import javax.xml.stream.XMLStreamReader;
38  import java.io.ByteArrayInputStream;
39  import java.io.FilterInputStream;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.OutputStream;
43  import java.util.ArrayList;
44  import java.util.List;
45  
46  public abstract class ExchangeDavRequest extends HttpPost implements ResponseHandler<List<MultiStatusResponse>> {
47      protected static final Logger LOGGER = Logger.getLogger(ExchangeDavRequest.class);
48      private static final String XML_CONTENT_TYPE = "text/xml; charset=UTF-8";
49  
50      private HttpResponse response;
51      private List<MultiStatusResponse> responses;
52  
53      /**
54       * Create PROPPATCH method.
55       *
56       * @param path path
57       */
58      public ExchangeDavRequest(String path) {
59          super(path);
60          AbstractHttpEntity httpEntity = new AbstractHttpEntity() {
61              byte[] content;
62  
63              @Override
64              public boolean isRepeatable() {
65                  return true;
66              }
67  
68              @Override
69              public long getContentLength() {
70                  if (content == null) {
71                      content = generateRequestContent();
72                  }
73                  return content.length;
74              }
75  
76              @Override
77              public InputStream getContent() throws UnsupportedOperationException {
78                  if (content == null) {
79                      content = generateRequestContent();
80                  }
81                  return new ByteArrayInputStream(content);
82              }
83  
84              @Override
85              public void writeTo(OutputStream outputStream) throws IOException {
86                  if (content == null) {
87                      content = generateRequestContent();
88                  }
89                  outputStream.write(content);
90              }
91  
92              @Override
93              public boolean isStreaming() {
94                  return false;
95              }
96          };
97  
98          httpEntity.setContentType(XML_CONTENT_TYPE);
99          setEntity(httpEntity);
100     }
101 
102     /**
103      * Generate request content from property values.
104      *
105      * @return request content as byte array
106      */
107     protected abstract byte[] generateRequestContent();
108 
109     @Override
110     public List<MultiStatusResponse> handleResponse(HttpResponse response) {
111         this.response = response;
112         Header contentTypeHeader = response.getFirstHeader("Content-Type");
113         if (contentTypeHeader != null && "text/xml".equals(contentTypeHeader.getValue())) {
114             responses = new ArrayList<>();
115             XMLStreamReader reader;
116             try {
117                 reader = XMLStreamUtil.createXMLStreamReader(new FilterInputStream(response.getEntity().getContent()) {
118                     final byte[] lastbytes = new byte[3];
119 
120                     @Override
121                     public int read(byte[] bytes, int off, int len) throws IOException {
122                         int count = in.read(bytes, off, len);
123                         // patch invalid element name
124                         for (int i = 0; i < count; i++) {
125                             byte currentByte = bytes[off + i];
126                             if ((lastbytes[0] == '<') && (currentByte >= '0' && currentByte <= '9')) {
127                                 // move invalid first tag char to valid range
128                                 bytes[off + i] = (byte) (currentByte + 49);
129                             }
130                             lastbytes[0] = lastbytes[1];
131                             lastbytes[1] = lastbytes[2];
132                             lastbytes[2] = currentByte;
133                         }
134                         return count;
135                     }
136 
137                 });
138                 while (reader.hasNext()) {
139                     reader.next();
140                     if (XMLStreamUtil.isStartTag(reader, "response")) {
141                         handleResponse(reader);
142                     }
143                 }
144 
145             } catch (IOException | XMLStreamException e) {
146                 LOGGER.error("Error while parsing soap response: " + e, e);
147             }
148         }
149         return responses;
150     }
151 
152     protected void handleResponse(XMLStreamReader reader) throws XMLStreamException {
153         MultiStatusResponse multiStatusResponse = null;
154         String href = null;
155         String responseStatus = "";
156         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "response")) {
157             reader.next();
158             if (XMLStreamUtil.isStartTag(reader)) {
159                 String tagLocalName = reader.getLocalName();
160                 if ("href".equals(tagLocalName)) {
161                     href = reader.getElementText();
162                 } else if ("status".equals(tagLocalName)) {
163                     responseStatus = reader.getElementText();
164                 } else if ("propstat".equals(tagLocalName)) {
165                     if (multiStatusResponse == null) {
166                         multiStatusResponse = new MultiStatusResponse(href, responseStatus);
167                     }
168                     handlePropstat(reader, multiStatusResponse);
169                 }
170             }
171         }
172         if (multiStatusResponse != null) {
173             responses.add(multiStatusResponse);
174         }
175     }
176 
177     protected void handlePropstat(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
178         int propstatStatus = 0;
179         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "propstat")) {
180             reader.next();
181             if (XMLStreamUtil.isStartTag(reader)) {
182                 String tagLocalName = reader.getLocalName();
183                 if ("status".equals(tagLocalName)) {
184                     if ("HTTP/1.1 200 OK".equals(reader.getElementText())) {
185                         propstatStatus = HttpStatus.SC_OK;
186                     } else {
187                         propstatStatus = 0;
188                     }
189                 } else if ("prop".equals(tagLocalName) && propstatStatus == HttpStatus.SC_OK) {
190                     handleProperty(reader, multiStatusResponse);
191                 }
192             }
193         }
194 
195     }
196 
197     protected void handleProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
198         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "prop")) {
199             reader.next();
200             if (XMLStreamUtil.isStartTag(reader)) {
201                 Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
202                 String tagLocalName = reader.getLocalName();
203                 if (reader.getAttributeCount() > 0 && "mv.string".equals(reader.getAttributeValue(0))) {
204                     handleMultiValuedProperty(reader, multiStatusResponse);
205                 } else {
206                     String tagContent = getTagContent(reader);
207                     if (tagContent != null) {
208                         multiStatusResponse.add(new DefaultDavProperty<>(tagLocalName, tagContent, namespace));
209                     }
210                 }
211             }
212         }
213     }
214 
215     protected void handleMultiValuedProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
216         String tagLocalName = reader.getLocalName();
217         Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
218         ArrayList<String> values = new ArrayList<>();
219         while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, tagLocalName)) {
220             reader.next();
221             if (XMLStreamUtil.isStartTag(reader)) {
222                 String tagContent = getTagContent(reader);
223                 if (tagContent != null) {
224                     values.add(tagContent);
225                 }
226             }
227         }
228         multiStatusResponse.add(new DefaultDavProperty<>(tagLocalName, values, namespace));
229     }
230 
231     protected String getTagContent(XMLStreamReader reader) throws XMLStreamException {
232         String value = null;
233         String tagLocalName = reader.getLocalName();
234         while (reader.hasNext() &&
235                 !((reader.getEventType() == XMLStreamConstants.END_ELEMENT) && tagLocalName.equals(reader.getLocalName()))) {
236             reader.next();
237             if (reader.getEventType() == XMLStreamConstants.CHARACTERS) {
238                 value = reader.getText();
239             }
240         }
241         // empty tag
242         if (!reader.hasNext()) {
243             throw new XMLStreamException("End element for " + tagLocalName + " not found");
244         }
245         return value;
246     }
247 
248     /**
249      * Get Multistatus responses.
250      *
251      * @return responses
252      * @throws HttpResponseException on error
253      */
254     public MultiStatusResponse[] getResponses() throws HttpResponseException {
255         if (responses == null) {
256             // TODO: compare with native HttpClient error handling
257             throw new HttpResponseException(response.getStatusLine().getStatusCode(),
258                     response.getStatusLine().getReasonPhrase());
259         }
260         return responses.toArray(new MultiStatusResponse[0]);
261     }
262 
263     /**
264      * Get single Multistatus response.
265      *
266      * @return response
267      * @throws HttpResponseException on error
268      */
269     public MultiStatusResponse getResponse() throws HttpResponseException {
270         if (responses == null || responses.size() != 1) {
271             throw new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
272         }
273         return responses.get(0);
274     }
275 
276     /**
277      * Return method http status code.
278      *
279      * @return http status code
280      * @throws HttpResponseException on error
281      */
282     public int getResponseStatusCode() throws HttpResponseException {
283         String responseDescription = getResponse().getResponseDescription();
284         if ("HTTP/1.1 201 Created".equals(responseDescription)) {
285             return HttpStatus.SC_CREATED;
286         } else {
287             return HttpStatus.SC_OK;
288         }
289     }
290 
291 }