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
12 changes: 12 additions & 0 deletions src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ public static Dictionary<string, HashSet<T>> CreateArrayMap<T>(this JsonNode? no
return jsonObject.TryGetPropertyValue("$ref", out var refNode) ? refNode?.GetScalarValue() : null;
}

public static void ValidateReferencePointerFormat(string pointer)
{
var hashIndex = pointer.IndexOf('#');
if (hashIndex < 0) return;
var slashIndex = pointer.IndexOf('/', hashIndex);
if (slashIndex >= 0 &&
slashIndex != hashIndex + 1)
{
throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, pointer));
}
}

/// <summary>
/// Returns the value of $dynamicRef if $ref is absent. Used to create a schema reference
/// for bare $dynamicRef schemas (no $ref) so they participate in reference resolution.
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
Expand Down Expand Up @@ -88,7 +88,7 @@ private static IOpenApiExtension LoadExtension(string name, JsonNode node, Parsi
private static (string, string?) GetReferenceIdAndExternalResource(string pointer)
{
var refSegments = pointer.Split('/');
var refId = refSegments[refSegments.Count() -1];
var refId = refSegments[refSegments.Length - 1];
var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);

string? externalResource = isExternalResource ? $"{refSegments[0]}/{refSegments[1].TrimEnd('#')}" : null;
Expand Down
4 changes: 3 additions & 1 deletion src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,10 @@ private static IOpenApiExtension LoadExtension(string name, JsonNode node, Parsi

private static (string, string?) GetReferenceIdAndExternalResource(string pointer)
{
JsonNodeHelper.ValidateReferencePointerFormat(pointer);

var refSegments = pointer.Split('/');
var refId = refSegments[refSegments.Count() -1];
var refId = refSegments[refSegments.Length - 1];
var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);

string? externalResource = null;
Expand Down
6 changes: 4 additions & 2 deletions src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
Expand Down Expand Up @@ -152,14 +152,16 @@ private static IOpenApiExtension LoadExtension(string name, JsonNode node, Parsi

private static (string, string?) GetReferenceIdAndExternalResource(string pointer)
{
JsonNodeHelper.ValidateReferencePointerFormat(pointer);

/* Check whether the reference pointer is a URL
* (id keyword allows you to supply a URL for the schema as a target for referencing)
* E.g. $ref: 'https://example.com/schemas/resource.json'
* or its a normal json pointer fragment syntax
* E.g. $ref: '#/components/schemas/pet'
*/
var refSegments = pointer.Split('/');
string refId = !pointer.Contains('#') ? pointer : refSegments[refSegments.Count()-1];
string refId = !pointer.Contains('#') ? pointer : refSegments[refSegments.Length - 1];

var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);
string? externalResource = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,40 @@ public class OpenApiDocumentTests
{
private const string SampleFolderPath = "V31Tests/Samples/OpenApiDocument/";

[Fact]
public void ParseDocumentWithMalformedReferenceShouldYieldExpectedDiagnostic()
{
var result = OpenApiDocument.Parse(
"""
openapi: 3.1.1
info:
title: test
version: 1.0.0
paths:
/test:
get:
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#components/schemas/Item'
components:
schemas:
Item:
type: object
properties:
id:
type: integer
""",
OpenApiConstants.Yaml,
SettingsFixture.ReaderSettings);

var error = Assert.Single(result.Diagnostic.Errors);
Assert.Equal("The reference string '#components/schemas/Item' has invalid format.", error.Message);
}

[Fact]
public async Task ParseDocumentWithWebhooksShouldSucceed()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,40 @@ public async Task ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic()
}, result.Diagnostic);
}

[Fact]
public void ParseDocumentWithMalformedReferenceShouldYieldExpectedDiagnostic()
{
var result = OpenApiDocument.Parse(
"""
openapi: 3.0.3
info:
title: test
version: 1.0.0
paths:
/test:
get:
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#components/schemas/Item'
components:
schemas:
Item:
type: object
properties:
id:
type: integer
""",
OpenApiConstants.Yaml,
SettingsFixture.ReaderSettings);

var error = Assert.Single(result.Diagnostic.Errors);
Assert.Equal("The reference string '#components/schemas/Item' has invalid format.", error.Message);
}

[Fact]
public async Task ParseMinimalDocumentShouldSucceed()
{
Expand Down
Loading