From 55c570986166d535e58db03a1b67220591447d5f Mon Sep 17 00:00:00 2001 From: "Josiah (Jay) Goodson" Date: Thu, 16 May 2024 07:13:51 -0400 Subject: [PATCH] use %20 instead of spaces when encoding URL params (#844) --- .changeset/odd-melons-brush.md | 5 ++++ pkg/infinity/request.go | 7 ++++- pkg/infinity/request_test.go | 34 +++++++++++++++++++++++++ pkg/models/settings.go | 3 +++ src/editors/config.editor.tsx | 4 +++ src/editors/config/QueryParamEditor.tsx | 20 +++++++++++++++ src/types/config.types.ts | 1 + 7 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 .changeset/odd-melons-brush.md create mode 100644 src/editors/config/QueryParamEditor.tsx diff --git a/.changeset/odd-melons-brush.md b/.changeset/odd-melons-brush.md new file mode 100644 index 000000000..d155eb845 --- /dev/null +++ b/.changeset/odd-melons-brush.md @@ -0,0 +1,5 @@ +--- +'grafana-infinity-datasource': minor +--- + +Add support for encoding space characters in URLs with '%20' instead of '+' diff --git a/pkg/infinity/request.go b/pkg/infinity/request.go index 3292d7112..f481e4183 100644 --- a/pkg/infinity/request.go +++ b/pkg/infinity/request.go @@ -68,7 +68,12 @@ func GetQueryURL(ctx context.Context, settings models.InfinitySettings, query mo } q.Set(settings.ApiKeyKey, val) } - u.RawQuery = q.Encode() + if settings.PathEncodedURLsEnabled { + customEncoder := strings.NewReplacer("+", "%20") + u.RawQuery = customEncoder.Replace(q.Encode()) + } else { + u.RawQuery = q.Encode() + } return NormalizeURL(u.String()), nil } diff --git a/pkg/infinity/request_test.go b/pkg/infinity/request_test.go index d0774f05f..eaf3c6d84 100644 --- a/pkg/infinity/request_test.go +++ b/pkg/infinity/request_test.go @@ -150,6 +150,40 @@ func Test_getQueryURL(t *testing.T) { excludeSecret: true, want: "https://foo.com/xxxxxxxx/hello?foo=bar&hello=xxxxxxxx&key=val&key_one=xxxxxxxx&key_three=xxxxxxxx&key_two=xxxxxxxx&mee=too", }, + { + name: "should use %20 instead of ' ' for all the query parameters", + settings: models.InfinitySettings{ + URL: "https://foo.com", + PathEncodedURLsEnabled: true, + }, + query: models.Query{ + URL: "/hello?key=val10&foo=bar", + URLOptions: models.URLOptions{ + Params: []models.URLOptionKeyValuePair{ + {Key: "key", Value: "val11 val12"}, + {Key: "key2", Value: "value2 value3"}, + }, + }, + }, + want: "https://foo.com/hello?foo=bar&key=val10&key=val11%20val12&key2=value2%20value3", + }, + { + name: "do not overwrite + that isn't a space in query parameters", + settings: models.InfinitySettings{ + URL: "https://foo.com", + PathEncodedURLsEnabled: true, + }, + query: models.Query{ + URL: "/hello?key=val10&foo=bar", + URLOptions: models.URLOptions{ + Params: []models.URLOptionKeyValuePair{ + {Key: "key", Value: "val11 val+12"}, + {Key: "key2", Value: "value2+value3"}, + }, + }, + }, + want: "https://foo.com/hello?foo=bar&key=val10&key=val11%20val%2B12&key2=value2%2Bvalue3", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/models/settings.go b/pkg/models/settings.go index 3c2b05e9c..e6ffd04d2 100644 --- a/pkg/models/settings.go +++ b/pkg/models/settings.go @@ -116,6 +116,7 @@ type InfinitySettings struct { AzureBlobAccountName string AzureBlobAccountKey string UnsecuredQueryHandling UnsecuredQueryHandlingMode + PathEncodedURLsEnabled bool // ProxyOpts is used for Secure Socks Proxy configuration ProxyOpts httpclient.Options } @@ -198,6 +199,7 @@ type InfinitySettingsJson struct { CustomHealthCheckUrl string `json:"customHealthCheckUrl,omitempty"` AzureBlobAccountUrl string `json:"azureBlobAccountUrl,omitempty"` AzureBlobAccountName string `json:"azureBlobAccountName,omitempty"` + PathEncodedURLsEnabled bool `json:"pathEncodedUrlsEnabled,omitempty"` // Security AllowedHosts []string `json:"allowedHosts,omitempty"` UnsecuredQueryHandling UnsecuredQueryHandlingMode `json:"unsecuredQueryHandling,omitempty"` @@ -240,6 +242,7 @@ func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings settings.TimeoutInSeconds = 60 settings.ProxyType = infJson.ProxyType settings.ProxyUrl = infJson.ProxyUrl + settings.PathEncodedURLsEnabled = infJson.PathEncodedURLsEnabled if settings.ProxyType == "" { settings.ProxyType = ProxyTypeEnv } diff --git a/src/editors/config.editor.tsx b/src/editors/config.editor.tsx index 95f105909..9342bcaa6 100644 --- a/src/editors/config.editor.tsx +++ b/src/editors/config.editor.tsx @@ -15,6 +15,7 @@ import { ReferenceDataEditor } from './config/ReferenceData'; import { CustomHealthCheckEditor } from './config/CustomHealthCheckEditor'; import type { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import type { InfinityOptions } from './../types'; +import { QueryParamEditor } from './config/QueryParamEditor'; const Collapse = CollapseOriginal as any; @@ -79,6 +80,9 @@ export const HeadersEditor = (props: DataSourcePluginOptionsEditorProps + + + ); }; diff --git a/src/editors/config/QueryParamEditor.tsx b/src/editors/config/QueryParamEditor.tsx new file mode 100644 index 000000000..3eb44f80f --- /dev/null +++ b/src/editors/config/QueryParamEditor.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { InlineLabel, InlineSwitch } from '@grafana/ui'; +import type { DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import type { InfinityOptions } from '../../types'; + +export const QueryParamEditor = (props: DataSourcePluginOptionsEditorProps) => { + const { options, onOptionsChange } = props; + const { jsonData = {} } = options; + return ( + <> +
+ Encode query parameters with %20 + onOptionsChange({ ...options, jsonData: { ...jsonData, pathEncodedUrlsEnabled: e.currentTarget.checked } })} + /> +
+ + ); +}; diff --git a/src/types/config.types.ts b/src/types/config.types.ts index a3ccf219d..c03956e2a 100644 --- a/src/types/config.types.ts +++ b/src/types/config.types.ts @@ -51,6 +51,7 @@ export interface InfinityOptions extends DataSourceJsonData { azureBlobAccountName?: string; unsecuredQueryHandling?: UnsecureQueryHandling; enableSecureSocksProxy?: boolean; + pathEncodedUrlsEnabled?: boolean; } export interface InfinitySecureOptions {