Store images

Editors upload images to the media library. SunBird keeps the files through the site's HCI.FileStorage IFileStorageService, so the site must register one. MapSunBird() stops the site from starting without one, with a message saying so.

Register a file store

For Azure Blob Storage, add HCI.FileStorage.AzureBlobStorage and register it in Program.cs, before AddSunBird():

using HCI.FileStorage.AzureBlobStorage;

builder.Services.AddAzureBlobStorage(builder.Configuration);
builder.AddSunBird().UseSqlServer(builder.Configuration.GetConnectionString("SunBird")!);

See the package's own README for its settings (connection string and container). The container should not allow public access: visitors get images from SunBird, never from the store directly.

SunBird stores its files under sunbird/media/, so it can share a store that the site uses for other files.

Note

Coming: a database-backed IFileStorageService in the HCI FileStorage library, so a site can keep its images in its own database with no extra service. Until then, register a provider such as Azure Blob Storage. For local development, the sample site registers a small store that writes to App_Data/files.

Set the size limit

Uploads are limited to 10 MB by default. Change it with SunBird:Media:MaxUploadMegabytes:

{
  "SunBird": {
    "Media": { "MaxUploadMegabytes": 5 }
  }
}

The limit also caps the size of the upload request, so a larger file is cut off rather than read. If the site sits behind a proxy or App Service front end with its own request size limit, keep that limit above this one. SunBird does not resize images: editors upload them at the size the page needs.

What SunBird accepts

Only JPEG, PNG, WebP and GIF images and PDF documents are accepted. SunBird reads the type from the file's first bytes, never from its name or the type the browser claims, so a script renamed photo.png is refused. SVG is refused, because an SVG file can carry script. An upload whose bytes match a file already in the library reuses that file instead of storing a copy.

How images are served

A published image slot renders <img src="/sunbird/media/{id}">. That address is public, and serves any image in the library that has not been deleted; the ids are random, so unpublished images cannot be guessed. Each response:

  • has the content type SunBird found from the bytes, and X-Content-Type-Options: nosniff, so a browser never treats it as anything else;
  • has Content-Security-Policy: default-src 'none'; sandbox, so even opened on its own it cannot run anything;
  • is cached for a year (Cache-Control: public, max-age=31536000, immutable), because an image's bytes never change. A changed picture is a new image with a new address.

A document chosen for an <sb-file> link is served at /sunbird/media/{id}/{file-name} with the same headers, plus Content-Disposition: inline and the name it was uploaded with, so it opens in the browser and saves under that name.

A deleted image or document returns 404. Serving only reads; it never writes to the database.

Editor endpoints

The media rail uses GET /sunbird/edit/media (the library), POST /sunbird/edit/media (a multipart upload with the file in the file field, and kind=document from the document picker) and POST /sunbird/edit/media/{id}/delete. They are for signed-in editors only and need the antiforgery token from the toolbar, like the other edit endpoints.