Skip to content

Commit

Permalink
feat: Use URL escape for DTO name field
Browse files Browse the repository at this point in the history
Use URL escape for DTO name field, including
- device name
- profile name
- service name
- interval and interval action name
- provision watcher name
- subscription name

Close edgexfoundry#850

Signed-off-by: bruce <weichou1229@gmail.com>
  • Loading branch information
weichou1229 committed Aug 24, 2023
1 parent 05565b0 commit 78f9b3a
Show file tree
Hide file tree
Showing 31 changed files with 87 additions and 91 deletions.
3 changes: 1 addition & 2 deletions clients/http/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package http
import (
"context"
"net/url"
"path"
"strconv"

"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/http/utils"
Expand Down Expand Up @@ -49,7 +48,7 @@ func (client *CommandClient) AllDeviceCoreCommands(ctx context.Context, offset i
// DeviceCoreCommandsByDeviceName returns all commands associated with the specified device name.
func (client *CommandClient) DeviceCoreCommandsByDeviceName(ctx context.Context, name string) (
res responses.DeviceCoreCommandResponse, err errors.EdgeX) {
path := path.Join(common.ApiDeviceRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, client.baseUrl, path, nil, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
11 changes: 5 additions & 6 deletions clients/http/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package http
import (
"context"
"net/url"
"path"
"strconv"
"strings"

Expand Down Expand Up @@ -66,7 +65,7 @@ func (dc DeviceClient) AllDevices(ctx context.Context, labels []string, offset i
}

func (dc DeviceClient) DeviceNameExists(ctx context.Context, name string) (res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiDeviceRoute, common.Check, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Check, common.Name, name)
err = utils.GetRequest(ctx, &res, dc.baseUrl, path, nil, dc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -75,7 +74,7 @@ func (dc DeviceClient) DeviceNameExists(ctx context.Context, name string) (res d
}

func (dc DeviceClient) DeviceByName(ctx context.Context, name string) (res responses.DeviceResponse, err errors.EdgeX) {
path := path.Join(common.ApiDeviceRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, dc.baseUrl, path, nil, dc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -84,7 +83,7 @@ func (dc DeviceClient) DeviceByName(ctx context.Context, name string) (res respo
}

func (dc DeviceClient) DeleteDeviceByName(ctx context.Context, name string) (res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiDeviceRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, name)
err = utils.DeleteRequest(ctx, &res, dc.baseUrl, path, dc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -93,7 +92,7 @@ func (dc DeviceClient) DeleteDeviceByName(ctx context.Context, name string) (res
}

func (dc DeviceClient) DevicesByProfileName(ctx context.Context, name string, offset int, limit int) (res responses.MultiDevicesResponse, err errors.EdgeX) {
requestPath := path.Join(common.ApiDeviceRoute, common.Profile, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Profile, common.Name, name)
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand All @@ -105,7 +104,7 @@ func (dc DeviceClient) DevicesByProfileName(ctx context.Context, name string, of
}

func (dc DeviceClient) DevicesByServiceName(ctx context.Context, name string, offset int, limit int) (res responses.MultiDevicesResponse, err errors.EdgeX) {
requestPath := path.Join(common.ApiDeviceRoute, common.Service, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Service, common.Name, name)
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand Down
4 changes: 2 additions & 2 deletions clients/http/deviceprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (client *DeviceProfileClient) UpdateByYaml(ctx context.Context, yamlFilePat
// DeleteByName deletes the device profile by name
func (client *DeviceProfileClient) DeleteByName(ctx context.Context, name string) (dtoCommon.BaseResponse, errors.EdgeX) {
var response dtoCommon.BaseResponse
requestPath := path.Join(common.ApiDeviceProfileRoute, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceProfileRoute, common.Name, name)
err := utils.DeleteRequest(ctx, &response, client.baseUrl, requestPath, client.authInjector)
if err != nil {
return response, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -93,7 +93,7 @@ func (client *DeviceProfileClient) DeleteByName(ctx context.Context, name string

// DeviceProfileByName queries the device profile by name
func (client *DeviceProfileClient) DeviceProfileByName(ctx context.Context, name string) (res responses.DeviceProfileResponse, edgexError errors.EdgeX) {
requestPath := path.Join(common.ApiDeviceProfileRoute, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceProfileRoute, common.Name, name)
err := utils.GetRequest(ctx, &res, client.baseUrl, requestPath, nil, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
5 changes: 2 additions & 3 deletions clients/http/deviceservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package http
import (
"context"
"net/url"
"path"
"strconv"
"strings"

Expand Down Expand Up @@ -70,7 +69,7 @@ func (dsc DeviceServiceClient) AllDeviceServices(ctx context.Context, labels []s

func (dsc DeviceServiceClient) DeviceServiceByName(ctx context.Context, name string) (
res responses.DeviceServiceResponse, err errors.EdgeX) {
path := path.Join(common.ApiDeviceServiceRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiDeviceServiceRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, dsc.baseUrl, path, nil, dsc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -80,7 +79,7 @@ func (dsc DeviceServiceClient) DeviceServiceByName(ctx context.Context, name str

func (dsc DeviceServiceClient) DeleteByName(ctx context.Context, name string) (
res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiDeviceServiceRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiDeviceServiceRoute, common.Name, name)
err = utils.DeleteRequest(ctx, &res, dsc.baseUrl, path, dsc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
4 changes: 2 additions & 2 deletions clients/http/deviceservicecallback.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2020-2022 IOTech Ltd
// Copyright (C) 2020-2023 IOTech Ltd
// Copyright (C) 2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
Expand Down Expand Up @@ -97,7 +97,7 @@ func (client *deviceServiceCallbackClient) UpdateProvisionWatcherCallback(ctx co

func (client *deviceServiceCallbackClient) DeleteProvisionWatcherCallback(ctx context.Context, name string) (dtoCommon.BaseResponse, errors.EdgeX) {
var response dtoCommon.BaseResponse
requestPath := path.Join(common.ApiWatcherCallbackRoute, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiWatcherCallbackRoute, common.Name, name)
err := utils.DeleteRequest(ctx, &response, client.baseUrl, requestPath, client.authInjector)
if err != nil {
return response, errors.NewCommonEdgeXWrapper(err)
Expand Down
5 changes: 2 additions & 3 deletions clients/http/interval.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package http
import (
"context"
"net/url"
"path"
"strconv"

"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/http/utils"
Expand Down Expand Up @@ -70,7 +69,7 @@ func (client IntervalClient) AllIntervals(ctx context.Context, offset int, limit
// IntervalByName query the interval by name
func (client IntervalClient) IntervalByName(ctx context.Context, name string) (
res responses.IntervalResponse, err errors.EdgeX) {
path := path.Join(common.ApiIntervalRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiIntervalRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, client.baseUrl, path, nil, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -81,7 +80,7 @@ func (client IntervalClient) IntervalByName(ctx context.Context, name string) (
// DeleteIntervalByName delete the interval by name
func (client IntervalClient) DeleteIntervalByName(ctx context.Context, name string) (
res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiIntervalRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiIntervalRoute, common.Name, name)
err = utils.DeleteRequest(ctx, &res, client.baseUrl, path, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
5 changes: 2 additions & 3 deletions clients/http/intervalaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package http
import (
"context"
"net/url"
"path"
"strconv"

"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/http/utils"
Expand Down Expand Up @@ -70,7 +69,7 @@ func (client IntervalActionClient) AllIntervalActions(ctx context.Context, offse
// IntervalActionByName query the intervalAction by name
func (client IntervalActionClient) IntervalActionByName(ctx context.Context, name string) (
res responses.IntervalActionResponse, err errors.EdgeX) {
path := path.Join(common.ApiIntervalActionRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiIntervalActionRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, client.baseUrl, path, nil, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -81,7 +80,7 @@ func (client IntervalActionClient) IntervalActionByName(ctx context.Context, nam
// DeleteIntervalActionByName delete the intervalAction by name
func (client IntervalActionClient) DeleteIntervalActionByName(ctx context.Context, name string) (
res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiIntervalActionRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiIntervalActionRoute, common.Name, name)
err = utils.DeleteRequest(ctx, &res, client.baseUrl, path, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
9 changes: 4 additions & 5 deletions clients/http/provisionwatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package http
import (
"context"
"net/url"
"path"
"strconv"
"strings"

Expand Down Expand Up @@ -69,7 +68,7 @@ func (pwc ProvisionWatcherClient) AllProvisionWatchers(ctx context.Context, labe
}

func (pwc ProvisionWatcherClient) ProvisionWatcherByName(ctx context.Context, name string) (res responses.ProvisionWatcherResponse, err errors.EdgeX) {
path := path.Join(common.ApiProvisionWatcherRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiProvisionWatcherRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, pwc.baseUrl, path, nil, pwc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -79,7 +78,7 @@ func (pwc ProvisionWatcherClient) ProvisionWatcherByName(ctx context.Context, na
}

func (pwc ProvisionWatcherClient) DeleteProvisionWatcherByName(ctx context.Context, name string) (res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiProvisionWatcherRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiProvisionWatcherRoute, common.Name, name)
err = utils.DeleteRequest(ctx, &res, pwc.baseUrl, path, pwc.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -89,7 +88,7 @@ func (pwc ProvisionWatcherClient) DeleteProvisionWatcherByName(ctx context.Conte
}

func (pwc ProvisionWatcherClient) ProvisionWatchersByProfileName(ctx context.Context, name string, offset int, limit int) (res responses.MultiProvisionWatchersResponse, err errors.EdgeX) {
requestPath := path.Join(common.ApiProvisionWatcherRoute, common.Profile, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiProvisionWatcherRoute, common.Profile, common.Name, name)
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand All @@ -102,7 +101,7 @@ func (pwc ProvisionWatcherClient) ProvisionWatchersByProfileName(ctx context.Con
}

func (pwc ProvisionWatcherClient) ProvisionWatchersByServiceName(ctx context.Context, name string, offset int, limit int) (res responses.MultiProvisionWatchersResponse, err errors.EdgeX) {
requestPath := path.Join(common.ApiProvisionWatcherRoute, common.Service, common.Name, name)
requestPath := utils.EscapeAndJoinPath(common.ApiProvisionWatcherRoute, common.Service, common.Name, name)
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand Down
4 changes: 2 additions & 2 deletions clients/http/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (client *SubscriptionClient) SubscriptionsByReceiver(ctx context.Context, r

// SubscriptionByName query subscription by name.
func (client *SubscriptionClient) SubscriptionByName(ctx context.Context, name string) (res responses.SubscriptionResponse, err errors.EdgeX) {
path := path.Join(common.ApiSubscriptionRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiSubscriptionRoute, common.Name, name)
err = utils.GetRequest(ctx, &res, client.baseUrl, path, nil, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -115,7 +115,7 @@ func (client *SubscriptionClient) SubscriptionByName(ctx context.Context, name s

// DeleteSubscriptionByName deletes a subscription by name.
func (client *SubscriptionClient) DeleteSubscriptionByName(ctx context.Context, name string) (res dtoCommon.BaseResponse, err errors.EdgeX) {
path := path.Join(common.ApiSubscriptionRoute, common.Name, name)
path := utils.EscapeAndJoinPath(common.ApiSubscriptionRoute, common.Name, name)
err = utils.DeleteRequest(ctx, &res, client.baseUrl, path, client.authInjector)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
3 changes: 2 additions & 1 deletion common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ func BuildTopic(parts ...string) string {
// URLEncode encodes the input string with additional common character support
func URLEncode(s string) string {
res := url.PathEscape(s)
res = strings.Replace(res, "+", "%2B", -1) // MQTT topic reserved char
res = strings.Replace(res, "-", "%2D", -1)
res = strings.Replace(res, ".", "%2E", -1)
res = strings.Replace(res, ".", "%2E", -1) // RegexCmd and Redis topic reserved char
res = strings.Replace(res, "_", "%5F", -1)
res = strings.Replace(res, "~", "%7E", -1)

Expand Down
21 changes: 11 additions & 10 deletions common/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@ import (
"net/url"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestURLEncode(t *testing.T) {
tests := []struct {
name string
input string
output string
EqualsURLPackage bool
name string
input string
output string
}{
{"valid", "^[this]+{is}?test:string*#", "%5E%5Bthis%5D+%7Bis%7D%3Ftest:string%2A%23", true},
{"valid - special character", "this-is_test.string~", "this%2Dis%5Ftest%2Estring%7E", false},
{"valid", "^[this]+{is}?test:string*#", "%5E%5Bthis%5D%2B%7Bis%7D%3Ftest:string%2A%23"},
{"valid - special character", "this-is_test.string~哈囉世界< >/!#%^*()+,`@$&", "this%2Dis%5Ftest%2Estring%7E%E5%93%88%E5%9B%89%E4%B8%96%E7%95%8C%3C%20%3E%2F%21%23%25%5E%2A%28%29%2B%2C%60@$&"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := URLEncode(tt.input)
require.Equal(t, tt.output, res)
if tt.EqualsURLPackage {
require.Equal(t, url.PathEscape(tt.input), res)
}
assert.Equal(t, tt.output, res)

unescaped, err := url.PathUnescape(tt.output)
require.NoError(t, err)
assert.Equal(t, tt.input, unescaped)
})
}
}
4 changes: 2 additions & 2 deletions dtos/corecommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
package dtos

type DeviceCoreCommand struct {
DeviceName string `json:"deviceName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
ProfileName string `json:"profileName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
DeviceName string `json:"deviceName" validate:"required,edgex-dto-none-empty-string"`
ProfileName string `json:"profileName" validate:"required,edgex-dto-none-empty-string"`
CoreCommands []CoreCommand `json:"coreCommands,omitempty" validate:"dive"`
}

Expand Down
12 changes: 6 additions & 6 deletions dtos/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import (
type Device struct {
DBTimestamp `json:",inline"`
Id string `json:"id,omitempty" yaml:"id,omitempty" validate:"omitempty,uuid"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
AdminState string `json:"adminState" yaml:"adminState" validate:"oneof='LOCKED' 'UNLOCKED'"`
OperatingState string `json:"operatingState" yaml:"operatingState" validate:"oneof='UP' 'DOWN' 'UNKNOWN'"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
Location interface{} `json:"location,omitempty" yaml:"location,omitempty"`
ServiceName string `json:"serviceName" yaml:"serviceName" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
ProfileName string `json:"profileName" yaml:"profileName" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
ServiceName string `json:"serviceName" yaml:"serviceName" validate:"required,edgex-dto-none-empty-string"`
ProfileName string `json:"profileName" yaml:"profileName" validate:"required,edgex-dto-none-empty-string"`
AutoEvents []AutoEvent `json:"autoEvents,omitempty" yaml:"autoEvents,omitempty" validate:"dive"`
Protocols map[string]ProtocolProperties `json:"protocols" yaml:"protocols" validate:"required,gt=0"`
Tags map[string]any `json:"tags,omitempty" yaml:"tags,omitempty"`
Expand All @@ -28,12 +28,12 @@ type Device struct {

type UpdateDevice struct {
Id *string `json:"id" validate:"required_without=Name,edgex-dto-uuid"`
Name *string `json:"name" validate:"required_without=Id,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name *string `json:"name" validate:"required_without=Id,edgex-dto-none-empty-string"`
Description *string `json:"description" validate:"omitempty"`
AdminState *string `json:"adminState" validate:"omitempty,oneof='LOCKED' 'UNLOCKED'"`
OperatingState *string `json:"operatingState" validate:"omitempty,oneof='UP' 'DOWN' 'UNKNOWN'"`
ServiceName *string `json:"serviceName" validate:"omitempty,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
ProfileName *string `json:"profileName" validate:"omitempty,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
ServiceName *string `json:"serviceName" validate:"omitempty,edgex-dto-none-empty-string"`
ProfileName *string `json:"profileName" validate:"omitempty,edgex-dto-none-empty-string"`
Labels []string `json:"labels"`
Location interface{} `json:"location"`
AutoEvents []AutoEvent `json:"autoEvents" validate:"dive"`
Expand Down
4 changes: 2 additions & 2 deletions dtos/deviceprofilebasicinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package dtos

type DeviceProfileBasicInfo struct {
Id string `json:"id" validate:"omitempty,uuid"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string"`
Manufacturer string `json:"manufacturer" yaml:"manufacturer"`
Description string `json:"description" yaml:"description"`
Model string `json:"model" yaml:"model"`
Expand All @@ -16,7 +16,7 @@ type DeviceProfileBasicInfo struct {

type UpdateDeviceProfileBasicInfo struct {
Id *string `json:"id" validate:"required_without=Name,edgex-dto-uuid"`
Name *string `json:"name" validate:"required_without=Id,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name *string `json:"name" validate:"required_without=Id,edgex-dto-none-empty-string"`
Manufacturer *string `json:"manufacturer"`
Description *string `json:"description"`
Model *string `json:"model"`
Expand Down
Loading

0 comments on commit 78f9b3a

Please sign in to comment.