//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Services.Discovery { using System.Xml.Serialization; using System.IO; using System; using System.Web.Services; using System.Web.Services.Protocols; using System.Net; using System.Collections; using System.Diagnostics; using System.Web.Services.Configuration; using System.Text; using System.Security.Permissions; using System.Globalization; using System.Threading; using System.Runtime.InteropServices; using System.Web.Services.Diagnostics; /// /// /// [To be supplied.] /// public class DiscoveryClientProtocol : HttpWebClientProtocol { private DiscoveryClientReferenceCollection references = new DiscoveryClientReferenceCollection(); private DiscoveryClientDocumentCollection documents = new DiscoveryClientDocumentCollection(); private Hashtable inlinedSchemas = new Hashtable(); private ArrayList additionalInformation = new ArrayList(); private DiscoveryExceptionDictionary errors = new DiscoveryExceptionDictionary(); /// /// /// [To be supplied.] /// public DiscoveryClientProtocol() : base() { } internal DiscoveryClientProtocol(HttpWebClientProtocol protocol) : base(protocol) { } /// /// /// [To be supplied.] /// public IList AdditionalInformation { get { return additionalInformation; } } /// /// /// [To be supplied.] /// public DiscoveryClientDocumentCollection Documents { get { return documents; } } /// /// /// [To be supplied.] /// public DiscoveryExceptionDictionary Errors { get { return errors; } } /// /// /// [To be supplied.] /// public DiscoveryClientReferenceCollection References { get { return references; } } /// /// /// [To be supplied.] /// internal Hashtable InlinedSchemas { get { return inlinedSchemas; } } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public DiscoveryDocument Discover(string url) { DiscoveryDocument doc = Documents[url] as DiscoveryDocument; if (doc != null) return doc; DiscoveryDocumentReference docRef = new DiscoveryDocumentReference(url); docRef.ClientProtocol = this; References[url] = docRef; Errors.Clear(); // this will auto-resolve and place the document in the Documents hashtable. return docRef.Document; } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public DiscoveryDocument DiscoverAny(string url) { Type[] refTypes = WebServicesSection.Current.DiscoveryReferenceTypes; DiscoveryReference discoRef = null; string contentType = null; Stream stream = Download(ref url, ref contentType); Errors.Clear(); bool allErrorsAreHtmlContentType = true; Exception errorInValidDocument = null; ArrayList specialErrorMessages = new ArrayList(); foreach (Type type in refTypes) { if (!typeof(DiscoveryReference).IsAssignableFrom(type)) continue; discoRef = (DiscoveryReference) Activator.CreateInstance(type); discoRef.Url = url; discoRef.ClientProtocol = this; stream.Position = 0; Exception e = discoRef.AttemptResolve(contentType, stream); if (e == null) break; Errors[type.FullName] = e; discoRef = null; InvalidContentTypeException e2 = e as InvalidContentTypeException; if (e2 == null || !ContentType.MatchesBase(e2.ContentType, "text/html")) allErrorsAreHtmlContentType = false; InvalidDocumentContentsException e3 = e as InvalidDocumentContentsException; if (e3 != null) { errorInValidDocument = e; break; } if (e.InnerException != null && e.InnerException.InnerException == null) specialErrorMessages.Add(e.InnerException.Message); } if (discoRef == null) { if (errorInValidDocument != null) { StringBuilder errorMessage = new StringBuilder(Res.GetString(Res.TheDocumentWasUnderstoodButContainsErrors)); while (errorInValidDocument != null) { errorMessage.Append("\n - ").Append(errorInValidDocument.Message); errorInValidDocument = errorInValidDocument.InnerException; } throw new InvalidOperationException(errorMessage.ToString()); } else if (allErrorsAreHtmlContentType) { throw new InvalidOperationException(Res.GetString(Res.TheHTMLDocumentDoesNotContainDiscoveryInformation)); } else { bool same = specialErrorMessages.Count == Errors.Count && Errors.Count > 0; for (int i = 1; same && i < specialErrorMessages.Count; i++) { if ((string) specialErrorMessages[i - 1] != (string) specialErrorMessages[i]) same = false; } if (same) throw new InvalidOperationException(Res.GetString(Res.TheDocumentWasNotRecognizedAsAKnownDocumentType, specialErrorMessages[0])); else { Exception e; StringBuilder errorMessage = new StringBuilder(Res.GetString(Res.WebMissingResource, url)); foreach (DictionaryEntry entry in Errors) { e = (Exception)(entry.Value); string refType = (string)(entry.Key); if (0 == string.Compare(refType, typeof(ContractReference).FullName, StringComparison.Ordinal)) { refType = Res.GetString(Res.WebContractReferenceName); } else if (0 == string.Compare(refType, typeof(SchemaReference).FullName, StringComparison.Ordinal)) { refType = Res.GetString(Res.WebShemaReferenceName); } else if (0 == string.Compare(refType, typeof(DiscoveryDocumentReference).FullName, StringComparison.Ordinal)) { refType = Res.GetString(Res.WebDiscoveryDocumentReferenceName); } errorMessage.Append("\n- ").Append(Res.GetString(Res.WebDiscoRefReport, refType, e.Message)); while (e.InnerException != null) { errorMessage.Append("\n - ").Append(e.InnerException.Message); e = e.InnerException; } } throw new InvalidOperationException(errorMessage.ToString()); } } } if (discoRef is DiscoveryDocumentReference) return ((DiscoveryDocumentReference) discoRef).Document; References[discoRef.Url] = discoRef; DiscoveryDocument doc = new DiscoveryDocument(); doc.References.Add(discoRef); return doc; } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public Stream Download(ref string url) { string contentType = null; return Download(ref url, ref contentType); } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public Stream Download(ref string url, ref string contentType) { WebRequest request = GetWebRequest(new Uri(url)); request.Method = "GET"; #if DEBUG // HttpWebRequest httpRequest = request as HttpWebRequest; if (httpRequest != null) { httpRequest.Timeout = httpRequest.Timeout * 2; } #endif WebResponse response = null; try { response = GetWebResponse(request); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } throw new WebException(Res.GetString(Res.ThereWasAnErrorDownloading0, url), e); } HttpWebResponse httpResponse = response as HttpWebResponse; if (httpResponse != null) { if (httpResponse.StatusCode != HttpStatusCode.OK) { string errorMessage = RequestResponseUtils.CreateResponseExceptionString(httpResponse); throw new WebException(Res.GetString(Res.ThereWasAnErrorDownloading0, url), new WebException(errorMessage, null, WebExceptionStatus.ProtocolError, response)); } } Stream responseStream = response.GetResponseStream(); try { // Uri.ToString() returns the unescaped version url = response.ResponseUri.ToString(); contentType = response.ContentType; if (response.ResponseUri.Scheme == Uri.UriSchemeFtp || response.ResponseUri.Scheme == Uri.UriSchemeFile) { int dotIndex = response.ResponseUri.AbsolutePath.LastIndexOf('.'); if (dotIndex != -1) { switch (response.ResponseUri.AbsolutePath.Substring(dotIndex + 1).ToLower(CultureInfo.InvariantCulture)) { case "xml": case "wsdl": case "xsd": case "disco": contentType = ContentType.TextXml; break; default: break; } } } // need to return a buffered stream (one that supports CanSeek) return RequestResponseUtils.StreamToMemoryStream(responseStream); } finally { responseStream.Close(); } } /// /// /// [To be supplied.] /// /// [Obsolete("This method will be removed from a future version. The method call is no longer required for resource discovery", false)] [ComVisible(false)] public void LoadExternals() { } internal void FixupReferences() { foreach (DiscoveryReference reference in References.Values) { reference.LoadExternals(InlinedSchemas); } foreach (string url in InlinedSchemas.Keys) { Documents.Remove(url); } } private static bool IsFilenameInUse(Hashtable filenames, string path) { return filenames[path.ToLower(CultureInfo.InvariantCulture)] != null; } private static void AddFilename(Hashtable filenames, string path) { filenames.Add(path.ToLower(CultureInfo.InvariantCulture), path); } private static string GetUniqueFilename(Hashtable filenames, string path) { if (IsFilenameInUse(filenames, path)) { string extension = Path.GetExtension(path); string allElse = path.Substring(0, path.Length - extension.Length); int append = 0; do { path = allElse + append.ToString(CultureInfo.InvariantCulture) + extension; append++; } while (IsFilenameInUse(filenames, path)); } AddFilename(filenames, path); return path; } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public DiscoveryClientResultCollection ReadAll(string topLevelFilename) { XmlSerializer ser = new XmlSerializer(typeof(DiscoveryClientResultsFile)); Stream file = File.OpenRead(topLevelFilename); string topLevelPath = Path.GetDirectoryName(topLevelFilename); DiscoveryClientResultsFile results = null; try { results = (DiscoveryClientResultsFile) ser.Deserialize(file); for (int i = 0; i < results.Results.Count; i++) { if (results.Results[i] == null) throw new InvalidOperationException(Res.GetString(Res.WebNullRef)); string typeName = results.Results[i].ReferenceTypeName; if (typeName == null || typeName.Length == 0) throw new InvalidOperationException(Res.GetString(Res.WebRefInvalidAttribute, "referenceType")); DiscoveryReference reference = (DiscoveryReference) Activator.CreateInstance(Type.GetType(typeName)); reference.ClientProtocol = this; string url = results.Results[i].Url; if (url == null || url.Length == 0) throw new InvalidOperationException(Res.GetString(Res.WebRefInvalidAttribute2, reference.GetType().FullName, "url")); reference.Url = url; string fileName = results.Results[i].Filename; if (fileName == null || fileName.Length == 0) throw new InvalidOperationException(Res.GetString(Res.WebRefInvalidAttribute2, reference.GetType().FullName, "filename")); Stream docFile = File.OpenRead(Path.Combine(topLevelPath, results.Results[i].Filename)); try { Documents[reference.Url] = reference.ReadDocument(docFile); Debug.Assert(Documents[reference.Url] != null, "Couldn't deserialize file " + results.Results[i].Filename); } finally { docFile.Close(); } References[reference.Url] = reference; } ResolveAll(); } finally { file.Close(); } return results.Results; } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public void ResolveAll() { // Resolve until we reach a 'steady state' (no more references added) Errors.Clear(); int resolvedCount = InlinedSchemas.Keys.Count; while (resolvedCount != References.Count) { resolvedCount = References.Count; DiscoveryReference[] refs = new DiscoveryReference[References.Count]; References.Values.CopyTo(refs, 0); for (int i = 0; i < refs.Length; i++) { DiscoveryReference discoRef = refs[i]; if (discoRef is DiscoveryDocumentReference) { try { // Resolve discovery document references deeply ((DiscoveryDocumentReference)discoRef).ResolveAll(true); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } // don't let the exception out - keep going. Just add it to the list of errors. Errors[discoRef.Url] = e; if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveAll", e); } } else { try { discoRef.Resolve(); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } // don't let the exception out - keep going. Just add it to the list of errors. Errors[discoRef.Url] = e; if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveAll", e); } } } } FixupReferences(); } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public void ResolveOneLevel() { // download everything we have a reference to, but don't recurse. Errors.Clear(); DiscoveryReference[] refs = new DiscoveryReference[References.Count]; References.Values.CopyTo(refs, 0); for (int i = 0; i < refs.Length; i++) { try { refs[i].Resolve(); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } // don't let the exception out - keep going. Just add it to the list of errors. Errors[refs[i].Url] = e; if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveOneLevel", e); } } } private static string GetRelativePath(string fullPath, string relativeTo) { string currentDir = Path.GetDirectoryName(Path.GetFullPath(relativeTo)); string answer = ""; while (currentDir.Length > 0) { if (currentDir.Length <= fullPath.Length && string.Compare(currentDir, fullPath.Substring(0, currentDir.Length), StringComparison.OrdinalIgnoreCase) == 0) { answer += fullPath.Substring(currentDir.Length); if (answer.StartsWith(""+Path.DirectorySeparatorChar, StringComparison.Ordinal)) answer = answer.Substring(1); return answer; } answer += ".." + Path.DirectorySeparatorChar; if (currentDir.Length < 2) break; else { int lastSlash = currentDir.LastIndexOf(Path.DirectorySeparatorChar, currentDir.Length - 2); currentDir = currentDir.Substring(0, lastSlash + 1); } } return fullPath; } /// /// /// [To be supplied.] /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public DiscoveryClientResultCollection WriteAll(string directory, string topLevelFilename) { DiscoveryClientResultsFile results = new DiscoveryClientResultsFile(); Hashtable filenames = new Hashtable(); string topLevelFullPath = Path.Combine(directory, topLevelFilename); // write out each of the documents DictionaryEntry[] entries = new DictionaryEntry[Documents.Count + InlinedSchemas.Keys.Count]; int i = 0; foreach (DictionaryEntry entry in Documents) { entries[i++] = entry; } foreach (DictionaryEntry entry in InlinedSchemas) { entries[i++] = entry; } foreach (DictionaryEntry entry in entries) { string url = (string) entry.Key; object document = entry.Value; if (document == null) continue; DiscoveryReference reference = References[url]; string filename = reference == null ? DiscoveryReference.FilenameFromUrl(Url) : reference.DefaultFilename; filename = GetUniqueFilename(filenames, Path.GetFullPath(Path.Combine(directory, filename))); results.Results.Add(new DiscoveryClientResult(reference == null ? null : reference.GetType(), url, GetRelativePath(filename, topLevelFullPath))); Stream file = File.Create(filename); try { reference.WriteDocument(document, file); } finally { file.Close(); } } // write out the file that points to all those documents. XmlSerializer ser = new XmlSerializer(typeof(DiscoveryClientResultsFile)); Stream topLevelFile = File.Create(topLevelFullPath); try { ser.Serialize(new StreamWriter(topLevelFile, new UTF8Encoding(false)), results); } finally { topLevelFile.Close(); } return results.Results; } // public sealed class DiscoveryClientResultsFile { private DiscoveryClientResultCollection results = new DiscoveryClientResultCollection(); /// /// /// [To be supplied.] /// public DiscoveryClientResultCollection Results { get { return results; } } } } /// /// /// [To be supplied.] /// public sealed class DiscoveryClientResultCollection : CollectionBase { /// /// /// [To be supplied.] /// public DiscoveryClientResult this[int i] { get { return (DiscoveryClientResult) List[i]; } set { List[i] = value; } } /// /// /// [To be supplied.] /// public int Add(DiscoveryClientResult value) { return List.Add(value); } /// /// /// [To be supplied.] /// public bool Contains(DiscoveryClientResult value) { return List.Contains(value); } /// /// /// [To be supplied.] /// public void Remove(DiscoveryClientResult value) { List.Remove(value); } } /// /// /// [To be supplied.] /// public sealed class DiscoveryClientResult { string referenceTypeName; string url; string filename; /// /// /// [To be supplied.] /// public DiscoveryClientResult() { } /// /// /// [To be supplied.] /// public DiscoveryClientResult(Type referenceType, string url, string filename) { this.referenceTypeName = referenceType == null ? string.Empty : referenceType.FullName; this.url = url; this.filename = filename; } /// /// /// [To be supplied.] /// [XmlAttribute("referenceType")] public string ReferenceTypeName { get { return referenceTypeName; } set { referenceTypeName = value; } } /// /// /// [To be supplied.] /// [XmlAttribute("url")] public string Url { get { return url; } set { url = value; } } /// /// /// [To be supplied.] /// [XmlAttribute("filename")] public string Filename { get { return filename; } set { filename = value; } } } }