Commit 80d6c482 authored by titanium007's avatar titanium007

Expose only safe properties

parent 74ed1c2e
......@@ -60,22 +60,22 @@ namespace Titanium.Web.Proxy.Test
Console.WriteLine(e.RequestURL);
//if (e.RequestURL.Contains("somewebsite.com"))
// if ((e.ProxyRequest.Method.ToUpper() == "POST" || e.ProxyRequest.Method.ToUpper() == "PUT") && e.ProxyRequest.ContentLength > 0)
// {
if (e.RequestURL.Contains("somewebsite.com"))
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT") && e.RequestContentLength > 0)
{
// var m = e.GetRequestBody().Replace("a", "b");
// e.SetRequestBody(m);
var m = e.GetRequestBody().Replace("a", "b");
e.SetRequestBody(m);
// }
}
////To cancel a request with a custom HTML content
////Filter URL
//To cancel a request with a custom HTML content
//Filter URL
//if (e.RequestURL.Contains("somewebsite.com"))
//{
// e.Ok("<!DOCTYPE html><html><body><h1>Blocked</h1><p>Website blocked.</p></body></html>");
//}
if (e.RequestURL.Contains("somewebsite.com"))
{
e.Ok("<!DOCTYPE html><html><body><h1>Blocked</h1><p>Website blocked.</p></body></html>");
}
}
......@@ -85,21 +85,22 @@ namespace Titanium.Web.Proxy.Test
{
//To modify a response
//if (e.ServerResponse.StatusCode == HttpStatusCode.OK)
//{
// if (e.ServerResponse.ContentType.Trim().ToLower().Contains("text/html"))
// {
// //Get response body
// string responseBody = e.GetResponseBody();
if (e.RequestURL.Contains("somewebsite.com"))
if (e.ResponseStatusCode == HttpStatusCode.OK)
{
if (e.ResponseContentType.Trim().ToLower().Contains("text/html"))
{
//Get response body
string responseBody = e.GetResponseBody();
// //Modify e.ServerResponse
// Regex rex = new Regex("</body>", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline);
// string modified = rex.Replace(responseBody, "<script type =\"text/javascript\">alert('Response was modified by this script!');</script></body>", 1);
//Modify e.ServerResponse
Regex rex = new Regex("</body>", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline);
string modified = rex.Replace(responseBody, "<script type =\"text/javascript\">alert('Response was modified by this script!');</script></body>", 1);
// //Set modifed response Html Body
// e.SetResponseBody(modified);
// }
//}
//Set modifed response Html Body
e.SetResponseBody(modified);
}
}
}
......
......@@ -14,25 +14,28 @@ namespace Titanium.Web.Proxy.Models
internal int BUFFER_SIZE;
internal TcpClient Client { get; set; }
internal Stream ClientStream { get; set; }
internal CustomBinaryReader ClientStreamReader { get; set; }
internal StreamWriter ClientStreamWriter { get; set; }
internal string HttpsHostName { get; set; }
internal string HttpsDecoratedHostName { get; set; }
internal int RequestContentLength { get; set; }
internal Encoding RequestEncoding { get; set; }
internal Version RequestHttpVersion { get; set; }
internal bool RequestIsAlive { get; set; }
internal bool CancelRequest { get; set; }
internal string RequestBody { get; set; }
internal bool RequestBodyRead { get; set; }
internal Encoding ResponseEncoding { get; set; }
internal Stream ResponseStream { get; set; }
internal string ResponseBody { get; set; }
internal bool ResponseBodyRead { get; set; }
internal TcpClient client { get; set; }
internal Stream clientStream { get; set; }
internal CustomBinaryReader clientStreamReader { get; set; }
internal StreamWriter clientStreamWriter { get; set; }
internal string httpsHostName { get; set; }
internal string httpsDecoratedHostName { get; set; }
internal int requestContentLength { get; set; }
internal Encoding requestEncoding { get; set; }
internal Version requestHttpVersion { get; set; }
internal bool requestIsAlive { get; set; }
internal bool cancelRequest { get; set; }
internal string requestBody { get; set; }
internal bool requestBodyRead { get; set; }
internal Encoding responseEncoding { get; set; }
internal Stream responseStream { get; set; }
internal string responseBody { get; set; }
internal bool responseBodyRead { get; set; }
internal HttpWebRequest proxyRequest { get; set; }
internal HttpWebResponse serverResponse { get; set; }
public int ClientPort { get; set; }
public IPAddress ClientIpAddress { get; set; }
......@@ -40,38 +43,44 @@ namespace Titanium.Web.Proxy.Models
public bool IsHttps { get; set; }
public string RequestURL { get; set; }
public string RequestHostname { get; set; }
public string RequestMethod { get { return this.proxyRequest.Method; } }
public int RequestContentLength { get { return requestContentLength; } }
public HttpWebRequest ProxyRequest { get; set; }
public HttpWebResponse ServerResponse { get; set; }
public HttpStatusCode ResponseStatusCode { get { return this.serverResponse.StatusCode; } }
public string ResponseContentType { get { return this.serverResponse.ContentType; } }
public void Dispose()
internal SessionEventArgs(int bufferSize)
{
if (this.ProxyRequest != null)
this.ProxyRequest.Abort();
BUFFER_SIZE = bufferSize;
}
if (this.ResponseStream != null)
this.ResponseStream.Dispose();
public void Dispose()
{
if (this.proxyRequest != null)
this.proxyRequest.Abort();
if (this.ServerResponse != null)
this.ServerResponse.Close();
if (this.responseStream != null)
this.responseStream.Dispose();
if (this.serverResponse != null)
this.serverResponse.Close();
}
public SessionEventArgs(int bufferSize)
{
BUFFER_SIZE = bufferSize;
}
public string GetRequestBody()
{
if ((ProxyRequest.Method.ToUpper() == "POST" || ProxyRequest.Method.ToUpper() == "PUT") && RequestContentLength > 0)
if ((proxyRequest.Method.ToUpper() == "POST" || proxyRequest.Method.ToUpper() == "PUT") && requestContentLength > 0)
{
if (RequestBody == null)
if (requestBody == null)
{
var buffer = ClientStreamReader.ReadBytes(RequestContentLength);
RequestBody = RequestEncoding.GetString(buffer);
var buffer = clientStreamReader.ReadBytes(requestContentLength);
requestBody = requestEncoding.GetString(buffer);
}
RequestBodyRead = true;
return RequestBody;
requestBodyRead = true;
return requestBody;
}
else
throw new BodyNotFoundException("Request don't have a body." +
......@@ -80,47 +89,47 @@ namespace Titanium.Web.Proxy.Models
}
public void SetRequestBody(string body)
{
this.RequestBody = body;
RequestBodyRead = true;
this.requestBody = body;
requestBodyRead = true;
}
public string GetResponseBody()
{
if (ResponseBody == null)
if (responseBody == null)
{
if (ResponseEncoding == null) ResponseEncoding = Encoding.GetEncoding(ServerResponse.CharacterSet);
if (ResponseEncoding == null) ResponseEncoding = Encoding.Default;
if (responseEncoding == null) responseEncoding = Encoding.GetEncoding(serverResponse.CharacterSet);
if (responseEncoding == null) responseEncoding = Encoding.Default;
switch (ServerResponse.ContentEncoding)
switch (serverResponse.ContentEncoding)
{
case "gzip":
ResponseBody = CompressionHelper.DecompressGzip(ResponseStream, ResponseEncoding);
responseBody = CompressionHelper.DecompressGzip(responseStream, responseEncoding);
break;
case "deflate":
ResponseBody = CompressionHelper.DecompressDeflate(ResponseStream, ResponseEncoding);
responseBody = CompressionHelper.DecompressDeflate(responseStream, responseEncoding);
break;
case "zlib":
ResponseBody = CompressionHelper.DecompressZlib(ResponseStream, ResponseEncoding);
responseBody = CompressionHelper.DecompressZlib(responseStream, responseEncoding);
break;
default:
ResponseBody = DecodeData(ResponseStream, ResponseEncoding);
responseBody = DecodeData(responseStream, responseEncoding);
break;
}
ResponseBodyRead = true;
responseBodyRead = true;
}
return ResponseBody;
return responseBody;
}
public void SetResponseBody(string body)
{
if (ResponseEncoding == null) ResponseEncoding = Encoding.GetEncoding(ServerResponse.CharacterSet);
if (ResponseEncoding == null) ResponseEncoding = Encoding.Default;
if (responseEncoding == null) responseEncoding = Encoding.GetEncoding(serverResponse.CharacterSet);
if (responseEncoding == null) responseEncoding = Encoding.Default;
this.ResponseBody = body;
ResponseBodyRead = true;
this.responseBody = body;
responseBodyRead = true;
}
//stream reader not recomended for images
private string DecodeData(Stream responseStream, Encoding e)
......@@ -138,8 +147,8 @@ namespace Titanium.Web.Proxy.Models
var result = Encoding.Default.GetBytes(html);
StreamWriter connectStreamWriter = new StreamWriter(ClientStream);
var s = String.Format("HTTP/{0}.{1} {2} {3}", RequestHttpVersion.Major, RequestHttpVersion.Minor, 200, "Ok");
StreamWriter connectStreamWriter = new StreamWriter(clientStream);
var s = String.Format("HTTP/{0}.{1} {2} {3}", requestHttpVersion.Major, requestHttpVersion.Minor, 200, "Ok");
connectStreamWriter.WriteLine(s);
connectStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString()));
connectStreamWriter.WriteLine("content-length: " + result.Length);
......@@ -147,7 +156,7 @@ namespace Titanium.Web.Proxy.Models
connectStreamWriter.WriteLine("Pragma: no-cache");
connectStreamWriter.WriteLine("Expires: 0");
if (RequestIsAlive)
if (requestIsAlive)
{
connectStreamWriter.WriteLine("Connection: Keep-Alive");
}
......@@ -157,10 +166,10 @@ namespace Titanium.Web.Proxy.Models
connectStreamWriter.WriteLine();
connectStreamWriter.Flush();
ClientStream.Write(result, 0, result.Length);
clientStream.Write(result, 0, result.Length);
CancelRequest = true;
cancelRequest = true;
}
......
......@@ -180,9 +180,9 @@ namespace Titanium.Web.Proxy
var args = new SessionEventArgs(BUFFER_SIZE);
args.Client = client;
args.HttpsHostName = httpsHostName;
args.HttpsDecoratedHostName = httpsDecoratedHostName;
args.client = client;
args.httpsHostName = httpsHostName;
args.httpsDecoratedHostName = httpsDecoratedHostName;
try
{
......@@ -216,14 +216,14 @@ namespace Titanium.Web.Proxy
}
//construct the web request that we are going to issue on behalf of the client.
args.ProxyRequest = (HttpWebRequest)HttpWebRequest.Create(remoteUri.Trim());
args.ProxyRequest.Proxy = null;
args.ProxyRequest.UseDefaultCredentials = true;
args.ProxyRequest.Method = method;
args.ProxyRequest.ProtocolVersion = version;
args.ClientStream = clientStream;
args.ClientStreamReader = clientStreamReader;
args.ClientStreamWriter = clientStreamWriter;
args.proxyRequest = (HttpWebRequest)HttpWebRequest.Create(remoteUri.Trim());
args.proxyRequest.Proxy = null;
args.proxyRequest.UseDefaultCredentials = true;
args.proxyRequest.Method = method;
args.proxyRequest.ProtocolVersion = version;
args.clientStream = clientStream;
args.clientStreamReader = clientStreamReader;
args.clientStreamWriter = clientStreamWriter;
for (int i = 1; i < requestLines.Count; i++)
{
......@@ -242,46 +242,46 @@ namespace Titanium.Web.Proxy
}
}
SetClientRequestHeaders(requestLines, args.ProxyRequest);
SetClientRequestHeaders(requestLines, args.proxyRequest);
args.ProxyRequest.AllowAutoRedirect = false;
args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None;
args.RequestHostname = args.ProxyRequest.RequestUri.Host;
args.RequestURL = args.ProxyRequest.RequestUri.OriginalString;
args.proxyRequest.AllowAutoRedirect = false;
args.proxyRequest.AutomaticDecompression = DecompressionMethods.None;
args.RequestHostname = args.proxyRequest.RequestUri.Host;
args.RequestURL = args.proxyRequest.RequestUri.OriginalString;
args.ClientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port;
args.ClientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
args.RequestHttpVersion = version;
args.RequestIsAlive = args.ProxyRequest.KeepAlive;
args.requestHttpVersion = version;
args.requestIsAlive = args.proxyRequest.KeepAlive;
//If requested interception
if (BeforeRequest != null)
{
args.RequestContentLength = (int)args.ProxyRequest.ContentLength;
args.RequestEncoding = args.ProxyRequest.GetEncoding();
args.requestContentLength = (int)args.proxyRequest.ContentLength;
args.requestEncoding = args.proxyRequest.GetEncoding();
BeforeRequest(null, args);
}
string tmpLine;
if (args.CancelRequest)
if (args.cancelRequest)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
args.ProxyRequest.ConnectionGroupName = args.RequestHostname;
args.ProxyRequest.AllowWriteStreamBuffering = true;
args.proxyRequest.ConnectionGroupName = args.RequestHostname;
args.proxyRequest.AllowWriteStreamBuffering = true;
//If request was modified by user
if (args.RequestBodyRead)
if (args.requestBodyRead)
{
byte[] requestBytes = args.RequestEncoding.GetBytes(args.RequestBody);
args.ProxyRequest.ContentLength = requestBytes.Length;
Stream newStream = args.ProxyRequest.GetRequestStream();
byte[] requestBytes = args.requestEncoding.GetBytes(args.requestBody);
args.proxyRequest.ContentLength = requestBytes.Length;
Stream newStream = args.proxyRequest.GetRequestStream();
newStream.Write(requestBytes, 0, requestBytes.Length);
args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
}
else
......@@ -294,7 +294,7 @@ namespace Titanium.Web.Proxy
}
//Http request body sent, now wait asynchronously for response
args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
}
......@@ -303,15 +303,15 @@ namespace Titanium.Web.Proxy
tmpLine = null;
requestLines.Clear();
while (!String.IsNullOrEmpty(tmpLine = args.ClientStreamReader.ReadLine()))
while (!String.IsNullOrEmpty(tmpLine = args.clientStreamReader.ReadLine()))
{
requestLines.Add(tmpLine);
}
httpCmd = requestLines.Count() > 0 ? requestLines[0] : null;
TcpClient Client = args.Client;
TcpClient Client = args.client;
//Http request body sent, now wait for next request
Task.Factory.StartNew(() => HandleHttpSessionRequest(Client, httpCmd, args.ClientStream, args.HttpsHostName, requestLines, args.ClientStreamReader, args.ClientStreamWriter, args.HttpsDecoratedHostName));
Task.Factory.StartNew(() => HandleHttpSessionRequest(Client, httpCmd, args.clientStream, args.httpsHostName, requestLines, args.clientStreamReader, args.clientStreamWriter, args.httpsDecoratedHostName));
}
catch
......@@ -436,32 +436,32 @@ namespace Titanium.Web.Proxy
{
// End the operation
Stream postStream = args.ProxyRequest.GetRequestStream();
Stream postStream = args.proxyRequest.GetRequestStream();
if (args.ProxyRequest.ContentLength > 0)
if (args.proxyRequest.ContentLength > 0)
{
args.ProxyRequest.AllowWriteStreamBuffering = true;
args.proxyRequest.AllowWriteStreamBuffering = true;
try
{
int totalbytesRead = 0;
int bytesToRead;
if (args.ProxyRequest.ContentLength < BUFFER_SIZE)
if (args.proxyRequest.ContentLength < BUFFER_SIZE)
{
bytesToRead = (int)args.ProxyRequest.ContentLength;
bytesToRead = (int)args.proxyRequest.ContentLength;
}
else
bytesToRead = BUFFER_SIZE;
while (totalbytesRead < (int)args.ProxyRequest.ContentLength)
while (totalbytesRead < (int)args.proxyRequest.ContentLength)
{
var buffer = args.ClientStreamReader.ReadBytes(bytesToRead);
var buffer = args.clientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
int RemainingBytes = (int)args.ProxyRequest.ContentLength - totalbytesRead;
int RemainingBytes = (int)args.proxyRequest.ContentLength - totalbytesRead;
if (RemainingBytes < bytesToRead)
{
bytesToRead = RemainingBytes;
......@@ -485,9 +485,9 @@ namespace Titanium.Web.Proxy
}
//Need to revist, find any potential bugs
else if (args.ProxyRequest.SendChunked)
else if (args.proxyRequest.SendChunked)
{
args.ProxyRequest.AllowWriteStreamBuffering = true;
args.proxyRequest.AllowWriteStreamBuffering = true;
try
{
......@@ -496,7 +496,7 @@ namespace Titanium.Web.Proxy
while (true)
{
args.ClientStream.Read(byteRead, 0, 1);
args.clientStream.Read(byteRead, 0, 1);
sb.Append(Encoding.ASCII.GetString(byteRead));
if (sb.ToString().EndsWith(Environment.NewLine))
......@@ -507,7 +507,7 @@ namespace Titanium.Web.Proxy
{
for (int i = 0; i < Encoding.ASCII.GetByteCount(Environment.NewLine); i++)
{
args.ClientStream.ReadByte();
args.clientStream.ReadByte();
}
break;
}
......@@ -523,7 +523,7 @@ namespace Titanium.Web.Proxy
while (totalbytesRead < chunckSize)
{
var buffer = args.ClientStreamReader.ReadBytes(bytesToRead);
var buffer = args.clientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
int RemainingBytes = chunckSize - totalbytesRead;
......@@ -537,7 +537,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < Encoding.ASCII.GetByteCount(Environment.NewLine); i++)
{
args.ClientStream.ReadByte();
args.clientStream.ReadByte();
}
sb.Clear();
......
......@@ -25,72 +25,72 @@ namespace Titanium.Web.Proxy
try
{
args.ServerResponse = (HttpWebResponse)args.ProxyRequest.EndGetResponse(asynchronousResult);
args.serverResponse = (HttpWebResponse)args.proxyRequest.EndGetResponse(asynchronousResult);
}
catch (WebException webEx)
{
//Things line 404, 500 etc
args.ServerResponse = webEx.Response as HttpWebResponse;
args.serverResponse = webEx.Response as HttpWebResponse;
}
try
{
if (args.ServerResponse != null)
if (args.serverResponse != null)
{
List<Tuple<String, String>> responseHeaders = ProcessResponse(args.ServerResponse);
args.ResponseStream = args.ServerResponse.GetResponseStream();
List<Tuple<String, String>> responseHeaders = ProcessResponse(args.serverResponse);
args.responseStream = args.serverResponse.GetResponseStream();
bool isChunked = args.ServerResponse.GetResponseHeader("transfer-encoding") == null ? false : args.ServerResponse.GetResponseHeader("transfer-encoding").ToLower() == "chunked" ? true : false;
bool isChunked = args.serverResponse.GetResponseHeader("transfer-encoding") == null ? false : args.serverResponse.GetResponseHeader("transfer-encoding").ToLower() == "chunked" ? true : false;
if (BeforeResponse != null)
BeforeResponse(null, args);
if (args.ResponseBodyRead)
if (args.responseBodyRead)
{
byte[] data;
switch (args.ServerResponse.ContentEncoding)
switch (args.serverResponse.ContentEncoding)
{
case "gzip":
data = CompressionHelper.CompressGzip(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked);
data = CompressionHelper.CompressGzip(args.responseBody, args.responseEncoding);
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, responseHeaders, data.Length);
SendData(args.clientStream, data, isChunked);
break;
case "deflate":
data = CompressionHelper.CompressDeflate(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked);
data = CompressionHelper.CompressDeflate(args.responseBody, args.responseEncoding);
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, responseHeaders, data.Length);
SendData(args.clientStream, data, isChunked);
break;
case "zlib":
data = CompressionHelper.CompressZlib(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked);
data = CompressionHelper.CompressZlib(args.responseBody, args.responseEncoding);
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, responseHeaders, data.Length);
SendData(args.clientStream, data, isChunked);
break;
default:
data = EncodeData(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked);
data = EncodeData(args.responseBody, args.responseEncoding);
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, responseHeaders, data.Length);
SendData(args.clientStream, data, isChunked);
break;
}
}
else
{
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders);
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, responseHeaders);
if (isChunked)
SendChunked(args.ResponseStream, args.ClientStream);
SendChunked(args.responseStream, args.clientStream);
else
SendNormal(args.ResponseStream, args.ClientStream);
SendNormal(args.responseStream, args.clientStream);
}
args.ClientStream.Flush();
args.clientStream.Flush();
}
......@@ -98,7 +98,7 @@ namespace Titanium.Web.Proxy
}
catch
{
Dispose(args.Client, args.ClientStream, args.ClientStreamReader, args.ClientStreamWriter, args);
Dispose(args.client, args.clientStream, args.clientStreamReader, args.clientStreamWriter, args);
}
finally
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment