// Copyright © 2019 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
using System;
using System.Collections.Specialized;
using System.IO;
using CefSharp.Callback;
namespace CefSharp.Lagacy
{
///
/// Legacy ResourceHandler, will be removed when CEF removes the old code path for
/// it's CefResourceHandler implementation. This is the older and well tested variant.
/// It doesn't however support range request headers (seek).
///
public class ResourceHandler : IResourceHandler
{
///
/// Gets or sets the Charset
///
public string Charset { get; set; }
///
/// Gets or sets the Mime Type.
///
public string MimeType { get; set; }
///
/// Gets or sets the resource stream.
///
public Stream Stream { get; set; }
///
/// Gets or sets the http status code.
///
public int StatusCode { get; set; }
///
/// Gets or sets the status text.
///
public string StatusText { get; set; }
///
/// Gets or sets ResponseLength, when you know the size of your
/// Stream (Response) set this property. This is optional.
/// If you use a MemoryStream and don't provide a value
/// here then it will be cast and it's size used
///
public long? ResponseLength { get; set; }
///
/// Gets or sets the headers.
///
/// The headers.
public NameValueCollection Headers { get; private set; }
///
/// When true the Stream will be Disposed when
/// this instance is Disposed. The default value for
/// this property is false.
///
public bool AutoDisposeStream { get; set; }
///
/// If the ErrorCode is set then the response will be ignored and
/// the errorCode returned.
///
public CefErrorCode? ErrorCode { get; set; }
///
/// Initializes a new instance of the class.
///
/// Optional mimeType defaults to
/// Optional Stream - must be set at some point to provide a valid response
/// When true the Stream will be disposed when this instance is Diposed, you will
/// be unable to use this ResourceHandler after the Stream has been disposed
/// response charset
public ResourceHandler(string mimeType = CefSharp.ResourceHandler.DefaultMimeType, Stream stream = null, bool autoDisposeStream = false, string charset = null)
{
if (string.IsNullOrEmpty(mimeType))
{
throw new ArgumentNullException("mimeType", "Please provide a valid mimeType");
}
StatusCode = 200;
StatusText = "OK";
MimeType = mimeType;
Headers = new NameValueCollection();
Stream = stream;
AutoDisposeStream = autoDisposeStream;
Charset = charset;
}
///
/// Begin processing the request. If you have the data in memory you can execute the callback
/// immediately and return true. For Async processing you would typically spawn a Task to perform processing,
/// then return true. When the processing is complete execute callback.Continue(); In your processing Task, simply set
/// the StatusCode, StatusText, MimeType, ResponseLength and Stream
///
/// The request object.
/// The callback used to Continue or Cancel the request (async).
/// To handle the request return true and call
/// once the response header information is available
/// can also be called from inside this method if
/// header information is available immediately).
/// To cancel the request return false.
protected virtual bool ProcessRequestAsync(IRequest request, ICallback callback)
{
callback.Continue();
return true;
}
///
/// Called if the request is cancelled
///
protected virtual void Cancel()
{
}
///
/// Dispose of resources here
///
protected virtual void Dispose()
{
if (AutoDisposeStream && Stream != null)
{
Stream.Dispose();
Stream = null;
}
}
///
/// Populate the response stream, response length. When this method is called
/// the response should be fully populated with data.
/// It is possible to redirect to another url at this point in time.
/// NOTE: It's no longer manditory to implement this method, you can simply populate the
/// properties of this instance and they will be set by the default implementation.
///
/// The response object used to set Headers, StatusCode, etc
/// length of the response
/// If set the request will be redirect to specified Url
/// The response stream
protected virtual Stream GetResponse(IResponse response, out long responseLength, out string redirectUrl)
{
redirectUrl = null;
responseLength = -1;
response.MimeType = MimeType;
response.StatusCode = StatusCode;
response.StatusText = StatusText;
response.Headers = Headers;
if (!string.IsNullOrEmpty(Charset))
{
response.Charset = Charset;
}
if (ResponseLength.HasValue)
{
responseLength = ResponseLength.Value;
}
else
{
//If no ResponseLength provided then attempt to infer the length
if (Stream != null && Stream.CanSeek)
{
responseLength = Stream.Length;
}
}
return Stream;
}
void IResourceHandler.Cancel()
{
Cancel();
Stream = null;
}
void IDisposable.Dispose()
{
Dispose();
}
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl)
{
if (ErrorCode.HasValue)
{
responseLength = 0;
redirectUrl = null;
response.ErrorCode = ErrorCode.Value;
}
else
{
Stream = GetResponse(response, out responseLength, out redirectUrl);
if (Stream != null && Stream.CanSeek)
{
//Reset the stream position to 0
Stream.Position = 0;
}
}
}
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback)
{
callback.Dispose();
//Legacy behaviour
handleRequest = false;
return false;
}
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback)
{
return ProcessRequestAsync(request, callback);
}
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback)
{
//Legacy behaviour
bytesRead = -1;
return false;
}
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback)
{
//We don't need the callback, as it's an unmanaged resource we should dispose it (could wrap it in a using statement).
callback.Dispose();
if (Stream == null)
{
bytesRead = 0;
return false;
}
//Data out represents an underlying buffer (typically 32kb in size).
var buffer = new byte[dataOut.Length];
bytesRead = Stream.Read(buffer, 0, buffer.Length);
//If bytesRead is 0 then no point attempting a write to dataOut
if (bytesRead == 0)
{
return false;
}
dataOut.Write(buffer, 0, buffer.Length);
return bytesRead > 0;
}
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback)
{
throw new NotImplementedException();
}
}
}