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 */ ExchangeDavRequest(String path)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 */ generateRequestContent()107 protected abstract byte[] generateRequestContent(); 108 109 @Override handleResponse(HttpResponse response)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 handleResponse(XMLStreamReader reader)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 handlePropstat(XMLStreamReader reader, MultiStatusResponse multiStatusResponse)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 handleProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse)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 handleMultiValuedProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse)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 getTagContent(XMLStreamReader reader)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 */ getResponses()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 */ getResponse()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 */ getResponseStatusCode()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 } 292