kyy
2025-07-02 07558e32634314eec359ec8437d97bdc5def64f9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright © 2021 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.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
 
namespace CefSharp.SchemeHandler
{
    //Shorthand for Owin pipeline func
    using AppFunc = Func<IDictionary<string, object>, Task>;
 
    /// <summary>
    /// <see cref="ResourceHandler"/> implementation that uses an OWIN capable host of fulfilling requests.
    /// Can be used with NancyFx or AspNet Core
    /// </summary>
    /// TODO:
    ///   - Multipart post data
    ///   - Cancellation Token
    public class OwinResourceHandler : ResourceHandler
    {
        private static readonly Dictionary<int, string> StatusCodeToStatusTextMapping = new Dictionary<int, string>
        {
            {200, "OK"},
            {301, "Moved Permanently"},
            {304, "Not Modified"},
            {404, "Not Found"}
        };
 
        private readonly AppFunc appFunc;
 
        /// <summary>
        /// OwinResourceHandler
        /// </summary>
        /// <param name="appFunc">Owin pipeline func</param>
        public OwinResourceHandler(AppFunc appFunc)
        {
            this.appFunc = appFunc;
        }
 
        /// <summary>
        /// Read the request, then process it through the OWEN pipeline
        /// then populate the response properties.
        /// </summary>
        /// <param name="request">request</param>
        /// <param name="callback">callback</param>
        /// <returns>always returns true as we'll handle all requests this handler is registered for.</returns>
        public override CefReturnValue ProcessRequestAsync(IRequest request, ICallback callback)
        {
            // PART 1 - Read the request - here we read the request and create a dictionary
            // that follows the OWEN standard
 
            var responseStream = new MemoryStream();
            var requestBody = Stream.Null;
 
            if (request.Method == "POST")
            {
                using (var postData = request.PostData)
                {
                    if (postData != null)
                    {
                        var postDataElements = postData.Elements;
 
 
                        var firstPostDataElement = postDataElements.First();
 
                        var bytes = firstPostDataElement.Bytes;
 
                        requestBody = new MemoryStream(bytes, 0, bytes.Length);
 
                        //TODO: Investigate how to process multi part POST data
                        //var charSet = request.GetCharSet();
                        //foreach (var element in elements)
                        //{
                        //    if (element.Type == PostDataElementType.Bytes)
                        //    {
                        //        var body = element.GetBody(charSet);
                        //    }
                        //}
                    }
                }
            }
 
            //var cancellationTokenSource = new CancellationTokenSource();
            //var cancellationToken = cancellationTokenSource.Token;
            var uri = new Uri(request.Url);
            var requestHeaders = ToDictionary(request.Headers);
            //Add Host header as per http://owin.org/html/owin.html#5-2-hostname
            requestHeaders.Add("Host", new[] { uri.Host + (uri.Port > 0 ? (":" + uri.Port) : "") });
 
            //http://owin.org/html/owin.html#3-2-environment
            //The Environment dictionary stores information about the request,
            //the response, and any relevant server state.
            //The server is responsible for providing body streams and header collections for both the request and response in the initial call.
            //The application then populates the appropriate fields with response data, writes the response body, and returns when done.
            //Keys MUST be compared using StringComparer.Ordinal.
            var owinEnvironment = new Dictionary<string, object>(StringComparer.Ordinal)
            {
                //Request http://owin.org/html/owin.html#3-2-1-request-data
                {"owin.RequestBody", requestBody},
                {"owin.RequestHeaders", requestHeaders},
                {"owin.RequestMethod", request.Method},
                {"owin.RequestPath", uri.AbsolutePath},
                {"owin.RequestPathBase", "/"},
                {"owin.RequestProtocol", "HTTP/1.1"},
                //To conform to the OWIN spec we need to remove the leading '?'
                {"owin.RequestQueryString", string.IsNullOrEmpty(uri.Query) ? string.Empty : uri.Query.Substring(1)},
                {"owin.RequestScheme", uri.Scheme},
                //Response http://owin.org/html/owin.html#3-2-2-response-data
                {"owin.ResponseHeaders", new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)},
                {"owin.ResponseBody", responseStream},
                //Other Data
                {"owin.Version", "1.0.0"},
                //{"owin.CallCancelled", cancellationToken}
            };
 
            //PART 2 - Spawn a new task to execute the OWIN pipeline
            //We execute this in an async fashion and return true so other processing
            //can occur
            Task.Run(async () =>
            {
                //Call into the OWEN pipeline
                try
                {
                    await appFunc(owinEnvironment);
 
                    //Response has been populated - reset the position to 0 so it can be read
                    responseStream.Position = 0;
 
                    int statusCode;
 
                    if (owinEnvironment.ContainsKey("owin.ResponseStatusCode"))
                    {
                        statusCode = Convert.ToInt32(owinEnvironment["owin.ResponseStatusCode"]);
                        //TODO: Improve status code mapping - see if CEF has a helper function that can be exposed
                        //StatusText = StatusCodeToStatusTextMapping[response.StatusCode];
                    }
                    else
                    {
                        statusCode = (int)HttpStatusCode.OK;
                        //StatusText = "OK";
                    }
 
                    //Grab a reference to the ResponseHeaders
                    var responseHeaders = (Dictionary<string, string[]>)owinEnvironment["owin.ResponseHeaders"];
 
                    //Populate the response properties
                    Stream = responseStream;
                    ResponseLength = responseStream.Length;
                    StatusCode = statusCode;
 
                    if(responseHeaders.ContainsKey("Content-Type"))
                    {
                        var contentType = responseHeaders["Content-Type"].First();
                        MimeType = contentType.Split(';').First();
                    }
                    else
                    {
                        MimeType = DefaultMimeType;
                    }                    
 
                    //Add the response headers from OWIN to the Headers NameValueCollection
                    foreach (var responseHeader in responseHeaders)
                    {
                        //It's possible for headers to have multiple values
                        foreach (var val in responseHeader.Value)
                        {
                            Headers.Add(responseHeader.Key, val);
                        }
                    }
                }
                catch(Exception ex)
                {
                    int statusCode = (int)HttpStatusCode.InternalServerError;
 
                    var responseData = GetByteArray("Error: " + ex.ToString(), System.Text.Encoding.UTF8, true);
 
                    //Populate the response properties
                    Stream = new MemoryStream(responseData);
                    ResponseLength = responseData.Length;
                    StatusCode = statusCode;
                    MimeType = "text/html";
                }
 
                //Once we've finished populating the properties we execute the callback
                //Callback wraps an unmanaged resource, so let's explicitly Dispose when we're done    
                using (callback)
                {
                    callback.Continue();
                }
            });
 
            return CefReturnValue.ContinueAsync;
        }
 
        private static IDictionary<string, string[]> ToDictionary(NameValueCollection nameValueCollection)
        {
            var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
            foreach (var key in nameValueCollection.AllKeys)
            {
                if (!dict.ContainsKey(key))
                {
                    dict.Add(key, new string[0]);
                }
                var strings = nameValueCollection.GetValues(key);
                if (strings == null)
                {
                    continue;
                }
                foreach (string value in strings)
                {
                    var values = dict[key].ToList();
                    values.Add(value);
                    dict[key] = values.ToArray();
                }
            }
            return dict;
        }
    }
}