Skip to content

Commit 32fb80c

Browse files
authored
fix: Handle disposed streams in browser script injection (#3352)
1 parent e792b70 commit 32fb80c

File tree

3 files changed

+167
-23
lines changed

3 files changed

+167
-23
lines changed

src/Agent/NewRelic/Agent/Core/Agent.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,14 @@ public async Task TryInjectBrowserScriptAsync(string contentType, string request
325325
if (rumBytes == null)
326326
{
327327
transaction?.LogFinest("Skipping RUM Injection: No script was available.");
328-
await baseStream.WriteAsync(buffer, 0, buffer.Length);
328+
try
329+
{
330+
await baseStream.WriteAsync(buffer, 0, buffer.Length);
331+
}
332+
catch (ObjectDisposedException)
333+
{
334+
transaction?.LogFinest("Skipping RUM Injection: Base stream was disposed.");
335+
}
329336
}
330337
else
331338
await BrowserScriptInjectionHelper.InjectBrowserScriptAsync(buffer, baseStream, rumBytes, transaction);

src/Agent/NewRelic/Agent/Core/BrowserMonitoring/BrowserScriptInjectionHelper.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2020 New Relic, Inc. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System;
45
using System.IO;
56
using System.Threading.Tasks;
67
using NewRelic.Agent.Api;
@@ -20,12 +21,11 @@ public static class BrowserScriptInjectionHelper
2021
public static async Task InjectBrowserScriptAsync(byte[] buffer, Stream baseStream, byte[] rumBytes, ITransaction transaction)
2122
{
2223
var index = BrowserScriptInjectionIndexHelper.TryFindInjectionIndex(buffer);
23-
2424
if (index == -1)
2525
{
2626
// not found, can't inject anything
2727
transaction?.LogFinest("Skipping RUM Injection: No suitable location found to inject script.");
28-
await baseStream.WriteAsync(buffer, 0, buffer.Length);
28+
await TryWriteStreamAsync(baseStream, buffer, 0, buffer.Length, transaction);
2929
return;
3030
}
3131

@@ -34,16 +34,26 @@ public static async Task InjectBrowserScriptAsync(byte[] buffer, Stream baseStre
3434
if (index < buffer.Length) // validate index is less than buffer length
3535
{
3636
// Write everything up to the insertion index
37-
await baseStream.WriteAsync(buffer, 0, index);
38-
37+
await TryWriteStreamAsync(baseStream, buffer, 0, index, transaction);
3938
// Write the RUM script
40-
await baseStream.WriteAsync(rumBytes, 0, rumBytes.Length);
41-
39+
await TryWriteStreamAsync(baseStream, rumBytes, 0, rumBytes.Length, transaction);
4240
// Write the rest of the doc, starting after the insertion index
43-
await baseStream.WriteAsync(buffer, index, buffer.Length - index);
41+
await TryWriteStreamAsync(baseStream, buffer, index, buffer.Length - index, transaction);
4442
}
4543
else
4644
transaction?.LogFinest($"Skipping RUM Injection: Insertion index was invalid.");
4745
}
46+
47+
private static async Task TryWriteStreamAsync(Stream stream, byte[] buffer, int offset, int count, ITransaction transaction)
48+
{
49+
try
50+
{
51+
await stream.WriteAsync(buffer, offset, count);
52+
}
53+
catch (ObjectDisposedException)
54+
{
55+
transaction?.LogFinest("RUM Injection aborted: Stream was disposed.");
56+
}
57+
}
4858
}
4959
}

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/BrowserInjectingStreamWrapper.cs

Lines changed: 142 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,94 @@ public override Task FlushAsync(CancellationToken cancellationToken)
4444
_isContentLengthSet = true;
4545
}
4646

47-
return _baseStream?.FlushAsync(cancellationToken) ?? Task.CompletedTask;
47+
try
48+
{
49+
return _baseStream?.FlushAsync(cancellationToken) ?? Task.CompletedTask;
50+
}
51+
catch (ObjectDisposedException)
52+
{
53+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: FlushAsync failed; base stream is disposed.");
54+
return Task.CompletedTask;
55+
}
4856
}
4957

50-
public override void Flush() => _baseStream?.Flush();
58+
public override void Flush()
59+
{
60+
try
61+
{
62+
_baseStream?.Flush();
63+
}
64+
catch (ObjectDisposedException)
65+
{
66+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Flush failed; base stream is disposed.");
67+
}
68+
}
5169

52-
public override int Read(byte[] buffer, int offset, int count) => _baseStream?.Read(buffer, offset, count) ?? 0;
70+
public override int Read(byte[] buffer, int offset, int count)
71+
{
72+
try
73+
{
74+
return _baseStream?.Read(buffer, offset, count) ?? 0;
75+
}
76+
catch (ObjectDisposedException)
77+
{
78+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Read failed; base stream is disposed.");
79+
return 0;
80+
}
81+
}
5382

54-
public override long Seek(long offset, SeekOrigin origin) => _baseStream?.Seek(offset, origin) ?? 0;
83+
public override long Seek(long offset, SeekOrigin origin)
84+
{
85+
try
86+
{
87+
return _baseStream?.Seek(offset, origin) ?? 0;
88+
}
89+
catch (ObjectDisposedException)
90+
{
91+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Seek failed; base stream is disposed.");
92+
return 0;
93+
}
94+
}
5595

5696
public override void SetLength(long value)
5797
{
58-
_baseStream?.SetLength(value);
98+
try
99+
{
100+
_baseStream?.SetLength(value);
101+
}
102+
catch (ObjectDisposedException)
103+
{
104+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: SetLength failed; base stream is disposed.");
105+
return;
106+
}
59107

60108
if (!Disabled)
61109
IsHtmlResponse(forceReCheck: true);
62110
}
63111

64-
public override void Write(ReadOnlySpan<byte> buffer) => _baseStream?.Write(buffer);
112+
public override void Write(ReadOnlySpan<byte> buffer)
113+
{
114+
try
115+
{
116+
_baseStream?.Write(buffer);
117+
}
118+
catch (ObjectDisposedException)
119+
{
120+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Write(ReadOnlySpan<byte>) failed; base stream is disposed.");
121+
}
122+
}
65123

66-
public override void WriteByte(byte value) => _baseStream?.WriteByte(value);
124+
public override void WriteByte(byte value)
125+
{
126+
try
127+
{
128+
_baseStream?.WriteByte(value);
129+
}
130+
catch (ObjectDisposedException)
131+
{
132+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: WriteByte failed; base stream is disposed.");
133+
}
134+
}
67135

68136
public override void Write(byte[] buffer, int offset, int count)
69137
{
@@ -86,7 +154,14 @@ public override void Write(byte[] buffer, int offset, int count)
86154
return;
87155
}
88156

89-
_baseStream?.Write(buffer, offset, count);
157+
try
158+
{
159+
_baseStream?.Write(buffer, offset, count);
160+
}
161+
catch (ObjectDisposedException)
162+
{
163+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Write(byte[], int, int) failed; base stream is disposed.");
164+
}
90165
}
91166

92167
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
@@ -112,23 +187,75 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
112187
}
113188

114189
if (_baseStream != null)
115-
await _baseStream.WriteAsync(buffer, cancellationToken);
190+
{
191+
try
192+
{
193+
await _baseStream.WriteAsync(buffer, cancellationToken);
194+
}
195+
catch (ObjectDisposedException)
196+
{
197+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: WriteAsync(ReadOnlyMemory<byte>) failed; base stream is disposed.");
198+
}
199+
}
116200
}
117201

118202
private const string InjectingRUM = "InjectingRUM";
119203

120-
private void FinishInjecting() => _context?.Items.Remove(InjectingRUM);
121-
private void StartInjecting() => _context?.Items.Add(InjectingRUM, null);
122-
private bool CurrentlyInjecting() => _context?.Items.ContainsKey(InjectingRUM) ?? false;
204+
private void FinishInjecting()
205+
{
206+
try
207+
{
208+
_context?.Items.Remove(InjectingRUM);
209+
}
210+
catch (ObjectDisposedException)
211+
{
212+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Unable to remove RUM injection flag, _context.Items was disposed.");
213+
}
214+
}
215+
216+
private void StartInjecting()
217+
{
218+
try
219+
{
220+
_context?.Items.Add(InjectingRUM, null);
221+
}
222+
catch (ObjectDisposedException)
223+
{
224+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Unable to insert RUM injection flag, _context.Items was disposed.");
225+
}
226+
}
227+
228+
private bool CurrentlyInjecting()
229+
{
230+
try
231+
{
232+
return _context?.Items.ContainsKey(InjectingRUM) ?? false;
233+
}
234+
catch (ObjectDisposedException)
235+
{
236+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: Unable to check for RUM injection flag, _context.Items was disposed.");
237+
return false;
238+
}
239+
}
123240

124241
public override async ValueTask DisposeAsync()
125242
{
126243
_context = null;
127244

128245
if (_baseStream != null)
129246
{
130-
await _baseStream.DisposeAsync();
131-
_baseStream = null;
247+
try
248+
{
249+
await _baseStream.DisposeAsync();
250+
}
251+
catch (ObjectDisposedException)
252+
{
253+
_agent.Logger.Log(Level.Finest, "BrowserInjectingStreamWrapper: DisposeAsync failed; base stream is already disposed.");
254+
}
255+
finally
256+
{
257+
_baseStream = null;
258+
}
132259
}
133260
}
134261

@@ -158,7 +285,7 @@ private bool IsHtmlResponse(bool forceReCheck = false)
158285
// * UTF-8 formatted (either explicitly or no charset defined)
159286
var responseContentType = _context.Response.ContentType;
160287
_isHtmlResponse =
161-
!string.IsNullOrEmpty(responseContentType) &&
288+
!string.IsNullOrEmpty(responseContentType) &&
162289
responseContentType.Contains("text/html", StringComparison.OrdinalIgnoreCase) &&
163290
(responseContentType.Contains("utf-8", StringComparison.OrdinalIgnoreCase) ||
164291
!responseContentType.Contains("charset=", StringComparison.OrdinalIgnoreCase));

0 commit comments

Comments
 (0)