Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
7 changes: 3 additions & 4 deletions eng/pipelines/templates/steps/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ steps:
displayName: "Install Coverage and Junit Dependencies"

- ${{ if eq(parameters.TestProxy, true) }}:
- template: /eng/common/testproxy/test-proxy-docker.yml
- template: /eng/common/testproxy/test-proxy-tool.yml

- task: PowerShell@2
displayName: 'Run Tests'
Expand All @@ -60,9 +60,8 @@ steps:

- ${{ if eq(parameters.TestProxy, true) }}:
- pwsh: |
# ambitious_azsdk_test_proxy is the hardcoded container name used
# by the test proxy startup script
docker logs ambitious_azsdk_test_proxy
# $(Build.SourcesDirectory)/test-proxy.log is the hardcoded output log location for the test-proxy-tool.yml
cat $(Build.SourcesDirectory)/test-proxy.log
displayName: 'Dump Test Proxy logs'
condition: succeededOrFailed()

Expand Down
2 changes: 2 additions & 0 deletions sdk/azidentity/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/Azure/azure-sdk-for-go/sdk/azidentity

go 1.16

replace github.com/Azure/azure-sdk-for-go/sdk/internal => ../internal
Comment thread
seankane-msft marked this conversation as resolved.

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.0
Expand Down
3 changes: 0 additions & 3 deletions sdk/azidentity/go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0 h1:8wVJL0HUP5yDFXvotdewORTw7Yu88JbreWN/mobSvsQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.0 h1:HMbyI+KfvL+XyuWekow/nWbRxsAhB6+DVzgQTIABecU=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.0/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
26 changes: 11 additions & 15 deletions sdk/azidentity/live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package azidentity
import (
"context"
"fmt"
"log"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -89,21 +88,10 @@ func init() {
func TestMain(m *testing.M) {
switch recording.GetRecordMode() {
case recording.PlaybackMode:
// enable BodilessMatcher because we don't record request bodies
// TODO: add an API for this to sdk/internal
req, err := http.NewRequest("POST", "http://localhost:5000/Admin/SetMatcher", http.NoBody)
err := recording.SetBodilessMatcher(nil, nil)
if err != nil {
panic(err)
}
req.Header["x-abstraction-identifier"] = []string{"BodilessMatcher"}
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
log.Panicf("failed to enable BodilessMatcher: %v", res)
}
// TODO: reset matcher
Comment thread
seankane-msft marked this conversation as resolved.
case recording.RecordingMode:
// remove default sanitizers such as the OAuth response sanitizer
err := recording.ResetProxy(nil)
Expand Down Expand Up @@ -155,10 +143,18 @@ func TestMain(m *testing.M) {
if err != nil {
panic(err)
}
// TODO: reset matcher
}()
}
os.Exit(m.Run())
val := m.Run()

if recording.GetRecordMode() == recording.PlaybackMode || recording.GetRecordMode() == recording.RecordingMode {
err := recording.SetBodilessMatcher(nil, nil)
if err != nil {
panic(err)
}
}

os.Exit(val)
}

func initRecording(t *testing.T) (policy.ClientOptions, func()) {
Expand Down
2 changes: 2 additions & 0 deletions sdk/data/aztables/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/Azure/azure-sdk-for-go/sdk/data/aztables

go 1.16

replace github.com/Azure/azure-sdk-for-go/sdk/internal => ../../internal

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.0
Expand Down
3 changes: 0 additions & 3 deletions sdk/data/aztables/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0 h1:8wVJL0HUP5yDFXvotdewORTw
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.0 h1:bLRntPH25SkY1uZ/YZW+dmxNky9r1fAHvDFrzluo+4Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.0/go.mod h1:TmXReXZ9yPp5D5TBRMTAtyz+UyOl15Py4hL5E5p6igQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.0 h1:HMbyI+KfvL+XyuWekow/nWbRxsAhB6+DVzgQTIABecU=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.0/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
12 changes: 10 additions & 2 deletions sdk/data/aztables/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ import (

func TestMain(m *testing.M) {
// 1. Set up session level sanitizers
if recording.GetRecordMode() == "record" {
switch recording.GetRecordMode() {
case recording.PlaybackMode:
err := recording.SetDefaultMatcher(nil, &recording.SetDefaultMatcherOptions{
ExcludedHeaders: []string{":path", ":auth", ":method", ":scheme"},
})
if err != nil {
panic(err)
}
case recording.RecordingMode:
for _, val := range []string{"TABLES_COSMOS_ACCOUNT_NAME", "TABLES_STORAGE_ACCOUNT_NAME"} {
account, ok := os.LookupEnv(val)
if !ok {
Expand All @@ -34,8 +42,8 @@ func TestMain(m *testing.M) {
panic(err)
}
}
}

}
// Run tests
exitVal := m.Run()

Expand Down
1 change: 1 addition & 0 deletions sdk/internal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.9.1 (Unreleased)

### Features Added
* Adds a `CustomDefaultMatcher` that adds headers `:path`, `:authority`, `:method`, and `:scheme` to the

### Breaking Changes

Expand Down
83 changes: 76 additions & 7 deletions sdk/internal/recording/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
package recording

import (
"fmt"
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"testing"
)

Expand All @@ -19,24 +22,90 @@ type MatcherOptions struct {
// SetBodilessMatcher adjusts the "match" operation to exclude the body when matching a request to a recording's entries.
// Pass in `nil` for `t` if you want the bodiless matcher to apply everywhere
func SetBodilessMatcher(t *testing.T, options *MatcherOptions) error {
f := false
return SetDefaultMatcher(t, &SetDefaultMatcherOptions{
CompareBodies: &f,
})
}

type SetDefaultMatcherOptions struct {
CompareBodies *bool
ExcludedHeaders []string
IgnoredHeaders []string
IgnoreQueryOrdering *bool
}

func (s *SetDefaultMatcherOptions) fillOptions() {
f := false
t := true
if s == nil {
s = &SetDefaultMatcherOptions{
CompareBodies: &t,
IgnoreQueryOrdering: &f,
}
return
}

if s.CompareBodies == nil {
s.CompareBodies = &t
}
if s.IgnoreQueryOrdering == nil {
s.IgnoreQueryOrdering = &f
}
}

func addDefaults(added []string) []string {
if added == nil {
return nil
}
needToAdd := []string{":path", ":authority", ":method", ":scheme"}
for _, a := range added {
for idx, n := range needToAdd {
if a == n {
needToAdd = append(needToAdd[:idx], needToAdd[idx+1:]...)
}
}
}
return append(added, needToAdd...)
}

// SetDefaultMatcher adjusts the "match" operation to exclude the body when matching a request to a recording's entries.
// Pass in `nil` for `t` if you want the bodiless matcher to apply everywhere
func SetDefaultMatcher(t *testing.T, options *SetDefaultMatcherOptions) error {
if recordMode != PlaybackMode {
return nil
}
options.fillOptions()
req, err := http.NewRequest("POST", "http://localhost:5000/Admin/SetMatcher", http.NoBody)
if err != nil {
panic(err)
}
req.Header["x-abstraction-identifier"] = []string{"BodilessMatcher"}
req.Header["x-abstraction-identifier"] = []string{"CustomDefaultMatcher"}
if t != nil {
req.Header["x-recording-id"] = []string{GetRecordingId(t)}
}

res, err := http.DefaultClient.Do(req)
if !(*options.CompareBodies) {
options.ExcludedHeaders = append(options.ExcludedHeaders, "Content-Length")
}

marshalled, err := json.MarshalIndent(struct {
CompareBodies *bool `json:"compareBodies,omitempty"`
ExcludedHeaders string `json:"excludedHeaders,omitempty"`
IncludedHeaders string `json:"includedHeaders,omitempty"`
IgnoreQueryOrdering *bool `json:"ignoreQueryOrdering,omitempty"`
}{
CompareBodies: options.CompareBodies,
ExcludedHeaders: strings.Join(addDefaults(options.ExcludedHeaders), ","),
IncludedHeaders: strings.Join(options.IgnoredHeaders, ","),
IgnoreQueryOrdering: options.IgnoreQueryOrdering,
}, "", "")
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("failed to enable BodilessMatcher: %v", res)
}
return nil

req.Body = ioutil.NopCloser(bytes.NewReader(marshalled))
req.ContentLength = int64(len(marshalled))

return handleProxyResponse(client.Do(req))
}
60 changes: 60 additions & 0 deletions sdk/internal/recording/matchers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,63 @@ func TestSetBodilessMatcherNilTest(t *testing.T) {
err = ResetProxy(nil)
require.NoError(t, err)
}

func TestSetDefaultMatcher(t *testing.T) {
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()

err := Start(t, packagePath, nil)
require.NoError(t, err)

req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)

req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))

client, err := GetHTTPClient(t)
require.NoError(t, err)

_, err = client.Do(req)
require.NoError(t, err)

err = Stop(t, nil)
require.NoError(t, err)

// Run a second request to with different body to verify it works
recordMode = PlaybackMode

err = Start(t, packagePath, nil)
require.NoError(t, err)

err = SetDefaultMatcher(nil, &SetDefaultMatcherOptions{ExcludedHeaders: []string{"ExampleHeader"}})
require.NoError(t, err)

req, err = http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)

req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set("ExampleHeader", "blah-blah-blah")

err = handleProxyResponse(client.Do(req))
require.NoError(t, err)

err = Stop(t, nil)
require.NoError(t, err)

err = ResetProxy(nil)
require.NoError(t, err)
}

func TestAddDefaults(t *testing.T) {
require.Equal(t, 4, len(addDefaults([]string{})))
require.Equal(t, 4, len(addDefaults([]string{":path"})))
require.Equal(t, 4, len(addDefaults([]string{":path", ":authority"})))
require.Equal(t, 4, len(addDefaults([]string{":path", ":authority", ":method"})))
require.Equal(t, 4, len(addDefaults([]string{":path", ":authority", ":method", ":scheme"})))
require.Equal(t, 5, len(addDefaults([]string{":path", ":authority", ":method", ":scheme", "extra"})))
}
16 changes: 16 additions & 0 deletions sdk/internal/recording/recording.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,22 @@ func init() {
if ok := certPool.AppendCertsFromPEM(cert); !ok {
log.Println("no certs appended, using system certs only")
}

// Set a Default matcher that ignores :path, :scheme, :authority, and :method headers
err = SetDefaultMatcher(
nil,
&SetDefaultMatcherOptions{ExcludedHeaders: []string{
":authority",
":method",
":path",
":scheme",
}},
)
if err != nil {
log.Println("could not set the default matcher")
} else {
log.Println("default matcher was set ")
}
}

var recordMode string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Entries": [
{
"RequestUri": "https://bing.com/",
"RequestMethod": "POST",
"RequestHeaders": {
":method": "POST",
"Accept-Encoding": "gzip",
"Content-Length": "0",
"User-Agent": "Go-http-client/2.0"
},
"RequestBody": null,
"StatusCode": 301,
"ResponseHeaders": {
"Accept-CH": "Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version",
"Cache-Control": "private",
"Content-Encoding": "gzip",
"Content-Length": "2943",
"Content-Type": "text/html; charset=utf-8",
"Date": "Wed, 26 Jan 2022 21:26:25 GMT",
"Location": "https://www.bing.com/?toWww=1\u0026redig=36D16E58BC3C46DC988D7753868E4F82\u0026toWww=1\u0026redig=B3FED55D25074AD2B1EFEA57D4E28847\u0026toWww=1\u0026redig=3542BC2407354C2B8946B4C66FCED165\u0026toWww=1\u0026redig=100A2DEF35DB4995858E0B5307C88DEE\u0026toWww=1\u0026redig=EFC20904D23348E9BE09AE4995CFADC7\u0026toWww=1\u0026redig=3404832CF38E41A5A730D8785F5E236A\u0026toWww=1\u0026redig=386345D1C6C34575B78049D25AA9B32E\u0026toWww=1\u0026redig=AC7B616A21A24DD9B292D9C29F5E2241\u0026toWww=1\u0026redig=AECF00D83B654B6E87ADD5D907FF2D96\u0026toWww=1\u0026redig=4B8D6950EE54455DA622D092C2461852\u0026toWww=1\u0026redig=8B5DCF6002804730994D8A060780C298\u0026toWww=1\u0026redig=817EB78E5F40451EBB80DFF0D7D61704\u0026toWww=1\u0026redig=BA5002CA24E743CFA3A5BE316105D264\u0026toWww=1\u0026redig=6048082DF9B44EBABF759817297CC027\u0026toWww=1\u0026redig=67A6F168987F47E481818E524A7FD608\u0026toWww=1\u0026redig=D57719045309432697D23569DA7781A4\u0026toWww=1\u0026redig=D7D685405D994F57A643BD7C7792068B\u0026toWww=1\u0026redig=4060BD3DCE9E41FC9C269D83601E5E84\u0026toWww=1\u0026redig=C0F5F7A3C76F452092438DE5B92CC49D\u0026toWww=1\u0026redig=4C88C00D3E3A4DD7BB483CBFA467146F\u0026toWww=1\u0026redig=605B790A04784A6A96C8AB3142222802\u0026toWww=1\u0026redig=0D9535924D3A41C8827AA3A26596A132\u0026toWww=1\u0026redig=EC9078B10F1449108D38D4E256414B30\u0026toWww=1\u0026redig=02614D8BEF064BF8822DEF7D5BC0F2BD\u0026toWww=1\u0026redig=A8C611933FF04D7384190EEB5B1BDF07\u0026toWww=1\u0026redig=96D2DFA70F2C4C1A99659C0252CEE07B\u0026toWww=1\u0026redig=83742D119D4244F99CE9C424AB265F36\u0026toWww=1\u0026redig=5A9F37CEB0744CAABCED6CED250F04CA\u0026toWww=1\u0026redig=461DA675C2554DE9A6737218E19DDDE9\u0026toWww=1\u0026redig=253A116BECEA4B7E9D6ADA85F9A7A3BE\u0026toWww=1\u0026redig=F7AD3071B1C745829B3E3630DCE477A6\u0026toWww=1\u0026redig=04D55828E7B44B07B697668840CD9DD6\u0026toWww=1\u0026redig=3424EE37C1DF423A8621C03918BFD456\u0026toWww=1\u0026redig=A33B381D37094F598285EBFD97A03327\u0026toWww=1\u0026redig=024BCA4BF7034FB9906CEB7D7AC0D5AA\u0026toWww=1\u0026redig=B03235A52A4B47EF9DCDDACD8D5606D8\u0026toWww=1\u0026redig=2279AF2C63A842B5BC66B84F06F46A61\u0026toWww=1\u0026redig=BD11B17E000640A7A37F0A208052A635\u0026toWww=1\u0026redig=068B2075D3E2489CADDACD43BE952D47\u0026toWww=1\u0026redig=05A9BCBE75FC4A2DBFC763FB5F4196E1\u0026toWww=1\u0026redig=6EBF752C32D04B5FB9BF092C3A8834DC\u0026toWww=1\u0026redig=6D64BBE8F50F4D4E9434A52526DA8C3C\u0026toWww=1\u0026redig=439989E7C6324EC7B2058EF5CDEC1F96\u0026toWww=1\u0026redig=926E3EA38A7D4A8CB7274D9D96271E25\u0026toWww=1\u0026redig=B2741CADB7F940B28B2549A1F86B67C1\u0026toWww=1\u0026redig=4F8659EAF2644F3CBDD103FAE7B8409F\u0026toWww=1\u0026redig=0C86A2B69CF3488585D23537BFC672C9\u0026toWww=1\u0026redig=62FDEE14D5FE48E7A2D63C2441AB72BF\u0026toWww=1\u0026redig=6EE10DB5ABC94C8B81A978780B04F4B0\u0026toWww=1\u0026redig=C77184D416F442A489B2610E53338EB0\u0026toWww=1\u0026redig=DC3E29A039B1494FA6D4D3E2917CBDA1",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"Vary": "Accept-Encoding",
"X-Cache": "CONFIG_NOCACHE",
"X-MSEdge-Ref": "Ref A: AC0B87B220834185ABC2A50A2F0B3247 Ref B: ASHEDGE1313 Ref C: 2022-01-26T21:26:26Z",
"X-SNR-Routing": "1"
},
"ResponseBody": [
"\u003Chtml\u003E\u003Chead\u003E\u003Ctitle\u003EObject moved\u003C/title\u003E\u003C/head\u003E\u003Cbody\u003E\r\n",
"\u003Ch2\u003EObject moved to \u003Ca href=\u0022https://www.bing.com:443/?toWww=1\u0026amp;redig=36D16E58BC3C46DC988D7753868E4F82\u0026amp;toWww=1\u0026amp;redig=B3FED55D25074AD2B1EFEA57D4E28847\u0026amp;toWww=1\u0026amp;redig=3542BC2407354C2B8946B4C66FCED165\u0026amp;toWww=1\u0026amp;redig=100A2DEF35DB4995858E0B5307C88DEE\u0026amp;toWww=1\u0026amp;redig=EFC20904D23348E9BE09AE4995CFADC7\u0026amp;toWww=1\u0026amp;redig=3404832CF38E41A5A730D8785F5E236A\u0026amp;toWww=1\u0026amp;redig=386345D1C6C34575B78049D25AA9B32E\u0026amp;toWww=1\u0026amp;redig=AC7B616A21A24DD9B292D9C29F5E2241\u0026amp;toWww=1\u0026amp;redig=AECF00D83B654B6E87ADD5D907FF2D96\u0026amp;toWww=1\u0026amp;redig=4B8D6950EE54455DA622D092C2461852\u0026amp;toWww=1\u0026amp;redig=8B5DCF6002804730994D8A060780C298\u0026amp;toWww=1\u0026amp;redig=817EB78E5F40451EBB80DFF0D7D61704\u0026amp;toWww=1\u0026amp;redig=BA5002CA24E743CFA3A5BE316105D264\u0026amp;toWww=1\u0026amp;redig=6048082DF9B44EBABF759817297CC027\u0026amp;toWww=1\u0026amp;redig=67A6F168987F47E481818E524A7FD608\u0026amp;toWww=1\u0026amp;redig=D57719045309432697D23569DA7781A4\u0026amp;toWww=1\u0026amp;redig=D7D685405D994F57A643BD7C7792068B\u0026amp;toWww=1\u0026amp;redig=4060BD3DCE9E41FC9C269D83601E5E84\u0026amp;toWww=1\u0026amp;redig=C0F5F7A3C76F452092438DE5B92CC49D\u0026amp;toWww=1\u0026amp;redig=4C88C00D3E3A4DD7BB483CBFA467146F\u0026amp;toWww=1\u0026amp;redig=605B790A04784A6A96C8AB3142222802\u0026amp;toWww=1\u0026amp;redig=0D9535924D3A41C8827AA3A26596A132\u0026amp;toWww=1\u0026amp;redig=EC9078B10F1449108D38D4E256414B30\u0026amp;toWww=1\u0026amp;redig=02614D8BEF064BF8822DEF7D5BC0F2BD\u0026amp;toWww=1\u0026amp;redig=A8C611933FF04D7384190EEB5B1BDF07\u0026amp;toWww=1\u0026amp;redig=96D2DFA70F2C4C1A99659C0252CEE07B\u0026amp;toWww=1\u0026amp;redig=83742D119D4244F99CE9C424AB265F36\u0026amp;toWww=1\u0026amp;redig=5A9F37CEB0744CAABCED6CED250F04CA\u0026amp;toWww=1\u0026amp;redig=461DA675C2554DE9A6737218E19DDDE9\u0026amp;toWww=1\u0026amp;redig=253A116BECEA4B7E9D6ADA85F9A7A3BE\u0026amp;toWww=1\u0026amp;redig=F7AD3071B1C745829B3E3630DCE477A6\u0026amp;toWww=1\u0026amp;redig=04D55828E7B44B07B697668840CD9DD6\u0026amp;toWww=1\u0026amp;redig=3424EE37C1DF423A8621C03918BFD456\u0026amp;toWww=1\u0026amp;redig=A33B381D37094F598285EBFD97A03327\u0026amp;toWww=1\u0026amp;redig=024BCA4BF7034FB9906CEB7D7AC0D5AA\u0026amp;toWww=1\u0026amp;redig=B03235A52A4B47EF9DCDDACD8D5606D8\u0026amp;toWww=1\u0026amp;redig=2279AF2C63A842B5BC66B84F06F46A61\u0026amp;toWww=1\u0026amp;redig=BD11B17E000640A7A37F0A208052A635\u0026amp;toWww=1\u0026amp;redig=068B2075D3E2489CADDACD43BE952D47\u0026amp;toWww=1\u0026amp;redig=05A9BCBE75FC4A2DBFC763FB5F4196E1\u0026amp;toWww=1\u0026amp;redig=6EBF752C32D04B5FB9BF092C3A8834DC\u0026amp;toWww=1\u0026amp;redig=6D64BBE8F50F4D4E9434A52526DA8C3C\u0026amp;toWww=1\u0026amp;redig=439989E7C6324EC7B2058EF5CDEC1F96\u0026amp;toWww=1\u0026amp;redig=926E3EA38A7D4A8CB7274D9D96271E25\u0026amp;toWww=1\u0026amp;redig=B2741CADB7F940B28B2549A1F86B67C1\u0026amp;toWww=1\u0026amp;redig=4F8659EAF2644F3CBDD103FAE7B8409F\u0026amp;toWww=1\u0026amp;redig=0C86A2B69CF3488585D23537BFC672C9\u0026amp;toWww=1\u0026amp;redig=62FDEE14D5FE48E7A2D63C2441AB72BF\u0026amp;toWww=1\u0026amp;redig=6EE10DB5ABC94C8B81A978780B04F4B0\u0026amp;toWww=1\u0026amp;redig=C77184D416F442A489B2610E53338EB0\u0026amp;toWww=1\u0026amp;redig=DC3E29A039B1494FA6D4D3E2917CBDA1\u0022\u003Ehere\u003C/a\u003E.\u003C/h2\u003E\r\n",
"\u003C/body\u003E\u003C/html\u003E\r\n"
]
}
],
"Variables": {}
}