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
196 changes: 196 additions & 0 deletions lib/node_modules/@stdlib/_tools/github/get/lib/linkheader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2026 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var parseURL = require( 'url' ).parse;
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var trim = require( '@stdlib/string/base/trim' );
var objectKeys = require( '@stdlib/utils/keys' );


// VARIABLES //

var MAX_LENGTH = 2000;


// FUNCTIONS //

/**
* Splits a link header into individual link values.
*
* @private
* @param {string} str - header string
* @returns {StringArray} link values
*/
function splitValues( str ) {
var values;
var quoted;
var angled;
var start;
var i;

values = [];
start = 0;
quoted = false;
angled = false;

for ( i = 0; i < str.length; i++ ) {
if ( str.charCodeAt( i ) === 34 ) {
quoted = !quoted;
} else if ( quoted === false ) {
if ( str.charCodeAt( i ) === 60 ) {
angled = true;
} else if ( str.charCodeAt( i ) === 62 ) {
angled = false;
} else if ( str.charCodeAt( i ) === 44 && angled === false ) {
values.push( str.substring( start, i ) );
start = i + 1;
}
}
}
values.push( str.substring( start ) );
return values;
}

/**
* Removes wrapping double quotes from a string.
*
* @private
* @param {string} str - input string
* @returns {string} unwrapped string
*/
function unwrap( str ) {
if (
str.length >= 2 &&
str.charCodeAt( 0 ) === 34 &&
str.charCodeAt( str.length-1 ) === 34
) {
return str.substring( 1, str.length-1 );
}
return str;
}

/**
* Parses an individual link header value.
*
* @private
* @param {string} str - link value
* @returns {(Object|null)} parsed link or null
*/
function parseValue( str ) {
var rels;
var out;
var url;
var idx;
var q;
var v;
var k;
var i;

str = trim( str );
if ( str.length === 0 || str.charCodeAt( 0 ) !== 60 ) {
return null;
}
idx = str.indexOf( '>' );
if ( idx < 0 ) {
return null;
}
url = str.substring( 1, idx );
out = {
'url': url
};

q = parseURL( url, true ).query;
for ( k in q ) {
if ( hasOwnProp( q, k ) ) {
out[ k ] = q[ k ];
}
}
str = str.substring( idx+1 );
str = str.split( ';' );

for ( i = 0; i < str.length; i++ ) {
v = trim( str[ i ] );
if ( v.length === 0 ) {
continue;
}
idx = v.indexOf( '=' );
if ( idx < 0 ) {
continue;
}
k = trim( v.substring( 0, idx ) );
v = unwrap( trim( v.substring( idx+1 ) ) );
out[ k ] = v;
}
if ( typeof out.rel !== 'string' || out.rel.length === 0 ) {
return null;
}
rels = out.rel.split( /\s+/ );
return {
'rels': rels,
'value': out
};
}


// MAIN //

/**
* Parses a Link header.
*
* @private
* @param {string} header - link header
* @returns {(Object|null)} parsed links or null
*/
function parseLinkHeader( header ) {
var values;
var out;
var obj;
var i;
var j;

if ( typeof header !== 'string' || header.length === 0 ) {
return null;
}
if ( header.length > MAX_LENGTH ) {
return null;
}
values = splitValues( header );
out = {};
for ( i = 0; i < values.length; i++ ) {
obj = parseValue( values[ i ] );
if ( obj === null ) {
continue;
}
for ( j = 0; j < obj.rels.length; j++ ) {
out[ obj.rels[ j ] ] = obj.value;
}
}
if ( objectKeys( out ).length === 0 ) {
return null;
}
return out;
}


// EXPORTS //

module.exports = parseLinkHeader;
2 changes: 1 addition & 1 deletion lib/node_modules/@stdlib/_tools/github/get/lib/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
// MODULES //

var logger = require( 'debug' );
var parseHeader = require( 'parse-link-header' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var parseHeader = require( './linkheader.js' );
var request = require( './request.js' );
var flatten = require( './flatten.js' );
var getOptions = require( './options.js' );
Expand Down Expand Up @@ -169,7 +169,7 @@

if ( curr === opts.page ) {
debug( 'First paginated result. Resolving %d remaining page(s).', total-count );
setTimeout( getNext( curr+1, last ), 0 ); // dezalgo'd

Check warning on line 172 in lib/node_modules/@stdlib/_tools/github/get/lib/resolve.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unknown word: "dezalgo'd"
}
done();
}
Expand Down
109 changes: 109 additions & 0 deletions lib/node_modules/@stdlib/_tools/github/get/test/test.linkheader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2026 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var parseLinkHeader = require( './../lib/linkheader.js' );


// TESTS //

tape( 'main export is a function', function test( t ) {
t.ok( true, __filename );
t.strictEqual( typeof parseLinkHeader, 'function', 'main export is a function' );
t.end();
});

tape( 'the function returns `null` if provided an empty header', function test( t ) {
t.strictEqual( parseLinkHeader( '' ), null, 'returns expected value' );
t.end();
});

tape( 'the function returns `null` if provided a malformed header', function test( t ) {
t.strictEqual( parseLinkHeader( 'beep; boop' ), null, 'returns expected value' );
t.end();
});

tape( 'the function returns `null` if provided a header exceeding the maximum length', function test( t ) {
var header;
var tail;
var i;

header = '<https://api.github.com/user/9287/repos?page=2&per_page=1>; rel="next", ';
tail = '';
for ( i = 0; i < 2100; i++ ) {
tail += 'a';
}
header += tail;

t.strictEqual( header.length > 2000, true, 'test fixture exceeds maximum length' );
t.strictEqual( parseLinkHeader( header ), null, 'returns expected value' );
t.end();
});

tape( 'the function parses paginated GitHub link headers', function test( t ) {
var header;
var out;

header = '<https://api.github.com/user/9287/repos?page=2&per_page=1>; rel="next", <https://api.github.com/user/9287/repos?page=3&per_page=1>; rel="last"';
out = parseLinkHeader( header );

t.deepEqual( out, {
'next': {
'url': 'https://api.github.com/user/9287/repos?page=2&per_page=1',
'page': '2',
'per_page': '1',
'rel': 'next'
},
'last': {
'url': 'https://api.github.com/user/9287/repos?page=3&per_page=1',
'page': '3',
'per_page': '1',
'rel': 'last'
}
}, 'returns expected value' );
t.end();
});

tape( 'the function supports multiple relation types for a single link value', function test( t ) {
var header;
var out;

header = '<https://api.github.com/user/9287/repos?page=1>; rel="prev previous"';
out = parseLinkHeader( header );

t.strictEqual( out.prev, out.previous, 'relations reference the same value' );
t.strictEqual( out.prev.page, '1', 'sets query parameter values' );
t.strictEqual( out.prev.rel, 'prev previous', 'retains original relation string' );
t.end();
});

tape( 'the function preserves additional link parameters', function test( t ) {
var header;
var out;

header = '<https://api.github.com/user/9287/repos?page=2>; rel="next"; type="application/json"; title="next, page"';
out = parseLinkHeader( header );

t.strictEqual( out.next.type, 'application/json', 'sets the media type' );
t.strictEqual( out.next.title, 'next, page', 'sets the title' );
t.end();
});
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@
"mathjax-node-sre": "^3.0.0",
"mkdirp": "^0.5.1",
"mustache": "^4.0.0",
"parse-link-header": "^1.0.1",
"plato": "^1.5.0",
"process": "^0.11.10",
"proxyquire": "^2.0.0",
Expand Down