Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into resource-datacontext
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Dec 28, 2024
2 parents a3a7255 + 5b54b96 commit 973efd0
Show file tree
Hide file tree
Showing 25 changed files with 685 additions and 277 deletions.
2 changes: 1 addition & 1 deletion .github/test-report/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ inputs:
runs:
using: composite
steps:
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.report-name }}
path: ${{ inputs.trx-path }}
Expand Down
17 changes: 9 additions & 8 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

## Supported Versions

When it comes to security updates, we always support the current major version together with the latest preview version.
Any vulnerabilities that affect older versions will be considered on a case-by-case basis.
When it comes to security updates, we always support the current stable version together with the latest preview version.
Each older stable version will continue to be supported for at least one year after the release of the subsequent stable version.
Any vulnerabilities that affect unsupported versions will be considered on a case-by-case basis.

| Version | Supported |
| ------- | ------------------ |
| 4.2.x | :white_check_mark: |
| 4.1.x | :white_check_mark: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |
| Version | Supported | Expires |
| ----------- | ------------------ | --------------- |
| 5.0 preview | :white_check_mark: | |
| 4.3 | :white_check_mark: | |
| 4.2 | :white_check_mark: | September 2025 |
| 4.1 | :x: | November 2024 |

## Reporting a Vulnerability

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private void AddNodeErrors(DothtmlNode node, int priority)
/// * the locations are processed using MapBindingLocation to make them useful in the context of a dothtml file </summary>
Dictionary<Exception, DotvvmCompilationSourceLocation?> AnnotateBindingExceptionWithLocation(ResolvedBinding binding, DotvvmProperty? relatedProperty, IEnumerable<Exception> errors)
{
var result = new Dictionary<Exception, DotvvmCompilationSourceLocation?>(new ReferenceEqualityComparer<Exception>());
var result = new Dictionary<Exception, DotvvmCompilationSourceLocation?>(ReferenceEqualityComparer<Exception>.Instance);
void recurse(Exception exception, DotvvmCompilationSourceLocation? location)
{
if (result.ContainsKey(exception))
Expand Down
14 changes: 10 additions & 4 deletions src/Framework/Framework/Controls/Repeater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ protected internal override void OnLoad(IDotvvmRequestContext context)
protected internal override void OnPreRender(IDotvvmRequestContext context)
{
SetChildren(context, renderClientTemplate: !RenderOnServer, memoizeReferences: false);
childrenCache.Clear(); // not going to need the unreferenced children anymore
base.OnPreRender(context);
}

Expand Down Expand Up @@ -250,7 +251,7 @@ private DotvvmControl CreateEmptyItem(IDotvvmRequestContext context, IStaticVal
return emptyDataContainer;
}

private ConditionalWeakTable<object, DataItemContainer> childrenCache = new ConditionalWeakTable<object, DataItemContainer>();
private readonly Dictionary<object, DataItemContainer> childrenCache = new(ReferenceEqualityComparer<object>.Instance);
private DotvvmControl AddItem(IList<DotvvmControl> c, IDotvvmRequestContext context, object? item = null, int? index = null, bool serverOnly = false, bool allowMemoizationRetrieve = false, bool allowMemoizationStore = false)
{
if (allowMemoizationRetrieve && item != null && childrenCache.TryGetValue(item, out var container2) && container2.Parent == null)
Expand Down Expand Up @@ -279,8 +280,12 @@ private DotvvmControl AddItem(IList<DotvvmControl> c, IDotvvmRequestContext cont
// write it to the cache after the content is build. If it would be before that, exception could be suppressed
if (allowMemoizationStore && item != null)
{
// this GetValue call adds the value without raising exception when the value is already added...
childrenCache.GetValue(item, _ => container);
#if DotNetCore
childrenCache.TryAdd(item, container);
#else
if (!childrenCache.ContainsKey(item))
childrenCache.Add(item, container);
#endif
}

return container;
Expand Down Expand Up @@ -313,14 +318,15 @@ private void SetChildren(IDotvvmRequestContext context, bool renderClientTemplat
if (dataSource != null)
{
var index = 0;
var isCommand = context.RequestType == DotvvmRequestType.Command; // on GET request we are not initializing the Repeater twice
foreach (var item in dataSource)
{
if (SeparatorTemplate != null && index > 0)
{
AddSeparator(Children, context);
}
AddItem(Children, context, item, index, serverOnly,
allowMemoizationRetrieve: context.RequestType == DotvvmRequestType.Command && !memoizeReferences, // on GET request we are not initializing the Repeater twice
allowMemoizationRetrieve: isCommand && !memoizeReferences,
allowMemoizationStore: memoizeReferences
);
index++;
Expand Down
19 changes: 12 additions & 7 deletions src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Utils;

namespace DotVVM.Framework.Hosting
{
/// <summary> Read markup files from embedded resources, if the virtual path has the following format: <c>embedded://{AssemblyName}/{ResourceName}</c></summary>
public class EmbeddedMarkupFileLoader : IMarkupFileLoader
{
/// <summary>
Expand All @@ -23,7 +26,7 @@ public class EmbeddedMarkupFileLoader : IMarkupFileLoader

if (resourceName.IndexOf('/') == -1 || resourceName.IndexOf('/') == 0)
{
throw new ArgumentException("Wrong string format", "virtualPath");
throw new ArgumentException("Wrong embedded file format. Use `embedded://{AssemblyName}/{ResourceName}`", "virtualPath");
}

string assemblyName = resourceName.Substring(0, resourceName.IndexOf('/'));
Expand All @@ -37,20 +40,22 @@ public class EmbeddedMarkupFileLoader : IMarkupFileLoader
//no such assembly found
catch (FileLoadException)
{
throw new ArgumentException("No such assembly was found", "virtualPath");
throw new ArgumentException($"Assembly '{assemblyName}' was not found", "virtualPath");
}

//no such resource found
resourceName = resourceName.Replace('/', '.');
if (assembly.GetManifestResourceInfo(resourceName) == null)
{
throw new ArgumentException("No such resource was found", "virtualPath");
throw new ArgumentException($"Resource '{resourceName}' was not found in assembly '{assembly.FullName}'", "virtualPath");
}

//load the file
using (Stream stream = assembly.GetManifestResourceStream(resourceName)!)
using (StreamReader sr = new StreamReader(stream))
return new MarkupFile(virtualPath, virtualPath, sr.ReadToEnd());
return new MarkupFile(virtualPath, virtualPath, () => {
//load the file
using (Stream stream = assembly.GetManifestResourceStream(resourceName)!)
using (var reader = new StreamReader(stream))
return reader.ReadToEnd();
});
}

/// <summary>
Expand Down
8 changes: 8 additions & 0 deletions src/Framework/Framework/Hosting/MarkupFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public MarkupFile(string fileName, string fullPath)
};
}

public MarkupFile(string fileName, string fullPath, Func<string> readContent, DateTime lastWriteDateTimeUtc = default)
{
FileName = fileName;
FullPath = fullPath;
ReadContent = readContent;
LastWriteDateTimeUtc = lastWriteDateTimeUtc;
}

public MarkupFile(string fileName, string fullPath, string contents, DateTime lastWriteDateTimeUtc = default)
{
FileName = fileName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ public static LinkResourceBase RegisterScriptFile(
/// <summary> Registers a <see cref="ScriptModuleResource" /> from the specified file.
/// The file can be anywhere in the filesystem, it does not have to be in the wwwroot folder.
/// DotVVM will handle its serving, caching, ... automatically </summary>
[Obsolete("<script type='module'> is always deferred, the attribute does nothing")]
public static LinkResourceBase RegisterScriptModuleFile(
this DotvvmResourceRepository repo,
string name,
Expand All @@ -88,7 +87,7 @@ public static LinkResourceBase RegisterScriptModuleFile(
/// <summary> Registers a <see cref="ScriptModuleResource" /> from the specified file.
/// The file can be anywhere in the filesystem, it does not have to be in the wwwroot folder.
/// DotVVM will handle its serving, caching, ... automatically </summary>
[Obsolete("<script type='module'> is always deferred, the attribute does nothing")]
[Obsolete("<script type='module'> is always deferred, the attribute does nothing. Please remove the defer parameter.")]
public static LinkResourceBase RegisterScriptModuleFile(
this DotvvmResourceRepository repo,
string name,
Expand Down
3 changes: 2 additions & 1 deletion src/Framework/Framework/Resources/Scripts/dotvvm-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ const dotvvmExports = {
getStateManager().patchState(a)
},
setState(a: any) { getStateManager().setState(a) },
updateState(updateFunction: StateUpdate<any>) { getStateManager().update(updateFunction) },
updateState(updateFunction: StateUpdate<any>) { getStateManager().updateState(updateFunction) },
get rootStateManager() { return getStateManager() },
viewModelObservables: {
get root() { return getViewModelObservable(); }
},
Expand Down
59 changes: 3 additions & 56 deletions src/Framework/Framework/Resources/Scripts/knockout-latest.debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -1873,7 +1873,6 @@ ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "uns
// (for consistency with mutating regular observables)
var underlyingArray = this.peek();
this.valueWillMutate();
this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);

applyObservableValidatorIfExists.call(this, underlyingArray);
Expand Down Expand Up @@ -1901,6 +1900,7 @@ ko.isObservableArray = function (instance) {
ko.exportSymbol('observableArray', ko.observableArray);
ko.exportSymbol('isObservableArray', ko.isObservableArray);
var arrayChangeEventName = 'arrayChange';
var appliedTrackChangesSymbol = Symbol()
ko.extenders['trackArrayChanges'] = function(target, options) {
// Use the provided options--each call to trackArrayChanges overwrites the previously set options
target.compareArrayOptions = {};
Expand All @@ -1910,9 +1910,10 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
target.compareArrayOptions['sparse'] = true;

// Only modify the target observable once
if (target.cacheDiffForKnownOperation) {
if (target[appliedTrackChangesSymbol]) {
return;
}
target[appliedTrackChangesSymbol] = true;
var trackingChanges = false,
cachedDiff = null,
changeSubscription,
Expand Down Expand Up @@ -2003,60 +2004,6 @@ ko.extenders['trackArrayChanges'] = function(target, options) {

return cachedDiff;
}

target.cacheDiffForKnownOperation = function(rawArray, operationName, args) {
// Only run if we're currently tracking changes for this observable array
// and there aren't any pending deferred notifications.
if (!trackingChanges || pendingChanges) {
return;
}
var diff = [],
arrayLength = rawArray.length,
argsLength = args.length,
offset = 0;

function pushDiff(status, value, index) {
return diff[diff.length] = { 'status': status, 'value': value, 'index': index };
}
switch (operationName) {
case 'push':
offset = arrayLength;
case 'unshift':
for (var index = 0; index < argsLength; index++) {
pushDiff('added', args[index], offset + index);
}
break;

case 'pop':
offset = arrayLength - 1;
case 'shift':
if (arrayLength) {
pushDiff('deleted', rawArray[offset], offset);
}
break;

case 'splice':
// Negative start index means 'from end of array'. After that we clamp to [0...arrayLength].
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength),
endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength),
endAddIndex = startIndex + argsLength - 2,
endIndex = Math.max(endDeleteIndex, endAddIndex),
additions = [], deletions = [];
for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
if (index < endDeleteIndex)
deletions.push(pushDiff('deleted', rawArray[index], index));
if (index < endAddIndex)
additions.push(pushDiff('added', args[argsIndex], index));
}
ko.utils.findMovesInArrayComparison(deletions, additions);
break;

default:
return;
}
cachedDiff = diff;
};
};
var computedState = ko.utils.createSymbolOrString('_state');

Expand Down
Loading

0 comments on commit 973efd0

Please sign in to comment.