/* i-net software provides programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability and/or fitness for a particular purpose. This programming example assumes that you are familiar with the programming language being demonstrated and the tools used to create and debug procedures. i-net software support professionals can help explain the functionality of a particular procedure, but they will not modify these examples to provide added functionality or construct procedures to meet your specific needs. � i-net software 1998-2013 */ using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Compression; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Web; using System.Linq; using Inet.Viewer.Helper; using Inet.Viewer.Resources; namespace Inet.Viewer.Data { /// /// An implementation of IReportData for a URL connection to the i-net Clear Reports server. /// The report properties are attached via URL, a connection is made to the server and the /// report data is fetched. /// ///
Here are a couple of very simple examples of what could be done by creating /// certain URLRenderData objects and manipulating them. /// /// Create a URLRenderData and pass it to the constructor of a ReportView as a data source

///

    ///     // Update class viewer
    ///     IReportData data = new URLRenderData("http://server:9000/?report=C:/abc.rpt");
    ///     IReportViewer viewer = new ReportViewer();
    ///     viewer.AddNewReportView(data);
    ///     ...
    /// 
///

Notes for overriding

///

public class URLRenderData : IRenderData { private const float ChunkLengthFloat = 4096f; private const int ChunkLengthInt = 4096; /// /// This is the Parameter for the locale /// public const string ParameterLocale = "locale"; /// /// Url parameter used for defining if the prompt should popup with refresh /// https://www.inetsoftware.de/documentation/clear-reports/online-help/features/report-url-parameters#promptonrefresh /// public const string ParameterPromptOnRefresh = "promptonrefresh"; /// /// A constant used for a Url Parameter /// public const string ParameterSubreportOnDemand = "subreport_ondemand"; /// /// A constant used for a Url Parameter /// public const string ParameterSubReport = "subreport"; /// /// Url parameter init /// public const string ParameterInit = "init"; /// /// Url parameter init /// public const string ParameterHasGroupTree = "hasgrouptree"; /// /// Url parameter page /// public const string ParameterPage = "page"; /// /// Url parameter reorder used for the interactive sorting /// public const string ParameterReorder = "reorder"; /// /// An internal Url parameter /// public const string ParameterExportFmt = "export_fmt"; /// /// An internal Url parameter /// public const string ParameterLayout = "layout"; /// /// An internal Url parameter /// public const string ParameterFile = "file"; /// /// url viewer parameter /// public const string ParameterHasExportButton = "hasexportbutton"; /// /// url viewer parameter /// public const string ParameterHasPrintButton = "hasprintbutton"; /// /// url viewer parameter /// public const string ParameterHasRefreshButton = "hasrefreshbutton"; /// /// url viewer parameter /// public const string ParameterHasToolbar = "hastoolbar"; /// /// url viewer parameter /// public const string ParameterHasZoomControl = "haszoomcontrol"; /// /// Value for "true" in URL parameters. /// public const string ValueTrue = "true"; /// /// Value for "false" in URL parameters. /// public const string ValueFalse = "false"; private const string PropKeyCmd = "cmd"; private const string PropValueCmdGroupTree = "get_grouptree"; private const string PropValueCmdPage = "get_pg"; private const string PropValueCmdPageCount = "get_pg_count"; private const string PropValueCmdPageLimit = "get_pg_limit"; private const string PropValueCmdExport = "export"; private const string PropValueCmdPing = "ping"; private readonly static Encoding UTF8 = Encoding.UTF8; /// /// Mapping of Mime.Types to the format short cut. Used to check if the data input is correct. /// private readonly static System.Collections.Hashtable MimeTypes = new System.Collections.Hashtable(); /// /// Static constructor to set the mimetypes /// static URLRenderData() { MimeTypes["application/crystalclear"] = "bin"; MimeTypes["application/clearreports"] = "bin"; MimeTypes["application/pdf"] = "pdf"; MimeTypes["application/ps"] = "ps"; MimeTypes["application/msword"] = "rtf"; MimeTypes["application/rtf"] = "rtf"; MimeTypes["application/jra"] = "jra"; MimeTypes["application/jar"] = "jar"; MimeTypes["text/rtf"] = "rtf"; MimeTypes["application/vnd.ms-excel"] = "xls"; MimeTypes["text/x-comma-separated-values"] = "csv"; MimeTypes["text/comma-separated-values"] = "csv"; MimeTypes["text/xml"] = "xml"; MimeTypes["text/plain"] = "txt"; MimeTypes["text/html"] = "htm"; MimeTypes["image/svg+xml"] = "svg"; MimeTypes["image/bmp"] = "bmp"; MimeTypes["image/gif"] = "gif"; MimeTypes["image/png"] = "png"; MimeTypes["image/jpeg"] = "jpg"; MimeTypes["application/zip"] = "zip"; MimeTypes["application/vnd.oasis.opendocument.spreadsheet"] = "ods"; } private readonly Dictionary reportProps = new Dictionary(); private Dictionary exportProperties; private bool promptOnRefresh; private string title; private string requestURL; // report-file private long generationTime = System.DateTime.Now.Ticks / 1000; private bool isRefresh; private bool wasCanceled; private Status exportStatus; private bool forceUseGET; private CredentialCache credentialCache = new CredentialCache(); private List webRequests = new List(); private object webRequestsMutex = new object(); /// /// Default Constructor does nothing /// public URLRenderData() { } /// /// Creates a URLRenderData object with the given URL as the location to connect to to fetch the report. /// Note that any properties should be URL-encoded with the UTF-8 code page. Properties within /// this URL (such as http://server:9000/?report=report1.rpt&prompt0=12&init=pdf) are extracted and placed /// into this ReportData object's properties, which can be retrieved using getProperties. /// /// URL to connect to to fetch the report (Properties should be URL-encoded with the UTF-8 code page) /// If this is null, before connecting to fetch a report, setReportLocation must be called! /// /// public URLRenderData(string requestURL) { ReportLocation = requestURL; } /// /// Creates a URLRenderData object with the given URL as the location to connect to to fetch the report. /// Note that any properties should be encoded within the URL in the UTF-8 code page. Properties within /// this URL (such as http://server:9000/?report=report1.rpt&promptA=;12&init=pdf are extracted and placed /// into this ReportData object's properties, which can be retrieved using getProperties. /// /// URL to connect to to fetch the report (Properties should be URL-encoded in the UTF-8 code page.) /// If this is null, before connecting to fetch a report, setReportLocation must be called! /// /// public URLRenderData(System.Uri url) : this() { ReportLocation = url.ToString(); } /// /// Sets the URL to connect to to fetch the report, e.g. http://localhost:9000/?report=C:/report1.rpt /// Note that any properties should be URL-encoded in the UTF-8 code page. Properties within /// this URL (such as http://server:9000/?report=report1.rpt&prompt0=12&init=pdf) are extracted and placed /// into this ReportData object's properties, which can be retrieved using getProperties. /// The location may not be null. /// URL to connect to to fetch the report (Properties should be URL-encoded in the UTF-8 code page). May not be null. /// public string ReportLocation { set { if (value == null) { return; } reportProps[ParameterLocale] = CultureInfo.CurrentCulture.Name; int indexOfQuestionMark = value.IndexOf('?'); if (indexOfQuestionMark >= 0) { string rest = value.Substring(indexOfQuestionMark + 1); value = value.Substring(0, indexOfQuestionMark + 1); ExtractParameter(rest); } else { value += "?"; } requestURL = value; Reset(); } get { return requestURL; } } /// /// Extracts the URL Parameter of this string. The extracted parameters will be URL decoded and added to the reportProps /// the paramter "init" and "page" will be ignored /// /// private void ExtractParameter(string rest) { // Split on delimiters "&" and "=". The delimiters are included. const string Delimiter = "&="; string[] tokens = Regex.Split(rest, @"(?=[" + Delimiter + "])|(?<=[" + Delimiter + "])"); bool statusReadingProp = true; string prop = string.Empty; string value = string.Empty; for (int i = 0; i < tokens.Length; i++) { string token = tokens[i]; if (token.Equals("&")) { // don't add ignored properties if (prop != null && !prop.Equals(ParameterInit) && !prop.Equals(ParameterPage)) { AddEncodedProp(prop, value); } statusReadingProp = true; prop = string.Empty; value = string.Empty; } else if (token.Equals("=")) { statusReadingProp = false; } else { if (statusReadingProp) { prop = token; } else { value = token; } } } // to add the last parameter in the string! // don't add ignored properties if (prop != null && !prop.Equals(ParameterInit) && !prop.Equals(ParameterPage)) { AddEncodedProp(prop, value); } } /// /// Decode properties that are url encoded and adds them to reportProps /// /// list of allowed parameters /// The encoded property name /// The Encoded property value private void AddEncodedProp(string prop, string value) { if (ParameterPromptOnRefresh.ToUpper().Equals(prop.ToUpper())) { if ("true".ToUpper().Equals(value.ToUpper())) { PromptOnRefresh = true; } else if ("false".ToUpper().Equals(value.ToUpper())) { PromptOnRefresh = false; } // do not add to the parameters return; } string decodedProp; string decodedValue; try { decodedProp = HttpUtility.UrlDecode(prop, UTF8).ToLower(); decodedProp = HttpUtility.HtmlDecode(decodedProp); } catch (System.ArgumentException) { ViewerUtils.Debug("Error: " + prop + " could not be decoded. Reason: "); decodedProp = prop; } try { decodedValue = HttpUtility.UrlDecode(value, UTF8); decodedValue = HttpUtility.HtmlDecode(decodedValue); } catch (System.ArgumentException) { ViewerUtils.Debug("Error: " + value + " could not be decoded. Reason: "); decodedValue = value; } reportProps[decodedProp] = decodedValue; } /// /// Creates the url-suffix string for all Parameters contained in parameters /// (z.B. "param1=23432&param2=2323...") /// /// parameters to be extracted /// the created URL-string private string GetAppendedParameters(Dictionary parameters) { StringBuilder result = new StringBuilder(); System.Collections.IEnumerator e = parameters.Keys.GetEnumerator(); while (e.MoveNext()) { if (result.Length > 0) { result.Append('&'); } string key = (string)e.Current; string val = parameters[key]; result.Append(key); result.Append('='); result.Append(val); } return result.ToString(); } /// /// Writes the parameter as POST-Parameter to the URLConnection /// Parameter zum Posten /// The connection that should be used /// For the case one will be thrown private void WritePostVariables(Dictionary parameters, System.Net.WebRequest request) { // Normally ContentType is standard, but Opera Browser dosen't set it with POST requests. // The post data from Servlet Engines will be ignored without ContentType (Noticed mit Tomcat 5.5) request.ContentType = "application/x-www-form-urlencoded"; string method = request.Method; request.Method = "POST"; Stream reqStream = request.GetRequestStream(); System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(reqStream); System.Collections.IEnumerator e = parameters.Keys.GetEnumerator(); while (e.MoveNext()) { string key = (string)e.Current; string val = parameters[key]; streamWriter.Write(key + "=" + val + "&"); } streamWriter.Close(); } /// /// useGET means, prompts wil not be trasnmitted as POST-Parameter. Because of strange /// checksumme-problems with firewall-client this is the fallback option /// /// empty status-object for connection and data /// connection props /// chosen format, what should be returned /// Use GET instead of POST /// if problems with the connection /// If the mim-type is wrong private void InitURLConnection(Status status, Dictionary parameters, string format, bool useGET) { bool urlTooLong = false; string localRequestURL = requestURL; useGET = useGET || forceUseGET; if (useGET) { string generatedRequestUrl = GenerateRequestURLWithParametersAppended(parameters); if (generatedRequestUrl != null) { localRequestURL = generatedRequestUrl; } else { urlTooLong = true; } } // add default credentials for requested URI to provide automatic Windows Authentication AddCredential(new Uri(localRequestURL), new string[] { "NTLM" }, CredentialCache.DefaultNetworkCredentials); // authentication loop HttpWebRequest request = null; WebResponse response = null; while (response == null) { request = (HttpWebRequest)WebRequest.Create(localRequestURL); request.CookieContainer = CookieContainer; request.Credentials = credentialCache; request.Timeout = Timeout.Infinite; AddWebRequest(request); if (!useGET || urlTooLong) { WritePostVariables(parameters, request); } try { response = request.GetResponse(); } catch (WebException ex) { if (ex.Response is HttpWebResponse && ((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Unauthorized) { string authHeader = ((HttpWebResponse)ex.Response).GetResponseHeader("WWW-Authenticate"); int s = authHeader.IndexOf(' '); string realm = string.Empty; string[] authTypes; if (s == -1) { authTypes = authHeader.Split(','); } else { authTypes = authHeader.Substring(0, s).Split(','); Match m = Regex.Match(authHeader, @"realm=""([^""]*)", RegexOptions.IgnoreCase); if (m.Success) { realm = m.Groups[1].Value; } } if (Authenticator == null) { throw ex; } NetworkCredential credentials = Authenticator.Authenticate(localRequestURL, realm); if (credentials == null) { // user canceled throw ex; } AddCredential(new Uri(localRequestURL), authTypes, credentials); } else { throw; } } } Stream input = response.GetResponseStream(); status.Input = input; status.Request = request; ThrowExceptionIfCanceled(); status.ContentLength = response.ContentLength; string mimeType = response.ContentType; // check mime-type if (mimeType == null) { throw new ViewerException(strings.ErrorMessage_renderdata_ContentTypeNull + " " + status.ContentLength); // show content in the error dialog text area } if ("gzip".Equals(((HttpWebResponse)request.GetResponse()).ContentEncoding)) { status.Input = new GZipStream(status.Input, CompressionMode.Decompress); status.ContentLength = -1; } // contains additional MimeType attributes, like for html: text/html; charset=UTF-8 int idx = mimeType.IndexOf(';'); if (idx != -1) { mimeType = mimeType.Substring(0, idx); } string currentFormat = (string)MimeTypes[mimeType]; if (currentFormat == null || !IsSameFormat(format, currentFormat)) { // An error occured if ("bin".Equals(currentFormat)) { // Check if data contain an error message Loader loader = new Loader(); loader.Data = ReadData(status); // If contains one Error Token, throw a ViewerException loader.ReadTokens(); } ThrowExceptionIfCanceled(); string msg = null; try { byte[] buffer = new byte[1024]; int size = status.Input.Read(buffer, 0, buffer.Length); status.Input.Close(); if (mimeType.Equals("text/html")) { msg = new System.Text.ASCIIEncoding().GetString(buffer, 0, size); } else { msg = (size > 0) ? Loader.GetHexDump(buffer, 0, size) : "no data"; } } catch (Exception th) { //errors during error handling will be ignored ViewerUtils.PrintStackTrace(th); } throw new ViewerException(0, strings.ErrorMessage_Renderdata_WrongMimeType + " " + mimeType + " URL:" + localRequestURL, format, null, null, null, 0, msg); } return; } /// /// Add credentials for the specified URL to the cache. Any previous /// added credentials for the same URI and authentication types will be /// overwritten. /// /// the URI of the credential /// the authentication types of the credential /// the credential to add private void AddCredential(Uri uri, string[] authTypes, NetworkCredential credentials) { lock (credentialCache) { foreach (string authType in authTypes) { credentialCache.Remove(uri, authType); credentialCache.Add(uri, authType, credentials); } } } /// /// Generates the required requestURL by adding the parameters to the URL /// Parameters to add to the URL /// Generated requestURL or null if it is too long (and forceUseGet is not turned on) internal virtual string GenerateRequestURLWithParametersAppended(Dictionary parameters) { string result = requestURL; string appendedParameters = GetAppendedParameters(parameters); if (!result.EndsWith("?")) { result += "&"; } result += appendedParameters; if (result.Length > 1023 && !forceUseGET) { return null; } else { return result; } } /// /// Compares the current export format (curFormat) with the format of the mime-types (mimeFormat). Expect for the /// exportformat "data". For this format the same mime-type will be uses as for the csv export. /// /// current export format /// Format of the current Mime-Types /// "true" if formats are equals otherwise "false" private bool IsSameFormat(string curFormat, string mimeFormat) { if (curFormat.ToUpper().Equals("data".ToUpper())) { curFormat = "csv"; } else if ("zip".Equals(mimeFormat)) { if ("jpg".Equals(curFormat) || "png".Equals(curFormat) || "gif".Equals(curFormat) || "bmp".Equals(curFormat)) { return true; } } return curFormat.ToLower().StartsWith(mimeFormat); } /// /// Gets the bytes from the server of this requests, defined by the parameters /// Parameter for the request /// Format of the Reports ("java", "pdf", etc.) /// Requested bytes /// If a IOException or similar occurs the Exception will be wrapped by the ViewerException private byte[] StartURLConnection(Dictionary parameters, string format) { return StartURLConnection(parameters, format, true); } /// /// Gets the bytes from the server of this requests, defined by the parameters /// Parameter for the request /// Format of the Reports ("java", "pdf", etc.) /// Sets if timeouts should be ignored. Timeouts could mean that the server is still rendering /// Requestete Bytes /// If a IOException or similar occurs the Exception will be wrapped by the ViewerException private byte[] StartURLConnection(Dictionary parameters, string format, bool ignoreTimeouts) { do { Status status = new Status(); try { bool fallBack = true; try { InitURLConnection(status, parameters, format, false); // No exception. We have data, so no fallback fallBack = false; } catch (ViewerException e) { if (forceUseGET || (e.ServerVersion != null && e.ServerVersion.Length > 0) || wasCanceled) { // a real exception form the server or have tried a GET anyways or the loading was stopped by the user throw e; } else { // the exception was generated by the viewer. Something went wrong with the connection: // fallback to get ViewerUtils.Debug("Encountered: " + e.Message + " while trying POST request."); } } catch (WebException ex) { if (ex.Status == WebExceptionStatus.Timeout) { ThrowExceptionIfCanceled(); // ignore timeouts - wg. z.B. ganz gro?en Berichten ViewerUtils.Debug(strings.ErrorMessage_Renderdata_StillRendering); } else { // if the POST request gets a a 401 because of wrong authorisation, the GET should not be invoked. // The authorization would have to be cancled twice. if (status.Request is HttpWebRequest) { HttpWebRequest request = (HttpWebRequest)status.Request; HttpWebResponse response = (HttpWebResponse)ex.Response; HttpStatusCode responseCode = response.StatusCode; if (responseCode == HttpStatusCode.Unauthorized) { throw ex; } else if ((int)responseCode >= 300 && (int)responseCode <= 399) { // redirect ViewerUtils.Debug("encountered HTTP " + responseCode + " --> " + response.Headers.Get("Location")); } System.IO.Stream stream = response.GetResponseStream(); if (stream != null) { string contentType = response.ContentType; if ("gzip".Equals(response.ContentEncoding)) { stream = new GZipStream(stream, CompressionMode.Decompress); } ViewerUtils.Debug(ReadAllFromStream(ReadAllFromStream(stream), contentType)); } } if (forceUseGET || wasCanceled) { // IOException during GET / Cancel: forward throw ex; } // Ignore because of fallback ViewerUtils.Debug("Encountered: " + ex.Message + " while trying POST request."); } } if (!fallBack) { byte[] data = ReadData(status); string cmd = parameters[PropKeyCmd].ToString(); if ((cmd != null && (cmd.Equals(PropValueCmdPageCount) || cmd.Equals(PropValueCmdPing))) || CheckCheckSum(data) || forceUseGET) { // If PageCount (only 4 bytes) checksum is correct, or GET is used anyways: // take the data, if not use fallback. // checksum will be check again later, so it is not a problem using the data if we have tried a request with GET before return data; } else { ViewerUtils.Debug("Encountered Checksum Problem!"); } } // ---- // FALLBACK on GET // ---- ViewerUtils.Debug(" => Fallback to HTTP GET Request instead of POST"); InitURLConnection(status, parameters, format, true); return ReadData(status); } catch (WebException ex) { ViewerUtils.Error("Problem when attemping to connect via GET with URL '" + requestURL + "'"); ThrowExceptionIfCanceled(); HandleWebException(ex, status); } catch (ViewerException) { ThrowExceptionIfCanceled(); throw; } catch (System.Exception ex) { ThrowExceptionIfCanceled(); throw ViewerException.CreateViewerException(ex); } finally { try { if (status.Input != null) { status.Input.Close(); } } catch (System.Exception) { // error on error handling, ignore it } } } while (ignoreTimeouts); throw new ViewerException("Connection Timeout"); } private void HandleWebException(WebException ex, Status status) { try { if (ex.Response is HttpWebResponse) { HttpWebResponse response = (HttpWebResponse)ex.Response; if (response.StatusCode == HttpStatusCode.GatewayTimeout) { // Gateway (Proxy) Timeout, treat like SocketTimeout ViewerUtils.Debug(strings.ErrorMessage_Renderdata_StillRendering); return; } System.IO.Stream stream = response.GetResponseStream(); string contentType = response.ContentType; if ("gzip".Equals(response.ContentEncoding)) { stream = new GZipStream(stream, CompressionMode.Decompress); } System.IO.MemoryStream data = ReadAllFromStream(stream); // Check if it is a CC error message string currentFormat = (string)MimeTypes[contentType]; if ("bin".Equals(currentFormat)) { try { Loader loader = new Loader(); loader.Data = data.ToArray(); loader.ReadTokens(); //if error token throw ViewerException } catch (ViewerException) { throw; } catch (System.Exception) { // ignore and continue with normal error message } } string msg = ReadAllFromStream(data, contentType); ViewerUtils.Debug(msg); if (msg.StartsWith("")) { throw ViewerException.CreateViewerExceptionWithMessage(msg, ex); } ViewerException vex = ViewerException.CreateViewerException(ex); // don't show if message is too short if (msg.Length > 5) { vex.ServerStackTrace = msg; // non-html message are shown in details } throw vex; } if (status.Input != null) { status.Input.Close(); } } catch (System.IO.IOException) { //error on error handling, ignore it } throw ViewerException.CreateViewerException(ex); } /// /// Creates a Memory Stream out of the Stram /// Stream to read /// A MemoryStream /// For the case IO problems occur private System.IO.MemoryStream ReadAllFromStream(System.IO.Stream stream) { System.IO.MemoryStream baos = new System.IO.MemoryStream(); byte[] buffer = new byte[1024]; int count; while ((count = stream.Read(buffer, 0, buffer.Length)) > 0) { baos.Write(buffer, 0, count); } return baos; } /// /// Reads the whole stream into a string /// stream to read /// the contentType of the stream /// stream in a string private string ReadAllFromStream(System.IO.MemoryStream memStream, string contentType) { Encoding codePage = UTF8; if (contentType != null) { try { int idx = contentType.IndexOf("charset="); if (idx > 0) { string codePageName = contentType.Substring(idx + "charset=".Length); int length = codePageName.Length; if (length > 2) { int ch0 = codePageName[0]; int ch2 = codePageName[length - 1]; if (ch0 == ch2 && (ch0 == '\'' || ch0 == '"')) { codePageName = codePageName.Substring(1, length - 2); } } codePage = Encoding.GetEncoding(codePageName); } } catch (ArgumentException) { // do not use encoding return memStream.ToString(); } } return codePage.GetString(memStream.ToArray()); } /// /// Checks the checksum in advance. If necessary the data can be transmitted /// again with GET. /// byte data to be checked /// checken ob checksumme korrekt ist (checksumme steht ganz vorne) /// private bool CheckCheckSum(byte[] data) { // Checksumm check at the and fo the whole byte array: Adler32 Checksumme int offset = data.Length - 4; if (offset < 0) { return false; } int checksum = (data[offset++] << 24) + ((data[offset++] & 255) << 16) + ((data[offset++] & 255) << 8) + (data[offset++] & 255); Adler32 checker = new Adler32(); checker.Update(data, 0, data.Length - 4); int value = checker.Checksum; // Checksum-problem means to use GET instead of POST return value == checksum; } /// /// Reset the URL connection if a parameter was changed /// protected internal void Reset() { // At the moment only the status of export is saved exportStatus = null; } /// /// gets the status.contentLength bytes out of the InputStream. Reads all data from the stream. /// If the contentLength does not match with the real data length a IOException is thrown. /// /// Status object that contains information about the current position in the stream /// status.contentLength Bytes from the InputStream /// If there were problems to read private byte[] ReadData(Status status) { return ReadData(status, (int)status.ContentLength, true); } /// /// Fetches the Bytes with the length out of the InputStream. An exception is thrown if the contentLength does not matche with /// the amount of received byets. /// Status object, that contains the information about the position in the stream /// Amount of bytes to be fetched /// To be sure that the right amount of bytes are available. If not, an Exception is thrown, if the stream stopped /// length Bytes out of the InputStream /// When problems occur private byte[] ReadData(Status status, int length, bool contentLengthCertain) { ThrowExceptionIfCanceled(); if (length > 0) { //Read with ContentLength byte[] result = new byte[length]; int offset = 0; while (length > 0) { int count = status.Input.Read(result, offset, length); if (count <= 0) { if (contentLengthCertain) { throw new ViewerException(strings.ErrorMessage_Renderdata_WwrongPackageSize + " " + offset + " " + strings.ErrorMessage_Renderdata_BytesExpected + " " + (length + offset)); } else { if (offset > 0) { byte[] realResult = new byte[offset]; System.Array.Copy(result, 0, realResult, 0, offset); return realResult; } else { return null; } } } length -= count; offset += count; } return result; } else { // Read without ContentLength System.IO.MemoryStream baos = new System.IO.MemoryStream(); byte[] puffer = new byte[ChunkLengthInt]; while (true) { int count = status.Input.Read(puffer, 0, puffer.Length); if (count == -1) { return baos.GetBuffer(); } baos.Write(puffer, 0, count); } } } /// /// /// Any page number greater than 0 is viewed as valid - if the page number is higher than the /// number of pages in the report, this will return the data from the last page. This is for /// performance reasons, so that the page count does not need to be requested. /// public byte[] GetPageData(int page, bool refresh) { if (page < 1) { throw new ViewerException("Page out of allowed range: " + page); } isRefresh = refresh; wasCanceled = false; byte[] data; if (refresh) { data = StartURLConnection(GetPageParameters("rfsh", page), "bin"); } else { data = StartURLConnection(GetPageParameters(PropValueCmdPage, page), "bin"); } // check if there is an error message in the data Loader loader = new Loader(); loader.Data = data; loader.ReadTokens();// If an error occured throw a ViewerException return data; } /// /// /// public int GetPageCount() { wasCanceled = false; byte[] result = StartURLConnection(GetPageCountParameters(), "bin"); if (result.Length == 4) { return (result[0] << 24) + ((result[1] & 255) << 16) + ((result[2] & 255) << 8) + (result[3] & 255); } else { // Check if an error message is included in the data Loader loader = new Loader(); loader.Data = result; loader.ReadTokens(); // If no error token throw this ViewerException throw new ViewerException("unknown data from the server: " + result); } } /// /// /// public bool IsPageLimitExceeded { get { Dictionary props = GetSinglePageParameters(); props[PropKeyCmd] = PropValueCmdPageLimit; byte[] result = StartURLConnection(props, "bin"); PageLimitLoader loader = new PageLimitLoader(); loader.Data = result; loader.ReadTokens(); //if error token throw a ViewerException return loader.PageLimitExceeded; } } /// /// /// public byte[] NextExportChunk() { if (exportProperties == null) { throw new ViewerException(strings.ErrorMessage_Rrenderdata_GetChunkCountNeeded); } while (true) { try { if (exportStatus == null) { return null; } if (exportStatus.ContentLength < 0) { // ContentLength unknown byte[] bytes = ReadData(exportStatus, ChunkLengthInt, false); if (bytes == null) { exportStatus = null; } return bytes; } if (exportStatus.ContentLength < ChunkLengthInt) { // End of stream byte[] bytes = ReadData(exportStatus); exportStatus = null; return bytes; } exportStatus.ContentLength -= ChunkLengthInt; return ReadData(exportStatus, ChunkLengthInt, true); } catch (TimeoutException) { ThrowExceptionIfCanceled(); // ignore timeouts - wg. z.B. ganz gro?en Berichten ViewerUtils.Debug(strings.ErrorMessage_Renderdata_StillRendering); } catch (ViewerException) { throw; } catch (System.Exception e) { exportStatus.Input.Close(); ThrowExceptionIfCanceled(); throw ViewerException.CreateViewerException(e); } } } /// /// /// public int GetExportChunkCount(Dictionary expProps) { exportProperties = GetSinglePageParameters(); exportProperties[PropKeyCmd] = PropValueCmdExport; if (expProps != null) { System.Collections.IEnumerator propertyNames = expProps.Keys.GetEnumerator(); while (propertyNames.MoveNext()) { string key = (string)propertyNames.Current; exportProperties[key] = ViewerUtils.Encode(expProps[key].ToString()); } } else { throw new System.ArgumentException("Null is not supported as argument for getExportChunkCount."); } while (true) { try { exportStatus = new Status(); InitURLConnection(exportStatus, exportProperties, exportProperties["export_fmt"].ToString(), false); return (int)System.Math.Ceiling((double)exportStatus.ContentLength / ChunkLengthFloat); } catch (ViewerException e) { if (forceUseGET || (e.ServerVersion != null && e.ServerVersion.Length > 0) || wasCanceled) { // In this case we have a real exception from server, // or tried GET already, // or the lading was stopped by the user throw; } else { // in this case the exception was generated by our code. There was something wrong with // the connection: Fallback on GET ViewerUtils.Debug("Encountered: " + e.Message + " while trying POST request."); } } catch (TimeoutException) { ThrowExceptionIfCanceled(); // ignore timeouts - for big reports ViewerUtils.Debug(strings.ErrorMessage_Renderdata_StillRendering); } catch (System.IO.IOException e) { if (forceUseGET || wasCanceled) { // IOException with GET / Cancel: forward ThrowExceptionIfCanceled(); throw ViewerException.CreateViewerException(e); } exportStatus.Input.Close(); // Ignore because of fallback ViewerUtils.Debug("Encountered: " + e.Message + " while trying POST request."); } catch (System.Exception e) { if (exportStatus.Input != null) { exportStatus.Input.Close(); } ThrowExceptionIfCanceled(); throw ViewerException.CreateViewerException(e); } try { // ---- // FALLBACK on GET // ---- ViewerUtils.Debug(" => Fallback to HTTP GET Request instead of POST"); exportStatus = new Status(); InitURLConnection(exportStatus, exportProperties, exportProperties["export_fmt"].ToString(), true); return (int)System.Math.Ceiling((double)exportStatus.ContentLength / ChunkLengthFloat); } catch (ViewerException) { throw; } catch (System.Exception e) { exportStatus.Input.Close(); ThrowExceptionIfCanceled(); throw ViewerException.CreateViewerException(e); } } } /// /// Check to see if rendering process was canceled - if so, throw ViewerException, otherwise do nothing /// If Rendering process was canceled private void ThrowExceptionIfCanceled() { if (wasCanceled) { ViewerException e = new ViewerException(strings.ErrorMessage_Renderdata_PageRrenderingCanceled); e.Canceled = true; throw e; } } /// /// /// public byte[] GetGrouptreeData() { return StartURLConnection(GetGroupTreeParameters(), "bin"); } /// /// /// public string ReportTitle { get { return title; } set { this.title = value; } } /// /// returns a new Parameter Dictionary prefilled with "viewer=java2" and "vgen=generationTime" /// Parameter Dictionary with viewer and vgen prefilled private Dictionary GetBasicParameters() { Dictionary urlParams = new Dictionary(); urlParams["viewer"] = "java2"; urlParams["vgen"] = generationTime.ToString(); return urlParams; } /// /// /// /// the parameter including the report parameter private Dictionary GetSinglePageParameters() { Dictionary urlParams = GetBasicParameters(); // encode for reportProps lock (reportProps) { System.Collections.IEnumerator propertyNames = reportProps.Keys.GetEnumerator(); while (propertyNames.MoveNext()) { string key = (string)propertyNames.Current; urlParams[ViewerUtils.Encode(key)] = ViewerUtils.Encode(reportProps[key]); } } return urlParams; } /// /// Fetches the parameters to get the format of this page /// /// format to fetch /// page nummer /// Parameter Dictionary that contains parameters to fetch a page in the given format private Dictionary GetPageParameters(string cmd, int page) { Dictionary parameters = GetSinglePageParameters(); if (isRefresh && promptOnRefresh) { parameters[ParameterPromptOnRefresh] = "1"; } isRefresh = false; parameters[PropKeyCmd] = cmd; parameters["page"] = page.ToString(); return parameters; } /// /// Gets the parameters to request the data for the tree /// /// parameters to request the tree private Dictionary GetGroupTreeParameters() { Dictionary s = GetSinglePageParameters(); s[PropKeyCmd] = PropValueCmdGroupTree; return s; } /// /// /// /// the parameters to request the page count private Dictionary GetPageCountParameters() { Dictionary p = GetSinglePageParameters(); p[PropKeyCmd] = PropValueCmdPageCount; return p; } /// /// /// public string this[string key] { get { lock (reportProps) { if (key != null) { string value; reportProps.TryGetValue(key.ToLower(), out value); return value; } } return null; } set { lock (reportProps) { // all properties will be stored lower case. if (value == null) { reportProps.Remove(key.ToLower()); } else { reportProps[key.ToLower()] = value; } } Reset(); } } /// /// /// public override bool Equals(object obj) { if (obj == null || obj.GetType() != GetType()) { return false; } URLRenderData u = (URLRenderData)obj; if (requestURL.Equals(u.requestURL)) { reportProps.Equals(u.reportProps); return Collections.DictionaryEqual(reportProps, u.reportProps); } return false; } /// /// /// /// public override int GetHashCode() { return this.requestURL.GetHashCode(); } /// /// /// public bool PromptOnRefresh { set { Reset(); this.promptOnRefresh = value; } get { return promptOnRefresh; } } /// /// /// /// IReportData public object Clone() { URLRenderData r = new URLRenderData(ReportLocation); r.isRefresh = this.isRefresh; r.forceUseGET = this.forceUseGET; lock (this.reportProps) { Inet.Viewer.Helper.Collections.PutAll(r.reportProps, this.reportProps); } r.Authenticator = this.Authenticator; r.CookieContainer = this.CookieContainer; return r; } /// /// Sends the command "stop" to the server. The connection to the server will be stopped /// public void Stop() { wasCanceled = true; try { StartURLConnection(GetPageParameters("stop", 1), "bin", false); } catch (Exception) { // ignore exceptions while aborting } lock (webRequestsMutex) { foreach (WeakReference weakRef in webRequests) { WebRequest webRequest = (WebRequest)weakRef.Target; if (webRequest != null) { try { webRequest.Abort(); } catch (Exception) { // ignore exceptions while aborting } } } } } /// /// /// public byte[] Search(string phrase, int startPage, SearchOption flags) { Dictionary properties = GetSinglePageParameters(); properties[PropKeyCmd] = "search"; properties["phrase"] = ViewerUtils.Encode(phrase); properties["page"] = startPage.ToString(); properties["flags"] = flags.ToString(); return StartURLConnection(properties, "bin"); } /// /// /// public byte[] GetFontData(int fontID) { Dictionary properties = GetSinglePageParameters(); properties["export_fmt"] = "font"; properties[PropKeyCmd] = "get_pg"; properties["page"] = fontID.ToString(); return StartURLConnection(properties, "bin"); } /// /// /// public ReportState Ping() { Dictionary properties = GetSinglePageParameters(); properties[PropKeyCmd] = PropValueCmdPing; byte[] content = StartURLConnection(properties, "bin"); PingLoader loader = new PingLoader(); loader.Data = content; loader.ReadTokens(); return loader.RenderState; } /// /// If you set this to true, the URL request will never send parameters as POST parameters but only as a GET request. /// This is useful if your server cannot handle POST parameters, or if there are other connection problems causing trouble /// with POST parameters. /// Note that this may cause problems if there are too many parameters. /// Whether or not to force the use of a GET request public virtual bool ForceUseGET { set { this.forceUseGET = value; } get { return forceUseGET; } } /// /// Adds a webrequest encapsulated in a weak refrence to our list. /// /// the request to add private void AddWebRequest(HttpWebRequest request) { lock (webRequestsMutex) { // clean up the list first webRequests.RemoveAll(weakRef => weakRef.Target == null); webRequests.Add(new WeakReference(request)); } } /// /// A status object will be created with the creation of the stream. To remember the current position and to know /// how much more is to be read. /// private class Status { /// /// Current stream being used for data retrieval, otherwise null if no connection currently open. /// internal Stream Input { get; set; } /// /// The remaining length left to be read from the input stream in. /// internal long ContentLength { get; set; } /// /// To disconnect a status at any position /// internal HttpWebRequest Request { get; set; } } /// /// The authenticator which handles authentication request after any /// 401 responses from the server. /// public IAuthenticator Authenticator { get; set; } /// /// Handles authentification requests after 401 responses from the server. /// A typical implementation would show a dialog and let the user enter his /// login and password for the specified URL. /// public interface IAuthenticator { /// /// Returns a credential object for the specified URL. /// /// the URL /// the message from the server /// the credential object NetworkCredential Authenticate(string url, string message); } /// /// The cookie container which is shared with the other URLRenderData /// instances. /// public CookieContainer CookieContainer { get; set; } } }