Request filter in HttpModule only works for form data
Hi,
Why does request filters in HttpModule only get triggered when the content type is x-www-form-urlencoded? A code example can be seen below.
Kind regards, Jesper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace MyWebApplication
{
public class MyHttpModule: IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += Application_BeginRequest;
}
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication) source;
HttpContext context = application.Context;
context.Request.Filter = new MyRequestFilter(context.Request.Filter, context.Request.ContentEncoding);
var _ = context.Request.Form; // To trigger the evaluation of the inputstream
}
public void Dispose() { }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyWebApplication
{
class MyRequestFilter: Stream
{
private MemoryStream ms;
private Stream _stream;
private Encoding _encoding;
public RequestFilter(Stream stream, Encoding encoding)
{
_stream = stream;
_encoding = encoding;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (ms == null)
{
var sr = new StreamReader(_stream, _encoding);
string content = sr.ReadToEnd();
content = content.ToUpper();
Byte[] bytes = _encoding.GetBytes(content);
ms = new MemoryStream();
ms.Write(bytes, 0, bytes.Length);
ms.Seek(0, SeekOrigin.Begin);
}
return ms.Read(buffer, offset, count);
}
// rest of the overrides
// ...
}
}
ASP.NET
C#
-
Yijing Sun-MSFT 7,081 Reputation points
2021-05-28T06:58:55.663+00:00 Hi @Jesper Lundin ,
Do you have register post method in HttpModule? Could you post web.config to us?
Best regards,
Yijing Sun -
Jesper Lundin 1 Reputation point
2021-05-28T08:00:14.913+00:00 Here is the webconfig. I had to rename the extension to .txt to be able to upload it.
I added logging to the project and I can see that the Read method in the request filter is executed. Also, post works when the content type is x-www-form-urlencoded but not for example json. Here are some examples from Postman. I am using an Web API which responds with the same content as the request.
Here is an example with form data
Here is an example with json
-
Yijing Sun-MSFT 7,081 Reputation points
2021-05-28T09:09:44.717+00:00 Hi @Jesper Lundin ,
I think,you need to reset the position of the stream before reading and use Json.NET to deserialize to an object.
Best regards,
Yijing Sun -
Jesper Lundin 1 Reputation point
2021-05-28T11:49:57.087+00:00 Hi YijingSun-MSFT,
Where do you want me to reset the position of the stream?
The request filter above only changes the request content to upper case (line 58) so there should not be any need to deserialize to an object.
If you notice line 22,
var _ = context.Request.Form; // To trigger the evaluation of the inputstream
This seems to trigger an evaluation of the input stream and the filter gets applied but only when the content-type is x-www-form-urlencoded but not for json. If I move this line and place it before I add the request filter (on line 21) then this filter stops working for x-www-form-urlencoded as well.
Why do I get this strange behaviour and how can I fix it so that the filter gets applied to all request no matter what the content-type is?
Kind regards, Jesper
-
Yijing Sun-MSFT 7,081 Reputation points
2021-05-31T08:25:51.053+00:00 Hi @Jesper Lundin ,
You might need to pay attention is that the original stream is loaded from the stack is not at the constructor time. It is right before the first read().You could refer to this:
https://jilongliao.com/2016/07/06/Request-Filtering/
Best regards,
Yijing Sun -
Jesper Lundin 1 Reputation point
2021-06-11T12:51:53.943+00:00 Hi YijingSun-MSFT,
I tried the link you provided but unfortunately it does not help me,
- The example you provided stops the request from reaching the site in the ProcessStreamContent() method and creates its own response. In my case I still want the request to reach the site. Also, even if I change the ProcessStreamContent() method I don't think it will affect the request content since the content is already written back to the ms object. Or am I missing something?
- I added some logging to the request filter and the only method that is called is the constructor and read method. My code does also load the streamcontent in the first read in the same way as in the link you provided.
Kind regards, Jesper
-
Jesper Lundin 1 Reputation point
2021-06-11T12:58:24.16+00:00 But if I comment out the line 55 in RequestFilter,
//RejectRequest(currentHttpContext, "test");
Change line 45 in RequestFilter to
streamContent = sr.ReadToEnd().ToUpper();
And add this line at the end of the OnPostAuthenticateRequest
var _ = app.Context.Request.Form;
Then the request will be changed to uppercase if the request content is of type x-www-form-urlencoded but not if it is raw json. I will attach both the files of the HttpModule and the WebApi file so you can see how the WebApi is reading the request. I changed the extention from .cs to .txt to be able to upload them here.
Kind regards, Jesper
104851-accessrestrictionmodule.txt
104852-requestfilter.txt
104748-simpleechocontroller.txt -
Yijing Sun-MSFT 7,081 Reputation points
2021-06-14T08:57:59.15+00:00 Hi @Jesper Lundin ,
We couldn't read it that the server refuses to authorize it. You could post important content inside text to us.
Best regards,
Yijing Sun -
Jesper Lundin 1 Reputation point
2021-06-17T14:52:52.343+00:00 Here is the HttpModule
namespace AccessRestrictionModule { public class AccessRestrictionModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication app) { app.PostAuthenticateRequest += OnPostAuthenticateRequest; } private void OnPostAuthenticateRequest(object sender, EventArgs args) { HttpApplication app = (HttpApplication)sender; app.Context.Request.Filter = new RequestFilter(app.Context.Request.Filter, app.Context); var _ = app.Context.Request.Form; } } }
-
Jesper Lundin 1 Reputation point
2021-06-17T14:54:34.95+00:00 Here is the request filter
PART1namespace AccessRestrictionModule { internal class RequestFilter : Stream { private static readonly ILog log = LogManager.GetLogger(typeof(RequestFilter)); private MemoryStream ms; private Stream originalStream; private string streamContent; private HttpContext currentHttpContext; public RequestFilter(Stream stream, HttpContext context) { log.Info("RequestFilter"); currentHttpContext = context; originalStream = stream; } public override bool CanRead { get { log.Info("CanRead"); TryLoadStream(); return true; } }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:00:08.26+00:00 PART2
public override long Length { get { log.Info("Length"); TryLoadStream(); return ms.Length; } } public override int Read(byte[] buffer, int offset, int count) { log.Info("Read"); TryLoadStream(); // process your content.. ProcessStreamContent(); return ms.Read(buffer, offset, count); }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:01:09.663+00:00 PART3
private void TryLoadStream() { log.Info("TryLoadStream"); if (String.IsNullOrEmpty(streamContent)) { StreamReader sr = new StreamReader(originalStream, currentHttpContext.Request.ContentEncoding); streamContent = sr.ReadToEnd(); log.Info($"TryLoadStream - BEFORE: {streamContent}"); streamContent = streamContent.ToUpper(); log.Info($"TryLoadStream - AFTER: {streamContent}"); ms = new MemoryStream(); byte[] bytes = currentHttpContext.Request.ContentEncoding.GetBytes(streamContent); ms.Write(bytes, 0, bytes.Length); ms.Seek(0, SeekOrigin.Begin); } } private void ProcessStreamContent() { log.Info("ProcessStreamContent"); }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:02:02.423+00:00 PART4
public override bool CanSeek { get { log.Info("CanSeek"); TryLoadStream(); return ms.CanSeek; } } public override bool CanWrite { get { log.Info("CanWrite"); TryLoadStream(); return ms.CanWrite; } } public override long Position { get { log.Info("Position.get"); TryLoadStream(); return ms.Position; } set { log.Info("Position.set"); TryLoadStream(); ms.Position = value; } } public override void Flush() { log.Info("Flush"); TryLoadStream(); ms.Flush(); }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:02:43.993+00:00 PART5:
public override long Seek(long offset, SeekOrigin origin) { log.Info("Seek"); TryLoadStream(); return ms.Seek(offset, origin); } public override void SetLength(long value) { log.Info("SetLength"); TryLoadStream(); ms.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { log.Info("Write"); TryLoadStream(); ms.Write(buffer, offset, count); } } }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:04:20.24+00:00 And here is the RestAPI:
PART1namespace Echo.Controllers { public class SimpleEchoController : ApiController { private static Logger log = LogManager.GetCurrentClassLogger(); // JSON [HttpPost, Route("EchoJson"), Route("EchoJson.aspx")] public HttpResponseMessage EchoJson() { string payload = Request.Content.ReadAsStringAsync().Result; log.Info($"JsonToJsonResponse: Payload: {payload}"); return Request.CreateResponse(HttpStatusCode.OK, JObject.Parse(payload), new JsonMediaTypeFormatter()); }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:04:43.097+00:00 PART2:
// FORM [HttpPost, Route("EchoForm.aspx"), Route("EchoForm.anything"), Route("EchoForm.html"), Route("EchoForm.txt"), Route("EchoForm"), Route("API/v1.0/Authenticate"), Route("API/v1.0/Authenticate.aspx")] public HttpResponseMessage EchoForm() { string payload = Request.Content.ReadAsStringAsync().Result; log.Info($"FormToJsonResponse: Payload: {payload}"); NameValueCollection form = HttpUtility.ParseQueryString(payload); JObject response = new JObject(); foreach (string key in form.AllKeys) { response.Add(key, form[key]); } return Request.CreateResponse(HttpStatusCode.OK, response, new JsonMediaTypeFormatter()); } } }
-
Jesper Lundin 1 Reputation point
2021-06-17T15:11:11.003+00:00 It didnt allowed me to post more than 1000 characters so I had to split it up into pieces. Hopefully you can assembly it, otherwise let me now and we will figure out some other way to send it. I really appreciate your help. I also simplified the AccessRestrictionModule class to make it easier to fit it into one post. But the result is the same.
-
Jesper Lundin 1 Reputation point
2021-07-16T09:25:37.097+00:00 Hi again,
Any update on this?
Kind regards, Jesper
Sign in to comment