Skip to content

Invoke-RestMethod -FollowRelLink does not work when links contain commas #17528

@ashscodes

Description

@ashscodes

Prerequisites

Steps to reproduce

Here is a basic sample with PowerShell based on what the current code seems to do:

[string[]]$Links = "<https://some.api.com/api/item?fields=id,description,name&offset=0&limit=1000>;rel=`"first`",<https://some.api.com/api/item?fields=id,short_description,name&offset=1000&limit=1000>;rel=`"next`",<https://some.api.com/api/item?fields=id,short_description,name&offset=4000&limit=1000>;rel=`"last`""

$Links[0].Split(',')

Result

<https://some.api.com/api/item?fields=id
description
name&offset=0&limit=1000>;rel="first"
<https://some.api.com/api/item?fields=id
short_description
name&offset=1000&limit=1000>;rel="next"
<https://some.api.com/api/item?fields=id
short_description
name&offset=4000&limit=1000>;rel="last"

Expected behavior

Invoke-RestMethod can follow RelLink when a comma does appear within a header url. Although I feel the REST API provider (I discovered this with ServiceNow) should provide urlencoded links, I believe this cannot be relied upon and is an easy fix by using a MatchCollection instead.

Current - ParseLinkHeader

Suggested

internal void ParseLinkHeader(HttpResponseMessage response, System.Uri requestUri)
{
    if (_relationLink == null)
    {
        // Must ignore the case of relation links. See RFC 8288 (https://tools.ietf.org/html/rfc8288)
        _relationLink = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    }
    else
    {
        _relationLink.Clear();
    }

    // we only support the URL in angle brackets and `rel`, other attributes are ignored
    // user can still parse it themselves via the Headers property
    const string pattern = "<(?<url>.*?)>;\\s*rel=(?<quoted>\")?(?<rel>(?(quoted).*?|[^,;]*))(?(quoted)\")";
    IEnumerable<string> links;
    if (response.Headers.TryGetValues("Link", out links))
    {
        foreach (string linkHeader in links)
        {
            MatchCollection matchCollection = Regex.Matches(link, pattern)
            foreach (Match match in matchCollection)
            {
                if (match.Success)
                {
                    string url = match.Groups["url"].Value;
                    string rel = match.Groups["rel"].Value;
                    if (url != string.Empty && rel != string.Empty && !_relationLink.ContainsKey(rel))
                    {
                        Uri absoluteUri = new(requestUri, url);
                        _relationLink.Add(rel, absoluteUri.AbsoluteUri);
                    }
                }
            }
        }
    }
}

Actual behavior

See Steps To Reproduce. Splitting the links returned by a comma breaks the links before they can be parsed by the regex.

Error details

N/A

Environment data

7+

Visuals

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.Resolution-FixedThe issue is fixed.WG-Cmdlets-Utilitycmdlets in the Microsoft.PowerShell.Utility module

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions