/*
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
/// - The methods of this class must be thread safe except for the methods
///
GetExportChunkCount
and GetNextExportChunk
.
/// - Each IReportView can have its own instance of ReportData, so the different
/// IReportData methods may be called simultaneously for different ReportViews and different Report instances.
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¶m2=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; }
}
}