Last active
April 28, 2023 09:26
-
-
Save FoxCouncil/13105d6a9b5c282bb1fc0a2059f6a50a to your computer and use it in GitHub Desktop.
This helps stop C#'s HttpClient from puking on Icecast and Shoutcat servers that send a ICY header instead of an HTTP header.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Main() | |
{ | |
var socketHandler = new SocketsHttpHandler { | |
PlaintextStreamFilter = (filterContext, ct) => new ValueTask<Stream>(new HttpFixerDelegatingStream(filterContext.PlaintextStream)) | |
}; | |
var httpClient = new HttpClient(socketHandler); | |
var httpGetter = await httpClient.GetAsync("http://icyserver:8000/my_awesome_stream.mp3", HttpCompletionOption.ResponseHeadersRead); | |
} | |
internal class HttpFixerDelegatingStream : DelegatingStream | |
{ | |
byte[] icyHeaderSignature = Encoding.ASCII.GetBytes("ICY"); | |
internal HttpFixerDelegatingStream(Stream innerStream) : base(innerStream) { } | |
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) | |
{ | |
var read = await base.ReadAsync(buffer, cancellationToken); | |
if (buffer.Span[0] == icyHeaderSignature[0] && buffer.Span[1] == icyHeaderSignature[1] && buffer.Span[2] == icyHeaderSignature[2]) | |
{ | |
var incomingHeader = Encoding.ASCII.GetString(buffer.ToArray(), 0, read); | |
var replacedHeader = incomingHeader.Replace("ICY 200 OK", "HTTP/1.0 200 OK"); | |
var replacedHeaderBytes = Encoding.ASCII.GetBytes(replacedHeader); | |
for (var i = 0; i < replacedHeaderBytes.Length; i++) | |
{ | |
buffer.Span[i] = replacedHeaderBytes[i]; | |
} | |
read = replacedHeaderBytes.Length; | |
} | |
return read; | |
} | |
} | |
internal class DelegatingStream : Stream | |
{ | |
private readonly Stream _innerStream; | |
#region Properties | |
public override bool CanRead | |
{ | |
get { return _innerStream.CanRead; } | |
} | |
public override bool CanSeek | |
{ | |
get { return _innerStream.CanSeek; } | |
} | |
public override bool CanWrite | |
{ | |
get { return _innerStream.CanWrite; } | |
} | |
public override long Length | |
{ | |
get { return _innerStream.Length; } | |
} | |
public override long Position | |
{ | |
get { return _innerStream.Position; } | |
set { _innerStream.Position = value; } | |
} | |
public override int ReadTimeout | |
{ | |
get { return _innerStream.ReadTimeout; } | |
set { _innerStream.ReadTimeout = value; } | |
} | |
public override bool CanTimeout | |
{ | |
get { return _innerStream.CanTimeout; } | |
} | |
public override int WriteTimeout | |
{ | |
get { return _innerStream.WriteTimeout; } | |
set { _innerStream.WriteTimeout = value; } | |
} | |
#endregion Properties | |
protected DelegatingStream(Stream innerStream) | |
{ | |
Debug.Assert(innerStream != null); | |
_innerStream = innerStream; | |
} | |
protected override void Dispose(bool disposing) | |
{ | |
if (disposing) | |
{ | |
_innerStream.Dispose(); | |
} | |
base.Dispose(disposing); | |
} | |
public override ValueTask DisposeAsync() | |
{ | |
return _innerStream.DisposeAsync(); | |
} | |
#region Read | |
public override long Seek(long offset, SeekOrigin origin) | |
{ | |
return _innerStream.Seek(offset, origin); | |
} | |
public override int Read(byte[] buffer, int offset, int count) | |
{ | |
return _innerStream.Read(buffer, offset, count); | |
} | |
public override int Read(Span<byte> buffer) | |
{ | |
return _innerStream.Read(buffer); | |
} | |
public override int ReadByte() | |
{ | |
return _innerStream.ReadByte(); | |
} | |
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | |
{ | |
return _innerStream.ReadAsync(buffer, offset, count, cancellationToken); | |
} | |
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) | |
{ | |
return _innerStream.ReadAsync(buffer, cancellationToken); | |
} | |
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) | |
{ | |
return _innerStream.BeginRead(buffer, offset, count, callback, state); | |
} | |
public override int EndRead(IAsyncResult asyncResult) | |
{ | |
return _innerStream.EndRead(asyncResult); | |
} | |
public override void CopyTo(Stream destination, int bufferSize) | |
{ | |
_innerStream.CopyTo(destination, bufferSize); | |
} | |
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) | |
{ | |
return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken); | |
} | |
#endregion Read | |
#region Write | |
public override void Flush() | |
{ | |
_innerStream.Flush(); | |
} | |
public override Task FlushAsync(CancellationToken cancellationToken) | |
{ | |
return _innerStream.FlushAsync(cancellationToken); | |
} | |
public override void SetLength(long value) | |
{ | |
_innerStream.SetLength(value); | |
} | |
public override void Write(byte[] buffer, int offset, int count) | |
{ | |
_innerStream.Write(buffer, offset, count); | |
} | |
public override void Write(ReadOnlySpan<byte> buffer) | |
{ | |
_innerStream.Write(buffer); | |
} | |
public override void WriteByte(byte value) | |
{ | |
_innerStream.WriteByte(value); | |
} | |
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | |
{ | |
return _innerStream.WriteAsync(buffer, offset, count, cancellationToken); | |
} | |
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) | |
{ | |
return _innerStream.WriteAsync(buffer, cancellationToken); | |
} | |
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) | |
{ | |
return _innerStream.BeginWrite(buffer, offset, count, callback, state); | |
} | |
public override void EndWrite(IAsyncResult asyncResult) | |
{ | |
_innerStream.EndWrite(asyncResult); | |
} | |
#endregion Write | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment