[backend/core] Fix link verification for sites served with Transfer-Encoding: chunked
This commit is contained in:
parent
107160c690
commit
a4717da8ab
3 changed files with 41 additions and 36 deletions
|
@ -28,4 +28,35 @@ public static class StreamExtensions
|
||||||
|
|
||||||
ValueTask<int> DoReadAsync() => source.ReadAsync(new Memory<byte>(buffer), cancellationToken);
|
ValueTask<int> DoReadAsync() => source.ReadAsync(new Memory<byte>(buffer), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We can't trust the Content-Length header, and it might be null.
|
||||||
|
/// This makes sure that we only ever read up to maxLength into memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The response content stream</param>
|
||||||
|
/// <param name="maxLength">The maximum length to buffer (null = unlimited)</param>
|
||||||
|
/// <param name="contentLength">The content length, if known</param>
|
||||||
|
/// <param name="token">A CancellationToken, if applicable</param>
|
||||||
|
/// <returns>Either a buffered MemoryStream, or Stream.Null</returns>
|
||||||
|
public static async Task<Stream> GetSafeStreamOrNullAsync(
|
||||||
|
this Stream stream, long? maxLength, long? contentLength, CancellationToken token = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (maxLength is 0) return Stream.Null;
|
||||||
|
if (contentLength > maxLength) return Stream.Null;
|
||||||
|
|
||||||
|
MemoryStream buf = new();
|
||||||
|
if (contentLength < maxLength)
|
||||||
|
maxLength = contentLength.Value;
|
||||||
|
|
||||||
|
await stream.CopyToAsync(buf, maxLength, token);
|
||||||
|
if (maxLength == null || buf.Length <= maxLength)
|
||||||
|
{
|
||||||
|
buf.Seek(0, SeekOrigin.Begin);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
await buf.DisposeAsync();
|
||||||
|
return Stream.Null;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -120,7 +120,7 @@ public class DriveService(
|
||||||
? storageConfig.Value.MaxCacheSizeBytes
|
? storageConfig.Value.MaxCacheSizeBytes
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
var stream = await GetSafeStreamOrNullAsync(input, maxLength, res.Content.Headers.ContentLength);
|
var stream = await input.GetSafeStreamOrNullAsync(maxLength, res.Content.Headers.ContentLength);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await StoreFileAsync(stream, user, request, skipImageProcessing);
|
return await StoreFileAsync(stream, user, request, skipImageProcessing);
|
||||||
|
@ -629,37 +629,6 @@ public class DriveService(
|
||||||
int GetTargetRes() => config.TargetRes ?? throw new Exception("TargetRes is required to encode images");
|
int GetTargetRes() => config.TargetRes ?? throw new Exception("TargetRes is required to encode images");
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// We can't trust the Content-Length header, and it might be null.
|
|
||||||
/// This makes sure that we only ever read up to maxLength into memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">The response content stream</param>
|
|
||||||
/// <param name="maxLength">The maximum length to buffer (null = unlimited)</param>
|
|
||||||
/// <param name="contentLength">The content length, if known</param>
|
|
||||||
/// <param name="token">A CancellationToken, if applicable</param>
|
|
||||||
/// <returns>Either a buffered MemoryStream, or Stream.Null</returns>
|
|
||||||
private static async Task<Stream> GetSafeStreamOrNullAsync(
|
|
||||||
Stream stream, long? maxLength, long? contentLength, CancellationToken token = default
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (maxLength is 0) return Stream.Null;
|
|
||||||
if (contentLength > maxLength) return Stream.Null;
|
|
||||||
|
|
||||||
MemoryStream buf = new();
|
|
||||||
if (contentLength < maxLength)
|
|
||||||
maxLength = contentLength.Value;
|
|
||||||
|
|
||||||
await stream.CopyToAsync(buf, maxLength, token);
|
|
||||||
if (maxLength == null || buf.Length <= maxLength)
|
|
||||||
{
|
|
||||||
buf.Seek(0, SeekOrigin.Begin);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
await buf.DisposeAsync();
|
|
||||||
return Stream.Null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DriveFileCreationRequest
|
public class DriveFileCreationRequest
|
||||||
|
|
|
@ -1203,13 +1203,14 @@ public class UserService(
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var res = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
|
const int maxLength = 1_000_000;
|
||||||
|
var res = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
res is not
|
res is not
|
||||||
{
|
{
|
||||||
IsSuccessStatusCode: true,
|
IsSuccessStatusCode: true,
|
||||||
Content.Headers: { ContentType.MediaType: "text/html", ContentLength: <= 1_000_000 }
|
Content.Headers: { ContentType.MediaType: "text/html", ContentLength: null or <= maxLength }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -1220,9 +1221,13 @@ public class UserService(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var html = await res.Content.ReadAsStringAsync();
|
var contentLength = res.Content.Headers.ContentLength;
|
||||||
var document = await new HtmlParser().ParseDocumentAsync(html);
|
var stream = await res.Content.ReadAsStreamAsync()
|
||||||
|
.ContinueWithResult(p => p.GetSafeStreamOrNullAsync(maxLength, contentLength));
|
||||||
|
|
||||||
|
if (stream == Stream.Null) throw new Exception("Response size limit exceeded");
|
||||||
|
|
||||||
|
var document = await new HtmlParser().ParseDocumentAsync(stream);
|
||||||
var headLinks = document.Head?.Children.Where(el => el.NodeName.ToLower() == "link").ToList() ?? [];
|
var headLinks = document.Head?.Children.Where(el => el.NodeName.ToLower() == "link").ToList() ?? [];
|
||||||
|
|
||||||
userProfileField.IsVerified =
|
userProfileField.IsVerified =
|
||||||
|
|
Loading…
Add table
Reference in a new issue