diff --git a/src/Html2OpenXml/IO/ImagePrefetcher.cs b/src/Html2OpenXml/IO/ImagePrefetcher.cs
index 062ad11..390409d 100644
--- a/src/Html2OpenXml/IO/ImagePrefetcher.cs
+++ b/src/Html2OpenXml/IO/ImagePrefetcher.cs
@@ -52,6 +52,7 @@ sealed class ImagePrefetcher : IImageLoader
private readonly T hostingPart;
private readonly IWebRequest resourceLoader;
private readonly HtmlImageInfoCollection prefetchedImages;
+ private readonly object lockObject = new object();
///
@@ -76,8 +77,12 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
///
public async Task Download(string imageUri, CancellationToken cancellationToken)
{
- if (prefetchedImages.Contains(imageUri))
- return prefetchedImages[imageUri];
+ // Check if image is already cached using thread-safe operation
+ lock (lockObject)
+ {
+ if (prefetchedImages.Contains(imageUri))
+ return prefetchedImages[imageUri];
+ }
HtmlImageInfo? iinfo;
if (DataUri.IsWellFormed(imageUri)) // data inline, encoded in base64
@@ -89,8 +94,18 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
iinfo = await DownloadRemoteImage(imageUri, cancellationToken).ConfigureAwait(false);
}
+ // Add to cache using thread-safe operation
if (iinfo != null)
- prefetchedImages.Add(iinfo);
+ {
+ lock (lockObject)
+ {
+ // Double-check pattern to prevent duplicate adds during concurrent access
+ if (!prefetchedImages.Contains(imageUri))
+ {
+ prefetchedImages.Add(iinfo);
+ }
+ }
+ }
return iinfo;
}
@@ -121,7 +136,16 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
return null;
}
- var ipart = hostingPart.AddImagePart(type);
+ // Generate a unique GUID-based relationship ID for the image part
+ string relationshipId = "img_" + Guid.NewGuid().ToString("N");
+
+ // Synchronize access to AddImagePart to prevent concurrent modifications to the OpenXml document
+ ImagePart ipart;
+ lock (lockObject)
+ {
+ ipart = hostingPart.AddImagePart(type, relationshipId);
+ }
+
Size originalSize;
using (var outputStream = ipart.GetStream(FileMode.Create))
{
@@ -131,7 +155,13 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
originalSize = GetImageSize(outputStream);
}
- return new HtmlImageInfo(src, hostingPart.GetIdOfPart(ipart)) {
+ string partId;
+ lock (lockObject)
+ {
+ partId = hostingPart.GetIdOfPart(ipart);
+ }
+
+ return new HtmlImageInfo(src, partId) {
TypeInfo = type,
Size = originalSize
};
@@ -147,7 +177,16 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
{
Size originalSize;
knownContentType.TryGetValue(dataUri!.Mime, out PartTypeInfo type);
- var ipart = hostingPart.AddImagePart(type);
+ // Generate a unique GUID-based relationship ID for the image part
+ string relationshipId = "img_" + Guid.NewGuid().ToString("N");
+
+ // Synchronize access to AddImagePart to prevent concurrent modifications to the OpenXml document
+ ImagePart ipart;
+ lock (lockObject)
+ {
+ ipart = hostingPart.AddImagePart(type, relationshipId);
+ }
+
using (var outputStream = ipart.GetStream(FileMode.Create))
{
outputStream.Write(dataUri.Data, 0, dataUri.Data.Length);
@@ -156,7 +195,13 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
originalSize = GetImageSize(outputStream);
}
- return new HtmlImageInfo(src, hostingPart.GetIdOfPart(ipart)) {
+ string partId;
+ lock (lockObject)
+ {
+ partId = hostingPart.GetIdOfPart(ipart);
+ }
+
+ return new HtmlImageInfo(src, partId) {
TypeInfo = type,
Size = originalSize
};