Repository: traefik/traefik (language: go)

Issue:
Config array plugin bug in 2.6.2?
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.containo.us) and didn't find any.

### What did you do?

Ref: https://github.com/BetterCorp/cloudflarewarp/issues/1 

It seems from v2.6.2 config arrays for plugins aren't being parsed correctly.

```
    cloudflarewarp:  
      plugin:  
        traefik-cf-warp:  
          trustip:  
            - "172.18.0.0/16"
            - "173.245.48.0/20"  
```    

Plugin Config definition:  
```
type Config struct {
	TrustIP []string `json:"trustip,omitempty" toml:"trustip,omitempty" yaml:"trustip,omitempty"`
}
```

Tests that specifically pass in the array work - https://github.com/BetterCorp/cloudflarewarp/blob/master/cloudflarewarp_test.go#L14 

### What did you see instead?

Instead of being array, it seems to be interpreted as a single string formatted by `||`  

Eg error: `invalid CIDR address: 172.18.0.0/16║173.245.48.0/20`



### What version of Traefik are you using?

2.6.2

### What is your environment & configuration?

```yaml
http:
  routers:
    my-router:
      rule: Path(`/whoami`)
      service: service-whoami
      entryPoints:
        - http
      middlewares:
        - cloudflarewarp

  services:
   service-whoami:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:5000
  middlewares:  
    cloudflarewarp:  
      plugin:  
        traefik-cf-warp:  
          trustip:  
            # Internal Docker "proxy" network  
            - "172.18.0.0/16"  
            # CF Proxy IPs: https://www.cloudflare.com/ips  
            - "173.245.48.0/20"  
            - "103.21.244.0/22"  
            - "103.22.200.0/22"  
            - "103.31.4.0/22"  
            - "141.101.64.0/18"  
            - "108.162.192.0/18"  
            - "190.93.240.0/20"  
            - "188.114.96.0/20"  
            - "197.234.240.0/22"  
            - "198.41.128.0/17"  
            - "162.158.0.0/15"  
            - "104.16.0.0/13"  
            - "104.24.0.0/14"  
            - "172.64.0.0/13"  
            - "131.0.72.0/22"  
```

Add more configuration information here.


### If applicable, please paste the log output in DEBUG level

```
time="2022-03-27T00:51:13+01:00" level=error msg="invalid CIDR address: 172.18.0.0/16║173.245.48.0/20" entryPointName=websecure routerName=tasmoadmin@file  
time="2022-03-27T00:51:13+01:00" level=error msg="invalid CIDR address: 172.18.0.0/16║173.245.48.0/20" routerName=proxmox2@file entryPointName=websecure  
time="2022-03-27T00:51:13+01:00" level=error msg="invalid CIDR address: 172.18.0.0/16║173.245.48.0/20" entryPointName=websecure routerName=youtrack@file  
```


Discussion / hints:
I too have noticed that with 2.6.2 (docker.io).
2.6.1 is unaffected, tho.
```YAML
http:
  middlewares:
    local-whitelist:
      ipwhitelist:
        sourcerange: 172.16.0.0/12, 10.0.0.0/8
  routers:
    apache:
      entrypoints: websecure
      rule: Host(`XXXXXXXX`)
      middlewares: local-whitelist, security-headers
      tls:
        certResolver: dnsChallenger
      service: apache
```
```log
time="2022-03-26T21:45:06+01:00" level=error msg="cannot parse CIDR whitelist [172.16.0.0/12, 10.0.0.0/8]: parsing CIDR trusted IPs <nil>: invalid CIDR address: 172.16.0.0/12, 10.0.0.0/8" entryPointName=websecure routerName=nginx@docker
time="2022-03-26T21:45:06+01:00" level=error msg="cannot parse CIDR whitelist [172.16.0.0/12, 10.0.0.0/8]: parsing CIDR trusted IPs <nil>: invalid CIDR address: 172.16.0.0/12, 10.0.0.0/8" entryPointName=websecure routerName=traefik-webui@docker
time="2022-03-26T21:45:06+01:00" level=error msg="middleware \"local-whitelist, security-headers\" does not exist" routerName=apache entryPointName=websecure
```

Fix patch:
diff --git a/pkg/plugins/middlewares.go b/pkg/plugins/middlewares.go
index 09edbc9543..8e476cce3d 100644
--- a/pkg/plugins/middlewares.go
+++ b/pkg/plugins/middlewares.go
@@ -86,7 +86,7 @@ func (p middlewareBuilder) createConfig(config map[string]interface{}) (reflect.
 	vConfig := results[0]
 
 	cfg := &mapstructure.DecoderConfig{
-		DecodeHook:       mapstructure.StringToSliceHookFunc(","),
+		DecodeHook:       stringToSliceHookFunc,
 		WeaklyTypedInput: true,
 		Result:           vConfig.Interface(),
 	}
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
index fdfb9fbcea..63b513ae82 100644
--- a/pkg/plugins/plugins.go
+++ b/pkg/plugins/plugins.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"reflect"
 	"strings"
 
 	"github.com/hashicorp/go-multierror"
@@ -166,3 +167,26 @@ func checkLocalPluginManifest(descriptor LocalDescriptor) error {
 
 	return errs.ErrorOrNil()
 }
+
+func stringToSliceHookFunc(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) {
+	if f != reflect.String || t != reflect.Slice {
+		return data, nil
+	}
+
+	raw := data.(string)
+	if raw == "" {
+		return []string{}, nil
+	}
+
+	if strings.Contains(raw, "║") {
+		values := strings.Split(raw, "║")
+		// Removes the first value if the slice has a length of 2 and a first value empty.
+		// It's a workaround to escape the parsing on `,`.
+		if len(values) == 2 && values[0] == "" {
+			return values[1:], nil
+		}
+		return values, nil
+	}
+
+	return strings.Split(raw, ","), nil
+}
diff --git a/pkg/plugins/providers.go b/pkg/plugins/providers.go
index abec8d1b82..ac9aa296f1 100644
--- a/pkg/plugins/providers.go
+++ b/pkg/plugins/providers.go
@@ -93,7 +93,7 @@ func newProvider(builder providerBuilder, config map[string]interface{}, provide
 	}
 
 	cfg := &mapstructure.DecoderConfig{
-		DecodeHook:       mapstructure.StringToSliceHookFunc(","),
+		DecodeHook:       stringToSliceHookFunc,
 		WeaklyTypedInput: true,
 		Result:           vConfig.Interface(),
 	}


Test patch:
diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go
new file mode 100644
index 0000000000..57d3d32354
--- /dev/null
+++ b/pkg/plugins/plugins_test.go
@@ -0,0 +1,60 @@
+package plugins
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_stringToSliceHookFunc(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		data     string
+		expected []string
+	}{
+		{
+			desc:     "without separator",
+			data:     "abc",
+			expected: []string{"abc"},
+		},
+		{
+			desc:     "with the file separator",
+			data:     "a║b║c",
+			expected: []string{"a", "b", "c"},
+		},
+		{
+			desc:     "with the label separator",
+			data:     "a,b,c",
+			expected: []string{"a", "b", "c"},
+		},
+		{
+			desc:     "with the file separator and values with commas",
+			data:     "a,z║b,w║c,x,y",
+			expected: []string{"a,z", "b,w", "c,x,y"},
+		},
+		{
+			desc:     "escaping workaround",
+			data:     "║a,z",
+			expected: []string{"a,z"},
+		},
+		{
+			desc:     "with the file separator and empty item",
+			data:     "║a║z",
+			expected: []string{"", "a", "z"},
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			values, err := stringToSliceHookFunc(reflect.String, reflect.Slice, test.data)
+			require.NoError(t, err)
+
+			assert.EqualValues(t, test.expected, values)
+		})
+	}
+}