// 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(); } } }