func (c Conn) Write(b []byte) (int, error) {
	for key := range c.Headers {
		b = bytes.Replace(b, []byte(http.CanonicalHeaderKey(key)+":"), []byte(key+":"), 1)
	}
	return c.Conn.Write(b)
}
Write implements Conn.Write.
package hhfm
import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"sort"
	"time"
	"github.com/ooni/probe-engine/experiment/urlgetter"
	"github.com/ooni/probe-engine/internal/httpheader"
	"github.com/ooni/probe-engine/internal/randx"
	"github.com/ooni/probe-engine/model"
	"github.com/ooni/probe-engine/netx"
	"github.com/ooni/probe-engine/netx/archival"
	"github.com/ooni/probe-engine/netx/errorx"
	"github.com/ooni/probe-engine/netx/selfcensor"
)
const (
	testName    = "http_header_field_manipulation"
	testVersion = "0.2.0"
)
type Config struct{}
type TestKeys struct {
	Agent      string                  `json:"agent"`
	Failure    *string                 `json:"failure"`
	Requests   []archival.RequestEntry `json:"requests"`
	SOCKSProxy *string                 `json:"socksproxy"`
	Tampering  Tampering               `json:"tampering"`
}
type Tampering struct {
	HeaderFieldName           bool     `json:"header_field_name"`
	HeaderFieldNumber         bool     `json:"header_field_number"`
	HeaderFieldValue          bool     `json:"header_field_value"`
	HeaderNameCapitalization  bool     `json:"header_name_capitalization"`
	HeaderNameDiff            []string `json:"header_name_diff"`
	RequestLineCapitalization bool     `json:"request_line_capitalization"`
	Total                     bool     `json:"total"`
}
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
	return Measurer{Config: config}
}
type Transport interface {
	RoundTrip(req *http.Request) (*http.Response, error)
	CloseIdleConnections()
}
type Measurer struct {
	Config    Config
	Transport Transport  
}
func (m Measurer) ExperimentName() string {
	return testName
}
func (m Measurer) ExperimentVersion() string {
	return testVersion
}
var (
	ErrNoAvailableTestHelpers = errors.New("no available helpers")
	ErrInvalidHelperType = errors.New("invalid helper type")
)
func (m Measurer) Run(
	ctx context.Context, sess model.ExperimentSession,
	measurement *model.Measurement, callbacks model.ExperimentCallbacks,
) error {
	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()
	urlgetter.RegisterExtensions(measurement)
	tk := new(TestKeys)
	tk.Agent = "agent"
	tk.Tampering.HeaderNameDiff = []string{}
	measurement.TestKeys = tk
	const helperName = "http-return-json-headers"
	helpers, ok := sess.GetTestHelpersByName(helperName)
	if !ok || len(helpers) < 1 {
		return ErrNoAvailableTestHelpers
	}
	helper := helpers[0]
	if helper.Type != "legacy" {
		return ErrInvalidHelperType
	}
	measurement.TestHelpers = map[string]interface{}{
		"backend": helper.Address,
	}
	req, err := http.NewRequest("GeT", helper.Address, nil)
	if err != nil {
		return err
	}
	headers := map[string]string{
		randx.ChangeCapitalization("Accept"):          httpheader.Accept(),
		randx.ChangeCapitalization("Accept-Charset"):  "ISO-8859-1,utf-8;q=0.7,*;q=0.3",
		randx.ChangeCapitalization("Accept-Encoding"): "gzip,deflate,sdch",
		randx.ChangeCapitalization("Accept-Language"): httpheader.AcceptLanguage(),
		randx.ChangeCapitalization("Host"):            randx.Letters(15) + ".com",
		randx.ChangeCapitalization("User-Agent"):      httpheader.UserAgent(),
	}
	for key, value := range headers {
		req.Header.Set(key, value)
	}
	req.Host = req.Header.Get("Host")
	tk.Requests = NewRequestEntryList(req, headers)
	txp := m.Transport
	if txp == nil {
		ht := http.DefaultTransport.(*http.Transport).Clone()  
		ht.DisableCompression = true                           
		ht.ForceAttemptHTTP2 = false
		ht.DialContext = Dialer{Headers: headers}.DialContext
		txp = ht
	}
	defer txp.CloseIdleConnections()
	resp, data, err := Transact(txp, req.WithContext(ctx), callbacks)
	if err != nil {
		tk.Failure = archival.NewFailure(err)
		tk.Requests[0].Failure = tk.Failure
		tk.Tampering.Total = true
		return nil  
	}
	tk.Requests[0].Response = NewHTTPResponse(resp, data)
	var jsonHeaders JSONHeaders
	if err := json.Unmarshal(data, &jsonHeaders); err != nil {
		failure := errorx.FailureJSONParseError
		tk.Failure = &failure
		tk.Tampering.Total = true
		return nil  
	}
	tk.FillTampering(req, jsonHeaders, headers)
	return nil
}
func Transact(txp Transport, req *http.Request,
	callbacks model.ExperimentCallbacks) (*http.Response, []byte, error) {
	resp, data, err := transact(txp, req, callbacks)
	err = errorx.SafeErrWrapperBuilder{
		Error: err, Operation: errorx.TopLevelOperation}.MaybeBuild()
	return resp, data, err
}
func transact(txp Transport, req *http.Request,
	callbacks model.ExperimentCallbacks) (*http.Response, []byte, error) {
	callbacks.OnProgress(0.25, "sending request...")
	resp, err := txp.RoundTrip(req)
	callbacks.OnProgress(0.50, fmt.Sprintf("got reseponse headers... %+v", err))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, nil, urlgetter.ErrHTTPRequestFailed
	}
	callbacks.OnProgress(0.75, "reading response body...")
	data, err := ioutil.ReadAll(resp.Body)
	callbacks.OnProgress(1.00, fmt.Sprintf("got reseponse body... %+v", err))
	if err != nil {
		return nil, nil, err
	}
	return resp, data, nil
}
func (tk *TestKeys) FillTampering(
	req *http.Request, jsonHeaders JSONHeaders, headers map[string]string) {
	tk.Tampering.RequestLineCapitalization = (fmt.Sprintf(
		"%s / HTTP/1.1", req.Method) != jsonHeaders.RequestLine)
	tk.Tampering.HeaderFieldNumber = len(headers) != len(jsonHeaders.HeadersDict)
	expectedHeaderKeys := make(map[string]string)
	for key := range headers {
		expectedHeaderKeys[http.CanonicalHeaderKey(key)] = key
	}
	receivedHeaderKeys := make(map[string]string)
	for key := range jsonHeaders.HeadersDict {
		receivedHeaderKeys[http.CanonicalHeaderKey(key)] = key
	}
	commonHeaderKeys := make(map[string]int)
	for key := range expectedHeaderKeys {
		commonHeaderKeys[key]++
	}
	for key := range receivedHeaderKeys {
		commonHeaderKeys[key]++
	}
	for key, count := range commonHeaderKeys {
		if count != 2 {
			continue  
		}
		expectedKey, receivedKey := expectedHeaderKeys[key], receivedHeaderKeys[key]
		if expectedKey != receivedKey {
			tk.Tampering.HeaderNameCapitalization = true
			tk.Tampering.HeaderNameDiff = append(tk.Tampering.HeaderNameDiff, expectedKey)
			tk.Tampering.HeaderNameDiff = append(tk.Tampering.HeaderNameDiff, receivedKey)
		}
		expectedValue := headers[expectedKey]
		receivedValue := jsonHeaders.HeadersDict[receivedKey]
		if len(receivedValue) != 1 || expectedValue != receivedValue[0] {
			tk.Tampering.HeaderFieldValue = true
		}
	}
}
func NewRequestEntryList(req *http.Request, headers map[string]string) (out []archival.RequestEntry) {
	out = []archival.RequestEntry{{
		Request: archival.HTTPRequest{
			Headers:     make(map[string]archival.MaybeBinaryValue),
			HeadersList: []archival.HTTPHeader{},
			Method:      req.Method,
			URL:         req.URL.String(),
		},
	}}
	for key, value := range headers {
		mbv := archival.MaybeBinaryValue{Value: value}
		out[0].Request.Headers[key] = mbv
		out[0].Request.HeadersList = append(out[0].Request.HeadersList,
			archival.HTTPHeader{Key: key, Value: mbv})
	}
	sort.Slice(out[0].Request.HeadersList, func(i, j int) bool {
		return out[0].Request.HeadersList[i].Key < out[0].Request.HeadersList[j].Key
	})
	return
}
func NewHTTPResponse(resp *http.Response, data []byte) (out archival.HTTPResponse) {
	out = archival.HTTPResponse{
		Body:        archival.HTTPBody{Value: string(data)},
		Code:        int64(resp.StatusCode),
		Headers:     make(map[string]archival.MaybeBinaryValue),
		HeadersList: []archival.HTTPHeader{},
	}
	for key := range resp.Header {
		mbv := archival.MaybeBinaryValue{Value: resp.Header.Get(key)}
		out.Headers[key] = mbv
		out.HeadersList = append(out.HeadersList, archival.HTTPHeader{Key: key, Value: mbv})
	}
	sort.Slice(out.HeadersList, func(i, j int) bool {
		return out.HeadersList[i].Key < out.HeadersList[j].Key
	})
	return
}
type JSONHeaders struct {
	HeadersDict map[string][]string `json:"headers_dict"`
	RequestLine string              `json:"request_line"`
}
type Dialer struct {
	Dialer  netx.Dialer  
	Headers map[string]string
}
func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	dialer := d.Dialer
	if dialer == nil {
		dialer = selfcensor.DefaultDialer
	}
	conn, err := dialer.DialContext(ctx, network, address)
	if err != nil {
		return nil, err
	}
	return Conn{Conn: conn, Headers: d.Headers}, nil
}
type Conn struct {
	net.Conn
	Headers map[string]string
}