Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace SW.Bitween.NativeAdapters.AzureBlobReceiver;

public class AzureBlobReceiverInput
{
[Required]
[Secure]
public string ConnectionString { get; set; } = string.Empty;

[Required]
public string ContainerName { get; set; } = string.Empty;

public string? FolderName { get; set; }

[DefaultValue(50)]
public int BatchSize { get; set; } = 50;

[DefaultValue("utf8")]
public string ResponseEncoding { get; set; } = "utf8";

public string? DeleteMovesFileTo { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Text;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using SW.PrimitiveTypes;

namespace SW.Bitween.NativeAdapters.AzureBlobReceiver;

public class NativeAzureBlobReceiver : INativeInfolinkReceiver
{
private AzureBlobReceiverInput _options = new();
private BlobContainerClient _container = null!;

public Task Initialize()
{
_container = new BlobContainerClient(_options.ConnectionString.Trim(), _options.ContainerName.Trim());
return Task.CompletedTask;
}

public Task Finalize()
{
return Task.CompletedTask;
}

public async Task<IEnumerable<string>> ListFiles()
{
var blobNames = new List<string>();

// Trailing slash keeps the prefix scoped to the folder itself, so a sibling
// like "incoming-archive/x.txt" doesn't match a FolderName of "incoming".
var prefix = string.IsNullOrEmpty(_options.FolderName) ? _options.FolderName : _options.FolderName + "/";

await foreach (var blob in _container.GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix))
{
blobNames.Add(blob.Name);
if (blobNames.Count >= _options.BatchSize)
break;
}

return blobNames;
}
Comment thread
hamzahalq marked this conversation as resolved.

public async Task<XchangeFile> GetFile(string fileId)
{
var blobClient = _container.GetBlobClient(fileId);
var download = await blobClient.DownloadAsync();

using var memoryStream = new MemoryStream();
await download.Value.Content.CopyToAsync(memoryStream);
var bytes = memoryStream.ToArray();

return (_options.ResponseEncoding ?? "utf8").ToLower() switch
{
"base64" => new XchangeFile(Convert.ToBase64String(bytes), fileId),
"utf8" => new XchangeFile(Encoding.UTF8.GetString(bytes), fileId),
_ => throw new ArgumentException(
$"Unknown {nameof(AzureBlobReceiverInput.ResponseEncoding)} '{_options.ResponseEncoding}'")
};
}

public async Task DeleteFile(string fileId)
{
var sourceBlob = _container.GetBlobClient(fileId);

// Retry-safe: if a prior attempt already moved/deleted this file, there's
// nothing left to do — treat it as already completed, not an error.
if (!await sourceBlob.ExistsAsync())
return;

if (!string.IsNullOrWhiteSpace(_options.DeleteMovesFileTo))
{
// Preserve the path relative to FolderName so files with the same name in
// different subdirectories don't collide at the destination.
var relativePath = !string.IsNullOrEmpty(_options.FolderName) && fileId.StartsWith(_options.FolderName + "/")
? fileId[(_options.FolderName.Length + 1)..]
: fileId;
var targetName = $"{_options.DeleteMovesFileTo}/{relativePath}";

// Server-side copy: Azure moves the blob internally, so no bytes are
// downloaded or re-uploaded through this process.
var targetBlob = _container.GetBlobClient(targetName);
await targetBlob.SyncCopyFromUriAsync(sourceBlob.Uri);
Comment on lines +78 to +81

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current Azure.Storage.Blobs SyncCopyFromUriAsync size limits / auth model
curl -s "https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/main/articles/storage/blobs/storage-blob-copy.md" | grep -i -A3 "size limit\|same storage account"

Repository: simplify9/Bitween-api

Length of output: 1994


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo root =="
pwd
echo

echo "== locate NativeAzureBlobReceiver.cs =="
fd -a 'NativeAzureBlobReceiver.cs' . || true
echo

echo "== search for SyncCopyFromUriAsync usage =="
rg -n "SyncCopyFromUriAsync|StartCopyFromUriAsync|CopyFromUriAsync" . || true
echo

echo "== Azure docs excerpts =="
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/main/articles/storage/blobs/storage-blob-copy.md"
text = urllib.request.urlopen(url, timeout=20).read().decode("utf-8", "replace")
for pat in [
    r"(?is).{0,200}size limit.{0,500}",
    r"(?is).{0,200}same storage account.{0,500}",
    r"(?is).{0,200}private source.{0,500}",
    r"(?is).{0,200}StartCopyFromUriAsync.{0,500}",
]:
    m = re.search(pat, text)
    print("MATCH" if m else "NO MATCH", pat.split(".{0,200}")[-1][:40])
    if m:
        snippet = m.group(0)
        print(snippet[:1200])
        print("---")
PY

Repository: simplify9/Bitween-api

Length of output: 1965


🌐 Web query:

Azure Storage blob copy SyncCopyFromUriAsync size limit same storage account private source URI StartCopyFromUriAsync

💡 Result:

When copying blobs in Azure Storage using the.NET SDK, there is a critical distinction between SyncCopyFromUriAsync and StartCopyFromUriAsync regarding source blob size limits and operation behavior [1][2][3]. SyncCopyFromUriAsync This method wraps the Copy Blob From URL REST API [2][3]. It operates synchronously and is subject to a strict source blob size limit of 256 MiB [1][2]. If the source blob exceeds this size, the operation will fail with a 409 (Conflict) error [2][4]. Additionally, this method typically requires the source blob to be public or authorized via a Shared Access Signature (SAS), even when the source is in the same storage account [2][3][5]. StartCopyFromUriAsync This method is designed for asynchronous, long-running copy operations [6][7]. It does not have the same 256 MiB size limitation as the synchronous version [8][6][7]. When using this method, the copy process is initiated and runs in the background; you can track its progress using the returned CopyFromUriOperation object [6][9][7]. Summary Recommendation For source blobs larger than 256 MiB, you must use StartCopyFromUriAsync [2][6]. If you have a requirement to move data within the same storage account and prefer synchronous-like behavior or need to handle larger files, consider using methods that support partial updates (such as StageBlockFromUri combined with PutBlockList), which provide greater flexibility and avoid the size constraints of the single-request Copy Blob From URL operation [10].

Citations:


🌐 Web query:

Azure Storage Copy Blob From URL same account source URI SAS public access SyncCopyFromUriAsync authorization docs

💡 Result:

When performing a synchronous copy operation using SyncCopyFromUriAsync (or related methods like SyncUploadFromUriAsync) within the same Azure storage account, the operation essentially uses the Copy Blob REST API underneath [1][2]. Authorization Requirements: The underlying REST API requires that the source blob be accessible to the copy service [3]. If the source blob is not public, the URI provided to the operation must be authorized [3][4]. Even within the same account, simply providing the base URI of a private source blob will often fail with a 409 (CannotVerifyCopySource) or similar authorization error [3]. To authorize the copy, you should typically append a Shared Access Signature (SAS) token to the source blob URI [5][3]. The SAS token must grant at least Read ('r') permissions [4]. While Microsoft Entra ID (RBAC) is recommended for overall service client authorization, the copy operation specifically requires that the source URL itself provides the necessary access for the storage service to perform the read [3][4]. Key Considerations: 1. SAS Token: Generating a SAS URI using the source blob client is the standard approach to provide the required authorization for the operation [5]. 2. Public Access: If the container or blob is configured for public access, a SAS token may not be strictly required, though using one is considered a best practice for security [3]. 3. Sync vs. Async: SyncCopyFromUriAsync is intended for synchronous copies of smaller blobs (up to 256 MiB) [6][7]. For larger operations or scenarios where asynchronous scheduling is preferred, other methods like StartCopyFromUriAsync should be used [4]. In summary, to successfully use SyncCopyFromUriAsync for a private blob within the same account, you must ensure the source URI includes a valid SAS token with at least read permissions [5][3].

Citations:


Use StartCopyFromUriAsync here
SyncCopyFromUriAsync is limited to 256 MiB and will fail for private source blobs unless the source URI is authorized. sourceBlob.Uri is a bare private URL, so this move path will break unless the blob/container is public or you append a SAS; the synchronous call also ties up the request thread for the copy duration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs`
around lines 67 - 71, The move logic in NativeAzureBlobReceiver should not use
the synchronous copy API with a bare source URI. Update the copy path in the
receiver method that builds sourceBlob and targetBlob to use
StartCopyFromUriAsync instead of SyncCopyFromUriAsync, and make sure the source
URI is authorized for private blobs (for example by using a SAS-backed URI or
equivalent authorization) before starting the copy.

}

await sourceBlob.DeleteAsync();
}
Comment thread
hamzahalq marked this conversation as resolved.

public string Name => "NativeAzureBlobReceiver";

public void InitializeStartupValues(IDictionary<string, string> settings)
{
_options = settings.ConvertTo<AzureBlobReceiverInput>();
}

public Type StartupValuesType => typeof(AzureBlobReceiverInput);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;

namespace SW.Bitween.NativeAdapters.AzureBlobUploadHandler;

public class AzureBlobUploadHandlerInput
{
[Required]
[Secure]
public string ConnectionString { get; set; } = string.Empty;

[Required]
public string ContainerName { get; set; } = string.Empty;

public string? FileName { get; set; }

public string? FileExtension { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Text;
using Azure.Storage.Blobs;
using SW.PrimitiveTypes;

namespace SW.Bitween.NativeAdapters.AzureBlobUploadHandler;

public class NativeAzureBlobUploadHandler : INativeInfolinkHandler
{
private AzureBlobUploadHandlerInput _options = new();

public async Task<XchangeFile> Handle(XchangeFile xchangeFile)
{
var container = new BlobContainerClient(_options.ConnectionString.Trim(), _options.ContainerName.Trim());

var blobName = _options.FileName;
if (string.IsNullOrWhiteSpace(blobName))
{
var extension = _options.FileExtension?.TrimStart('.');
var name = $"{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}";
blobName = string.IsNullOrEmpty(extension) ? name : $"{name}.{extension}";
}

var blobClient = container.GetBlobClient(blobName);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(xchangeFile.Data));
await blobClient.UploadAsync(stream, overwrite: true);

return new XchangeFile(blobName, xchangeFile.Filename);
}

public string Name => "NativeAzureBlobUploadHandler";

public void InitializeStartupValues(IDictionary<string, string> settings)
{
_options = settings.ConvertTo<AzureBlobUploadHandlerInput>();
}

public Type StartupValuesType => typeof(AzureBlobUploadHandlerInput);
}
1 change: 1 addition & 0 deletions SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
curl -s https://api.nuget.org/v3-flatcontainer/azure.storage.blobs/index.json | jq '.versions[-5:]'
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NUGET, package: "Azure.Storage.Blobs") {
    nodes {
      advisory { summary severity publishedAt }
      vulnerableVersionRange
      firstPatchedVersion { identifier }
    }
  }
}'

Repository: simplify9/Bitween-api

Length of output: 1994


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== latest versions =="
curl -fsSL https://api.nuget.org/v3-flatcontainer/azure.storage.blobs/index.json \
  | jq -r '.versions[-10:][]'

echo
echo "== vulnerability feed metadata =="
curl -fsSL https://api.nuget.org/v3/vulnerabilities/index.json \
  | jq '{vulnerabilities: (.vulnerabilities // .packageVulnerabilities // .data // .)}' | sed -n '1,160p'

Repository: simplify9/Bitween-api

Length of output: 1965


🌐 Web query:

Azure.Storage.Blobs NuGet latest version vulnerability advisories

💡 Result:

As of July 8, 2026, the latest version of the Azure.Storage.Blobs NuGet package is 12.29.1 [1][2][3]. Regarding security and vulnerability advisories: Official Security Reporting: Microsoft manages security vulnerabilities for the Azure SDK for.NET through the Microsoft Security Response Center (MSRC) [4][5]. Users are advised not to report vulnerabilities via public GitHub issues but to use the official MSRC channels [4][6]. Historical Vulnerabilities: The package has had historical security disclosures, such as CVE-2022-30187, which involved potential information exposure when using client-side encryption [7][8][9]. This was addressed in version 12.13.0 [7]. Additionally, broader vulnerabilities affecting the Azure SDK for.NET, such as information disclosure (CVE-2022-26907) [10] or issues in related dependencies like Azure Identity (e.g., CVE-2023-36414) [11], have historically impacted the ecosystem. Recommended Best Practices: 1. Always Use the Latest Version: Microsoft strongly recommends keeping client library versions up to date to receive the latest security patches, bug fixes, and performance improvements [12]. 2. Monitor Official Sources: For the most accurate and up-to-date vulnerability information, consult the Microsoft Security Update Guide (MSRC) [5] or the official Azure SDK for.NET GitHub repository security page [4]. 3. Changelog Review: You can review specific release notes and changes in the project's official CHANGELOG.md on GitHub [3].

Citations:


Bump Azure.Storage.Blobs 12.23.0 is several releases behind the current NuGet version (12.29.1); update it to keep the dependency current.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj` at line 17, The
Azure.Storage.Blobs package reference is outdated, so update the
PackageReference in the project file to the current NuGet version. Adjust the
existing Azure.Storage.Blobs entry in SW.Bitween.NativeAdapters.csproj from
12.23.0 to 12.29.1, keeping the same package name and location.

<PackageReference Include="DotLiquid" Version="2.2.692" />
<PackageReference Include="MailKit" Version="4.17.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
8 changes: 8 additions & 0 deletions SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using SW.Bitween.NativeAdapters.AzureBlobReceiver;
using SW.Bitween.NativeAdapters.AzureBlobUploadHandler;
using SW.Bitween.NativeAdapters.HttpReceiver;
using SW.Bitween.NativeAdapters.Pop3Receiver;
using SW.Bitween.NativeAdapters.RebexFtpReceiver;
Expand Down Expand Up @@ -46,6 +48,12 @@ public static void AddNativeAdapters(this IServiceCollection serviceCollection,
serviceCollection.AddScoped<INativeInfolinkReceiver, NativeS3Receiver>();
serviceCollection.AddScoped<INativeAdapter, NativeS3Receiver>();

serviceCollection.AddScoped<INativeInfolinkHandler, NativeAzureBlobUploadHandler>();
serviceCollection.AddScoped<INativeAdapter, NativeAzureBlobUploadHandler>();

serviceCollection.AddScoped<INativeInfolinkReceiver, NativeAzureBlobReceiver>();
serviceCollection.AddScoped<INativeAdapter, NativeAzureBlobReceiver>();

if (!string.IsNullOrEmpty(rebexLicenseKey))
{
serviceCollection.AddScoped<INativeInfolinkReceiver>(_ => new NativeRebexPop3Receiver(rebexLicenseKey));
Expand Down