replace parse-link-header

it had a node dependency which required a webpack fallback from
querystring to querystring-es3

flag=none

Change-Id: Ib403113f41990c279e2192662b780be10f744853
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/336059
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Michael Hulse <michael.hulse@instructure.com>
QA-Review: Aaron Shafovaloff <ashafovaloff@instructure.com>
Product-Review: Aaron Shafovaloff <ashafovaloff@instructure.com>
This commit is contained in:
Aaron Shafovaloff 2023-12-30 10:43:51 -05:00
parent d21933407f
commit bb3a0d5d60
14 changed files with 172 additions and 11 deletions

View File

@ -17,7 +17,7 @@
*/
import {combineReducers} from 'redux'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import initialState from '../store/initialState'
const emailRegex = /([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})/i

View File

@ -16,7 +16,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import ajaxJSON from '@canvas/jquery/jquery.ajaxJSON'
import createStore from '@canvas/backbone/createStore'

View File

@ -35,7 +35,7 @@ import $ from 'jquery'
import {useScope as useI18nScope} from '@canvas/i18n'
import Spinner from 'spin.js'
import htmlEscape from '@instructure/html-escape'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
const I18n = useI18nScope('paginated_list')

View File

@ -17,7 +17,7 @@
*/
import axios from '@canvas/axios'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import numberHelper from '@canvas/i18n/numberHelper'
import categories, {OTHER_ID} from './categories'

View File

@ -18,7 +18,7 @@
*/
import getCookie from '@instructure/get-cookie'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import {defaultFetchOptions} from '@canvas/util/xhr'
import {toQueryString} from '@canvas/query-string-encoding'
import type {QueryParameterRecord} from '@canvas/query-string-encoding'

View File

@ -18,7 +18,7 @@
import _ from 'lodash'
import uuid from 'uuid'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import NaiveFetchDispatch from './NaiveFetchDispatch'
import makePromisePool from '@canvas/make-promise-pool'

View File

@ -16,7 +16,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import NetworkFake from '../../NetworkFake'
import {sendGetRequest} from '../../specHelpers'

View File

@ -17,7 +17,7 @@
*/
import {find, isArray} from 'lodash'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import deferPromise from '@instructure/defer-promise'
/*

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import parseLinkHeader from '../parseLinkHeader'
const linkHeader =
'<https://www.example.com/path?page=2&per_page=10>; rel="next", ' +
'<https://www.example.com/path?page=1&per_page=10>; rel="prev"; foo="bar", ' +
'<https://www.example.com/path?page=5&per_page=10>; rel="last"'
describe('parseLinkHeader', () => {
it('should parse a link header', () => {
const parsed = parseLinkHeader(linkHeader)
expect(parsed).toEqual({
next: {
page: '2',
per_page: '10',
rel: 'next',
url: 'https://www.example.com/path?page=2&per_page=10',
},
prev: {
page: '1',
per_page: '10',
foo: 'bar',
rel: 'prev',
url: 'https://www.example.com/path?page=1&per_page=10',
},
last: {
page: '5',
per_page: '10',
rel: 'last',
url: 'https://www.example.com/path?page=5&per_page=10',
},
})
})
})

View File

@ -0,0 +1,7 @@
{
"name": "@canvas/parse-link-header",
"private": true,
"version": "1.0.0",
"author": "neme",
"main": "./parseLinkHeader.ts"
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2023 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// based on https://github.com/thlorenz/parse-link-header/blob/master/index.js (MIT)
type LinkInfo = {
[key: string]: string
}
function parseQueryParams(linkUrl: string): {[key: string]: string} {
const queryParams: {[key: string]: string} = {}
const urlParts = linkUrl.split('?')
if (urlParts.length > 1) {
urlParts[1].split('&').forEach(param => {
const [key, value] = param.split('=')
queryParams[key] = decodeURIComponent(value)
})
}
return queryParams
}
function parseLink(link: string): LinkInfo | null {
try {
const linkMatch = link.match(/<([^>]*)>\s*(.*)/)
if (!linkMatch) {
return null
}
const [, linkUrl, partsString] = linkMatch
const parts = partsString.split(';').map(part => part.trim())
const info: LinkInfo = {url: linkUrl}
parts.forEach(part => {
const partMatch = part.match(/(.+)\s*=\s*"?([^"]+)"?/)
if (partMatch) {
const [, key, value] = partMatch
info[key.trim()] = value.trim()
}
})
return {...parseQueryParams(linkUrl), ...info}
} catch (e) {
return null
}
}
function hasRel(x: LinkInfo | null): x is LinkInfo {
return x !== null && 'rel' in x
}
function intoRels(acc: {[rel: string]: LinkInfo}, x: LinkInfo): {[rel: string]: LinkInfo} {
x.rel.split(/\s+/).forEach(rel => {
const {...rest} = x
acc[rel] = rest
})
return acc
}
const PARSE_LINK_HEADER_MAXLEN = 2000
const PARSE_LINK_HEADER_THROW_ON_MAXLEN_EXCEEDED =
process.env.PARSE_LINK_HEADER_THROW_ON_MAXLEN_EXCEEDED != null
function checkHeader(linkHeader: string | undefined): boolean {
if (!linkHeader) return false
if (linkHeader.length > PARSE_LINK_HEADER_MAXLEN) {
if (PARSE_LINK_HEADER_THROW_ON_MAXLEN_EXCEEDED) {
throw new Error(
`Input string too long, it should be under ${PARSE_LINK_HEADER_MAXLEN} characters.`
)
} else {
return false
}
}
return true
}
export default function parseLinkHeader(linkHeader: string): {[rel: string]: LinkInfo} | null {
if (!checkHeader(linkHeader)) return null
return linkHeader
.split(/,\s*(?=<)/)
.map(parseLink)
.filter(hasRel)
.reduce(intoRels, {})
}

View File

@ -18,7 +18,7 @@
import {createActions, createAction} from 'redux-actions'
import axios from 'axios'
import {asAxios, getPrefetchedXHR} from '@canvas/util/xhr'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import configureAxios from '../utilities/configureAxios'
import {alert} from '../utilities/alertUtils'
import {useScope as useI18nScope} from '@canvas/i18n'

View File

@ -17,7 +17,7 @@
*/
import axios from 'axios'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
import {put, select, call, all, takeEvery} from 'redux-saga/effects'
import {getFirstLoadedMoment, getLastLoadedMoment} from '../utilities/dateUtils'
import {

View File

@ -17,7 +17,7 @@
*/
import moment from 'moment-timezone'
import _ from 'lodash'
import parseLinkHeader from 'parse-link-header'
import parseLinkHeader from '@canvas/parse-link-header'
const getItemDetailsFromPlannable = apiResponse => {
const {plannable, plannable_type, planner_override} = apiResponse