Compare commits

..

4 commits

Author SHA1 Message Date
uwaa
9aa8a41389 pleroma: fix null deref 2024-11-30 13:14:31 +00:00
uwaa
c15371ea9f pleroma: add Delete() 2024-11-30 13:14:20 +00:00
uwaa
43ec5a372d pleroma: retry if throttled 2024-11-30 13:14:00 +00:00
uwaa
f4862e6c8a http: fix incomplete content read 2024-11-30 11:18:33 +00:00
3 changed files with 113 additions and 9 deletions

View file

@ -75,11 +75,20 @@ class HttpStream : IDisposable
}
}
public ValueTask<int> Read(Memory<byte> buffer)
public async ValueTask<int> Read(Memory<byte> buffer)
{
try
{
return Buffer.ReadAsync(buffer);
int index = 0;
while (index < buffer.Length)
{
int count = await Buffer.ReadAsync(buffer[index..]);
if (count == 0)
break;
index += count;
}
return index;
}
catch (IOException e)
{

View file

@ -35,7 +35,7 @@ public class Status
public bool Pinned { get; set; }
[JsonIgnore]
public string Content => Pleroma?.Content.Plain ?? HtmlContent;
public string Content => Pleroma?.Content?.Plain ?? HtmlContent;
public bool CheckMention(string id)
{

View file

@ -40,6 +40,85 @@ public class Pleroma
Authorization = authorization;
}
async Task RequestJSONRetry(HttpRequest req)
{
while (true)
{
try
{
await RequestJSON(req);
return;
}
catch (PleromaException e)
{
if (e.Text == "Throttled")
{
await Task.Delay(5000);
continue;
}
else
throw;
}
}
}
async Task<T?> RequestJSONRetry<T>(HttpRequest req) where T : class
{
while (true)
{
try
{
return await RequestJSON<T>(req);
}
catch (PleromaException e)
{
if (e.Text == "Throttled")
{
await Task.Delay(5000);
continue;
}
else
throw;
}
}
}
async Task RequestJSON(HttpRequest req)
{
req.Fields.UserAgent = UserAgent;
req.Fields.Authorization = Authorization;
HttpResponse res = await HttpClient.Request(Host, true, req);
if (res.StatusCode == 404)
return;
if (!res.Fields.ContentType.HasValue || !res.Fields.ContentType.Value.Match(JsonMIMEType))
throw new HttpException("Server did not respond with JSON" + (res.Content.HasValue ? ", got: " + res.Content.Value.AsText : null));
if (!res.Content.HasValue)
throw new HttpException("Server responded with no content");
string text = res.Content.Value.AsText;
if (res.StatusCode is >= 400 and < 600)
{
try
{
PleromaException? err = JsonSerializer.Deserialize<PleromaException>(text, SerializerOptions);
if (err != null && err.Text != null)
throw err;
}
catch (JsonException)
{
//Not an error
}
}
if (res.StatusCode is not >= 200 or not < 300)
throw new HttpException("Unknown error occurred");
}
async Task<T?> RequestJSON<T>(HttpRequest req) where T : class
{
req.Fields.UserAgent = UserAgent;
@ -88,7 +167,7 @@ public class Pleroma
HttpRequest req = new HttpRequest(HttpMethod.POST, "/api/v1/statuses");
req.Content = new HttpContent(JsonMIMEType, JsonSerializer.SerializeToUtf8Bytes(status, SerializerOptions));
req.Fields.Accept = [JsonMIMEType];
return RequestJSON<Status>(req)!;
return RequestJSONRetry<Status>(req)!;
}
/// <summary>
@ -99,7 +178,7 @@ public class Pleroma
//TODO: Parameters and selecting different timelines (home, public, bubble)
HttpRequest req = new HttpRequest(HttpMethod.GET, "/api/v1/timelines/public");
req.Fields.Accept = [ JsonMIMEType ];
return RequestJSON<Status[]>(req)!;
return RequestJSONRetry<Status[]>(req)!;
}
/// <summary>
@ -115,7 +194,7 @@ public class Pleroma
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/accounts/{account_id}/statuses");
req.Fields.Accept = [JsonMIMEType];
return RequestJSON<Status[]>(req)!;
return RequestJSONRetry<Status[]>(req)!;
}
/// <summary>
@ -125,7 +204,7 @@ public class Pleroma
{
HttpRequest req = new HttpRequest(HttpMethod.GET, "/api/v1/accounts/verify_credentials");
req.Fields.Accept = [JsonMIMEType];
return RequestJSON<Account>(req)!;
return RequestJSONRetry<Account>(req)!;
}
/// <summary>
@ -135,7 +214,7 @@ public class Pleroma
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/accounts/{id}");
req.Fields.Accept = [JsonMIMEType];
return RequestJSON<Account>(req);
return RequestJSONRetry<Account>(req);
}
/// <summary>
@ -151,6 +230,22 @@ public class Pleroma
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/statuses/{status_id}/context");
req.Fields.Accept = [JsonMIMEType];
return RequestJSON<Context>(req);
return RequestJSONRetry<Context>(req);
}
/// <summary>
/// Deletes a status.
/// </summary>
public Task Delete(Status status)
=> Delete(status.ID);
/// <summary>
/// Deletes a status by ID.
/// </summary>
public Task Delete(string status_id)
{
HttpRequest req = new HttpRequest(HttpMethod.DELETE, $"/api/v1/statuses/{status_id}");
req.Fields.Accept = [JsonMIMEType];
return RequestJSONRetry(req);
}
}