[
  {
    "path": ".github/FUNDING.yml",
    "content": "open_collective: goproxy"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # GitHub Actions Pipeline\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n\n  # Go packages\n  - package-ecosystem: \"gomod\"\n    directories:\n      - /\n      - /ext\n      - /examples\n    schedule:\n      interval: \"daily\"\n    ignore:\n      - dependency-name: \"github.com/elazarl/goproxy\"\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Code Check\n\non:\n  workflow_dispatch:\n  pull_request:\n    types:\n      - opened\n      - synchronize\n      - reopened\n\njobs:\n  build:\n    name: Build And Test Go code\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      \n      - uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      # https://stackoverflow.com/questions/76269119/github-actions-go-lambda-project-different-sha256sums\n      - name: Build\n        run: go build -v -buildvcs=false ./...\n        env:\n          # Make sure to not use dependencies that rely on CGO\n          CGO_ENABLED: 0\n\n      # Make sure to detect eventual race conditions\n      # (CGO must be enabled to use -race detector)\n      - name: Test\n        # -count=2 ensures that test fixtures cleanup after themselves\n        # because any leftover state will generally cause the second run to fail.\n        run: go test -race -p 1 -v -shuffle=on -count=2 ./...\n\n      - name: Linter\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: latest\n        env:\n          GOFLAGS: \"-buildvcs=false\"\n"
  },
  {
    "path": ".gitignore",
    "content": "bin\n*.swp\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  modules-download-mode: readonly\n\n# List from https://golangci-lint.run/usage/linters/\nlinters:\n  enable:\n    - asasalint\n    - asciicheck\n    - bidichk\n    - containedctx\n    - decorder\n    - dogsled\n    - durationcheck\n    - errchkjson\n    - errname\n    - errorlint\n    - exhaustive\n    - fatcontext\n    - forbidigo\n    - forcetypeassert\n    - gocheckcompilerdirectives\n    - gochecksumtype\n    - gocritic\n    - godot\n    - goheader\n    - gomodguard\n    - goprintffuncname\n    - gosec\n    - gosmopolitan\n    - grouper\n    - iface\n    - importas\n    - interfacebloat\n    - lll\n    - loggercheck\n    - makezero\n    - mirror\n    - misspell\n    - nakedret\n    - nilerr\n    - noctx\n    - nolintlint\n    - perfsprint\n    - prealloc\n    - predeclared\n    - reassign\n    - revive\n    - staticcheck\n    - tagalign\n    - testableexamples\n    - testifylint\n    - testpackage\n    - thelper\n    - tparallel\n    - unconvert\n    - usestdlibvars\n    - wastedassign\n    - whitespace\n  disable:\n    - bodyclose\n    - canonicalheader\n    - contextcheck # Re-enable in V2\n    - copyloopvar\n    - cyclop\n    - depguard\n    - dupl\n    - dupword\n    - err113\n    - exhaustruct\n    - funlen\n    - ginkgolinter\n    - gochecknoglobals\n    - gochecknoinits\n    - gocognit\n    - goconst\n    - gocyclo\n    - godox\n    - gomoddirectives\n    - inamedparam\n    - intrange\n    - ireturn\n    - maintidx\n    - mnd\n    - musttag\n    - nestif # TODO: Re-enable in V2\n    - nilnil\n    - nlreturn\n    - nonamedreturns\n    - nosprintfhostport\n    - paralleltest\n    - promlinter\n    - protogetter\n    - rowserrcheck\n    - sloglint\n    - spancheck\n    - sqlclosecheck\n    - tagliatelle\n    - unparam\n    - varnamelen\n    - wrapcheck\n    - wsl\n    - zerologlint\n  settings:\n    gosec:\n      excludes:\n        - G402 # InsecureSkipVerify\n        - G102 # Binds to all network interfaces\n        - G403 # RSA keys should be at least 2048 bits\n        - G115 # Integer overflow conversion (uint64 -> int64)\n        - G404 # Use of weak random number generator (math/rand)\n        - G204 # Subprocess launched with a potential tainted input or cmd arguments\n        - G602 # Slice index out of range\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    rules:\n      - linters:\n          - gocritic\n        text: ifElseChain\n      - linters:\n          - lll\n        source: '^// '\n      - linters:\n          - revive\n        text: 'add-constant: '\n      - linters:\n          - revive\n        text: 'unused-parameter: '\n      - linters:\n          - revive\n        text: 'empty-block: '\n      - linters:\n          - revive\n        text: 'var-naming: ' # TODO: Re-enable in V2\n      - linters:\n          - staticcheck\n        text: ' should be ' # TODO: Re-enable in V2\n      - linters:\n          - staticcheck\n        text: 'ST1003: should not use ALL_CAPS in Go names; use CamelCase instead'\n    paths:\n      - examples$\n      - transport\nformatters:\n  enable:\n    - gci\n    - gofmt\n    - gofumpt\n  settings:\n    gci:\n      sections:\n        - standard\n        - default\n      custom-order: true\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012 Elazar Leibovich. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Elazar Leibovich. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# GoProxy\n\n![Status](https://github.com/elazarl/goproxy/workflows/Go/badge.svg)\n[![GoDoc](https://pkg.go.dev/badge/github.com/elazarl/goproxy)](https://pkg.go.dev/github.com/elazarl/goproxy)\n[![Go Report](https://goreportcard.com/badge/github.com/elazarl/goproxy)](https://goreportcard.com/report/github.com/elazarl/goproxy)\n[![BSD-3 License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://opensource.org/licenses/BSD-3-Clause)\n[![Pull Requests](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n[![Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go?tab=readme-ov-file#networking)\n\nGoProxy is a library to create a `customized` HTTP/HTTPS `proxy server` using\nGo (aka Golang), with several configurable settings available.\nThe target of this project is to offer an `optimized` proxy server, usable with\nreasonable amount of traffic, yet `customizable` and `programmable`.\n\nThe proxy itself is simply a `net/http` handler, so you can add multiple\nmiddlewares (panic recover, logging, compression, etc.) over it. It can be\neasily integrated with any other HTTP network library.\n\nIn order to use goproxy, one should set their browser (or any other client)\nto use goproxy as an HTTP proxy.\nHere is how you do that in [Chrome](https://www.wikihow.com/Connect-to-a-Proxy-Server)\nand in [Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox).\nIf you decide to start with the `base` example, the URL you should use as\nproxy is `localhost:8080`, which is the default one in our example.\nYou also have to [trust](https://github.com/elazarl/goproxy/blob/master/examples/customca/README.md)\nthe proxy CA certificate, to avoid any certificate issue in the clients.\n\n> [✈️ Telegram Group](https://telegram.me/goproxygroup)\n>\n> [🎁 Become a Sponsor](https://opencollective.com/goproxy)\n\n## Features\n- Perform certain actions only on `specific hosts`,  with a single equality comparison or with regex evaluation\n- Manipulate `requests` and `responses` before sending them to the browser\n- Use a `custom http.Transport` to perform requests to the target server\n- You can specify a `MITM certificates cache`, to reuse them later for other requests to the same host, thus saving CPU. Not enabled by default, but you should use it in production!\n- Redirect normal HTTP traffic to a `custom handler`, when the target is a `relative path` (e.g. `/ping`)\n- You can choose the logger to use, by implementing the `Logger` interface\n- You can `disable` the HTTP request headers `canonicalization`, by setting `PreventCanonicalization` to true\n\n## Proxy modes\n1. Regular HTTP proxy\n2. HTTPS through CONNECT\n3. HTTPS MITM (\"Man in the Middle\") proxy server, in which the server generate TLS certificates to parse request/response data and perform actions on them\n4. \"Hijacked\" proxy connection, where the configured handler can access the raw net.Conn data\n\n## Sponsors\nDoes your company use GoProxy? Help us keep the project maintained and healthy!\nSupporting GoProxy allows us to dedicate more time to bug fixes and new features.\nIn exchange, if you choose a Gold Supporter or Enterprise plan, we'll proudly display your company logo here.\n\n> [Become a Sponsor](https://opencollective.com/goproxy)\n\n[![Gold Supporters](https://opencollective.com/goproxy/tiers/gold-sponsor.svg?width=890)](https://opencollective.com/goproxy)\n[![Enterprise Supporters](https://opencollective.com/goproxy/tiers/enterprise.svg?width=890)](https://opencollective.com/goproxy)\n\n## Maintainers\n- [Elazar Leibovich](https://github.com/elazarl): Creator of the project, Software Engineer\n- [Erik Pellizzon](https://github.com/ErikPelli): Maintainer, Freelancer (open to collaborations!)\n\nIf you need to integrate GoProxy into your project, or you need some custom\nfeatures to maintain in your fork, you can contact [Erik](mailto:erikpelli@tutamail.com)\n(the current maintainer) by email, and you can discuss together how he\ncan help you as a paid independent consultant.\n\n## Contributions\nIf you have any trouble, suggestion, or if you find a bug, feel free to reach\nout by opening a GitHub `issue`.\nThis is an `open source` project managed by volunteers, and we're happy\nto discuss anything that can improve it.\n\nMake sure to explain everything, including the reason behind the issue\nand what you want to change, to make the problem easier to understand.\nYou can also directly open a `Pull Request`, if it's a small code change, but\nyou need to explain in the description everything.\nIf you open a pull request named `refactoring` with `5,000` lines changed,\nwe won't merge it... `:D`\n\nThe code for this project is released under the `BSD 3-Clause` license,\nmaking it useful for `commercial` uses as well.\n\n### Submit your case study\nSo, you have introduced & integrated GoProxy into one of your personal projects\nor a project inside the company you work for.\n\nWe're happy to learn about new `creative solutions` made with this library,\nso feel free to `contact` the maintainer listed above via e-mail, to explaining\nwhy you found this project useful for your needs.\n\nIf you have signed a `Non Disclosure Agreement` with the company, you\ncan propose them to write a `blog post` on their official website about\nthis topic, so this information will be public by their choice, and you can\n`share the link` of the blog post with us :)\n\nThe purpose of case studies is to share with the `community` why all the\n`contributors` to this project are `improving` the world with their help and\nwhat people are building using it.\n\n### Linter\nThe codebase uses an automatic lint check over your Pull Request code.\nBefore opening it, you should check if your changes respect it, by running\nthe linter in your local machine, so you won't have any surprise.\n\nTo install the linter:\n```sh\ngo install github.com/golangci/golangci-lint/cmd/golangci-lint@latest\n```\n\nThis will create an executable in your `$GOPATH/bin` folder\n(`$GOPATH` is an environment variable, usually\nits value is equivalent to `~/go`, check its value in your machine if you\naren't sure about it).\nMake sure to include the bin folder in the path of your shell, to be able to\ndirectly use the `golangci-lint run` command.\n\n## A taste of GoProxy\n\nTo get a taste of `goproxy`, here you are a basic HTTP/HTTPS proxy\nthat just forward data to the destination:\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"net/http\"\n\n    \"github.com/elazarl/goproxy\"\n)\n\nfunc main() {\n    proxy := goproxy.NewProxyHttpServer()\n    proxy.Verbose = true\n    log.Fatal(http.ListenAndServe(\":8080\", proxy))\n}\n```\n\n### Request handler\nThis line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy,\nbefore sending them to the destination:\n\n```go\nproxy.OnRequest().DoFunc(\n    func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {\n        r.Header.Set(\"X-GoProxy\",\"yxorPoG-X\")\n        return r,nil\n    })\n```\n\nWhen the `OnRequest()` input is empty, the function specified in `DoFunc`\nwill process all incoming requests to the proxy. In this case, it will add\na header to the request and return it to the caller.\nThe proxy will send the modified request to the destination.\nYou can also use `Do` instead of `DoFunc`, if you implement the specified\ninterface in your type.\n\n> ⚠️ Note we returned a nil value as the response.\n> If the returned response is not nil, goproxy will discard the request\n> and send the specified response to the client.\n\n### Conditional Request handler\nRefuse connections to www.reddit.com between 8 and 17 in the server\nlocal timezone:\n\n```go\nproxy.OnRequest(goproxy.DstHostIs(\"www.reddit.com\")).DoFunc(\n    func(req *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {\n        if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 {\n\t\t\tresp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, \"Don't waste your time!\")\n            return req, resp\n        }\n        return req, nil\n})\n```\n\n`DstHostIs` returns a `ReqCondition`, which is a function receiving a `*http.Request`\nand returning a boolean that checks if the request satisfies the condition (and that will be processed).\n`DstHostIs(\"www.reddit.com\")` will return a `ReqCondition` that returns true\nwhen the request is directed to \"www.reddit.com\".\nThe host equality check is `case-insensitive`, to reflect the behaviour of DNS\nresolvers, so even if the user types \"www.rEdDit.com\", the comparison will\nsatisfy the condition.\nWhen the hour is between 8:00am and 5:59pm, we directly return\na response in `DoFunc()`, so the remote destination will not receive the\nrequest and the client will receive the `\"Don't waste your time!\"` response.\n\n### Let's start\n```go\nimport \"github.com/elazarl/goproxy\"\n```\n\nThere are some proxy usage examples in the `examples` folder, which\ncover the most common cases. Take a look at them and good luck!\n\n## Request & Response manipulation\n\nThere are 3  different types of handlers to manipulate the behavior of the proxy, as follows:\n\n```go\n// handler called after receiving HTTP CONNECT from the client, and\n// before proxy establishes connection with the destination host\nhttpsHandlers   []HttpsHandler\n\n// handler called before proxy sends HTTP request to destination host\nreqHandlers     []ReqHandler \n\n// handler called after proxy receives HTTP Response from destination host,\n// and before proxy forwards the Response to the client\nrespHandlers    []RespHandler \n```\n\nDepending on what you want to manipulate, the ways to add handlers to each of the previous lists are:\n\n```go\n// Add handlers to httpsHandlers \nproxy.OnRequest(some ReqConditions).HandleConnect(YourHandlerFunc())\n\n// Add handlers to reqHandlers\nproxy.OnRequest(some ReqConditions).Do(YourReqHandlerFunc())\n\n// Add handlers to respHandlers\nproxy.OnResponse(some RespConditions).Do(YourRespHandlerFunc())\n```\n\nExample:\n\n```go\n// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase.\n// Reddit URL check is case-insensitive because of (?i), so the block will work also if the user types something like rEdDit.com.\nproxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(\"(?i)reddit.*:443$\"))).HandleConnect(goproxy.AlwaysReject)\n\n// Be careful about this example! It shows you a common error that you\n// need to avoid.\n// This will NOT reject the HTTPS request with URL ending with .gif because,\n// if the scheme is HTTPS, the proxy will receive only URL.Hostname\n// and URL.Port during the HTTP CONNECT phase.\nproxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject)\n\n// To fix the previous example, here there is the correct way to manipulate\n// an HTTP request using URL.Path (target path) as a condition.\nproxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc())\n```\n\n## Error handling\n### Generic error\nIf an error occurs while handling a request through the proxy, by default\nthe proxy returns HTTP error `500` (Internal Server Error) with the `error\nmessage` as the `body` content.\n\nIf you want to override this behaviour, you can define your own\n`RespHandler` that changes the error response.\nAmong the context parameters, `ctx.Error` contains the `error` occurred,\nif any, or the `nil` value, if no error happened.\n\nYou can handle it as you wish, including returning a custom JSON as the body.\nExample of an error handler:\n```\nproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\tvar dnsError *net.DNSError\n\tif errors.As(ctx.Error, &dnsError) {\n\t\t// Do not leak our DNS server's address\n\t\tdnsError.Server = \"<server-redacted>\"\n\t\treturn goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusBadGateway, dnsError.Error())\n\t}\n\treturn resp\n})\n```\n\n### Connection error\nIf an error occurs while sending data to the target remote server (or to\nthe proxy client), the `proxy.ConnectionErrHandler` is called to handle the\nerror, if present, else a `default handler` will be used.\nThe error is passed as `function parameter` and not inside the proxy context,\nso you don't have to check the ctx.Error field in this handler.\n\nIn this handler you have access to the raw connection with the proxy\nclient (as an `io.Writer`), so you could send any HTTP data over it,\nif needed, containing the error data.\nThere is no guarantee that the connection hasn't already been closed, so\nthe `Write()` could return an error.\n\nThe `connection` will be `automatically closed` by the proxy library after the\nerror handler call, so you don't have to worry about it.\n\n## Project Status\nThis project has been created `10 years` ago, and has reached a stage of\n`maturity`. It can be safely used in `production`, and many projects\nalready do that.\n\nIf there will be any `breaking change` in the future, a `new version` of the\nGo module will be released (e.g. v2).\n\n## Trusted, as a direct dependency, by:\n<p align=\"left\">\n<a href=\"https://github.com/stripe/goproxy\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/856813?s=50\" alt=\"Stripe\" title=\"Stripe\" /> </a>\n<a href=\"https://github.com/dependabot/goproxy\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/27347476?s=50\" alt=\"Dependabot\" title=\"Dependabot\" /> </a>\n<a href=\"https://github.com/go-git/go-git\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/57653224?s=50\" alt=\"Go Git\" title=\"Go Git\" /> </a>\n<a href=\"https://github.com/google/oss-rebuild\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/1342004?s=50\" alt=\"Google\" title=\"Google\" /> </a>\n<a href=\"https://github.com/grafana/grafana-plugin-sdk-go\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/7195757?s=50\" alt=\"Grafana\" title=\"Grafana\" /> </a>\n<a href=\"https://github.com/superfly/tokenizer\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/22525303?s=50\" alt=\"Fly.io\" title=\"Fly.io\" /> </a>\n<a href=\"https://github.com/kubernetes/minikube\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/13629408?s=50\" alt=\"Kubernetes / Minikube\" title=\"Kubernetes / Minikube\" /> </a>\n<a href=\"https://github.com/newrelic/newrelic-client-go\" target=\"_blank\" rel=\"noreferrer\"> <img src=\"https://avatars.githubusercontent.com/u/31739?s=50\" alt=\"New Relic\" title=\"New Relic\" /> </a>\n</p>\n"
  },
  {
    "path": "actions.go",
    "content": "package goproxy\n\nimport \"net/http\"\n\n// ReqHandler will \"tamper\" with the request coming to the proxy server\n// If Handle returns req,nil the proxy will send the returned request\n// to the destination server. If it returns nil,resp the proxy will\n// skip sending any requests, and will simply return the response `resp`\n// to the client.\ntype ReqHandler interface {\n\tHandle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)\n}\n\n// A wrapper that would convert a function to a ReqHandler interface type.\ntype FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)\n\n// FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx).\nfunc (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {\n\treturn f(req, ctx)\n}\n\n// after the proxy have sent the request to the destination server, it will\n// \"filter\" the response through the RespHandlers it has.\n// The proxy server will send to the client the response returned by the RespHandler.\n// In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error.\ntype RespHandler interface {\n\tHandle(resp *http.Response, ctx *ProxyCtx) *http.Response\n}\n\n// A wrapper that would convert a function to a RespHandler interface type.\ntype FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response\n\n// FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx).\nfunc (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response {\n\treturn f(resp, ctx)\n}\n\n// When a client send a CONNECT request to a host, the request is filtered through\n// all the HttpsHandlers the proxy has, and if one returns true, the connection is\n// sniffed using Man in the Middle attack.\n// That is, the proxy will create a TLS connection with the client, another TLS\n// connection with the destination the client wished to connect to, and would\n// send back and forth all messages from the server to the client and vice versa.\n// The request and responses sent in this Man In the Middle channel are filtered\n// through the usual flow (request and response filtered through the ReqHandlers\n// and RespHandlers).\ntype HttpsHandler interface {\n\tHandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string)\n}\n\n// A wrapper that would convert a function to a HttpsHandler interface type.\ntype FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string)\n\n// FuncHttpsHandler should implement the RespHandler interface.\nfunc (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) {\n\treturn f(host, ctx)\n}\n"
  },
  {
    "path": "all.bash",
    "content": "#!/bin/bash\n\ngo test || exit\nfor action in $@; do go $action; done\n\nmkdir -p bin\nfind regretable examples/* ext/* -maxdepth 0 -type d | while read d; do\n\t(cd $d\n\tgo build -o ../../bin/$(basename $d)\n\tfind *_test.go -maxdepth 0 2>/dev/null|while read f;do\n\t\tfor action in $@; do go $action; done\n\t\tgo test\n\t\tbreak\n\tdone)\ndone\n"
  },
  {
    "path": "ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD\nVQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM\nB0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0\naHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0\nMDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE\nCAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV\nBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI\nhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9\n3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP\nsqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9\nV6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh\nhiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr\nlHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq\nj0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo\nWsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD\nfqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj\nYOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh\nWKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj\nUzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4\nuDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB\nCwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F\nAqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0\nC6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3\nBa0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin\no0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye\ni6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr\nbvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY\nVfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft\n8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86\nNCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV\nBUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certs/openssl-gen.sh",
    "content": "#!/bin/bash\nset -ex\n# generate CA's  key\nopenssl genrsa -aes256 -passout pass:1 -out ca.key.pem 4096\nopenssl rsa -passin pass:1 -in ca.key.pem -out ca.key.pem.tmp\nmv ca.key.pem.tmp ca.key.pem\n\nopenssl req -config openssl.cnf -key ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out ca.pem\n"
  },
  {
    "path": "certs/openssl.cnf",
    "content": "[ ca ]\ndefault_ca\t= CA_default\n[ CA_default ]\ndefault_md\t= sha256\n[ v3_ca ]\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer\nbasicConstraints = critical,CA:true\n[ req ]\ndistinguished_name\t= req_distinguished_name\n[ req_distinguished_name ]\ncountryName\t\t\t= Country Name (2 letter code)\ncountryName_default\t\t= IL\ncountryName_min\t\t\t= 2\ncountryName_max\t\t\t= 2\n\nstateOrProvinceName\t\t= State or Province Name (full name)\nstateOrProvinceName_default\t= Center\n\nlocalityName\t\t\t= Locality Name (eg, city)\nlocalityName_default\t\t= Lod\n\n0.organizationName\t\t= Organization Name (eg, company)\n0.organizationName_default\t= GoProxy\n\n# we can do this but it is not needed normally :-)\n#1.organizationName\t\t= Second Organization Name (eg, company)\n#1.organizationName_default\t= World Wide Web Pty Ltd\n\norganizationalUnitName\t\t= Organizational Unit Name (eg, section)\norganizationalUnitName_default\t= GoProxy\n\ncommonName\t\t\t= Common Name (e.g. server FQDN or YOUR name)\ncommonName_default\t\t= goproxy.github.io\ncommonName_max\t\t\t= 64\n\nemailAddress\t\t\t= Email Address\nemailAddress_default\t\t= elazarl@gmail.com\nemailAddress_max\t\t= 64\n"
  },
  {
    "path": "certs.go",
    "content": "package goproxy\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n)\n\nvar GoproxyCa tls.Certificate\n\nfunc init() {\n\t// When we included the embedded certificate inside this file, we made\n\t// sure that it was valid.\n\t// If there is an error here, this is a really exceptional case that requires\n\t// a panic. It should NEVER happen!\n\tvar err error\n\tGoproxyCa, err = tls.X509KeyPair(CA_CERT, CA_KEY)\n\tif err != nil {\n\t\tpanic(\"Error parsing builtin CA: \" + err.Error())\n\t}\n\n\tif GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil {\n\t\tpanic(\"Error parsing builtin CA leaf: \" + err.Error())\n\t}\n}\n\nvar tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true}\n\nvar defaultTLSConfig = &tls.Config{\n\tInsecureSkipVerify: true,\n}\n\nvar CA_CERT = []byte(`-----BEGIN CERTIFICATE-----\nMIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD\nVQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM\nB0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0\naHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0\nMDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE\nCAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV\nBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI\nhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9\n3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP\nsqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9\nV6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh\nhiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr\nlHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq\nj0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo\nWsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD\nfqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj\nYOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh\nWKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj\nUzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4\nuDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB\nCwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F\nAqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0\nC6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3\nBa0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin\no0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye\ni6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr\nbvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY\nVfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft\n8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86\nNCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV\nBUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==\n-----END CERTIFICATE-----`)\n\nvar CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF\n0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw\nHM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf\nm31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+\nqiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ\n0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I\nyeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq\nAmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU\nBIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK\n0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic\ngeFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA\nAQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR\nkPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3\nlVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt\nzCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7\n+68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ\n3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf\npjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U\nC440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4\nOod3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3\n4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm\nV6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9\njZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag\n/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6\neDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw\n+LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ\nZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt\nFQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC\n06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7\nOSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9\n7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf\nKR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt\nsjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB\nN/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa\nQbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv\n5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W\nt3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF\n540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru\nsIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi\nL0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um\nYJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi\n9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe\nyKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ\nQBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c\nue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH\n759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh\npmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1\ncj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88\n4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w=\n-----END RSA PRIVATE KEY-----`)\n"
  },
  {
    "path": "ctx.go",
    "content": "package goproxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"mime\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// ProxyCtx is the Proxy context, contains useful information about every request. It is passed to\n// every user function. Also used as a logger.\ntype ProxyCtx struct {\n\t// Will contain the client request from the proxy\n\tReq *http.Request\n\t// Will contain the remote server's response (if available. nil if the request wasn't send yet)\n\tResp         *http.Response\n\tRoundTripper RoundTripper\n\t// Specify a custom connection dialer that will be used only for the current\n\t// request, including WebSocket connection upgrades\n\tDialer func(ctx context.Context, network string, addr string) (net.Conn, error)\n\t// will contain the recent error that occurred while trying to send receive or parse traffic\n\tError error\n\t// A handle for the user to keep data in the context, from the call of ReqHandler to the\n\t// call of RespHandler\n\tUserData any\n\t// Will connect a request to a response\n\tSession   int64\n\tcertStore CertStorage\n\tProxy     *ProxyHttpServer\n}\n\ntype RoundTripper interface {\n\tRoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error)\n}\n\ntype CertStorage interface {\n\tFetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error)\n}\n\ntype RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error)\n\nfunc (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) {\n\treturn f(req, ctx)\n}\n\nfunc (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) {\n\tif ctx.RoundTripper != nil {\n\t\treturn ctx.RoundTripper.RoundTrip(req, ctx)\n\t}\n\treturn ctx.Proxy.Tr.RoundTrip(req)\n}\n\nfunc (ctx *ProxyCtx) printf(msg string, argv ...any) {\n\tctx.Proxy.Logger.Printf(\"[%03d] \"+msg+\"\\n\", append([]any{ctx.Session & 0xFFFF}, argv...)...)\n}\n\n// Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter\n// This message will be printed only if the Verbose field of the ProxyHttpServer is set to true\n//\n//\tproxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){\n//\t\tnr := atomic.AddInt32(&counter,1)\n//\t\tctx.Printf(\"So far %d requests\",nr)\n//\t\treturn r, nil\n//\t})\nfunc (ctx *ProxyCtx) Logf(msg string, argv ...any) {\n\tif ctx.Proxy.Verbose {\n\t\tctx.printf(\"INFO: \"+msg, argv...)\n\t}\n}\n\n// Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter\n// This message will always be printed.\n//\n//\tproxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){\n//\t\tf,err := os.OpenFile(cachedContent)\n//\t\tif err != nil {\n//\t\t\tctx.Warnf(\"error open file %v: %v\",cachedContent,err)\n//\t\t\treturn r, nil\n//\t\t}\n//\t\treturn r, nil\n//\t})\nfunc (ctx *ProxyCtx) Warnf(msg string, argv ...any) {\n\tctx.printf(\"WARN: \"+msg, argv...)\n}\n\n// Will try to infer the character set of the request from the headers.\n// Returns the empty string if we don't know which character set it used.\n// Currently it will look for charset=<charset> in the Content-Type header of the request.\nfunc (ctx *ProxyCtx) Charset() string {\n\tcontentType := ctx.Resp.Header.Get(\"Content-Type\")\n\tif _, params, err := mime.ParseMediaType(contentType); err == nil {\n\t\tif cs, ok := params[\"charset\"]; ok {\n\t\t\treturn cs\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "dispatcher.go",
    "content": "package goproxy\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request\n// before sending it to the remote server.\ntype ReqCondition interface {\n\tRespCondition\n\tHandleReq(req *http.Request, ctx *ProxyCtx) bool\n}\n\n// RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response\n// before sending it to the proxy client. Note that resp might be nil, in case there was an\n// error sending the request.\ntype RespCondition interface {\n\tHandleResp(resp *http.Response, ctx *ProxyCtx) bool\n}\n\n// ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx).\ntype ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool\n\n// RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx).\ntype RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool\n\nfunc (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool {\n\treturn c(req, ctx)\n}\n\n// ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that\n// to be usable as RespCondition.\nfunc (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool {\n\treturn c(ctx.Req, ctx)\n}\n\nfunc (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool {\n\treturn c(resp, ctx)\n}\n\n// UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested\n// has the given prefix, with or without the host.\n// For example UrlHasPrefix(\"host/x\") will match requests of the form 'GET host/x', and will match\n// requests to url 'http://host/x'\nfunc UrlHasPrefix(prefix string) ReqConditionFunc {\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\t// Make sure to include the / as the first path character when we do a match\n\t\t// using the host\n\t\trelativePath := req.URL.Path\n\t\tif length := len(relativePath); length == 0 || (length > 0 && relativePath[0] != '/') {\n\t\t\trelativePath = \"/\" + relativePath\n\t\t}\n\t\t// We use the original value to distinguish between \"\" and \"/\" in the user specified string\n\t\treturn strings.HasPrefix(req.URL.Path, prefix) ||\n\t\t\tstrings.HasPrefix(req.URL.Host+relativePath, prefix) ||\n\t\t\t// Scheme value is something like \"https\", we must include the :// characters\n\t\t\tstrings.HasPrefix(req.URL.Scheme+\"://\"+req.URL.Host+relativePath, prefix)\n\t}\n}\n\n// UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings\n// with or without the host prefix.\n// UrlIs(\"google.com/\",\"foo\") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to\n// any host, and requests of the form 'GET foo'.\nfunc UrlIs(urls ...string) ReqConditionFunc {\n\turlSet := make(map[string]bool)\n\tfor _, u := range urls {\n\t\turlSet[u] = true\n\t}\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\t_, pathOk := urlSet[req.URL.Path]\n\t\t_, hostAndOk := urlSet[req.URL.Host+req.URL.Path]\n\t\treturn pathOk || hostAndOk\n\t}\n}\n\n// ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches\n// any of the given regular expressions.\nfunc ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc {\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\tfor _, re := range regexps {\n\t\t\tif re.MatchString(req.Host) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n}\n\n// ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal\n// to one of the given strings.\nfunc ReqHostIs(hosts ...string) ReqConditionFunc {\n\thostSet := make(map[string]bool)\n\tfor _, h := range hosts {\n\t\thostSet[h] = true\n\t}\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\t_, ok := hostSet[req.URL.Host]\n\t\treturn ok\n\t}\n}\n\n// IsLocalHost checks whether the destination host is localhost.\nvar IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool {\n\th := req.URL.Hostname()\n\tif h == \"localhost\" {\n\t\treturn true\n\t}\n\tif ip := net.ParseIP(h); ip != nil {\n\t\treturn ip.IsLoopback()\n\t}\n\n\t// In case of IPv6 without a port number Hostname() sometimes returns the invalid value.\n\tif ip := net.ParseIP(req.URL.Host); ip != nil {\n\t\treturn ip.IsLoopback()\n\t}\n\n\treturn false\n}\n\n// UrlMatches returns a ReqCondition testing whether the destination URL\n// of the request matches the given regexp, with or without prefix.\nfunc UrlMatches(re *regexp.Regexp) ReqConditionFunc {\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\treturn re.MatchString(req.URL.Path) ||\n\t\t\tre.MatchString(req.URL.Host+req.URL.Path)\n\t}\n}\n\n// DstHostIs returns a ReqCondition testing wether the host in the request url is the given string.\nfunc DstHostIs(host string) ReqConditionFunc {\n\t// Make sure to perform a case-insensitive host check\n\thost = strings.ToLower(host)\n\tvar port string\n\n\t// Check if the user specified a custom port that we need to match\n\tif strings.Contains(host, \":\") {\n\t\thostOnly, portOnly, err := net.SplitHostPort(host)\n\t\tif err == nil {\n\t\t\thost = hostOnly\n\t\t\tport = portOnly\n\t\t}\n\t}\n\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\t// Check port matching only if it was specified\n\t\tif port != \"\" && port != req.URL.Port() {\n\t\t\treturn false\n\t\t}\n\n\t\treturn strings.ToLower(req.URL.Hostname()) == host\n\t}\n}\n\n// SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings.\nfunc SrcIpIs(ips ...string) ReqCondition {\n\treturn ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool {\n\t\tfor _, ip := range ips {\n\t\t\tif strings.HasPrefix(req.RemoteAddr, ip+\":\") {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n}\n\n// Not returns a ReqCondition negating the given ReqCondition.\nfunc Not(r ReqCondition) ReqConditionFunc {\n\treturn func(req *http.Request, ctx *ProxyCtx) bool {\n\t\treturn !r.HandleReq(req, ctx)\n\t}\n}\n\n// ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal\n// to one of the given strings.\nfunc ContentTypeIs(typ string, types ...string) RespCondition {\n\ttypes = append(types, typ)\n\treturn RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool {\n\t\tif resp == nil {\n\t\t\treturn false\n\t\t}\n\t\tcontentType := resp.Header.Get(\"Content-Type\")\n\t\tfor _, typ := range types {\n\t\t\tif contentType == typ || strings.HasPrefix(contentType, typ+\";\") {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n}\n\n// StatusCodeIs returns a RespCondition, testing whether or not the HTTP status\n// code is one of the given ints.\nfunc StatusCodeIs(codes ...int) RespCondition {\n\tcodeSet := make(map[int]bool)\n\tfor _, c := range codes {\n\t\tcodeSet[c] = true\n\t}\n\treturn RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool {\n\t\tif resp == nil {\n\t\t\treturn false\n\t\t}\n\t\t_, codeMatch := codeSet[resp.StatusCode]\n\t\treturn codeMatch\n\t})\n}\n\n// ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions.\n// You will use the ReqProxyConds struct to register a ReqHandler, that would filter\n// the request, only if all the given ReqCondition matched.\n// Typical usage:\n//\n//\tproxy.OnRequest(UrlIs(\"example.com/foo\"),UrlMatches(regexp.MustParse(`.*\\.exampl.\\com\\./.*`)).Do(...)\nfunc (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds {\n\treturn &ReqProxyConds{proxy, conds}\n}\n\n// ReqProxyConds aggregate ReqConditions for a ProxyHttpServer.\n// Upon calling Do, it will register a ReqHandler that would\n// handle the request if all conditions on the HTTP request are met.\ntype ReqProxyConds struct {\n\tproxy    *ProxyHttpServer\n\treqConds []ReqCondition\n}\n\n// DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)).\nfunc (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) {\n\tpcond.Do(FuncReqHandler(f))\n}\n\n// ReqProxyConds.Do will register the ReqHandler on the proxy,\n// the ReqHandler will handle the HTTP request if all the conditions\n// aggregated in the ReqProxyConds are met. Typical usage:\n//\n//\tproxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy\n//\tproxy.OnRequest(cond1,cond2).Do(handler)\n//\t// given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true\n//\t// if they are, will call handler.Handle(req,ctx)\nfunc (pcond *ReqProxyConds) Do(h ReqHandler) {\n\tpcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers,\n\t\tFuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {\n\t\t\tfor _, cond := range pcond.reqConds {\n\t\t\t\tif !cond.HandleReq(r, ctx) {\n\t\t\t\t\treturn r, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn h.Handle(r, ctx)\n\t\t}))\n}\n\n// HandleConnect is used when proxy receives an HTTP CONNECT request,\n// it'll then use the HttpsHandler to determine what should it\n// do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction\n// struct returned will determine what to do with this request. ConnectAccept will simply accept the request\n// forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the\n// client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man\n// in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped\n// connection.\n// The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy\n// will use the default tls configuration.\n//\n//\tproxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests\nfunc (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) {\n\tpcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,\n\t\tFuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {\n\t\t\tfor _, cond := range pcond.reqConds {\n\t\t\t\tif !cond.HandleReq(ctx.Req, ctx) {\n\t\t\t\t\treturn nil, \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn h.HandleConnect(host, ctx)\n\t\t}))\n}\n\n// HandleConnectFunc is equivalent to HandleConnect,\n// for example, accepting CONNECT request if they contain a password in header\n//\n//\tio.WriteString(h,password)\n//\tpassHash := h.Sum(nil)\n//\tproxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {\n//\t\tc := sha1.New()\n//\t\tio.WriteString(c,ctx.Req.Header.Get(\"X-GoProxy-Auth\"))\n//\t\tif c.Sum(nil) == passHash {\n//\t\t\treturn OkConnect, host\n//\t\t}\n//\t\treturn RejectConnect, host\n//\t})\nfunc (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) {\n\tpcond.HandleConnect(FuncHttpsHandler(f))\n}\n\nfunc (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) {\n\tpcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,\n\t\tFuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {\n\t\t\tfor _, cond := range pcond.reqConds {\n\t\t\t\tif !cond.HandleReq(ctx.Req, ctx) {\n\t\t\t\t\treturn nil, \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ConnectAction{Action: ConnectHijack, Hijack: f}, host\n\t\t}))\n}\n\n// ProxyConds is used to aggregate RespConditions for a ProxyHttpServer.\n// Upon calling ProxyConds.Do, it will register a RespHandler that would\n// handle the HTTP response from remote server if all conditions on the HTTP response are met.\ntype ProxyConds struct {\n\tproxy    *ProxyHttpServer\n\treqConds []ReqCondition\n\trespCond []RespCondition\n}\n\n// ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)).\nfunc (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) {\n\tpcond.Do(FuncRespHandler(f))\n}\n\n// ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every\n// request that matches the conditions aggregated in pcond.\nfunc (pcond *ProxyConds) Do(h RespHandler) {\n\tpcond.proxy.respHandlers = append(pcond.proxy.respHandlers,\n\t\tFuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {\n\t\t\tfor _, cond := range pcond.reqConds {\n\t\t\t\tif !cond.HandleReq(ctx.Req, ctx) {\n\t\t\t\t\treturn resp\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, cond := range pcond.respCond {\n\t\t\t\tif !cond.HandleResp(resp, ctx) {\n\t\t\t\t\treturn resp\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn h.Handle(resp, ctx)\n\t\t}))\n}\n\n// OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is\n//\n//\tproxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used\n//\t\t\t\t// if cond1.HandleResp(resp) && cond2.HandleResp(resp)\nfunc (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds {\n\treturn &ProxyConds{proxy, make([]ReqCondition, 0), conds}\n}\n\n// AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to\n// eavesdrop all https connections to www.google.com, we can use\n//\n//\tproxy.OnRequest(goproxy.ReqHostIs(\"www.google.com\")).HandleConnect(goproxy.AlwaysMitm)\nvar AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {\n\treturn MitmConnect, host\n}\n\n// AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow\n// connections to hosts on any other port than 443\n//\n//\tproxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(\":443$\"))).\n//\t\tHandleConnect(goproxy.AlwaysReject)\nvar AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {\n\treturn RejectConnect, host\n}\n\n// HandleBytes will return a RespHandler that read the entire body of the request\n// to a byte array in memory, would run the user supplied f function on the byte arra,\n// and will replace the body of the original response with the resulting byte array.\nfunc HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler {\n\treturn FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {\n\t\tb, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tctx.Warnf(\"Cannot read response %s\", err)\n\t\t\treturn resp\n\t\t}\n\t\tresp.Body.Close()\n\n\t\tresp.Body = io.NopCloser(bytes.NewBuffer(f(b, ctx)))\n\t\treturn resp\n\t})\n}\n"
  },
  {
    "path": "dispatcher_test.go",
    "content": "package goproxy_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\nfunc TestIsLocalHost(t *testing.T) {\n\thosts := []string{\n\t\t\"localhost\",\n\t\t\"127.0.0.1\",\n\t\t\"127.0.0.7\",\n\t\t\"::ffff:127.0.0.1\",\n\t\t\"::ffff:127.0.0.7\",\n\t\t\"::1\",\n\t\t\"0:0:0:0:0:0:0:1\",\n\t}\n\tports := []string{\n\t\t\"\",\n\t\t\"80\",\n\t\t\"443\",\n\t}\n\n\tfor _, host := range hosts {\n\t\tfor _, port := range ports {\n\t\t\tif port == \"\" && strings.HasPrefix(host, \"::ffff:\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\taddr := host\n\t\t\tif port != \"\" {\n\t\t\t\taddr = net.JoinHostPort(host, port)\n\t\t\t}\n\t\t\tt.Run(addr, func(t *testing.T) {\n\t\t\t\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, \"http://\"+addr, http.NoBody)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tif !goproxy.IsLocalHost(req, nil) {\n\t\t\t\t\tt.Fatal(\"expected true\")\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "/*\nPackage goproxy provides a customizable HTTP proxy,\nsupporting hijacking HTTPS connection.\n\nThe intent of the proxy, is to be usable with reasonable amount of traffic\nyet, customizable and programmable.\n\nThe proxy itself is simply an `net/http` handler.\n\nTypical usage is\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest(..conditions..).Do(..requesthandler..)\n\tproxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..)\n\tproxy.OnResponse(..conditions..).Do(..responesHandler..)\n\tproxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..)\n\thttp.ListenAndServe(\":8080\", proxy)\n\nAdding a header to each request\n\n\tproxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){\n\t\tr.Header.Set(\"X-GoProxy\",\"1\")\n\t\treturn r, nil\n\t})\n\n> Note that the function is called before the proxy sends the request to the server\n\nFor printing the content type of all incoming responses\n\n\tproxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{\n\t\tprintln(ctx.Req.Host,\"->\",r.Header.Get(\"Content-Type\"))\n\t\treturn r\n\t})\n\nnote that we used the ProxyCtx context variable here. It contains the request\nand the response (Req and Resp, Resp is nil if unavailable) of this specific client\ninteraction with the proxy.\n\nTo print the content type of all responses from a certain url, we'll add a\nReqCondition to the OnResponse function:\n\n\tproxy.OnResponse(goproxy.UrlIs(\"golang.org/pkg\")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{\n\t\tprintln(ctx.Req.Host,\"->\",r.Header.Get(\"Content-Type\"))\n\t\treturn r\n\t})\n\nWe can write the condition ourselves, conditions can be set on request and on response\n\n\tvar random = ReqConditionFunc(func(r *http.Request) bool {\n\t\treturn rand.Intn(1) == 0\n\t})\n\tvar hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool {\n\t\treturn resp.Header.Get(\"X-GoProxy\") != \"\"\n\t})\n\nCaution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't\nmake sense to read the response, if you still haven't got it!\n\nFinally, we have convenience function to throw a quick response\n\n\tproxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response {\n\t\tr.Body.Close()\n\t\treturn goproxy.NewResponse(\n\t\t\tctx.Req, goproxy.ContentTypeText, http.StatusForbidden, \"Can't see response with X-GoProxy header!\"\n\t\t)\n\t})\n\nwe close the body of the original response, and return a new 403 response with a short message.\n\nExample use cases:\n\n1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize\n\nTo measure the average size of an Html served in your site. One can ask\nall the QA team to access the website by a proxy, and the proxy will\nmeasure the average size of all text/html responses from your host.\n\n2. [not yet implemented]\n\nAll requests to your web servers should be directed through the proxy,\nwhen the proxy will detect html pieces sent as a response to AJAX\nrequest, it'll send a warning email.\n\n3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/\n\nGenerate a real traffic to your website by real users using through\nproxy. Record the traffic, and try it again for more real load testing.\n\n4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime\n\nWill allow browsing to reddit.com between 8:00am and 17:00pm\n\n5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version\n\nWill warn if multiple versions of jquery are used in the same domain.\n\n6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/\n\nModifies image files in an HTTP response via goproxy's image extension found in ext/.\n*/\npackage goproxy\n"
  },
  {
    "path": "examples/base/README.md",
    "content": "# Simple HTTP Proxy\n\nThis example contains a base HTTP proxy server that listens on port :8080.\nIt only handles explicit CONNECT requests.\n\nStart it in one shell:\n\n```sh\ngo build\nbase -v\n```\n\nFetch a website using the proxy:\n```sh\nhttp_proxy=http://127.0.0.1:8080 wget -O - \\\n\thttp://ripper234.com/p/introducing-goproxy-light-http-proxy/\n```\n\nThe homepage HTML content should be displayed in the console.\nThe proxy should have logged the request being processed:\n\n```sh\n2015/04/09 18:19:17 [001] INFO: Got request /p/introducing-goproxy-light-http-proxy/ ripper234.com GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/\n2015/04/09 18:19:17 [001] INFO: Sending request GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/\n2015/04/09 18:19:18 [001] INFO: Received response 200 OK\n2015/04/09 18:19:18 [001] INFO: Copying response to client 200 OK [200]\n2015/04/09 18:19:18 [001] INFO: Copied 44333 bytes to client error=<nil>\n```\n\n"
  },
  {
    "path": "examples/base/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.Verbose = *verbose\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n"
  },
  {
    "path": "examples/cascadeproxy/README.md",
    "content": "# CascadeProxy\n\n`CascadeProxy` is an example that shows an aggregator server that forwards\nthe requests to another proxy server (end proxy).\n\nDiagram:\n```\nclient --> middle proxy --> end proxy --> internet\n```\n\nThis example starts both proxy servers using goproxy, the middle one\nlistens on port `8081`, and the end one on port `8082`.\n\nThe middle proxy must be an HTTP server, since we use goproxy that\nexpose only it.\nThe end proxy can be any type of proxy supported by Go, including SOCKS5,\nthere is a comment in the part where you can put its address.\n"
  },
  {
    "path": "examples/cascadeproxy/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/ext/auth\"\n)\n\nconst _proxyAuthHeader = \"Proxy-Authorization\"\n\nfunc SetBasicAuth(username, password string, req *http.Request) {\n\treq.Header.Set(_proxyAuthHeader, \"Basic \"+base64.StdEncoding.EncodeToString([]byte(username+\":\"+password)))\n}\n\nfunc main() {\n\tusername, password := \"foo\", \"bar\"\n\n\t// Start end proxy server\n\tendProxy := goproxy.NewProxyHttpServer()\n\tendProxy.Verbose = true\n\tauth.ProxyBasic(endProxy, \"my_realm\", func(user, pwd string) bool {\n\t\treturn subtle.ConstantTimeCompare([]byte(user), []byte(username)) == 1 &&\n\t\t\tsubtle.ConstantTimeCompare([]byte(pwd), []byte(password)) == 1\n\t})\n\tlog.Println(\"serving end proxy server at localhost:8082\")\n\tgo http.ListenAndServe(\"localhost:8082\", endProxy)\n\n\t// Start middle proxy server\n\tmiddleProxy := goproxy.NewProxyHttpServer()\n\tmiddleProxy.Verbose = true\n\tmiddleProxy.KeepHeader = true\n\tmiddleProxy.Tr.Proxy = func(req *http.Request) (*url.URL, error) {\n\t\t// Here we specify the proxy URL of the other server.\n\t\t// If it was a socks5 proxy, we would have used an url like\n\t\t// socks5://localhost:8082\n\t\treturn url.Parse(\"http://localhost:8082\")\n\t}\n\tconnectReqHandler := func(req *http.Request) {\n\t\tSetBasicAuth(username, password, req)\n\t}\n\tmiddleProxy.ConnectDial = middleProxy.NewConnectDialToProxyWithHandler(\"http://localhost:8082\", connectReqHandler)\n\n\tmiddleProxy.OnRequest().Do(goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tSetBasicAuth(username, password, req)\n\t\treturn req, nil\n\t}))\n\tlog.Println(\"serving middle proxy server at localhost:8081\")\n\tgo http.ListenAndServe(\"localhost:8081\", middleProxy)\n\n\ttime.Sleep(1 * time.Second)\n\n\t// Make a single HTTP request, from client to internet, through the 2 proxies\n\tmiddleProxyUrl := \"http://localhost:8081\"\n\trequest, err := http.NewRequest(http.MethodGet, \"https://ip.cn\", nil)\n\tif err != nil {\n\t\tlog.Fatalf(\"new request failed:%v\", err)\n\t}\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(middleProxyUrl)\n\t\t\t},\n\t\t},\n\t}\n\tresp, err := client.Do(request)\n\tif err != nil {\n\t\tlog.Fatalf(\"get resp failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\tdata, _ := io.ReadAll(resp.Body)\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tlog.Fatalf(\"status %d, data %s\", resp.StatusCode, data)\n\t}\n\n\tlog.Printf(\"resp: %s\", data)\n}\n"
  },
  {
    "path": "examples/cascadeproxy-socks/README.md",
    "content": "# CascadeSocksProxy\r\n\r\n`CascadeSocksProxy` is an example that shows an aggregator server that forwards\r\nthe requests to another socks proxy server. This example is written base on `cascadeproxy` example.\r\n\r\nDiagram:\r\n```\r\nclient --> goproxy --> socks5 proxy --> internet\r\n```\r\n\r\nThis example starts a HTTP/HTTPS proxy using goproxy that listens on port `8080`, and forward the requests to the socks5 proxy on `socks5://localhost:1080`.\r\nIt uses MITM (Man in the Middle) proxy mode to retriece and parse the request, and then forwards it to the destination using  the socks5 proxy client implemented in the standard Go `net/http` library. \r\n\r\n### Example usage:\r\n\r\nAggregator server that have HTTP proxy server run on port `8080` and forward the requests to socks proxy listens on `socks5://localhost:1080` with no auth\r\n```shell\r\n./socks -v -addr \":8080\" -socks \"localhost:1080\"\r\n``` \r\n\r\nWith auth:\r\n```shell\r\n./socks -v -addr \":8080\" -socks \"localhost:1080\" -user \"bob\" -pass \"123\"\r\n ```\r\n\r\nYou can run the socks proxy server locally for testing with the following command - this will start a socks5 proxy server on port `1080` with no auth:\r\n```shell\r\n./socks5proxyserver/socks5proxyserver\r\n```"
  },
  {
    "path": "examples/cascadeproxy-socks/socks5proxyserver/go.mod",
    "content": "module socks5proxyserver\n\ngo 1.20\n\nrequire github.com/things-go/go-socks5 v0.0.5\n"
  },
  {
    "path": "examples/cascadeproxy-socks/socks5proxyserver/go.sum",
    "content": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=\ngithub.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=\ngolang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\n"
  },
  {
    "path": "examples/cascadeproxy-socks/socks5proxyserver/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"github.com/things-go/go-socks5\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Create a SOCKS5 server\n\tserver := socks5.NewServer(\n\t\tsocks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, \"socks5: \", log.LstdFlags))),\n\t\tsocks5.WithDialAndRequest(func(ctx context.Context, network, addr string, request *socks5.Request) (net.Conn, error) {\n\t\t\tlog.Printf(\"Request from %s to %s\", request.RemoteAddr, request.DestAddr)\n\t\t\treturn net.Dial(network, addr)\n\t\t}),\n\t)\n\n\t// Create SOCKS5 proxy on localhost port 1080\n\tif err := server.ListenAndServe(\"tcp\", \":1080\"); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "examples/cascadeproxy-socks/socksproxy.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\ntype SocksAuth struct {\n\tUsername, Password string\n}\n\nfunc createSocksProxy(socksAddr string, auth SocksAuth) func(r *http.Request) (*url.URL, error) {\n\treturn func(r *http.Request) (*url.URL, error) {\n\t\tUrl := &url.URL{\n\t\t\tScheme: \"socks5\",\n\t\t\tHost:   socksAddr,\n\t\t}\n\t\tif auth.Username != \"\" {\n\t\t\tUrl.User = url.UserPassword(auth.Username, auth.Password)\n\t\t}\n\t\treturn Url, nil\n\t}\n}\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tsocksAddr := flag.String(\"socks\", \"127.0.0.1:1080\", \"socks proxy address\")\n\tusername := flag.String(\"user\", \"\", \"username for SOCKS5 proxy if auth is required\")\n\tpassword := flag.String(\"pass\", \"\", \"password for SOCKS5 proxy\")\n\tflag.Parse()\n\n\tauth := SocksAuth{\n\t\tUsername: *username,\n\t\tPassword: *password,\n\t}\n\tproxyServer := goproxy.NewProxyHttpServer()\n\n\tproxyServer.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tclient := &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tProxy: createSocksProxy(*socksAddr, auth),\n\t\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t// https://stackoverflow.com/questions/19595860/http-request-requesturi-field-when-making-request-in-go\n\t\treq.RequestURI = \"\"\n\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tctx.Logf(\"Failed to forward request: \" + err.Error())\n\t\t\treturn nil, nil\n\t\t}\n\t\tctx.Logf(\"Succesfully forwarded request to socks proxy\")\n\t\treturn req, resp\n\t})\n\n\tproxyServer.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxyServer.Verbose = *verbose\n\n\tlog.Fatalln(http.ListenAndServe(*addr, proxyServer))\n}\n"
  },
  {
    "path": "examples/certstorage/README.md",
    "content": "# CertStorage\n\nCertStorage example is important to improve the performance of an\nHTTPS proxy server, which you can build using goproxy.\nWithout a `proxy.CertStore`, every HTTPS request will generate new TLS\ncertificates and this, repeated for hundreds of request, will destroy your CPU.\n\nA lot of people opened issues in the projects complaining about this, because\nthey didn't use a certificates cache.\nThe cache implementation is up to you, maybe you can cache only the\nmost used hostnames, if you want to.\n"
  },
  {
    "path": "examples/certstorage/cache.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"sync\"\n)\n\n// CertStorage is a simple certificate cache that keeps\n// everything in memory.\ntype CertStorage struct {\n\tcerts map[string]*tls.Certificate\n\tmtx   sync.RWMutex\n}\n\nfunc (cs *CertStorage) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {\n\tcs.mtx.RLock()\n\tcert, ok := cs.certs[hostname]\n\tcs.mtx.RUnlock()\n\tif ok {\n\t\treturn cert, nil\n\t}\n\n\tcert, err := gen()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs.mtx.Lock()\n\tcs.certs[hostname] = cert\n\tcs.mtx.Unlock()\n\n\treturn cert, nil\n}\n\nfunc NewCertStorage() *CertStorage {\n\treturn &CertStorage{\n\t\tcerts: make(map[string]*tls.Certificate),\n\t}\n}\n"
  },
  {
    "path": "examples/certstorage/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"github.com/elazarl/goproxy\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.CertStore = NewCertStorage()\n\tproxy.Verbose = *verbose\n\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t// Log requested URL\n\t\tlog.Println(req.URL.String())\n\t\treturn req, nil\n\t})\n\n\t// Start proxy server\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n"
  },
  {
    "path": "examples/customca/README.md",
    "content": "# CustomCA\n\nThis example shows you how to use a custom CA to sign the HTTPS MITM\nrequests (you can use your own generated certificates).\nIf the client has some kind of SSL pinning to check the TLS certificates, all\nthe request will most likely fail, so make sure to remove it before using\nthis proxy or opening new issues.\n\nProxy server will generate a custom certificate for the target host, for each\nrequest, and it's used to read the request data of an HTTPS\nconnection.\nThe client will establish a TLS connection using the generated certificate\nwith the proxy server, the server will read the request data, process it\naccording to the user needs, and then it will do a new request to the real\ndestination.\n\nThe CA certificate must be trusted by your system, or the client will reject\nthe connection, since it's not recognized.\n\n## Trust CA certificate\nThe default CA certificate used by GoProxy is in the root folder of this\nproject (in files `ca.pem`, and its private key `key.pem`).\n\n### Use your certificate\nYou can trust the default certificate or use your own with GoProxy, and\ntrust it instead of the provided `ca.pem`.\nIf you want to do this, just replace the occurrences of this file in the next\nparagraphs with your CA certificate filename.\nYou can generate your own self-signed certificate with\n[openssl](https://stackoverflow.com/questions/10175812/how-to-generate-a-self-signed-ssl-certificate-using-openssl).\n\n### Firefox\nYou have to reach the certificate manager configuration in order to add\nthe certificate to the trusted ones.\nTo reach it, open the settings and type in search bar \"Certificates\", then\nclick on the button \"View Certificates...\".\nIn the tab \"Authorities\", click \"Import...\" and select the `ca.pem` file.\nGoProxy CA is now trusted by your browser!\n\n### Chrome\nOpen the certificate manager configuration:\n> \"Settings\" > \"Privacy and Security\" > \"Security\" > \"Manage certificates\"\n\nGo to the tab \"Authorities\", click \"Import\" and select the `ca.pem` file.\nGoProxy CA is now trusted by your browser!\n\n### System\nIf you want the root certificate to be trusted by all applications in your\nenvironment, consider adding it to the system trusted certificates.\nHere is a couple of guides about how to do it, but we don't provide any support:\n- [1](https://manuals.gfi.com/en/kerio/connect/content/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)\n- [2](https://unix.stackexchange.com/questions/90450/adding-a-self-signed-certificate-to-the-trusted-list)\n\n#### MkCert\nDo you want a managed, easy to use solution that automatically generates\na root CA certificate for local usage, and automatically adds it to the trusted system\ncertificates? Consider [MkCert](https://github.com/FiloSottile/mkcert).\nIt's enough to just use it and add the generated trusted certificate to GoProxy.\n"
  },
  {
    "path": "examples/customca/cert.go",
    "content": "package main\n\nvar _caCert = []byte(`-----BEGIN CERTIFICATE-----\nMIIDkzCCAnugAwIBAgIJAKe/ZGdfcHdPMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\naWRnaXRzIFB0eSBMdGQxGTAXBgNVBAMMEGRlbW8gZm9yIGdvcHJveHkwHhcNMTYw\nOTI3MTQzNzQ3WhcNMTkwOTI3MTQzNzQ3WjBgMQswCQYDVQQGEwJBVTETMBEGA1UE\nCAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk\nMRkwFwYDVQQDDBBkZW1vIGZvciBnb3Byb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6\n1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0\n69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW//\n1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+\nJEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK\nvhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABo1AwTjAdBgNVHQ4EFgQU\nGnlWcIbfsWJW7GId+6xZIK8YlFEwHwYDVR0jBBgwFoAUGnlWcIbfsWJW7GId+6xZ\nIK8YlFEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAoFUjSD15rKlY\nxudzyVlr6n0fRNhITkiZMX3JlFOvtHNYif8RfK4TH/oHNBTmle69AgixjMgy8GGd\nH90prytGQ5zCs1tKcCFsN5gRSgdAkc2PpRFOK6u8HwOITV5lV7sjucsddXJcOJbQ\n4fyVe47V9TTxI+A7lRnUP2HYTR1Bd0R/IgRAH57d1ZHs7omHIuQ+Ea8ph2ppXMnP\nDXVOlZ9zfczSnPnQoomqULOU9Fq2ycyi8Y/ROtAHP6O7wCFbYHXhxojdaHSdhkcd\ntroTflFMD2/4O6MtBKbHxSmEG6H0FBYz5xUZhZq7WUH24V3xYsfge29/lOCd5/Xf\nA+j0RJc/lQ==\n-----END CERTIFICATE-----`)\n\nvar _caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6\n1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0\n69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW//\n1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+\nJEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK\nvhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABAoIBAHK94ww8W0G5QIWL\nQwkc9XeGvg4eLUxVknva2Ll4fkZJxY4WveKx9OCd1lv4n7WoacYIwUGIDaQBZShW\ns/eKnkmqGy+PvpC87gqL4sHvQpuqqJ1LYpxylLEFqduWOuGPUVC2Lc+QnWCycsCS\nCgqZzsbMq0S+kkKRGSvw32JJneZCzqLgLNssQNVk+Gm6SI3s4jJsGPesjhnvoPaa\nxZK14uFpltaA05GSTDaQeZJFEdnnb3f/eNPc2xMEfi0S2ZlJ6Q92WJEOepAetDlR\ncRFi004bNyTb4Bphg8s4+9Cti5is199aFkGCRDWxeqEnc6aMY3Ezu9Qg3uttLVUd\nuy830GUCgYEA7qS0X+9UH1R02L3aoANyADVbFt2ZpUwQGauw9WM92pH52xeHAw1S\nohus6FI3OC8xQq2CN525tGLUbFDZnNZ3YQHqFsfgevfnTs1//gbKXomitev0oFKh\nVT+WYS4lkgYtPlXzhdGuk32q99T/wIocAguvCUY3PiA7yBz93ReyausCgYEA6+P8\nbugMqT8qjoiz1q/YCfxsw9bAGWjlVqme2xmp256AKtxvCf1BPsToAaJU3nFi3vkw\nICLxUWAYoMBODJ3YnbOsIZOavdXZwYHv54JqwqFealC3DG0Du6fZYZdiY8pK+E6m\n3fiYzP1WoVK5tU4bH8ibuIQvpcI8j7Gy0cV6/AMCgYBHl7fZNAZro72uLD7DVGVF\n9LvP/0kR0uDdoqli5JPw12w6szM40i1hHqZfyBJy042WsFDpeHL2z9Nkb1jpeVm1\nC4r7rJkGqwqElJf6UHUzqVzb8N6hnkhyN7JYkyyIQzwdgFGfaslRzBiXYxoa3BQM\n9Q5c3OjDxY3JuhDa3DoVYwKBgDNqrWJLSD832oHZAEIicBe1IswJKjQfriWWsV6W\nmHSbdtpg0/88aZVR/DQm+xLFakSp0jifBTS0momngRu06Dtvp2xmLQuF6oIIXY97\n2ON1owvPbibSOEcWDgb8pWCU/oRjOHIXts6vxctCKeKAFN93raGphm0+Ck9T72NU\nBTubAoGBAMEhI/Wy9wAETuXwN84AhmPdQsyCyp37YKt2ZKaqu37x9v2iL8JTbPEz\npdBzkA2Gc0Wdb6ekIzRrTsJQl+c/0m9byFHsRsxXW2HnezfOFX1H4qAmF6KWP0ub\nM8aIn6Rab4sNPSrvKGrU6rFpv/6M33eegzldVnV9ku6uPJI1fFTC\n-----END RSA PRIVATE KEY-----`)\n"
  },
  {
    "path": "examples/customca/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\n\tcert, err := parseCA(_caCert, _caKey)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcustomCaMitm := &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(cert)}\n\tvar customAlwaysMitm goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {\n\t\treturn customCaMitm, host\n\t}\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(customAlwaysMitm)\n\tproxy.Verbose = *verbose\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n\nfunc parseCA(caCert, caKey []byte) (*tls.Certificate, error) {\n\tparsedCert, err := tls.X509KeyPair(caCert, caKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif parsedCert.Leaf, err = x509.ParseCertificate(parsedCert.Certificate[0]); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &parsedCert, nil\n}\n"
  },
  {
    "path": "examples/go.mod",
    "content": "module github.com/elazarl/goproxy/examples/goproxy-transparent\n\ngo 1.23\n\nrequire (\n\tgithub.com/coder/websocket v1.8.14\n\tgithub.com/elazarl/goproxy v1.5.0\n\tgithub.com/elazarl/goproxy/ext v0.0.0-20250117123040-e9229c451ab8\n\tgithub.com/inconshreveable/go-vhost v1.0.0\n)\n\nrequire (\n\tgolang.org/x/net v0.35.0 // indirect\n\tgolang.org/x/text v0.22.0 // indirect\n)\n\nreplace github.com/elazarl/goproxy => ../\n"
  },
  {
    "path": "examples/go.sum",
    "content": "github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=\ngithub.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/elazarl/goproxy/ext v0.0.0-20250117123040-e9229c451ab8 h1:rGxOExXmpBcmZc4ZnEXBGkcxSReZx7S9ECtuv6BtUYQ=\ngithub.com/elazarl/goproxy/ext v0.0.0-20250117123040-e9229c451ab8/go.mod h1:q2JQCFWg+AQfe6O2cbf7LJDB48R68w+q0pBU53v02iM=\ngithub.com/inconshreveable/go-vhost v1.0.0 h1:IK4VZTlXL4l9vz2IZoiSFbYaaqUW7dXJAiPriUN5Ur8=\ngithub.com/inconshreveable/go-vhost v1.0.0/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngolang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=\ngolang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=\ngolang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=\ngolang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "examples/goproxy-httpdump/README.md",
    "content": "# Trace HTTP Requests and Responses\n\n`goproxy-httpdump` starts an HTTP proxy on :8080. It handles explicit CONNECT\nrequests and traces them in a \"db\" directory created in the proxy working\ndirectory.  Each request type and headers are logged in a \"log\" file, while\ntheir bodies are dumped in files prefixed with the request session identifier.\n\nAdditionally, the example demonstrates how to:\n- Log information asynchronously (see HttpLogger)\n- Allow the proxy to be stopped manually while ensuring all pending requests\n  have been processed (in this case, logged).\n\nStart it in one shell:\n\n```sh\ngoproxy-httpdump\n```\n\nFetch goproxy homepage in another:\n\n```sh\nhttp_proxy=http://127.0.0.1:8080 wget -O - \\\n\thttp://ripper234.com/p/introducing-goproxy-light-http-proxy/\n```\n\nA \"db\" directory should have appeared where you started the proxy, containing\ntwo files:\n- log: the request/response traces\n- 1\\_resp: the first response body\n\n"
  },
  {
    "path": "examples/goproxy-httpdump/httpdump.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/transport\"\n)\n\ntype FileStream struct {\n\tpath string\n\tf    *os.File\n}\n\nfunc NewFileStream(path string) *FileStream {\n\treturn &FileStream{path, nil}\n}\n\nfunc (fs *FileStream) Write(b []byte) (nr int, err error) {\n\tif fs.f == nil {\n\t\tfs.f, err = os.Create(fs.path)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\treturn fs.f.Write(b)\n}\n\nfunc (fs *FileStream) Close() error {\n\tfmt.Println(\"Close\", fs.path)\n\tif fs.f == nil {\n\t\treturn errors.New(\"FileStream was never written into\")\n\t}\n\treturn fs.f.Close()\n}\n\ntype Meta struct {\n\treq      *http.Request\n\tresp     *http.Response\n\terr      error\n\tt        time.Time\n\tsess     int64\n\tbodyPath string\n\tfrom     string\n}\n\nfunc fprintf(nr *int64, err *error, w io.Writer, pat string, a ...any) {\n\tif *err != nil {\n\t\treturn\n\t}\n\tvar n int\n\tn, *err = fmt.Fprintf(w, pat, a...)\n\t*nr += int64(n)\n}\n\nfunc write(nr *int64, err *error, w io.Writer, b []byte) {\n\tif *err != nil {\n\t\treturn\n\t}\n\tvar n int\n\tn, *err = w.Write(b)\n\t*nr += int64(n)\n}\n\nfunc (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {\n\tif m.req != nil {\n\t\tfprintf(&nr, &err, w, \"Type: request\\r\\n\")\n\t} else if m.resp != nil {\n\t\tfprintf(&nr, &err, w, \"Type: response\\r\\n\")\n\t}\n\tfprintf(&nr, &err, w, \"ReceivedAt: %v\\r\\n\", m.t)\n\tfprintf(&nr, &err, w, \"Session: %d\\r\\n\", m.sess)\n\tfprintf(&nr, &err, w, \"From: %v\\r\\n\", m.from)\n\tif m.err != nil {\n\t\t// note the empty response\n\t\tfprintf(&nr, &err, w, \"Error: %v\\r\\n\\r\\n\\r\\n\\r\\n\", m.err)\n\t} else if m.req != nil {\n\t\tfprintf(&nr, &err, w, \"\\r\\n\")\n\t\tbuf, err2 := httputil.DumpRequest(m.req, false)\n\t\tif err2 != nil {\n\t\t\treturn nr, err2\n\t\t}\n\t\twrite(&nr, &err, w, buf)\n\t} else if m.resp != nil {\n\t\tfprintf(&nr, &err, w, \"\\r\\n\")\n\t\tbuf, err2 := httputil.DumpResponse(m.resp, false)\n\t\tif err2 != nil {\n\t\t\treturn nr, err2\n\t\t}\n\t\twrite(&nr, &err, w, buf)\n\t}\n\treturn\n}\n\n// HttpLogger is an asynchronous HTTP request/response logger. It traces\n// requests and responses headers in a \"log\" file in logger directory and dumps\n// their bodies in files prefixed with the session identifiers.\n// Close it to ensure pending items are correctly logged.\ntype HttpLogger struct {\n\tpath  string\n\tc     chan *Meta\n\terrch chan error\n}\n\nfunc NewLogger(basepath string) (*HttpLogger, error) {\n\tf, err := os.Create(path.Join(basepath, \"log\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogger := &HttpLogger{basepath, make(chan *Meta), make(chan error)}\n\tgo func() {\n\t\tfor m := range logger.c {\n\t\t\tif _, err := m.WriteTo(f); err != nil {\n\t\t\t\tlog.Println(\"Can't write meta\", err)\n\t\t\t}\n\t\t}\n\t\tlogger.errch <- f.Close()\n\t}()\n\treturn logger, nil\n}\n\nfunc (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) {\n\tbody := path.Join(logger.path, fmt.Sprintf(\"%d_resp\", ctx.Session))\n\tfrom := \"\"\n\tif ctx.UserData != nil {\n\t\tfrom = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String()\n\t}\n\tif resp == nil {\n\t\tresp = emptyResp\n\t} else {\n\t\tresp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body))\n\t}\n\tlogger.LogMeta(&Meta{\n\t\tresp: resp,\n\t\terr:  ctx.Error,\n\t\tt:    time.Now(),\n\t\tsess: ctx.Session,\n\t\tfrom: from})\n}\n\nvar emptyResp = &http.Response{}\nvar emptyReq = &http.Request{}\n\nfunc (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) {\n\tbody := path.Join(logger.path, fmt.Sprintf(\"%d_req\", ctx.Session))\n\tif req == nil {\n\t\treq = emptyReq\n\t} else {\n\t\treq.Body = NewTeeReadCloser(req.Body, NewFileStream(body))\n\t}\n\tlogger.LogMeta(&Meta{\n\t\treq:  req,\n\t\terr:  ctx.Error,\n\t\tt:    time.Now(),\n\t\tsess: ctx.Session,\n\t\tfrom: req.RemoteAddr})\n}\n\nfunc (logger *HttpLogger) LogMeta(m *Meta) {\n\tlogger.c <- m\n}\n\nfunc (logger *HttpLogger) Close() error {\n\tclose(logger.c)\n\treturn <-logger.errch\n}\n\n// TeeReadCloser extends io.TeeReader by allowing reader and writer to be\n// closed.\ntype TeeReadCloser struct {\n\tr io.Reader\n\tw io.WriteCloser\n\tc io.Closer\n}\n\nfunc NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser {\n\treturn &TeeReadCloser{io.TeeReader(r, w), w, r}\n}\n\nfunc (t *TeeReadCloser) Read(b []byte) (int, error) {\n\treturn t.r.Read(b)\n}\n\n// Close attempts to close the reader and write. It returns an error if both\n// failed to Close.\nfunc (t *TeeReadCloser) Close() error {\n\terr1 := t.c.Close()\n\terr2 := t.w.Close()\n\tif err1 != nil {\n\t\treturn err1\n\t}\n\treturn err2\n}\n\n// stoppableListener serves stoppableConn and tracks their lifetime to notify\n// when it is safe to terminate the application.\ntype stoppableListener struct {\n\tnet.Listener\n\tsync.WaitGroup\n}\n\ntype stoppableConn struct {\n\tnet.Conn\n\twg *sync.WaitGroup\n}\n\nfunc newStoppableListener(l net.Listener) *stoppableListener {\n\treturn &stoppableListener{l, sync.WaitGroup{}}\n}\n\nfunc (sl *stoppableListener) Accept() (net.Conn, error) {\n\tc, err := sl.Listener.Accept()\n\tif err != nil {\n\t\treturn c, err\n\t}\n\tsl.Add(1)\n\treturn &stoppableConn{c, &sl.WaitGroup}, nil\n}\n\nfunc (sc *stoppableConn) Close() error {\n\tsc.wg.Done()\n\treturn sc.Conn.Close()\n}\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"l\", \":8080\", \"on which address should the proxy listen\")\n\tflag.Parse()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.Verbose = *verbose\n\tif err := os.MkdirAll(\"db\", 0755); err != nil {\n\t\tlog.Fatal(\"Can't create dir\", err)\n\t}\n\tlogger, err := NewLogger(\"db\")\n\tif err != nil {\n\t\tlog.Fatal(\"can't open log file\", err)\n\t}\n\ttr := transport.Transport{Proxy: transport.ProxyFromEnvironment}\n\t// For every incoming request, override the RoundTripper to extract\n\t// connection information. Store it is session context log it after\n\t// handling the response.\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tctx.RoundTripper = goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (resp *http.Response, err error) {\n\t\t\tctx.UserData, resp, err = tr.DetailedRoundTrip(req)\n\t\t\treturn\n\t\t})\n\t\tlogger.LogReq(req, ctx)\n\t\treturn req, nil\n\t})\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tlogger.LogResp(resp, ctx)\n\t\treturn resp\n\t})\n\tl, err := net.Listen(\"tcp\", *addr)\n\tif err != nil {\n\t\tlog.Fatal(\"listen:\", err)\n\t}\n\tsl := newStoppableListener(l)\n\tch := make(chan os.Signal)\n\tsignal.Notify(ch, os.Interrupt)\n\tgo func() {\n\t\t<-ch\n\t\tlog.Println(\"Got SIGINT exiting\")\n\t\tsl.Add(1)\n\t\tsl.Close()\n\t\tlogger.Close()\n\t\tsl.Done()\n\t}()\n\tlog.Println(\"Starting Proxy\")\n\thttp.Serve(sl, proxy)\n\tsl.Wait()\n\tlog.Println(\"All connections closed - exit\")\n}\n"
  },
  {
    "path": "examples/goproxy-transparent/README.md",
    "content": "# Transparent Proxy\n\nThis transparent example in goproxy is meant to show how to transparent proxy and hijack all http and https connections while doing a man-in-the-middle to the TLS session.  It requires that goproxy sees all the packets traversing out to the internet.  Linux iptables rules deal with changing the source/destination IPs to act transparently, but you do need to set up your network configuration so that goproxy is a mandatory stop on the outgoing route.  Primarily you can do this by placing the proxy inline.  goproxy does not have any WCCP support itself; patches are welcome.\n\n## Why not explicit?\n\nTransparent proxies are more difficult to maintain and set up from a server side, but they require no configuration on the client(s) which could be in unmanaged systems or systems that don't support a proxy configuration.  See the [eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go) if you want to see an explicit proxy example.\n\n## Potential Issues\n\nSupport for very old clients using HTTPS will fail.  Clients need to send the SNI value in the TLS ClientHello which most modern clients do these days, but old clients will break.\n\nIf you're routing table allows for it, an explicit http request to goproxy will cause it to fail in an endless loop since it will try to request resources from itself repeatedly.  This could be solved in the goproxy code by looking up the hostnames, but it adds a delay that is much easier/faster to handle on the routing side.\n\n## Routing Rules\n\nExample routing rules are included in [proxy.sh](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-transparent/proxy.sh) but are best when set up using your distribution's configuration.\n"
  },
  {
    "path": "examples/goproxy-transparent/proxy.sh",
    "content": "#!/bin/sh\n# goproxy IP\nGOPROXY_SERVER=\"10.10.10.1\"\n# goproxy port\nGOPROXY_PORT=\"3129\"\nGOPROXY_PORT_TLS=\"3128\"\n# DO NOT MODIFY BELOW\n# Load IPTABLES modules for NAT and IP conntrack support\nmodprobe ip_conntrack\nmodprobe ip_conntrack_ftp\necho 1 > /proc/sys/net/ipv4/ip_forward\necho 2 > /proc/sys/net/ipv4/conf/all/rp_filter\n\n# Clean old firewall\niptables -t nat -F\niptables -t nat -X\niptables -t mangle -F\niptables -t mangle -X\n\n# Write new rules\niptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT -j ACCEPT\niptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT_TLS -j ACCEPT\niptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT\niptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT_TLS\n# The following line supports using goproxy as an explicit proxy in addition\niptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT\niptables -t nat -A POSTROUTING -j MASQUERADE\niptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT -j DROP\niptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT_TLS -j DROP\n"
  },
  {
    "path": "examples/goproxy-transparent/transparent.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/inconshreveable/go-vhost\"\n)\n\nfunc orPanic(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", true, \"should every proxy request be logged to stdout\")\n\thttp_addr := flag.String(\"httpaddr\", \":3129\", \"proxy http listen address\")\n\thttps_addr := flag.String(\"httpsaddr\", \":3128\", \"proxy https listen address\")\n\tflag.Parse()\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.Verbose = *verbose\n\tif proxy.Verbose {\n\t\tlog.Printf(\"Server starting up! - configured to listen on http interface %s and https interface %s\", *http_addr, *https_addr)\n\t}\n\n\tproxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tif req.Host == \"\" {\n\t\t\tfmt.Fprintln(w, \"Cannot handle requests without Host header, e.g., HTTP 1.0\")\n\t\t\treturn\n\t\t}\n\t\treq.URL.Scheme = \"http\"\n\t\treq.URL.Host = req.Host\n\t\tproxy.ServeHTTP(w, req)\n\t})\n\tproxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(\"^.*$\"))).\n\t\tHandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(\"^.*:80$\"))).\n\t\tHijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {\n\t\t\tdefer func() {\n\t\t\t\tif e := recover(); e != nil {\n\t\t\t\t\tctx.Logf(\"error connecting to remote: %v\", e)\n\t\t\t\t\tclient.Write([]byte(\"HTTP/1.1 500 Cannot reach destination\\r\\n\\r\\n\"))\n\t\t\t\t}\n\t\t\t\tclient.Close()\n\t\t\t}()\n\t\t\tclientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))\n\n\t\t\tremote, err := connectDial(req.Context(), proxy, \"tcp\", req.URL.Host)\n\t\t\torPanic(err)\n\t\t\tremoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))\n\t\t\tfor {\n\t\t\t\treq, err := http.ReadRequest(clientBuf.Reader)\n\t\t\t\torPanic(err)\n\t\t\t\torPanic(req.Write(remoteBuf))\n\t\t\t\torPanic(remoteBuf.Flush())\n\t\t\t\tresp, err := http.ReadResponse(remoteBuf.Reader, req)\n\t\t\t\torPanic(err)\n\t\t\t\torPanic(resp.Write(clientBuf.Writer))\n\t\t\t\torPanic(clientBuf.Flush())\n\t\t\t}\n\t\t})\n\n\tgo func() {\n\t\tlog.Fatalln(http.ListenAndServe(*http_addr, proxy))\n\t}()\n\n\t// listen to the TLS ClientHello but make it a CONNECT request instead\n\tln, err := net.Listen(\"tcp\", *https_addr)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error listening for https connections - %v\", err)\n\t}\n\tfor {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error accepting new connection - %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tgo func(c net.Conn) {\n\t\t\ttlsConn, err := vhost.TLS(c)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error accepting new connection - %v\", err)\n\t\t\t}\n\t\t\tif tlsConn.Host() == \"\" {\n\t\t\t\tlog.Printf(\"Cannot support non-SNI enabled clients\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconnectReq := &http.Request{\n\t\t\t\tMethod: http.MethodConnect,\n\t\t\t\tURL: &url.URL{\n\t\t\t\t\tOpaque: tlsConn.Host(),\n\t\t\t\t\tHost:   net.JoinHostPort(tlsConn.Host(), \"443\"),\n\t\t\t\t},\n\t\t\t\tHost:       tlsConn.Host(),\n\t\t\t\tHeader:     make(http.Header),\n\t\t\t\tRemoteAddr: c.RemoteAddr().String(),\n\t\t\t}\n\t\t\tresp := dumbResponseWriter{tlsConn}\n\t\t\tproxy.ServeHTTP(resp, connectReq)\n\t\t}(c)\n\t}\n}\n\n// copied/converted from https.go\nfunc dial(ctx context.Context, proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {\n\tif proxy.Tr.DialContext != nil {\n\t\treturn proxy.Tr.DialContext(ctx, network, addr)\n\t}\n\tvar d net.Dialer\n\treturn d.DialContext(ctx, network, addr)\n}\n\n// copied/converted from https.go\nfunc connectDial(ctx context.Context, proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {\n\tif proxy.ConnectDial == nil {\n\t\treturn dial(ctx, proxy, network, addr)\n\t}\n\treturn proxy.ConnectDial(network, addr)\n}\n\ntype dumbResponseWriter struct {\n\tnet.Conn\n}\n\nfunc (dumb dumbResponseWriter) Header() http.Header {\n\tpanic(\"Header() should not be called on this ResponseWriter\")\n}\n\nfunc (dumb dumbResponseWriter) Write(buf []byte) (int, error) {\n\tif bytes.Equal(buf, []byte(\"HTTP/1.0 200 OK\\r\\n\\r\\n\")) {\n\t\treturn len(buf), nil // throw away the HTTP OK response from the faux CONNECT request\n\t}\n\treturn dumb.Conn.Write(buf)\n}\n\nfunc (dumb dumbResponseWriter) WriteHeader(code int) {\n\tpanic(\"WriteHeader() should not be called on this ResponseWriter\")\n}\n\nfunc (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\treturn dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil\n}\n"
  },
  {
    "path": "examples/hijack/README.md",
    "content": "# Hijack\nIn this example we intercept the data of an HTTP request and decide to\nmodify them before sending to the client.\nIn this mode, we take over on the raw connection and we could send any\ndata that we want.\n\nCurl example:\n\n```\n$ curl -x localhost:8080 http://google.it -v -k -p\n\n* Host localhost:8080 was resolved.\n* IPv6: ::1\n* IPv4: 127.0.0.1\n*   Trying [::1]:8080...\n* Connected to localhost (::1) port 8080\n* CONNECT tunnel: HTTP/1.1 negotiated\n* allocate connect buffer\n* Establish HTTP proxy tunnel to google.it:80\n> CONNECT google.it:80 HTTP/1.1\n> Host: google.it:80\n> User-Agent: curl/8.9.1\n> Proxy-Connection: Keep-Alive\n>\n< HTTP/1.1 200 Ok\n<\n* CONNECT phase completed\n* CONNECT tunnel established, response 200\n> GET / HTTP/1.1\n> Host: google.it\n> User-Agent: curl/8.9.1\n> Accept: */*\n>\n< HTTP/1.1 200 OK\n< test: 1234\n< Content-Length: 0\n```\n"
  },
  {
    "path": "examples/hijack/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"regexp\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\nfunc main() {\n\tproxy := goproxy.NewProxyHttpServer()\n\t// Reject all requests to baidu\n\tproxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(\"baidu.*:443$\"))).\n\t\tHandleConnect(goproxy.AlwaysReject)\n\n\t// Instead of returning the Internet response, send custom data from\n\t// our proxy server, using connection hijack\n\tproxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(\"^.*$\"))).\n\t\tHijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {\n\t\t\tclient.Write([]byte(\"HTTP/1.1 200 Ok\\r\\n\\r\\n\"))\n\n\t\t\tw := bufio.NewWriter(client)\n\n\t\t\tresp := &http.Response{\n\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\tProtoMajor: 1,\n\t\t\t\tProtoMinor: 1,\n\t\t\t\tHeader: http.Header{\n\t\t\t\t\t\"test\": {\"1234\"},\n\t\t\t\t},\n\t\t\t}\n\t\t\tresp.Write(w)\n\t\t\tw.Flush()\n\t\t\tclient.Close()\n\t\t})\n\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\tproxy.Verbose = *verbose\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n"
  },
  {
    "path": "examples/html-parser/README.md",
    "content": "# HTML Parser\n\n`html-parser` starts an HTTP proxy on :8080.\nIt checks HTML responses, looks for scripts referencing jQuery library\nand log warnings if different versions of the library are being used\nfor a given host.\nThis is an example of how a proxy can parse the received responses and\nmanipulate them to do useful actions.\n\nStart the server:\n\n```sh\ngo build\nhtml-parser\n```\n\nMake a test request in another shell:\n\n```sh\nhttp_proxy=http://127.0.0.1:8080 wget -O - \\\n\thttp://ripper234.com/p/introducing-goproxy-light-http-proxy/\n```\n\nGoproxy example homepage contains jQuery and a mix of JQuery plugins.\nFirst the proxy reports the first use of jQuery it detects for the domain.\nThen, because the regular expression matching the jQuery sources is imprecise,\nit reports a mismatch with a plugin reference:\n\n```sh\n2015/04/11 11:23:02 [001] WARN: ripper234.com uses //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\n2015/04/11 11:23:02 [001] WARN: In http://ripper234.com/p/introducing-goproxy-light-http-proxy/, \\\n  Contradicting jqueries //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js \\\n  http://ripper234.wpengine.netdna-cdn.com/wp-content/plugins/wp-ajax-edit-comments/js/jquery.colorbox.min.js?ver=5.0.36\n```\n"
  },
  {
    "path": "examples/html-parser/jquery1.html",
    "content": "<!doctype html>\n<html>\n<head>\n<script src=\"jquery.1.4.js\"></script>\n</head>\n<body/>\n</html>\n\n"
  },
  {
    "path": "examples/html-parser/jquery2.html",
    "content": "<!doctype html>\n<html>\n<head>\n<script src=\"jquery.1.3.js\"></script>\n</head>\n<body/>\n</html>\n\n"
  },
  {
    "path": "examples/html-parser/jquery_homepage.html",
    "content": "<!DOCTYPE html>\n\t<html>\n\t<head>\n\t\t<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n\t\t<title>jQuery: The Write Less, Do More, JavaScript Library</title>\n\t\t<link rel=\"stylesheet\" href=\"http://static.jquery.com/files/rocker/css/reset.css\" type=\"text/css\" />\n\t\t<link rel=\"stylesheet\" href=\"http://static.jquery.com/files/rocker/css/screen.css\" type=\"text/css\" />\n\t\t<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js\"></script>\n\t\t<script>!window.jQuery && document.write('<script src=\"http://code.jquery.com/jquery-1.4.2.min.js\"><\\/script>');</script>\n\t\t<script src=\"http://static.jquery.com/files/rocker/scripts/custom.js\"></script>\n\t\t<link rel=\"alternate\" type=\"application/rss+xml\" title=\"jQuery Blog\" href=\"http://jquery.com/blog/feed/\" />\n\t\t<link rel=\"shortcut icon\" href=\"http://static.jquery.com/favicon.ico\" type=\"image/x-icon\"/>\n\t</head>\n\t<body>\n\t<div id=\"jq-siteContain\">\n\t\t\t<div id=\"jq-header\">\n\t\t\t\t<a id=\"jq-siteLogo\" href=\"http://jquery.com\" title=\"jQuery Home\"><img src=\"http://static.jquery.com/files/rocker/images/logo_jquery_215x53.gif\" width=\"215\" height=\"53\" alt=\"jQuery: Write Less, Do More.\" /></a>\n\n\t\t\t\t<div id=\"jq-primaryNavigation\">\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t<li class=\"jq-jquery jq-current\"><a href=\"http://jquery.com/\" title=\"jQuery Home\">jQuery</a></li>\n\t\t\t\t\t\t<li class=\"jq-ui\"><a href=\"http://jqueryui.com/\" title=\"jQuery UI\">UI</a></li>\n\t\t\t\t\t\t<li class=\"jq-mobile\"><a href=\"http://jquerymobile.com/\" title=\"jQuery Mobile\">Mobile</a></li>\n\t\t\t\t\t\t<li class=\"jq-plugins\"><a href=\"http://plugins.jquery.com/\" title=\"jQuery Plugins\">Plugins</a></li>\n\t\t\t\t\t\t<li class=\"jq-meetup\"><a href=\"http://meetups.jquery.com/\" title=\"jQuery Meetups\">Meetups</a></li>\n\t\t\t\t\t\t<li class=\"jq-forum\"><a href=\"http://forum.jquery.com/\" title=\"jQuery Forum\">Forum</a></li>\n\t\t\t\t\t\t<li class=\"jq-blog\"><a href=\"http://blog.jquery.com/\" title=\"jQuery Blog\">Blog</a></li>\n\t\t\t\t\t\t<li class=\"jq-about\"><a href=\"http://jquery.org/about\" title=\"About jQuery\">About</a></li>\n\t\t\t\t\t\t<li class=\"jq-donate\"><a href=\"http://jquery.org/donate\" title=\"Donate to jQuery\">Donate</a></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div><!-- /#primaryNavigation -->\n\n\t\t\t\t<div id=\"jq-secondaryNavigation\">\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t<li class=\"jq-download jq-first\"><a href=\"http://docs.jquery.com/Downloading_jQuery\">Download</a></li>\n\n\t\t\t\t\t\t<li class=\"jq-documentation\"><a href=\"http://docs.jquery.com\">Documentation</a></li>\n\t\t\t\t\t\t<li class=\"jq-tutorials\"><a href=\"http://docs.jquery.com/Tutorials\">Tutorials</a></li>\n\t\t\t\t\t\t<li class=\"jq-bugTracker\"><a href=\"http://dev.jquery.com/\">Bug Tracker</a></li>\n\t\t\t\t\t\t<li class=\"jq-discussion jq-last\"><a href=\"http://docs.jquery.com/Discussion\">Discussion</a></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div><!-- /#secondaryNavigation -->\n\n\n\n\t\t\t</div><!-- /#header -->\n\n\t\t\t<div id=\"jq-content\" class=\"jq-clearfix\">\n\n\t\t\t\t<div id=\"jq-intro\" class=\"jq-clearfix\">\n\t\t\t\t\t<h2><span class=\"jq-jquery\"><span>jQuery</span></span> is a new kind of JavaScript Library.</h2>\n\t\t\t\t\t<p>jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. <strong>jQuery is designed to change the way that you write JavaScript.</strong></p>\n\t\t\t\t\t<ul class=\"jq-checkpoints jq-clearfix\">\n\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Tutorials\" title=\"Lightweight Footprint\" class=\"jq-thickbox\">Lightweight Footprint</a>\n\t\t\t\t\t\t\t<div class=\"jq-checkpointSubhead\">\n\n\t\t\t\t\t\t\t\t<p>About 31KB in size <em>(Minified and Gzipped)</em></p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Tutorials\" title=\"CSS3 Compliant\" class=\"jq-thickbox\">CSS3 Compliant</a>\n\t\t\t\t\t\t\t<div class=\"jq-checkpointSubhead\">\n\t\t\t\t\t\t\t\t<p>Supports CSS 1-3 selectors and more!</p>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Tutorials\" title=\"Cross-browser\" class=\"jq-thickbox\">Cross-browser</a>\n\t\t\t\t\t\t\t<div class=\"jq-checkpointSubhead\">\n\t\t\t\t\t\t\t\t<p>IE 6.0+, FF 3.6+, Safari 5.0+, Opera, Chrome</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div><!-- /#intro -->\n\n\t\t\t\t<div id=\"jq-download\">\n\t\t\t\t\t<h2>Grab the latest version!</h2>\n\t\t\t\t\t<form action=\"\" method=\"get\">\n\t\t\t\t\t\t<fieldset>\n\t\t\t\t\t\t\t<legend>Choose your compression level:</legend>\n\t\t\t\t\t\t\t<div id=\"jq-compression\" class=\"jq-clearfix\">\n\t\t\t\t\t\t\t\t<input type=\"radio\" name=\"name\" value=\"http://code.jquery.com/jquery-1.7.2.min.js\" id=\"jq-production\" checked=\"checked\" />\n\t\t\t\t\t\t\t\t<a class=\"jq-radioToggle name jq-checked\" href=\"http://code.jquery.com/jquery-1.7.2.min.js\">jquery-1.7.2.min.js</a>\n\t\t\t\t\t\t\t\t<label for=\"jq-production\">Production <em>(<strong>32KB</strong>, Minified and Gzipped)</em></label>\n\t\t\t\t\t\t\t\t<input type=\"radio\" name=\"name\" value=\"http://code.jquery.com/jquery-1.7.2.js\" id=\"jq-development\" />\n\t\t\t\t\t\t\t\t<a class=\"jq-radioToggle name\" href=\"http://code.jquery.com/jquery-1.7.2.js\">jquery-1.7.2.js</a>\n\t\t\t\t\t\t\t\t<label for=\"jq-development\">Development <em>(<strong>247KB</strong>, Uncompressed Code)</em></label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<button type=\"submit\" name=\"downloadBtn\" id=\"jq-downloadBtn\"><span>Download</span></button>\n\t\t\t\t\t\t\t<p class=\"jq-version\"><strong>Current Release:</strong> v1.7.2</p>\n\t\t\t\t\t\t</fieldset>\n\t\t\t\t\t</form>\n\t\t\t\t\t<script>\n\t\t\t\t\tjQuery(\"#jq-download form\").submit(function(){\n\t\t\t\t\t\twindow.location = jQuery(this).find(\"input:checked\").val();\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t});\n\t\t\t\t\t</script>\n\t\t\t\t</div><!-- /#download -->\n\n\t\t\t\t<div id=\"jq-whosUsing\">\n\t\t\t\t\t<h2 class=\"jq-whosUsing\">Who's using jQuery?</h2>\n\t\t\t\t\t<ul class=\"jq-whosUsing\">\n\t\t\t\t\t\t<li><a href=\"http://www.google.com\" class=\"jq-google\" title=\"Google\">Google</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.dell.com\" class=\"jq-dell\" title=\"Dell\">Dell</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.bankofamerica.com\" class=\"jq-boa\" title=\"Bank of America\">Bank of America</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.mlb.com\" class=\"jq-mlb\" title=\"Major League Baseball\">Major League Baseball</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.digg.com\" class=\"jq-digg\" title=\"Digg\">Digg</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.nbc.com\" class=\"jq-nbc\" title=\"NBC\">NBC</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.cbs.com\" class=\"jq-cbs\" title=\"CBS News\">CBS News</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.netflix.com\" class=\"jq-netflix\" title=\"Netflix\">Netflix</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.technorati.com\" class=\"jq-technorati\" title=\"Technorati\">Technorati</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.mozilla.org\" class=\"jq-mozilla\" title=\"Mozilla\">Mozilla</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.wordpress.org\" class=\"jq-wordpress\" title=\"Wordpress\">Wordpress</a></li>\n\t\t\t\t\t\t<li><a href=\"http://www.drupal.org\" class=\"jq-drupal\" title=\"Drupal\">Drupal</a></li>\n\t\t\t\t\t</ul>\n        </div><!-- /#jq-whosUsing -->\n\n\n\t\t\t\t<div id=\"jq-learnjQuery\" class=\"jq-clearfix\">\n\n\t\t\t\t\t<div id=\"jq-learnNow\">\n\t\t\t\t\t\t<h2>Learn <span class=\"jq-jquery\"><span>jQuery</span></span> Now!</h2>\n\t\t\t\t\t\t<p>What does jQuery code look like? Here's the quick and dirty:</p>\n\t\t\t\t\t\t<div class=\"jq-codeDemo jq-clearfix\">\n\t\t\t\t\t\t\t<pre><code>$(\"p.neat\").addClass(\"ohmy\").show(\"slow\");</code></pre>\n\t\t\t\t\t\t\t<a href=\"http://docs.jquery.com/Tutorials\" class=\"jq-runCode\">Run Code</a>\n\n\t\t\t\t\t\t\t<p class=\"neat\"><strong>Congratulations!</strong> You just ran a snippet of jQuery code. Wasn't that easy? There's lots of example code throughout the <strong><a href=\"http://docs.jquery.com/\">documentation</a></strong> on this site. Be sure to give all the code a test run to see what happens.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div><!-- /#learnNow -->\n\n\n\n\t\t\t\t\t\t<div id=\"jq-resources\" class=\"clearfix\">\n\t\t\t\t\t<h2>jQuery Resources</h2>\n\n\t\t\t\t\t<div class=\"jq-gettingStarted\">\n\t\t\t\t\t\t<h3>Getting Started With jQuery</h3>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/How_jQuery_Works\">How jQuery Works</a></li>\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Tutorials\">Tutorials</a></li>\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Using_jQuery_with_Other_Libraries\">Using jQuery with other libraries</a></li>\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/\">jQuery Documentation</a></li>\n\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"jq-devResources\">\n\t\t\t\t\t\t<h3>Developer Resources</h3>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Discussion\">Mailing List</a></li>\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Downloading_jQuery\">Source code / Git</a></li>\n\n\t\t\t\t\t\t\t<li><a href=\"http://docs.jquery.com/Plugins/Authoring\">Plugin Authoring</a></li>\n\t\t\t\t\t\t\t<li><a href=\"http://dev.jquery.com/newticket/\">Submit a New Bug Report</a></li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</div>\n\t\t\t\t\t</div><!-- /#resources -->\n\n\t\t\t\t</div><!-- /#learnjQuery -->\n\n\t\t\t\t<div id=\"jq-books\" style=\"width:auto; float: none\">\n\t\t\t\t\t<h2>Books About jQuery</h2>\n\n\t\t\t\t\t<ul>\n\t\t\t\t\t  <li class=\"jq-clearfix\" style=\"width:270px;float:left;clear:none;\">\n\t\t\t\t\t\t\t<a href=\"http://link.packtpub.com/S3Fr9Q\" class=\"jq-bookImg\"><img src=\"http://learningjquery.kswedberg.netdna-cdn.com/wp-content/themes/ljq/images/ljq3rded.jpg\" alt=\"Learning jQuery Third Edition\" width=\"55\" height=\"70\" /></a>\n\t\t\t\t\t\t\t<h3><a href=\"http://link.packtpub.com/S3Fr9Q\">Learning jQuery Third Edition</a></h3>\n\t\t\t\t\t\t\t<div class=\"jq-author\">Karl Swedberg and <br />Jonathan Chaffer</div>\n\t\t\t\t\t\t\t<a href=\"http://link.packtpub.com/S3Fr9Q\" class=\"jq-buyNow\">Buy Now</a>\n\t\t\t\t\t\t</li>\n  \t\t\t\t\t<li class=\"jq-clearfix\" style=\"width:270px;float:left;clear:none;\">\n  \t\t\t\t\t\t\t<a href=\"http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15\" class=\"jq-bookImg\"><img src=\"http://static.jquery.com/books/jquery-animation-beginners-guide.jpg\" alt=\"jQuery 1.4 Animation Techniques: Beginners Guide\" width=\"55\" height=\"70\" /></a>\n  \t\t\t\t\t\t\t<h3><a href=\"http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15\">jQuery 1.4 Animation Techniques: Beginners Guide</a></h3>\n  \t\t\t\t\t\t\t<div class=\"jq-author\">Dan Wellman</div>\n  \t\t\t\t\t\t\t<a href=\"http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15\" class=\"jq-buyNow\">Buy Now</a>\n  \t\t\t\t\t\t</li>\n\t\t\t\t\t  <li class=\"jq-clearfix\" style=\"width:270px;float:left;clear:none;\">\n  \t\t\t\t\t\t\t<a href=\"http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz\" class=\"jq-bookImg\"><img src=\"http://static.jquery.com/books/jquery-plugin-developers-guide_thumb.jpg\" alt=\"jQuery Plugin Development Beginner's Guide\" width=\"55\" height=\"70\" /></a>\n  \t\t\t\t\t\t\t<h3><a href=\"http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz\">jQuery Plugin Development Beginner's Guide</a></h3>\n  \t\t\t\t\t\t\t<div class=\"jq-author\">Guilio Bai</div>\n  \t\t\t\t\t\t\t<a href=\"http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz\" class=\"jq-buyNow\">Buy Now</a>\n  \t\t\t\t\t\t</li>\n\n\t\t\t\t\t\t<li class=\"jq-clearfix\" style=\"width:270px;float:left;clear:left;\">\n\t\t\t\t\t\t\t<a href=\"http://www.manning.com/affiliate/idevaffiliate.php?id=648_176\" class=\"jq-bookImg\"><img src=\"http://static.jquery.com/books/jquery-in-action-2ed_thumb.jpg\" alt=\"jQuery in Action\" width=\"55\" height=\"70\" /></a>\n\t\t\t\t\t\t\t<h3><a href=\"http://www.manning.com/affiliate/idevaffiliate.php?id=648_176\">jQuery in Action</a></h3>\n\t\t\t\t\t\t\t<div class=\"jq-author\">Bear Bibeault\n\t\t\t\t\t\t\t  <br />and Yehuda Katz</div>\n\t\t\t\t\t\t\t<a href=\"http://www.manning.com/affiliate/idevaffiliate.php?id=648_176\" class=\"jq-buyNow\">Buy Now</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li class=\"jq-clearfix\" style=\"width:270px;float:left;clear:none;\">\n\t\t\t\t\t\t  <a class=\"jq-bookImg\" href=\"http://jqueryenlightenment.com/\"><img src=\"http://static.jquery.com/books/jquery-enlightenment_thumb.jpg\" alt=\"jQuery Enlightenment\" width=\"55\" height=\"70\" /></a>\n\t\t\t\t\t\t\t<h3><a href=\"http://jqueryenlightenment.com/\">jQuery Enlightenment</a></h3>\n\t\t\t\t\t\t\t<div class=\"jq-author\">Cody Lindley</div>\n\t\t\t\t\t\t\t<a href=\"http://jqueryenlightenment.com/\" class=\"jq-buyNow\">Buy Now</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\n\t\t\t\t</div><!-- /#news -->\n\n\n\n\t\t\t</div><!-- /#content -->\n\n\n\t\t\t<div id=\"jq-footer\" class=\"jq-clearfix\">\n\n\t\t\t\t<div id=\"jq-credits\">\n\t\t\t\t\t<p id=\"jq-copyright\">&copy; 2010 <a href=\"http://jquery.org/\">The jQuery Project</a></p>\n\t\t\t\t\t<p id=\"jq-hosting\">Sponsored by <a href=\"http://mediatemple.net\" class=\"jq-mediaTemple\">Media Temple</a> and <a href=\"http://jquery.org/sponsors\">others</a>.</p>\n\t\t\t\t</div>\n\n\t\t\t\t<div id=\"jq-footerNavigation\">\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t<li class=\"jq-download jq-first\"><a href=\"http://docs.jquery.com/Downloading_jQuery\">Download</a></li>\n\t\t\t\t\t\t<li class=\"jq-documentation\"><a href=\"http://docs.jquery.com\">Documentation</a></li>\n\n\t\t\t\t\t\t<li class=\"jq-tutorials\"><a href=\"http://docs.jquery.com/Tutorials\">Tutorials</a></li>\n\t\t\t\t\t\t<li class=\"jq-bugTracker\"><a href=\"http://dev.jquery.com/\">Bug Tracker</a></li>\n\t\t\t\t\t\t<li class=\"jq-discussion jq-last\"><a href=\"http://docs.jquery.com/Discussion\">Discussion</a></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div><!-- /#secondaryNavigation -->\n\n\t\t\t</div><!-- /#footer -->\n\t</div><!-- /#siteContain -->\n\t<script src=\"http://static.jquery.com/donate/donate.js\" type=\"text/javascript\"></script>\n\t<script type=\"text/javascript\">\n\tvar _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-1076265-1']); _gaq.push(['_trackPageview']); _gaq.push(['_setDomainName', '.jquery.com']);\n\t(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;\n\tga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';\n\t(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);})();\n\t</script>\n\t</body>\n</html>\n"
  },
  {
    "path": "examples/html-parser/jquery_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc equal(u, v []string) bool {\n\tif len(u) != len(v) {\n\t\treturn false\n\t}\n\tfor i := range u {\n\t\tif u[i] != v[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc readFile(fname string, t *testing.T) string {\n\tb, err := os.ReadFile(fname)\n\tif err != nil {\n\t\tt.Fatal(\"readFile\", err)\n\t}\n\treturn string(b)\n}\n\nfunc TestDefectiveScriptParser(t *testing.T) {\n\tif l := len(findScriptSrc(`<!DOCTYPE HTML>\n    <html>\n    <body>\n\n    <video width=\"320\" height=\"240\" controls=\"controls\">\n      <source src=\"movie.mp4\" type=\"video/mp4\" />\n\t<source src=\"movie.ogg\" type=\"video/ogg\" />\n\t  <source src=\"movie.webm\" type=\"video/webm\" />\n\t  Your browser does not support the video tag.\n\t  </video>\n\n\t  </body>\n\t  </html>`)); l != 0 {\n\t\tt.Fail()\n\t}\n\turls := findScriptSrc(readFile(\"w3schools.html\", t))\n\tif !equal(urls, []string{\"http://partner.googleadservices.com/gampad/google_service.js\",\n\t\t\"//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit\"}) {\n\t\tt.Error(\"w3schools.html\", \"src scripts are not recognized\", urls)\n\t}\n\turls = findScriptSrc(readFile(\"jquery_homepage.html\", t))\n\tif !equal(urls, []string{\"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js\",\n\t\t\"http://code.jquery.com/jquery-1.4.2.min.js\",\n\t\t\"http://static.jquery.com/files/rocker/scripts/custom.js\",\n\t\t\"http://static.jquery.com/donate/donate.js\"}) {\n\t\tt.Error(\"jquery_homepage.html\", \"src scripts are not recognized\", urls)\n\t}\n}\n\nfunc proxyWithLog() (*http.Client, *bytes.Buffer) {\n\tproxy := NewJQueryVersionProxy()\n\tproxyServer := httptest.NewServer(proxy)\n\tbuf := new(bytes.Buffer)\n\tproxy.Logger = log.New(buf, \"\", 0)\n\tproxyUrl, _ := url.Parse(proxyServer.URL)\n\ttr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}\n\tclient := &http.Client{Transport: tr}\n\treturn client, buf\n}\n\nfunc get(t *testing.T, server *httptest.Server, client *http.Client, url string) {\n\tresp, err := client.Get(server.URL + url)\n\tif err != nil {\n\t\tt.Fatal(\"cannot get proxy\", err)\n\t}\n\tio.ReadAll(resp.Body)\n\tresp.Body.Close()\n}\n\nfunc TestProxyServiceTwoVersions(t *testing.T) {\n\tvar fs = httptest.NewServer(http.FileServer(http.Dir(\".\")))\n\tdefer fs.Close()\n\n\tclient, buf := proxyWithLog()\n\n\tget(t, fs, client, \"/w3schools.html\")\n\tget(t, fs, client, \"/php_man.html\")\n\tif buf.String() != \"\" &&\n\t\t!strings.Contains(buf.String(), \" uses jquery \") {\n\t\tt.Error(\"shouldn't warn on a single URL\", buf.String())\n\t}\n\tget(t, fs, client, \"/jquery1.html\")\n\twarnings := buf.String()\n\tif !strings.Contains(warnings, \"http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js\") ||\n\t\t!strings.Contains(warnings, \"jquery.1.4.js\") ||\n\t\t!strings.Contains(warnings, \"Contradicting\") {\n\t\tt.Error(\"contradicting jquery versions (php_man.html, w3schools.html) does not issue warning\", warnings)\n\t}\n}\n\nfunc TestProxyService(t *testing.T) {\n\tvar fs = httptest.NewServer(http.FileServer(http.Dir(\".\")))\n\tdefer fs.Close()\n\n\tclient, buf := proxyWithLog()\n\n\tget(t, fs, client, \"/jquery_homepage.html\")\n\twarnings := buf.String()\n\tif !strings.Contains(warnings, \"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js\") ||\n\t\t!strings.Contains(warnings, \"http://code.jquery.com/jquery-1.4.2.min.js\") ||\n\t\t!strings.Contains(warnings, \"Contradicting\") {\n\t\tt.Error(\"contradicting jquery versions does not issue warning\")\n\t}\n}\n"
  },
  {
    "path": "examples/html-parser/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/ext/html\"\n\t\"log\"\n\t\"net/http\"\n\t\"regexp\"\n)\n\nvar (\n\t// who said we can't parse HTML with regexp?\n\tscriptMatcher  = regexp.MustCompile(`(?i:<script\\s+)`)\n\tsrcAttrMatcher = regexp.MustCompile(`^(?i:[^>]*\\ssrc=[\"']([^\"']*)[\"'])`)\n)\n\n// findScripts returns all sources of HTML script tags found in input text.\nfunc findScriptSrc(html string) []string {\n\tsrcs := make([]string, 0)\n\tmatches := scriptMatcher.FindAllStringIndex(html, -1)\n\tfor _, match := range matches {\n\t\t// -1 to capture the whitespace at the end of the script tag\n\t\tsrcMatch := srcAttrMatcher.FindStringSubmatch(html[match[1]-1:])\n\t\tif srcMatch != nil {\n\t\t\tsrcs = append(srcs, srcMatch[1])\n\t\t}\n\t}\n\treturn srcs\n}\n\n// NewJQueryVersionProxy creates a proxy checking responses HTML content, looks\n// for scripts referencing jQuery library and emits warnings if different\n// versions of the library are being used for a given host.\nfunc NewJQueryVersionProxy() *goproxy.ProxyHttpServer {\n\tproxy := goproxy.NewProxyHttpServer()\n\tm := make(map[string]string)\n\tjqueryMatcher := regexp.MustCompile(`(?i:jquery\\.)`)\n\tproxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString(\n\t\tfunc(s string, ctx *goproxy.ProxyCtx) string {\n\t\t\tfor _, src := range findScriptSrc(s) {\n\t\t\t\tif !jqueryMatcher.MatchString(src) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprev, ok := m[ctx.Req.Host]\n\t\t\t\tif ok {\n\t\t\t\t\tif prev != src {\n\t\t\t\t\t\tctx.Warnf(\"In %v, Contradicting jqueries %v %v\",\n\t\t\t\t\t\t\tctx.Req.URL, prev, src)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tctx.Warnf(\"%s uses jquery %s\", ctx.Req.Host, src)\n\t\t\t\t\tm[ctx.Req.Host] = src\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn s\n\t\t}))\n\treturn proxy\n}\n\nfunc main() {\n\tproxy := NewJQueryVersionProxy()\n\tlog.Fatal(http.ListenAndServe(\":8080\", proxy))\n}\n"
  },
  {
    "path": "examples/html-parser/php_man.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n                      \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head profile=\"http://purl.org/NET/erdf/profile\">\n <title>PHP: PHP Manual - Manual</title>\n <style type=\"text/css\" media=\"all\">\n  @import url(\"http://static.php.net/www.php.net/styles/site.css\");\n  @import url(\"http://static.php.net/www.php.net/styles/phpnet.css\");\n  \n </style>\n <!--[if IE]><![if gte IE 6]><![endif]-->\n  <style type=\"text/css\" media=\"print\">\n   @import url(\"http://static.php.net/www.php.net/styles/print.css\");\n  </style>\n <!--[if IE]><![endif]><![endif]-->\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n <link rel=\"shortcut icon\" href=\"http://static.php.net/www.php.net/favicon.ico\" />\n <link rel=\"contents\" href=\"index.php\" />\n <link rel=\"index\" href=\"\" />\n <link rel=\"prev\" href=\"\" />\n <link rel=\"next\" href=\"\" />\n <link rel=\"schema.dc\" href=\"http://purl.org/dc/elements/1.1/\" />\n <link rel=\"schema.rdfs\" href=\"http://www.w3.org/2000/01/rdf-schema#\" />\n <link rev=\"canonical\" rel=\"self alternate shorter shorturl shortlink\" href=\"http://php.net/index\" />\n <link rel=\"license\" href=\"http://creativecommons.org/licenses/by/3.0/\" about=\"#content\" />\n <link rel=\"alternate\" href=\"/manual/en/feeds/index.atom\" type=\"application/atom+xml\" />\n <link rel=\"canonical\" href=\"http://php.net/manual/en/index.php\" />\n <script type=\"text/javascript\" src=\"http://static.php.net/www.php.net/userprefs.js\"></script>\n <base href=\"http://www.php.net/manual/en/index.php\" />\n <meta http-equiv=\"Content-language\" content=\"en\" />\n            <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js\"></script>\n            <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js\"></script>\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    var toggleImage = function(elem) {\n        if ($(elem).hasClass(\"shown\")) {\n            $(elem).removeClass(\"shown\").addClass(\"hidden\");\n            $(\"img\", elem).attr(\"src\", \"/images/notes-add.gif\");\n        }\n        else {\n            $(elem).removeClass(\"hidden\").addClass(\"shown\");\n            $(\"img\", elem).attr(\"src\", \"/images/notes-reject.gif\");\n        }\n    };\n\n    $(\".refsect1 h3.title\").each(function() {\n        url = \"https://bugs.php.net/report.php?bug_type=Documentation+problem&amp;manpage=\" + $(this).parent().parent().attr(\"id\") + \"%23\" + $(this).parent().attr(\"id\");\n        $(this).parent().prepend(\"<div class='reportbug'><a href='\" + url + \"'>Report a bug</a></div>\");\n        $(this).prepend(\"<a class='toggler shown' href='#'><img src='/images/notes-reject.gif' alt='reject note' /></a> \");\n    });\n    $(\"#usernotes .head\").each(function() {\n        $(this).prepend(\"<a class='toggler shown' href='#'><img src='/images/notes-reject.gif' alt='reject note' /></a> \");\n    });\n    $(\".refsect1 h3.title .toggler\").click(function() {\n        $(this).parent().siblings().slideToggle(\"slow\");\n        toggleImage(this);\n        return false;\n    });\n    $(\"#usernotes .head .toggler\").click(function() {\n        $(this).parent().next().slideToggle(\"slow\");\n        toggleImage(this);\n        return false;\n    });\n});\n</script>\n\n</head>\n<body>\n\n<div id=\"headnav\">\n <a href=\"/\" rel=\"home\"><img src=\"http://static.php.net/www.php.net/images/php.gif\"\n alt=\"PHP\" width=\"120\" height=\"67\" id=\"phplogo\" /></a>\n <div id=\"headmenu\">\n  <a href=\"/downloads.php\">downloads</a> |\n  <a href=\"/docs.php\">documentation</a> |\n  <a href=\"/FAQ.php\">faq</a> |\n  <a href=\"/support.php\">getting help</a> |\n  <a href=\"/mailing-lists.php\">mailing lists</a> |\n  <a href=\"/license\">licenses</a> |\n  <a href=\"https://wiki.php.net/\">wiki</a> |\n  <a href=\"https://bugs.php.net/\">reporting bugs</a> |\n  <a href=\"/sites.php\">php.net sites</a> |\n  <a href=\"/links.php\">links</a> |\n  <a href=\"/conferences/\">conferences</a> |\n  <a href=\"/my.php\">my php.net</a>\n </div>\n</div>\n\n<div id=\"headsearch\">\n <form method=\"post\" action=\"/search.php\" id=\"topsearch\">\n  <p>\n   <span title=\"Keyboard shortcut: Alt+S (Win), Ctrl+S (Apple)\">\n    <span class=\"shortkey\">s</span>earch for\n   </span>\n   <input type=\"text\" name=\"pattern\" value=\"\" size=\"30\" accesskey=\"s\" />\n   <span>in the</span>\n   <select name=\"show\">\n    <option value=\"all\"      >all php.net sites</option>\n    <option value=\"local\"    >this mirror only</option>\n    <option value=\"quickref\" selected=\"selected\">function list</option>\n    <option value=\"manual\"   >online documentation</option>\n    <option value=\"bugdb\"    >bug database</option>\n    <option value=\"news_archive\">Site News Archive</option>\n    <option value=\"changelogs\">All Changelogs</option>\n    <option value=\"pear\"     >just pear.php.net</option>\n    <option value=\"pecl\"     >just pecl.php.net</option>\n    <option value=\"talks\"    >just talks.php.net</option>\n    <option value=\"maillist\" >general mailing list</option>\n    <option value=\"devlist\"  >developer mailing list</option>\n    <option value=\"phpdoc\"   >documentation mailing list</option>\n   </select>\n   <input type=\"image\"\n          src=\"http://static.php.net/www.php.net/images/small_submit_white.gif\"\n          class=\"submit\" alt=\"search\" />\n   <input type=\"hidden\" name=\"lang\" value=\"en\" />\n  </p>\n </form>\n</div>\n\n<div id=\"layout_2\">\n <div id=\"leftbar\">\n<!--UdmComment-->\n<ul class=\"toc\">\n <li class=\"header home\"><a href=\"index.php\">PHP Manual</a></li>\n</ul><!--/UdmComment-->\n\n </div>\n <div id=\"content\" class=\"manual/en\">\n<!--UdmComment-->\n<div class=\"manualnavbar manualnavbar_top\">\n <span class=\"next\">\n </span>\n <span class=\"prev\">\n  &nbsp;\n </span>\n <hr />\n <span class=\"lastupdated\">[<a href=\"https://edit.php.net/?project=PHP&amp;perm=en/index.php\">edit</a>] Last updated: Fri, 23 Mar 2012</span>\n <div class=\"langchooser\">\n  <form action=\"/manual/change.php\" method=\"get\">\n   <p>view this page in </p><fieldset><select name=\"page\">\n    <option value=\"pt_BR/index.php\">Brazilian Portuguese</option>\n    <option value=\"zh/index.php\">Chinese (Simplified)</option>\n    <option value=\"fr/index.php\">French</option>\n    <option value=\"de/index.php\">German</option>\n    <option value=\"ja/index.php\">Japanese</option>\n    <option value=\"pl/index.php\">Polish</option>\n    <option value=\"ro/index.php\">Romanian</option>\n    <option value=\"ru/index.php\">Russian</option>\n    <option value=\"fa/index.php\">Persian</option>\n    <option value=\"es/index.php\">Spanish</option>\n    <option value=\"tr/index.php\">Turkish</option>\n    <option value=\"help-translate.php\">Other</option>\n   </select>\n   <input type=\"image\" src=\"http://static.php.net/www.php.net/images/small_submit.gif\" id=\"changeLangImage\" alt=\"Change language\" />\n  </fieldset></form>\n </div>\n</div>\n<!--/UdmComment-->\n\n<div id=\"index\" class=\"set\">\n <h1 class=\"title\">PHP Manual</h1>\n \n\n\n <div class=\"info\">\n  <div class=\"authorgroup\" id=\"authors\">\n\n   <div class=\"author vcard\"><strong class=\"by\">by</strong>:<br />\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Mehdi</span>  <span class=\"surname family-name\">Achour</span>  \n   </span>\n   </div>\n\n   \n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Friedhelm</span>  <span class=\"surname family-name\">Betz</span>  \n   </span>\n   </div>\n\n\n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Antony</span>  <span class=\"surname family-name\">Dovgal</span>  \n   </span>\n   </div>\n\n   \n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Nuno</span>  <span class=\"surname family-name\">Lopes</span>  \n   </span>\n   </div>\n\n\n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Hannes</span>  <span class=\"surname family-name\">Magnusson</span>  \n   </span>\n   </div>\n\n\n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Georg</span>  <span class=\"surname family-name\">Richter</span>  \n   </span>\n   </div>\n\n\n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Damien</span>  <span class=\"surname family-name\">Seguy</span>  \n   </span>\n   </div>\n\n\n   <div class=\"author vcard\">\n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Jakub</span>  <span class=\"surname family-name\">Vrana</span>  \n   </span>\n   </div>\n\n\n   \n   <div class=\"othercredit\">\n    <span class=\"personname fn\">\n      <span class=\"othername\">\n     <a href=\"preface.php#contributors\" class=\"link\">And several others</a>\n     </span> \n    </span>\n   </div>\n\n  </div>\n  <div class=\"pubdate\">2012-03-23</div>\n  \n  <div class=\"authorgroup\" id=\"editors\">\n   <div class=\"editor vcard\"><strong class=\"editedby\">Edited By</strong>: \n   <span class=\"personname fn\">\n     <span class=\"firstname given-name\">Philip</span>  <span class=\"surname family-name\">Olson</span>  \n   </span>\n   </div>\n\n  </div>\n\n  <div class=\"copyright\">&copy; \n   <span class=\"year\">1997-2012</span>\n   <span class=\"holder\">the PHP Documentation Group</span>\n  </div>\n\n  \n\n </div>\n \n\n\n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n<ul class=\"chunklist chunklist_set\"><li><a href=\"copyright.php\">Copyright</a></li><li><a href=\"manual.php\">PHP Manual</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"preface.php\">Preface</a></li></ul></li><li><a href=\"getting-started.php\">Getting Started</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"introduction.php\">Introduction</a></li><li><a href=\"tutorial.php\">A simple tutorial</a></li></ul></li><li><a href=\"install.php\">Installation and Configuration</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"install.general.php\">General Installation Considerations</a></li><li><a href=\"install.unix.php\">Installation on Unix systems</a></li><li><a href=\"install.macosx.php\">Installation on Mac OS X</a></li><li><a href=\"install.windows.php\">Installation on Windows systems</a></li><li><a href=\"install.cloud.php\">Installation on Cloud Computing platforms</a></li><li><a href=\"install.fpm.php\">FastCGI Process Manager (FPM)</a></li><li><a href=\"install.pecl.php\">Installation of PECL extensions</a></li><li><a href=\"install.problems.php\">Problems?</a></li><li><a href=\"configuration.php\">Runtime Configuration</a></li></ul></li><li><a href=\"langref.php\">Language Reference</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"language.basic-syntax.php\">Basic syntax</a></li><li><a href=\"language.types.php\">Types</a></li><li><a href=\"language.variables.php\">Variables</a></li><li><a href=\"language.constants.php\">Constants</a></li><li><a href=\"language.expressions.php\">Expressions</a></li><li><a href=\"language.operators.php\">Operators</a></li><li><a href=\"language.control-structures.php\">Control Structures</a></li><li><a href=\"language.functions.php\">Functions</a></li><li><a href=\"language.oop5.php\">Classes and Objects</a></li><li><a href=\"language.namespaces.php\">Namespaces</a></li><li><a href=\"language.exceptions.php\">Exceptions</a></li><li><a href=\"language.references.php\">References Explained</a></li><li><a href=\"reserved.variables.php\">Predefined Variables</a></li><li><a href=\"reserved.exceptions.php\">Predefined Exceptions</a></li><li><a href=\"reserved.interfaces.php\">Predefined Interfaces</a></li><li><a href=\"context.php\">Context options and parameters</a></li><li><a href=\"wrappers.php\">Supported Protocols and Wrappers</a></li></ul></li><li><a href=\"security.php\">Security</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"security.intro.php\">Introduction</a></li><li><a href=\"security.general.php\">General considerations</a></li><li><a href=\"security.cgi-bin.php\">Installed as CGI binary</a></li><li><a href=\"security.apache.php\">Installed as an Apache module</a></li><li><a href=\"security.filesystem.php\">Filesystem Security</a></li><li><a href=\"security.database.php\">Database Security</a></li><li><a href=\"security.errors.php\">Error Reporting</a></li><li><a href=\"security.globals.php\">Using Register Globals</a></li><li><a href=\"security.variables.php\">User Submitted Data</a></li><li><a href=\"security.magicquotes.php\">Magic Quotes</a></li><li><a href=\"security.hiding.php\">Hiding PHP</a></li><li><a href=\"security.current.php\">Keeping Current</a></li></ul></li><li><a href=\"features.php\">Features</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"features.http-auth.php\">HTTP authentication with PHP</a></li><li><a href=\"features.cookies.php\">Cookies</a></li><li><a href=\"features.sessions.php\">Sessions</a></li><li><a href=\"features.xforms.php\">Dealing with XForms</a></li><li><a href=\"features.file-upload.php\">Handling file uploads</a></li><li><a href=\"features.remote-files.php\">Using remote files</a></li><li><a href=\"features.connection-handling.php\">Connection handling</a></li><li><a href=\"features.persistent-connections.php\">Persistent Database Connections</a></li><li><a href=\"features.safe-mode.php\">Safe Mode</a></li><li><a href=\"features.commandline.php\">Command line usage</a> — Using PHP from the command line</li><li><a href=\"features.gc.php\">Garbage Collection</a></li></ul></li><li><a href=\"funcref.php\">Function Reference</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"refs.basic.php.php\">Affecting PHP's Behaviour</a></li><li><a href=\"refs.utilspec.audio.php\">Audio Formats Manipulation</a></li><li><a href=\"refs.remote.auth.php\">Authentication Services</a></li><li><a href=\"refs.calendar.php\">Date and Time Related Extensions</a></li><li><a href=\"refs.utilspec.cmdline.php\">Command Line Specific Extensions</a></li><li><a href=\"refs.compression.php\">Compression and Archive Extensions</a></li><li><a href=\"refs.creditcard.php\">Credit Card Processing</a></li><li><a href=\"refs.crypto.php\">Cryptography Extensions</a></li><li><a href=\"refs.database.php\">Database Extensions</a></li><li><a href=\"refs.fileprocess.file.php\">File System Related Extensions</a></li><li><a href=\"refs.international.php\">Human Language and Character Encoding Support</a></li><li><a href=\"refs.utilspec.image.php\">Image Processing and Generation</a></li><li><a href=\"refs.remote.mail.php\">Mail Related Extensions</a></li><li><a href=\"refs.math.php\">Mathematical Extensions</a></li><li><a href=\"refs.utilspec.nontext.php\">Non-Text MIME Output</a></li><li><a href=\"refs.fileprocess.process.php\">Process Control Extensions</a></li><li><a href=\"refs.basic.other.php\">Other Basic Extensions</a></li><li><a href=\"refs.remote.other.php\">Other Services</a></li><li><a href=\"refs.search.php\">Search Engine Extensions</a></li><li><a href=\"refs.utilspec.server.php\">Server Specific Extensions</a></li><li><a href=\"refs.basic.session.php\">Session Extensions</a></li><li><a href=\"refs.basic.text.php\">Text Processing</a></li><li><a href=\"refs.basic.vartype.php\">Variable and Type Related Extensions</a></li><li><a href=\"refs.webservice.php\">Web Services</a></li><li><a href=\"refs.utilspec.windows.php\">Windows Only Extensions</a></li><li><a href=\"refs.xml.php\">XML Manipulation</a></li></ul></li><li><a href=\"internals2.php\">PHP at the Core: A Hacker's Guide to the Zend Engine</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"internals2.preface.php\">Preface</a></li><li><a href=\"internals2.counter.php\">The &quot;counter&quot; Extension - A Continuing Example</a></li><li><a href=\"internals2.buildsys.php\">The PHP 5 build system</a></li><li><a href=\"internals2.structure.php\">Extension structure</a></li><li><a href=\"internals2.memory.php\">Memory management</a></li><li><a href=\"internals2.variables.php\">Working with variables</a></li><li><a href=\"internals2.funcs.php\">Writing functions</a></li><li><a href=\"internals2.objects.php\">Working with classes and objects</a></li><li><a href=\"internals2.resources.php\">Working with resources</a></li><li><a href=\"internals2.ini.php\">Working with INI settings</a></li><li><a href=\"internals2.streams.php\">Working with streams</a></li><li><a href=\"internals2.pdo.php\">PDO Driver How-To</a></li><li><a href=\"internals2.faq.php\">Extension FAQs</a></li><li><a href=\"internals2.apiref.php\">Zend Engine 2 API reference</a></li><li><a href=\"internals2.opcodes.php\">Zend Engine 2 Opcodes</a></li><li><a href=\"internals2.ze1.php\">Zend Engine 1</a></li></ul></li><li><a href=\"faq.php\">FAQ</a> — FAQ: Frequently Asked Questions<ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"faq.general.php\">General Information</a></li><li><a href=\"faq.mailinglist.php\">Mailing lists</a></li><li><a href=\"faq.obtaining.php\">Obtaining PHP</a></li><li><a href=\"faq.databases.php\">Database issues</a></li><li><a href=\"faq.installation.php\">Installation</a></li><li><a href=\"faq.build.php\">Build Problems</a></li><li><a href=\"faq.using.php\">Using PHP</a></li><li><a href=\"faq.passwords.php\">Password Hashing</a> — Safe Password Hashing</li><li><a href=\"faq.html.php\">PHP and HTML</a></li><li><a href=\"faq.com.php\">PHP and COM</a></li><li><a href=\"faq.languages.php\">PHP and other languages</a></li><li><a href=\"faq.migration5.php\">Migrating from PHP 4 to PHP 5</a></li><li><a href=\"faq.misc.php\">Miscellaneous Questions</a></li></ul></li><li><a href=\"appendices.php\">Appendices</a><ul class=\"chunklist chunklist_set chunklist_children\"><li><a href=\"history.php\">History of PHP and Related Projects</a></li><li><a href=\"migration54.php\">Migrating from PHP 5.3.x to PHP 5.4.x</a></li><li><a href=\"migration53.php\">Migrating from PHP 5.2.x to PHP 5.3.x</a></li><li><a href=\"migration52.php\">Migrating from PHP 5.1.x to PHP 5.2.x</a></li><li><a href=\"migration51.php\">Migrating from PHP 5.0.x to PHP 5.1.x</a></li><li><a href=\"migration5.php\">Migrating from PHP 4 to PHP 5.0.x</a></li><li><a href=\"oop4.php\">Classes and Objects (PHP 4)</a></li><li><a href=\"debugger.php\">Debugging in PHP</a></li><li><a href=\"configure.php\">Configure options</a></li><li><a href=\"ini.php\">php.ini directives</a></li><li><a href=\"extensions.php\">Extension List/Categorization</a></li><li><a href=\"aliases.php\">List of Function Aliases</a></li><li><a href=\"reserved.php\">List of Reserved Words</a></li><li><a href=\"resource.php\">List of Resource Types</a></li><li><a href=\"filters.php\">List of Available Filters</a></li><li><a href=\"transports.php\">List of Supported Socket Transports</a></li><li><a href=\"types.comparisons.php\">PHP type comparison tables</a></li><li><a href=\"tokens.php\">List of Parser Tokens</a></li><li><a href=\"userlandnaming.php\">Userland Naming Guide</a></li><li><a href=\"about.php\">About the manual</a></li><li><a href=\"cc.license.php\">Creative Commons Attribution 3.0</a></li><li><a href=\"indexes.php\">Index listing</a></li></ul></li></ul></div><br /><br />\n<div id=\"usernotes\">\n <div class=\"head\">\n  <span class=\"action\"><a href=\"/manual/add-note.php?sect=index&amp;redirect=http://www.php.net/manual/en/index.php\"><img src=\"http://static.php.net/www.php.net/images/notes-add.gif\" alt=\"add a note\" width=\"13\" height=\"13\" class=\"middle\" /></a> <small><a href=\"/manual/add-note.php?sect=index&amp;redirect=http://www.php.net/manual/en/index.php\">add a note</a></small></span>\n  <small>User Contributed Notes</small>\n  <strong>PHP Manual</strong>\n </div>\n <div class=\"note\">There are no user contributed notes for this page.</div></div><br />\n </div>\n <div class=\"cleaner\">&nbsp;</div>\n</div>\n\n<div id=\"footnav\">\n   <a href=\"/source.php?url=/manual/en/index.php\">show source</a> |\n <a href=\"/credits.php\">credits</a> |\n <a href=\"/stats/\">stats</a> |\n <a href=\"/sitemap.php\">sitemap</a> |\n <a href=\"/contact.php\">contact</a> |\n <a href=\"/contact.php#ads\">advertising</a> |\n <a href=\"/mirrors.php\">mirror sites</a>\n</div>\n\n<div id=\"pagefooter\">\n <div id=\"copyright\">\n  <a href=\"/copyright.php\">Copyright &copy; 2001-2012 The PHP Group</a><br />\n  All rights reserved.\n </div>\n\n <div id=\"thismirror\">\n  <a href=\"/mirror.php\">This mirror</a> generously provided by:\n  <a href=\"http://developer.yahoo.com/\">Yahoo! Inc.</a><br />\n  Last updated: Wed Mar 28 09:41:05 2012 UTC\n </div>\n</div>\n<!--[if IE 6]>\n<script type=\"text/javascript\">\n    /*Load jQuery if not already loaded*/ if(typeof jQuery == 'undefined'){ document.write(\"<script type=\\\"text/javascript\\\"   src=\\\"http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js\\\"></\"+\"script>\"); var __noconflict = true; }\n    var IE6UPDATE_OPTIONS = {\n        icons_path: \"/ie6update/images/\"\n    }\n</script>\n<script type=\"text/javascript\" src=\"/ie6update/ie6update.js\"></script>\n<![endif]-->\n</body>\n</html>"
  },
  {
    "path": "examples/html-parser/w3schools.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"en-US\">\n\n<head>\n\n\n\n<title>HTML5 Tutorial</title>\n\n \n\n<link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" />\n\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\" />\n\n<meta name=\"Keywords\" content=\"html,css,tutorial,html5,dhtml,css3,xsl,xslt,xhtml,javascript,jquery,asp,ado,net,vbscript,dom,sql,colors,soap,php,rss,authoring,programming,training,learning,quiz,beginner's guide,primer,lessons,school,howto,reference,examples,samples,source code,tags,demos,tips,links,FAQ,tag list,forms,frames,color table,w3c,cascading style sheets,active server pages,dynamic html,internet,database,development,Web building,Webmaster,html guide\" />\n\n<meta name=\"Description\" content=\"Free HTML XHTML CSS JavaScript jQuery XML DOM XSL XSLT RSS AJAX ASP .NET PHP SQL tutorials, references, examples for web building.\" />\n\n<script type=\"text/javascript\">\n\n<!--\n\nvar gaJsHost = ((\"https:\" == document.location.protocol) ? \"https://ssl.\" : \"http://www.\");\n\ndocument.write(unescape(\"%3Cscript src='\" + gaJsHost + \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n\n//-->\n\n</script>\n\n<script type=\"text/javascript\">\n\n<!--\n\nfunction searchfield_focus(obj)\n\n{\n\nobj.style.color=\"\"\n\nobj.style.fontStyle=\"\"\n\nif (obj.value==\"Search w3schools.com\")\n\n\t{obj.value=\"\"}\n\n}\n\nvar pageTracker = _gat._getTracker(\"UA-3855518-1\");\n\npageTracker._initData();\n\npageTracker._trackPageview();\n\nvar addr=document.location.href;\n\nfunction displayError()\n\n{\n\ndocument.getElementById(\"err_url\").value=addr;\n\ndocument.getElementById(\"err_form\").style.display=\"block\";\n\ndocument.getElementById(\"err_desc\").focus();\n\nhideSent();\n\n}\n\nfunction hideError()\n\n{\n\ndocument.getElementById(\"err_form\").style.display=\"none\";\n\n}\n\nfunction hideSent()\n\n{\n\ndocument.getElementById(\"err_sent\").style.display=\"none\";\n\n}\n\nfunction sendErr()\n\n{\n\nvar xmlhttp;\n\nvar err_url=document.getElementById(\"err_url\").value;\n\nvar err_email=document.getElementById(\"err_email\").value;\n\nvar err_desc=document.getElementById(\"err_desc\").value;\n\nif (window.XMLHttpRequest)\n\n  {// code for IE7+, Firefox, Chrome, Opera, Safari\n\n  xmlhttp=new XMLHttpRequest();\n\n  }\n\nelse\n\n  {// code for IE6, IE5\n\n  xmlhttp=new ActiveXObject(\"Microsoft.XMLHTTP\");\n\n  }\n\nxmlhttp.open(\"POST\",\"/err_sup.asp\",true);\n\nxmlhttp.setRequestHeader(\"Content-type\",\"application/x-www-form-urlencoded\");\n\nxmlhttp.send(\"err_url=\" + err_url + \"&err_email=\" + err_email + \"&err_desc=\" + escape(err_desc));\n\nhideError();\n\ndocument.getElementById(\"err_sent\").style.display=\"block\";\n\n}\n\nfunction click_expandingMenuHeader(obj,sectionName)\n\n{\n\nvar x=document.getElementById(\"cssprop_\" + sectionName).parentNode.className;\n\nif (x.indexOf(\"expandingMenuNotSelected\")>-1)\n\n\t{\n\n\tx=x.replace(\"expandingMenuNotSelected\",\"expandingMenuSelected\");\n\n\tdocument.getElementById(\"cssprop_\" + sectionName).parentNode.className=x;\n\n\tdocument.getElementById(\"cssprop_\" + sectionName).style.display=\"block\";\n\n\t}\n\nelse\n\n\t{\n\n\tx=x.replace(\"expandingMenuSelected\",\"expandingMenuNotSelected\");\n\n\tdocument.getElementById(\"cssprop_\" + sectionName).parentNode.className=x;\n\n\tdocument.getElementById(\"cssprop_\" + sectionName).style.display=\"none\";\n\n\t}\n\n}\n\n//-->\n\n</script>\n\n<!--[if lt IE 7]>\n\n<style>\n\n#leftcolumn{margin-left:0}\n\n</style>\n\n<![endif]-->\n\n<link rel=\"stylesheet\" type=\"text/css\" href=\"/stdtheme.css\" />\n\n<style>\n\n#html5headline\n\n{\n\nfont-size:24px;\n\ntext-align:center;\n\nbackground-color:#D9D9D9;\n\nmargin:0;\n\npadding:0;\n\nheight:32px;\n\n}\n\ndiv.html5header\n\n{\n\nborder:5px solid #D9D9D9;\n\nwidth:625px;\n\nheight:379px;\n\nmargin:0;\n\npadding:0;\n\n}\n\ndiv.html5header #header_image\n\n{\n\nwidth:290px;\n\nheight:235px;\n\nfloat:left;\n\nborder:10px solid #D9D9D9;\n\n}\n\ndiv.html5header #header_image div\n\n{\n\nwidth:290px;\n\nheight:235px;\n\nmargin:0;\n\npadding:0;\n\n}\n\n\n\ndiv.html5header #header_text\n\n{\n\nwidth:275px;\n\nheight:235px;\n\nfloat:left;\n\nmargin:0;\n\npadding:0;\n\npadding-left:10px;\n\npadding-right:10px;\n\nborder:10px solid #D9D9D9;\n\n}\n\ndiv.html5header #header_icons\n\n{\n\nwidth:628px;\n\nheight:84px;\n\nclear:both;\n\n}\n\n\n\ndiv.html5header #header_icons ul\n\n{\n\nlist-style-type:none;\n\nmargin:0;\n\npadding:0;\n\noverflow:hidden;\n\n}\n\ndiv.html5header #header_icons li\n\n{\n\nfloat:left;\n\nwidth:104px;\n\nheight:92px;\n\n}\n\ndiv.html5header #header_icons #header_html5{background:url('img_logo_map.gif') 0 0;background-repeat:no-repeat;}\n\ndiv.html5header #header_icons #header_multimedia{background:url('img_logo_map.gif') -104px 0;background-repeat:no-repeat;}\n\ndiv.html5header #header_icons #header_3d{background:url('img_logo_map.gif') -208px 0;background-repeat:no-repeat;}\n\ndiv.html5header #header_icons #header_offline{background:url('img_logo_map.gif') -312px 0;background-repeat:no-repeat;}\n\ndiv.html5header #header_icons #header_form{background:url('img_logo_map.gif') -418px 0;background-repeat:no-repeat;}\n\ndiv.html5header #header_icons #header_css3{background:url('img_logo_map.gif') -521px 0;background-repeat:no-repeat;}\n\n\n\n#header_text\n\n{\n\nfont-size:120%;\n\n}\n\n#header_text h2\n\n{\n\ntext-align:center;\n\n}\n\ndiv.html5header #header_icons #header_html5\n\n{\n\nbackground:url('img_logo_map.gif') 0 -92px;\n\nbackground-repeat:no-repeat;\n\n}\n\n</style>\n\n<script>\n\nselIcon=\"html5\";\n\nfunction changeHeader(logoId,imgPos)\n\n\t{\n\n\tx=document.getElementById(\"header_text\").childNodes;\n\n\tfor (i=0;i<x.length;i++)\n\n\t  {\n\n\t  if(x[i].nodeType==1)\n\n\t  \t{\n\n\t  \tx[i].style.display=\"none\";\n\n\t  \t}\n\n\t  }\n\n\tx=document.getElementById(\"header_image\").childNodes;\n\n\tfor (i=0;i<x.length;i++)\n\n\t  {\n\n\t  if(x[i].nodeType==1)\n\n\t  \t{\n\n\t  \tx[i].style.display=\"none\";\n\n\t  \t}\n\n\t  }\n\n  \ttry\n\n  \t\t{\n\n  \t\tdocument.getElementById('html5_vid').pause();\n\n  \t\t}\n\n  \tcatch(er)\n\n  \t\t{\n\n\t\n\n  \t\t}\n\n\n\n\tx=document.getElementById(\"header_icons_list\").childNodes;\n\n\t\n\n\tdocument.getElementById(\"header_html5\").style.backgroundPosition=\"0 0\"\n\n\tdocument.getElementById(\"header_multimedia\").style.backgroundPosition=\"-104px 0\"\n\n\tdocument.getElementById(\"header_3d\").style.backgroundPosition=\"-208px 0\"\n\n\tdocument.getElementById(\"header_offline\").style.backgroundPosition=\"-312px 0\"\n\n\tdocument.getElementById(\"header_form\").style.backgroundPosition=\"-418px 0\"\n\n\tdocument.getElementById(\"header_css3\").style.backgroundPosition=\"-521px 0\"\n\n\t\t\t\t\t\n\n\tbgText=\"header_text_\"+logoId;\n\n\tbgImage=\"header_image_\"+logoId;\n\n\tbgLogo=\"header_\"+logoId;\n\n\tdocument.getElementById(bgImage).style.display=\"block\";\n\n\tdocument.getElementById(bgText).style.display=\"inline\";\t\n\n\tdocument.getElementById(bgLogo).style.backgroundPosition=imgPos+\"px -92px\"\n\n\t}\n\nfunction iconHover(logoId,imgPos)\n\n\t{\n\n\tbgLogo=\"header_\"+logoId;\n\n\tdocument.getElementById(bgLogo).style.backgroundPosition=imgPos+\"px -92px\"\n\n\t}\n\nfunction iconHoverOut(logoId,imgPos)\n\n\t{\n\n\tif (selIcon!=logoId)\n\n\t\t{\n\n\t\tbgLogo=\"header_\"+logoId;\n\n\t\tdocument.getElementById(bgLogo).style.backgroundPosition=imgPos+\"px 0\"\n\n\t\t}\n\n\t}\n\n\t\n\nfunction playVideo()\n\n{\n\ntry\n\n\t{\n\n\tdocument.getElementById('html5_vid').play()\n\n\t}\n\ncatch(er)\n\n\t{\n\n\tdocument.getElementById('header_image_multimedia').innerHTML=\"\";\n\n\tdocument.getElementById('header_image_multimedia').style.backgroundImage=\"url('img_html5_multimedia.jpg')\";\n\n\t}\n\n}\n\n</script>\n\n</head>\n\n<body>\n\n\n\n<div style=\"position:relative;width:100%;margin-top:0px;margin-bottom:0px;\">\n\n<a id=\"top\"></a>\n\n<div style=\"width:960px;margin-top:5px;margin-left:auto;margin-right:auto\">\n\n<div style=\"width:960px;height:74px;margin:0;padding:0;\">\n\n<div style=\"width:340px;text-align:left;float:left;\">\n\n\t<a href=\"http://www.w3schools.com\"><img width=\"336\" height=\"69\" src=\"/images/w3schoolslogo.gif\" alt=\"W3Schools.com\" style=\"border:0;margin-top:5px;\" /></a>\n\n</div>\n\n\n\n<div style=\"width:300px;float:right;text-align:right;margin-top:20px;margin-right:14px;margin-bottom:5px;\">\n\n<div id=\"google_translate_element\" style=\"display:none\"></div>\n\n<div id=\"translate_link\" style=\"margin-bottom:14px\">\n\n<a href=\"#\" class=\"topnav\"\n\nonclick=\"document.getElementById('google_translate_element').style.display='inline';document.getElementById('translate_link').style.display='none';return false;\">\n\nTRANSLATE\n\n</a>\n\n</div>\n\n\t<form style=\"font-size:11px;\" method=\"get\" name=\"searchform\" action=\"http://www.google.com/search\" target=\"_blank\">\n\n    <input type=\"hidden\" name=\"sitesearch\" value=\"www.w3schools.com\" />\n\n    <input onfocus=\"searchfield_focus(this)\" style=\"width:150px;color:#808080;font-style:italic;margin:0;\" \n\n    type=\"text\" name=\"as_q\" size=\"20\" value=\"Search w3schools.com\" /><input type=\"submit\" style=\"margin:0;\" value=\"Search\" title=\"Search\" />\n\n\t</form>\n\n</div>\n\n</div>\n\n<div id=\"topnav\" style=\"clear:both;width:960px;height:25px;\">\n\n<div style=\"float:left;width:400px;word-spacing:12px;font-size:90%;padding-left:15px;padding-top:6px;white-space:nowrap;text-align:left;\">\n\n\t<a class=\"topnav\" href=\"/default.asp\" target=\"_top\">HOME </a>\n\n\t<a class=\"topnav\" href=\"/html/default.asp\" target=\"_top\">HTML </a>\n\n\t<a class=\"topnav\" href=\"/css/default.asp\" target=\"_top\">CSS </a>\n\n\t<a class=\"topnav\" href=\"/xml/default.asp\" target=\"_top\">XML </a>\n\n\t<a class=\"topnav\" href=\"/js/default.asp\" target=\"_top\">JAVASCRIPT </a>\n\n\t<a class=\"topnav\" href=\"/asp/default.asp\" target=\"_top\">ASP </a>\n\n\t<a class=\"topnav\" href=\"/php/default.asp\" target=\"_top\">PHP </a>\n\n\t<a class=\"topnav\" href=\"/sql/default.asp\" target=\"_top\">SQL </a>\t\n\n\t<a class=\"topnav\" href=\"/sitemap/default.asp\" target=\"_top\">MORE...</a>\n\n</div>\n\n<div style=\"float:right;width:280px;word-spacing:6px;font-size:80%;padding-right:13px;padding-top:7px;color:#888888;white-space:nowrap;text-align:right;\">\n\n\t<a class=\"topnav\" href=\"/sitemap/default.asp#references\" target=\"_top\">REFERENCES</a> |\n\n\t<a class=\"topnav\" href=\"/sitemap/default.asp#examples\" target=\"_top\">EXAMPLES</a> |\n\n\t<a class=\"topnav\" href=\"/forum/default.asp\" target=\"_top\">FORUM</a> |\n\n\t<a class=\"topnav\" href=\"/about/default.asp\" target=\"_top\">ABOUT</a>\t\n\n</div>\n\n</div>\n\n\n\n<div style=\"width:960px;height:94px;position:relative;margin-left:auto;margin-right:auto;margin:0px;padding:0px;overflow:hidden\">\n\n<script type='text/javascript' src='http://partner.googleadservices.com/gampad/google_service.js'>\n\n</script>\n\n<script type='text/javascript'>\n\nGS_googleAddAdSenseService(\"ca-pub-3440800076797949\");\n\nGS_googleEnableAllServices();\n\n</script>\n\n<script type='text/javascript'>\n\n\n\nGA_googleAddSlot(\"ca-pub-3440800076797949\", \"MainLeaderboard\");\n\nGA_googleAddSlot(\"ca-pub-3440800076797949\", \"SmallPS\");\n\nGA_googleAddSlot(\"ca-pub-3440800076797949\", \"LargePS\");\n\nGA_googleAddSlot(\"ca-pub-3440800076797949\", \"BottomRectangle\");\n\nGA_googleAddSlot(\"ca-pub-3440800076797949\", \"SkyScraper\");\n\n\n\nGA_googleAddSlot(\"ca-pub-3440800076797949\", \"TopRectangle\");\n\n</script>\n\n<script type='text/javascript'>\n\nGA_googleFetchAds();\n\n</script>\n\n<!-- TopRectangle -->\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"TopRectangle\");\n\n</script>\n\n<div style=\"width:728px;height:90px;position:absolute;right:0px;top:0px;margin:0;padding:0;overflow:hidden;\">\n\n\n\n<!-- MainLeaderboard -->\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"MainLeaderboard\");\n\n</script>\n\n\n\n</div></div>\n\n<div style=\"width:960px;padding:0px;margin:0px;\">\n\n<div id=\"leftcolumn\" style=\"width:170px;margin:0;padding:0;margin-top:5px;float:left;\"><h2 class=\"left\"><span class=\"left_h2\">HTML5</span> Tutorial</h2>\n\n<a target=\"_top\" href=\"default.asp\" style='font-weight:bold;'>HTML5 Home</a><br />\n\n<a target=\"_top\" href=\"html5_intro.asp\" >HTML5 Introduction</a><br />\n\n<a target=\"_top\" href=\"html5_new_elements.asp\" >HTML5 New Elements</a><br />\n\n<a target=\"_top\" href=\"html5_video.asp\" >HTML5 Video</a><br />\n\n<a target=\"_top\" href=\"html5_video_dom.asp\" >HTML5 Video/DOM</a><br />\n\n<a target=\"_top\" href=\"html5_audio.asp\" >HTML5 Audio</a><br />\n\n<a target=\"_top\" href=\"html5_draganddrop.asp\" >HTML5 Drag and Drop</a><br />\n\n<a target=\"_top\" href=\"html5_canvas.asp\" >HTML5 Canvas</a><br />\n\n<a target=\"_top\" href=\"html5_svg.asp\" >HTML5 SVG</a><br />\n\n<a target=\"_top\" href=\"html5_canvas_vs_svg.asp\" >HTML5 Canvas vs. SVG</a><br />\n\n<a target=\"_top\" href=\"html5_geolocation.asp\" >HTML5 Geolocation</a><br />\n\n<a target=\"_top\" href=\"html5_webstorage.asp\" >HTML5 Web Storage</a><br />\n\n<a target=\"_top\" href=\"html5_app_cache.asp\" >HTML5 App Cache</a><br />\n\n<a target=\"_top\" href=\"html5_webworkers.asp\" >HTML5 Web Workers</a><br />\n\n<a target=\"_top\" href=\"html5_serversentevents.asp\" >HTML5 SSE</a><br />\n\n<br />\n\n<h2 class=\"left\"><span class=\"left_h2\">HTML5</span> Forms</h2>\n\n<a target=\"_top\" href=\"html5_form_input_types.asp\" >HTML5 Input Types</a><br />\n\n<a target=\"_top\" href=\"html5_form_elements.asp\" >HTML5 Form Elements</a><br />\n\n<a target=\"_top\" href=\"html5_form_attributes.asp\" >HTML5 Form Attributes</a><br />\n\n<br />\n\n<h2 class=\"left\"><span class=\"left_h2\">HTML5</span> Reference</h2>\n\n<a target=\"_top\" href=\"html5_reference.asp\" >HTML5 Tags</a><br />\n\n<a target=\"_top\" href=\"html5_ref_globalattributes.asp\" >HTML5 Attributes</a><br />\n\n<a target=\"_top\" href=\"html5_ref_eventattributes.asp\" >HTML5 Events</a><br />\n\n<a target=\"_top\" href=\"html5_ref_av_dom.asp\" >HTML5 Audio/Video</a><br />\n\n<a target=\"_top\" href=\"html5_ref_canvas.asp\" >HTML5 Canvas 2d</a><br />\n\n<a target=\"_top\" href=\"html5_ref_dtd.asp\" >HTML Valid DTDs</a><br />\n\n<br />\n\n<h2 class=\"left\"><span class=\"left_h2\">HTML5</span> Tags</h2>\n\n<a target=\"_top\" href=\"tag_comment.asp\" >&lt;!--&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_doctype.asp\" >&lt;!DOCTYPE&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_a.asp\" >&lt;a&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_abbr.asp\" >&lt;abbr&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_acronym.asp\" >&lt;acronym&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_address.asp\" >&lt;address&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_applet.asp\" >&lt;applet&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_area.asp\" >&lt;area&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_article.asp\" >&lt;article&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_aside.asp\" >&lt;aside&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_audio.asp\" >&lt;audio&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_b.asp\" >&lt;b&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_base.asp\" >&lt;base&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_basefont.asp\" >&lt;basefont&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_bdi.asp\" >&lt;bdi&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_bdo.asp\" >&lt;bdo&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_big.asp\" >&lt;big&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_blockquote.asp\" >&lt;blockquote&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_body.asp\" >&lt;body&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_br.asp\" >&lt;br&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_button.asp\" >&lt;button&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_canvas.asp\" >&lt;canvas&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_caption.asp\" >&lt;caption&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_center.asp\" >&lt;center&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_cite.asp\" >&lt;cite&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;code&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_col.asp\" >&lt;col&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_colgroup.asp\" >&lt;colgroup&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_command.asp\" >&lt;command&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_datalist.asp\" >&lt;datalist&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_dd.asp\" >&lt;dd&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_del.asp\" >&lt;del&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_details.asp\" >&lt;details&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;dfn&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_dir.asp\" >&lt;dir&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_div.asp\" >&lt;div&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_dl.asp\" >&lt;dl&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_dt.asp\" >&lt;dt&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;em&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_embed.asp\" >&lt;embed&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_fieldset.asp\" >&lt;fieldset&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_figcaption.asp\" >&lt;figcaption&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_figure.asp\" >&lt;figure&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_font.asp\" >&lt;font&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_footer.asp\" >&lt;footer&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_form.asp\" >&lt;form&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_frame.asp\" >&lt;frame&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_frameset.asp\" >&lt;frameset&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_hn.asp\" >&lt;h1&gt; - &lt;h6&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_head.asp\" >&lt;head&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_header.asp\" >&lt;header&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_hgroup.asp\" >&lt;hgroup&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_hr.asp\" >&lt;hr&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_html.asp\" >&lt;html&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_i.asp\" >&lt;i&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_iframe.asp\" >&lt;iframe&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_img.asp\" >&lt;img&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_input.asp\" >&lt;input&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_ins.asp\" >&lt;ins&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_keygen.asp\" >&lt;keygen&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;kbd&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_label.asp\" >&lt;label&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_legend.asp\" >&lt;legend&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_li.asp\" >&lt;li&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_link.asp\" >&lt;link&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_map.asp\" >&lt;map&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_mark.asp\" >&lt;mark&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_menu.asp\" >&lt;menu&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_meta.asp\" >&lt;meta&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_meter.asp\" >&lt;meter&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_nav.asp\" >&lt;nav&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_noframes.asp\" >&lt;noframes&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_noscript.asp\" >&lt;noscript&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_object.asp\" >&lt;object&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_ol.asp\" >&lt;ol&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_optgroup.asp\" >&lt;optgroup&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_option.asp\" >&lt;option&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_output.asp\" >&lt;output&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_p.asp\" >&lt;p&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_param.asp\" >&lt;param&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_pre.asp\" >&lt;pre&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_progress.asp\" >&lt;progress&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_q.asp\" >&lt;q&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_rp.asp\" >&lt;rp&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_rt.asp\" >&lt;rt&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_ruby.asp\" >&lt;ruby&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_s.asp\" >&lt;s&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;samp&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_script.asp\" >&lt;script&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_section.asp\" >&lt;section&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_select.asp\" >&lt;select&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_small.asp\" >&lt;small&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_source.asp\" >&lt;source&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_span.asp\" >&lt;span&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_strike.asp\" >&lt;strike&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;strong&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_style.asp\" >&lt;style&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_sup.asp\" >&lt;sub&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_summary.asp\" >&lt;summary&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_sup.asp\" >&lt;sup&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_table.asp\" >&lt;table&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_tbody.asp\" >&lt;tbody&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_td.asp\" >&lt;td&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_textarea.asp\" >&lt;textarea&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_tfoot.asp\" >&lt;tfoot&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_th.asp\" >&lt;th&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_thead.asp\" >&lt;thead&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_time.asp\" >&lt;time&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_title.asp\" >&lt;title&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_tr.asp\" >&lt;tr&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_track.asp\" >&lt;track&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_tt.asp\" >&lt;tt&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_u.asp\" >&lt;u&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_ul.asp\" >&lt;ul&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_phrase_elements.asp\" >&lt;var&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_video.asp\" >&lt;video&gt;</a><br />\n\n<a target=\"_top\" href=\"tag_wbr.asp\" >&lt;wbr&gt;</a><br />\n\n<br /></div>\n\n<div style=\"width:634px;margin:0px;padding:0px;background-color:#ffffff;color:#000000;padding-bottom:8px;padding-right:5px;padding-top:4px;float:left;\">\n\n\n\n<h1>HTML5 <span class=\"color_h1\">Tutorial</span></h1>\n\n<div class=\"chapter\">\n\n<div class=\"prev\"><a class=\"chapter\" href=\"/default.asp\">&laquo; W3Schools Home</a></div>\n\n<div class=\"next\"><a class=\"chapter\" href=\"html5_intro.asp\">Next Chapter &raquo;</a></div>\n\n</div>\n\n<br />\n\n<div class=\"html5header\">\n\n<div id=\"html5headline\">HTML5 is The New HTML Standard</div>\n\n<div id=\"header_image\">\n\n\n\n\n\n<div id=\"header_image_html5\" style=\"background:url('img_html5_html5.gif');\"></div>\n\n<div id=\"header_image_multimedia\" style=\"display:none;margin:0;padding:0;padding-top:20px;height:215px;text-align:center\">\n\n<video width=\"270\" controls=\"controls\" id=\"html5_vid\">\n\n  <source src=\"mov_bbb.mp4\" type=\"video/mp4\" />\n\n  <source src=\"mov_bbb.ogg\" type=\"video/ogg\" />\n\n  <source src=\"mov_bbb.webm\" type=\"video/webm\" />\n\nYour browser does not support the video tag.\n\n</video>\n\n<p style=\"text-align:center;margin:0;padding:0\">Video courtesy of <a href=\"http://www.bigbuckbunny.org/\" target=\"_blank\">Big Buck Bunny</a></p>\n\n</div>\n\n<div id=\"header_image_3d\" style=\"display:none;background:url('img_html5_3d.jpg')\"></div>\n\n<div id=\"header_image_offline\" style=\"display:none;background:url('img_html5_offline.jpg')\"></div>\n\n<div id=\"header_image_form\" style=\"display:none;background:url('img_html5_form.jpg')\"></div>\n\n<div id=\"header_image_css3\" style=\"display:none;background:url('img_html5_css3.jpg')\"></div>\n\n</div>\n\n<div id=\"header_text\">\n\n<div id=\"header_text_html5\">\n\n<h2>HTML5</h2>\n\n<ul>\n\n<li>New Elements</li>\n\n<li>New Attributes</li>\n\n<li>Full CSS3 Support</li>\n\n<li>Video and Audio</li>\n\n<li>2D/3D Graphics</li>\n\n<li>Local Storage</li>\n\n<li>Local SQL Database</li>\n\n<li>Web Applications</li>\n\n</ul>\n\n</div>\n\n\n\n<div id=\"header_text_multimedia\" style=\"display:none;\">\n\n<h2>HTML5 Multimedia</h2>\n\n<p>With HTML5, playing video and audio is easier than ever.</p>\n\n<ul>\n\n<li>HTML5 <a href=\"html5_video.asp\">&lt;video&gt;</a></li>\n\n<li>HTML5 <a href=\"html5_audio.asp\">&lt;audio&gt;</a></li>\n\n</ul>\n\n</div>\n\n\n\n<div id=\"header_text_offline\" style=\"display:none\">\n\n<h2>HTML5 Applications</h2>\n\n<p>With HTML5, web application development is easier than ever.</p>\n\n<ul>\n\n<li>Local data storage</li>\n\n<li>Local file access</li>\n\n<li>Local SQL database</li>\n\n<li>Application cache</li>\n\n<li>Javascript workers</li>\n\n<li>XHTMLHttpRequest 2</li>\n\n</ul>\n\n</div>\n\n\n\n<div id=\"header_text_3d\" style=\"display:none\">\n\n<h2>HTML5 Graphics</h2>\n\n<p>With HTML5, drawing graphics is easier than ever:</p>\n\n<ul>\n\n<li>Using the <a href=\"html5_canvas.asp\">&lt;canvas&gt;</a> element</li>\n\n<li>Using inline <a href=\"/svg/default.asp\">SVG</a></li>\n\n<li>Using <a href=\"/css3/default.asp\">CSS3 2D/3D</a></li>\n\n</ul>\n\n</div>\n\n\n\n<div id=\"header_text_css3\" style=\"display:none\">\n\n<h2>HTML5 uses CSS3</h2>\n\n<ul>\n\n<li>New Selectors</li>\n\n<li>New Properties</li>\n\n<li>Animations</li>\n\n<li>2D/3D Transformations</li>\n\n<li>Rounded Corners</li>\n\n<li>Shadow Effects</li>\n\n<li>Downloadable Fonts</li>\n\n</ul>\n\n<p>Read more in our <a href=\"/css3/default.asp\">CSS3 tutorial.</a></p>\n\n</div>\n\n<div id=\"header_text_form\" style=\"display:none\">\n\n<h2>Semantic Elements</h2>\n\n<p>New elements for headers, footers, menues, sections and articles.</p>\n\n<h2>HTML5 Forms</h2>\n\n<p>New form elements, new attributes, new input types, automatic validation.</p>\n\n</div>\n\n</div>\n\n<div id=\"header_icons\">\n\n<ul id=\"header_icons_list\">\n\n<li id=\"header_html5\" onclick=\"changeHeader('html5','0');selIcon='html5'\" onmouseover=\"iconHover('html5','0')\" onmouseout=\"iconHoverOut('html5','0')\" title=\"HTML5\"></li>\n\n<li id=\"header_multimedia\" onclick=\"changeHeader('multimedia','-104');selIcon='multimedia';playVideo()\" onmouseover=\"iconHover('multimedia','-104')\" onmouseout=\"iconHoverOut('multimedia','-104')\" title=\"HTML5 Multimedia\"></li>\n\n<li id=\"header_3d\" onclick=\"changeHeader('3d','-208');selIcon='3d'\" onmouseover=\"iconHover('3d','-208')\" onmouseout=\"iconHoverOut('3d','-208')\" title=\"HTML5 Graphics\"></li>\n\n<li id=\"header_offline\" onclick=\"changeHeader('offline','-312');selIcon='offline'\" onmouseover=\"iconHover('offline','-312')\" onmouseout=\"iconHoverOut('offline','-312')\" title=\"HTML5 Local Storage\"></li>\n\n<li style=\"width:105px;\" id=\"header_form\" onclick=\"changeHeader('form','-418');selIcon='form'\" onmouseover=\"iconHover('form','-418')\" onmouseout=\"iconHoverOut('form','-418')\" title=\"HTML5 Semantics and Forms\"></li>\n\n<li id=\"header_css3\" onclick=\"changeHeader('css3','-521');selIcon='css3'\" onmouseover=\"iconHover('css3','-521')\" onmouseout=\"iconHoverOut('css3','-521')\" title=\"HTML5 CSS3\"></li>\n\n</ul>\n\n</div>\n\n</div>\n\n<br />\n\n<h2 class=\"tutheader\">Examples in Each Chapter</h2>\n\n<p>With our HTML editor, you can edit the HTML, and click on a button to view the result.</p>\n\n<div class=\"example\">\n\n<h2 class=\"example\">Example</h2>\n\n<div class=\"example_code notranslate\">\n\n\t&lt;!DOCTYPE HTML&gt;<br />\n\n\t&lt;html&gt;<br />\n\n\t&lt;body&gt;<br />\n\n\t<br />\n\n\t&lt;video width=&quot;320&quot; height=&quot;240&quot; controls=&quot;controls&quot;&gt;<br />\n\n&nbsp; &lt;source src=&quot;movie.mp4&quot; type=&quot;video/mp4&quot; /&gt;<br />\n\n&nbsp; &lt;source src=&quot;movie.ogg&quot; type=&quot;video/ogg&quot; /&gt;<br />\n\n&nbsp; &lt;source src=&quot;movie.webm&quot; type=&quot;video/webm&quot; /&gt;<br />\n\n\tYour browser does not support the video tag.<br />\n\n\t&lt;/video&gt;<br />\n\n\t<br />\n\n\t&lt;/body&gt;<br />\n\n\t&lt;/html&gt;\n\n</div>\n\n<br />\n\n\t<a class=\"tryitbtn\" target=\"_blank\" href=\"tryit.asp?filename=tryhtml5_video_bear\">Try it yourself &raquo;</a></div>\n\n<p><b>Click on the &quot;Try it yourself&quot; button to see how it works</b></p>\n\n<p><b><a href=\"html5_intro.asp\">Start learning HTML5 now!</a></b></p>\n\n\n\n<h2 class=\"tutheader\">HTML5 References</h2>\n\n<p>At W3Schools you will find complete references about tags, global attributes,\n\nstandard events, and more.</p>\n\n<p>\n\n<a href=\"html5_reference.asp\">HTML5 Tag Reference</a>\n\n</p>\n\n\n\n<br />\n\n<div class=\"chapter\">\n\n<div class=\"prev\"><a class=\"chapter\" href=\"/default.asp\">&laquo; W3Schools Home</a></div>\n\n<div class=\"next\"><a class=\"chapter\" href=\"html5_intro.asp\">Next Chapter &raquo;</a></div>\n\n</div>\n\n\n\n<!-- **** SPOTLIGHTS 1 **** -->\n\n<!-- SmallPS -->\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"SmallPS\");\n\n</script>\n\n<!-- **** SPOTLIGHTS 2 **** -->\n\n<!-- LargePS -->\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"LargePS\");\n\n</script>\n\n<!-- **** SPOTLIGHTS 3 **** -->\n\n<!-- BottomRectangle -->\n\n<div style=\"width:340px;margin:auto\">\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"BottomRectangle\");\n\n</script>\n\n</div>\n\n\n\n<!-- BottomBanner -->\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"BottomBanner\");\n\n</script>\n\n\n\n<div id=\"err_form\" style=\"display:none\">\n\n<h2>Your suggestion:</h2>\n\n<p><label for=\"err_email\">Your E-mail (optional):</label> <input type=\"text\" id=\"err_email\" name=\"err_email\" /></p>\n\n<p><label for=\"err_url\">Page address:</label> <input type=\"text\" disabled=\"disabled\" id=\"err_url\" name=\"err_url\" /></p>\n\n<p><label for=\"err_desc\">Description:</label> <textarea name=\"err_desc\" id=\"err_desc\" cols=\"92\" rows=\"20\"></textarea></p>\n\n<p class=\"submit\"><input type=\"button\" value=\"Submit\" onclick=\"sendErr()\" /></p>\n\n<div class=\"err_close\" onclick=\"hideError()\">Close [X]</div>\n\n</div>\n\n<div id=\"err_sent\" style=\"display:none\">\n\n<h2>Thank you for your support.</h2>\n\n<div class=\"err_close\" onclick=\"hideSent()\">Close [X]</div>\n\n</div>\n\n</div>\n\n\n\n<div id=\"rightcolumn\" style=\"width:150px;margin:0px;padding:0px;float:left\">\n\n<table>\n\n<tr><th>WEB HOSTING</th></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.lunarpages.com/id/w3schools/goto/w3schools\">Best Web Hosting</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.eukhost.com\">PHP MySQL Hosting</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.web-hosting-top.com/coupons\">Best Hosting Coupons</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.heartinternet.co.uk/index.shtml\">UK Reseller Hosting</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.webhosting.uk.com/cloud-hosting.php\">Cloud Hosting</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.justhost.com/track/w3schools/textlink\">Top Web Hosting</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.doteasy.com/index.cfm?A=w3text\">$3.98 Unlimited Hosting</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.website.com/\">Premium Website Design</a>\n\n</td></tr>\n\n</table>\n\n<table>\n\n<tr><th>WEB BUILDING</th></tr>\n\n<tr><td>\n\n\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.altova.com/ref/?s=w3s_text&amp;q=xmlspy\">Download XML Editor</a>\n\n\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.wix.com/html5/now-on-wix?utm_campaign=ma_w3schools.com&amp;experiment_id=ma_html_w3schools.comlink1\">FREE Website BUILDER</a>\n\n</td></tr>\n\n<tr><td>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.dreamtemplate.com/?ref=w3txtfree\">Free Website Templates</a>\n\n<a target=\"_blank\" rel=\"nofollow\" href=\"http://www.dreamtemplate.com/?ref=w3txtfree\">Free CSS Templates</a>\n\n</td></tr>\n\n<tr><td>\n\n<a href=\"http://www.wix.com/html5/now-on-wix?utm_campaign=ma_w3schools.com&amp;experiment_id=ma_html_w3schools.comlink2\" rel=\"nofollow\" target=\"_blank\">CREATE HTML Websites</a>\n\n</td></tr>\n\n</table>\n\n<table>\n\n<tr><th>W3SCHOOLS EXAMS</th></tr>\n\n<tr><td>\n\n<a target=\"_blank\" href=\"/cert/default.asp\">Get Certified in:<br />HTML, CSS, JavaScript, XML, PHP, and ASP</a>\n\n</td></tr>\n\n</table>\n\n<table>\n\n<tr><th>W3SCHOOLS BOOKS</th></tr>\n\n<tr><td>\n\n<a target=\"_blank\" href=\"/books/default.asp\">\n\nNew Books:<br />HTML, CSS<br />\n\nJavaScript, and Ajax</a>\n\n</td></tr>\n\n</table>\n\n<table>\n\n<tr><th>STATISTICS</th></tr>\n\n<tr><td>\n\n<a target=\"_top\" href=\"/browsers/browsers_stats.asp\">Browser Statistics</a><br />\n\n<a target=\"_top\" href=\"/browsers/browsers_os.asp\">Browser OS</a><br />\n\n<a target=\"_top\" href=\"/browsers/browsers_display.asp\">Browser Display</a>\n\n</td></tr></table>\n\n<script type=\"text/javascript\">\n\n<!--\n\nfunction sharethis()\n\n{\n\ntxt='<a href=\"http://www.facebook.com/sharer.php?u='+document.URL+'\" target=\"_blank\" title=\"Facebook\">'\n\ntxt=txt+'<img src=\"/images/share_facebook.gif\" width=\"16px\" height=\"16px\" style=\"margin-right:4px\" /></a>';\n\ntxt=txt+'<a href=\"http://twitter.com/home?status=Currently reading '+document.URL+'\" target=\"_blank\" title=\"Twitter\">';\n\ntxt=txt+'<img src=\"/images/share_twitter.gif\" width=\"16px\" height=\"16px\" style=\"margin-right:4px\" /></a>';\n\ntxt=txt+'<a href=\"mailto:?&amp;subject='+document.title+'&amp;body=Take%20a%20look%20at%20this%20page%20at%20W3Schools.com:%20'+document.URL+'\" target=\"_blank\" title=\"E-mail\">';\n\ntxt=txt+'<img src=\"/images/share_email.gif\" width=\"16px\" height=\"16px\" style=\"margin-right:4px\" /></a>';\n\ntxt=txt+'<a href=\"http://delicious.com/save?v=5&noui&jump=close&url='+document.URL+'&title='+document.title+'\" target=\"_blank\" title=\"Delicious\">';\n\ntxt=txt+'<img src=\"/images/share_delicious.gif\" width=\"16px\" height=\"16px\" style=\"margin-right:4px\" /></a>';\n\ntxt=txt+'<a href=\"http://www.reddit.com/submit?url='+document.URL+'\" target=\"_blank\" title=\"Reddit\">';\n\ntxt=txt+'<img src=\"/images/share_reddit.gif\" width=\"16px\" height=\"16px\" style=\"margin-right:4px\" /></a>';\n\ntxt=txt+'<a href=\"http://digg.com/submit?url='+document.URL+'&amp;title='+document.title+'\" target=\"_blank\" title=\"Digg\">';\n\ntxt=txt+'<img src=\"/images/share_digg.gif\" width=\"16px\" height=\"16px\" style=\"margin-right:4px\" /></a>';\n\ntxt=txt+'<a href=\"http://www.stumbleupon.com/submit?url='+document.URL+'%26title%3D'+document.title+'\" target=\"_blank\" title=\"Stumbleupon\">';\n\ntxt=txt+'<img src=\"/images/share_stumbleupon.gif\" width=\"16px\" height=\"16px\" /></a>';\n\ndocument.getElementById(\"sharethis\").innerHTML=txt;\n\n}\n\n//--></script>\n\n<table>\n\n<tr><th>SHARE THIS PAGE</th></tr>\n\n<tr>\n\n<td id=\"sharethis\">\n\n<div style=\"height:16px\">\n\n<a href=\"#\" onclick=\"sharethis();return false;\">Share with &raquo;</a>\n\n</div>\n\n</td>\n\n</tr>\n\n</table>\n\n<table>\n\n<tr><td><br />\n\n<div style=\"width:124px;margin:auto\">\n\n<!-- SkyScraper -->\n\n<script type='text/javascript'>\n\nGA_googleFillSlot(\"SkyScraper\");\n\n</script>\n\n</div>\n\n</td>\n\n</tr>\n\n</table>\n\n\n\n</div>\n\n</div>\n\n<br />\n\n</div>\n\n<div style=\"width:100%;clear:both;margin:0;padding:0;background-color:transparent;background-image:url('/images/gradientbottom.jpg');background-repeat:repeat-x;position:relative;\">\n\n<div id=\"footer\" style=\"width:960px;margin-left:auto;margin-right:auto;height:110px;\">\n\n<div style=\"float:left;width:200px;text-align:left;padding-left:3px;padding-top:11px;\"><a href=\"http://www.w3schools.com\">\n\n<img style=\"border:0\" src=\"/images/w3schoolscom_gray.gif\" alt=\"W3Schools.com\" /></a>\n\n</div>\n\n<div style=\"word-spacing:6px;font-size:80%;padding-right:12px;padding-top:19px;float:right;width:600px;text-align:right;\">\n\n<a href=\"\" onclick=\"displayError();return false\">REPORT ERROR</a> |\n\n<a href=\"/default.asp\" target=\"_top\">HOME</a> |\n\n<a href=\"#top\" target=\"_top\">TOP</a> |\n\n<a href='/html5/default.asp?output=print' target=\"_blank\">PRINT</a> |\n\n<a href=\"/forum/default.asp\" target=\"_blank\">FORUM</a> |\n\n<a href=\"/about/default.asp\" target=\"_top\">ABOUT</a>\n\n</div>\n\n<div style=\"padding-top:13px;color:#404040;clear:both;\">\n\nW3Schools is optimized for learning, testing, and training. Examples might be simplified to improve reading and basic understanding.<br />\n\nTutorials, references, and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content.<br />\n\nWhile using this site, you agree to have read and accepted our\n\n<a href=\"/about/about_copyright.asp\">terms of use</a> and\n\n<a href=\"/about/about_privacy.asp\">privacy policy</a>.<br />\n\n<a href=\"/about/about_copyright.asp\">Copyright 1999-2012</a> by Refsnes Data. All Rights Reserved.\n\n</div>\n\n</div>\n\n</div>\n\n</div>\n\n<script type=\"text/javascript\">\n\nfunction googleTranslateElementInit() {\n\n  new google.translate.TranslateElement({\n\n    pageLanguage: 'en',\n\n    autoDisplay: false,    \n\n    gaTrack: true,\n\n    layout: google.translate.TranslateElement.InlineLayout.SIMPLE\n\n  }, 'google_translate_element');\n\n}\n\n</script><script src=\"//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit\" type=\"text/javascript\"></script>\n\n\n\n</body>\n\n</html>\n\n"
  },
  {
    "path": "examples/image-manipulation/README.md",
    "content": "# Image Manipulation\n\nThis example starts a proxy server that manipulate the received images,\nto make them appear upside down.\nThis directly modify the response received from the response server,\nand returns the new data to the proxy caller.\n"
  },
  {
    "path": "examples/image-manipulation/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/ext/image\"\n\t\"image\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {\n\t\tdx, dy := img.Bounds().Dx(), img.Bounds().Dy()\n\n\t\tnewImg := image.NewRGBA(img.Bounds())\n\t\tfor i := 0; i < dx; i++ {\n\t\t\tfor j := 0; j <= dy; j++ {\n\t\t\t\tnewImg.Set(i, j, img.At(i, dy-j-1))\n\t\t\t}\n\t\t}\n\t\treturn newImg\n\t}))\n\tproxy.Verbose = true\n\tlog.Fatal(http.ListenAndServe(\":8080\", proxy))\n}\n"
  },
  {
    "path": "examples/redirect-https/README.md",
    "content": "# Redirect HTTPS\n\n`redirect-https` example redirects all the HTTPS request to HTTP endpoint,\nby returning a `303 See Other` HTTP response to the client.\nThe client will then make another request using the HTTP scheme.\n"
  },
  {
    "path": "examples/redirect-https/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"github.com/elazarl/goproxy\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tif req.URL.Scheme != \"https\" {\n\t\t\treturn req, nil\n\t\t}\n\n\t\treq.URL.Scheme = \"http\"\n\t\tresp := &http.Response{\n\t\t\tStatusCode: http.StatusSeeOther,\n\t\t\tProtoMajor: 1,\n\t\t\tProtoMinor: 1,\n\t\t\tRequest:    req,\n\t\t\tHeader: http.Header{\n\t\t\t\t\"Location\": []string{req.URL.String()},\n\t\t\t},\n\t\t\tBody:          io.NopCloser(bytes.NewReader(nil)),\n\t\t\tContentLength: 0,\n\t\t}\n\t\treturn nil, resp\n\t})\n\tproxy.Verbose = *verbose\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n"
  },
  {
    "path": "examples/remove-https/README.md",
    "content": "# Remove HTTPS\n\n`remove-https` example forwards all the HTTPS request as HTTP requests,\neffectively removing the https schema from the requests.\nThis example shows you how you can rewrite the request URL, when\nneeded.\n\nThis is important because shows you how to effectively use MITM, to\nintercept the request and read its data.\n"
  },
  {
    "path": "examples/remove-https/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"github.com/elazarl/goproxy\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tif req.URL.Scheme == \"https\" {\n\t\t\treq.URL.Scheme = \"http\"\n\t\t}\n\t\treturn req, nil\n\t})\n\tproxy.Verbose = *verbose\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n"
  },
  {
    "path": "examples/request-filtering/README.md",
    "content": "# Request Filtering\n\n`request-filtering` starts an HTTP proxy on :8080. It denies requests\nto \"www.reddit.com\" made between 8am to 5pm inclusive, local server\ntime.\n\nStart the server:\n\n```sh\n$ request-filtering\n```\n\nMake a test request in another shell:\n\n```sh\n$ http_proxy=http://127.0.0.1:8080 wget -O - http://www.reddit.com\n--2015-04-11 16:59:01--  http://www.reddit.com/\nConnecting to 127.0.0.1:8080... connected.\nProxy request sent, awaiting response... 403 Forbidden\n2015-04-11 16:59:01 ERROR 403: Forbidden.\n```\n"
  },
  {
    "path": "examples/request-filtering/noreddit.go",
    "content": "package main\n\nimport (\n\t\"github.com/elazarl/goproxy\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n)\n\nfunc main() {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest(goproxy.DstHostIs(\"www.reddit.com\")).DoFunc(\n\t\tfunc(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t\th, _, _ := time.Now().Clock()\n\t\t\tif h >= 8 && h <= 17 {\n\t\t\t\treturn r, goproxy.NewResponse(r,\n\t\t\t\t\tgoproxy.ContentTypeText, http.StatusForbidden,\n\t\t\t\t\t\"Don't waste your time!\")\n\t\t\t} else {\n\t\t\t\tctx.Warnf(\"clock: %d, you can waste your time...\", h)\n\t\t\t}\n\t\t\treturn r, nil\n\t\t})\n\tlog.Fatalln(http.ListenAndServe(\":8080\", proxy))\n}\n"
  },
  {
    "path": "examples/socket-keepalive/README.md",
    "content": "# Socket KeepAlive\n\n`socket-keepalive` example adds a custom net.Dialer that can be configured\nby the user, enabling TCP keep alives in this example.\nBy default, Go already uses 15 seconds TCP keep alives for the connections,\nso this example is not strictly required, as it is provided.\nTCP keep alives are useful for a connection that can be idle for a while,\nto avoid the TCP connection close.\nThe TCP connection is closed when the request context expires."
  },
  {
    "path": "examples/socket-keepalive/keepalive.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"github.com/elazarl/goproxy\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tverbose := flag.Bool(\"v\", false, \"should every proxy request be logged to stdout\")\n\taddr := flag.String(\"addr\", \":8080\", \"proxy listen address\")\n\tflag.Parse()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.Tr.DialContext = func(ctx context.Context, network, addr string) (c net.Conn, err error) {\n\t\tvar d net.Dialer\n\t\tc, err = d.DialContext(ctx, network, addr)\n\t\tif c, ok := c.(*net.TCPConn); err == nil && ok {\n\t\t\tc.SetKeepAlive(true)\n\t\t\tgo func() {\n\t\t\t\t<-ctx.Done()\n\t\t\t\tc.Close()\n\t\t\t}()\n\t\t}\n\t\treturn\n\t}\n\tproxy.Verbose = *verbose\n\tlog.Fatal(http.ListenAndServe(*addr, proxy))\n}\n"
  },
  {
    "path": "examples/websockets/README.md",
    "content": "# Websockets\n`websockets` example shows an example of a WebSocket request made\nthrough the proxy server.\nThe target server continuously send data to the client, and the proxy will\nforward them to websocket client.\nTo run this example, it's enough to just run the main.\n\nInside this folder there are also some self-signed certificates valid for\nlocalhost, that are automatically used for the WebSocket server.\n"
  },
  {
    "path": "examples/websockets/localhost-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIC1vu3JK+Z6WtbpaTL4LquNQVcwSha53sVIxEdcLNG6WoAoGCCqGSM49\nAwEHoUQDQgAE8OpaVYv567gc00WZ8nSTDvcic+8tW7dCgGffiJogyesaERxDi+fg\n0E/9WiNrF66Pl05I3r4NFD0EoIlcaSbMqw==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "examples/websockets/localhost.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICozCCAYugAwIBAgIUEdJ4Tu/Hs4HznpHjnb3Akj7uNKgwDQYJKoZIhvcNAQEL\nBQAwDTELMAkGA1UEAxMCQ0EwHhcNMTYwNTA1MjIxMjAwWhcNMjEwNTA0MjIxMjAw\nWjBGMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNU2FuIEZyYW5jaXNjbzELMAkGA1UE\nBxMCQ0ExEjAQBgNVBAMTCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABPDqWlWL+eu4HNNFmfJ0kw73InPvLVu3QoBn34iaIMnrGhEcQ4vn4NBP/Voj\naxeuj5dOSN6+DRQ9BKCJXGkmzKujgYwwgYkwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud\nJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFD8hwmfkMLy1\n9iajA7jsiEzBDT6DMB8GA1UdIwQYMBaAFBZgsrPxVqk7nrwIzeqIEl62Nj3IMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAPYJUCwgNCVLm\noITJdYtroJIOt3LegIQJWWYll4J3/WidfmW4FWD4Qumd0YRjqgIhTcgmxHWhhJXB\nKFd+hMD+UUs7jdYRQ80Ch/S/r/PzBNLiHkZ+ILklCU2JG25hJjXX48BB5y1eVF7O\n9ds5h6KHJqNwG6B5yfB5iPxpd4/Qk/+6vNYODX5LSQqo9MpGotByjMGywcY4TesF\n5AYT7pPoRkxucAifTRjw/8k653Zm2ZA9HCo+i9GrW+kyk3XP5Xk8z0GuvOCPnBIu\np1jiUOH84pXap2cuR0z3vkp76mPAgsBk64RCBjCJJV8LEQbiPW4W9dQjcvlLkW7X\nlP5b19hr6g==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/websockets/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"github.com/coder/websocket\"\n\t\"github.com/elazarl/goproxy\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\nfunc echo(w http.ResponseWriter, r *http.Request) {\n\tc, err := websocket.Accept(w, r, nil)\n\tif err != nil {\n\t\tlog.Printf(\"upgrade: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer c.Close(websocket.StatusNormalClosure, \"\")\n\n\tctx := context.Background()\n\tfor {\n\t\tmt, message, err := c.Read(ctx)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"read: %v\\n\", err)\n\t\t\tbreak\n\t\t}\n\t\tlog.Printf(\"recv: %s\\n\", message)\n\t\tif err := c.Write(ctx, mt, message); err != nil {\n\t\t\tlog.Printf(\"write: %v\\n\", err)\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc StartEchoServer() {\n\tlog.Println(\"Starting echo server\")\n\tgo func() {\n\t\thttp.HandleFunc(\"/\", echo)\n\t\terr := http.ListenAndServeTLS(\":12345\", \"localhost.pem\", \"localhost-key.pem\", nil)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n}\n\nfunc StartProxy() {\n\tlog.Println(\"Starting proxy server\")\n\tgo func() {\n\t\tproxy := goproxy.NewProxyHttpServer()\n\t\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\t\tproxy.Verbose = true\n\n\t\tif err := http.ListenAndServe(\":54321\", proxy); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n}\n\nfunc main() {\n\tinterrupt := make(chan os.Signal, 1)\n\tsignal.Notify(interrupt, os.Interrupt)\n\tStartEchoServer()\n\tStartProxy()\n\n\tproxyUrl := \"http://localhost:54321\"\n\tparsedProxy, err := url.Parse(proxyUrl)\n\tif err != nil {\n\t\tlog.Fatal(\"unable to parse proxy URL\")\n\t}\n\n\tctx := context.Background()\n\tendpointUrl := \"wss://localhost:12345\"\n\n\tc, _, err := websocket.Dial(ctx, endpointUrl, &websocket.DialOptions{\n\t\tHTTPClient: &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\t},\n\t\t\t\tProxy: http.ProxyURL(parsedProxy),\n\t\t\t},\n\t\t},\n\t\tSubprotocols: []string{\"p1\"},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(\"dial:\", err)\n\t}\n\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tdefer close(done)\n\t\tfor {\n\t\t\t_, message, err := c.Read(ctx)\n\t\t\tif err != nil {\n\t\t\t\tlog.Println(\"read:\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Printf(\"recv: %s\", message)\n\t\t}\n\t}()\n\n\tticker := time.NewTicker(time.Second)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase t := <-ticker.C: // Message send\n\t\t\t// Write current time to the websocket client every 1 second\n\t\t\tif err := c.Write(ctx, websocket.MessageText, []byte(t.String())); err != nil {\n\t\t\t\tlog.Println(\"write:\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-interrupt: // Server shutdown\n\t\t\tlog.Println(\"interrupt\")\n\t\t\t// To cleanly close a connection, a client should send a close\n\t\t\t// frame and wait for the server to close the connection.\n\t\t\terr := c.Close(websocket.StatusNormalClosure, \"\")\n\t\t\tif err != nil {\n\t\t\t\tlog.Println(\"write close:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\tcase <-time.After(time.Second):\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ext/auth/basic.go",
    "content": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\nvar unauthorizedMsg = []byte(\"407 Proxy Authentication Required\")\n\nfunc BasicUnauthorized(req *http.Request, realm string) *http.Response {\n\t// TODO(elazar): verify realm is well formed\n\treturn &http.Response{\n\t\tStatusCode: http.StatusProxyAuthRequired,\n\t\tProtoMajor: 1,\n\t\tProtoMinor: 1,\n\t\tRequest:    req,\n\t\tHeader: http.Header{\n\t\t\t\"Proxy-Authenticate\": []string{\"Basic realm=\" + realm},\n\t\t\t\"Proxy-Connection\":   []string{\"close\"},\n\t\t},\n\t\tBody:          io.NopCloser(bytes.NewBuffer(unauthorizedMsg)),\n\t\tContentLength: int64(len(unauthorizedMsg)),\n\t}\n}\n\nvar proxyAuthorizationHeader = \"Proxy-Authorization\"\n\nfunc auth(req *http.Request, f func(user, passwd string) bool) bool {\n\tauthheader := strings.SplitN(req.Header.Get(proxyAuthorizationHeader), \" \", 2)\n\treq.Header.Del(proxyAuthorizationHeader)\n\tif len(authheader) != 2 || authheader[0] != \"Basic\" {\n\t\treturn false\n\t}\n\tuserpassraw, err := base64.StdEncoding.DecodeString(authheader[1])\n\tif err != nil {\n\t\treturn false\n\t}\n\tuserpass := strings.SplitN(string(userpassraw), \":\", 2)\n\tif len(userpass) != 2 {\n\t\treturn false\n\t}\n\treturn f(userpass[0], userpass[1])\n}\n\n// Basic returns a basic HTTP authentication handler for requests\n//\n// You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities\nfunc Basic(realm string, f func(user, passwd string) bool) goproxy.ReqHandler {\n\treturn goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tif !auth(req, f) {\n\t\t\treturn nil, BasicUnauthorized(req, realm)\n\t\t}\n\t\treturn req, nil\n\t})\n}\n\n// BasicConnect returns a basic HTTP authentication handler for CONNECT requests\n//\n// You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities\nfunc BasicConnect(realm string, f func(user, passwd string) bool) goproxy.HttpsHandler {\n\treturn goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {\n\t\tif !auth(ctx.Req, f) {\n\t\t\tctx.Resp = BasicUnauthorized(ctx.Req, realm)\n\t\t\treturn goproxy.RejectConnect, host\n\t\t}\n\t\treturn nil, host\n\t})\n}\n\n// ProxyBasic will force HTTP authentication before any request to the proxy is processed\nfunc ProxyBasic(proxy *goproxy.ProxyHttpServer, realm string, f func(user, passwd string) bool) {\n\tproxy.OnRequest().Do(Basic(realm, f))\n\tproxy.OnRequest().HandleConnect(BasicConnect(realm, f))\n}\n"
  },
  {
    "path": "ext/auth/basic_test.go",
    "content": "package auth_test\n\nimport (\n\t\"encoding/base64\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/ext/auth\"\n)\n\ntype ConstantHanlder string\n\nfunc (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tio.WriteString(w, string(h))\n}\n\nfunc oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, s *httptest.Server) {\n\ts = httptest.NewServer(proxy)\n\n\tproxyUrl, _ := url.Parse(s.URL)\n\ttr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}\n\tclient = &http.Client{Transport: tr}\n\treturn\n}\n\nfunc times(n int, s string) string {\n\tr := make([]byte, 0, n*len(s))\n\tfor i := 0; i < n; i++ {\n\t\tr = append(r, s...)\n\t}\n\treturn string(r)\n}\n\nfunc TestBasicConnectAuthWithCurl(t *testing.T) {\n\texpected := \":c>\"\n\tbackground := httptest.NewTLSServer(ConstantHanlder(expected))\n\tdefer background.Close()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(auth.BasicConnect(\"my_realm\", func(user, passwd string) bool {\n\t\treturn user == \"user\" && passwd == \"open sesame\"\n\t}))\n\t_, proxyserver := oneShotProxy(proxy)\n\tdefer proxyserver.Close()\n\n\tcmd := exec.Command(\"curl\",\n\t\t\"--silent\", \"--show-error\", \"--insecure\",\n\t\t\"-x\", proxyserver.URL,\n\t\t\"-U\", \"user:open sesame\",\n\t\t\"-p\",\n\t\t\"--url\", background.URL+\"/[1-3]\",\n\t)\n\tout, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr\n\tif err != nil {\n\t\tt.Fatal(err, string(out))\n\t}\n\tfinalexpected := times(3, expected)\n\tif string(out) != finalexpected {\n\t\tt.Error(\"Expected\", finalexpected, \"got\", string(out))\n\t}\n}\n\nfunc TestBasicAuthWithCurl(t *testing.T) {\n\texpected := \":c>\"\n\tbackground := httptest.NewServer(ConstantHanlder(expected))\n\tdefer background.Close()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().Do(auth.Basic(\"my_realm\", func(user, passwd string) bool {\n\t\treturn user == \"user\" && passwd == \"open sesame\"\n\t}))\n\t_, proxyserver := oneShotProxy(proxy)\n\tdefer proxyserver.Close()\n\n\tcmd := exec.Command(\"curl\",\n\t\t\"--silent\", \"--show-error\",\n\t\t\"-x\", proxyserver.URL,\n\t\t\"-U\", \"user:open sesame\",\n\t\t\"--url\", background.URL+\"/[1-3]\",\n\t)\n\tout, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr\n\tif err != nil {\n\t\tt.Fatal(err, string(out))\n\t}\n\tfinalexpected := times(3, expected)\n\tif string(out) != finalexpected {\n\t\tt.Error(\"Expected\", finalexpected, \"got\", string(out))\n\t}\n}\n\nfunc TestBasicAuth(t *testing.T) {\n\texpected := \"hello\"\n\tbackground := httptest.NewServer(ConstantHanlder(expected))\n\tdefer background.Close()\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().Do(auth.Basic(\"my_realm\", func(user, passwd string) bool {\n\t\treturn user == \"user\" && passwd == \"open sesame\"\n\t}))\n\tclient, proxyserver := oneShotProxy(proxy)\n\tdefer proxyserver.Close()\n\n\t// without auth\n\tresp, err := client.Get(background.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif resp.Header.Get(\"Proxy-Authenticate\") != \"Basic realm=my_realm\" {\n\t\tt.Error(\"Expected Proxy-Authenticate header got\", resp.Header.Get(\"Proxy-Authenticate\"))\n\t}\n\tif resp.StatusCode != http.StatusProxyAuthRequired {\n\t\tt.Error(\"Expected status 407 Proxy Authentication Required, got\", resp.Status)\n\t}\n\n\t// with auth\n\treq, err := http.NewRequest(http.MethodGet, background.URL, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"Proxy-Authorization\",\n\t\t\"Basic \"+base64.StdEncoding.EncodeToString([]byte(\"user:open sesame\")))\n\tresp, err = client.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\tt.Error(\"Expected status 200 OK, got\", resp.Status)\n\t}\n\tmsg, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(msg) != \"hello\" {\n\t\tt.Errorf(\"Expected '%s', actual '%s'\", expected, string(msg))\n\t}\n}\n\nfunc TestWithBrowser(t *testing.T) {\n\t// an easy way to check if auth works with webserver\n\t// to test, run with\n\t// $ go test -run TestWithBrowser -- server\n\t// configure a browser to use the printed proxy address, use the proxy\n\t// and exit with Ctrl-C. It will throw error if your haven't actually used the proxy\n\tif os.Args[len(os.Args)-1] != \"server\" {\n\t\treturn\n\t}\n\tproxy := goproxy.NewProxyHttpServer()\n\tprintln(\"proxy localhost port 8082\")\n\taccess := int32(0)\n\tproxy.OnRequest().Do(auth.Basic(\"my_realm\", func(user, passwd string) bool {\n\t\tatomic.AddInt32(&access, 1)\n\t\treturn user == \"user\" && passwd == \"1234\"\n\t}))\n\tl, err := net.Listen(\"tcp\", \"localhost:8082\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tch := make(chan os.Signal)\n\tsignal.Notify(ch, os.Interrupt)\n\tgo func() {\n\t\t<-ch\n\t\tl.Close()\n\t}()\n\thttp.Serve(l, proxy)\n\tif access <= 0 {\n\t\tt.Error(\"No one accessed the proxy\")\n\t}\n}\n"
  },
  {
    "path": "ext/go.mod",
    "content": "module github.com/elazarl/goproxy/ext\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa3811c\n\tgithub.com/stretchr/testify v1.11.1\n\tgolang.org/x/net v0.36.0\n\tgolang.org/x/text v0.22.0\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "ext/go.sum",
    "content": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa3811c h1:yWAGp1CjD1mQGLUsADqPn5s1n2AkGAX33XLDUgoXzyo=\ngithub.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa3811c/go.mod h1:P73liMk9TZCyF9fXG/RyMeSizmATvpvy3ZS61/1eXn4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngolang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=\ngolang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=\ngolang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=\ngolang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "ext/har/logger.go",
    "content": "package har\n\nimport (\n    \"net/http\"\n    \"time\"\n\n    \"github.com/elazarl/goproxy\"\n)\n\n// ExportFunc is a function type that users can implement to handle exported entries\ntype ExportFunc func([]Entry)\n\n// Logger implements a HAR logging extension for goproxy\ntype Logger struct {\n    exportFunc      ExportFunc\n    exportInterval  time.Duration\n    exportThreshold int\n    dataCh          chan Entry\n}\n\n// LoggerOption is a function type for configuring the Logger\ntype LoggerOption func(*Logger)\n\n// WithExportInterval sets the interval for automatic exports\nfunc WithExportInterval(d time.Duration) LoggerOption {\n    return func(l *Logger) {\n        l.exportInterval = d\n    }\n}\n\n// WithExportCount sets the number of requests after which to export entries\nfunc WithExportThreshold(threshold int) LoggerOption {\n    return func(l *Logger) {\n        l.exportThreshold = threshold\n    }\n}\n\n// NewLogger creates a new HAR logger instance\nfunc NewLogger(exportFunc ExportFunc, opts ...LoggerOption) *Logger {\n    l := &Logger{\n        exportFunc:     exportFunc,\n        exportThreshold: 100,    // Default threshold\n        exportInterval: 0,       // Default no interval\n        dataCh:         make(chan Entry), \n    }\n    \n    // Apply options\n    for _, opt := range opts {\n        opt(l)\n    }\n    \n    go l.exportLoop()\n    return l\n}\n// OnRequest handles incoming HTTP requests\nfunc (l *Logger) OnRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n    ctx.UserData = time.Now()\n    return req, nil\n}\n\n// OnResponse handles HTTP responses\nfunc (l *Logger) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n    if resp == nil || ctx.Req == nil || ctx.UserData == nil {\n        return resp\n    }\n    startTime, ok := ctx.UserData.(time.Time)\n    if !ok {\n        return resp\n    }\n    \n    entry := Entry{\n        StartedDateTime: startTime,\n        Time:           time.Since(startTime).Milliseconds(),\n        Request:        parseRequest(ctx),\n        Response:       parseResponse(ctx),\n        Timings: Timings{\n            Send:    0,\n            Wait:    time.Since(startTime).Milliseconds(),\n            Receive: 0,\n        },\n    }\n    entry.fillIPAddress(ctx.Req)\n    \n    l.dataCh <- entry \n    return resp\n}\n\nfunc (l *Logger) exportLoop() {\n   var entries []Entry \n    \n   exportIfNeeded := func() {\n        if len(entries) > 0 {\n            go l.exportFunc(entries)\n            entries = nil \n        } \n    } \n    \n    var tickerC <-chan time.Time\n    if l.exportInterval > 0 {\n        ticker := time.NewTicker(l.exportInterval)\n        defer ticker.Stop()\n        tickerC = ticker.C\n    }\n    \n    for {\n        select {\n        case entry, ok := <-l.dataCh:\n            if !ok {\n                exportIfNeeded()\n                return\n            }\n            entries = append(entries, entry)\n            if l.exportThreshold > 0 && len(entries) >= l.exportThreshold {\n                exportIfNeeded()\n            } \n        case <-tickerC:\n            exportIfNeeded()\n        }\n    }\n}\n\nfunc (l *Logger) Stop() {\n    close(l.dataCh)\n}\n"
  },
  {
    "path": "ext/har/logger_test.go",
    "content": "package har\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// ConstantHandler is a simple HTTP handler that returns a constant response\ntype ConstantHandler string\n\nfunc (h ConstantHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"text/plain\")\n    io.WriteString(w, string(h))\n}\n\n// createTestProxy sets up a test proxy with a HAR logger\nfunc createTestProxy(logger *Logger) *httptest.Server {\n    proxy := goproxy.NewProxyHttpServer()\n    proxy.OnRequest().DoFunc(logger.OnRequest)\n    proxy.OnResponse().DoFunc(logger.OnResponse)\n    return httptest.NewServer(proxy)\n}\n\n// createProxyClient creates an HTTP client that uses the given proxy\nfunc createProxyClient(proxyURL string) *http.Client {\n    proxyURLParsed, _ := url.Parse(proxyURL)\n    tr := &http.Transport{\n        Proxy: http.ProxyURL(proxyURLParsed),\n    }\n    return &http.Client{Transport: tr}\n}\n\nfunc TestHarLoggerBasicFunctionality(t *testing.T) {\n    testCases := []struct {\n        name           string\n        method         string\n        body           string\n        contentType    string\n        expectedMethod string\n    }{\n        {\n            name:           \"GET Request\",\n            method:         http.MethodGet,\n            expectedMethod: http.MethodGet,\n        },\n        {\n            name:           \"POST Request\",\n            method:         http.MethodPost,\n            body:           `{\"test\":\"data\"}`,\n            contentType:    \"application/json\",\n            expectedMethod: http.MethodPost,\n        },\n    }\n\n    for _, tc := range testCases {\n        t.Run(tc.name, func(t *testing.T) {\n            var wg sync.WaitGroup\n            wg.Add(1)\n            \n            var exportedEntries []Entry\n            exportFunc := func(entries []Entry) {\n                exportedEntries = append(exportedEntries, entries...)\n                wg.Done()\n            }\n            \n            logger := NewLogger(exportFunc, WithExportThreshold(1)) // Export after each request\n            defer logger.Stop()\n\n            background := httptest.NewServer(ConstantHandler(\"hello world\"))\n            defer background.Close()\n\n            proxyServer := createTestProxy(logger)\n            defer proxyServer.Close()\n\n            client := createProxyClient(proxyServer.URL)\n\n            req, err := http.NewRequestWithContext(\n                context.Background(),\n                tc.method,\n                background.URL,\n                strings.NewReader(tc.body),\n            )\n            require.NoError(t, err, \"Should create request\")\n\n            if tc.contentType != \"\" {\n                req.Header.Set(\"Content-Type\", tc.contentType)\n            }\n\n            resp, err := client.Do(req)\n            require.NoError(t, err, \"Should send request successfully\")\n            defer resp.Body.Close()\n            \n            bodyBytes, err := io.ReadAll(resp.Body)\n            require.NoError(t, err, \"Should read response body\")\n            \n            body := string(bodyBytes)\n            assert.Equal(t, \"hello world\", body, \"Response body should match\")\n\n            wg.Wait() // Wait for export to complete\n\n            assert.Len(t, exportedEntries, 1, \"Should have exactly one exported entry\")\n            assert.Equal(t, tc.expectedMethod, exportedEntries[0].Request.Method, \"Request method should match\")\n        })\n    }\n}\n\nfunc TestLoggerThresholdExport(t *testing.T) {\n    var wg sync.WaitGroup\n    var exports [][]Entry\n    var mtx sync.Mutex\n    wg.Add(3) // Expect 3 exports (3,3,1)\n    \n    exportFunc := func(entries []Entry) {\n        mtx.Lock()\n        exports = append(exports, entries)\n        mtx.Unlock()\n        \n        t.Logf(\"Export occurred with %d entries\", len(entries))\n        wg.Done()\n    }\n    \n    threshold := 3\n    logger := NewLogger(exportFunc, WithExportThreshold(threshold))\n    \n    background := httptest.NewServer(ConstantHandler(\"test\"))\n    defer background.Close()\n    proxyServer := createTestProxy(logger)\n    defer proxyServer.Close()\n    client := createProxyClient(proxyServer.URL)\n    \n    // Send 7 requests\n    for i := 0; i < 7; i++ {\n        req, err := http.NewRequestWithContext(\n            context.Background(),\n            http.MethodGet,\n            background.URL,\n            nil,\n        )\n        require.NoError(t, err)\n        \n        resp, err := client.Do(req)\n        require.NoError(t, err)\n        resp.Body.Close()\n    }  \n    \n    // Call Stop to trigger final export of remaining entries\n    logger.Stop()\n    wg.Wait()\n\n    require.Equal(t, 3, len(exports), \"should have 3 export batches\")\n\n    // Count batches by size\n    batchCounts := make(map[int]int)\n    for _, batch := range exports {\n        batchCounts[len(batch)]++\n    }\n\n    // Check batch sizes\n    assert.Equal(t, 2, batchCounts[threshold], \"should have two batches of threshold size\")\n    assert.Equal(t, 1, batchCounts[1], \"should have one batch with 1 entry\")\n}\n\nfunc TestHarLoggerExportInterval(t *testing.T) {\n    var wg sync.WaitGroup\n    var mtx sync.Mutex\n    var exports [][]Entry\n    wg.Add(1) // Expect 1 export with all entries\n    \n   exportFunc := func(entries []Entry) {\n        mtx.Lock()\n        exports = append(exports, entries)\n        mtx.Unlock()\n        \n        t.Logf(\"Export occurred with %d entries\", len(entries))\n        wg.Done()\n    } \n\n    logger := NewLogger(exportFunc, WithExportInterval(time.Second))\n    \n    background := httptest.NewServer(ConstantHandler(\"test\"))\n    defer background.Close()\n    proxyServer := createTestProxy(logger)\n    defer proxyServer.Close()\n    client := createProxyClient(proxyServer.URL)\n    \n    // Send 3 requests\n    for i := 0; i < 3; i++ {\n        req, err := http.NewRequestWithContext(\n            context.Background(),\n            http.MethodGet,\n            background.URL,\n            nil,\n        )\n        require.NoError(t, err)\n        \n        resp, err := client.Do(req)\n        require.NoError(t, err)\n        resp.Body.Close()\n    } \n    \n    wg.Wait()\n    logger.Stop()\n    \n    require.Equal(t, 1, len(exports), \"should have 1 export batch\")\n    assert.Equal(t, 3, len(exports[0]), \"Should have exported 3 entries\")\n}\n\n"
  },
  {
    "path": "ext/har/types.go",
    "content": "// Original implementation from abourget/goproxy, adapted for use as an extension.\n// HAR specification: http://www.softwareishard.com/blog/har-12-spec/\npackage har\n\nimport (\n    \"bytes\"\n    \"io\"\n    \"net/http\"\n    \"net/url\"\n    \"mime\"\n    \"net\"\n    \"strings\"\n    \"time\"\n\n    \"github.com/elazarl/goproxy\"\n)\n\ntype Har struct {\n\tLog Log `json:\"log\"`\n}\n\ntype Log struct {\n\tVersion string   `json:\"version\"`\n\tCreator Creator  `json:\"creator\"`\n\tBrowser *Browser `json:\"browser,omitempty\"`\n\tPages   []Page   `json:\"pages,omitempty\"`\n\tEntries []Entry  `json:\"entries\"`\n\tComment string   `json:\"comment,omitempty\"`\n}\n\nfunc New() *Har {\n\thar := &Har{\n\t\tLog: Log{\n\t\t\tVersion: \"1.2\",\n\t\t\tCreator: Creator{\n\t\t\t\tName:    \"GoProxy\",\n\t\t\t\tVersion: \"1.0\",\n\t\t\t},\n\t\t\tPages:   make([]Page, 0, 10),\n\t\t\tEntries: makeNewEntries(),\n\t\t},\n\t}\n\treturn har\n}\n\nfunc makeNewEntries() []Entry {\n    const startingEntrySize int = 1000\n\treturn make([]Entry, 0, startingEntrySize)\n}\n\ntype Creator struct {\n\tName    string `json:\"name\"`\n\tVersion string `json:\"version\"`\n\tComment string `json:\"comment,omitempty\"`\n}\n\ntype Browser struct {\n\tName    string `json:\"name\"`\n\tVersion string `json:\"version\"`\n\tComment string `json:\"comment,omitempty\"`\n}\n\ntype Page struct {\n\tID              string      `json:\"id,omitempty\"`\n\tStartedDateTime time.Time   `json:\"startedDateTime\"`\n\tTitle           string      `json:\"title\"`\n\tPageTimings     PageTimings `json:\"pageTimings\"`\n\tComment         string      `json:\"comment,omitempty\"`\n}\n\ntype Entry struct {\n\tPageRef         string    `json:\"pageref,omitempty\"`\n\tStartedDateTime time.Time `json:\"startedDateTime\"`\n\tTime            int64     `json:\"time\"`\n\tRequest         *Request  `json:\"request\"`\n\tResponse        *Response `json:\"response\"`\n\tCache           Cache     `json:\"cache\"`\n\tTimings         Timings   `json:\"timings\"`\n\tServerIpAddress string    `json:\"serverIpAddress,omitempty\"`\n\tConnection      string    `json:\"connection,omitempty\"`\n\tComment         string    `json:\"comment,omitempty\"`\n}\n\ntype Cache struct {\n\tBeforeRequest *CacheEntry `json:\"beforeRequest,omitempty\"`\n\tAfterRequest  *CacheEntry `json:\"afterRequest,omitempty\"`\n}\n\ntype CacheEntry struct {\n\tExpires    string `json:\"expires,omitempty\"`\n\tLastAccess string `json:\"lastAccess\"`\n\tETag       string `json:\"eTag\"`\n\tHitCount   int    `json:\"hitCount\"`\n\tComment    string `json:\"comment,omitempty\"`\n}\n\ntype Request struct {\n\tMethod      string          `json:\"method\"`\n\tUrl         string          `json:\"url\"`\n\tHttpVersion string          `json:\"httpVersion\"`\n\tCookies     []Cookie        `json:\"cookies\"`\n\tHeaders     []NameValuePair `json:\"headers\"`\n\tQueryString []NameValuePair `json:\"queryString\"`\n\tPostData    *PostData       `json:\"postData,omitempty\"`\n\tBodySize    int64           `json:\"bodySize\"`\n\tHeadersSize int64           `json:\"headersSize\"`\n}\n\nfunc (entry *Entry) fillIPAddress(req *http.Request) {\n    host := req.URL.Hostname()\n    \n    // try to parse the host as an IP address\n    if ip := net.ParseIP(host); ip != nil {\n        entry.ServerIpAddress = ip.String()\n        return\n    } \n}\n\n// Shared utility function for reading body content\nfunc readBody(ctx *goproxy.ProxyCtx, body io.ReadCloser) ([]byte, error) {\n    content, err := io.ReadAll(body)\n    if err != nil {\n        ctx.Proxy.Logger.Printf(\"Error reading body: %v\", err)\n        return nil, err\n    }\n    return content, nil\n}\n\n// Shared function for handling mime types\nfunc parseMediaType(ctx *goproxy.ProxyCtx, header http.Header) string {\n    contentType := header.Get(\"Content-Type\")\n    if contentType == \"\" {\n        return \"\"\n    }\n    \n    mediaType, _, err := mime.ParseMediaType(contentType)\n    if err != nil {\n        ctx.Proxy.Logger.Printf(\"Error parsing media type: %v\", err)\n        return \"\"\n    }\n    return mediaType\n}\n\nfunc parsePostData(ctx *goproxy.ProxyCtx, req *http.Request) *PostData {\n    mediaType := parseMediaType(ctx, req.Header)\n    if mediaType == \"\" {\n        return nil\n    }\n    \n    harPostData := &PostData{\n        MimeType: mediaType,\n    } \n\n    if err := req.ParseForm(); err != nil {\n        ctx.Proxy.Logger.Printf(\"Error parsing form: %v\", err)\n        return nil\n    }\n    \n    if len(req.PostForm) > 0 {\n        for k, vals := range req.PostForm {\n            for _, v := range vals {\n                param := PostDataParam{\n                    Name:  k,\n                    Value: v,\n                }\n                harPostData.Params = append(harPostData.Params, param)\n            }\n        }\n    } else if body, err := readBody(ctx, req.Body); err == nil {\n        req.Body = io.NopCloser(bytes.NewBuffer(body))\n        harPostData.Text = string(body)\n    }\n    \n    return harPostData\n}\n\ntype Response struct {\n\tStatus      int             `json:\"status\"`\n\tStatusText  string          `json:\"statusText\"`\n\tHttpVersion string          `json:\"httpVersion\"`\n\tCookies     []Cookie        `json:\"cookies\"`\n\tHeaders     []NameValuePair `json:\"headers\"`\n\tContent     Content         `json:\"content\"`\n\tRedirectUrl string          `json:\"redirectURL\"`\n\tBodySize    int64           `json:\"bodySize\"`\n\tHeadersSize int64           `json:\"headersSize\"`\n\tComment     string          `json:\"comment,omitempty\"`\n}\n\nfunc parseResponse(ctx *goproxy.ProxyCtx) *Response {\n    if ctx.Resp == nil {\n        return nil\n    } \n\n    resp := ctx.Resp\n    harResponse := Response{\n        Status:      resp.StatusCode,\n        StatusText:  http.StatusText(resp.StatusCode),\n        HttpVersion: resp.Proto,\n        Cookies:     parseCookies(resp.Cookies()),\n        Headers:     parseStringArrMap(resp.Header),\n        RedirectUrl: resp.Header.Get(\"Location\"),\n        BodySize:    resp.ContentLength,\n        HeadersSize: -1,\n    }\n\n    if resp.Body == nil {\n        return &harResponse\n    }\n\n    body, err := readBody(ctx, resp.Body)\n    if err != nil {\n        return &harResponse\n    }\n\n    resp.Body = io.NopCloser(bytes.NewBuffer(body))\n    harResponse.Content = Content{\n        Size:     len(body),\n        Text:     string(body),\n        MimeType: parseMediaType(ctx, resp.Header),\n    }\n\n    return &harResponse\n}\n\nfunc parseRequest(ctx *goproxy.ProxyCtx) *Request {\n    if ctx.Req == nil {\n        ctx.Proxy.Logger.Printf(\"ParseRequest: nil request\")\n        return nil\n    }\n    \n    req := ctx.Req\n    harRequest := &Request{\n        Method:      req.Method,\n        Url:         req.URL.String(),\n        HttpVersion: req.Proto,\n        Cookies:     parseCookies(req.Cookies()),\n        Headers:     parseStringArrMap(req.Header),\n        QueryString: parseStringArrMap(req.URL.Query()),\n        BodySize:    req.ContentLength,\n        HeadersSize: -1,\n    }\n    \n    if req.Method != http.MethodPost && req.Method != http.MethodPut {\n        return harRequest\n    }\n\n    ctx.Proxy.Logger.Printf(\"ParseRequest: creating PostData, hasBody=%v, hasGetBody=%v\", \n        req.Body != nil, req.GetBody != nil)\n        \n    if postData := parsePostData(ctx, req); postData != nil {\n        harRequest.PostData = postData\n    }\n\n    return harRequest\n}\n\nfunc parseStringArrMap(stringArrMap map[string][]string) []NameValuePair {\n\tharQueryString := make([]NameValuePair, 0, len(stringArrMap))\n\t\n\tfor k, v := range stringArrMap {\n\t\tescapedKey, err := url.QueryUnescape(k)\n\t\tif err != nil {\n\t\t\t// Use original key if unescaping fails\n\t\t\tescapedKey = k\n\t\t}\n\n\t\tescapedValues, err := url.QueryUnescape(strings.Join(v, \",\"))\n\t\tif err != nil {\n\t\t\t// Use original joined values if unescaping fails\n\t\t\tescapedValues = strings.Join(v, \",\")\n\t\t}\n\n\t\tharNameValuePair := NameValuePair{\n\t\t\tName:  escapedKey,\n\t\t\tValue: escapedValues,\n\t\t}\n\t\t\n\t\tharQueryString = append(harQueryString, harNameValuePair)\n\t}\n\t\n\treturn harQueryString\n}\n\nfunc parseCookies(cookies []*http.Cookie) []Cookie {\n\tharCookies := make([]Cookie, len(cookies))\n\tfor i, cookie := range cookies {\n\t\tharCookie := Cookie{\n\t\t\tName:     cookie.Name,\n\t\t\tDomain:   cookie.Domain,\n\t\t\tHttpOnly: cookie.HttpOnly,\n\t\t\tPath:     cookie.Path,\n\t\t\tSecure:   cookie.Secure,\n\t\t\tValue:    cookie.Value,\n\t\t}\n\t\tif !cookie.Expires.IsZero() {\n\t\t\tharCookie.Expires = &cookie.Expires\n\t\t}\n\t\tharCookies[i] = harCookie\n\t}\n\treturn harCookies\n}\n\ntype Cookie struct {\n\tName     string     `json:\"name\"`\n\tValue    string     `json:\"value\"`\n\tPath     string     `json:\"path,omitempty\"`\n\tDomain   string     `json:\"domain,omitempty\"`\n\tExpires  *time.Time `json:\"expires,omitempty\"`\n\tHttpOnly bool       `json:\"httpOnly,omitempty\"`\n\tSecure   bool       `json:\"secure,omitempty\"`\n}\n\ntype NameValuePair struct {\n\tName  string `json:\"name\"`\n\tValue string `json:\"value\"`\n}\n\ntype PostData struct {\n\tMimeType string          `json:\"mimeType\"`\n\tParams   []PostDataParam `json:\"params,omitempty\"`\n\tText     string          `json:\"text,omitempty\"`\n\tComment  string          `json:\"comment,omitempty\"`\n}\n\ntype PostDataParam struct {\n\tName        string `json:\"name\"`\n\tValue       string `json:\"value,omitempty\"`\n\tFileName    string `json:\"fileName,omitempty\"`\n\tContentType string `json:\"contentType,omitempty\"`\n\tComment     string `json:\"comment,omitempty\"`\n}\n\ntype Content struct {\n\tSize        int    `json:\"size\"`\n\tCompression int    `json:\"compression,omitempty\"`\n\tMimeType    string `json:\"mimeType\"`\n\tText        string `json:\"text,omitempty\"`\n\tEncoding    string `json:\"encoding,omitempty\"`\n\tComment     string `json:\"comment,omitempty\"`\n}\n\ntype PageTimings struct {\n\tOnContentLoad int64  `json:\"onContentLoad\"`\n\tOnLoad        int64  `json:\"onLoad\"`\n\tComment       string `json:\"comment,omitempty\"`\n}\n\ntype Timings struct {\n\tDns     int64  `json:\"dns,omitempty\"`\n\tBlocked int64  `json:\"blocked,omitempty\"`\n\tConnect int64  `json:\"connect,omitempty\"`\n\tSend    int64  `json:\"send\"`\n\tWait    int64  `json:\"wait\"`\n\tReceive int64  `json:\"receive\"`\n\tSsl     int64  `json:\"ssl,omitempty\"`\n\tComment string `json:\"comment,omitempty\"`\n}\n"
  },
  {
    "path": "ext/html/cp1255.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n  \r\n<html lang=\"he\">\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1255\">\r\n<meta http-equiv=\"Content-Language\" content=\"he\"/>\r\n<!--meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /-->\r\n<META NAME=\"Keywords\" CONTENT=\",  , , Distance Learning, E-Learning, University, Education, Open, Courseware, Israel, Higher Education, Satellite, Telecourses, Courses,  educational technology, computer-mediated,  studies\">\r\n<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\r\n<title>   &quot;</title>\r\n<link rel=\"alternate\" href=\"rss/rss.xml\" type=\"application/rss+xml\" title=\"  &quot;,       \"/>\t\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"design/common.css\">\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"design/news.css\">\r\n\r\n<script language=\"javascript\" type=\"text/javascript\" src=\"js_scripts/shoham_common.js\"></script>\r\n<script language=\"JavaScript\" src=\"include/email_unobfuscator.js\" type=\"text/javascript\"></script>\r\n<script type=\"text/javascript\" src=\"/js_scripts/toolbox.js\"></script> \r\n\r\n<script type=\"text/javascript\">\r\nvar semester='2012b';\t// current semester\r\nvar hebrewSemester;\r\n\r\n//js for the flash movie:\r\nfunction getSemester(){ \r\n\treturn semester;\r\n\t//return '2009a';\r\n}\r\n\r\nsetSemester(semester);\r\nswitchLinkSemester(semester);\r\ncreateSuggestionScript();\r\nswitchCoursesSuggestionSource(semester);\r\n\r\naddLoadEvent(function() {\r\n\r\n\t// ADD JAVASCRIPT EVENTS/FUNCTIONS ETC. YOU WANT TO BE CALLED ONLOAD HERE AND ONLY HERE\r\n\t\r\n\tshowHideMessage();\r\n\t//addIt();\r\n\t//setAddThis();\r\n\t\r\n}  )\r\n\r\n\r\n\r\nvar tBox = new QQtoolBox();\r\n\r\n</script>\r\n\r\n<!--script language=\"javascript\" src=\"js_scripts/flash2.js\"></script-->\r\n\r\n<link rel=\"stylesheet\" href=\"suggestion/suggestion.css\" media=\"screen\" type=\"text/css\">\t\r\n\r\n</head>\r\n\r\n<body>\r\n<div id=\"container\">\r\n<div id=\"header\">\r\n<div id=\"header_right\">\r\n<a href=\"/\" target=\"_self\" title=\"&quot;\"><img src=\"graphics_nhp/shoam_logo_s.jpg\" border=\"0\" alt=\"&quot;\" target=\"_self\"></a>\r\n</div>\r\n\r\n<div id=\"header_left\">\r\n\r\n<div id=\"header_left_op\">\r\n<a href=\"http://www.openu.ac.il/\" target=\"_self\" title=\"'\"><img src=\"graphics_nhp/openu.gif\" border=\"0\" alt=\"'\"></a>\r\n</div>\r\n<!--div id=\"search\"-->\r\n<!-- search_hp.html (START) -->\r\n\r\n<!--script language=\"javascript\" src=\"opusSearch.js\" type=\"text/javascript\"></script-->\r\n\r\n<div id=\"search\">\r\n\r\n\r\n<form name=\"form\" action=\"http://www.google.co.il/search\" id=\"simpleSearch\" target=\"\" method=\"get\">\r\n<input type=\"text\" id=\"as_q\" name=\"as_q\" onFocus=\"this.select()\" value=\"    \">\r\n<input type=\"hidden\" id=\"hl\" name=\"hl\" value=\"iw\">\r\n<input type=\"hidden\" id=\"as_sitesearch\" name=\"as_sitesearch\" value=\"telem.openu.ac.il\">\r\n<!--a id=\"button\" title=\" \" href=\"javascript:simpleSearch();\"><span>&nbsp;</span></a-->\r\n<a id=\"button\" title=\" \" onclick=\"form.submit()\"><span>&nbsp;</span></a>\r\n</form>\r\n\r\n<div id=\"header_eng\"><span class=\"eng\"><a href=\"http://www-e.openu.ac.il/geninfor/shoham.html\" target=\"_self\" title=\"shoham english site\">English</a></span></div>\r\n\r\n</div>\r\n<!-- search_hp.html (END) -->\r\n<!--/div-->\r\n\r\n</div>\r\n<!--end of header-->\r\n</div>\r\n\r\n<div id=\"content\">\r\n\r\n<div id=\"text\">\r\n\r\n<div id=\"right\">\r\n<div id=\"top_right\">\r\n<div id=\"top_right_text\">\r\n\r\n<h1>  </h1>\r\n<!-- suggestion.htm (START) -->\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1255\">\n<meta http-equiv=\"Content-Language\" content=\"he\"/>\n\n<link rel=\"stylesheet\" href=\"suggestion/suggestion.css\" media=\"screen\" type=\"text/css\">\n\n<style type=\"text/css\">\n/* script suggestion CSS START */\n/* uri: don't touch this section unless your name is \"Uri Shefi\" */\n#suggestion_box{\n\t/*dawidth: 245px; */   \t/* causes some courses to disapear */\n\tborder:solid 0px;\n\tfont-size: 15px;\n}\n\n#course_suggestion{ /* input */\n\tdirection: rtl;\n\tfont-family: Arial, Verdana, Tahoma;\n\tfont-size: 12px;\n\tborder: 1px solid #CCCCCC;\n\twidth: 170px;\n\theight:15px;\n\t\n}\n.autocomplete{\n\t/* right: 178px; */\n\twidth: 280px;\n}\n/* script suggestion CSS END */\n\n.suggestion_button{\n\tborder: 0px solid #CCCCCC;\n\tvertical-align:middle;\n\tcursor: pointer;\n\n\t/*\n\twidth:23px;\n    height:19px;\n\t*/\n\tpadding-bottom:2px;\n\n\t\t/*cursor: hand;*/\n}\n#current_semester{\n\n\tfont-family: Arial, Verdana, Tahoma;\n\tfont-size: 15px;\n\ttext-transform: uppercase;\n\twidth: 122px;\n\n}\n#autoComplteMessageLayer{\n\n}\n\n#summerLink, #summerLink a{\n\tcolor: red;\n\tdisplay: none;\n\tfont-weight: bold;\n\tfont-size: 15px;\n\ttext-decoration: underline;\n\tcursor: hand;\n}\n</style>\n\n<div id=\"switch_semester_box\">\n<span id=\"koteret\"></span>\n<select id=\"current_semester\" onChange=\"refreshSemester(this.options[this.selectedIndex].value);\">\n <option value=\"2012b\">2012  ()</option>\n <option value=\"2012a\">2012  ()</option>\n <option value=\"2011c\">2011  ()</option>\n <option value=\"2011b\">2011  ()</option>\n <option value=\"2011a\">2011  ()</option>\n <option value=\"2010c\">2010  ()</option>\n <option value=\"2010b\">2010  ()</option>\n <option value=\"2010a\">2010  ()</option>\n <option value=\"2009c\">2009  ()</option>\n <option value=\"2009b\">2009  ()</option>\n <option value=\"2009a\">2009  ()</option>\n <option value=\"2008c\">2008  ()</option>\n <option value=\"2008b\">2008  ()</option>\n <option value=\"2008a\">2008  ()</option>\n <option value=\"2007c\">2007  ()</option>\n <option value=\"2007b\">2007  ()</option> \n <option value=\"2007a\">2007  ()</option>\n <option value=\"2006c\">2006  ()</option>\n <option value=\"2006b\">2006  ()</option> \n <option value=\"2006a\">2006  ()</option>\n <option value=\"2005c\">2005  ()</option>\n <option value=\"2005b\">2005  ()</option> \n <option value=\"2005a\">2005  ()</option>\n</select>\n</div>\n\n<!-- SEGGUESTION BOX (START) -->\n<div style=\"text-align: right; color:#404040\" id=\"suggestion_box\">\n     <div id=\"suggestion_course_display\"></div><br/>\n<input type=\"text\" id=\"course_suggestion\" value=\"    \" maxlength=\"60\" title=\"    \"><img src=\"graphics_nhp/mgIcon2.jpg\" onclick=\"evalChoice();\" class=\"suggestion_button\" alt=\" \" />\n<!--div id=\"autoComplteMessageLayer\"></div-->\n<!--<span id='summerLink' onClick=\"refreshSemester('2011c');\">  2011  </span>-->\n<span id='summerLink'><a href=\"courses_lists/courses_2011c.html\">  2011  </a></span>\n</div>\n<!-- SEGGUESTION BOX (END) -->\n\n\n<div id=\"fulllist\">\n<a href=\"courses_lists/courses_2009a.html\" id=\"courses_full_link\" target=\"_self\">    </a>  <div id=\"courses_full_link_display\"></div>\n</div>\r\n<!-- suggestion.htm (END) -->\r\n</div>\r\n<!--end of top_right-->\r\n</div>\r\n\r\n<div id=\"bottom_right\">\r\n<div id=\"bottom_right_text\">\r\n<h1> </h1>\r\n<div class=\"linklist_a\">\r\n<a href=\"content/help_staff.html\" target=\"_self\" class=\"linklist_item_a\" title=\" \"> </a><br>\r\n<a href=\"content/training.html\" target=\"_self\" class=\"linklist_item_a\" title=\"  \">   </a>&nbsp;<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span><br>\r\n<a href=\"http://telem.openu.ac.il/segel/\" target=\"_self\" class=\"linklist_item_a\" title=\"  \">  </a><br>\r\n<a href=\"http://telem.openu.ac.il/content/virtual_shoham.html\" target=\"_self\" class=\"linklist_item_a\" title=\" \"> </a><br>\r\n<a href=\"http://telem.openu.ac.il/content/matalot_staff.html\" target=\"_self\" class=\"linklist_item_a\" title=\" \"> </a><br>\r\n<a href=\"http://telem.openu.ac.il/tikshuv_prize/\" target=\"_blank\" class=\"linklist_item_a\" title=\"  \">  </a><br>\r\n<a href=\"http://telem.openu.ac.il/content/hp_search.html\" target=\"_blank\" class=\"linklist_item_a\" title=\"  \">  </a><br>\r\n<!--a href=\"http://telem.openu.ac.il/courses/resources/private/oracle_Brochure.pdf\" target=\"_blank\" class=\"linklist_item_a\" title=\"    \">  </a>&nbsp;&nbsp;(<a href=\"https://sheilta.apps.openu.ac.il/pls/myopr/BOOKLET.FIRST\" target=\"_blank\" title=\" \"> </a>)<!--&nbsp;&nbsp;&nbsp;<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br-->\r\n<a href=\"http://www.openu.ac.il/surveys/teaching_reports.html\" target=\"_blank\" class=\"linklist_item_a\" title=\"  \">  </a>&nbsp;&nbsp;(<img border=\"0\"  src=\"graphics_nhp/content_img/lockg.gif\" title=\"     &quot;\" alt=\"     &quot;\" />) \r\n<!--&nbsp;&nbsp;&nbsp;<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n\r\n\r\n<a href=\"https://sso.apps.openu.ac.il/login?T_PLACE=https://sheilta.apps.openu.ac.il/pls/myopr/PELE.FIRST\" target=\"_blank\" class=\"linklist_item_a\" title=\" \">  </a>&nbsp;\r\n(<img border=\"0\"   src=\"graphics_nhp/content_img/lockg.gif\" title=\"     &quot;\" alt=\"     &quot;\" />) \r\n\r\n<!--&nbsp;&nbsp;<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n\r\n\r\n<a href=\"http://opal.openu.ac.il\" title=\"   \"    target=\"_blank\" class=\"new\"> </a><!--&nbsp;<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n\r\n\r\n\r\n<a href=\"http://opal.openu.ac.il/course/view.php?id=107\" title=\" \"    target=\"_blank\">   -  </a>&nbsp;(<img border=\"0\"   src=\"graphics_nhp/content_img/lockg.gif\" title=\"     &quot;\" alt=\"     &quot;\" />) \r\n<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\"><span class=\"new\"></span><br>\r\n\r\n\r\n\r\n<br>\r\n</div>\r\n\r\n<h1></h1>\r\n<div class=\"linklist_a\">\r\n<a href=\"/content/help_students.html\" target=\"_self\" class=\"linklist_item_a\" title=\" \"> </a><br>\r\n<a href=\"https://sheilta.apps.openu.ac.il/pls/dmyopt2/user_form.first?p_from=http://telem.openu.ac.il/hp_files/html_files/user_info.html\" target=\"_blank\" class=\"linklist_item_a\" title=\"  \">  </a><br>\r\n\r\n<a href=\"http://www.openu.ac.il/sheilta/\" target=\"_blank\" class=\"linklist_item_a\" title=\"'\">&quot;</a><br>\r\n\r\n<a href=\"https://sheilta.apps.openu.ac.il/pls/dmyopt2/LUACH_SHANA.first?p_time=\" target=\"_blank\" class=\"linklist_item_a\" title=\"  '\">  - &quot;</a><br>\r\n\r\n<a href=\"/content/matalot_stud.html\" target=\"_self\" class=\"linklist_item_a\" title=\" \"> </a><br>\r\n\r\n<a href=\"/content/ofek.html\" target=\"_self\" class=\"linklist_item_a\" title=\"\"> -  </a>&nbsp;&nbsp;&nbsp;&nbsp;<!--img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span--><br>\r\n\r\n\r\n\r\n<a href=\"http://telem.openu.ac.il/content/elluminate_support.html\" target=\"_self\" class=\"linklist_item_a\" title=\"   (Elluminate)\">   (Elluminate) </a><br>\r\n\r\n\r\n<!--<a href=\"http://telem.openu.ac.il/content/GoToMeeting.html\" target=\"_self\" class=\"linklist_item_a\" title=\"   (GoToMeeting)\">   (GoToMeeting) </a><br>-->\r\n\r\n\r\n<a href=\"/content/courseware.html\" target=\"_self\" class=\"linklist_item_a\" title=\"  \">  </a><br>\r\n\r\n<a href=\"http://telem.openu.ac.il/academic-paper/index.htm\" target=\"_blank\" class=\"linklist_item_a\" title=\"  \">   ()</a><br>\r\n\r\n<a href=\"http://estudy.openu.ac.il/opus/bin/en.jsp?enPage=AnnotationsPage&enDispWho=Annotations&enZone=Annotations\" target=\"_blank\" class=\"linklist_item_a\" title=\"  \"> </a><br>\r\n \r\n\r\n<a href=\"http://www.openu.ac.il/new-student/\" target=\"_blank\" class=\"linklist_item_a\" title=\" \">  </a> \r\n<br>\r\n\r\n\r\n \r\n\r\n\r\n</div>\r\n \r\n<!--<div id=\"mashov_gen\">\r\n<div class=\"mashov\"><a href=\"http://forum.openu.ac.il/opus/bin/en.jsp?enZone=Forum117583\" target=\"_blank\" title=\" &quot;\">  -     , , ... </a></div>\r\n</div>-->\r\n</div>\r\n\r\n<!--end of bootom_right-->\r\n</div>\r\n<!--end of right-->\r\n</div>\r\n\r\n<div id=\"left\">\r\n<div id=\"flash_zone\" title=\"Falsh Movie\" astyle=\"background:url(graphics_nhp/flash.jpg) 100 0 no-repeat;\">\r\n<!--  <script type=\"text/javascript\" src=\"js_scripts/flash.js\"></script> \r\n--><!--must be after the last </boject> -->\r\n\r\n<img src=\"graphics_nhp/flash.jpg\" border=\"0\" alt=\"rss\" valign=\"left\" align=\"abstop\">\r\n</div>\r\n\r\n<div id=\"message\" class=\"messageHidden\">\r\n\n\n<!-- -->\n<!--spring-->\n<div class=\"tm\">\n<div class=\"img\">\n<IMG alt=\"\" src=\"graphics_nhp/message_gifs/icon_spring.gif\" border=0></div>\n<strong>&nbsp;&nbsp; 2012  .  \"    .</strong>\n<span class=\"date\">(4.3.12)</span>\n</div>\r\n</div>\r\n\r\n<!--center><img src=\"content/happy_holiday.gif\" style=\"padding: 3px; margin: 3px;\"></center-->\r\n\r\n<div id=\"links_zone\">\r\n\r\n\r\n<div id=\"links_right\">\r\n<h1>\r\n<span class=\"news\"> </span>\r\n<span class=\"rss\">\r\n<span class=\"rss_text\"><a href=\"content/rss_help.html\" target=\"_blank\" title=\"\"><span id=\"rss_gif\">?</span>rss&nbsp;</a><img src=\"graphics_nhp/rss.jpg\" border=\"0\" alt=\"rss\" valign=\"left\" align=\"abstop\"></span></span>\r\n\r\n\r\n</h1>\r\n\r\n<div id=\"rss_list\">\r\n<ul class='newsBriefList'>\n\n\t<li class='newsBriefItem'><a href='http://shohamnews.blogspot.com/2012/03/html5.html' target='_blank'>HTML5</a>\n<div class='newsBriefDate'>2012-03-21 12:45:00</div>\n\t</li>\n\t<li class='newsBriefItem'><a href='http://shohamnews.blogspot.com/2012/03/2012.html' target='_blank'>     &quot;  2012</a>\n<div class='newsBriefDate'>2012-03-07 12:52:00</div>\n\t</li>\n\t<li class='newsBriefItem'><a href='http://shohamnews.blogspot.com/2012/03/19312.html' target='_blank'>        &quot; - 19.3.12</a>\n<div class='newsBriefDate'>2012-03-07 09:03:00</div>\n\t</li>\n\t<li class='newsBriefItem'><a href='http://peer-news.blogspot.com/2012/01/blog-post_18.html' target='_blank'>    &#39;: &quot;     </a>\n<div class='newsBriefDate'>2012-01-18 14:41:00</div>\n\t</li>\n\t<li class='newsBriefItem'><a href='http://peer-news.blogspot.com/2012/01/blog-post.html' target='_blank'>    &#39;: &quot;  &#39;  </a>\n<div class='newsBriefDate'>2012-01-18 14:31:00</div>\n\t</li>\n\t<li class='newsBriefItem'><a href='http://peer-news.blogspot.com/2012/01/10934.html' target='_blank'>   &quot; -  &quot; (10934)</a>\n<div class='newsBriefDate'>2012-01-15 14:21:00</div>\n\t</li>\n\n</ul>\n\r\n</div>\r\n\r\n<div id=\"allnews\">\r\n<div id=\"allnews_link\">\r\n<a href=\"content/news.html\">  </a>\r\n<!--a href=\"/content/news.html\" target=\"_blank\" title=\"  \"-->\r\n</div>\r\n</div>\r\n</div>\r\n\r\n\r\n\r\n<div id=\"links_mid\">\r\n<h1> </h1>\r\n<div class=\"linklist\">\r\n<a href=\"/content/about_shoham.html\" target=\"_self\" class=\"linklist_item\" title=\" &quot;\"> &quot;</a><br>\r\n\r\n\r\n<a href=\"http://telem.openu.ac.il/content/about_shoham.html#organizational_structure\" target=\"_self\" class=\"linklist_item\" title=\" \"> </a><!--&nbsp;<img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--> <br>\r\n\r\n\r\n<a href=\"/content/distance_edu.html\" target=\"_self\" class=\"linklist_item\" title=\"  \">  </a><br>\r\n<a href=\"/content/courses_sites.html\" target=\"_self\" class=\"linklist_item\" title=\" \"> </a><br>\r\n\r\n\r\n<a href=\"http://telem.openu.ac.il/content/moodle_move.html\" target=\"_self\" class=\"linklist_item\" title=\"   &quot; Moodle\">   &quot; Moodle</a>&nbsp;<img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\"><br>\r\n\r\n<a href=\"/content/vid_learning.html\" target=\"_self\" class=\"linklist_item\" title=\"    \">  </a> <img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span><br>\r\n\r\n\r\n<a href=\"/content/virtual_class.html\" target=\"_self\" class=\"linklist_item\" title=\"   -   \">  -  </a> <!--<img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n\r\n<!--<a href=\"http://telem.openu.ac.il/content/elluminate.html\" target=\"_self\" class=\"linklist_item\" title=\"  -  Elluminate\">  -  Elluminate</a> --> \r\n\r\n\r\n<a href=\"http://telem.openu.ac.il/content/video_classes.html\" target=\"_self\" class=\"linklist_item\" title=\"    \">   </a><!-- <img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n\r\n\r\n<a href=\"/content/digital.html\" target=\"_self\" class=\"linklist_item\" title=\"  \">  </a> <!--<img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n<a href=\"/content/pedagogical_dev.html\" target=\"_self\" class=\"linklist_item\" title=\" ,  \"> ,  </a><br>\r\n<a href=\"/content/tools.html\" target=\"_self\" class=\"linklist_item\" title=\"   \">   </a><br>\r\n<!--<a href=\"/content/future_project.html\" target=\"_self\" class=\"linklist_item\" title=\"  \">    </a> &nbsp;--><!--<img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--> \r\n\r\n<a href=\"http://telem.openu.ac.il/content/sadan.html\" target=\"_self\" class=\"linklist_item\" title=\"&quot; -   \">  </a>&nbsp;<img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span><br>\r\n\r\n \r\n<a href=\"http://telem.openu.ac.il/content/audio_books.html\" target=\"_self\" class=\"linklist_item\" title=\"  &ndash;   \">     </a> <!--&nbsp;<img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\">  --><br>\r\n\r\n \r\n\r\n<a href=\"/content/cooperative_learning.html\" target=\"_self\" class=\"linklist_item\" title=\" \"> </a><br>\r\n<a href=\"http://wiki-openu.openu.ac.il/courses/wikiop\" target=\"_blank\" class=\"linklist_item\" title=\"\"><img src=\"graphics_nhp/wiki.gif\" border=\"0\" alt=\"\">&nbsp;</a><br>\r\n</div>\r\n\r\n<br>\r\n<div class=\"linklist_em\">\r\n<a href=\"http://telem.openu.ac.il/hp_files/paper/archive/index.html\" target=\"_self\" class=\"linklist_item\" title=\" \">  -  &quot;</a><!-- <img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br> \r\n<a href=\"http://www.shoham-at-ou.blogspot.com/\" target=\"_blank\" class=\"linklist_item\" title=\" &quot;\"> &quot;</a><br>\r\n<img src=\"graphics_nhp/twitter_icon_small.gif\" border=\"0\" alt=\"\" align=\"absbottom\">&nbsp; <a href=\"http://twitter.com/shoham1\" target=\"_blank\" class=\"linklist_item\" title=\"twitter\">  -twitter </a><!--img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span--><br>\r\n<a href=\"content/articles.html\" target=\"_self\" class=\"linklist_item\" title=\"  \"> ,  </a><!-- <img src=\"graphics_nhp/star_bg_gray.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span>--><br>\r\n<a href=\"content/lectures.html\" target=\"_self\" class=\"linklist_item\" title=\" \">   </a><!-- <img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span><br>-->\r\n</div>\r\n</div>\r\n\r\n\r\n\r\n\r\n<div id=\"links_left\">\r\n\r\n<!--div class=\"linklist\" id=\"linklist\"-->\r\n<!--a href=\"http://telem.openu.ac.il/content/contact_us.html\" class=\"linklist_item\" title=\" \"> </a><br-->\r\n<!--a href=\"http://www-e.openu.ac.il/geninfor/136.html\" target=\"_self\" class=\"linklist_item\" title=\"english\">English</a><br><br-->\r\n<!--a href=\"http://ocw.openu.ac.il/\" target=\"_blank\" class=\"linklist_item_gif\" title=\"'\"><img src=\"graphics_nhp/ocw.gif\" border=\"0\" alt=\"'\"></a><br-->\r\n<!--/div-->\r\n\r\n\r\n<h1>  </h1>\r\n <img src=\"graphics_nhp/logos8.jpg\" width=\"170\" border=\"1\" usemap=\"#Map\">\r\n <map name=\"Map\">\r\n  <area shape=\"rect\" coords=\"2,68,81,106\" href=\"http://goo.gl/4BPH\" target=\"_blank\" title=\" &quot;  \">\r\n  <area shape=\"rect\" coords=\"89,68,167,108\" href=\"http://www.youtube.com/user/openofek\" target=\"_blank\" title=\" YouTub   &quot;\">\r\n  <area shape=\"rect\" coords=\"90,112,168,154\" href=\"http://www.kotar.co.il/\" target=\"_blank\" title=\" &quot;    &quot;\">\r\n  <area shape=\"rect\" coords=\"13,2,155,61\" href=\"http://ocw.openu.ac.il/\" target=\"_blank\" alt=\"    \"  title=\"    \">\r\n  <area shape=\"rect\" coords=\"2,113,81,154\" onclick=\"tBox.objVisibility('iconmenu');\" target=\"_blank\" title=\" &quot;  \">\r\n\r\n</map>\r\n\r\n<div id=\"iconmenu\" style=\"display: none; border: 1px solid gray; background-color: white; padding-right:2px; font-size:1.3em;    color:#336699;\tpadding-right:2px; \">\r\n<a href=\"http://goo.gl/kBQ1F\" title=\"\" target=\"_blank\" class=\"linklist_item\"></a><br>\r\n<a href=\"http://goo.gl/dywt\" title=\" \" target=\"_blank\"class=\"linklist_item\"> </a>\r\n\r\n\r\n<div style=\"color: blue; cursor: pointer; text-align: center;\" onclick=\"tBox.objVisibility('iconmenu', 0);\">[x] </div> \r\n\r\n</div> \r\n \r\n \r\n<br><br>\r\n<div class=\"linklist_em\">\r\n <a href=\"/content/opensources.html\" target=\"_self\" class=\"linklist_item\" title=\"   \">     </a><!--img src=\"graphics_nhp/star.gif\" border=\"0\" alt=\"\">&nbsp;<span class=\"new\"></span--><br>\r\n\r\n</div>\r\n<!--<div class=\"linklist_em\">\r\n<a href=\"http://ocw.openu.ac.il/\" target=\"_blank\" class=\"linklist_item_gif\" title=\"'\"><img src=\"graphics_nhp/ocw_s1_gray.gif\" border=\"0\" alt='\" -   '></a><br>\r\n<a href=\"http://www.kotar.co.il/\" target=\"_blank\" class=\"linklist_item\" title=' \"   \"'>  \"   \"</a><br>\r\n<a href=\"http://goo.gl/4BPH\" target=\"_blank\" class=\"linklist_item\" title=' \" google books'>    \"   google books  </a> <br> \r\n<a href=\"http://www.youtube.com/user/openofek\" target=\"_blank\" class=\"linklist_item\" title=' YouTube   \"'><img src=\"graphics_nhp/youtube_small.gif\" alt=\"youtube\" width=\"40\" height=\"17\" border=\"0\" align=\"absbottom\">&nbsp;  \"</a>\r\n<a href=\"http://www.icast.co.il/default.aspx?p=default&c_list=1&c=82\" target=\"_blank\" class=\"linklist_item\" title=' \" - iCast'> \" - iCast &nbsp;<!--img src=\"graphics_nhp/icast_logo.png\" border=\"0\" alt=\"youtube\" align=\"absbottom\"></a><br>-->\r\n\r\n<!--<a href=\"http://ocw.openu.ac.il/newsletter/01.html\" target=\"_blank\" class=\"linklist_item\" title=' -  \"'>    -  \"      </a> <br> -->\r\n\r\n<!--a href=\"content/presentations.html\" target=\"_self\" class=\"linklist_item\" title=\" \"> </a><br>\r\n\r\n</div>-->\r\n<br>\r\n<h1></h1>\r\n<div class=\"linklist\" id=\"links\">\r\n<a href=\"http://www.openu.ac.il/Library/\" target=\"_blank\" class=\"linklist_item\" title=\"  \">  </a><br>\r\n<a href=\"http://www.openu.ac.il/innovation/\" target=\"_blank\" class=\"linklist_item\" title=\"    \">    </a><br>\r\n<a href=\"http://meital.iucc.ac.il/\" target=\"_blank\" class=\"linklist_item\" title=\"    \">&quot;</a><br>\r\n\r\n<!--<a href=\"http://wiki-openu.openu.ac.il/courses/wikiop\" target=\"_blank\" class=\"linklist_item\" title=\"\"><img src=\"graphics_nhp/oui_books_icon.jpg\" alt=\" \" width=\"23\" height=\"13\" border=\"0\">&nbsp;  </a><br>-->\r\n</div>\r\n\r\n\r\n<!-- share this button (start) \r\n<span  class='st_sharethis' displayText='ShareThis' style=\"width: 100px; height: 100px;\"></span>\r\n<script type=\"text/javascript\">var switchTo5x=false;</script><script type=\"text/javascript\" src=\"http://w.sharethis.com/button/buttons.js\"></script><script type=\"text/javascript\">stLight.options({publisher:'c009e98c-ad2e-4156-afda-95a59aca520c'});</script>\r\n share this button (end) -->\r\n\r\n \r\n<!-- AddToAny BEGIN -->\r\n<a class=\"a2a_dd\" href=\"http://www.addtoany.com/share_save\"><img src=\"http://static.addtoany.com/buttons/share_save_171_16.png\" width=\"171\" height=\"16\" border=\"0\" alt=\"Share\"/></a>\r\n<script type=\"text/javascript\">\r\nvar a2a_config = a2a_config || {};\r\na2a_config.onclick = 1;\r\na2a_config.locale = \"he\";\r\na2a_config.num_services = 4;\r\n</script>\r\n<script type=\"text/javascript\" src=\"http://static.addtoany.com/menu/page.js\"></script>\r\n<!-- AddToAny END -->\r\n\r\n<!-- google +1 (START) -->\r\n<script type=\"text/javascript\" src=\"http://apis.google.com/js/plusone.js\">\r\n  {lang: 'iw'}\r\n</script>\r\n<g:plusone size=\"small\"></g:plusone>\r\n<!-- google +1 (START) -->\r\n\r\n\r\n</div>\r\n\r\n<!--end of links_zone-->\r\n</div>\r\n\r\n<!--end of left-->\r\n</div>\r\n\r\n<!--end of text-->\r\n</div>\r\n<div id=\"close\">\r\n\r\n</div>\r\n\r\n<!--end of content-->\r\n</div>\r\n<div id=\"footer\">\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1255\" />\r\n<meta http-equiv=\"Content-Language\" content=\"he\" />\r\n\r\n<div id=\"footer1\">\r\n\r\n<a href=\"http://telem.openu.ac.il/content/about_shoham.html\" id=\"abouts\" title=\"\" target=\"_blank\"></a><!--&nbsp;|-->\r\n<a href=\"http://telem.openu.ac.il/content/contact_us.html\" id=\"conts\" title=\" \" target=\"_blank\"> </a><!--&nbsp;|-->\r\n<a href=\"/\" id=\"hps\" title=\" \" target=\"_self\"> </a><!--&nbsp;|-->\r\n<a href=\"http://www.openu.ac.il/\" id=\"opnets\" title=\"'\" target=\"_self\"><img src=\"http://telem.openu.ac.il/graphics_nhp/openu_icon.gif\" border=\"0\" alt=\"\" /></a><!--&nbsp;|--> \r\n</div>\r\n\r\n<div id=\"footer2\">\r\n<table id=\"copyRight\" style=\"display: none;\" align=\"center\" width=\"180\" cellspacing=\"0\" cellpadding=\"1\" border=\"0\"  bgcolor=\"#000000\">\r\n <tr>\r\n  <td>\r\n   <table cellspacing=\"0\" cellpadding=\"3\" border=\"0\" width=\"100%\">\r\n\t<tr>\r\n\t <td bgcolor=\"#FFFFFF\">\r\n\t<span id=\"open\" style=\"cursor: pointer;\" onclick=\"showHideLayer('copyRight', '', '');\">[]</span>\r\n\t </td>\r\n\t</tr>\r\n    <tr>\r\n\t <td bgcolor=\"#FFFFFF\">\r\n   <center><b>       ,         ' <BR /></b>\r\n   </center>\r\n     </td>\r\n\t</tr>\r\n   </table>\r\n  </td>\r\n </tr>\r\n</table>\r\n\r\nCopyright &copy;,The Open University of Israel 1997-2012 <b style=\"cursor: pointer;\" onclick=\"showHideLayer('copyRight', '', '');\">all rights reserved<br />\r\n  \t</b>&copy;  . \" - 2012\r\n</div>\r\n\r\n<!--include virtual=\"/include/general/footer_source_popup_shoham_logical.htm\"  -->   \r\n<!--include virtual=\"/include/general/footer_code_popup_shoham_logical.html\"  -->\r\n\r\n\r\n<!-- GOOGLE ANALYTICS (TELEM ONLY) START -->\n<script type=\"text/javascript\">\nvar gaJsHost = ((\"https:\" == document.location.protocol) ? \"https://ssl.\" : \"http://www.\");\ndocument.write(unescape(\"%3Cscript src='\" + gaJsHost + \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n</script>\n<script type=\"text/javascript\">\nvar pageTracker = _gat._getTracker(\"UA-4646503-5\");\n//pageTracker._initData();\npageTracker._trackPageview();\n</script>\n<!-- GOOGLE ANALYTICS (TELEM ONLY) END -->\r\n\r\n\r\n</div>\r\n<!--end of container-->\r\n</div>\r\n\r\n\r\n\r\n</body>\r\n</html>\r\n\r\n\r\n"
  },
  {
    "path": "ext/html/cp1255.txt",
    "content": ""
  },
  {
    "path": "ext/html/html.go",
    "content": "// extension to goproxy that will allow you to easily filter web browser related content.\npackage goproxy_html\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"golang.org/x/net/html/charset\"\n\t\"golang.org/x/text/transform\"\n)\n\nvar IsHtml goproxy.RespCondition = goproxy.ContentTypeIs(\"text/html\")\n\nvar IsCss goproxy.RespCondition = goproxy.ContentTypeIs(\"text/css\")\n\nvar IsJavaScript goproxy.RespCondition = goproxy.ContentTypeIs(\"text/javascript\",\n\t\"application/javascript\")\n\nvar IsJson goproxy.RespCondition = goproxy.ContentTypeIs(\"text/json\")\n\nvar IsXml goproxy.RespCondition = goproxy.ContentTypeIs(\"text/xml\")\n\nvar IsWebRelatedText goproxy.RespCondition = goproxy.ContentTypeIs(\n\t\"text/html\",\n\t\"text/css\",\n\t\"text/javascript\", \"application/javascript\",\n\t\"text/xml\",\n\t\"text/json\",\n)\n\n// HandleString will receive a function that filters a string, and will convert the\n// request body to a utf8 string, according to the charset specified in the Content-Type\n// header.\n// guessing Html charset encoding from the <META> tags is not yet implemented.\nfunc HandleString(f func(s string, ctx *goproxy.ProxyCtx) string) goproxy.RespHandler {\n\treturn HandleStringReader(func(r io.Reader, ctx *goproxy.ProxyCtx) io.Reader {\n\t\tb, err := io.ReadAll(r)\n\t\tif err != nil {\n\t\t\tctx.Warnf(\"Cannot read string from resp body: %v\", err)\n\t\t\treturn r\n\t\t}\n\t\treturn bytes.NewBufferString(f(string(b), ctx))\n\t})\n}\n\n// Will receive an input stream which would convert the response to utf-8\n// The given function must close the reader r, in order to close the response body.\nfunc HandleStringReader(f func(r io.Reader, ctx *goproxy.ProxyCtx) io.Reader) goproxy.RespHandler {\n\treturn goproxy.FuncRespHandler(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tif ctx.Error != nil {\n\t\t\treturn nil\n\t\t}\n\t\tcharsetName := ctx.Charset()\n\t\tif charsetName == \"\" {\n\t\t\tcharsetName = \"utf-8\"\n\t\t}\n\n\t\tif strings.ToLower(charsetName) != \"utf-8\" {\n\t\t\ttr, _ := charset.Lookup(charsetName)\n\t\t\tif tr == nil {\n\t\t\t\tctx.Warnf(\"Cannot convert from %s to utf-8: not found\", charsetName)\n\t\t\t\treturn resp\n\t\t\t}\n\n\t\t\t// Pass UTF-8 data to the callback f() function and convert its\n\t\t\t// result back to the original encoding\n\t\t\tr := transform.NewReader(resp.Body, tr.NewDecoder())\n\t\t\tnewr := transform.NewReader(f(r, ctx), tr.NewEncoder())\n\t\t\tresp.Body = &readFirstCloseBoth{io.NopCloser(newr), resp.Body}\n\t\t} else {\n\t\t\t//no translation is needed, already at utf-8\n\t\t\tresp.Body = &readFirstCloseBoth{io.NopCloser(f(resp.Body, ctx)), resp.Body}\n\t\t}\n\t\treturn resp\n\t})\n}\n\ntype readFirstCloseBoth struct {\n\tr io.ReadCloser\n\tc io.Closer\n}\n\nfunc (rfcb *readFirstCloseBoth) Read(b []byte) (nr int, err error) {\n\treturn rfcb.r.Read(b)\n}\nfunc (rfcb *readFirstCloseBoth) Close() error {\n\terr1 := rfcb.r.Close()\n\terr2 := rfcb.c.Close()\n\tif err1 != nil && err2 != nil {\n\t\treturn errors.New(err1.Error() + \", \" + err2.Error())\n\t}\n\tif err1 != nil {\n\t\treturn err1\n\t}\n\treturn err2\n}\n"
  },
  {
    "path": "ext/html/html_test.go",
    "content": "package goproxy_html_test\n\nimport (\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/ext/html\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n)\n\ntype ConstantServer int\n\nfunc (s ConstantServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"text/plain; charset=iso-8859-8\")\n\tw.Write([]byte{0xe3, 0xf3})\n}\n\nfunc TestCharset(t *testing.T) {\n\ts := httptest.NewServer(ConstantServer(1))\n\tdefer s.Close()\n\n\tch := make(chan string, 2)\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse().Do(goproxy_html.HandleString(\n\t\tfunc(s string, ctx *goproxy.ProxyCtx) string {\n\t\t\tch <- s\n\t\t\treturn s\n\t\t}))\n\tproxyServer := httptest.NewServer(proxy)\n\tdefer proxyServer.Close()\n\n\tproxyUrl, _ := url.Parse(proxyServer.URL)\n\tclient := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}}\n\n\tresp, err := client.Get(s.URL + \"/cp1255.txt\")\n\tif err != nil {\n\t\tt.Fatal(\"GET:\", err)\n\t}\n\tb, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(\"readAll:\", err)\n\t}\n\tresp.Body.Close()\n\n\tinHandleString := \"\"\n\tselect {\n\tcase inHandleString = <-ch:\n\tdefault:\n\t}\n\n\tif len(b) != 2 || b[0] != 0xe3 || b[1] != 0xf3 {\n\t\tt.Error(\"Did not translate back to 0xe3,0xf3, instead\", b)\n\t}\n\tif inHandleString != \"דף\" {\n\t\tt.Error(\"HandleString did not convert DALET & PEH SOFIT (דף) from ISO-8859-8 to utf-8, got\", []byte(inHandleString))\n\t}\n}\n"
  },
  {
    "path": "ext/image/image.go",
    "content": "package goproxy_image\n\nimport (\n\t\"bytes\"\n\t. \"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/regretable\"\n\t\"image\"\n\t_ \"image/gif\"\n\t\"image/jpeg\"\n\t\"image/png\"\n\t\"io\"\n\t\"net/http\"\n)\n\nvar RespIsImage = ContentTypeIs(\"image/gif\",\n\t\"image/jpeg\",\n\t\"image/pjpeg\",\n\t\"application/octet-stream\",\n\t\"image/png\")\n\n// \"image/tiff\" tiff support is in external package, and rarely used, so we omitted it\n\nfunc HandleImage(f func(img image.Image, ctx *ProxyCtx) image.Image) RespHandler {\n\treturn FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {\n\t\tif !RespIsImage.HandleResp(resp, ctx) {\n\t\t\treturn resp\n\t\t}\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t// we might get 304 - not modified response without data\n\t\t\treturn resp\n\t\t}\n\t\tcontentType := resp.Header.Get(\"Content-Type\")\n\n\t\tconst kb = 1024\n\t\tregret := regretable.NewRegretableReaderCloserSize(resp.Body, 16*kb)\n\t\tresp.Body = regret\n\t\timg, imgType, err := image.Decode(resp.Body)\n\t\tif err != nil {\n\t\t\tregret.Regret()\n\t\t\tctx.Warnf(\"%s: %s\", ctx.Req.Method+\" \"+ctx.Req.URL.String()+\" Image from \"+ctx.Req.RequestURI+\"content type\"+\n\t\t\t\tcontentType+\"cannot be decoded returning original image\", err)\n\t\t\treturn resp\n\t\t}\n\t\tresult := f(img, ctx)\n\t\tbuf := bytes.NewBuffer([]byte{})\n\t\tswitch contentType {\n\t\t// No gif image encoder in go - convert to png\n\t\tcase \"image/gif\", \"image/png\":\n\t\t\tif err := png.Encode(buf, result); err != nil {\n\t\t\t\tctx.Warnf(\"Cannot encode image, returning orig %v %v\", ctx.Req.URL.String(), err)\n\t\t\t\treturn resp\n\t\t\t}\n\t\t\tresp.Header.Set(\"Content-Type\", \"image/png\")\n\t\tcase \"image/jpeg\", \"image/pjpeg\":\n\t\t\tif err := jpeg.Encode(buf, result, nil); err != nil {\n\t\t\t\tctx.Warnf(\"Cannot encode image, returning orig %v %v\", ctx.Req.URL.String(), err)\n\t\t\t\treturn resp\n\t\t\t}\n\t\tcase \"application/octet-stream\":\n\t\t\tswitch imgType {\n\t\t\tcase \"jpeg\":\n\t\t\t\tif err := jpeg.Encode(buf, result, nil); err != nil {\n\t\t\t\t\tctx.Warnf(\"Cannot encode image as jpeg, returning orig %v %v\", ctx.Req.URL.String(), err)\n\t\t\t\t\treturn resp\n\t\t\t\t}\n\t\t\tcase \"png\", \"gif\":\n\t\t\t\tif err := png.Encode(buf, result); err != nil {\n\t\t\t\t\tctx.Warnf(\"Cannot encode image as png, returning orig %v %v\", ctx.Req.URL.String(), err)\n\t\t\t\t\treturn resp\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tpanic(\"unhandlable type\" + contentType)\n\t\t}\n\t\tresp.Body = io.NopCloser(buf)\n\t\treturn resp\n\t})\n}\n"
  },
  {
    "path": "ext/image/image_test.go",
    "content": "package goproxy_image_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"github.com/elazarl/goproxy\"\n\tgoproxy_image \"github.com/elazarl/goproxy/ext/image\"\n\t\"image\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n)\n\nvar acceptAllCerts = &tls.Config{InsecureSkipVerify: true}\n\nfunc oneShotProxy(proxy *goproxy.ProxyHttpServer, t *testing.T) (client *http.Client, s *httptest.Server) {\n\ts = httptest.NewServer(proxy)\n\n\tproxyUrl, _ := url.Parse(s.URL)\n\ttr := &http.Transport{TLSClientConfig: acceptAllCerts, Proxy: http.ProxyURL(proxyUrl)}\n\tclient = &http.Client{Transport: tr}\n\treturn\n}\n\nfunc getImage(file string, t *testing.T) image.Image {\n\tnewimage, err := os.ReadFile(file)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot read file\", file, err)\n\t}\n\timg, _, err := image.Decode(bytes.NewReader(newimage))\n\tif err != nil {\n\t\tt.Fatal(\"Cannot decode image\", file, err)\n\t}\n\treturn img\n}\n\nfunc compareImage(eImg, aImg image.Image, t *testing.T) {\n\tif eImg.Bounds().Dx() != aImg.Bounds().Dx() || eImg.Bounds().Dy() != aImg.Bounds().Dy() {\n\t\tt.Error(\"image sizes different\")\n\t\treturn\n\t}\n\tfor i := 0; i < eImg.Bounds().Dx(); i++ {\n\t\tfor j := 0; j < eImg.Bounds().Dy(); j++ {\n\t\t\ter, eg, eb, ea := eImg.At(i, j).RGBA()\n\t\t\tar, ag, ab, aa := aImg.At(i, j).RGBA()\n\t\t\tif er != ar || eg != ag || eb != ab || ea != aa {\n\t\t\t\tt.Error(\"images different at\", i, j, \"vals\\n\", er, eg, eb, ea, \"\\n\", ar, ag, ab, aa, aa)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nvar fs = httptest.NewServer(http.FileServer(http.Dir(\".\")))\n\nfunc localFile(url string) string { return fs.URL + \"/\" + url }\n\nfunc TestConstantImageHandler(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tfootball := getImage(\"test_data/football.png\", t)\n\tproxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {\n\t\treturn football\n\t}))\n\n\tclient, l := oneShotProxy(proxy, t)\n\tdefer l.Close()\n\n\tresp, err := client.Get(localFile(\"test_data/panda.png\"))\n\tif err != nil {\n\t\tt.Fatal(\"Cannot get panda.png\", err)\n\t}\n\n\timg, _, err := image.Decode(resp.Body)\n\tif err != nil {\n\t\tt.Error(\"decode\", err)\n\t} else {\n\t\tcompareImage(football, img, t)\n\t}\n}\n\nfunc TestImageHandler(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tfootball := getImage(\"test_data/football.png\", t)\n\n\tproxy.OnResponse(goproxy.UrlIs(\"/test_data/panda.png\")).Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {\n\t\treturn football\n\t}))\n\n\tclient, l := oneShotProxy(proxy, t)\n\tdefer l.Close()\n\n\tresp, err := client.Get(localFile(\"test_data/panda.png\"))\n\tif err != nil {\n\t\tt.Fatal(\"Cannot get panda.png\", err)\n\t}\n\n\timg, _, err := image.Decode(resp.Body)\n\tif err != nil {\n\t\tt.Error(\"decode\", err)\n\t} else {\n\t\tcompareImage(football, img, t)\n\t}\n\n\t// and again\n\tresp, err = client.Get(localFile(\"test_data/panda.png\"))\n\tif err != nil {\n\t\tt.Fatal(\"Cannot get panda.png\", err)\n\t}\n\n\timg, _, err = image.Decode(resp.Body)\n\tif err != nil {\n\t\tt.Error(\"decode\", err)\n\t} else {\n\t\tcompareImage(football, img, t)\n\t}\n}\n\nfunc fatalOnErr(err error, msg string, t *testing.T) {\n\tif err != nil {\n\t\tt.Fatal(msg, err)\n\t}\n}\n\nfunc get(url string, client *http.Client) ([]byte, error) {\n\tresp, err := client.Get(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttxt, err := io.ReadAll(resp.Body)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn txt, nil\n}\n\nfunc getOrFail(url string, client *http.Client, t *testing.T) []byte {\n\ttxt, err := get(url, client)\n\tif err != nil {\n\t\tt.Fatal(\"Can't fetch url\", url, err)\n\t}\n\treturn txt\n}\n\nfunc TestReplaceImage(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\n\tpanda := getImage(\"test_data/panda.png\", t)\n\tfootball := getImage(\"test_data/football.png\", t)\n\n\tproxy.OnResponse(goproxy.UrlIs(\"/test_data/panda.png\")).Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {\n\t\treturn football\n\t}))\n\tproxy.OnResponse(goproxy.UrlIs(\"/test_data/football.png\")).Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {\n\t\treturn panda\n\t}))\n\n\tclient, l := oneShotProxy(proxy, t)\n\tdefer l.Close()\n\n\timgByPandaReq, _, err := image.Decode(bytes.NewReader(getOrFail(localFile(\"test_data/panda.png\"), client, t)))\n\tfatalOnErr(err, \"decode panda\", t)\n\tcompareImage(football, imgByPandaReq, t)\n\n\timgByFootballReq, _, err := image.Decode(bytes.NewReader(getOrFail(localFile(\"test_data/football.png\"), client, t)))\n\tfatalOnErr(err, \"decode football\", t)\n\tcompareImage(panda, imgByFootballReq, t)\n}\n"
  },
  {
    "path": "ext/limitation/concurrency.go",
    "content": "package limitation\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\n// ConcurrentRequests implements a mechanism to limit the number of\n// concurrently handled HTTP requests, configurable by the user.\n// The ReqHandler can simply be added to the server with OnRequest().\nfunc ConcurrentRequests(limit int) goproxy.ReqHandler {\n\t// Do nothing when the specified limit is invalid\n\tif limit <= 0 {\n\t\treturn goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t\treturn req, nil\n\t\t})\n\t}\n\n\tlimitation := make(chan struct{}, limit)\n\treturn goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tlimitation <- struct{}{}\n\n\t\t// Release semaphore when request finishes\n\t\tgo func() {\n\t\t\t<-req.Context().Done()\n\t\t\t<-limitation\n\t\t}()\n\n\t\treturn req, nil\n\t})\n}\n"
  },
  {
    "path": "ext/limitation/concurrency_test.go",
    "content": "package limitation_test\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/ext/limitation\"\n)\n\nfunc TestConcurrentRequests(t *testing.T) {\n\tmockRequest := &http.Request{Host: \"test.com\"}\n\tctx := &goproxy.ProxyCtx{}\n\tmaximumDuration := 100 * time.Millisecond\n\n\tt.Run(\"empty limitation\", func(t *testing.T) {\n\t\ttimer := time.NewTimer(maximumDuration)\n\t\tdefer timer.Stop()\n\t\tdone := make(chan struct{})\n\n\t\tgo func() {\n\t\t\tzeroLimiter := limitation.ConcurrentRequests(0)\n\t\t\tzeroLimiter.Handle(mockRequest, ctx)\n\t\t\tdone <- struct{}{}\n\t\t}()\n\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tt.Error(\"Limiter took too long\")\n\t\tcase <-done:\n\t\t}\n\t})\n\n\tt.Run(\"normal limitation\", func(t *testing.T) {\n\t\ttimer := time.NewTimer(maximumDuration)\n\t\tdefer timer.Stop()\n\t\tdone := make(chan struct{})\n\n\t\tgo func() {\n\t\t\toneLimiter := limitation.ConcurrentRequests(1)\n\t\t\toneLimiter.Handle(mockRequest, ctx)\n\t\t\tdone <- struct{}{}\n\t\t}()\n\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tt.Error(\"Limiter took too long\")\n\t\tcase <-done:\n\t\t}\n\t})\n\n\tt.Run(\"more than the limit\", func(t *testing.T) {\n\t\ttimer := time.NewTimer(maximumDuration)\n\t\tdefer timer.Stop()\n\t\tdone := make(chan struct{})\n\n\t\tgo func() {\n\t\t\toneLimiter := limitation.ConcurrentRequests(1)\n\t\t\toneLimiter.Handle(mockRequest, ctx)\n\t\t\toneLimiter.Handle(mockRequest, ctx)\n\t\t\tdone <- struct{}{}\n\t\t}()\n\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\t// Do nothing, we expect to reach the timeout\n\t\tcase <-done:\n\t\t\tt.Error(\"Limiter was too fast\")\n\t\t}\n\t})\n\n\tt.Run(\"more than the limit but one request finishes\", func(t *testing.T) {\n\t\ttimer := time.NewTimer(maximumDuration)\n\t\tdefer timer.Stop()\n\t\tdone := make(chan struct{})\n\n\t\ttimeoutCtx, cancel := context.WithCancel(mockRequest.Context())\n\t\tmockRequestWithCancel := mockRequest.WithContext(timeoutCtx)\n\n\t\tgo func() {\n\t\t\toneLimiter := limitation.ConcurrentRequests(1)\n\t\t\toneLimiter.Handle(mockRequestWithCancel, ctx)\n\t\t\tcancel()\n\t\t\toneLimiter.Handle(mockRequest, ctx)\n\t\t\tdone <- struct{}{}\n\t\t}()\n\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tt.Error(\"Limiter took too long\")\n\t\tcase <-done:\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/elazarl/goproxy\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/coder/websocket v1.8.14\n\tgithub.com/stretchr/testify v1.11.1\n\tgolang.org/x/net v0.43.0\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgolang.org/x/text v0.28.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=\ngithub.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngolang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=\ngolang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=\ngolang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=\ngolang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "h2.go",
    "content": "package goproxy\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"golang.org/x/net/http2\"\n)\n\nvar ErrInvalidH2Frame = errors.New(\"invalid H2 frame\")\n\n// H2Transport is an implementation of RoundTripper that abstracts an entire\n// HTTP/2 session, sending all client frames to the server and responses back\n// to the client.\ntype H2Transport struct {\n\tClientReader io.Reader\n\tClientWriter io.Writer\n\tTLSConfig    *tls.Config\n\tHost         string\n}\n\n// RoundTrip executes an HTTP/2 session (including all contained streams).\n// The request and response are ignored but any error encountered during the\n// proxying from the session is returned as a result of the invocation.\nfunc (r *H2Transport) RoundTrip(_ *http.Request) (*http.Response, error) {\n\traddr := r.Host\n\tif !strings.Contains(raddr, \":\") {\n\t\traddr += \":443\"\n\t}\n\trawServerTLS, err := dial(\"tcp\", raddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rawServerTLS.Close()\n\t// Ensure that we only advertise HTTP/2 as the accepted protocol.\n\tr.TLSConfig.NextProtos = []string{http2.NextProtoTLS}\n\t// Initiate TLS and check remote host name against certificate.\n\trawServerTLS = tls.Client(rawServerTLS, r.TLSConfig)\n\trawTLSConn, ok := rawServerTLS.(*tls.Conn)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid TLS connection\")\n\t}\n\tif err = rawTLSConn.HandshakeContext(context.Background()); err != nil {\n\t\treturn nil, err\n\t}\n\tif r.TLSConfig == nil || !r.TLSConfig.InsecureSkipVerify {\n\t\tif err = rawTLSConn.VerifyHostname(raddr[:strings.LastIndex(raddr, \":\")]); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\t// Send new client preface to match the one parsed in req.\n\tif _, err := io.WriteString(rawServerTLS, http2.ClientPreface); err != nil {\n\t\treturn nil, err\n\t}\n\tserverTLSReader := bufio.NewReader(rawServerTLS)\n\tcToS := http2.NewFramer(rawServerTLS, r.ClientReader)\n\tsToC := http2.NewFramer(r.ClientWriter, serverTLSReader)\n\terrSToC := make(chan error)\n\terrCToS := make(chan error)\n\tgo func() {\n\t\tfor {\n\t\t\tif err := proxyFrame(sToC); err != nil {\n\t\t\t\terrSToC <- err\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\tgo func() {\n\t\tfor {\n\t\t\tif err := proxyFrame(cToS); err != nil {\n\t\t\t\terrCToS <- err\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\tfor i := 0; i < 2; i++ {\n\t\tselect {\n\t\tcase err := <-errSToC:\n\t\t\tif !errors.Is(err, io.EOF) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase err := <-errCToS:\n\t\t\tif !errors.Is(err, io.EOF) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc dial(network, addr string) (c net.Conn, err error) {\n\taddri, err := net.ResolveTCPAddr(network, addr)\n\tif err != nil {\n\t\treturn\n\t}\n\tc, err = net.DialTCP(network, nil, addri)\n\treturn\n}\n\n// proxyFrame reads a single frame from the Framer and, when successful, writes\n// a ~identical one back to the Framer.\nfunc proxyFrame(fr *http2.Framer) error {\n\tf, err := fr.ReadFrame()\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch f.Header().Type {\n\tcase http2.FrameData:\n\t\ttf, ok := f.(*http2.DataFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\tterr := fr.WriteData(tf.StreamID, tf.StreamEnded(), tf.Data())\n\t\tif terr == nil && tf.StreamEnded() {\n\t\t\tterr = io.EOF\n\t\t}\n\t\treturn terr\n\tcase http2.FrameHeaders:\n\t\ttf, ok := f.(*http2.HeadersFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\tterr := fr.WriteHeaders(http2.HeadersFrameParam{\n\t\t\tStreamID:      tf.StreamID,\n\t\t\tBlockFragment: tf.HeaderBlockFragment(),\n\t\t\tEndStream:     tf.StreamEnded(),\n\t\t\tEndHeaders:    tf.HeadersEnded(),\n\t\t\tPadLength:     0,\n\t\t\tPriority:      tf.Priority,\n\t\t})\n\t\tif terr == nil && tf.StreamEnded() {\n\t\t\tterr = io.EOF\n\t\t}\n\t\treturn terr\n\tcase http2.FrameContinuation:\n\t\ttf, ok := f.(*http2.ContinuationFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WriteContinuation(tf.StreamID, tf.HeadersEnded(), tf.HeaderBlockFragment())\n\tcase http2.FrameGoAway:\n\t\ttf, ok := f.(*http2.GoAwayFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WriteGoAway(tf.StreamID, tf.ErrCode, tf.DebugData())\n\tcase http2.FramePing:\n\t\ttf, ok := f.(*http2.PingFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WritePing(tf.IsAck(), tf.Data)\n\tcase http2.FrameRSTStream:\n\t\ttf, ok := f.(*http2.RSTStreamFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WriteRSTStream(tf.StreamID, tf.ErrCode)\n\tcase http2.FrameSettings:\n\t\ttf, ok := f.(*http2.SettingsFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\tif tf.IsAck() {\n\t\t\treturn fr.WriteSettingsAck()\n\t\t}\n\t\tvar settings []http2.Setting\n\t\t// NOTE: If we want to parse headers, need to handle\n\t\t// settings where s.ID == http2.SettingHeaderTableSize and\n\t\t// accordingly update the Framer options.\n\t\tfor i := 0; i < tf.NumSettings(); i++ {\n\t\t\tsettings = append(settings, tf.Setting(i))\n\t\t}\n\t\treturn fr.WriteSettings(settings...)\n\tcase http2.FrameWindowUpdate:\n\t\ttf, ok := f.(*http2.WindowUpdateFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WriteWindowUpdate(tf.StreamID, tf.Increment)\n\tcase http2.FramePriority:\n\t\ttf, ok := f.(*http2.PriorityFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WritePriority(tf.StreamID, tf.PriorityParam)\n\tcase http2.FramePushPromise:\n\t\ttf, ok := f.(*http2.PushPromiseFrame)\n\t\tif !ok {\n\t\t\treturn ErrInvalidH2Frame\n\t\t}\n\t\treturn fr.WritePushPromise(http2.PushPromiseParam{\n\t\t\tStreamID:      tf.StreamID,\n\t\t\tPromiseID:     tf.PromiseID,\n\t\t\tBlockFragment: tf.HeaderBlockFragment(),\n\t\t\tEndHeaders:    tf.HeadersEnded(),\n\t\t\tPadLength:     0,\n\t\t})\n\tdefault:\n\t\treturn errors.New(\"Unsupported frame: \" + string(f.Header().Type))\n\t}\n}\n"
  },
  {
    "path": "http.go",
    "content": "package goproxy\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\nfunc (proxy *ProxyHttpServer) handleHttp(w http.ResponseWriter, r *http.Request) {\n\tctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy}\n\n\tctx.Logf(\"Got request %v %v %v %v\", r.URL.Path, r.Host, r.Method, r.URL.String())\n\tif !r.URL.IsAbs() {\n\t\tproxy.NonproxyHandler.ServeHTTP(w, r)\n\t\treturn\n\t}\n\tr, resp := proxy.filterRequest(r, ctx)\n\n\tif resp == nil {\n\t\tif !proxy.KeepHeader {\n\t\t\tRemoveProxyHeaders(ctx, r)\n\t\t}\n\n\t\tvar err error\n\t\tresp, err = ctx.RoundTrip(r)\n\t\tif err != nil {\n\t\t\tctx.Error = err\n\t\t}\n\t}\n\n\tvar origBody io.ReadCloser\n\n\tif resp != nil {\n\t\torigBody = resp.Body\n\t\tdefer origBody.Close()\n\t}\n\n\tresp = proxy.filterResponse(resp, ctx)\n\n\tif resp == nil {\n\t\tvar errorString string\n\t\tif ctx.Error != nil {\n\t\t\terrorString = \"error read response \" + r.URL.Host + \" : \" + ctx.Error.Error()\n\t\t\tctx.Logf(errorString)\n\t\t\thttp.Error(w, ctx.Error.Error(), http.StatusInternalServerError)\n\t\t} else {\n\t\t\terrorString = \"error read response \" + r.URL.Host\n\t\t\tctx.Logf(errorString)\n\t\t\thttp.Error(w, errorString, http.StatusInternalServerError)\n\t\t}\n\t\treturn\n\t}\n\tctx.Logf(\"Copying response to client %v [%d]\", resp.Status, resp.StatusCode)\n\t// http.ResponseWriter will take care of filling the correct response length\n\t// Setting it now, might impose wrong value, contradicting the actual new\n\t// body the user returned.\n\t// We keep the original body to remove the header only if things changed.\n\t// This will prevent problems with HEAD requests where there's no body, yet,\n\t// the Content-Length header should be set.\n\tif origBody != resp.Body {\n\t\tresp.Header.Del(\"Content-Length\")\n\t}\n\tcopyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders)\n\tw.WriteHeader(resp.StatusCode)\n\n\tif isWebSocketHandshake(resp.Header) {\n\t\tctx.Logf(\"Response looks like websocket upgrade.\")\n\n\t\t// We have already written the \"101 Switching Protocols\" response,\n\t\t// now we hijack the connection to send WebSocket data\n\t\tif clientConn, err := proxy.hijackConnection(ctx, w); err == nil {\n\t\t\twsConn, ok := resp.Body.(io.ReadWriter)\n\t\t\tif !ok {\n\t\t\t\tctx.Warnf(\"Unable to use Websocket connection\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tproxy.proxyWebsocket(ctx, wsConn, clientConn)\n\t\t}\n\t\treturn\n\t}\n\n\tvar copyWriter io.Writer = w\n\t// Content-Type header may also contain charset definition, so here we need to check the prefix.\n\t// Transfer-Encoding can be a list of comma separated values, so we use Contains() for it.\n\tif strings.HasPrefix(w.Header().Get(\"content-type\"), \"text/event-stream\") ||\n\t\tstrings.Contains(w.Header().Get(\"transfer-encoding\"), \"chunked\") {\n\t\t// server-side events, flush the buffered data to the client.\n\t\tcopyWriter = &flushWriter{w: w}\n\t}\n\n\tnr, err := io.Copy(copyWriter, resp.Body)\n\tif err := resp.Body.Close(); err != nil {\n\t\tctx.Warnf(\"Can't close response body %v\", err)\n\t}\n\tctx.Logf(\"Copied %v bytes to client error=%v\", nr, err)\n}\n"
  },
  {
    "path": "https.go",
    "content": "package goproxy\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/elazarl/goproxy/internal/http1parser\"\n\t\"github.com/elazarl/goproxy/internal/signer\"\n)\n\ntype ConnectActionLiteral int\n\nconst (\n\tConnectAccept = iota\n\tConnectReject\n\tConnectMitm\n\tConnectHijack\n\t// Deprecated: use ConnectMitm.\n\tConnectHTTPMitm\n\tConnectProxyAuthHijack\n)\n\nvar (\n\tOkConnect   = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)}\n\tMitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}\n\t// Deprecated: use MitmConnect.\n\tHTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}\n\tRejectConnect   = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)}\n)\n\nvar _errorRespMaxLength int64 = 500\n\nconst _tlsRecordTypeHandshake = byte(22)\n\ntype readBufferedConn struct {\n\tnet.Conn\n\tr io.Reader\n}\n\nfunc (c *readBufferedConn) Read(p []byte) (int, error) {\n\treturn c.r.Read(p)\n}\n\n// ConnectAction enables the caller to override the standard connect flow.\n// When Action is ConnectHijack, it is up to the implementer to send the\n// HTTP 200, or any other valid http response back to the client from within the\n// Hijack func.\ntype ConnectAction struct {\n\tAction    ConnectActionLiteral\n\tHijack    func(req *http.Request, client net.Conn, ctx *ProxyCtx)\n\tTLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error)\n}\n\nfunc stripPort(s string) string {\n\tvar ix int\n\tif strings.Contains(s, \"[\") && strings.Contains(s, \"]\") {\n\t\t// ipv6 address example: [2606:4700:4700::1111]:443\n\t\t// strip '[' and ']'\n\t\ts = strings.ReplaceAll(s, \"[\", \"\")\n\t\ts = strings.ReplaceAll(s, \"]\", \"\")\n\n\t\tix = strings.LastIndexAny(s, \":\")\n\t\tif ix == -1 {\n\t\t\treturn s\n\t\t}\n\t} else {\n\t\t// ipv4\n\t\tix = strings.IndexRune(s, ':')\n\t\tif ix == -1 {\n\t\t\treturn s\n\t\t}\n\t}\n\treturn s[:ix]\n}\n\nfunc (proxy *ProxyHttpServer) dial(ctx *ProxyCtx, network, addr string) (c net.Conn, err error) {\n\tif ctx.Dialer != nil {\n\t\treturn ctx.Dialer(ctx.Req.Context(), network, addr)\n\t}\n\n\tif proxy.Tr != nil && proxy.Tr.DialContext != nil {\n\t\treturn proxy.Tr.DialContext(ctx.Req.Context(), network, addr)\n\t}\n\n\t// if the user didn't specify any dialer, we just use the default one,\n\t// provided by net package\n\tvar d net.Dialer\n\treturn d.DialContext(ctx.Req.Context(), network, addr)\n}\n\nfunc (proxy *ProxyHttpServer) connectDial(ctx *ProxyCtx, network, addr string) (c net.Conn, err error) {\n\tif proxy.ConnectDialWithReq == nil && proxy.ConnectDial == nil {\n\t\treturn proxy.dial(ctx, network, addr)\n\t}\n\n\tif proxy.ConnectDialWithReq != nil {\n\t\treturn proxy.ConnectDialWithReq(ctx.Req, network, addr)\n\t}\n\n\treturn proxy.ConnectDial(network, addr)\n}\n\ntype halfClosable interface {\n\tnet.Conn\n\tCloseWrite() error\n\tCloseRead() error\n}\n\nvar _ halfClosable = (*net.TCPConn)(nil)\n\nfunc (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {\n\tctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore}\n\n\thij, ok := w.(http.Hijacker)\n\tif !ok {\n\t\tpanic(\"httpserver does not support hijacking\")\n\t}\n\n\tproxyClient, _, e := hij.Hijack()\n\tif e != nil {\n\t\tpanic(\"Cannot hijack connection \" + e.Error())\n\t}\n\n\tctx.Logf(\"Running %d CONNECT handlers\", len(proxy.httpsHandlers))\n\ttodo, host := OkConnect, r.URL.Host\n\tfor i, h := range proxy.httpsHandlers {\n\t\tnewtodo, newhost := h.HandleConnect(host, ctx)\n\n\t\t// If found a result, break the loop immediately\n\t\tif newtodo != nil {\n\t\t\ttodo, host = newtodo, newhost\n\t\t\tctx.Logf(\"on %dth handler: %v %s\", i, todo, host)\n\t\t\tbreak\n\t\t}\n\t}\n\tswitch todo.Action {\n\tcase ConnectAccept:\n\t\tif !hasPort.MatchString(host) {\n\t\t\thost += \":80\"\n\t\t}\n\t\ttargetSiteCon, err := proxy.connectDial(ctx, \"tcp\", host)\n\t\tif err != nil {\n\t\t\tctx.Warnf(\"Error dialing to %s: %s\", host, err.Error())\n\t\t\thttpError(proxyClient, ctx, err)\n\t\t\treturn\n\t\t}\n\t\tctx.Logf(\"Accepting CONNECT to %s\", host)\n\t\t_, _ = proxyClient.Write([]byte(\"HTTP/1.0 200 Connection established\\r\\n\\r\\n\"))\n\n\t\ttargetTCP, targetOK := targetSiteCon.(halfClosable)\n\t\tproxyClientTCP, clientOK := proxyClient.(halfClosable)\n\t\tif targetOK && clientOK {\n\t\t\tgo func() {\n\t\t\t\tvar wg sync.WaitGroup\n\t\t\t\twg.Add(2)\n\t\t\t\tgo copyAndClose(ctx, targetTCP, proxyClientTCP, &wg)\n\t\t\t\tgo copyAndClose(ctx, proxyClientTCP, targetTCP, &wg)\n\t\t\t\twg.Wait()\n\t\t\t\t// Make sure to close the underlying TCP socket.\n\t\t\t\t// CloseRead() and CloseWrite() keep it open until its timeout,\n\t\t\t\t// causing error when there are thousands of requests.\n\t\t\t\tproxyClientTCP.Close()\n\t\t\t\ttargetTCP.Close()\n\t\t\t}()\n\t\t} else {\n\t\t\t// There is a race with the runtime here. In the case where the\n\t\t\t// connection to the target site times out, we cannot control which\n\t\t\t// io.Copy loop will receive the timeout signal first. This means\n\t\t\t// that in some cases the error passed to the ConnErrorHandler will\n\t\t\t// be the timeout error, and in other cases it will be an error raised\n\t\t\t// by the use of a closed network connection.\n\t\t\t//\n\t\t\t// 2020/05/28 23:42:17 [001] WARN: Error copying to client: read tcp 127.0.0.1:33742->127.0.0.1:34763: i/o timeout\n\t\t\t// 2020/05/28 23:42:17 [001] WARN: Error copying to client: read tcp 127.0.0.1:45145->127.0.0.1:60494: use of closed\n\t\t\t//                                                          network connection\n\t\t\t//\n\t\t\t// It's also not possible to synchronize these connection closures due to\n\t\t\t// TCP connections which are half-closed. When this happens, only the one\n\t\t\t// side of the connection breaks out of its io.Copy loop. The other side\n\t\t\t// of the connection remains open until it either times out or is reset by\n\t\t\t// the client.\n\t\t\tgo func() {\n\t\t\t\terr := copyOrWarn(ctx, targetSiteCon, proxyClient)\n\t\t\t\tif err != nil && proxy.ConnectionErrHandler != nil {\n\t\t\t\t\tproxy.ConnectionErrHandler(proxyClient, ctx, err)\n\t\t\t\t}\n\t\t\t\t_ = targetSiteCon.Close()\n\t\t\t}()\n\n\t\t\tgo func() {\n\t\t\t\t_ = copyOrWarn(ctx, proxyClient, targetSiteCon)\n\t\t\t\t_ = proxyClient.Close()\n\t\t\t}()\n\t\t}\n\n\tcase ConnectHijack:\n\t\ttodo.Hijack(r, proxyClient, ctx)\n\tcase ConnectHTTPMitm, ConnectMitm:\n\t\t_, _ = proxyClient.Write([]byte(\"HTTP/1.0 200 OK\\r\\n\\r\\n\"))\n\t\tctx.Logf(\"Received CONNECT request, mitm proxying it\")\n\t\t// this goes in a separate goroutine, so that the net/http server won't think we're\n\t\t// still handling the request even after hijacking the connection. Those HTTP CONNECT\n\t\t// request can take forever, and the server will be stuck when \"closed\".\n\t\t// TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible\n\t\tgo func() {\n\t\t\t// Check if this is an HTTP or an HTTPS MITM request\n\t\t\treadBuffer := bufio.NewReader(proxyClient)\n\t\t\tpeek, _ := readBuffer.Peek(1)\n\t\t\tisTLS := len(peek) > 0 && peek[0] == _tlsRecordTypeHandshake\n\n\t\t\tvar client net.Conn = &readBufferedConn{Conn: proxyClient, r: readBuffer}\n\t\t\tdefer func() {\n\t\t\t\t_ = client.Close()\n\t\t\t}()\n\n\t\t\tvar tlsConfig *tls.Config\n\t\t\tscheme := \"http\"\n\t\t\tif isTLS {\n\t\t\t\tscheme = \"https\"\n\t\t\t\ttlsConfig = defaultTLSConfig\n\t\t\t\tif todo.TLSConfig != nil {\n\t\t\t\t\tvar err error\n\t\t\t\t\ttlsConfig, err = todo.TLSConfig(host, ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\thttpError(proxyClient, ctx, err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Create a TLS connection over the TCP connection\n\t\t\t\trawClientTls := tls.Server(client, tlsConfig)\n\t\t\t\tclient = rawClientTls\n\t\t\t\tif err := rawClientTls.HandshakeContext(context.Background()); err != nil {\n\t\t\t\t\tctx.Warnf(\"Cannot handshake client %v %v\", r.Host, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclientReader := http1parser.NewRequestReader(proxy.PreventCanonicalization, client)\n\t\t\tfor !clientReader.IsEOF() {\n\t\t\t\treq, err := clientReader.ReadRequest()\n\t\t\t\tctx := &ProxyCtx{\n\t\t\t\t\tReq:          req,\n\t\t\t\t\tSession:      atomic.AddInt64(&proxy.sess, 1),\n\t\t\t\t\tProxy:        proxy,\n\t\t\t\t\tUserData:     ctx.UserData,\n\t\t\t\t\tRoundTripper: ctx.RoundTripper,\n\t\t\t\t}\n\t\t\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\t\t\tctx.Warnf(\"Cannot read request from mitm'd client %v %v\", r.Host, err)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// since we're converting the request, need to carry over the\n\t\t\t\t// original connecting IP as well\n\t\t\t\treq.RemoteAddr = r.RemoteAddr\n\t\t\t\tctx.Logf(\"req %v\", r.Host)\n\n\t\t\t\tif !strings.HasPrefix(req.URL.String(), scheme+\"://\") {\n\t\t\t\t\treq.URL, err = url.Parse(scheme + \"://\" + r.Host + req.URL.String())\n\t\t\t\t}\n\n\t\t\t\tif continueLoop := func(req *http.Request) bool {\n\t\t\t\t\t// Since we handled the request parsing by our own, we manually\n\t\t\t\t\t// need to set a cancellable context when we finished the request\n\t\t\t\t\t// processing (same behaviour of the stdlib)\n\t\t\t\t\trequestContext, finishRequest := context.WithCancel(req.Context())\n\t\t\t\t\treq = req.WithContext(requestContext)\n\t\t\t\t\tdefer finishRequest()\n\n\t\t\t\t\t// explicitly discard request body to avoid data races in certain RoundTripper implementations\n\t\t\t\t\t// see https://github.com/golang/go/issues/61596#issuecomment-1652345131\n\t\t\t\t\tdefer req.Body.Close()\n\n\t\t\t\t\t// Bug fix which goproxy fails to provide request\n\t\t\t\t\t// information URL in the context when does HTTPS MITM\n\t\t\t\t\tctx.Req = req\n\n\t\t\t\t\treq, resp := proxy.filterRequest(req, ctx)\n\t\t\t\t\tif resp == nil {\n\t\t\t\t\t\tif req.Method == \"PRI\" {\n\t\t\t\t\t\t\t// Handle HTTP/2 connections.\n\n\t\t\t\t\t\t\t// NOTE: As of 1.22, golang's http module will not recognize or\n\t\t\t\t\t\t\t// parse the HTTP Body for PRI requests. This leaves the body of\n\t\t\t\t\t\t\t// the http2.ClientPreface (\"SM\\r\\n\\r\\n\") on the wire which we need\n\t\t\t\t\t\t\t// to clear before setting up the connection.\n\t\t\t\t\t\t\treader := clientReader.Reader()\n\t\t\t\t\t\t\t_, err := reader.Discard(6)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tctx.Warnf(\"Failed to process HTTP2 client preface: %v\", err)\n\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !proxy.AllowHTTP2 {\n\t\t\t\t\t\t\t\tctx.Warnf(\"HTTP2 connection failed: disallowed\")\n\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttr := H2Transport{reader, client, tlsConfig, host}\n\t\t\t\t\t\t\tif _, err := tr.RoundTrip(req); err != nil {\n\t\t\t\t\t\t\t\tctx.Warnf(\"HTTP2 connection failed: %v\", err)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tctx.Logf(\"Exiting on EOF\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tif req.URL != nil {\n\t\t\t\t\t\t\t\tctx.Warnf(\"Illegal URL %s\", scheme+\"://\"+r.Host+req.URL.Path)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tctx.Warnf(\"Illegal URL %s\", scheme+\"://\"+r.Host)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !proxy.KeepHeader {\n\t\t\t\t\t\t\tRemoveProxyHeaders(ctx, req)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresp, err = ctx.RoundTrip(req)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tctx.Warnf(\"Cannot read response from mitm'd server %v\", err)\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctx.Logf(\"resp %v\", resp.Status)\n\t\t\t\t\t}\n\t\t\t\t\torigBody := resp.Body\n\t\t\t\t\tresp = proxy.filterResponse(resp, ctx)\n\t\t\t\t\tbodyModified := resp.Body != origBody\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tif bodyModified || (resp.ContentLength <= 0 && resp.Header.Get(\"Content-Length\") == \"\") {\n\t\t\t\t\t\t// Return chunked encoded response when we don't know the length of the resp, if the body\n\t\t\t\t\t\t// has been modified by the response handler or if there is no content length in the response.\n\t\t\t\t\t\t// We include 0 in resp.ContentLength <= 0 because 0 is the field zero value and some user\n\t\t\t\t\t\t// might incorrectly leave it instead of setting it to -1 when the length is unknown (but we\n\t\t\t\t\t\t// also check that the Content-Length header is empty, so there is no issue with empty bodies).\n\t\t\t\t\t\tresp.ContentLength = -1\n\t\t\t\t\t\tresp.Header.Del(\"Content-Length\")\n\t\t\t\t\t\tresp.TransferEncoding = []string{\"chunked\"}\n\t\t\t\t\t}\n\n\t\t\t\t\tif isWebSocketHandshake(resp.Header) {\n\t\t\t\t\t\tctx.Logf(\"Response looks like websocket upgrade.\")\n\n\t\t\t\t\t\t// According to resp.Body documentation:\n\t\t\t\t\t\t// As of Go 1.12, the Body will also implement io.Writer\n\t\t\t\t\t\t// on a successful \"101 Switching Protocols\" response,\n\t\t\t\t\t\t// as used by WebSockets and HTTP/2's \"h2c\" mode.\n\t\t\t\t\t\twsConn, ok := resp.Body.(io.ReadWriter)\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\tctx.Warnf(\"Unable to use Websocket connection\")\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Set Body to nil so resp.Write only writes the headers\n\t\t\t\t\t\t// and returns immediately without blocking on the body\n\t\t\t\t\t\t// (or else we wouldn't be able to proxy WebSocket data).\n\t\t\t\t\t\tresp.Body = nil\n\t\t\t\t\t\tif err := resp.Write(client); err != nil {\n\t\t\t\t\t\t\tctx.Warnf(\"Cannot write response header from mitm'd client: %v\", err)\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tproxy.proxyWebsocket(ctx, wsConn, client)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tif err := resp.Write(client); err != nil {\n\t\t\t\t\t\tctx.Warnf(\"Cannot write response from mitm'd client: %v\", err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true\n\t\t\t\t}(req); !continueLoop {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.Logf(\"Exiting on EOF\")\n\t\t}()\n\tcase ConnectProxyAuthHijack:\n\t\t_, _ = proxyClient.Write([]byte(\"HTTP/1.1 407 Proxy Authentication Required\\r\\n\"))\n\t\ttodo.Hijack(r, proxyClient, ctx)\n\tcase ConnectReject:\n\t\tif ctx.Resp != nil {\n\t\t\tif err := ctx.Resp.Write(proxyClient); err != nil {\n\t\t\t\tctx.Warnf(\"Cannot write response that reject http CONNECT: %v\", err)\n\t\t\t}\n\t\t}\n\t\t_ = proxyClient.Close()\n\t}\n}\n\nfunc httpError(w io.WriteCloser, ctx *ProxyCtx, err error) {\n\tif ctx.Proxy.ConnectionErrHandler != nil {\n\t\tctx.Proxy.ConnectionErrHandler(w, ctx, err)\n\t} else {\n\t\terrorMessage := err.Error()\n\t\terrStr := fmt.Sprintf(\n\t\t\t\"HTTP/1.1 502 Bad Gateway\\r\\nContent-Type: text/plain\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\t\tlen(errorMessage),\n\t\t\terrorMessage,\n\t\t)\n\t\tif _, err := io.WriteString(w, errStr); err != nil {\n\t\t\tctx.Warnf(\"Error responding to client: %s\", err)\n\t\t}\n\t}\n\tif err := w.Close(); err != nil {\n\t\tctx.Warnf(\"Error closing client connection: %s\", err)\n\t}\n}\n\nfunc copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader) error {\n\t_, err := io.Copy(dst, src)\n\tif err != nil && errors.Is(err, net.ErrClosed) {\n\t\t// Discard closed connection errors\n\t\terr = nil\n\t} else if err != nil {\n\t\tctx.Warnf(\"Error copying to client: %s\", err)\n\t}\n\treturn err\n}\n\nfunc copyAndClose(ctx *ProxyCtx, dst, src halfClosable, wg *sync.WaitGroup) {\n\t_, err := io.Copy(dst, src)\n\tif err != nil && !errors.Is(err, net.ErrClosed) {\n\t\tctx.Warnf(\"Error copying to client: %s\", err.Error())\n\t}\n\n\t_ = dst.CloseWrite()\n\t_ = src.CloseRead()\n\twg.Done()\n}\n\nfunc dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) {\n\thttpsProxy := os.Getenv(\"HTTPS_PROXY\")\n\tif httpsProxy == \"\" {\n\t\thttpsProxy = os.Getenv(\"https_proxy\")\n\t}\n\tif httpsProxy == \"\" {\n\t\treturn nil\n\t}\n\treturn proxy.NewConnectDialToProxy(httpsProxy)\n}\n\nfunc (proxy *ProxyHttpServer) NewConnectDialToProxy(httpsProxy string) func(network, addr string) (net.Conn, error) {\n\treturn proxy.NewConnectDialToProxyWithHandler(httpsProxy, nil)\n}\n\nfunc (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(\n\thttpsProxy string,\n\tconnectReqHandler func(req *http.Request),\n) func(network, addr string) (net.Conn, error) {\n\tu, err := url.Parse(httpsProxy)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tif u.Scheme == \"\" || u.Scheme == \"http\" || u.Scheme == \"ws\" {\n\t\tif !strings.ContainsRune(u.Host, ':') {\n\t\t\tu.Host += \":80\"\n\t\t}\n\t\treturn func(network, addr string) (net.Conn, error) {\n\t\t\tconnectReq := &http.Request{\n\t\t\t\tMethod: http.MethodConnect,\n\t\t\t\tURL:    &url.URL{Opaque: addr},\n\t\t\t\tHost:   addr,\n\t\t\t\tHeader: make(http.Header),\n\t\t\t}\n\t\t\tif connectReqHandler != nil {\n\t\t\t\tconnectReqHandler(connectReq)\n\t\t\t}\n\t\t\tc, err := proxy.dial(&ProxyCtx{Req: &http.Request{}}, network, u.Host)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t_ = connectReq.Write(c)\n\t\t\t// Read response.\n\t\t\t// Okay to use and discard buffered reader here, because\n\t\t\t// TLS server will not speak until spoken to.\n\t\t\tbr := bufio.NewReader(c)\n\t\t\tresp, err := http.ReadResponse(br, connectReq)\n\t\t\tif err != nil {\n\t\t\t\t_ = c.Close()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tresp, err := io.ReadAll(io.LimitReader(resp.Body, _errorRespMaxLength))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\t_ = c.Close()\n\t\t\t\treturn nil, errors.New(\"proxy refused connection\" + string(resp))\n\t\t\t}\n\t\t\treturn c, nil\n\t\t}\n\t}\n\tif u.Scheme == \"https\" || u.Scheme == \"wss\" {\n\t\tif !strings.ContainsRune(u.Host, ':') {\n\t\t\tu.Host += \":443\"\n\t\t}\n\t\treturn func(network, addr string) (net.Conn, error) {\n\t\t\tctx := &ProxyCtx{Req: &http.Request{}}\n\t\t\tc, err := proxy.dial(ctx, network, u.Host)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tc, err = proxy.initializeTLSconnection(ctx, c, proxy.Tr.TLSClientConfig, u.Host)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tconnectReq := &http.Request{\n\t\t\t\tMethod: http.MethodConnect,\n\t\t\t\tURL:    &url.URL{Opaque: addr},\n\t\t\t\tHost:   addr,\n\t\t\t\tHeader: make(http.Header),\n\t\t\t}\n\t\t\tif connectReqHandler != nil {\n\t\t\t\tconnectReqHandler(connectReq)\n\t\t\t}\n\t\t\t_ = connectReq.Write(c)\n\t\t\t// Read response.\n\t\t\t// Okay to use and discard buffered reader here, because\n\t\t\t// TLS server will not speak until spoken to.\n\t\t\tbr := bufio.NewReader(c)\n\t\t\tresp, err := http.ReadResponse(br, connectReq)\n\t\t\tif err != nil {\n\t\t\t\t_ = c.Close()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tbody, err := io.ReadAll(io.LimitReader(resp.Body, _errorRespMaxLength))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\t_ = c.Close()\n\t\t\t\treturn nil, errors.New(\"proxy refused connection\" + string(body))\n\t\t\t}\n\t\t\treturn c, nil\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) {\n\treturn func(host string, ctx *ProxyCtx) (*tls.Config, error) {\n\t\tvar err error\n\t\tvar cert *tls.Certificate\n\n\t\thostname := stripPort(host)\n\t\tconfig := defaultTLSConfig.Clone()\n\t\tctx.Logf(\"signing for %s\", stripPort(host))\n\n\t\tgenCert := func() (*tls.Certificate, error) {\n\t\t\treturn signer.SignHost(*ca, []string{hostname})\n\t\t}\n\t\tif ctx.certStore != nil {\n\t\t\tcert, err = ctx.certStore.Fetch(hostname, genCert)\n\t\t} else {\n\t\t\tcert, err = genCert()\n\t\t}\n\n\t\tif err != nil {\n\t\t\tctx.Warnf(\"Cannot sign host certificate with provided CA: %s\", err)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tconfig.Certificates = append(config.Certificates, *cert)\n\t\treturn config, nil\n\t}\n}\n\nfunc (proxy *ProxyHttpServer) initializeTLSconnection(\n\tctx *ProxyCtx,\n\ttargetConn net.Conn,\n\ttlsConfig *tls.Config,\n\taddr string,\n) (net.Conn, error) {\n\t// Infer target ServerName, it's a copy of implementation inside tls.Dial()\n\tif tlsConfig.ServerName == \"\" {\n\t\tcolonPos := strings.LastIndex(addr, \":\")\n\t\tif colonPos == -1 {\n\t\t\tcolonPos = len(addr)\n\t\t}\n\t\thostname := addr[:colonPos]\n\t\t// Make a copy to avoid polluting argument or default.\n\t\tc := tlsConfig.Clone()\n\t\tc.ServerName = hostname\n\t\ttlsConfig = c\n\t}\n\n\ttlsConn := tls.Client(targetConn, tlsConfig)\n\tif err := tlsConn.HandshakeContext(ctx.Req.Context()); err != nil {\n\t\treturn nil, err\n\t}\n\treturn tlsConn, nil\n}\n"
  },
  {
    "path": "internal/http1parser/header.go",
    "content": "package http1parser\n\nimport (\n\t\"errors\"\n\t\"net/textproto\"\n\t\"strings\"\n)\n\nvar ErrBadProto = errors.New(\"bad protocol\")\n\n// Http1ExtractHeaders is an HTTP/1.0 and HTTP/1.1 header-only parser,\n// to extract the original header names for the received request.\n// Fully inspired by readMIMEHeader() in\n// https://github.com/golang/go/blob/master/src/net/textproto/reader.go\nfunc Http1ExtractHeaders(r *textproto.Reader) ([]string, error) {\n\t// Discard first line, it doesn't contain useful information, and it has\n\t// already been validated in http.ReadRequest()\n\tif _, err := r.ReadLine(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// The first line cannot start with a leading space.\n\tif buf, err := r.R.Peek(1); err == nil && (buf[0] == ' ' || buf[0] == '\\t') {\n\t\treturn nil, ErrBadProto\n\t}\n\n\tvar headerNames []string\n\tfor {\n\t\tkv, err := r.ReadContinuedLine()\n\t\tif len(kv) == 0 {\n\t\t\t// We have finished to parse the headers if we receive empty\n\t\t\t// data without an error\n\t\t\treturn headerNames, err\n\t\t}\n\n\t\t// Key ends at first colon.\n\t\tk, _, ok := strings.Cut(kv, \":\")\n\t\tif !ok {\n\t\t\treturn nil, ErrBadProto\n\t\t}\n\t\theaderNames = append(headerNames, k)\n\t}\n}\n"
  },
  {
    "path": "internal/http1parser/header_test.go",
    "content": "package http1parser_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"net/textproto\"\n\t\"testing\"\n\n\t\"github.com/elazarl/goproxy/internal/http1parser\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHttp1ExtractHeaders_Empty(t *testing.T) {\n\thttp1Data := \"POST /index.html HTTP/1.1\\r\\n\" +\n\t\t\"\\r\\n\"\n\n\ttextParser := textproto.NewReader(bufio.NewReader(bytes.NewReader([]byte(http1Data))))\n\theaders, err := http1parser.Http1ExtractHeaders(textParser)\n\trequire.NoError(t, err)\n\tassert.Empty(t, headers)\n}\n\nfunc TestHttp1ExtractHeaders(t *testing.T) {\n\thttp1Data := \"POST /index.html HTTP/1.1\\r\\n\" +\n\t\t\"Host: www.test.com\\r\\n\" +\n\t\t\"Accept: */ /*\\r\\n\" +\n\t\t\"Content-Length: 17\\r\\n\" +\n\t\t\"lowercase: 3z\\r\\n\" +\n\t\t\"\\r\\n\" +\n\t\t`{\"hello\":\"world\"}`\n\n\ttextParser := textproto.NewReader(bufio.NewReader(bytes.NewReader([]byte(http1Data))))\n\theaders, err := http1parser.Http1ExtractHeaders(textParser)\n\trequire.NoError(t, err)\n\tassert.Len(t, headers, 4)\n\tassert.Contains(t, headers, \"Content-Length\")\n\tassert.Contains(t, headers, \"lowercase\")\n}\n\nfunc TestHttp1ExtractHeaders_InvalidData(t *testing.T) {\n\thttp1Data := \"POST /index.html HTTP/1.1\\r\\n\" +\n\t\t`{\"hello\":\"world\"}`\n\n\ttextParser := textproto.NewReader(bufio.NewReader(bytes.NewReader([]byte(http1Data))))\n\t_, err := http1parser.Http1ExtractHeaders(textParser)\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "internal/http1parser/request.go",
    "content": "package http1parser\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/textproto\"\n)\n\ntype RequestReader struct {\n\tpreventCanonicalization bool\n\treader                  *bufio.Reader\n\t// Used only when preventCanonicalization value is true\n\tcloned *bytes.Buffer\n}\n\nfunc NewRequestReader(preventCanonicalization bool, conn io.Reader) *RequestReader {\n\tif !preventCanonicalization {\n\t\treturn &RequestReader{\n\t\t\tpreventCanonicalization: false,\n\t\t\treader:                  bufio.NewReader(conn),\n\t\t}\n\t}\n\n\tvar cloned bytes.Buffer\n\treader := bufio.NewReader(io.TeeReader(conn, &cloned))\n\treturn &RequestReader{\n\t\tpreventCanonicalization: true,\n\t\treader:                  reader,\n\t\tcloned:                  &cloned,\n\t}\n}\n\n// IsEOF returns true if there is no more data that can be read from the\n// buffer and the underlying connection is closed.\nfunc (r *RequestReader) IsEOF() bool {\n\t_, err := r.reader.Peek(1)\n\treturn errors.Is(err, io.EOF)\n}\n\n// Reader is used to take over the buffered connection data\n// (e.g. with HTTP/2 data).\n// After calling this function, make sure to consume all the data related\n// to the current request.\nfunc (r *RequestReader) Reader() *bufio.Reader {\n\treturn r.reader\n}\n\nfunc (r *RequestReader) ReadRequest() (*http.Request, error) {\n\tif !r.preventCanonicalization {\n\t\t// Just call the HTTP library function if the preventCanonicalization\n\t\t// configuration is disabled\n\t\treturn http.ReadRequest(r.reader)\n\t}\n\n\treq, err := http.ReadRequest(r.reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thttpDataReader := getRequestReader(r.reader, r.cloned)\n\theaders, _ := Http1ExtractHeaders(httpDataReader)\n\n\tfor _, headerName := range headers {\n\t\tcanonicalizedName := textproto.CanonicalMIMEHeaderKey(headerName)\n\t\tif canonicalizedName == headerName {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Rewrite header keys to the non-canonical parsed value\n\t\tvalues, ok := req.Header[canonicalizedName]\n\t\tif ok {\n\t\t\treq.Header.Del(canonicalizedName)\n\t\t\treq.Header[headerName] = values\n\t\t}\n\t}\n\n\treturn req, nil\n}\n\nfunc getRequestReader(r *bufio.Reader, cloned *bytes.Buffer) *textproto.Reader {\n\t// \"Cloned\" buffer uses the raw connection as the data source.\n\t// However, the *bufio.Reader can read also bytes of another unrelated\n\t// request on the same connection, since it's buffered, so we have to\n\t// ignore them before passing the data to our headers parser.\n\t// Data related to the next request will remain inside the buffer for\n\t// later usage.\n\tdata := cloned.Next(cloned.Len() - r.Buffered())\n\treturn &textproto.Reader{\n\t\tR: bufio.NewReader(bytes.NewReader(data)),\n\t}\n}\n"
  },
  {
    "path": "internal/http1parser/request_test.go",
    "content": "package http1parser_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/elazarl/goproxy/internal/http1parser\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\t_data = \"POST /index.html HTTP/1.1\\r\\n\" +\n\t\t\"Host: www.test.com\\r\\n\" +\n\t\t\"Accept: */*\\r\\n\" +\n\t\t\"Content-Length: 17\\r\\n\" +\n\t\t\"lowercase: 3z\\r\\n\" +\n\t\t\"\\r\\n\" +\n\t\t`{\"hello\":\"world\"}`\n\n\t_data2 = \"GET /index.html HTTP/1.1\\r\\n\" +\n\t\t\"Host: www.test.com\\r\\n\" +\n\t\t\"Accept: */*\\r\\n\" +\n\t\t\"lowercase: 3z\\r\\n\" +\n\t\t\"\\r\\n\"\n)\n\nfunc TestCanonicalRequest(t *testing.T) {\n\t// Here we are simulating two requests on the same connection\n\thttp1Data := bytes.NewReader(append([]byte(_data), _data2...))\n\tparser := http1parser.NewRequestReader(false, http1Data)\n\n\t// 1st request\n\treq, err := parser.ReadRequest()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, req.Header)\n\tassert.NotContains(t, req.Header, \"lowercase\")\n\tassert.Contains(t, req.Header, \"Lowercase\")\n\trequire.NoError(t, req.Body.Close())\n\n\t// 2nd request\n\treq, err = parser.ReadRequest()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, req.Header)\n\n\t// Make sure that the buffers are empty after all requests have been processed\n\tassert.True(t, parser.IsEOF())\n}\n\nfunc TestNonCanonicalRequest(t *testing.T) {\n\thttp1Data := bytes.NewReader([]byte(_data))\n\tparser := http1parser.NewRequestReader(true, http1Data)\n\n\treq, err := parser.ReadRequest()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, req.Header)\n\tassert.Contains(t, req.Header, \"lowercase\")\n\tassert.NotContains(t, req.Header, \"Lowercase\")\n}\n\nfunc TestMultipleNonCanonicalRequests(t *testing.T) {\n\thttp1Data := bytes.NewReader(append([]byte(_data), _data2...))\n\tparser := http1parser.NewRequestReader(true, http1Data)\n\n\treq, err := parser.ReadRequest()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, req.Header)\n\tassert.Contains(t, req.Header, \"lowercase\")\n\tassert.NotContains(t, req.Header, \"Lowercase\")\n\n\tbody, err := io.ReadAll(req.Body)\n\trequire.NoError(t, err)\n\tassert.Len(t, body, 17)\n\trequire.NoError(t, req.Body.Close())\n\n\treq, err = parser.ReadRequest()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, req.Header)\n\n\tassert.True(t, parser.IsEOF())\n}\n\n// reqTest is inspired by https://github.com/golang/go/blob/master/src/net/http/readrequest_test.go\ntype reqTest struct {\n\tRaw     string\n\tReq     *http.Request\n\tBody    string\n\tTrailer http.Header\n\tError   string\n}\n\nvar (\n\tnoError   = \"\"\n\tnoBodyStr = \"\"\n\tnoTrailer http.Header\n)\n\nvar reqTests = []reqTest{\n\t// Baseline test; All Request fields included for template use\n\t{\n\t\t\"GET http://www.techcrunch.com/ HTTP/1.1\\r\\n\" +\n\t\t\t\"Host: www.techcrunch.com\\r\\n\" +\n\t\t\t\"user-agent: Fake\\r\\n\" +\n\t\t\t\"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\" +\n\t\t\t\"Accept-Language: en-us,en;q=0.5\\r\\n\" +\n\t\t\t\"Accept-Encoding: gzip,deflate\\r\\n\" +\n\t\t\t\"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\\r\\n\" +\n\t\t\t\"Keep-Alive: 300\\r\\n\" +\n\t\t\t\"Content-Length: 7\\r\\n\" +\n\t\t\t\"Proxy-Connection: keep-alive\\r\\n\\r\\n\" +\n\t\t\t\"abcdef\\n???\",\n\t\t&http.Request{\n\t\t\tMethod: http.MethodGet,\n\t\t\tURL: &url.URL{\n\t\t\t\tScheme: \"http\",\n\t\t\t\tHost:   \"www.techcrunch.com\",\n\t\t\t\tPath:   \"/\",\n\t\t\t},\n\t\t\tProto:      \"HTTP/1.1\",\n\t\t\tProtoMajor: 1,\n\t\t\tProtoMinor: 1,\n\t\t\tHeader: http.Header{\n\t\t\t\t\"Accept\":           {\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"},\n\t\t\t\t\"Accept-Language\":  {\"en-us,en;q=0.5\"},\n\t\t\t\t\"Accept-Encoding\":  {\"gzip,deflate\"},\n\t\t\t\t\"Accept-Charset\":   {\"ISO-8859-1,utf-8;q=0.7,*;q=0.7\"},\n\t\t\t\t\"Keep-Alive\":       {\"300\"},\n\t\t\t\t\"Proxy-Connection\": {\"keep-alive\"},\n\t\t\t\t\"Content-Length\":   {\"7\"},\n\t\t\t\t\"user-agent\":       {\"Fake\"},\n\t\t\t},\n\t\t\tClose:         false,\n\t\t\tContentLength: 7,\n\t\t\tHost:          \"www.techcrunch.com\",\n\t\t\tRequestURI:    \"http://www.techcrunch.com/\",\n\t\t},\n\t\t\"abcdef\\n\",\n\t\tnoTrailer,\n\t\tnoError,\n\t},\n\n\t// GET request with no body (the normal case)\n\t{\n\t\t\"GET / HTTP/1.1\\r\\n\" +\n\t\t\t\"Host: foo.com\\r\\n\\r\\n\",\n\t\t&http.Request{\n\t\t\tMethod: http.MethodGet,\n\t\t\tURL: &url.URL{\n\t\t\t\tPath: \"/\",\n\t\t\t},\n\t\t\tProto:         \"HTTP/1.1\",\n\t\t\tProtoMajor:    1,\n\t\t\tProtoMinor:    1,\n\t\t\tHeader:        http.Header{},\n\t\t\tClose:         false,\n\t\t\tContentLength: 0,\n\t\t\tHost:          \"foo.com\",\n\t\t\tRequestURI:    \"/\",\n\t\t},\n\t\tnoBodyStr,\n\t\tnoTrailer,\n\t\tnoError,\n\t},\n}\n\nfunc TestReadRequest(t *testing.T) {\n\tfor i := range reqTests {\n\t\ttt := &reqTests[i]\n\n\t\ttestName := fmt.Sprintf(\"Test %d (%q)\", i, tt.Raw)\n\t\tt.Run(testName, func(t *testing.T) {\n\t\t\tr := bufio.NewReader(strings.NewReader(tt.Raw))\n\t\t\tparser := http1parser.NewRequestReader(true, r)\n\t\t\treq, err := parser.ReadRequest()\n\t\t\tif err != nil && err.Error() == tt.Error {\n\t\t\t\t// Test finished, we expected an error\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Check request equality (excluding body)\n\t\t\trbody := req.Body\n\t\t\treq.Body = nil\n\t\t\tassert.Equal(t, tt.Req, req)\n\n\t\t\t// Check if the two bodies match\n\t\t\tvar bodyString string\n\t\t\tif rbody != nil {\n\t\t\t\tdata, err := io.ReadAll(rbody)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tbodyString = string(data)\n\t\t\t\t_ = rbody.Close()\n\t\t\t}\n\t\t\tassert.Equal(t, tt.Body, bodyString)\n\t\t\tassert.Equal(t, tt.Trailer, req.Trailer)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/signer/counterecryptor.go",
    "content": "package signer\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"errors\"\n)\n\ntype CounterEncryptorRand struct {\n\tcipher  cipher.Block\n\tcounter []byte\n\trand    []byte\n\tix      int\n}\n\nfunc NewCounterEncryptorRandFromKey(key any, seed []byte) (r CounterEncryptorRand, err error) {\n\tvar keyBytes []byte\n\tswitch key := key.(type) {\n\tcase *rsa.PrivateKey:\n\t\tkeyBytes = x509.MarshalPKCS1PrivateKey(key)\n\tcase *ecdsa.PrivateKey:\n\t\tif keyBytes, err = x509.MarshalECPrivateKey(key); err != nil {\n\t\t\treturn\n\t\t}\n\tcase ed25519.PrivateKey:\n\t\tif keyBytes, err = x509.MarshalPKCS8PrivateKey(key); err != nil {\n\t\t\treturn\n\t\t}\n\tdefault:\n\t\treturn r, errors.New(\"only RSA, ED25519 and ECDSA keys supported\")\n\t}\n\th := sha256.New()\n\tif r.cipher, err = aes.NewCipher(h.Sum(keyBytes)[:aes.BlockSize]); err != nil {\n\t\treturn r, err\n\t}\n\tr.counter = make([]byte, r.cipher.BlockSize())\n\tif seed != nil {\n\t\tcopy(r.counter, h.Sum(seed)[:r.cipher.BlockSize()])\n\t}\n\tr.rand = make([]byte, r.cipher.BlockSize())\n\tr.ix = len(r.rand)\n\treturn r, nil\n}\n\nfunc (c *CounterEncryptorRand) Seed(b []byte) {\n\tif len(b) != len(c.counter) {\n\t\tpanic(\"SetCounter: wrong counter size\")\n\t}\n\tcopy(c.counter, b)\n}\n\nfunc (c *CounterEncryptorRand) refill() {\n\tc.cipher.Encrypt(c.rand, c.counter)\n\tfor i := 0; i < len(c.counter); i++ {\n\t\tif c.counter[i]++; c.counter[i] != 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\tc.ix = 0\n}\n\nfunc (c *CounterEncryptorRand) Read(b []byte) (n int, err error) {\n\tif c.ix == len(c.rand) {\n\t\tc.refill()\n\t}\n\tif n = len(c.rand) - c.ix; n > len(b) {\n\t\tn = len(b)\n\t}\n\tcopy(b, c.rand[c.ix:c.ix+n])\n\tc.ix += n\n\treturn\n}\n"
  },
  {
    "path": "internal/signer/counterecryptor_test.go",
    "content": "package signer_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/rsa\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"math\"\n\t\"math/rand\"\n\t\"testing\"\n\n\t\"github.com/elazarl/goproxy/internal/signer\"\n)\n\ntype RandSeedReader struct {\n\tr rand.Rand\n}\n\nfunc (r *RandSeedReader) Read(b []byte) (n int, err error) {\n\tfor i := range b {\n\t\tb[i] = byte(r.r.Int() & 0xFF)\n\t}\n\treturn len(b), nil\n}\n\nfunc fatalOnErr(t *testing.T, err error, msg string) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Fatal(msg, err)\n\t}\n}\n\nfunc TestCounterEncDifferentConsecutive(t *testing.T) {\n\tk, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)\n\tfatalOnErr(t, err, \"rsa.GenerateKey\")\n\tc, err := signer.NewCounterEncryptorRandFromKey(k, []byte(\"the quick brown fox run over the lazy dog\"))\n\tfatalOnErr(t, err, \"NewCounterEncryptorRandFromKey\")\n\tfor i := 0; i < 100*1000; i++ {\n\t\tvar a, b int64\n\t\tfatalOnErr(t, binary.Read(&c, binary.BigEndian, &a), \"read a\")\n\t\tfatalOnErr(t, binary.Read(&c, binary.BigEndian, &b), \"read b\")\n\t\tif a == b {\n\t\t\tt.Fatal(\"two consecutive equal int64\", a, b)\n\t\t}\n\t}\n}\n\nfunc TestCounterEncIdenticalStreams(t *testing.T) {\n\tk, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)\n\tfatalOnErr(t, err, \"rsa.GenerateKey\")\n\tc1, err := signer.NewCounterEncryptorRandFromKey(k, []byte(\"the quick brown fox run over the lazy dog\"))\n\tfatalOnErr(t, err, \"NewCounterEncryptorRandFromKey\")\n\tc2, err := signer.NewCounterEncryptorRandFromKey(k, []byte(\"the quick brown fox run over the lazy dog\"))\n\tfatalOnErr(t, err, \"NewCounterEncryptorRandFromKey\")\n\tconst nOut = 1000\n\tout1, out2 := make([]byte, nOut), make([]byte, nOut)\n\t_, _ = io.ReadFull(&c1, out1)\n\ttmp := out2\n\tfor len(tmp) > 0 {\n\t\tn := 1 + rand.Intn(256)\n\t\tif n > len(tmp) {\n\t\t\tn = len(tmp)\n\t\t}\n\t\tn, err := c2.Read(tmp[:n])\n\t\tfatalOnErr(t, err, \"CounterEncryptorRand.Read\")\n\t\ttmp = tmp[n:]\n\t}\n\tif !bytes.Equal(out1, out2) {\n\t\tt.Error(\"identical CSPRNG does not produce the same output\")\n\t}\n}\n\nfunc stddev(data []int) float64 {\n\tvar sum, sumSqr float64 = 0, 0\n\tfor _, h := range data {\n\t\tsum += float64(h)\n\t\tsumSqr += float64(h) * float64(h)\n\t}\n\tn := float64(len(data))\n\tvariance := (sumSqr - ((sum * sum) / n)) / (n - 1)\n\treturn math.Sqrt(variance)\n}\n\nfunc TestCounterEncStreamHistogram(t *testing.T) {\n\tk, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)\n\tfatalOnErr(t, err, \"rsa.GenerateKey\")\n\tc, err := signer.NewCounterEncryptorRandFromKey(k, []byte(\"the quick brown fox run over the lazy dog\"))\n\tfatalOnErr(t, err, \"NewCounterEncryptorRandFromKey\")\n\tnout := 100 * 1000\n\tout := make([]byte, nout)\n\t_, _ = io.ReadFull(&c, out)\n\trefhist := make([]int, 512)\n\tfor i := 0; i < nout; i++ {\n\t\trefhist[rand.Intn(256)]++\n\t}\n\thist := make([]int, 512)\n\tfor _, b := range out {\n\t\thist[int(b)]++\n\t}\n\trefstddev, stddev := stddev(refhist), stddev(hist)\n\t// due to lack of time, I guestimate\n\tt.Logf(\"ref:%v - act:%v = %v\", refstddev, stddev, math.Abs(refstddev-stddev))\n\tif math.Abs(refstddev-stddev) >= 1 {\n\t\tt.Errorf(\"stddev of ref histogram different than regular PRNG: %v %v\", refstddev, stddev)\n\t}\n}\n"
  },
  {
    "path": "internal/signer/signer.go",
    "content": "package signer\n\nimport (\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"math/rand\"\n\t\"net\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst _goproxySignerVersion = \":goproxy2\"\n\nfunc hashSorted(lst []string) []byte {\n\tc := make([]string, len(lst))\n\tcopy(c, lst)\n\tsort.Strings(c)\n\th := sha256.New()\n\th.Write([]byte(strings.Join(c, \",\")))\n\treturn h.Sum(nil)\n}\n\nfunc SignHost(ca tls.Certificate, hosts []string) (cert *tls.Certificate, err error) {\n\t// Use the provided CA for certificate generation.\n\t// Use already parsed Leaf certificate when present.\n\tx509ca := ca.Leaf\n\tif x509ca == nil {\n\t\tif x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnow := time.Now()\n\tstart := now.Add(-30 * 24 * time.Hour) // -30 days\n\tend := now.Add(365 * 24 * time.Hour)   // 365 days\n\n\t// Always generate a positive int value\n\t// (Two complement is not enabled when the first bit is 0)\n\tgenerated := rand.Uint64()\n\tgenerated >>= 1\n\n\ttemplate := x509.Certificate{\n\t\tSerialNumber: big.NewInt(int64(generated)),\n\t\tIssuer:       x509ca.Subject,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"GoProxy untrusted MITM proxy Inc\"},\n\t\t},\n\t\tNotBefore: start,\n\t\tNotAfter:  end,\n\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\tfor _, h := range hosts {\n\t\tif ip := net.ParseIP(h); ip != nil {\n\t\t\ttemplate.IPAddresses = append(template.IPAddresses, ip)\n\t\t} else {\n\t\t\ttemplate.DNSNames = append(template.DNSNames, h)\n\t\t\ttemplate.Subject.CommonName = h\n\t\t}\n\t}\n\n\thash := hashSorted(append(hosts, _goproxySignerVersion, \":\"+runtime.Version()))\n\tvar csprng CounterEncryptorRand\n\tif csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar certpriv crypto.Signer\n\tswitch ca.PrivateKey.(type) {\n\tcase *rsa.PrivateKey:\n\t\tif certpriv, err = rsa.GenerateKey(&csprng, 2048); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase *ecdsa.PrivateKey:\n\t\tif certpriv, err = ecdsa.GenerateKey(elliptic.P256(), &csprng); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase ed25519.PrivateKey:\n\t\tif _, certpriv, err = ed25519.GenerateKey(&csprng); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported key type %T\", ca.PrivateKey)\n\t}\n\n\tderBytes, err := x509.CreateCertificate(&csprng, &template, x509ca, certpriv.Public(), ca.PrivateKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Save an already parsed leaf certificate to use less CPU\n\t// when it will be used\n\tleafCert, err := x509.ParseCertificate(derBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcertBytes := make([][]byte, 1+len(ca.Certificate))\n\tcertBytes[0] = derBytes\n\tfor i, singleCertBytes := range ca.Certificate {\n\t\tcertBytes[i+1] = singleCertBytes\n\t}\n\n\treturn &tls.Certificate{\n\t\tCertificate: certBytes,\n\t\tPrivateKey:  certpriv,\n\t\tLeaf:        leafCert,\n\t}, nil\n}\n"
  },
  {
    "path": "internal/signer/signer_test.go",
    "content": "package signer_test\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/internal/signer\"\n)\n\nfunc orFatal(t *testing.T, msg string, err error) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Fatal(msg, err)\n\t}\n}\n\ntype ConstantHanlder string\n\nfunc (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, _ *http.Request) {\n\t_, _ = io.WriteString(w, string(h))\n}\n\nfunc getBrowser(args []string) string {\n\tfor i, arg := range args {\n\t\tif arg == \"-browser\" && i+1 < len(arg) {\n\t\t\treturn args[i+1]\n\t\t}\n\t\tif strings.HasPrefix(arg, \"-browser=\") {\n\t\t\treturn arg[len(\"-browser=\"):]\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc testSignerX509(t *testing.T, ca tls.Certificate) {\n\tt.Helper()\n\tcert, err := signer.SignHost(ca, []string{\"example.com\", \"1.1.1.1\", \"localhost\"})\n\torFatal(t, \"singHost\", err)\n\tcert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])\n\torFatal(t, \"ParseCertificate\", err)\n\tcertpool := x509.NewCertPool()\n\tcertpool.AddCert(ca.Leaf)\n\torFatal(t, \"VerifyHostname\", cert.Leaf.VerifyHostname(\"example.com\"))\n\torFatal(t, \"CheckSignatureFrom\", cert.Leaf.CheckSignatureFrom(ca.Leaf))\n\t_, err = cert.Leaf.Verify(x509.VerifyOptions{\n\t\tDNSName: \"example.com\",\n\t\tRoots:   certpool,\n\t})\n\torFatal(t, \"Verify\", err)\n}\n\nfunc testSignerTLS(t *testing.T, ca tls.Certificate) {\n\tt.Helper()\n\tcert, err := signer.SignHost(ca, []string{\"example.com\", \"1.1.1.1\", \"localhost\"})\n\torFatal(t, \"singHost\", err)\n\tcert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])\n\torFatal(t, \"ParseCertificate\", err)\n\texpected := \"key verifies with Go\"\n\tserver := httptest.NewUnstartedServer(ConstantHanlder(expected))\n\tdefer server.Close()\n\tserver.TLS = &tls.Config{\n\t\tCertificates: []tls.Certificate{*cert, ca},\n\t\tMinVersion:   tls.VersionTLS12,\n\t}\n\tserver.StartTLS()\n\tcertpool := x509.NewCertPool()\n\tcertpool.AddCert(ca.Leaf)\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{RootCAs: certpool},\n\t}\n\tasLocalhost := strings.ReplaceAll(server.URL, \"127.0.0.1\", \"localhost\")\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, asLocalhost, nil)\n\torFatal(t, \"NewRequest\", err)\n\tresp, err := tr.RoundTrip(req)\n\torFatal(t, \"RoundTrip\", err)\n\ttxt, err := io.ReadAll(resp.Body)\n\torFatal(t, \"io.ReadAll\", err)\n\tif string(txt) != expected {\n\t\tt.Errorf(\"Expected '%s' got '%s'\", expected, string(txt))\n\t}\n\tbrowser := getBrowser(os.Args)\n\tif browser != \"\" {\n\t\tctx := context.Background()\n\t\t_ = exec.CommandContext(ctx, browser, asLocalhost).Run()\n\t\ttime.Sleep(10 * time.Second)\n\t}\n}\n\nfunc TestSignerRsaTls(t *testing.T) {\n\ttestSignerTLS(t, goproxy.GoproxyCa)\n}\n\nfunc TestSignerRsaX509(t *testing.T) {\n\ttestSignerX509(t, goproxy.GoproxyCa)\n}\n\nfunc TestSignerEcdsaTls(t *testing.T) {\n\ttestSignerTLS(t, EcdsaCa)\n}\n\nfunc TestSignerEcdsaX509(t *testing.T) {\n\ttestSignerX509(t, EcdsaCa)\n}\n\nfunc BenchmarkSignRsa(b *testing.B) {\n\tvar cert *tls.Certificate\n\tvar err error\n\tfor n := 0; n < b.N; n++ {\n\t\tcert, err = signer.SignHost(goproxy.GoproxyCa, []string{\"example.com\", \"1.1.1.1\", \"localhost\"})\n\t}\n\t_ = cert\n\t_ = err\n}\n\nfunc BenchmarkSignEcdsa(b *testing.B) {\n\tvar cert *tls.Certificate\n\tvar err error\n\tfor n := 0; n < b.N; n++ {\n\t\tcert, err = signer.SignHost(EcdsaCa, []string{\"example.com\", \"1.1.1.1\", \"localhost\"})\n\t}\n\t_ = cert\n\t_ = err\n}\n\n//\n// Eliptic Curve certificate and key for testing\n//\n\nvar EcdsaCaCert = []byte(`-----BEGIN CERTIFICATE-----\nMIICGDCCAb8CFEkSgqYhlT0+Yyr9anQNJgtclTL0MAoGCCqGSM49BAMDMIGOMQsw\nCQYDVQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNV\nBAoMB0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHku\nZ2l0aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0x\nOTA1MDcxMTUwMThaFw0zOTA1MDIxMTUwMThaMIGOMQswCQYDVQQGEwJJTDEPMA0G\nA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAO\nBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJ\nKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABDlH4YrdukPFAjbO8x+gR9F8ID7eCU8Orhba/MIblSRrRVedpj08lK+2\nsvyoAcrcDsynClO9aQtsC9ivZ+Pmr3MwCgYIKoZIzj0EAwMDRwAwRAIgGRSSJVSE\n1b1KVU0+w+SRtnR5Wb7jkwnaDNxQ3c3FXoICIBJV/l1hFM7mbd68Oi5zLq/4ZsrL\n98Bb3nddk2xys6a9\n-----END CERTIFICATE-----`)\n\nvar EcdsaCaKey = []byte(`-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEsc8m+2aZfagnesg\nqMgXe8ph4LtVu2VOUYhHttuEDsChRANCAAQ5R+GK3bpDxQI2zvMfoEfRfCA+3glP\nDq4W2vzCG5Uka0VXnaY9PJSvtrL8qAHK3A7MpwpTvWkLbAvYr2fj5q9z\n-----END PRIVATE KEY-----`)\n\nvar EcdsaCa, ecdsaCaErr = tls.X509KeyPair(EcdsaCaCert, EcdsaCaKey)\n\nfunc init() {\n\tif ecdsaCaErr != nil {\n\t\tpanic(\"Error parsing ecdsa CA \" + ecdsaCaErr.Error())\n\t}\n\tvar err error\n\tif EcdsaCa.Leaf, err = x509.ParseCertificate(EcdsaCa.Certificate[0]); err != nil {\n\t\tpanic(\"Error parsing ecdsa CA \" + err.Error())\n\t}\n}\n"
  },
  {
    "path": "key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF\n0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw\nHM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf\nm31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+\nqiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ\n0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I\nyeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq\nAmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU\nBIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK\n0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic\ngeFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA\nAQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR\nkPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3\nlVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt\nzCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7\n+68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ\n3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf\npjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U\nC440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4\nOod3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3\n4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm\nV6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9\njZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag\n/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6\neDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw\n+LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ\nZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt\nFQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC\n06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7\nOSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9\n7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf\nKR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt\nsjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB\nN/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa\nQbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv\n5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W\nt3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF\n540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru\nsIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi\nL0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um\nYJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi\n9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe\nyKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ\nQBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c\nue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH\n759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh\npmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1\ncj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88\n4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "logger.go",
    "content": "package goproxy\n\ntype Logger interface {\n\tPrintf(format string, v ...any)\n}\n"
  },
  {
    "path": "proxy.go",
    "content": "package goproxy\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n)\n\n// The basic proxy type. Implements http.Handler.\ntype ProxyHttpServer struct {\n\t// session variable must be aligned in i386\n\t// see http://golang.org/src/pkg/sync/atomic/doc.go#L41\n\tsess int64\n\t// KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying\n\tKeepDestinationHeaders bool\n\t// setting Verbose to true will log information on each request sent to the proxy\n\tVerbose         bool\n\tLogger          Logger\n\tNonproxyHandler http.Handler\n\treqHandlers     []ReqHandler\n\trespHandlers    []RespHandler\n\thttpsHandlers   []HttpsHandler\n\tTr              *http.Transport\n\t// ConnectionErrHandler will be invoked to return a custom response\n\t// to clients (written using conn parameter), when goproxy fails to connect\n\t// to a target proxy.\n\t// The error is passed as function parameter and not inside the proxy\n\t// context, to avoid race conditions.\n\tConnectionErrHandler func(conn io.Writer, ctx *ProxyCtx, err error)\n\t// ConnectDial will be used to create TCP connections for CONNECT requests\n\t// if nil Tr.Dial will be used\n\tConnectDial        func(network string, addr string) (net.Conn, error)\n\tConnectDialWithReq func(req *http.Request, network string, addr string) (net.Conn, error)\n\tCertStore          CertStorage\n\tKeepHeader         bool\n\tAllowHTTP2         bool\n\t// When PreventCanonicalization is true, the header names present in\n\t// the request sent through the proxy are directly passed to the destination server,\n\t// instead of following the HTTP RFC for their canonicalization.\n\t// This is useful when the header name isn't treated as a case-insensitive\n\t// value by the target server, because they don't follow the specs.\n\tPreventCanonicalization bool\n\t// KeepAcceptEncoding, if true, prevents the proxy from dropping\n\t// Accept-Encoding headers from the client.\n\t//\n\t// Note that the outbound http.Transport may still choose to add\n\t// Accept-Encoding: gzip if the client did not explicitly send an\n\t// Accept-Encoding header. To disable this behavior, set\n\t// Tr.DisableCompression to true.\n\tKeepAcceptEncoding bool\n}\n\nvar hasPort = regexp.MustCompile(`:\\d+$`)\n\nfunc copyHeaders(dst, src http.Header, keepDestHeaders bool) {\n\tif !keepDestHeaders {\n\t\tfor k := range dst {\n\t\t\tdst.Del(k)\n\t\t}\n\t}\n\tfor k, vs := range src {\n\t\t// direct assignment to avoid canonicalization\n\t\tdst[k] = append([]string(nil), vs...)\n\t}\n}\n\nfunc (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) {\n\treq = r\n\tfor _, h := range proxy.reqHandlers {\n\t\treq, resp = h.Handle(req, ctx)\n\t\t// non-nil resp means the handler decided to skip sending the request\n\t\t// and return canned response instead.\n\t\tif resp != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n\nfunc (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) {\n\tresp = respOrig\n\tfor _, h := range proxy.respHandlers {\n\t\tctx.Resp = resp\n\t\tresp = h.Handle(resp, ctx)\n\t}\n\treturn\n}\n\n// RemoveProxyHeaders removes all proxy headers which should not propagate to the next hop.\nfunc RemoveProxyHeaders(ctx *ProxyCtx, r *http.Request) {\n\tr.RequestURI = \"\" // this must be reset when serving a request with the client\n\tctx.Logf(\"Sending request %v %v\", r.Method, r.URL.String())\n\tif !ctx.Proxy.KeepAcceptEncoding {\n\t\t// If no Accept-Encoding header exists, Transport will add the headers it can accept\n\t\t// and would wrap the response body with the relevant reader.\n\t\tr.Header.Del(\"Accept-Encoding\")\n\t}\n\t// curl can add that, see\n\t// https://jdebp.eu./FGA/web-proxy-connection-header.html\n\tr.Header.Del(\"Proxy-Connection\")\n\tr.Header.Del(\"Proxy-Authenticate\")\n\tr.Header.Del(\"Proxy-Authorization\")\n\t// Connection, Authenticate and Authorization are single hop Header:\n\t// http://www.w3.org/Protocols/rfc2616/rfc2616.txt\n\t// 14.10 Connection\n\t//   The Connection general-header field allows the sender to specify\n\t//   options that are desired for that particular connection and MUST NOT\n\t//   be communicated by proxies over further connections.\n\n\t// We need to keep \"Connection: upgrade\" header, since it's part of\n\t// the WebSocket handshake, and it won't work without it.\n\t// For all the other cases (close, keep-alive), we already handle them, by\n\t// setting the r.Close variable in the previous lines.\n\tif !isWebSocketHandshake(r.Header) {\n\t\tr.Header.Del(\"Connection\")\n\t}\n}\n\ntype flushWriter struct {\n\tw io.Writer\n}\n\nfunc (fw flushWriter) Write(p []byte) (int, error) {\n\tn, err := fw.w.Write(p)\n\tif f, ok := fw.w.(http.Flusher); ok {\n\t\t// only flush if the Writer implements the Flusher interface.\n\t\tf.Flush()\n\t}\n\n\treturn n, err\n}\n\n// Standard net/http function. Shouldn't be used directly, http.Serve will use it.\nfunc (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif r.Method == http.MethodConnect {\n\t\tproxy.handleHttps(w, r)\n\t} else {\n\t\tproxy.handleHttp(w, r)\n\t}\n}\n\n// NewProxyHttpServer creates and returns a proxy server, logging to stderr by default.\nfunc NewProxyHttpServer() *ProxyHttpServer {\n\tproxy := ProxyHttpServer{\n\t\tLogger: log.New(os.Stderr, \"\", log.LstdFlags),\n\t\tNonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\t\thttp.Error(w, \"This is a proxy server. Does not respond to non-proxy requests.\", http.StatusInternalServerError)\n\t\t}),\n\t\tTr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment},\n\t}\n\tproxy.ConnectDial = dialerFromEnv(&proxy)\n\treturn &proxy\n}\n"
  },
  {
    "path": "proxy_test.go",
    "content": "package goproxy_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/http/httptrace\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\thttps = httptest.NewTLSServer(nil)\n\tsrv   = httptest.NewServer(nil)\n\tfs    = httptest.NewServer(http.FileServer(http.Dir(\".\")))\n)\n\ntype QueryHandler struct{}\n\nfunc (QueryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif err := req.ParseForm(); err != nil {\n\t\tpanic(err)\n\t}\n\t_, _ = io.WriteString(w, req.Form.Get(\"result\"))\n}\n\ntype HeadersHandler struct{}\n\n// This handlers returns a body with a string containing all the request headers it received.\nfunc (HeadersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tvar sb strings.Builder\n\tfor name, values := range req.Header {\n\t\tfor _, value := range values {\n\t\t\tsb.WriteString(name)\n\t\t\tsb.WriteString(\": \")\n\t\t\tsb.WriteString(value)\n\t\t\tsb.WriteString(\";\")\n\t\t}\n\t}\n\t_, _ = io.WriteString(w, sb.String())\n}\n\nfunc init() {\n\thttp.DefaultServeMux.Handle(\"/bobo\", ConstantHanlder(\"bobo\"))\n\thttp.DefaultServeMux.Handle(\"/query\", QueryHandler{})\n\thttp.DefaultServeMux.Handle(\"/headers\", HeadersHandler{})\n}\n\ntype ConstantHanlder string\n\nfunc (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t_, _ = io.WriteString(w, string(h))\n}\n\nfunc get(url string, client *http.Client) ([]byte, error) {\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttxt, err := io.ReadAll(resp.Body)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn txt, nil\n}\n\nfunc getOrFail(t *testing.T, url string, client *http.Client) []byte {\n\tt.Helper()\n\ttxt, err := get(url, client)\n\tif err != nil {\n\t\tt.Fatal(\"Can't fetch url\", url, err)\n\t}\n\treturn txt\n}\n\nfunc getCert(t *testing.T, c *tls.Conn) []byte {\n\tt.Helper()\n\tif err := c.HandshakeContext(context.Background()); err != nil {\n\t\tt.Fatal(\"cannot handshake\", err)\n\t}\n\treturn c.ConnectionState().PeerCertificates[0].Raw\n}\n\nfunc localFile(url string) string {\n\treturn fs.URL + \"/\" + url\n}\n\nfunc TestSimpleHttpReqWithProxy(t *testing.T) {\n\tclient, s := oneShotProxy(goproxy.NewProxyHttpServer())\n\tdefer s.Close()\n\n\tif r := string(getOrFail(t, srv.URL+\"/bobo\", client)); r != \"bobo\" {\n\t\tt.Error(\"proxy server does not serve constant handlers\", r)\n\t}\n\tif r := string(getOrFail(t, srv.URL+\"/bobo\", client)); r != \"bobo\" {\n\t\tt.Error(\"proxy server does not serve constant handlers\", r)\n\t}\n\n\tif string(getOrFail(t, https.URL+\"/bobo\", client)) != \"bobo\" {\n\t\tt.Error(\"TLS server does not serve constant handlers, when proxy is used\")\n\t}\n}\n\nfunc oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, s *httptest.Server) {\n\ts = httptest.NewServer(proxy)\n\n\tproxyUrl, _ := url.Parse(s.URL)\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t\tProxy: http.ProxyURL(proxyUrl),\n\t}\n\tclient = &http.Client{Transport: tr}\n\treturn\n}\n\nfunc TestSimpleHook(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest(goproxy.SrcIpIs(\"127.0.0.1\")).DoFunc(\n\t\tfunc(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t\treq.URL.Path = \"/bobo\"\n\t\t\treturn req, nil\n\t\t},\n\t)\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tif result := string(getOrFail(t, srv.URL+(\"/momo\"), client)); result != \"bobo\" {\n\t\tt.Error(\"Redirecting all requests from 127.0.0.1 to bobo, didn't work.\" +\n\t\t\t\" (Might break if Go's client sets RemoteAddr to IPv6 address). Got: \" +\n\t\t\tresult)\n\t}\n}\n\nfunc TestAlwaysHook(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\treq.URL.Path = \"/bobo\"\n\t\treturn req, nil\n\t})\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tif result := string(getOrFail(t, srv.URL+(\"/momo\"), client)); result != \"bobo\" {\n\t\tt.Error(\"Redirecting all requests from 127.0.0.1 to bobo, didn't work.\" +\n\t\t\t\" (Might break if Go's client sets RemoteAddr to IPv6 address). Got: \" +\n\t\t\tresult)\n\t}\n}\n\nfunc TestReplaceResponse(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tresp.StatusCode = http.StatusOK\n\t\tresp.Body = io.NopCloser(bytes.NewBufferString(\"chico\"))\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tif result := string(getOrFail(t, srv.URL+(\"/momo\"), client)); result != \"chico\" {\n\t\tt.Error(\"hooked response, should be chico, instead:\", result)\n\t}\n}\n\nfunc TestReplaceReponseForUrl(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse(goproxy.UrlIs(\"/koko\")).DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tresp.StatusCode = http.StatusOK\n\t\tresp.Body = io.NopCloser(bytes.NewBufferString(\"chico\"))\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tif result := string(getOrFail(t, srv.URL+(\"/koko\"), client)); result != \"chico\" {\n\t\tt.Error(\"hooked 'koko', should be chico, instead:\", result)\n\t}\n\tif result := string(getOrFail(t, srv.URL+(\"/bobo\"), client)); result != \"bobo\" {\n\t\tt.Error(\"still, bobo should stay as usual, instead:\", result)\n\t}\n}\n\nfunc TestOneShotFileServer(t *testing.T) {\n\tclient, l := oneShotProxy(goproxy.NewProxyHttpServer())\n\tdefer l.Close()\n\n\tfile := \"test_data/panda.png\"\n\tinfo, err := os.Stat(file)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot find\", file)\n\t}\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fs.URL+\"/\"+file, nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\tif resp, err := client.Do(req); err == nil {\n\t\tb, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"got\", string(b))\n\t\t}\n\t\tif int64(len(b)) != info.Size() {\n\t\t\tt.Error(\"Expected Length\", file, info.Size(), \"actually\", len(b), \"starts\", string(b[:10]))\n\t\t}\n\t} else {\n\t\tt.Fatal(\"Cannot read from fs server\", err)\n\t}\n}\n\nfunc TestContentType(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse(goproxy.ContentTypeIs(\"image/png\")).DoFunc(\n\t\tfunc(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\t\tresp.Header.Set(\"X-Shmoopi\", \"1\")\n\t\t\treturn resp\n\t\t},\n\t)\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tfor _, file := range []string{\"test_data/panda.png\", \"test_data/football.png\"} {\n\t\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, localFile(file), nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Cannot create request\", err)\n\t\t}\n\t\tif resp, err := client.Do(req); err != nil || resp.Header.Get(\"X-Shmoopi\") != \"1\" {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"pngs should have X-Shmoopi header = 1, actually\", resp.Header.Get(\"X-Shmoopi\"))\n\t\t\t} else {\n\t\t\t\tt.Error(\"error reading png\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, localFile(\"baby.jpg\"), nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\tif resp, err := client.Do(req); err != nil || resp.Header.Get(\"X-Shmoopi\") != \"\" {\n\t\tif err == nil {\n\t\t\tt.Error(\"Non png images should NOT have X-Shmoopi header at all\", resp.Header.Get(\"X-Shmoopi\"))\n\t\t} else {\n\t\t\tt.Error(\"error reading png\", err)\n\t\t}\n\t}\n}\n\nfunc panicOnErr(err error, msg string) {\n\tif err != nil {\n\t\tlog.Fatal(err.Error() + \":-\" + msg)\n\t}\n}\n\nfunc TestChangeResp(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\t_, _ = resp.Body.Read([]byte{0})\n\t\tresp.Body = io.NopCloser(new(bytes.Buffer))\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, localFile(\"test_data/panda.png\"), nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, _ = io.ReadAll(resp.Body)\n\treq, err = http.NewRequestWithContext(context.Background(), http.MethodGet, localFile(\"/bobo\"), nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\t_, err = client.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestSimpleMitm(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest(goproxy.ReqHostIs(https.Listener.Addr().String())).HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest(goproxy.ReqHostIs(\"no such host exists\")).HandleConnect(goproxy.AlwaysMitm)\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tctx := context.Background()\n\tc, err := (&tls.Dialer{\n\t\tConfig: &tls.Config{InsecureSkipVerify: true},\n\t}).DialContext(ctx, \"tcp\", https.Listener.Addr().String())\n\tif err != nil {\n\t\tt.Fatal(\"cannot dial to tcp server\", err)\n\t}\n\ttlsConn, ok := c.(*tls.Conn)\n\tassert.True(t, ok)\n\torigCert := getCert(t, tlsConn)\n\t_ = c.Close()\n\n\tc2, err := (&net.Dialer{}).DialContext(ctx, \"tcp\", l.Listener.Addr().String())\n\tif err != nil {\n\t\tt.Fatal(\"dialing to proxy\", err)\n\t}\n\tcreq, err := http.NewRequestWithContext(context.Background(), http.MethodConnect, https.URL, nil)\n\tif err != nil {\n\t\tt.Fatal(\"create new request\", creq)\n\t}\n\t_ = creq.Write(c2)\n\tc2buf := bufio.NewReader(c2)\n\tresp, err := http.ReadResponse(c2buf, creq)\n\tif err != nil || resp.StatusCode != http.StatusOK {\n\t\tt.Fatal(\"Cannot CONNECT through proxy\", err)\n\t}\n\tc2tls := tls.Client(c2, &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t})\n\tproxyCert := getCert(t, c2tls)\n\n\tif bytes.Equal(proxyCert, origCert) {\n\t\tt.Errorf(\"Certificate after mitm is not different\\n%v\\n%v\",\n\t\t\tbase64.StdEncoding.EncodeToString(origCert),\n\t\t\tbase64.StdEncoding.EncodeToString(proxyCert))\n\t}\n\n\tif resp := string(getOrFail(t, https.URL+\"/bobo\", client)); resp != \"bobo\" {\n\t\tt.Error(\"Wrong response when mitm\", resp, \"expected bobo\")\n\t}\n\tif resp := string(getOrFail(t, https.URL+\"/query?result=bar\", client)); resp != \"bar\" {\n\t\tt.Error(\"Wrong response when mitm\", resp, \"expected bar\")\n\t}\n}\n\nfunc TestMitmMutateRequest(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t// We inject a header in the request\n\t\treq.Header.Set(\"Mitm-Header-Inject\", \"true\")\n\t\treturn req, nil\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tr := string(getOrFail(t, https.URL+\"/headers\", client))\n\tif !strings.Contains(r, \"Mitm-Header-Inject: true\") {\n\t\tt.Error(\"Expected response body to contain the MITM injected header. Got instead: \", r)\n\t}\n}\n\nfunc TestConnectHandler(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\talthttps := httptest.NewTLSServer(ConstantHanlder(\"althttps\"))\n\tproxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {\n\t\tu, _ := url.Parse(althttps.URL)\n\t\treturn goproxy.OkConnect, u.Host\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\tif resp := string(getOrFail(t, https.URL+\"/alturl\", client)); resp != \"althttps\" {\n\t\tt.Error(\"Proxy should redirect CONNECT requests to local althttps server, expected 'althttps' got \", resp)\n\t}\n}\n\nfunc TestMitmIsFiltered(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest(goproxy.ReqHostIs(https.Listener.Addr().String())).HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest(goproxy.UrlIs(\"/momo\")).DoFunc(\n\t\tfunc(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t\treturn nil, goproxy.TextResponse(req, \"koko\")\n\t\t},\n\t)\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tif resp := string(getOrFail(t, https.URL+\"/momo\", client)); resp != \"koko\" {\n\t\tt.Error(\"Proxy should capture /momo to be koko and not\", resp)\n\t}\n\n\tif resp := string(getOrFail(t, https.URL+\"/bobo\", client)); resp != \"bobo\" {\n\t\tt.Error(\"But still /bobo should be bobo and not\", resp)\n\t}\n}\n\nfunc TestFirstHandlerMatches(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\treturn nil, goproxy.TextResponse(req, \"koko\")\n\t})\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tpanic(\"should never get here, previous response is no null\")\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tif resp := string(getOrFail(t, srv.URL+\"/\", client)); resp != \"koko\" {\n\t\tt.Error(\"should return always koko and not\", resp)\n\t}\n}\n\nfunc TestIcyResponse(t *testing.T) {\n\t// TODO: fix this test\n\t/*s := constantHttpServer([]byte(\"ICY 200 OK\\r\\n\\r\\nblablabla\"))\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.Verbose = true\n\t_, l := oneShotProxy(proxy, t)\n\tdefer l.Close()\n\treq, err := http.NewRequest(\"GET\", \"http://\"+s, nil)\n\tpanicOnErr(err, \"newReq\")\n\tproxyip := l.URL[len(\"http://\"):]\n\tprintln(\"got ip: \" + proxyip)\n\tc, err := net.Dial(\"tcp\", proxyip)\n\tpanicOnErr(err, \"dial\")\n\tdefer c.Close()\n\treq.WriteProxy(c)\n\traw, err := io.ReadAll(c)\n\tpanicOnErr(err, \"readAll\")\n\tif string(raw) != \"ICY 200 OK\\r\\n\\r\\nblablabla\" {\n\t\tt.Error(\"Proxy did not send the malformed response received\")\n\t}*/\n}\n\ntype VerifyNoProxyHeaders struct {\n\t*testing.T\n}\n\nfunc (v VerifyNoProxyHeaders) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif r.Header.Get(\"Connection\") != \"\" || r.Header.Get(\"Proxy-Connection\") != \"\" ||\n\t\tr.Header.Get(\"Proxy-Authenticate\") != \"\" || r.Header.Get(\"Proxy-Authorization\") != \"\" {\n\t\tv.Error(\"Got Connection header from goproxy\", r.Header)\n\t}\n}\n\nfunc TestNoProxyHeaders(t *testing.T) {\n\ts := httptest.NewServer(VerifyNoProxyHeaders{t})\n\tclient, l := oneShotProxy(goproxy.NewProxyHttpServer())\n\tdefer l.Close()\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, s.URL, nil)\n\tpanicOnErr(err, \"bad request\")\n\treq.Header.Add(\"Proxy-Connection\", \"close\")\n\treq.Header.Add(\"Proxy-Authenticate\", \"auth\")\n\treq.Header.Add(\"Proxy-Authorization\", \"auth\")\n\t_, _ = client.Do(req)\n}\n\nfunc TestNoProxyHeadersHttps(t *testing.T) {\n\ts := httptest.NewTLSServer(VerifyNoProxyHeaders{t})\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, s.URL, nil)\n\tpanicOnErr(err, \"bad request\")\n\treq.Header.Add(\"Proxy-Connection\", \"close\")\n\t_, _ = client.Do(req)\n}\n\ntype VerifyAcceptEncodingHeader struct {\n\tReceivedHeaderValue string\n}\n\nfunc (v *VerifyAcceptEncodingHeader) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tv.ReceivedHeaderValue = r.Header.Get(\"Accept-Encoding\")\n}\n\nfunc TestAcceptEncoding(t *testing.T) {\n\tv := VerifyAcceptEncodingHeader{}\n\ts := httptest.NewServer(&v)\n\tfor i, tc := range []struct {\n\t\tkeepAcceptEncoding bool\n\t\tdisableCompression bool\n\t\tacceptEncoding     string\n\t\texpectedValue      string\n\t}{\n\t\t{false, false, \"\", \"gzip\"},\n\t\t{false, false, \"identity\", \"gzip\"},\n\t\t{false, true, \"\", \"\"},\n\t\t{false, true, \"identity\", \"\"},\n\t\t{true, false, \"\", \"gzip\"},\n\t\t{true, false, \"identity\", \"identity\"},\n\t\t{true, true, \"\", \"\"},\n\t\t{true, true, \"identity\", \"identity\"},\n\t} {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tproxy := goproxy.NewProxyHttpServer()\n\t\t\tproxy.KeepAcceptEncoding = tc.keepAcceptEncoding\n\t\t\tproxy.Tr.DisableCompression = tc.disableCompression\n\t\t\tclient, l := oneShotProxy(proxy)\n\t\t\tdefer l.Close()\n\t\t\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, s.URL, nil)\n\t\t\tpanicOnErr(err, \"bad request\")\n\t\t\t// fully control the Accept-Encoding header we send to the proxy\n\t\t\ttr, ok := client.Transport.(*http.Transport)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"invalid client transport\")\n\t\t\t}\n\t\t\ttr.DisableCompression = true\n\t\t\tif tc.acceptEncoding != \"\" {\n\t\t\t\treq.Header.Add(\"Accept-Encoding\", tc.acceptEncoding)\n\t\t\t}\n\t\t\t_, err = client.Do(req)\n\t\t\tpanicOnErr(err, \"bad response\")\n\t\t\tif v.ReceivedHeaderValue != tc.expectedValue {\n\t\t\t\tt.Errorf(\"%+v expected Accept-Encoding: %s, got %s\", tc, tc.expectedValue, v.ReceivedHeaderValue)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHeadReqHasContentLength(t *testing.T) {\n\tclient, l := oneShotProxy(goproxy.NewProxyHttpServer())\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodHead, localFile(\"test_data/panda.png\"), nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tpanicOnErr(err, \"resp to HEAD\")\n\tif resp.Header.Get(\"Content-Length\") == \"\" {\n\t\tt.Error(\"Content-Length should exist on HEAD requests\")\n\t}\n}\n\nfunc TestChunkedResponse(t *testing.T) {\n\tctx := context.Background()\n\n\tl, err := (&net.ListenConfig{}).Listen(ctx, \"tcp\", \":10234\")\n\tpanicOnErr(err, \"listen\")\n\tdefer l.Close()\n\tgo func() {\n\t\tfor i := 0; i < 2; i++ {\n\t\t\tc, err := l.Accept()\n\t\t\tpanicOnErr(err, \"accept\")\n\t\t\t_, err = http.ReadRequest(bufio.NewReader(c))\n\t\t\tpanicOnErr(err, \"readrequest\")\n\t\t\t_, _ = io.WriteString(c, \"HTTP/1.1 200 OK\\r\\n\"+\n\t\t\t\t\"Content-Type: text/plain\\r\\n\"+\n\t\t\t\t\"Transfer-Encoding: chunked\\r\\n\\r\\n\"+\n\t\t\t\t\"25\\r\\n\"+\n\t\t\t\t\"This is the data in the first chunk\\r\\n\\r\\n\"+\n\t\t\t\t\"1C\\r\\n\"+\n\t\t\t\t\"and this is the second one\\r\\n\\r\\n\"+\n\t\t\t\t\"3\\r\\n\"+\n\t\t\t\t\"con\\r\\n\"+\n\t\t\t\t\"8\\r\\n\"+\n\t\t\t\t\"sequence\\r\\n0\\r\\n\\r\\n\")\n\t\t\t_ = c.Close()\n\t\t}\n\t}()\n\n\tc, err := (&net.Dialer{}).DialContext(ctx, \"tcp\", \"localhost:10234\")\n\tpanicOnErr(err, \"dial\")\n\tdefer c.Close()\n\treq, _ := http.NewRequestWithContext(ctx, http.MethodGet, \"/\", nil)\n\t_ = req.Write(c)\n\tresp, err := http.ReadResponse(bufio.NewReader(c), req)\n\tpanicOnErr(err, \"readresp\")\n\tb, err := io.ReadAll(resp.Body)\n\tpanicOnErr(err, \"readall\")\n\texpected := \"This is the data in the first chunk\\r\\nand this is the second one\\r\\nconsequence\"\n\tif string(b) != expected {\n\t\tt.Errorf(\"Got `%v` expected `%v`\", string(b), expected)\n\t}\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tpanicOnErr(ctx.Error, \"error reading output\")\n\t\tb, err := io.ReadAll(resp.Body)\n\t\t_ = resp.Body.Close()\n\t\tpanicOnErr(err, \"readall onresp\")\n\t\tif enc := resp.Header.Get(\"Transfer-Encoding\"); enc != \"\" {\n\t\t\tt.Fatal(\"Chunked response should be received as plaintext\", enc)\n\t\t}\n\t\tresp.Body = io.NopCloser(bytes.NewBufferString(strings.ReplaceAll(string(b), \"e\", \"E\")))\n\t\treturn resp\n\t})\n\n\tclient, s := oneShotProxy(proxy)\n\tdefer s.Close()\n\n\treq, err = http.NewRequestWithContext(ctx, http.MethodGet, \"http://localhost:10234/\", nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\n\tresp, err = client.Do(req)\n\tpanicOnErr(err, \"client.Get\")\n\tb, err = io.ReadAll(resp.Body)\n\tpanicOnErr(err, \"readall proxy\")\n\tif string(b) != strings.ReplaceAll(expected, \"e\", \"E\") {\n\t\tt.Error(\"expected\", expected, \"w/ e->E. Got\", string(b))\n\t}\n}\n\nfunc TestGoproxyThroughProxy(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy2 := goproxy.NewProxyHttpServer()\n\tdoubleString := func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tb, err := io.ReadAll(resp.Body)\n\t\tpanicOnErr(err, \"readAll resp\")\n\t\tresp.Body = io.NopCloser(bytes.NewBufferString(string(b) + \" \" + string(b)))\n\t\treturn resp\n\t}\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnResponse().DoFunc(doubleString)\n\n\t_, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tproxy2.ConnectDial = proxy2.NewConnectDialToProxy(l.URL)\n\n\tclient, l2 := oneShotProxy(proxy2)\n\tdefer l2.Close()\n\tif r := string(getOrFail(t, https.URL+\"/bobo\", client)); r != \"bobo bobo\" {\n\t\tt.Error(\"Expected bobo doubled twice, got\", r)\n\t}\n}\n\nfunc TestHttpProxyAddrsFromEnv(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tdoubleString := func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tb, err := io.ReadAll(resp.Body)\n\t\tpanicOnErr(err, \"readAll resp\")\n\t\tresp.Body = io.NopCloser(bytes.NewBufferString(string(b) + \" \" + string(b)))\n\t\treturn resp\n\t}\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnResponse().DoFunc(doubleString)\n\n\t_, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tt.Setenv(\"https_proxy\", l.URL)\n\tproxy2 := goproxy.NewProxyHttpServer()\n\n\tclient, l2 := oneShotProxy(proxy2)\n\tdefer l2.Close()\n\tif r := string(getOrFail(t, https.URL+\"/bobo\", client)); r != \"bobo bobo\" {\n\t\tt.Error(\"Expected bobo doubled twice, got\", r)\n\t}\n}\n\nfunc TestGoproxyHijackConnect(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest(goproxy.ReqHostIs(srv.Listener.Addr().String())).\n\t\tHijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {\n\t\t\tt.Logf(\"URL %+#v\\nSTR %s\", req.URL, req.URL.String())\n\t\t\tgetReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, (&url.URL{\n\t\t\t\tScheme: \"http\",\n\t\t\t\tHost:   req.URL.Host,\n\t\t\t\tPath:   \"/bobo\",\n\t\t\t}).String(), nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Cannot create request\", err)\n\t\t\t}\n\t\t\thttpClient := &http.Client{}\n\t\t\tresp, err := httpClient.Do(getReq)\n\t\t\tpanicOnErr(err, \"http.Get(CONNECT url)\")\n\t\t\tpanicOnErr(resp.Write(client), \"resp.Write(client)\")\n\t\t\t_ = resp.Body.Close()\n\t\t\t_ = client.Close()\n\t\t})\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\tproxyAddr := l.Listener.Addr().String()\n\tconn, err := (&net.Dialer{}).DialContext(context.Background(), \"tcp\", proxyAddr)\n\tpanicOnErr(err, \"conn \"+proxyAddr)\n\tbuf := bufio.NewReader(conn)\n\twriteConnect(conn)\n\tif txt := readResponse(buf); txt != \"bobo\" {\n\t\tt.Error(\"Expected bobo for CONNECT /foo, got\", txt)\n\t}\n\n\tif r := string(getOrFail(t, https.URL+\"/bobo\", client)); r != \"bobo\" {\n\t\tt.Error(\"Expected bobo would keep working with CONNECT\", r)\n\t}\n}\n\nfunc readResponse(buf *bufio.Reader) string {\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\tpanicOnErr(err, \"NewRequest\")\n\tresp, err := http.ReadResponse(buf, req)\n\tpanicOnErr(err, \"resp.Read\")\n\tdefer resp.Body.Close()\n\ttxt, err := io.ReadAll(resp.Body)\n\tpanicOnErr(err, \"resp.Read\")\n\treturn string(txt)\n}\n\nfunc writeConnect(w io.Writer) {\n\t// this will let us use IP address of server as url in http.NewRequest by\n\t// passing it as //127.0.0.1:64584 (prefixed with //).\n\t// Passing IP address with port alone (without //) will raise error:\n\t// \"first path segment in URL cannot contain colon\" more details on this\n\t// here: https://github.com/golang/go/issues/18824\n\treq := &http.Request{\n\t\tMethod: http.MethodConnect,\n\t\tURL:    &url.URL{Opaque: srv.Listener.Addr().String()},\n\t\tHost:   srv.Listener.Addr().String(),\n\t\tHeader: make(http.Header),\n\t}\n\terr := req.Write(w)\n\tpanicOnErr(err, \"req(CONNECT).Write\")\n}\n\nfunc TestCurlMinusP(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {\n\t\treturn goproxy.MitmConnect, host\n\t})\n\tcalled := false\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\tcalled = true\n\t\treturn req, nil\n\t})\n\t_, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tcmd := exec.CommandContext(ctx, \"curl\", \"-p\", \"-sS\", \"--proxy\", l.URL, srv.URL+\"/bobo\")\n\tvar out bytes.Buffer\n\tcmd.Stdout = &out\n\tif err := cmd.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif output := out.String(); output != \"bobo\" {\n\t\tt.Error(\"Expected bobo, got\", output)\n\t}\n\tif !called {\n\t\tt.Error(\"handler not called\")\n\t}\n}\n\nfunc TestSelfRequest(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\t_, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\tif !strings.Contains(string(getOrFail(t, l.URL, &http.Client{})), \"non-proxy\") {\n\t\tt.Fatal(\"non proxy requests should fail\")\n\t}\n}\n\nfunc TestHasGoproxyCA(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\ts := httptest.NewServer(proxy)\n\n\tproxyUrl, _ := url.Parse(s.URL)\n\tgoproxyCA := x509.NewCertPool()\n\tgoproxyCA.AddCert(goproxy.GoproxyCa.Leaf)\n\n\ttr := &http.Transport{TLSClientConfig: &tls.Config{RootCAs: goproxyCA}, Proxy: http.ProxyURL(proxyUrl)}\n\tclient := &http.Client{Transport: tr}\n\n\tif resp := string(getOrFail(t, https.URL+\"/bobo\", client)); resp != \"bobo\" {\n\t\tt.Error(\"Wrong response when mitm\", resp, \"expected bobo\")\n\t}\n}\n\ntype TestCertStorage struct {\n\tcerts  map[string]*tls.Certificate\n\thits   int\n\tmisses int\n}\n\nfunc (tcs *TestCertStorage) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {\n\tvar cert *tls.Certificate\n\tvar err error\n\tcert, ok := tcs.certs[hostname]\n\tif ok {\n\t\tlog.Printf(\"hit %v\\n\", cert == nil)\n\t\ttcs.hits++\n\t} else {\n\t\tcert, err = gen()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlog.Printf(\"miss %v\\n\", cert == nil)\n\t\ttcs.certs[hostname] = cert\n\t\ttcs.misses++\n\t}\n\treturn cert, err\n}\n\nfunc (tcs *TestCertStorage) statHits() int {\n\treturn tcs.hits\n}\n\nfunc (tcs *TestCertStorage) statMisses() int {\n\treturn tcs.misses\n}\n\nfunc newTestCertStorage() *TestCertStorage {\n\ttcs := &TestCertStorage{}\n\ttcs.certs = make(map[string]*tls.Certificate)\n\n\treturn tcs\n}\n\nfunc TestProxyWithCertStorage(t *testing.T) {\n\ttcs := newTestCertStorage()\n\tt.Logf(\"TestProxyWithCertStorage started\")\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.CertStore = tcs\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\treq.URL.Path = \"/bobo\"\n\t\treturn req, nil\n\t})\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tresp.Close = true\n\t\treturn resp\n\t})\n\n\ts := httptest.NewServer(proxy)\n\n\tproxyUrl, _ := url.Parse(s.URL)\n\tgoproxyCA := x509.NewCertPool()\n\tgoproxyCA.AddCert(goproxy.GoproxyCa.Leaf)\n\n\ttr := &http.Transport{TLSClientConfig: &tls.Config{RootCAs: goproxyCA}, Proxy: http.ProxyURL(proxyUrl)}\n\tclient := &http.Client{Transport: tr}\n\n\tif resp := string(getOrFail(t, https.URL+\"/bobo\", client)); resp != \"bobo\" {\n\t\tt.Error(\"Wrong response when mitm\", resp, \"expected bobo\")\n\t}\n\n\tif tcs.statHits() != 0 {\n\t\tt.Fatalf(\"Expected 0 cache hits, got %d\", tcs.statHits())\n\t}\n\tif tcs.statMisses() != 1 {\n\t\tt.Fatalf(\"Expected 1 cache miss, got %d\", tcs.statMisses())\n\t}\n\n\t// Another round - this time the certificate can be loaded\n\tif resp := string(getOrFail(t, https.URL+\"/bobo\", client)); resp != \"bobo\" {\n\t\tt.Error(\"Wrong response when mitm\", resp, \"expected bobo\")\n\t}\n\n\tif tcs.statHits() != 1 {\n\t\tt.Fatalf(\"Expected 1 cache hit, got %d\", tcs.statHits())\n\t}\n\tif tcs.statMisses() != 1 {\n\t\tt.Fatalf(\"Expected 1 cache miss, got %d\", tcs.statMisses())\n\t}\n}\n\nfunc TestHttpsMitmURLRewrite(t *testing.T) {\n\tscheme := \"https\"\n\n\ttestCases := []struct {\n\t\tHost      string\n\t\tRawPath   string\n\t\tAddOpaque bool\n\t}{\n\t\t{\n\t\t\tHost:      \"example.com\",\n\t\t\tRawPath:   \"/blah/v1/data/realtime\",\n\t\t\tAddOpaque: true,\n\t\t},\n\t\t{\n\t\t\tHost:    \"example.com:443\",\n\t\t\tRawPath: \"/blah/v1/data/realtime?encodedURL=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile\",\n\t\t},\n\t\t{\n\t\t\tHost:    \"example.com:443\",\n\t\t\tRawPath: \"/blah/v1/data/realtime?unencodedURL=https://www.googleapis.com/auth/userinfo.profile\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tproxy := goproxy.NewProxyHttpServer()\n\t\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\n\t\tproxy.OnRequest(goproxy.DstHostIs(tc.Host)).DoFunc(\n\t\t\tfunc(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t\t\treturn nil, goproxy.TextResponse(req, \"Dummy response\")\n\t\t\t})\n\n\t\tclient, s := oneShotProxy(proxy)\n\t\tdefer s.Close()\n\n\t\tfullURL := scheme + \"://\" + tc.Host + tc.RawPath\n\t\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fullURL, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif tc.AddOpaque {\n\t\t\treq.URL.Scheme = scheme\n\t\t\treq.URL.Opaque = \"//\" + tc.Host + tc.RawPath\n\t\t}\n\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tb, err := io.ReadAll(resp.Body)\n\t\t_ = resp.Body.Close()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tbody := string(b)\n\t\tif body != \"Dummy response\" {\n\t\t\tt.Errorf(\"Expected proxy to return dummy body content but got %s\", body)\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusAccepted {\n\t\t\tt.Errorf(\"Expected status: %d, got: %d\", http.StatusAccepted, resp.StatusCode)\n\t\t}\n\t}\n}\n\nfunc TestSimpleHttpRequest(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\n\tvar server *http.Server\n\tgo func() {\n\t\tt.Log(\"serving end proxy server at localhost:5000\")\n\t\tserver = &http.Server{\n\t\t\tAddr:              \"localhost:5000\",\n\t\t\tHandler:           proxy,\n\t\t\tReadHeaderTimeout: 10 * time.Second,\n\t\t}\n\t\terr := server.ListenAndServe()\n\t\tif err == nil {\n\t\t\tt.Error(\"Error shutdown should always return error\", err)\n\t\t}\n\t}()\n\n\ttime.Sleep(1 * time.Second)\n\tu, _ := url.Parse(\"http://localhost:5000\")\n\ttr := &http.Transport{\n\t\tProxy: http.ProxyURL(u),\n\t\t// Disable HTTP/2.\n\t\tTLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),\n\t}\n\tclient := http.Client{Transport: tr}\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, \"http://example.com\", nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tt.Error(\"Error requesting http site\", err)\n\t} else if resp.StatusCode != http.StatusOK {\n\t\tt.Error(\"Non-OK status requesting http site\", err)\n\t}\n\n\treq, err = http.NewRequestWithContext(context.Background(), http.MethodGet, \"http://example.invalid\", nil)\n\tif err != nil {\n\t\tt.Fatal(\"Cannot create request\", err)\n\t}\n\n\tresp, _ = client.Do(req)\n\tif resp == nil {\n\t\tt.Error(\"No response requesting invalid http site\")\n\t}\n\n\treturnNil := func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\treturn nil\n\t}\n\tproxy.OnResponse(goproxy.UrlMatches(regexp.MustCompile(\".*\"))).DoFunc(returnNil)\n\n\tresp, _ = client.Do(req)\n\tif resp == nil {\n\t\tt.Error(\"No response requesting invalid http site\")\n\t}\n\n\t_ = server.Shutdown(context.TODO())\n}\n\nfunc TestResponseContentLength(t *testing.T) {\n\t// target server\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write([]byte(\"hello world\"))\n\t}))\n\tdefer srv.Close()\n\n\t// proxy server\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tbuf := &bytes.Buffer{}\n\t\tbuf.WriteString(\"change\")\n\t\tresp.Body = io.NopCloser(buf)\n\t\treturn resp\n\t})\n\tproxySrv := httptest.NewServer(proxy)\n\tdefer proxySrv.Close()\n\n\t// send request\n\tclient := &http.Client{}\n\tclient.Transport = &http.Transport{\n\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\treturn url.Parse(proxySrv.URL)\n\t\t},\n\t}\n\treq, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\tresp, _ := client.Do(req)\n\n\tbody, _ := io.ReadAll(resp.Body)\n\t_ = resp.Body.Close()\n\n\tif int64(len(body)) != resp.ContentLength {\n\t\tt.Logf(\"response body: %s\", string(body))\n\t\tt.Logf(\"response body Length: %d\", len(body))\n\t\tt.Logf(\"response Content-Length: %d\", resp.ContentLength)\n\t\tt.Fatalf(\"Wrong response Content-Length.\")\n\t}\n}\n\nfunc TestMITMResponseHTTP2MissingContentLength(t *testing.T) {\n\thandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tif f, ok := w.(http.Flusher); ok {\n\t\t\t// Force missing Content-Length\n\t\t\tf.Flush()\n\t\t}\n\t\t_, _ = w.Write([]byte(\"HTTP/2 response\"))\n\t})\n\n\t// Explicitly make an HTTP/2 server\n\tsrv := httptest.NewUnstartedServer(handler)\n\tsrv.EnableHTTP2 = true\n\tsrv.StartTLS()\n\tdefer srv.Close()\n\n\t// proxy server\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\t// Connection between the proxy client and the proxy server\n\t\tassert.Equal(t, \"HTTP/1.1\", req.Proto)\n\t\treturn req, nil\n\t})\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\t// Connection between the proxy server and the origin\n\t\tassert.Equal(t, \"HTTP/2.0\", resp.Proto)\n\t\treturn resp\n\t})\n\n\t// Configure proxy transport to use HTTP/2 to communicate with the server\n\tproxy.Tr = &http.Transport{\n\t\tForceAttemptHTTP2: true,\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t\tNextProtos:         []string{\"h2\"},\n\t\t},\n\t}\n\n\tproxySrv := httptest.NewServer(proxy)\n\tdefer proxySrv.Close()\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(proxySrv.URL)\n\t\t\t},\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t},\n\t\t},\n\t}\n\n\treq, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\n\tbody, _ := io.ReadAll(resp.Body)\n\t_ = resp.Body.Close()\n\n\tassert.EqualValues(t, -1, resp.ContentLength)\n\tassert.Equal(t, []string{\"chunked\"}, resp.TransferEncoding)\n\tassert.Len(t, body, 15)\n}\n\nfunc TestMITMResponseContentLength(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\t// Don't touch the body at all\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, https.URL+\"/bobo\", nil)\n\trequire.NoError(t, err)\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\t_ = resp.Body.Close()\n\n\tassert.EqualValues(t, len(body), resp.ContentLength)\n}\n\nfunc TestMITMEmptyBody(t *testing.T) {\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write(nil)\n\t}))\n\tdefer srv.Close()\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\trequire.NoError(t, err)\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\t_ = resp.Body.Close()\n\n\tassert.EqualValues(t, 0, resp.ContentLength)\n}\n\nfunc TestMITMOverwriteAlreadyEmptyBody(t *testing.T) {\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write(nil)\n\t}))\n\tdefer srv.Close()\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tassert.EqualValues(t, 0, resp.ContentLength)\n\t\tresp.Body = io.NopCloser(bytes.NewReader(nil))\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\trequire.NoError(t, err)\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\t_ = resp.Body.Close()\n\n\tassert.EqualValues(t, 0, resp.ContentLength)\n}\n\nfunc TestMITMOverwriteBodyToEmpty(t *testing.T) {\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write([]byte(\"test\"))\n\t}))\n\tdefer srv.Close()\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {\n\t\tassert.EqualValues(t, 4, resp.ContentLength)\n\t\tresp.Body = io.NopCloser(bytes.NewReader(nil))\n\t\treturn resp\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\trequire.NoError(t, err)\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\t_ = resp.Body.Close()\n\n\tassert.EqualValues(t, 0, resp.ContentLength)\n}\n\nfunc TestMITMRequestCancel(t *testing.T) {\n\t// target server\n\tsrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write([]byte(\"hello world\"))\n\t}))\n\tdefer srv.Close()\n\n\t// proxy server\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tvar request *http.Request\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\trequest = req\n\t\treturn req, nil\n\t})\n\tproxySrv := httptest.NewServer(proxy)\n\tdefer proxySrv.Close()\n\n\t// send request\n\tclient := &http.Client{}\n\tclient.Transport = &http.Transport{\n\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\treturn url.Parse(proxySrv.URL)\n\t\t},\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil)\n\trequire.NoError(t, err)\n\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\t_ = resp.Body.Close()\n\n\tassert.Equal(t, \"hello world\", string(body))\n\tassert.NotNil(t, request)\n\n\tselect {\n\tcase _, ok := <-request.Context().Done():\n\t\tassert.False(t, ok)\n\tdefault:\n\t\tassert.Fail(t, \"request hasn't been cancelled\")\n\t}\n}\n\nfunc TestNewResponseProtoVersion(t *testing.T) {\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, \"https://example.com/\", nil)\n\trequire.NoError(t, err)\n\n\tresp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, \"blocked\")\n\n\tassert.Equal(t, \"HTTP/1.1\", resp.Proto)\n\tassert.Equal(t, 1, resp.ProtoMajor)\n\tassert.Equal(t, 1, resp.ProtoMinor)\n\n\tvar buf bytes.Buffer\n\terr = resp.Write(&buf)\n\trequire.NoError(t, err)\n\n\tline, err := buf.ReadString('\\n')\n\trequire.NoError(t, err)\n\tassert.True(t, strings.HasPrefix(line, \"HTTP/1.1 403\"), \"expected HTTP/1.1 status line, got: %s\", line)\n}\n\nfunc TestNewResponseMitmWrite(t *testing.T) {\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {\n\t\treturn nil, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, \"blocked\")\n\t})\n\n\tclient, l := oneShotProxy(proxy)\n\tdefer l.Close()\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, https.URL+\"/anything\", nil)\n\trequire.NoError(t, err)\n\n\tresp, err := client.Do(req)\n\trequire.NoError(t, err)\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, http.StatusForbidden, resp.StatusCode)\n\tassert.Equal(t, \"blocked\", string(body))\n}\n\nfunc TestPersistentMitmRequest(t *testing.T) {\n\trequestCount := 0\n\tbackend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintf(w, \"Request number %d\", requestCount)\n\t\trequestCount++\n\t}))\n\tdefer backend.Close()\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\tproxyServer := httptest.NewServer(proxy)\n\tdefer proxyServer.Close()\n\n\tproxyURL, err := url.Parse(proxyServer.URL)\n\trequire.NoError(t, err)\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy: http.ProxyURL(proxyURL),\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t},\n\t\t\t// We disable HTTP/2 to make sure to test HTTP/1.1 Keep-Alive\n\t\t\tForceAttemptHTTP2: false,\n\t\t},\n\t}\n\n\tfor i := 0; i < 2; i++ {\n\t\tvar connReused bool\n\t\ttrace := &httptrace.ClientTrace{\n\t\t\tGotConn: func(info httptrace.GotConnInfo) {\n\t\t\t\tconnReused = info.Reused\n\t\t\t},\n\t\t}\n\n\t\tctx := httptrace.WithClientTrace(context.Background(), trace)\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, backend.URL, nil)\n\t\trequire.NoError(t, err)\n\n\t\tresp, err := client.Do(req)\n\t\trequire.NoError(t, err)\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\trequire.NoError(t, err)\n\t\t_ = resp.Body.Close()\n\n\t\tassert.Equal(t, fmt.Sprintf(\"Request number %d\", i), string(body))\n\n\t\t// First request creates the connection, second request reuses it\n\t\tswitch i {\n\t\tcase 0:\n\t\t\tassert.False(t, connReused)\n\t\tcase 1:\n\t\t\tassert.True(t, connReused)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "regretable/regretreader.go",
    "content": "package regretable\n\nimport (\n\t\"io\"\n)\n\n// Reader in regretable package will allow you to read from a reader,\n// and then to \"regret\" reading it, and push back everything you've read.\n// For example:\n//\n//\trb := NewRegretableReader(bytes.NewBuffer([]byte{1,2,3}))\n//\tvar b = make([]byte,1)\n//\trb.Read(b) // b[0] = 1\n//\trb.Regret()\n//\tioutil.ReadAll(rb.Read) // returns []byte{1,2,3},nil\ntype Reader struct {\n\treader   io.Reader\n\toverflow bool\n\tr, w     int\n\tbuf      []byte\n}\n\nconst _defaultBufferSize = 500\n\n// The next read from the RegretableReader will be as if the underlying reader\n// was never read (or from the last point forget is called).\nfunc (rb *Reader) Regret() {\n\tif rb.overflow {\n\t\tpanic(\"regretting after overflow makes no sense\")\n\t}\n\trb.r = 0\n}\n\n// Will \"forget\" everything read so far.\n//\n//\trb := NewRegretableReader(bytes.NewBuffer([]byte{1,2,3}))\n//\tvar b = make([]byte,1)\n//\trb.Read(b) // b[0] = 1\n//\trb.Forget()\n//\trb.Read(b) // b[0] = 2\n//\trb.Regret()\n//\tioutil.ReadAll(rb.Read) // returns []byte{2,3},nil\nfunc (rb *Reader) Forget() {\n\tif rb.overflow {\n\t\tpanic(\"forgetting after overflow makes no sense\")\n\t}\n\trb.r = 0\n\trb.w = 0\n}\n\n// initialize a RegretableReader with underlying reader r, whose buffer is size bytes long.\nfunc NewRegretableReaderSize(r io.Reader, size int) *Reader {\n\treturn &Reader{reader: r, buf: make([]byte, size)}\n}\n\n// initialize a RegretableReader with underlying reader r.\nfunc NewRegretableReader(r io.Reader) *Reader {\n\treturn NewRegretableReaderSize(r, _defaultBufferSize)\n}\n\n// reads from the underlying reader. Will buffer all input until Regret is called.\nfunc (rb *Reader) Read(p []byte) (n int, err error) {\n\tif rb.overflow {\n\t\treturn rb.reader.Read(p)\n\t}\n\tif rb.r < rb.w {\n\t\tn = copy(p, rb.buf[rb.r:rb.w])\n\t\trb.r += n\n\t\treturn\n\t}\n\tn, err = rb.reader.Read(p)\n\tbn := copy(rb.buf[rb.w:], p[:n])\n\trb.w, rb.r = rb.w+bn, rb.w+n\n\tif bn < n {\n\t\trb.overflow = true\n\t}\n\treturn\n}\n\n// ReaderCloser is the same as Reader, but allows closing the underlying reader.\ntype ReaderCloser struct {\n\tReader\n\tc io.Closer\n}\n\n// initialize a RegretableReaderCloser with underlying readCloser rc.\nfunc NewRegretableReaderCloser(rc io.ReadCloser) *ReaderCloser {\n\treturn &ReaderCloser{*NewRegretableReader(rc), rc}\n}\n\n// initialize a RegretableReaderCloser with underlying readCloser rc.\nfunc NewRegretableReaderCloserSize(rc io.ReadCloser, size int) *ReaderCloser {\n\treturn &ReaderCloser{*NewRegretableReaderSize(rc, size), rc}\n}\n\n// Closes the underlying readCloser, you cannot regret after closing the stream.\nfunc (rbc *ReaderCloser) Close() error {\n\treturn rbc.c.Close()\n}\n"
  },
  {
    "path": "regretable/regretreader_test.go",
    "content": "package regretable_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/elazarl/goproxy/regretable\"\n)\n\nfunc assertEqual(t *testing.T, expected, actual string) {\n\tt.Helper()\n\tif expected != actual {\n\t\tt.Fatal(\"Expected\", expected, \"actual\", actual)\n\t}\n}\n\nfunc assertReadAll(t *testing.T, r io.Reader) string {\n\tt.Helper()\n\ts, err := io.ReadAll(r)\n\tif err != nil {\n\t\tt.Fatal(\"error when reading\", err)\n\t}\n\treturn string(s)\n}\n\nfunc TestRegretableReader(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmb := regretable.NewRegretableReader(buf)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\tfivebytes := make([]byte, 5)\n\t_, _ = mb.Read(fivebytes)\n\tmb.Regret()\n\n\ts, _ := io.ReadAll(mb)\n\tif string(s) != word {\n\t\tt.Errorf(\"Uncommitted read is gone, [%d,%d] actual '%v' expected '%v'\\n\", len(s), len(word), string(s), word)\n\t}\n}\n\nfunc TestRegretableEmptyRead(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmb := regretable.NewRegretableReader(buf)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\tzero := make([]byte, 0)\n\t_, _ = mb.Read(zero)\n\tmb.Regret()\n\n\ts, err := io.ReadAll(mb)\n\tif string(s) != word {\n\t\tt.Error(\"Uncommitted read is gone, actual:\", string(s), \"expected:\", word, \"err:\", err)\n\t}\n}\n\nfunc TestRegretableAlsoEmptyRead(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmb := regretable.NewRegretableReader(buf)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\tone := make([]byte, 1)\n\tzero := make([]byte, 0)\n\tfive := make([]byte, 5)\n\t_, _ = mb.Read(one)\n\t_, _ = mb.Read(zero)\n\t_, _ = mb.Read(five)\n\tmb.Regret()\n\n\ts, _ := io.ReadAll(mb)\n\tif string(s) != word {\n\t\tt.Error(\"Uncommitted read is gone\", string(s), \"expected\", word)\n\t}\n}\n\nfunc TestRegretableRegretBeforeRead(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmb := regretable.NewRegretableReader(buf)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\tfive := make([]byte, 5)\n\tmb.Regret()\n\t_, _ = mb.Read(five)\n\n\ts, err := io.ReadAll(mb)\n\tif string(s) != \"678\" {\n\t\tt.Error(\"Uncommitted read is gone\", string(s), len(string(s)), \"expected\", \"678\", len(\"678\"), \"err:\", err)\n\t}\n}\n\nfunc TestRegretableFullRead(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmb := regretable.NewRegretableReader(buf)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\ttwenty := make([]byte, 20)\n\t_, _ = mb.Read(twenty)\n\tmb.Regret()\n\n\ts, _ := io.ReadAll(mb)\n\tif string(s) != word {\n\t\tt.Error(\"Uncommitted read is gone\", string(s), len(string(s)), \"expected\", word, len(word))\n\t}\n}\n\nfunc TestRegretableRegretTwice(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmb := regretable.NewRegretableReader(buf)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\tassertEqual(t, word, assertReadAll(t, mb))\n\tmb.Regret()\n\tassertEqual(t, word, assertReadAll(t, mb))\n\tmb.Regret()\n\tassertEqual(t, word, assertReadAll(t, mb))\n}\n\ntype CloseCounter struct {\n\tr      io.Reader\n\tclosed int\n}\n\nfunc (cc *CloseCounter) Read(b []byte) (int, error) {\n\treturn cc.r.Read(b)\n}\n\nfunc (cc *CloseCounter) Close() error {\n\tcc.closed++\n\treturn nil\n}\n\nfunc TestRegretableCloserSizeRegrets(t *testing.T) {\n\tdefer func() {\n\t\tr := recover()\n\t\tif r == nil {\n\t\t\tt.Error(\"Did not panic when regretting overread buffer:\", r)\n\t\t}\n\n\t\tstringValue, ok := r.(string)\n\t\tif !ok || !strings.Contains(stringValue, \"regret\") {\n\t\t\tt.Error(\"Invalid panic value when regretting overread buffer:\", r)\n\t\t}\n\t}()\n\tbuf := new(bytes.Buffer)\n\tbuf.WriteString(\"123456\")\n\tmb := regretable.NewRegretableReaderCloserSize(io.NopCloser(buf), 3)\n\t_, _ = mb.Read(make([]byte, 4))\n\tmb.Regret()\n}\n\nfunc TestRegretableCloserRegretsClose(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tcc := &CloseCounter{buf, 0}\n\tmb := regretable.NewRegretableReaderCloser(cc)\n\tword := \"12345678\"\n\tbuf.WriteString(word)\n\n\t_, _ = mb.Read([]byte{0})\n\t_ = mb.Close()\n\tif cc.closed != 1 {\n\t\tt.Error(\"RegretableReaderCloser ignores Close\")\n\t}\n\tmb.Regret()\n\t_ = mb.Close()\n\tif cc.closed != 2 {\n\t\tt.Error(\"RegretableReaderCloser does ignore Close after regret\")\n\t}\n\t// TODO(elazar): return an error if client issues Close more than once after regret\n}\n"
  },
  {
    "path": "responses.go",
    "content": "package goproxy\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n)\n\n// Will generate a valid http response to the given request the response will have\n// the given contentType, and http status.\n// Typical usage, refuse to process requests to local addresses:\n//\n//\tproxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) {\n//\t\treturn nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized,\n//\t\t\t`<!doctype html><html><head><title>Can't use proxy for local addresses</title></head><body/></html>`)\n//\t})\nfunc NewResponse(r *http.Request, contentType string, status int, body string) *http.Response {\n\tresp := &http.Response{}\n\tresp.Request = r\n\tresp.TransferEncoding = r.TransferEncoding\n\tresp.Header = make(http.Header)\n\tresp.Header.Add(\"Content-Type\", contentType)\n\tresp.StatusCode = status\n\tresp.Status = http.StatusText(status)\n\tresp.Proto = \"HTTP/1.1\"\n\tresp.ProtoMajor = 1\n\tresp.ProtoMinor = 1\n\tbuf := bytes.NewBufferString(body)\n\tresp.ContentLength = int64(buf.Len())\n\tresp.Body = io.NopCloser(buf)\n\treturn resp\n}\n\nconst (\n\tContentTypeText = \"text/plain\"\n\tContentTypeHtml = \"text/html\"\n)\n\n// Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text).\nfunc TextResponse(r *http.Request, text string) *http.Response {\n\treturn NewResponse(r, ContentTypeText, http.StatusAccepted, text)\n}\n"
  },
  {
    "path": "transport/roundtripper.go",
    "content": "package transport\n\nimport \"net/http\"\n\ntype RoundTripper interface {\n\t// RoundTrip executes a single HTTP transaction, returning\n\t// the Response for the request req.  RoundTrip should not\n\t// attempt to interpret the response.  In particular,\n\t// RoundTrip must return err == nil if it obtained a response,\n\t// regardless of the response's HTTP status code.  A non-nil\n\t// err should be reserved for failure to obtain a response.\n\t// Similarly, RoundTrip should not attempt to handle\n\t// higher-level protocol details such as redirects,\n\t// authentication, or cookies.\n\t//\n\t// RoundTrip should not modify the request, except for\n\t// consuming the Body.  The request's URL and Header fields\n\t// are guaranteed to be initialized.\n\tRoundTrip(*http.Request) (*http.Response, error)\n\tDetailedRoundTrip(*http.Request) (*RoundTripDetails, *http.Response, error)\n}\n"
  },
  {
    "path": "transport/transport.go",
    "content": "// Copyright 2011 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// HTTP client implementation. See RFC 2616.\n//\n// This is the low-level Transport implementation of RoundTripper.\n// The high-level interface is in client.go.\n\n// This file is DEPRECATED and keep solely for backward compatibility.\n\npackage transport\n\nimport (\n\t\"bufio\"\n\t\"compress/gzip\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// DefaultTransport is the default implementation of Transport and is\n// used by DefaultClient.  It establishes a new network connection for\n// each call to Do and uses HTTP proxies as directed by the\n// $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy)\n// environment variables.\nvar DefaultTransport RoundTripper = &Transport{Proxy: ProxyFromEnvironment}\n\n// DefaultMaxIdleConnsPerHost is the default value of Transport's\n// MaxIdleConnsPerHost.\nconst DefaultMaxIdleConnsPerHost = 2\n\n// Transport is an implementation of RoundTripper that supports http,\n// https, and http proxies (for either http or https with CONNECT).\n// Transport can also cache connections for future re-use.\ntype Transport struct {\n\tlk       sync.Mutex\n\tidleConn map[string][]*persistConn\n\taltProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper\n\n\t// TODO: tunable on global max cached connections\n\t// TODO: tunable on timeout on cached connections\n\t// TODO: optional pipelining\n\n\t// Proxy specifies a function to return a proxy for a given\n\t// Request. If the function returns a non-nil error, the\n\t// request is aborted with the provided error.\n\t// If Proxy is nil or returns a nil *URL, no proxy is used.\n\tProxy func(*http.Request) (*url.URL, error)\n\n\t// Dial specifies the dial function for creating TCP\n\t// connections.\n\t// If Dial is nil, net.Dial is used.\n\tDial func(net, addr string) (c net.Conn, err error)\n\n\t// TLSClientConfig specifies the TLS configuration to use with\n\t// tls.Client. If nil, the default configuration is used.\n\tTLSClientConfig *tls.Config\n\n\tDisableKeepAlives  bool\n\tDisableCompression bool\n\n\t// MaxIdleConnsPerHost, if non-zero, controls the maximum idle\n\t// (keep-alive) to keep to keep per-host.  If zero,\n\t// DefaultMaxIdleConnsPerHost is used.\n\tMaxIdleConnsPerHost int\n}\n\n// ProxyFromEnvironment returns the URL of the proxy to use for a\n// given request, as indicated by the environment variables\n// $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy).\n// An error is returned if the proxy environment is invalid.\n// A nil URL and nil error are returned if no proxy is defined in the\n// environment, or a proxy should not be used for the given request.\nfunc ProxyFromEnvironment(req *http.Request) (*url.URL, error) {\n\tproxy := getenvEitherCase(\"HTTP_PROXY\")\n\tif proxy == \"\" {\n\t\treturn nil, nil\n\t}\n\tif !useProxy(canonicalAddr(req.URL)) {\n\t\treturn nil, nil\n\t}\n\tproxyURL, err := url.Parse(proxy)\n\tif err != nil || proxyURL.Scheme == \"\" {\n\t\tproxyURL, err = url.Parse(\"http://\" + proxy)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid proxy address %q: %w\", proxy, err)\n\t}\n\treturn proxyURL, nil\n}\n\n// ProxyURL returns a proxy function (for use in a Transport)\n// that always returns the same URL.\nfunc ProxyURL(fixedURL *url.URL) func(*http.Request) (*url.URL, error) {\n\treturn func(*http.Request) (*url.URL, error) {\n\t\treturn fixedURL, nil\n\t}\n}\n\n// transportRequest is a wrapper around a *Request that adds\n// optional extra headers to write.\ntype transportRequest struct {\n\t*http.Request             // original request, not to be mutated\n\textra         http.Header // extra headers to write, or nil\n}\n\nfunc (tr *transportRequest) extraHeaders() http.Header {\n\tif tr.extra == nil {\n\t\ttr.extra = make(http.Header)\n\t}\n\treturn tr.extra\n}\n\ntype RoundTripDetails struct {\n\tHost    string\n\tTCPAddr *net.TCPAddr\n\tIsProxy bool\n\tError   error\n}\n\nfunc (t *Transport) DetailedRoundTrip(req *http.Request) (details *RoundTripDetails, resp *http.Response, err error) {\n\tif req.URL == nil {\n\t\treturn nil, nil, errors.New(\"http: nil Request.URL\")\n\t}\n\tif req.Header == nil {\n\t\treturn nil, nil, errors.New(\"http: nil Request.Header\")\n\t}\n\tif req.URL.Scheme != \"http\" && req.URL.Scheme != \"https\" {\n\t\tt.lk.Lock()\n\t\tvar rt RoundTripper\n\t\tif t.altProto != nil {\n\t\t\trt = t.altProto[req.URL.Scheme]\n\t\t}\n\t\tt.lk.Unlock()\n\t\tif rt == nil {\n\t\t\treturn nil, nil, &badStringError{\"unsupported protocol scheme\", req.URL.Scheme}\n\t\t}\n\t\treturn rt.DetailedRoundTrip(req)\n\t}\n\ttreq := &transportRequest{Request: req}\n\tcm, err := t.connectMethodForRequest(treq)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// Get the cached or newly-created connection to either the\n\t// host (for http or https), the http proxy, or the http proxy\n\t// pre-CONNECTed to https server.  In any case, we'll be ready\n\t// to send it requests.\n\tpconn, err := t.getConn(cm)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tresp, err = pconn.roundTrip(treq)\n\treturn &RoundTripDetails{pconn.host, pconn.ip, pconn.isProxy, err}, resp, err\n}\n\n// RoundTrip implements the RoundTripper interface.\nfunc (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {\n\t_, resp, err = t.DetailedRoundTrip(req)\n\treturn\n}\n\n// RegisterProtocol registers a new protocol with scheme.\n// The Transport will pass requests using the given scheme to rt.\n// It is rt's responsibility to simulate HTTP request semantics.\n//\n// RegisterProtocol can be used by other packages to provide\n// implementations of protocol schemes like \"ftp\" or \"file\".\nfunc (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) {\n\tif scheme == \"http\" || scheme == \"https\" {\n\t\tpanic(\"protocol \" + scheme + \" already registered\")\n\t}\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\tif t.altProto == nil {\n\t\tt.altProto = make(map[string]RoundTripper)\n\t}\n\tif _, exists := t.altProto[scheme]; exists {\n\t\tpanic(\"protocol \" + scheme + \" already registered\")\n\t}\n\tt.altProto[scheme] = rt\n}\n\n// CloseIdleConnections closes any connections which were previously\n// connected from previous requests but are now sitting idle in\n// a \"keep-alive\" state. It does not interrupt any connections currently\n// in use.\nfunc (t *Transport) CloseIdleConnections() {\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\tif t.idleConn == nil {\n\t\treturn\n\t}\n\tfor _, conns := range t.idleConn {\n\t\tfor _, pconn := range conns {\n\t\t\tpconn.close()\n\t\t}\n\t}\n\tt.idleConn = make(map[string][]*persistConn)\n}\n\n//\n// Private implementation past this point.\n//\n\nfunc getenvEitherCase(k string) string {\n\tif v := os.Getenv(strings.ToUpper(k)); v != \"\" {\n\t\treturn v\n\t}\n\treturn os.Getenv(strings.ToLower(k))\n}\n\nfunc (t *Transport) connectMethodForRequest(treq *transportRequest) (*connectMethod, error) {\n\tcm := &connectMethod{\n\t\ttargetScheme: treq.URL.Scheme,\n\t\ttargetAddr:   canonicalAddr(treq.URL),\n\t}\n\tif t.Proxy != nil {\n\t\tvar err error\n\t\tcm.proxyURL, err = t.Proxy(treq.Request)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn cm, nil\n}\n\n// proxyAuth returns the Proxy-Authorization header to set\n// on requests, if applicable.\nfunc (cm *connectMethod) proxyAuth() string {\n\tif cm.proxyURL == nil {\n\t\treturn \"\"\n\t}\n\tif u := cm.proxyURL.User; u != nil {\n\t\treturn \"Basic \" + base64.URLEncoding.EncodeToString([]byte(u.String()))\n\t}\n\treturn \"\"\n}\n\n// putIdleConn adds pconn to the list of idle persistent connections awaiting\n// a new request.\n// If pconn is no longer needed or not in a good state, putIdleConn\n// returns false.\nfunc (t *Transport) putIdleConn(pconn *persistConn) bool {\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\tif t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 {\n\t\tpconn.close()\n\t\treturn false\n\t}\n\tif pconn.isBroken() {\n\t\treturn false\n\t}\n\tkey := pconn.cacheKey\n\tmaxIdleConns := t.MaxIdleConnsPerHost\n\tif maxIdleConns == 0 {\n\t\tmaxIdleConns = DefaultMaxIdleConnsPerHost\n\t}\n\tif len(t.idleConn[key]) >= maxIdleConns {\n\t\tpconn.close()\n\t\treturn false\n\t}\n\tt.idleConn[key] = append(t.idleConn[key], pconn)\n\treturn true\n}\n\nfunc (t *Transport) getIdleConn(cm *connectMethod) (pconn *persistConn) {\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\tif t.idleConn == nil {\n\t\tt.idleConn = make(map[string][]*persistConn)\n\t}\n\tkey := cm.String()\n\tfor {\n\t\tpconns, ok := t.idleConn[key]\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\tif len(pconns) == 1 {\n\t\t\tpconn = pconns[0]\n\t\t\tdelete(t.idleConn, key)\n\t\t} else {\n\t\t\t// 2 or more cached connections; pop last\n\t\t\t// TODO: queue?\n\t\t\tpconn = pconns[len(pconns)-1]\n\t\t\tt.idleConn[key] = pconns[0 : len(pconns)-1]\n\t\t}\n\t\tif !pconn.isBroken() {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (t *Transport) dial(network, addr string) (c net.Conn, raddr string, ip *net.TCPAddr, err error) {\n\tif t.Dial != nil {\n\t\tip, err = net.ResolveTCPAddr(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tc, err = t.Dial(network, addr)\n\t\traddr = addr\n\t\treturn\n\t}\n\taddri, err := net.ResolveTCPAddr(\"tcp\", addr)\n\tif err != nil {\n\t\treturn\n\t}\n\tc, err = net.DialTCP(\"tcp\", nil, addri)\n\traddr = addr\n\tip = addri\n\treturn\n}\n\n// getConn dials and creates a new persistConn to the target as\n// specified in the connectMethod.  This includes doing a proxy CONNECT\n// and/or setting up TLS.  If this doesn't return an error, the persistConn\n// is ready to write requests to.\nfunc (t *Transport) getConn(cm *connectMethod) (*persistConn, error) {\n\tif pc := t.getIdleConn(cm); pc != nil {\n\t\treturn pc, nil\n\t}\n\n\tconn, raddr, ip, err := t.dial(\"tcp\", cm.addr())\n\tif err != nil {\n\t\tif cm.proxyURL != nil {\n\t\t\terr = fmt.Errorf(\"http: error connecting to proxy %s: %w\", cm.proxyURL, err)\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tpa := cm.proxyAuth()\n\n\tpconn := &persistConn{\n\t\tt:        t,\n\t\tcacheKey: cm.String(),\n\t\tconn:     conn,\n\t\treqch:    make(chan requestAndChan, 50),\n\t\thost:     raddr,\n\t\tip:       ip,\n\t}\n\n\tswitch {\n\tcase cm.proxyURL == nil:\n\t\t// Do nothing.\n\tcase cm.targetScheme == \"http\":\n\t\tpconn.isProxy = true\n\t\tif pa != \"\" {\n\t\t\tpconn.mutateHeaderFunc = func(h http.Header) {\n\t\t\t\th.Set(\"Proxy-Authorization\", pa)\n\t\t\t}\n\t\t}\n\tcase cm.targetScheme == \"https\":\n\t\tconnectReq := &http.Request{\n\t\t\tMethod: http.MethodConnect,\n\t\t\tURL:    &url.URL{Opaque: cm.targetAddr},\n\t\t\tHost:   cm.targetAddr,\n\t\t\tHeader: make(http.Header),\n\t\t}\n\t\tif pa != \"\" {\n\t\t\tconnectReq.Header.Set(\"Proxy-Authorization\", pa)\n\t\t}\n\t\t_ = connectReq.Write(conn)\n\n\t\t// Read response.\n\t\t// Okay to use and discard buffered reader here, because\n\t\t// TLS server will not speak until spoken to.\n\t\tbr := bufio.NewReader(conn)\n\t\tresp, err := http.ReadResponse(br, connectReq)\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\tf := strings.SplitN(resp.Status, \" \", 2)\n\t\t\tconn.Close()\n\t\t\treturn nil, errors.New(f[1])\n\t\t}\n\t}\n\n\tif cm.targetScheme == \"https\" {\n\t\t// Initiate TLS and check remote host name against certificate.\n\t\tconn = tls.Client(conn, t.TLSClientConfig)\n\t\ttlsConn, ok := conn.(*tls.Conn)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid TLS connection\")\n\t\t}\n\t\tif err = tlsConn.Handshake(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif t.TLSClientConfig == nil || !t.TLSClientConfig.InsecureSkipVerify {\n\t\t\tif err = tlsConn.VerifyHostname(cm.tlsHost()); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tpconn.conn = conn\n\t}\n\n\tpconn.br = bufio.NewReader(pconn.conn)\n\tpconn.bw = bufio.NewWriter(pconn.conn)\n\tgo pconn.readLoop()\n\treturn pconn, nil\n}\n\n// useProxy returns true if requests to addr should use a proxy,\n// according to the NO_PROXY or no_proxy environment variable.\n// addr is always a canonicalAddr with a host and port.\nfunc useProxy(addr string) bool {\n\tif len(addr) == 0 {\n\t\treturn true\n\t}\n\thost, _, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif host == \"localhost\" {\n\t\treturn false\n\t}\n\tif ip := net.ParseIP(host); ip != nil {\n\t\tif ip.IsLoopback() {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tno_proxy := getenvEitherCase(\"NO_PROXY\")\n\tif no_proxy == \"*\" {\n\t\treturn false\n\t}\n\n\taddr = strings.ToLower(strings.TrimSpace(addr))\n\tif hasPort(addr) {\n\t\taddr = addr[:strings.LastIndex(addr, \":\")]\n\t}\n\n\tfor _, p := range strings.Split(no_proxy, \",\") {\n\t\tp = strings.ToLower(strings.TrimSpace(p))\n\t\tif len(p) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif hasPort(p) {\n\t\t\tp = p[:strings.LastIndex(p, \":\")]\n\t\t}\n\t\tif addr == p || (p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:])) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// connectMethod is the map key (in its String form) for keeping persistent\n// TCP connections alive for subsequent HTTP requests.\n//\n// A connect method may be of the following types:\n//\n// Cache key form                Description\n// -----------------             -------------------------\n// ||http|foo.com                http directly to server, no proxy\n// ||https|foo.com               https directly to server, no proxy\n// http://proxy.com|https|foo.com  http to proxy, then CONNECT to foo.com\n// http://proxy.com|http           http to proxy, http to anywhere after that\n//\n// Note: no support to https to the proxy yet.\ntype connectMethod struct {\n\tproxyURL     *url.URL // nil for no proxy, else full proxy URL\n\ttargetScheme string   // \"http\" or \"https\"\n\ttargetAddr   string   // Not used if proxy + http targetScheme (4th example in table)\n}\n\nfunc (cm *connectMethod) String() string {\n\tproxyStr := \"\"\n\tif cm.proxyURL != nil {\n\t\tproxyStr = cm.proxyURL.String()\n\t}\n\treturn strings.Join([]string{proxyStr, cm.targetScheme, cm.targetAddr}, \"|\")\n}\n\n// addr returns the first hop \"host:port\" to which we need to TCP connect.\nfunc (cm *connectMethod) addr() string {\n\tif cm.proxyURL != nil {\n\t\treturn canonicalAddr(cm.proxyURL)\n\t}\n\treturn cm.targetAddr\n}\n\n// tlsHost returns the host name to match against the peer's\n// TLS certificate.\nfunc (cm *connectMethod) tlsHost() string {\n\th := cm.targetAddr\n\tif hasPort(h) {\n\t\th = h[:strings.LastIndex(h, \":\")]\n\t}\n\treturn h\n}\n\n// persistConn wraps a connection, usually a persistent one\n// (but may be used for non-keep-alive requests as well).\ntype persistConn struct {\n\tt        *Transport\n\tcacheKey string // its connectMethod.String()\n\tconn     net.Conn\n\tbr       *bufio.Reader       // from conn\n\tbw       *bufio.Writer       // to conn\n\treqch    chan requestAndChan // written by roundTrip(); read by readLoop()\n\tisProxy  bool\n\n\t// mutateHeaderFunc is an optional func to modify extra\n\t// headers on each outbound request before it's written. (the\n\t// original Request given to RoundTrip is not modified)\n\tmutateHeaderFunc func(http.Header)\n\n\tlk                   sync.Mutex // guards numExpectedResponses and broken\n\tnumExpectedResponses int\n\tbroken               bool // an error has happened on this connection; marked broken so it's not reused.\n\n\thost string\n\tip   *net.TCPAddr\n}\n\nfunc (pc *persistConn) isBroken() bool {\n\tpc.lk.Lock()\n\tdefer pc.lk.Unlock()\n\treturn pc.broken\n}\n\nfunc (pc *persistConn) readLoop() {\n\talive := true\n\tvar lastbody io.ReadCloser // last response body, if any, read on this connection\n\n\tfor alive {\n\t\tpb, err := pc.br.Peek(1)\n\n\t\tpc.lk.Lock()\n\t\tif pc.numExpectedResponses == 0 {\n\t\t\tpc.closeLocked()\n\t\t\tpc.lk.Unlock()\n\t\t\tif len(pb) > 0 {\n\t\t\t\tlog.Printf(\"Unsolicited response received on idle HTTP channel starting with %q; err=%v\",\n\t\t\t\t\tstring(pb), err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tpc.lk.Unlock()\n\n\t\trc := <-pc.reqch\n\n\t\t// Advance past the previous response's body, if the\n\t\t// caller hasn't done so.\n\t\tif lastbody != nil {\n\t\t\tlastbody.Close() // assumed idempotent\n\t\t\tlastbody = nil\n\t\t}\n\t\tresp, err := http.ReadResponse(pc.br, rc.req)\n\n\t\tif err != nil {\n\t\t\tpc.close()\n\t\t} else {\n\t\t\thasBody := rc.req.Method != http.MethodHead && resp.ContentLength != 0\n\t\t\tif rc.addedGzip && hasBody && resp.Header.Get(\"Content-Encoding\") == \"gzip\" {\n\t\t\t\tresp.Header.Del(\"Content-Encoding\")\n\t\t\t\tresp.Header.Del(\"Content-Length\")\n\t\t\t\tresp.ContentLength = -1\n\t\t\t\tgzReader, zerr := gzip.NewReader(resp.Body)\n\t\t\t\tif zerr != nil {\n\t\t\t\t\tpc.close()\n\t\t\t\t\terr = zerr\n\t\t\t\t} else {\n\t\t\t\t\tresp.Body = &readFirstCloseBoth{&discardOnCloseReadCloser{gzReader}, resp.Body}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresp.Body = &bodyEOFSignal{body: resp.Body}\n\t\t}\n\n\t\tif err != nil || resp.Close || rc.req.Close {\n\t\t\talive = false\n\t\t}\n\n\t\thasBody := resp != nil && resp.ContentLength != 0\n\t\tvar waitForBodyRead chan bool\n\t\tif alive {\n\t\t\tif hasBody {\n\t\t\t\tbodyEof, ok := resp.Body.(*bodyEOFSignal)\n\t\t\t\tif !ok {\n\t\t\t\t\talive = false\n\t\t\t\t}\n\t\t\t\tlastbody = resp.Body\n\t\t\t\twaitForBodyRead = make(chan bool)\n\t\t\t\tbodyEof.fn = func() {\n\t\t\t\t\tif !pc.t.putIdleConn(pc) {\n\t\t\t\t\t\talive = false\n\t\t\t\t\t}\n\t\t\t\t\twaitForBodyRead <- true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// When there's no response body, we immediately\n\t\t\t\t// reuse the TCP connection (putIdleConn), but\n\t\t\t\t// we need to prevent ClientConn.Read from\n\t\t\t\t// closing the Response.Body on the next\n\t\t\t\t// loop, otherwise it might close the body\n\t\t\t\t// before the client code has had a chance to\n\t\t\t\t// read it (even though it'll just be 0, EOF).\n\t\t\t\tlastbody = nil\n\n\t\t\t\tif !pc.t.putIdleConn(pc) {\n\t\t\t\t\talive = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trc.ch <- responseAndError{resp, err}\n\n\t\t// Wait for the just-returned response body to be fully consumed\n\t\t// before we race and peek on the underlying bufio reader.\n\t\tif waitForBodyRead != nil {\n\t\t\t<-waitForBodyRead\n\t\t}\n\t}\n}\n\ntype responseAndError struct {\n\tres *http.Response\n\terr error\n}\n\ntype requestAndChan struct {\n\treq *http.Request\n\tch  chan responseAndError\n\n\t// did the Transport (as opposed to the client code) add an\n\t// Accept-Encoding gzip header? only if it we set it do\n\t// we transparently decode the gzip.\n\taddedGzip bool\n}\n\nfunc (pc *persistConn) roundTrip(req *transportRequest) (resp *http.Response, err error) {\n\tif pc.mutateHeaderFunc != nil {\n\t\tpanic(\"mutateHeaderFunc not supported in modified Transport\")\n\t}\n\n\t// Ask for a compressed version if the caller didn't set their\n\t// own value for Accept-Encoding. We only attempted to\n\t// uncompress the gzip stream if we were the layer that\n\t// requested it.\n\trequestedGzip := false\n\tif !pc.t.DisableCompression && req.Header.Get(\"Accept-Encoding\") == \"\" {\n\t\t// Request gzip only, not deflate. Deflate is ambiguous and\n\t\t// not as universally supported anyway.\n\t\t// See: http://www.gzip.org/zlib/zlib_faq.html#faq38\n\t\trequestedGzip = true\n\t\treq.extraHeaders().Set(\"Accept-Encoding\", \"gzip\")\n\t}\n\n\tpc.lk.Lock()\n\tpc.numExpectedResponses++\n\tpc.lk.Unlock()\n\n\t// orig: err = req.Request.write(pc.bw, pc.isProxy, req.extra)\n\tif pc.isProxy {\n\t\terr = req.Request.WriteProxy(pc.bw)\n\t} else {\n\t\terr = req.Request.Write(pc.bw)\n\t}\n\tif err != nil {\n\t\tpc.close()\n\t\treturn nil, err\n\t}\n\tpc.bw.Flush()\n\n\tch := make(chan responseAndError, 1)\n\tpc.reqch <- requestAndChan{req.Request, ch, requestedGzip}\n\tre := <-ch\n\tpc.lk.Lock()\n\tpc.numExpectedResponses--\n\tpc.lk.Unlock()\n\n\treturn re.res, re.err\n}\n\nfunc (pc *persistConn) close() {\n\tpc.lk.Lock()\n\tdefer pc.lk.Unlock()\n\tpc.closeLocked()\n}\n\nfunc (pc *persistConn) closeLocked() {\n\tpc.broken = true\n\tpc.conn.Close()\n\tpc.mutateHeaderFunc = nil\n}\n\nvar portMap = map[string]string{\n\t\"http\":  \"80\",\n\t\"https\": \"443\",\n}\n\n// canonicalAddr returns url.Host but always with a \":port\" suffix.\nfunc canonicalAddr(url *url.URL) string {\n\taddr := url.Host\n\tif !hasPort(addr) {\n\t\treturn addr + \":\" + portMap[url.Scheme]\n\t}\n\treturn addr\n}\n\n// bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most\n// once, right before the final Read() or Close() call returns, but after\n// EOF has been seen.\ntype bodyEOFSignal struct {\n\tbody     io.ReadCloser\n\tfn       func()\n\tisClosed bool\n}\n\nfunc (es *bodyEOFSignal) Read(p []byte) (n int, err error) {\n\tn, err = es.body.Read(p)\n\tif es.isClosed && n > 0 {\n\t\tpanic(\"http: unexpected bodyEOFSignal Read after Close; see issue 1725\")\n\t}\n\tif errors.Is(err, io.EOF) && es.fn != nil {\n\t\tes.fn()\n\t\tes.fn = nil\n\t}\n\treturn\n}\n\nfunc (es *bodyEOFSignal) Close() (err error) {\n\tif es.isClosed {\n\t\treturn nil\n\t}\n\tes.isClosed = true\n\terr = es.body.Close()\n\tif err == nil && es.fn != nil {\n\t\tes.fn()\n\t\tes.fn = nil\n\t}\n\treturn\n}\n\ntype readFirstCloseBoth struct {\n\tio.ReadCloser\n\tio.Closer\n}\n\nfunc (r *readFirstCloseBoth) Close() error {\n\tif err := r.ReadCloser.Close(); err != nil {\n\t\tr.Closer.Close()\n\t\treturn err\n\t}\n\tif err := r.Closer.Close(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// discardOnCloseReadCloser consumes all its input on Close.\ntype discardOnCloseReadCloser struct {\n\tio.ReadCloser\n}\n\nfunc (d *discardOnCloseReadCloser) Close() error {\n\t_, _ = io.Copy(io.Discard, d.ReadCloser) // ignore errors; likely invalid or already closed\n\treturn d.ReadCloser.Close()\n}\n"
  },
  {
    "path": "transport/util.go",
    "content": "package transport\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype badStringError struct {\n\twhat string\n\tstr  string\n}\n\nfunc (e *badStringError) Error() string { return fmt.Sprintf(\"%s %q\", e.what, e.str) }\n\nfunc hasPort(s string) bool { return strings.LastIndex(s, \":\") > strings.LastIndex(s, \"]\") }\n"
  },
  {
    "path": "websocket.go",
    "content": "package goproxy\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n)\n\nfunc headerContains(header http.Header, name string, value string) bool {\n\tfor _, v := range header[name] {\n\t\tfor _, s := range strings.Split(v, \",\") {\n\t\t\tif strings.EqualFold(value, strings.TrimSpace(s)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc isWebSocketHandshake(header http.Header) bool {\n\treturn headerContains(header, \"Connection\", \"Upgrade\") &&\n\t\theaderContains(header, \"Upgrade\", \"websocket\")\n}\n\nfunc (proxy *ProxyHttpServer) hijackConnection(ctx *ProxyCtx, w http.ResponseWriter) (net.Conn, error) {\n\t// Connect to Client\n\thj, ok := w.(http.Hijacker)\n\tif !ok {\n\t\tpanic(\"httpserver does not support hijacking\")\n\t}\n\tclientConn, _, err := hj.Hijack()\n\tif err != nil {\n\t\tctx.Warnf(\"Hijack error: %v\", err)\n\t\treturn nil, err\n\t}\n\treturn clientConn, nil\n}\n\nfunc (proxy *ProxyHttpServer) proxyWebsocket(ctx *ProxyCtx, remoteConn io.ReadWriter, proxyClient io.ReadWriter) {\n\t// 2 is the number of goroutines, this code is implemented according to\n\t// https://stackoverflow.com/questions/52031332/wait-for-one-goroutine-to-finish\n\twaitChan := make(chan struct{}, 2)\n\tgo func() {\n\t\t_ = copyOrWarn(ctx, remoteConn, proxyClient)\n\t\twaitChan <- struct{}{}\n\t}()\n\n\tgo func() {\n\t\t_ = copyOrWarn(ctx, proxyClient, remoteConn)\n\t\twaitChan <- struct{}{}\n\t}()\n\n\t// Wait until one end closes the connection\n\t<-waitChan\n}\n"
  },
  {
    "path": "websocket_test.go",
    "content": "package goproxy_test\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/coder/websocket\"\n\t\"github.com/elazarl/goproxy\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWebSocketMitm(t *testing.T) {\n\t// Start a WebSocket echo server\n\tbackend := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tc, err := websocket.Accept(w, r, &websocket.AcceptOptions{\n\t\t\tInsecureSkipVerify: true,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = c.Close(websocket.StatusNormalClosure, \"\")\n\t\t}()\n\n\t\tctx := r.Context()\n\t\tfor {\n\t\t\tmt, message, err := c.Read(ctx)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\terr = c.Write(ctx, mt, append([]byte(\"ECHO: \"), message...))\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}))\n\tbackend.StartTLS()\n\tdefer backend.Close()\n\n\t// Start goproxy\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)\n\n\tproxyServer := httptest.NewServer(proxy)\n\tdefer proxyServer.Close()\n\n\t// Configure WebSocket client to use proxy\n\tproxyURL, err := url.Parse(proxyServer.URL)\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tc, _, err := websocket.Dial(ctx, backend.URL, &websocket.DialOptions{\n\t\tHTTPClient: &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tProxy: http.ProxyURL(proxyURL),\n\t\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\t_ = c.Close(websocket.StatusNormalClosure, \"\")\n\t}()\n\n\t// Verify bidirectional communication\n\tmessage := []byte(\"Hello WebSocket\")\n\terr = c.Write(ctx, websocket.MessageText, message)\n\trequire.NoError(t, err)\n\n\tmt, response, err := c.Read(ctx)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, websocket.MessageText, mt)\n\tassert.Equal(t, \"ECHO: Hello WebSocket\", string(response))\n}\n"
  }
]