/* * The MIT License * Copyright (c) 2012 Microsoft Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package microsoft.exchange.webservices.data.autodiscover.request; import microsoft.exchange.webservices.data.EWSConstants; import microsoft.exchange.webservices.data.autodiscover.AutodiscoverService; import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverErrorCode; import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverResponseException; import microsoft.exchange.webservices.data.autodiscover.response.AutodiscoverResponse; import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter; import microsoft.exchange.webservices.data.core.EwsUtilities; import microsoft.exchange.webservices.data.core.EwsXmlReader; import microsoft.exchange.webservices.data.core.ExchangeServerInfo; import microsoft.exchange.webservices.data.core.XmlElementNames; import microsoft.exchange.webservices.data.core.request.HttpWebRequest; import microsoft.exchange.webservices.data.core.response.ServiceResponse; import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags; import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException; import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRemoteException; import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRequestException; import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException; import microsoft.exchange.webservices.data.misc.SoapFaultDetails; import microsoft.exchange.webservices.data.security.XmlNodeType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.xml.stream.XMLStreamException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; /** * Represents the base class for all requested made to the Autodiscover service. */ public abstract class AutodiscoverRequest { private static final Log LOG = LogFactory.getLog(AutodiscoverRequest.class); /** * The service. */ private AutodiscoverService service; /** * The url. */ private URI url; /** * Initializes a new instance of the AutodiscoverResponse class. * * @param service Autodiscover service associated with this request. * @param url URL of Autodiscover service. */ protected AutodiscoverRequest(AutodiscoverService service, URI url) { this.service = service; this.url = url; } /** * Determines whether response is a redirection. * * @param request the request * @return True if redirection response. * @throws EWSHttpException the EWS http exception */ public static boolean isRedirectionResponse(HttpWebRequest request) throws EWSHttpException { return ((request.getResponseCode() == 301) || (request.getResponseCode() == 302) || (request.getResponseCode() == 307) || (request .getResponseCode() == 303)); } /** * Validates the request. * * @throws Exception the exception */ protected void validate() throws Exception { this.getService().validate(); } /** * Executes this instance. * * @return the autodiscover response * @throws Exception the exception */ protected AutodiscoverResponse internalExecute() throws Exception { this.validate(); HttpWebRequest request = null; try { request = this.service.prepareHttpWebRequestForUrl(this.url); this.service.traceHttpRequestHeaders( TraceFlags.AutodiscoverRequestHttpHeaders, request); boolean needSignature = this.getService().getCredentials() != null && this.getService().getCredentials().isNeedSignature(); boolean needTrace = this.getService().isTraceEnabledFor( TraceFlags.AutodiscoverRequest); OutputStream urlOutStream = request.getOutputStream(); // OutputStreamWriter out = new OutputStreamWriter(request // .getOutputStream()); ByteArrayOutputStream memoryStream = new ByteArrayOutputStream(); EwsServiceXmlWriter writer = new EwsServiceXmlWriter(this .getService(), memoryStream); writer.setRequireWSSecurityUtilityNamespace(needSignature); this.writeSoapRequest(this.url, writer); if (needSignature) { this.service.getCredentials().sign(memoryStream); } if (needTrace) { memoryStream.flush(); this.service.traceXml(TraceFlags.AutodiscoverRequest, memoryStream); } memoryStream.writeTo(urlOutStream); urlOutStream.flush(); urlOutStream.close(); memoryStream.close(); // out.write(memoryStream.toString()); // out.close(); request.executeRequest(); request.getResponseCode(); if (AutodiscoverRequest.isRedirectionResponse(request)) { AutodiscoverResponse response = this .createRedirectionResponse(request); if (response != null) { return response; } else { throw new ServiceRemoteException("The service returned an invalid redirection response."); } } memoryStream = new ByteArrayOutputStream(); InputStream serviceResponseStream = request.getInputStream(); while (true) { int data = serviceResponseStream.read(); if (-1 == data) { break; } else { memoryStream.write(data); } } memoryStream.flush(); serviceResponseStream.close(); if (this.service.isTraceEnabled()) { this.service.traceResponse(request, memoryStream); } ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream( memoryStream.toByteArray()); EwsXmlReader ewsXmlReader = new EwsXmlReader(memoryStreamIn); // WCF may not generate an XML declaration. ewsXmlReader.read(); if (ewsXmlReader.getNodeType().getNodeType() == XmlNodeType.START_DOCUMENT) { ewsXmlReader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName); } else if ((ewsXmlReader.getNodeType().getNodeType() != XmlNodeType.START_ELEMENT) || (!ewsXmlReader.getLocalName().equals( XmlElementNames.SOAPEnvelopeElementName)) || (!ewsXmlReader.getNamespaceUri().equals( EwsUtilities.getNamespaceUri(XmlNamespace.Soap)))) { throw new ServiceXmlDeserializationException("The Autodiscover service response was invalid."); } this.readSoapHeaders(ewsXmlReader); AutodiscoverResponse response = this.readSoapBody(ewsXmlReader); ewsXmlReader.readEndElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName); if (response.getErrorCode() == AutodiscoverErrorCode.NoError) { return response; } else { throw new AutodiscoverResponseException( response.getErrorCode(), response.getErrorMessage()); } } catch (XMLStreamException ex) { this.service.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("XML parsing error: %s", ex.getMessage())); // Wrap exception throw new ServiceRequestException(String.format("The request failed. %s", ex.getMessage()), ex); } catch (IOException ex) { this.service.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("I/O error: %s", ex.getMessage())); // Wrap exception throw new ServiceRequestException(String.format("The request failed. %s", ex.getMessage()), ex); } catch (Exception ex) { // HttpWebRequest httpWebResponse = (HttpWebRequest)ex; if (null != request && request.getResponseCode() == 7) { if (AutodiscoverRequest.isRedirectionResponse(request)) { this.service .processHttpResponseHeaders( TraceFlags.AutodiscoverResponseHttpHeaders, request); AutodiscoverResponse response = this .createRedirectionResponse(request); if (response != null) { return response; } } else { this.processWebException(ex, request); } } // Wrap exception if the above code block didn't throw throw new ServiceRequestException(String.format("The request failed. %s", ex.getMessage()), ex); } finally { try { if (request != null) { request.close(); } } catch (Exception e) { // do nothing } } } /** * Processes the web exception. * * @param exception WebException * @param req HttpWebRequest */ private void processWebException(Exception exception, HttpWebRequest req) { if (null != req) { try { if (500 == req.getResponseCode()) { if (this.service .isTraceEnabledFor( TraceFlags.AutodiscoverRequest)) { ByteArrayOutputStream memoryStream = new ByteArrayOutputStream(); InputStream serviceResponseStream = AutodiscoverRequest .getResponseStream(req); while (true) { int data = serviceResponseStream.read(); if (-1 == data) { break; } else { memoryStream.write(data); } } memoryStream.flush(); serviceResponseStream.close(); this.service.traceResponse(req, memoryStream); ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream( memoryStream.toByteArray()); EwsXmlReader reader = new EwsXmlReader(memoryStreamIn); this.readSoapFault(reader); memoryStream.close(); } else { InputStream serviceResponseStream = AutodiscoverRequest .getResponseStream(req); EwsXmlReader reader = new EwsXmlReader( serviceResponseStream); SoapFaultDetails soapFaultDetails = this.readSoapFault(reader); serviceResponseStream.close(); if (soapFaultDetails != null) { throw new ServiceResponseException( new ServiceResponse(soapFaultDetails)); } } } else { this.service.processHttpErrorResponse(req, exception); } } catch (Exception e) { LOG.error(e); } } } /** * Create a redirection response. * * @param httpWebResponse the HTTP web response * @return AutodiscoverResponse autodiscoverResponse object * @throws XMLStreamException the XML stream exception * @throws IOException signals that an I/O exception has occurred * @throws EWSHttpException the EWS http exception */ private AutodiscoverResponse createRedirectionResponse( HttpWebRequest httpWebResponse) throws XMLStreamException, IOException, EWSHttpException { String location = httpWebResponse.getResponseHeaderField("Location"); if (!(location == null || location.isEmpty())) { try { URI redirectionUri = new URI(location); String scheme = redirectionUri.getScheme(); if (scheme.equalsIgnoreCase(EWSConstants.HTTP_SCHEME) || scheme.equalsIgnoreCase(EWSConstants.HTTPS_SCHEME)) { AutodiscoverResponse response = this.createServiceResponse(); response.setErrorCode(AutodiscoverErrorCode.RedirectUrl); response.setRedirectionUrl(redirectionUri); return response; } this.service .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "Invalid redirection" + " URL '%s' " + "returned by Autodiscover " + "service.", redirectionUri.toString())); } catch (URISyntaxException ex) { this.service .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "Invalid redirection " + "location '%s' " + "returned by Autodiscover " + "service.", location)); } } else { this.service .traceMessage( TraceFlags.AutodiscoverConfiguration, "Redirection response returned by Autodiscover " + "service without redirection location."); } return null; } /** * Reads the SOAP fault. * * @param reader The reader. * @return SOAP fault details. */ private SoapFaultDetails readSoapFault(EwsXmlReader reader) { SoapFaultDetails soapFaultDetails = null; try { reader.read(); if (reader.getNodeType().getNodeType() == XmlNodeType.START_DOCUMENT) { reader.read(); } if (!reader.isStartElement() || (!reader.getLocalName().equals( XmlElementNames.SOAPEnvelopeElementName))) { return null; } // Get the namespace URI from the envelope element and use it for // the rest of the parsing. // If it's not 1.1 or 1.2, we can't continue. XmlNamespace soapNamespace = EwsUtilities .getNamespaceFromUri(reader.getNamespaceUri()); if (soapNamespace == XmlNamespace.NotSpecified) { return null; } reader.read(); // Skip SOAP header. if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPHeaderElementName)) { do { reader.read(); } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPHeaderElementName)); // Queue up the next read reader.read(); } // Parse the fault element contained within the SOAP body. if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPBodyElementName)) { do { reader.read(); // Parse Fault element if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPFaultElementName)) { soapFaultDetails = SoapFaultDetails.parse(reader, soapNamespace); } } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPBodyElementName)); } reader.readEndElement(soapNamespace, XmlElementNames.SOAPEnvelopeElementName); } catch (Exception e) { // If response doesn't contain a valid SOAP fault, just ignore // exception and // return null for SOAP fault details. LOG.error(e); } return soapFaultDetails; } /** * Writes the autodiscover SOAP request. * * @param requestUrl request URL * @param writer writer object * @throws XMLStreamException the XML stream exception * @throws ServiceXmlSerializationException the service xml serialization exception */ protected void writeSoapRequest(URI requestUrl, EwsServiceXmlWriter writer) throws XMLStreamException, ServiceXmlSerializationException { if (writer.isRequireWSSecurityUtilityNamespace()) { writer.writeAttributeValue("xmlns", EwsUtilities.WSSecurityUtilityNamespacePrefix, EwsUtilities.WSSecurityUtilityNamespace); } writer.writeStartDocument(); writer.writeStartElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName); writer.writeAttributeValue("xmlns", EwsUtilities .getNamespacePrefix(XmlNamespace.Soap), EwsUtilities .getNamespaceUri(XmlNamespace.Soap)); writer.writeAttributeValue("xmlns", EwsUtilities.AutodiscoverSoapNamespacePrefix, EwsUtilities.AutodiscoverSoapNamespace); writer.writeAttributeValue("xmlns", EwsUtilities.WSAddressingNamespacePrefix, EwsUtilities.WSAddressingNamespace); writer.writeAttributeValue("xmlns", EwsUtilities.EwsXmlSchemaInstanceNamespacePrefix, EwsUtilities.EwsXmlSchemaInstanceNamespace); writer.writeStartElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName); if (this.service.getCredentials() != null) { this.service.getCredentials().emitExtraSoapHeaderNamespaceAliases( writer.getInternalWriter()); } writer.writeElementValue(XmlNamespace.Autodiscover, XmlElementNames.RequestedServerVersion, this.service .getRequestedServerVersion().toString()); writer.writeElementValue(XmlNamespace.WSAddressing, XmlElementNames.Action, this.getWsAddressingActionName()); writer.writeElementValue(XmlNamespace.WSAddressing, XmlElementNames.To, requestUrl.toString()); this.writeExtraCustomSoapHeadersToXml(writer); if (this.service.getCredentials() != null) { this.service.getCredentials().serializeWSSecurityHeaders( writer.getInternalWriter()); } this.service.doOnSerializeCustomSoapHeaders(writer.getInternalWriter()); writer.writeEndElement(); // soap:Header writer.writeStartElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName); this.writeBodyToXml(writer); writer.writeEndElement(); // soap:Body writer.writeEndElement(); // soap:Envelope writer.flush(); writer.dispose(); } /** * Write extra headers. * * @param writer the writer * @throws ServiceXmlSerializationException the service xml serialization exception * @throws XMLStreamException the XML stream exception */ protected void writeExtraCustomSoapHeadersToXml(EwsServiceXmlWriter writer) throws XMLStreamException, ServiceXmlSerializationException { // do nothing here. // currently used only by GetUserSettingRequest to emit the BinarySecret header. } /** * Writes XML body. * * @param writer the writer * @throws ServiceXmlSerializationException the service xml serialization exception * @throws XMLStreamException the XML stream exception */ protected void writeBodyToXml(EwsServiceXmlWriter writer) throws ServiceXmlSerializationException, XMLStreamException { writer.writeStartElement(XmlNamespace.Autodiscover, this .getRequestXmlElementName()); this.writeAttributesToXml(writer); this.writeElementsToXml(writer); writer.writeEndElement(); // m:this.GetXmlElementName() } /** * Gets the response stream (may be wrapped with GZip/Deflate stream to * decompress content). * * @param request the request * @return ResponseStream * @throws EWSHttpException the EWS http exception * @throws IOException signals that an I/O exception has occurred. */ protected static InputStream getResponseStream(HttpWebRequest request) throws EWSHttpException, IOException { String contentEncoding = ""; if (null != request.getContentEncoding()) { contentEncoding = request.getContentEncoding().toLowerCase(); } InputStream responseStream; if (contentEncoding.contains("gzip")) { responseStream = new GZIPInputStream(request.getInputStream()); } else if (contentEncoding.contains("deflate")) { responseStream = new InflaterInputStream(request.getInputStream()); } else { responseStream = request.getInputStream(); } return responseStream; } /** * Read SOAP header. * * @param reader EwsXmlReader. * @throws Exception the exception */ protected void readSoapHeaders(EwsXmlReader reader) throws Exception { reader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName); do { reader.read(); this.readSoapHeader(reader); } while (!reader.isEndElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName)); } /** * Reads a single SOAP header. * * @param reader EwsXmlReader * @throws Exception on error */ protected void readSoapHeader(EwsXmlReader reader) throws Exception { // Is this the ServerVersionInfo? if (reader.isStartElement(XmlNamespace.Autodiscover, XmlElementNames.ServerVersionInfo)) { this.service.setServerInfo(this.readServerVersionInfo(reader)); } } /** * Read ServerVersionInfo SOAP header. * * @param reader EwsXmlReader. * @return ExchangeServerInfo ExchangeServerInfo object * @throws Exception the exception */ private ExchangeServerInfo readServerVersionInfo(EwsXmlReader reader) throws Exception { ExchangeServerInfo serverInfo = new ExchangeServerInfo(); do { reader.read(); if (reader.isStartElement()) { if (reader.getLocalName().equals(XmlElementNames.MajorVersion)) { serverInfo.setMajorVersion(reader .readElementValue(Integer.class)); } else if (reader.getLocalName().equals( XmlElementNames.MinorVersion)) { serverInfo.setMinorVersion(reader .readElementValue(Integer.class)); } else if (reader.getLocalName().equals( XmlElementNames.MajorBuildNumber)) { serverInfo.setMajorBuildNumber(reader .readElementValue(Integer.class)); } else if (reader.getLocalName().equals( XmlElementNames.MinorBuildNumber)) { serverInfo.setMinorBuildNumber(reader .readElementValue(Integer.class)); } else if (reader.getLocalName() .equals(XmlElementNames.Version)) { serverInfo.setVersionString(reader.readElementValue()); } } } while (!reader.isEndElement(XmlNamespace.Autodiscover, XmlElementNames.ServerVersionInfo)); return serverInfo; } /** * Read SOAP body. * * @param reader EwsXmlReader. * @return AutodiscoverResponse AutodiscoverResponse object * @throws Exception the exception */ protected AutodiscoverResponse readSoapBody(EwsXmlReader reader) throws Exception { reader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName); AutodiscoverResponse responses = this.loadFromXml(reader); reader.readEndElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName); return responses; } /** * Loads response from XML. * * @param reader The reader. * @return AutodiscoverResponse object * @throws Exception the exception */ protected AutodiscoverResponse loadFromXml(EwsXmlReader reader) throws Exception { String elementName = this.getResponseXmlElementName(); reader.readStartElement(XmlNamespace.Autodiscover, elementName); AutodiscoverResponse response = this.createServiceResponse(); response.loadFromXml(reader, elementName); return response; } /** * Gets the name of the request XML element. * * @return RequestXmlElementName gets XmlElementName. */ protected abstract String getRequestXmlElementName(); /** * Gets the name of the response XML element. * * @return ResponseXmlElementName gets XmlElementName. */ protected abstract String getResponseXmlElementName(); /** * Gets the WS-Addressing action name. * * @return WsAddressingActionName gets WsAddressingActionName. */ protected abstract String getWsAddressingActionName(); /** * Creates the service response. * * @return AutodiscoverResponse AutodiscoverResponse object. */ protected abstract AutodiscoverResponse createServiceResponse(); /** * Writes attribute to request XML. * * @param writer The writer. * @throws ServiceXmlSerializationException the service xml serialization exception */ protected abstract void writeAttributesToXml(EwsServiceXmlWriter writer) throws ServiceXmlSerializationException; /** * Writes elements to request XML. * * @param writer the writer * @throws XMLStreamException the XML stream exception * @throws ServiceXmlSerializationException the service xml serialization exception */ protected abstract void writeElementsToXml(EwsServiceXmlWriter writer) throws XMLStreamException, ServiceXmlSerializationException; /** * Gets the Service. * * @return AutodiscoverService AutodiscoverService object. */ protected AutodiscoverService getService() { return this.service; } /** * Gets the URL. * * @return url URL Object. */ protected URI getUrl() { return this.url; } }