Skip to content
Open
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
28 changes: 20 additions & 8 deletions docs/03-tutorials/01-simple-viewer/04-viewer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ initViewer(document.getElementById('preview')).then(viewer => {

async function setupModelSelection(viewer, selectedUrn) {
const dropdown = document.getElementById('models');
dropdown.innerHTML = '';
dropdown.replaceChildren();
try {
const resp = await fetch('/api/models');
if (!resp.ok) {
throw new Error(await resp.text());
}
const models = await resp.json();
dropdown.innerHTML = models.map(model => `<option value=${model.urn} ${model.urn === selectedUrn ? 'selected' : ''}>${model.name}</option>`).join('\n');
dropdown.replaceChildren(...models.map(model => new Option(model.name, model.urn, false, model.urn === selectedUrn)));
dropdown.onchange = () => onModelSelected(viewer, dropdown.value);
if (dropdown.value) {
onModelSelected(viewer, dropdown.value);
Expand All @@ -124,7 +124,9 @@ async function setupModelUpload(viewer) {
}
upload.setAttribute('disabled', 'true');
models.setAttribute('disabled', 'true');
showNotification(`Uploading model <em>${file.name}</em>. Do not reload the page.`);
const filename = document.createElement('em');
filename.textContent = file.name;
showNotification('Uploading model ', filename, '. Do not reload the page.');
try {
const resp = await fetch('/api/models', { method: 'POST', body: data });
if (!resp.ok) {
Expand Down Expand Up @@ -164,9 +166,16 @@ async function onModelSelected(viewer, urn) {
showNotification(`Model is being translated (${status.progress})...`);
window.onModelSelectedTimeout = setTimeout(onModelSelected, 5000, viewer, urn);
break;
case 'failed':
showNotification(`Translation failed. <ul>${status.messages.map(msg => `<li>${JSON.stringify(msg)}</li>`).join('')}</ul>`);
case 'failed': {
const list = document.createElement('ul');
for (const msg of status.messages) {
const item = document.createElement('li');
item.textContent = JSON.stringify(msg);
list.append(item);
}
showNotification('Translation failed. ', list);
break;
}
default:
clearNotification();
loadModel(viewer, urn);
Expand All @@ -178,15 +187,18 @@ async function onModelSelected(viewer, urn) {
}
}

function showNotification(message) {
function showNotification(...content) {
const overlay = document.getElementById('overlay');
overlay.innerHTML = `<div class="notification">${message}</div>`;
const notification = document.createElement('div');
notification.className = 'notification';
notification.append(...content);
overlay.replaceChildren(notification);
overlay.style.display = 'flex';
}

function clearNotification() {
const overlay = document.getElementById('overlay');
overlay.innerHTML = '';
overlay.replaceChildren();
overlay.style.display = 'none';
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Create a `ModelsController.cs` file under the `Controllers` subfolder with the f
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -14,6 +15,13 @@ public class ModelsController : ControllerBase
{
public record BucketObject(string name, string urn);

private static readonly Regex ForbiddenFilenameChars = new(@"[<>""`\\/\x00-\x1f\x7f]");

private static bool IsSafeFilename(string name)
{
return !string.IsNullOrEmpty(name) && name.Length <= 255 && !ForbiddenFilenameChars.IsMatch(name);
}

private readonly APS _aps;

public ModelsController(APS aps)
Expand Down Expand Up @@ -46,12 +54,20 @@ public class ModelsController : ControllerBase
}

[HttpPost(), DisableRequestSizeLimit]
public async Task<BucketObject> UploadAndTranslateModel([FromForm] UploadModelForm form)
public async Task<IActionResult> UploadAndTranslateModel([FromForm] UploadModelForm form)
{
if (form.File == null)
{
return BadRequest("The required field (\"model-file\") is missing.");
}
if (!IsSafeFilename(form.File.FileName))
{
return BadRequest("The filename contains invalid characters.");
}
using var stream = form.File.OpenReadStream();
var obj = await _aps.UploadModel(form.File.FileName, stream);
var job = await _aps.TranslateModel(obj.ObjectId, form.Entrypoint);
return new BucketObject(obj.ObjectKey, job.Urn);
return Ok(new BucketObject(obj.ObjectKey, job.Urn));
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const { listObjects, uploadObject, translateObject, getManifest, urnify } = requ

let router = express.Router();

function isSafeFilename(name, allowSlash = false) {
const forbidden = allowSlash ? /[<>"`\\\x00-\x1f\x7f]/ : /[<>"`\\/\x00-\x1f\x7f]/;
return typeof name === 'string' && name.length > 0 && name.length <= 255 && !forbidden.test(name);
}

router.get('/api/models', async function (req, res, next) {
try {
const objects = await listObjects();
Expand Down Expand Up @@ -49,6 +54,10 @@ router.post('/api/models', formidable({ maxFileSize: Infinity }), async function
res.status(400).send('The required field ("model-file") is missing.');
return;
}
if (!isSafeFilename(file.name)) {
res.status(400).send('The filename contains invalid characters.');
return;
}
try {
const obj = await uploadObject(file.name, file.path);
await translateObject(urnify(obj.objectId), req.fields['model-zip-entrypoint']);
Expand Down
Loading