Showing preview only (393K chars total). Download the full file or copy to clipboard to get everything.
Repository: elazarl/goproxy
Branch: master
Commit: ffdf0b284e35
Files: 103
Total size: 367.9 KB
Directory structure:
gitextract_ij9ic0cm/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ └── go.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── README.md
├── actions.go
├── all.bash
├── ca.pem
├── certs/
│ ├── openssl-gen.sh
│ └── openssl.cnf
├── certs.go
├── ctx.go
├── dispatcher.go
├── dispatcher_test.go
├── doc.go
├── examples/
│ ├── base/
│ │ ├── README.md
│ │ └── main.go
│ ├── cascadeproxy/
│ │ ├── README.md
│ │ └── main.go
│ ├── cascadeproxy-socks/
│ │ ├── README.md
│ │ ├── socks5proxyserver/
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ └── main.go
│ │ └── socksproxy.go
│ ├── certstorage/
│ │ ├── README.md
│ │ ├── cache.go
│ │ └── main.go
│ ├── customca/
│ │ ├── README.md
│ │ ├── cert.go
│ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── goproxy-httpdump/
│ │ ├── README.md
│ │ └── httpdump.go
│ ├── goproxy-transparent/
│ │ ├── README.md
│ │ ├── proxy.sh
│ │ └── transparent.go
│ ├── hijack/
│ │ ├── README.md
│ │ └── main.go
│ ├── html-parser/
│ │ ├── README.md
│ │ ├── jquery1.html
│ │ ├── jquery2.html
│ │ ├── jquery_homepage.html
│ │ ├── jquery_test.go
│ │ ├── main.go
│ │ ├── php_man.html
│ │ └── w3schools.html
│ ├── image-manipulation/
│ │ ├── README.md
│ │ └── main.go
│ ├── redirect-https/
│ │ ├── README.md
│ │ └── main.go
│ ├── remove-https/
│ │ ├── README.md
│ │ └── main.go
│ ├── request-filtering/
│ │ ├── README.md
│ │ └── noreddit.go
│ ├── socket-keepalive/
│ │ ├── README.md
│ │ └── keepalive.go
│ └── websockets/
│ ├── README.md
│ ├── localhost-key.pem
│ ├── localhost.pem
│ └── main.go
├── ext/
│ ├── auth/
│ │ ├── basic.go
│ │ └── basic_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── har/
│ │ ├── logger.go
│ │ ├── logger_test.go
│ │ └── types.go
│ ├── html/
│ │ ├── cp1255.html
│ │ ├── cp1255.txt
│ │ ├── html.go
│ │ └── html_test.go
│ ├── image/
│ │ ├── image.go
│ │ └── image_test.go
│ └── limitation/
│ ├── concurrency.go
│ └── concurrency_test.go
├── go.mod
├── go.sum
├── h2.go
├── http.go
├── https.go
├── internal/
│ ├── http1parser/
│ │ ├── header.go
│ │ ├── header_test.go
│ │ ├── request.go
│ │ └── request_test.go
│ └── signer/
│ ├── counterecryptor.go
│ ├── counterecryptor_test.go
│ ├── signer.go
│ └── signer_test.go
├── key.pem
├── logger.go
├── proxy.go
├── proxy_test.go
├── regretable/
│ ├── regretreader.go
│ └── regretreader_test.go
├── responses.go
├── transport/
│ ├── roundtripper.go
│ ├── transport.go
│ └── util.go
├── websocket.go
└── websocket_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
open_collective: goproxy
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
# GitHub Actions Pipeline
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Go packages
- package-ecosystem: "gomod"
directories:
- /
- /ext
- /examples
schedule:
interval: "daily"
ignore:
- dependency-name: "github.com/elazarl/goproxy"
================================================
FILE: .github/workflows/go.yml
================================================
name: Code Check
on:
workflow_dispatch:
pull_request:
types:
- opened
- synchronize
- reopened
jobs:
build:
name: Build And Test Go code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
# https://stackoverflow.com/questions/76269119/github-actions-go-lambda-project-different-sha256sums
- name: Build
run: go build -v -buildvcs=false ./...
env:
# Make sure to not use dependencies that rely on CGO
CGO_ENABLED: 0
# Make sure to detect eventual race conditions
# (CGO must be enabled to use -race detector)
- name: Test
# -count=2 ensures that test fixtures cleanup after themselves
# because any leftover state will generally cause the second run to fail.
run: go test -race -p 1 -v -shuffle=on -count=2 ./...
- name: Linter
uses: golangci/golangci-lint-action@v9
with:
version: latest
env:
GOFLAGS: "-buildvcs=false"
================================================
FILE: .gitignore
================================================
bin
*.swp
================================================
FILE: .golangci.yml
================================================
version: "2"
run:
modules-download-mode: readonly
# List from https://golangci-lint.run/usage/linters/
linters:
enable:
- asasalint
- asciicheck
- bidichk
- containedctx
- decorder
- dogsled
- durationcheck
- errchkjson
- errname
- errorlint
- exhaustive
- fatcontext
- forbidigo
- forcetypeassert
- gocheckcompilerdirectives
- gochecksumtype
- gocritic
- godot
- goheader
- gomodguard
- goprintffuncname
- gosec
- gosmopolitan
- grouper
- iface
- importas
- interfacebloat
- lll
- loggercheck
- makezero
- mirror
- misspell
- nakedret
- nilerr
- noctx
- nolintlint
- perfsprint
- prealloc
- predeclared
- reassign
- revive
- staticcheck
- tagalign
- testableexamples
- testifylint
- testpackage
- thelper
- tparallel
- unconvert
- usestdlibvars
- wastedassign
- whitespace
disable:
- bodyclose
- canonicalheader
- contextcheck # Re-enable in V2
- copyloopvar
- cyclop
- depguard
- dupl
- dupword
- err113
- exhaustruct
- funlen
- ginkgolinter
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocyclo
- godox
- gomoddirectives
- inamedparam
- intrange
- ireturn
- maintidx
- mnd
- musttag
- nestif # TODO: Re-enable in V2
- nilnil
- nlreturn
- nonamedreturns
- nosprintfhostport
- paralleltest
- promlinter
- protogetter
- rowserrcheck
- sloglint
- spancheck
- sqlclosecheck
- tagliatelle
- unparam
- varnamelen
- wrapcheck
- wsl
- zerologlint
settings:
gosec:
excludes:
- G402 # InsecureSkipVerify
- G102 # Binds to all network interfaces
- G403 # RSA keys should be at least 2048 bits
- G115 # Integer overflow conversion (uint64 -> int64)
- G404 # Use of weak random number generator (math/rand)
- G204 # Subprocess launched with a potential tainted input or cmd arguments
- G602 # Slice index out of range
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- gocritic
text: ifElseChain
- linters:
- lll
source: '^// '
- linters:
- revive
text: 'add-constant: '
- linters:
- revive
text: 'unused-parameter: '
- linters:
- revive
text: 'empty-block: '
- linters:
- revive
text: 'var-naming: ' # TODO: Re-enable in V2
- linters:
- staticcheck
text: ' should be ' # TODO: Re-enable in V2
- linters:
- staticcheck
text: 'ST1003: should not use ALL_CAPS in Go names; use CamelCase instead'
paths:
- examples$
- transport
formatters:
enable:
- gci
- gofmt
- gofumpt
settings:
gci:
sections:
- standard
- default
custom-order: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: LICENSE
================================================
Copyright (c) 2012 Elazar Leibovich. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Elazar Leibovich. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# GoProxy

[](https://pkg.go.dev/github.com/elazarl/goproxy)
[](https://goreportcard.com/report/github.com/elazarl/goproxy)
[](https://opensource.org/licenses/BSD-3-Clause)
[](https://makeapullrequest.com)
[](https://github.com/avelino/awesome-go?tab=readme-ov-file#networking)
GoProxy is a library to create a `customized` HTTP/HTTPS `proxy server` using
Go (aka Golang), with several configurable settings available.
The target of this project is to offer an `optimized` proxy server, usable with
reasonable amount of traffic, yet `customizable` and `programmable`.
The proxy itself is simply a `net/http` handler, so you can add multiple
middlewares (panic recover, logging, compression, etc.) over it. It can be
easily integrated with any other HTTP network library.
In order to use goproxy, one should set their browser (or any other client)
to use goproxy as an HTTP proxy.
Here is how you do that in [Chrome](https://www.wikihow.com/Connect-to-a-Proxy-Server)
and in [Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox).
If you decide to start with the `base` example, the URL you should use as
proxy is `localhost:8080`, which is the default one in our example.
You also have to [trust](https://github.com/elazarl/goproxy/blob/master/examples/customca/README.md)
the proxy CA certificate, to avoid any certificate issue in the clients.
> [✈️ Telegram Group](https://telegram.me/goproxygroup)
>
> [🎁 Become a Sponsor](https://opencollective.com/goproxy)
## Features
- Perform certain actions only on `specific hosts`, with a single equality comparison or with regex evaluation
- Manipulate `requests` and `responses` before sending them to the browser
- Use a `custom http.Transport` to perform requests to the target server
- 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!
- Redirect normal HTTP traffic to a `custom handler`, when the target is a `relative path` (e.g. `/ping`)
- You can choose the logger to use, by implementing the `Logger` interface
- You can `disable` the HTTP request headers `canonicalization`, by setting `PreventCanonicalization` to true
## Proxy modes
1. Regular HTTP proxy
2. HTTPS through CONNECT
3. 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
4. "Hijacked" proxy connection, where the configured handler can access the raw net.Conn data
## Sponsors
Does your company use GoProxy? Help us keep the project maintained and healthy!
Supporting GoProxy allows us to dedicate more time to bug fixes and new features.
In exchange, if you choose a Gold Supporter or Enterprise plan, we'll proudly display your company logo here.
> [Become a Sponsor](https://opencollective.com/goproxy)
[](https://opencollective.com/goproxy)
[](https://opencollective.com/goproxy)
## Maintainers
- [Elazar Leibovich](https://github.com/elazarl): Creator of the project, Software Engineer
- [Erik Pellizzon](https://github.com/ErikPelli): Maintainer, Freelancer (open to collaborations!)
If you need to integrate GoProxy into your project, or you need some custom
features to maintain in your fork, you can contact [Erik](mailto:erikpelli@tutamail.com)
(the current maintainer) by email, and you can discuss together how he
can help you as a paid independent consultant.
## Contributions
If you have any trouble, suggestion, or if you find a bug, feel free to reach
out by opening a GitHub `issue`.
This is an `open source` project managed by volunteers, and we're happy
to discuss anything that can improve it.
Make sure to explain everything, including the reason behind the issue
and what you want to change, to make the problem easier to understand.
You can also directly open a `Pull Request`, if it's a small code change, but
you need to explain in the description everything.
If you open a pull request named `refactoring` with `5,000` lines changed,
we won't merge it... `:D`
The code for this project is released under the `BSD 3-Clause` license,
making it useful for `commercial` uses as well.
### Submit your case study
So, you have introduced & integrated GoProxy into one of your personal projects
or a project inside the company you work for.
We're happy to learn about new `creative solutions` made with this library,
so feel free to `contact` the maintainer listed above via e-mail, to explaining
why you found this project useful for your needs.
If you have signed a `Non Disclosure Agreement` with the company, you
can propose them to write a `blog post` on their official website about
this topic, so this information will be public by their choice, and you can
`share the link` of the blog post with us :)
The purpose of case studies is to share with the `community` why all the
`contributors` to this project are `improving` the world with their help and
what people are building using it.
### Linter
The codebase uses an automatic lint check over your Pull Request code.
Before opening it, you should check if your changes respect it, by running
the linter in your local machine, so you won't have any surprise.
To install the linter:
```sh
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
```
This will create an executable in your `$GOPATH/bin` folder
(`$GOPATH` is an environment variable, usually
its value is equivalent to `~/go`, check its value in your machine if you
aren't sure about it).
Make sure to include the bin folder in the path of your shell, to be able to
directly use the `golangci-lint run` command.
## A taste of GoProxy
To get a taste of `goproxy`, here you are a basic HTTP/HTTPS proxy
that just forward data to the destination:
```go
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
```
### Request handler
This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy,
before sending them to the destination:
```go
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
```
When the `OnRequest()` input is empty, the function specified in `DoFunc`
will process all incoming requests to the proxy. In this case, it will add
a header to the request and return it to the caller.
The proxy will send the modified request to the destination.
You can also use `Do` instead of `DoFunc`, if you implement the specified
interface in your type.
> ⚠️ Note we returned a nil value as the response.
> If the returned response is not nil, goproxy will discard the request
> and send the specified response to the client.
### Conditional Request handler
Refuse connections to www.reddit.com between 8 and 17 in the server
local timezone:
```go
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
func(req *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 {
resp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "Don't waste your time!")
return req, resp
}
return req, nil
})
```
`DstHostIs` returns a `ReqCondition`, which is a function receiving a `*http.Request`
and returning a boolean that checks if the request satisfies the condition (and that will be processed).
`DstHostIs("www.reddit.com")` will return a `ReqCondition` that returns true
when the request is directed to "www.reddit.com".
The host equality check is `case-insensitive`, to reflect the behaviour of DNS
resolvers, so even if the user types "www.rEdDit.com", the comparison will
satisfy the condition.
When the hour is between 8:00am and 5:59pm, we directly return
a response in `DoFunc()`, so the remote destination will not receive the
request and the client will receive the `"Don't waste your time!"` response.
### Let's start
```go
import "github.com/elazarl/goproxy"
```
There are some proxy usage examples in the `examples` folder, which
cover the most common cases. Take a look at them and good luck!
## Request & Response manipulation
There are 3 different types of handlers to manipulate the behavior of the proxy, as follows:
```go
// handler called after receiving HTTP CONNECT from the client, and
// before proxy establishes connection with the destination host
httpsHandlers []HttpsHandler
// handler called before proxy sends HTTP request to destination host
reqHandlers []ReqHandler
// handler called after proxy receives HTTP Response from destination host,
// and before proxy forwards the Response to the client
respHandlers []RespHandler
```
Depending on what you want to manipulate, the ways to add handlers to each of the previous lists are:
```go
// Add handlers to httpsHandlers
proxy.OnRequest(some ReqConditions).HandleConnect(YourHandlerFunc())
// Add handlers to reqHandlers
proxy.OnRequest(some ReqConditions).Do(YourReqHandlerFunc())
// Add handlers to respHandlers
proxy.OnResponse(some RespConditions).Do(YourRespHandlerFunc())
```
Example:
```go
// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase.
// Reddit URL check is case-insensitive because of (?i), so the block will work also if the user types something like rEdDit.com.
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("(?i)reddit.*:443$"))).HandleConnect(goproxy.AlwaysReject)
// Be careful about this example! It shows you a common error that you
// need to avoid.
// This will NOT reject the HTTPS request with URL ending with .gif because,
// if the scheme is HTTPS, the proxy will receive only URL.Hostname
// and URL.Port during the HTTP CONNECT phase.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject)
// To fix the previous example, here there is the correct way to manipulate
// an HTTP request using URL.Path (target path) as a condition.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc())
```
## Error handling
### Generic error
If an error occurs while handling a request through the proxy, by default
the proxy returns HTTP error `500` (Internal Server Error) with the `error
message` as the `body` content.
If you want to override this behaviour, you can define your own
`RespHandler` that changes the error response.
Among the context parameters, `ctx.Error` contains the `error` occurred,
if any, or the `nil` value, if no error happened.
You can handle it as you wish, including returning a custom JSON as the body.
Example of an error handler:
```
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
var dnsError *net.DNSError
if errors.As(ctx.Error, &dnsError) {
// Do not leak our DNS server's address
dnsError.Server = "<server-redacted>"
return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusBadGateway, dnsError.Error())
}
return resp
})
```
### Connection error
If an error occurs while sending data to the target remote server (or to
the proxy client), the `proxy.ConnectionErrHandler` is called to handle the
error, if present, else a `default handler` will be used.
The error is passed as `function parameter` and not inside the proxy context,
so you don't have to check the ctx.Error field in this handler.
In this handler you have access to the raw connection with the proxy
client (as an `io.Writer`), so you could send any HTTP data over it,
if needed, containing the error data.
There is no guarantee that the connection hasn't already been closed, so
the `Write()` could return an error.
The `connection` will be `automatically closed` by the proxy library after the
error handler call, so you don't have to worry about it.
## Project Status
This project has been created `10 years` ago, and has reached a stage of
`maturity`. It can be safely used in `production`, and many projects
already do that.
If there will be any `breaking change` in the future, a `new version` of the
Go module will be released (e.g. v2).
## Trusted, as a direct dependency, by:
<p align="left">
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
</p>
================================================
FILE: actions.go
================================================
package goproxy
import "net/http"
// ReqHandler will "tamper" with the request coming to the proxy server
// If Handle returns req,nil the proxy will send the returned request
// to the destination server. If it returns nil,resp the proxy will
// skip sending any requests, and will simply return the response `resp`
// to the client.
type ReqHandler interface {
Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)
}
// A wrapper that would convert a function to a ReqHandler interface type.
type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)
// FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx).
func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {
return f(req, ctx)
}
// after the proxy have sent the request to the destination server, it will
// "filter" the response through the RespHandlers it has.
// The proxy server will send to the client the response returned by the RespHandler.
// In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error.
type RespHandler interface {
Handle(resp *http.Response, ctx *ProxyCtx) *http.Response
}
// A wrapper that would convert a function to a RespHandler interface type.
type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response
// FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx).
func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response {
return f(resp, ctx)
}
// When a client send a CONNECT request to a host, the request is filtered through
// all the HttpsHandlers the proxy has, and if one returns true, the connection is
// sniffed using Man in the Middle attack.
// That is, the proxy will create a TLS connection with the client, another TLS
// connection with the destination the client wished to connect to, and would
// send back and forth all messages from the server to the client and vice versa.
// The request and responses sent in this Man In the Middle channel are filtered
// through the usual flow (request and response filtered through the ReqHandlers
// and RespHandlers).
type HttpsHandler interface {
HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string)
}
// A wrapper that would convert a function to a HttpsHandler interface type.
type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string)
// FuncHttpsHandler should implement the RespHandler interface.
func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) {
return f(host, ctx)
}
================================================
FILE: all.bash
================================================
#!/bin/bash
go test || exit
for action in $@; do go $action; done
mkdir -p bin
find regretable examples/* ext/* -maxdepth 0 -type d | while read d; do
(cd $d
go build -o ../../bin/$(basename $d)
find *_test.go -maxdepth 0 2>/dev/null|while read f;do
for action in $@; do go $action; done
go test
break
done)
done
================================================
FILE: ca.pem
================================================
-----BEGIN CERTIFICATE-----
MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD
VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM
B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0
aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0
MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE
CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV
BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI
hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9
3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP
sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9
V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh
hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr
lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq
j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo
WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD
fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj
YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh
WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj
UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4
uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F
AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0
C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3
Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin
o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye
i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr
bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY
VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft
8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86
NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV
BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==
-----END CERTIFICATE-----
================================================
FILE: certs/openssl-gen.sh
================================================
#!/bin/bash
set -ex
# generate CA's key
openssl genrsa -aes256 -passout pass:1 -out ca.key.pem 4096
openssl rsa -passin pass:1 -in ca.key.pem -out ca.key.pem.tmp
mv ca.key.pem.tmp ca.key.pem
openssl req -config openssl.cnf -key ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out ca.pem
================================================
FILE: certs/openssl.cnf
================================================
[ ca ]
default_ca = CA_default
[ CA_default ]
default_md = sha256
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
[ req ]
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = IL
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Center
localityName = Locality Name (eg, city)
localityName_default = Lod
0.organizationName = Organization Name (eg, company)
0.organizationName_default = GoProxy
# we can do this but it is not needed normally :-)
#1.organizationName = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd
organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = GoProxy
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_default = goproxy.github.io
commonName_max = 64
emailAddress = Email Address
emailAddress_default = elazarl@gmail.com
emailAddress_max = 64
================================================
FILE: certs.go
================================================
package goproxy
import (
"crypto/tls"
"crypto/x509"
)
var GoproxyCa tls.Certificate
func init() {
// When we included the embedded certificate inside this file, we made
// sure that it was valid.
// If there is an error here, this is a really exceptional case that requires
// a panic. It should NEVER happen!
var err error
GoproxyCa, err = tls.X509KeyPair(CA_CERT, CA_KEY)
if err != nil {
panic("Error parsing builtin CA: " + err.Error())
}
if GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil {
panic("Error parsing builtin CA leaf: " + err.Error())
}
}
var tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true}
var defaultTLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
var CA_CERT = []byte(`-----BEGIN CERTIFICATE-----
MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD
VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM
B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0
aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0
MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE
CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV
BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI
hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9
3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP
sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9
V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh
hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr
lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq
j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo
WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD
fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj
YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh
WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj
UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4
uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F
AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0
C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3
Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin
o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye
i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr
bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY
VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft
8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86
NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV
BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==
-----END CERTIFICATE-----`)
var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF
0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw
HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf
m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+
qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ
0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I
yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq
AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU
BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK
0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic
geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA
AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR
kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3
lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt
zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7
+68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ
3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf
pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U
C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4
Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3
4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm
V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9
jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag
/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6
eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw
+LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ
ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt
FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC
06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7
OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9
7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf
KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt
sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB
N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa
QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv
5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W
t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF
540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru
sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi
L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um
YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi
9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe
yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ
QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c
ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH
759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh
pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1
cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88
4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w=
-----END RSA PRIVATE KEY-----`)
================================================
FILE: ctx.go
================================================
package goproxy
import (
"context"
"crypto/tls"
"mime"
"net"
"net/http"
)
// ProxyCtx is the Proxy context, contains useful information about every request. It is passed to
// every user function. Also used as a logger.
type ProxyCtx struct {
// Will contain the client request from the proxy
Req *http.Request
// Will contain the remote server's response (if available. nil if the request wasn't send yet)
Resp *http.Response
RoundTripper RoundTripper
// Specify a custom connection dialer that will be used only for the current
// request, including WebSocket connection upgrades
Dialer func(ctx context.Context, network string, addr string) (net.Conn, error)
// will contain the recent error that occurred while trying to send receive or parse traffic
Error error
// A handle for the user to keep data in the context, from the call of ReqHandler to the
// call of RespHandler
UserData any
// Will connect a request to a response
Session int64
certStore CertStorage
Proxy *ProxyHttpServer
}
type RoundTripper interface {
RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
}
type CertStorage interface {
Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error)
}
type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) {
return f(req, ctx)
}
func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) {
if ctx.RoundTripper != nil {
return ctx.RoundTripper.RoundTrip(req, ctx)
}
return ctx.Proxy.Tr.RoundTrip(req)
}
func (ctx *ProxyCtx) printf(msg string, argv ...any) {
ctx.Proxy.Logger.Printf("[%03d] "+msg+"\n", append([]any{ctx.Session & 0xFFFF}, argv...)...)
}
// Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter
// This message will be printed only if the Verbose field of the ProxyHttpServer is set to true
//
// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
// nr := atomic.AddInt32(&counter,1)
// ctx.Printf("So far %d requests",nr)
// return r, nil
// })
func (ctx *ProxyCtx) Logf(msg string, argv ...any) {
if ctx.Proxy.Verbose {
ctx.printf("INFO: "+msg, argv...)
}
}
// Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter
// This message will always be printed.
//
// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
// f,err := os.OpenFile(cachedContent)
// if err != nil {
// ctx.Warnf("error open file %v: %v",cachedContent,err)
// return r, nil
// }
// return r, nil
// })
func (ctx *ProxyCtx) Warnf(msg string, argv ...any) {
ctx.printf("WARN: "+msg, argv...)
}
// Will try to infer the character set of the request from the headers.
// Returns the empty string if we don't know which character set it used.
// Currently it will look for charset=<charset> in the Content-Type header of the request.
func (ctx *ProxyCtx) Charset() string {
contentType := ctx.Resp.Header.Get("Content-Type")
if _, params, err := mime.ParseMediaType(contentType); err == nil {
if cs, ok := params["charset"]; ok {
return cs
}
}
return ""
}
================================================
FILE: dispatcher.go
================================================
package goproxy
import (
"bytes"
"io"
"net"
"net/http"
"regexp"
"strings"
)
// ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request
// before sending it to the remote server.
type ReqCondition interface {
RespCondition
HandleReq(req *http.Request, ctx *ProxyCtx) bool
}
// RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response
// before sending it to the proxy client. Note that resp might be nil, in case there was an
// error sending the request.
type RespCondition interface {
HandleResp(resp *http.Response, ctx *ProxyCtx) bool
}
// ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx).
type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool
// RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx).
type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool
func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool {
return c(req, ctx)
}
// ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that
// to be usable as RespCondition.
func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool {
return c(ctx.Req, ctx)
}
func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool {
return c(resp, ctx)
}
// UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested
// has the given prefix, with or without the host.
// For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match
// requests to url 'http://host/x'
func UrlHasPrefix(prefix string) ReqConditionFunc {
return func(req *http.Request, ctx *ProxyCtx) bool {
// Make sure to include the / as the first path character when we do a match
// using the host
relativePath := req.URL.Path
if length := len(relativePath); length == 0 || (length > 0 && relativePath[0] != '/') {
relativePath = "/" + relativePath
}
// We use the original value to distinguish between "" and "/" in the user specified string
return strings.HasPrefix(req.URL.Path, prefix) ||
strings.HasPrefix(req.URL.Host+relativePath, prefix) ||
// Scheme value is something like "https", we must include the :// characters
strings.HasPrefix(req.URL.Scheme+"://"+req.URL.Host+relativePath, prefix)
}
}
// UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings
// with or without the host prefix.
// UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to
// any host, and requests of the form 'GET foo'.
func UrlIs(urls ...string) ReqConditionFunc {
urlSet := make(map[string]bool)
for _, u := range urls {
urlSet[u] = true
}
return func(req *http.Request, ctx *ProxyCtx) bool {
_, pathOk := urlSet[req.URL.Path]
_, hostAndOk := urlSet[req.URL.Host+req.URL.Path]
return pathOk || hostAndOk
}
}
// ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches
// any of the given regular expressions.
func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc {
return func(req *http.Request, ctx *ProxyCtx) bool {
for _, re := range regexps {
if re.MatchString(req.Host) {
return true
}
}
return false
}
}
// ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal
// to one of the given strings.
func ReqHostIs(hosts ...string) ReqConditionFunc {
hostSet := make(map[string]bool)
for _, h := range hosts {
hostSet[h] = true
}
return func(req *http.Request, ctx *ProxyCtx) bool {
_, ok := hostSet[req.URL.Host]
return ok
}
}
// IsLocalHost checks whether the destination host is localhost.
var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool {
h := req.URL.Hostname()
if h == "localhost" {
return true
}
if ip := net.ParseIP(h); ip != nil {
return ip.IsLoopback()
}
// In case of IPv6 without a port number Hostname() sometimes returns the invalid value.
if ip := net.ParseIP(req.URL.Host); ip != nil {
return ip.IsLoopback()
}
return false
}
// UrlMatches returns a ReqCondition testing whether the destination URL
// of the request matches the given regexp, with or without prefix.
func UrlMatches(re *regexp.Regexp) ReqConditionFunc {
return func(req *http.Request, ctx *ProxyCtx) bool {
return re.MatchString(req.URL.Path) ||
re.MatchString(req.URL.Host+req.URL.Path)
}
}
// DstHostIs returns a ReqCondition testing wether the host in the request url is the given string.
func DstHostIs(host string) ReqConditionFunc {
// Make sure to perform a case-insensitive host check
host = strings.ToLower(host)
var port string
// Check if the user specified a custom port that we need to match
if strings.Contains(host, ":") {
hostOnly, portOnly, err := net.SplitHostPort(host)
if err == nil {
host = hostOnly
port = portOnly
}
}
return func(req *http.Request, ctx *ProxyCtx) bool {
// Check port matching only if it was specified
if port != "" && port != req.URL.Port() {
return false
}
return strings.ToLower(req.URL.Hostname()) == host
}
}
// SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings.
func SrcIpIs(ips ...string) ReqCondition {
return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool {
for _, ip := range ips {
if strings.HasPrefix(req.RemoteAddr, ip+":") {
return true
}
}
return false
})
}
// Not returns a ReqCondition negating the given ReqCondition.
func Not(r ReqCondition) ReqConditionFunc {
return func(req *http.Request, ctx *ProxyCtx) bool {
return !r.HandleReq(req, ctx)
}
}
// ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal
// to one of the given strings.
func ContentTypeIs(typ string, types ...string) RespCondition {
types = append(types, typ)
return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool {
if resp == nil {
return false
}
contentType := resp.Header.Get("Content-Type")
for _, typ := range types {
if contentType == typ || strings.HasPrefix(contentType, typ+";") {
return true
}
}
return false
})
}
// StatusCodeIs returns a RespCondition, testing whether or not the HTTP status
// code is one of the given ints.
func StatusCodeIs(codes ...int) RespCondition {
codeSet := make(map[int]bool)
for _, c := range codes {
codeSet[c] = true
}
return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool {
if resp == nil {
return false
}
_, codeMatch := codeSet[resp.StatusCode]
return codeMatch
})
}
// ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions.
// You will use the ReqProxyConds struct to register a ReqHandler, that would filter
// the request, only if all the given ReqCondition matched.
// Typical usage:
//
// proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...)
func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds {
return &ReqProxyConds{proxy, conds}
}
// ReqProxyConds aggregate ReqConditions for a ProxyHttpServer.
// Upon calling Do, it will register a ReqHandler that would
// handle the request if all conditions on the HTTP request are met.
type ReqProxyConds struct {
proxy *ProxyHttpServer
reqConds []ReqCondition
}
// DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)).
func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) {
pcond.Do(FuncReqHandler(f))
}
// ReqProxyConds.Do will register the ReqHandler on the proxy,
// the ReqHandler will handle the HTTP request if all the conditions
// aggregated in the ReqProxyConds are met. Typical usage:
//
// proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy
// proxy.OnRequest(cond1,cond2).Do(handler)
// // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true
// // if they are, will call handler.Handle(req,ctx)
func (pcond *ReqProxyConds) Do(h ReqHandler) {
pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers,
FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(r, ctx) {
return r, nil
}
}
return h.Handle(r, ctx)
}))
}
// HandleConnect is used when proxy receives an HTTP CONNECT request,
// it'll then use the HttpsHandler to determine what should it
// do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction
// struct returned will determine what to do with this request. ConnectAccept will simply accept the request
// forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the
// client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man
// in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped
// connection.
// The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy
// will use the default tls configuration.
//
// proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests
func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) {
pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,
FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(ctx.Req, ctx) {
return nil, ""
}
}
return h.HandleConnect(host, ctx)
}))
}
// HandleConnectFunc is equivalent to HandleConnect,
// for example, accepting CONNECT request if they contain a password in header
//
// io.WriteString(h,password)
// passHash := h.Sum(nil)
// proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
// c := sha1.New()
// io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth"))
// if c.Sum(nil) == passHash {
// return OkConnect, host
// }
// return RejectConnect, host
// })
func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) {
pcond.HandleConnect(FuncHttpsHandler(f))
}
func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) {
pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,
FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(ctx.Req, ctx) {
return nil, ""
}
}
return &ConnectAction{Action: ConnectHijack, Hijack: f}, host
}))
}
// ProxyConds is used to aggregate RespConditions for a ProxyHttpServer.
// Upon calling ProxyConds.Do, it will register a RespHandler that would
// handle the HTTP response from remote server if all conditions on the HTTP response are met.
type ProxyConds struct {
proxy *ProxyHttpServer
reqConds []ReqCondition
respCond []RespCondition
}
// ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)).
func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) {
pcond.Do(FuncRespHandler(f))
}
// ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every
// request that matches the conditions aggregated in pcond.
func (pcond *ProxyConds) Do(h RespHandler) {
pcond.proxy.respHandlers = append(pcond.proxy.respHandlers,
FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(ctx.Req, ctx) {
return resp
}
}
for _, cond := range pcond.respCond {
if !cond.HandleResp(resp, ctx) {
return resp
}
}
return h.Handle(resp, ctx)
}))
}
// OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is
//
// proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used
// // if cond1.HandleResp(resp) && cond2.HandleResp(resp)
func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds {
return &ProxyConds{proxy, make([]ReqCondition, 0), conds}
}
// AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to
// eavesdrop all https connections to www.google.com, we can use
//
// proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm)
var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
return MitmConnect, host
}
// AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow
// connections to hosts on any other port than 443
//
// proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))).
// HandleConnect(goproxy.AlwaysReject)
var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
return RejectConnect, host
}
// HandleBytes will return a RespHandler that read the entire body of the request
// to a byte array in memory, would run the user supplied f function on the byte arra,
// and will replace the body of the original response with the resulting byte array.
func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler {
return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {
b, err := io.ReadAll(resp.Body)
if err != nil {
ctx.Warnf("Cannot read response %s", err)
return resp
}
resp.Body.Close()
resp.Body = io.NopCloser(bytes.NewBuffer(f(b, ctx)))
return resp
})
}
================================================
FILE: dispatcher_test.go
================================================
package goproxy_test
import (
"context"
"net"
"net/http"
"strings"
"testing"
"github.com/elazarl/goproxy"
)
func TestIsLocalHost(t *testing.T) {
hosts := []string{
"localhost",
"127.0.0.1",
"127.0.0.7",
"::ffff:127.0.0.1",
"::ffff:127.0.0.7",
"::1",
"0:0:0:0:0:0:0:1",
}
ports := []string{
"",
"80",
"443",
}
for _, host := range hosts {
for _, port := range ports {
if port == "" && strings.HasPrefix(host, "::ffff:") {
continue
}
addr := host
if port != "" {
addr = net.JoinHostPort(host, port)
}
t.Run(addr, func(t *testing.T) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://"+addr, http.NoBody)
if err != nil {
t.Fatal(err)
}
if !goproxy.IsLocalHost(req, nil) {
t.Fatal("expected true")
}
})
}
}
}
================================================
FILE: doc.go
================================================
/*
Package goproxy provides a customizable HTTP proxy,
supporting hijacking HTTPS connection.
The intent of the proxy, is to be usable with reasonable amount of traffic
yet, customizable and programmable.
The proxy itself is simply an `net/http` handler.
Typical usage is
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest(..conditions..).Do(..requesthandler..)
proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..)
proxy.OnResponse(..conditions..).Do(..responesHandler..)
proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..)
http.ListenAndServe(":8080", proxy)
Adding a header to each request
proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
r.Header.Set("X-GoProxy","1")
return r, nil
})
> Note that the function is called before the proxy sends the request to the server
For printing the content type of all incoming responses
proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{
println(ctx.Req.Host,"->",r.Header.Get("Content-Type"))
return r
})
note that we used the ProxyCtx context variable here. It contains the request
and the response (Req and Resp, Resp is nil if unavailable) of this specific client
interaction with the proxy.
To print the content type of all responses from a certain url, we'll add a
ReqCondition to the OnResponse function:
proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{
println(ctx.Req.Host,"->",r.Header.Get("Content-Type"))
return r
})
We can write the condition ourselves, conditions can be set on request and on response
var random = ReqConditionFunc(func(r *http.Request) bool {
return rand.Intn(1) == 0
})
var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool {
return resp.Header.Get("X-GoProxy") != ""
})
Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't
make sense to read the response, if you still haven't got it!
Finally, we have convenience function to throw a quick response
proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response {
r.Body.Close()
return goproxy.NewResponse(
ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, "Can't see response with X-GoProxy header!"
)
})
we close the body of the original response, and return a new 403 response with a short message.
Example use cases:
1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize
To measure the average size of an Html served in your site. One can ask
all the QA team to access the website by a proxy, and the proxy will
measure the average size of all text/html responses from your host.
2. [not yet implemented]
All requests to your web servers should be directed through the proxy,
when the proxy will detect html pieces sent as a response to AJAX
request, it'll send a warning email.
3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/
Generate a real traffic to your website by real users using through
proxy. Record the traffic, and try it again for more real load testing.
4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime
Will allow browsing to reddit.com between 8:00am and 17:00pm
5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version
Will warn if multiple versions of jquery are used in the same domain.
6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/
Modifies image files in an HTTP response via goproxy's image extension found in ext/.
*/
package goproxy
================================================
FILE: examples/base/README.md
================================================
# Simple HTTP Proxy
This example contains a base HTTP proxy server that listens on port :8080.
It only handles explicit CONNECT requests.
Start it in one shell:
```sh
go build
base -v
```
Fetch a website using the proxy:
```sh
http_proxy=http://127.0.0.1:8080 wget -O - \
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
```
The homepage HTML content should be displayed in the console.
The proxy should have logged the request being processed:
```sh
2015/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/
2015/04/09 18:19:17 [001] INFO: Sending request GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/
2015/04/09 18:19:18 [001] INFO: Received response 200 OK
2015/04/09 18:19:18 [001] INFO: Copying response to client 200 OK [200]
2015/04/09 18:19:18 [001] INFO: Copied 44333 bytes to client error=<nil>
```
================================================
FILE: examples/base/main.go
================================================
package main
import (
"flag"
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = *verbose
log.Fatal(http.ListenAndServe(*addr, proxy))
}
================================================
FILE: examples/cascadeproxy/README.md
================================================
# CascadeProxy
`CascadeProxy` is an example that shows an aggregator server that forwards
the requests to another proxy server (end proxy).
Diagram:
```
client --> middle proxy --> end proxy --> internet
```
This example starts both proxy servers using goproxy, the middle one
listens on port `8081`, and the end one on port `8082`.
The middle proxy must be an HTTP server, since we use goproxy that
expose only it.
The end proxy can be any type of proxy supported by Go, including SOCKS5,
there is a comment in the part where you can put its address.
================================================
FILE: examples/cascadeproxy/main.go
================================================
package main
import (
"crypto/subtle"
"encoding/base64"
"io"
"log"
"net/http"
"net/url"
"time"
"github.com/elazarl/goproxy"
"github.com/elazarl/goproxy/ext/auth"
)
const _proxyAuthHeader = "Proxy-Authorization"
func SetBasicAuth(username, password string, req *http.Request) {
req.Header.Set(_proxyAuthHeader, "Basic "+base64.StdEncoding.EncodeToString([]byte(username+":"+password)))
}
func main() {
username, password := "foo", "bar"
// Start end proxy server
endProxy := goproxy.NewProxyHttpServer()
endProxy.Verbose = true
auth.ProxyBasic(endProxy, "my_realm", func(user, pwd string) bool {
return subtle.ConstantTimeCompare([]byte(user), []byte(username)) == 1 &&
subtle.ConstantTimeCompare([]byte(pwd), []byte(password)) == 1
})
log.Println("serving end proxy server at localhost:8082")
go http.ListenAndServe("localhost:8082", endProxy)
// Start middle proxy server
middleProxy := goproxy.NewProxyHttpServer()
middleProxy.Verbose = true
middleProxy.KeepHeader = true
middleProxy.Tr.Proxy = func(req *http.Request) (*url.URL, error) {
// Here we specify the proxy URL of the other server.
// If it was a socks5 proxy, we would have used an url like
// socks5://localhost:8082
return url.Parse("http://localhost:8082")
}
connectReqHandler := func(req *http.Request) {
SetBasicAuth(username, password, req)
}
middleProxy.ConnectDial = middleProxy.NewConnectDialToProxyWithHandler("http://localhost:8082", connectReqHandler)
middleProxy.OnRequest().Do(goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
SetBasicAuth(username, password, req)
return req, nil
}))
log.Println("serving middle proxy server at localhost:8081")
go http.ListenAndServe("localhost:8081", middleProxy)
time.Sleep(1 * time.Second)
// Make a single HTTP request, from client to internet, through the 2 proxies
middleProxyUrl := "http://localhost:8081"
request, err := http.NewRequest(http.MethodGet, "https://ip.cn", nil)
if err != nil {
log.Fatalf("new request failed:%v", err)
}
client := &http.Client{
Transport: &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
return url.Parse(middleProxyUrl)
},
},
}
resp, err := client.Do(request)
if err != nil {
log.Fatalf("get resp failed: %v", err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
log.Fatalf("status %d, data %s", resp.StatusCode, data)
}
log.Printf("resp: %s", data)
}
================================================
FILE: examples/cascadeproxy-socks/README.md
================================================
# CascadeSocksProxy
`CascadeSocksProxy` is an example that shows an aggregator server that forwards
the requests to another socks proxy server. This example is written base on `cascadeproxy` example.
Diagram:
```
client --> goproxy --> socks5 proxy --> internet
```
This 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`.
It 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.
### Example usage:
Aggregator 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
```shell
./socks -v -addr ":8080" -socks "localhost:1080"
```
With auth:
```shell
./socks -v -addr ":8080" -socks "localhost:1080" -user "bob" -pass "123"
```
You 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:
```shell
./socks5proxyserver/socks5proxyserver
```
================================================
FILE: examples/cascadeproxy-socks/socks5proxyserver/go.mod
================================================
module socks5proxyserver
go 1.20
require github.com/things-go/go-socks5 v0.0.5
================================================
FILE: examples/cascadeproxy-socks/socks5proxyserver/go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
================================================
FILE: examples/cascadeproxy-socks/socks5proxyserver/main.go
================================================
package main
import (
"context"
"github.com/things-go/go-socks5"
"log"
"net"
"os"
)
func main() {
// Create a SOCKS5 server
server := socks5.NewServer(
socks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))),
socks5.WithDialAndRequest(func(ctx context.Context, network, addr string, request *socks5.Request) (net.Conn, error) {
log.Printf("Request from %s to %s", request.RemoteAddr, request.DestAddr)
return net.Dial(network, addr)
}),
)
// Create SOCKS5 proxy on localhost port 1080
if err := server.ListenAndServe("tcp", ":1080"); err != nil {
panic(err)
}
}
================================================
FILE: examples/cascadeproxy-socks/socksproxy.go
================================================
package main
import (
"crypto/tls"
"flag"
"log"
"net/http"
"net/url"
"github.com/elazarl/goproxy"
)
type SocksAuth struct {
Username, Password string
}
func createSocksProxy(socksAddr string, auth SocksAuth) func(r *http.Request) (*url.URL, error) {
return func(r *http.Request) (*url.URL, error) {
Url := &url.URL{
Scheme: "socks5",
Host: socksAddr,
}
if auth.Username != "" {
Url.User = url.UserPassword(auth.Username, auth.Password)
}
return Url, nil
}
}
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
socksAddr := flag.String("socks", "127.0.0.1:1080", "socks proxy address")
username := flag.String("user", "", "username for SOCKS5 proxy if auth is required")
password := flag.String("pass", "", "password for SOCKS5 proxy")
flag.Parse()
auth := SocksAuth{
Username: *username,
Password: *password,
}
proxyServer := goproxy.NewProxyHttpServer()
proxyServer.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
client := &http.Client{
Transport: &http.Transport{
Proxy: createSocksProxy(*socksAddr, auth),
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
// https://stackoverflow.com/questions/19595860/http-request-requesturi-field-when-making-request-in-go
req.RequestURI = ""
resp, err := client.Do(req)
if err != nil {
ctx.Logf("Failed to forward request: " + err.Error())
return nil, nil
}
ctx.Logf("Succesfully forwarded request to socks proxy")
return req, resp
})
proxyServer.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxyServer.Verbose = *verbose
log.Fatalln(http.ListenAndServe(*addr, proxyServer))
}
================================================
FILE: examples/certstorage/README.md
================================================
# CertStorage
CertStorage example is important to improve the performance of an
HTTPS proxy server, which you can build using goproxy.
Without a `proxy.CertStore`, every HTTPS request will generate new TLS
certificates and this, repeated for hundreds of request, will destroy your CPU.
A lot of people opened issues in the projects complaining about this, because
they didn't use a certificates cache.
The cache implementation is up to you, maybe you can cache only the
most used hostnames, if you want to.
================================================
FILE: examples/certstorage/cache.go
================================================
package main
import (
"crypto/tls"
"sync"
)
// CertStorage is a simple certificate cache that keeps
// everything in memory.
type CertStorage struct {
certs map[string]*tls.Certificate
mtx sync.RWMutex
}
func (cs *CertStorage) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {
cs.mtx.RLock()
cert, ok := cs.certs[hostname]
cs.mtx.RUnlock()
if ok {
return cert, nil
}
cert, err := gen()
if err != nil {
return nil, err
}
cs.mtx.Lock()
cs.certs[hostname] = cert
cs.mtx.Unlock()
return cert, nil
}
func NewCertStorage() *CertStorage {
return &CertStorage{
certs: make(map[string]*tls.Certificate),
}
}
================================================
FILE: examples/certstorage/main.go
================================================
package main
import (
"flag"
"github.com/elazarl/goproxy"
"log"
"net/http"
)
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.CertStore = NewCertStorage()
proxy.Verbose = *verbose
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
// Log requested URL
log.Println(req.URL.String())
return req, nil
})
// Start proxy server
log.Fatal(http.ListenAndServe(*addr, proxy))
}
================================================
FILE: examples/customca/README.md
================================================
# CustomCA
This example shows you how to use a custom CA to sign the HTTPS MITM
requests (you can use your own generated certificates).
If the client has some kind of SSL pinning to check the TLS certificates, all
the request will most likely fail, so make sure to remove it before using
this proxy or opening new issues.
Proxy server will generate a custom certificate for the target host, for each
request, and it's used to read the request data of an HTTPS
connection.
The client will establish a TLS connection using the generated certificate
with the proxy server, the server will read the request data, process it
according to the user needs, and then it will do a new request to the real
destination.
The CA certificate must be trusted by your system, or the client will reject
the connection, since it's not recognized.
## Trust CA certificate
The default CA certificate used by GoProxy is in the root folder of this
project (in files `ca.pem`, and its private key `key.pem`).
### Use your certificate
You can trust the default certificate or use your own with GoProxy, and
trust it instead of the provided `ca.pem`.
If you want to do this, just replace the occurrences of this file in the next
paragraphs with your CA certificate filename.
You can generate your own self-signed certificate with
[openssl](https://stackoverflow.com/questions/10175812/how-to-generate-a-self-signed-ssl-certificate-using-openssl).
### Firefox
You have to reach the certificate manager configuration in order to add
the certificate to the trusted ones.
To reach it, open the settings and type in search bar "Certificates", then
click on the button "View Certificates...".
In the tab "Authorities", click "Import..." and select the `ca.pem` file.
GoProxy CA is now trusted by your browser!
### Chrome
Open the certificate manager configuration:
> "Settings" > "Privacy and Security" > "Security" > "Manage certificates"
Go to the tab "Authorities", click "Import" and select the `ca.pem` file.
GoProxy CA is now trusted by your browser!
### System
If you want the root certificate to be trusted by all applications in your
environment, consider adding it to the system trusted certificates.
Here is a couple of guides about how to do it, but we don't provide any support:
- [1](https://manuals.gfi.com/en/kerio/connect/content/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)
- [2](https://unix.stackexchange.com/questions/90450/adding-a-self-signed-certificate-to-the-trusted-list)
#### MkCert
Do you want a managed, easy to use solution that automatically generates
a root CA certificate for local usage, and automatically adds it to the trusted system
certificates? Consider [MkCert](https://github.com/FiloSottile/mkcert).
It's enough to just use it and add the generated trusted certificate to GoProxy.
================================================
FILE: examples/customca/cert.go
================================================
package main
var _caCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDkzCCAnugAwIBAgIJAKe/ZGdfcHdPMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxGTAXBgNVBAMMEGRlbW8gZm9yIGdvcHJveHkwHhcNMTYw
OTI3MTQzNzQ3WhcNMTkwOTI3MTQzNzQ3WjBgMQswCQYDVQQGEwJBVTETMBEGA1UE
CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
MRkwFwYDVQQDDBBkZW1vIGZvciBnb3Byb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6
1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0
69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW//
1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+
JEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK
vhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABo1AwTjAdBgNVHQ4EFgQU
GnlWcIbfsWJW7GId+6xZIK8YlFEwHwYDVR0jBBgwFoAUGnlWcIbfsWJW7GId+6xZ
IK8YlFEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAoFUjSD15rKlY
xudzyVlr6n0fRNhITkiZMX3JlFOvtHNYif8RfK4TH/oHNBTmle69AgixjMgy8GGd
H90prytGQ5zCs1tKcCFsN5gRSgdAkc2PpRFOK6u8HwOITV5lV7sjucsddXJcOJbQ
4fyVe47V9TTxI+A7lRnUP2HYTR1Bd0R/IgRAH57d1ZHs7omHIuQ+Ea8ph2ppXMnP
DXVOlZ9zfczSnPnQoomqULOU9Fq2ycyi8Y/ROtAHP6O7wCFbYHXhxojdaHSdhkcd
troTflFMD2/4O6MtBKbHxSmEG6H0FBYz5xUZhZq7WUH24V3xYsfge29/lOCd5/Xf
A+j0RJc/lQ==
-----END CERTIFICATE-----`)
var _caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6
1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0
69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW//
1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+
JEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK
vhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABAoIBAHK94ww8W0G5QIWL
Qwkc9XeGvg4eLUxVknva2Ll4fkZJxY4WveKx9OCd1lv4n7WoacYIwUGIDaQBZShW
s/eKnkmqGy+PvpC87gqL4sHvQpuqqJ1LYpxylLEFqduWOuGPUVC2Lc+QnWCycsCS
CgqZzsbMq0S+kkKRGSvw32JJneZCzqLgLNssQNVk+Gm6SI3s4jJsGPesjhnvoPaa
xZK14uFpltaA05GSTDaQeZJFEdnnb3f/eNPc2xMEfi0S2ZlJ6Q92WJEOepAetDlR
cRFi004bNyTb4Bphg8s4+9Cti5is199aFkGCRDWxeqEnc6aMY3Ezu9Qg3uttLVUd
uy830GUCgYEA7qS0X+9UH1R02L3aoANyADVbFt2ZpUwQGauw9WM92pH52xeHAw1S
ohus6FI3OC8xQq2CN525tGLUbFDZnNZ3YQHqFsfgevfnTs1//gbKXomitev0oFKh
VT+WYS4lkgYtPlXzhdGuk32q99T/wIocAguvCUY3PiA7yBz93ReyausCgYEA6+P8
bugMqT8qjoiz1q/YCfxsw9bAGWjlVqme2xmp256AKtxvCf1BPsToAaJU3nFi3vkw
ICLxUWAYoMBODJ3YnbOsIZOavdXZwYHv54JqwqFealC3DG0Du6fZYZdiY8pK+E6m
3fiYzP1WoVK5tU4bH8ibuIQvpcI8j7Gy0cV6/AMCgYBHl7fZNAZro72uLD7DVGVF
9LvP/0kR0uDdoqli5JPw12w6szM40i1hHqZfyBJy042WsFDpeHL2z9Nkb1jpeVm1
C4r7rJkGqwqElJf6UHUzqVzb8N6hnkhyN7JYkyyIQzwdgFGfaslRzBiXYxoa3BQM
9Q5c3OjDxY3JuhDa3DoVYwKBgDNqrWJLSD832oHZAEIicBe1IswJKjQfriWWsV6W
mHSbdtpg0/88aZVR/DQm+xLFakSp0jifBTS0momngRu06Dtvp2xmLQuF6oIIXY97
2ON1owvPbibSOEcWDgb8pWCU/oRjOHIXts6vxctCKeKAFN93raGphm0+Ck9T72NU
BTubAoGBAMEhI/Wy9wAETuXwN84AhmPdQsyCyp37YKt2ZKaqu37x9v2iL8JTbPEz
pdBzkA2Gc0Wdb6ekIzRrTsJQl+c/0m9byFHsRsxXW2HnezfOFX1H4qAmF6KWP0ub
M8aIn6Rab4sNPSrvKGrU6rFpv/6M33eegzldVnV9ku6uPJI1fFTC
-----END RSA PRIVATE KEY-----`)
================================================
FILE: examples/customca/main.go
================================================
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
cert, err := parseCA(_caCert, _caKey)
if err != nil {
log.Fatal(err)
}
customCaMitm := &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(cert)}
var customAlwaysMitm goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
return customCaMitm, host
}
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().HandleConnect(customAlwaysMitm)
proxy.Verbose = *verbose
log.Fatal(http.ListenAndServe(*addr, proxy))
}
func parseCA(caCert, caKey []byte) (*tls.Certificate, error) {
parsedCert, err := tls.X509KeyPair(caCert, caKey)
if err != nil {
return nil, err
}
if parsedCert.Leaf, err = x509.ParseCertificate(parsedCert.Certificate[0]); err != nil {
return nil, err
}
return &parsedCert, nil
}
================================================
FILE: examples/go.mod
================================================
module github.com/elazarl/goproxy/examples/goproxy-transparent
go 1.23
require (
github.com/coder/websocket v1.8.14
github.com/elazarl/goproxy v1.5.0
github.com/elazarl/goproxy/ext v0.0.0-20250117123040-e9229c451ab8
github.com/inconshreveable/go-vhost v1.0.0
)
require (
golang.org/x/net v0.35.0 // indirect
golang.org/x/text v0.22.0 // indirect
)
replace github.com/elazarl/goproxy => ../
================================================
FILE: examples/go.sum
================================================
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy/ext v0.0.0-20250117123040-e9229c451ab8 h1:rGxOExXmpBcmZc4ZnEXBGkcxSReZx7S9ECtuv6BtUYQ=
github.com/elazarl/goproxy/ext v0.0.0-20250117123040-e9229c451ab8/go.mod h1:q2JQCFWg+AQfe6O2cbf7LJDB48R68w+q0pBU53v02iM=
github.com/inconshreveable/go-vhost v1.0.0 h1:IK4VZTlXL4l9vz2IZoiSFbYaaqUW7dXJAiPriUN5Ur8=
github.com/inconshreveable/go-vhost v1.0.0/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: examples/goproxy-httpdump/README.md
================================================
# Trace HTTP Requests and Responses
`goproxy-httpdump` starts an HTTP proxy on :8080. It handles explicit CONNECT
requests and traces them in a "db" directory created in the proxy working
directory. Each request type and headers are logged in a "log" file, while
their bodies are dumped in files prefixed with the request session identifier.
Additionally, the example demonstrates how to:
- Log information asynchronously (see HttpLogger)
- Allow the proxy to be stopped manually while ensuring all pending requests
have been processed (in this case, logged).
Start it in one shell:
```sh
goproxy-httpdump
```
Fetch goproxy homepage in another:
```sh
http_proxy=http://127.0.0.1:8080 wget -O - \
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
```
A "db" directory should have appeared where you started the proxy, containing
two files:
- log: the request/response traces
- 1\_resp: the first response body
================================================
FILE: examples/goproxy-httpdump/httpdump.go
================================================
package main
import (
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"os"
"os/signal"
"path"
"sync"
"time"
"github.com/elazarl/goproxy"
"github.com/elazarl/goproxy/transport"
)
type FileStream struct {
path string
f *os.File
}
func NewFileStream(path string) *FileStream {
return &FileStream{path, nil}
}
func (fs *FileStream) Write(b []byte) (nr int, err error) {
if fs.f == nil {
fs.f, err = os.Create(fs.path)
if err != nil {
return 0, err
}
}
return fs.f.Write(b)
}
func (fs *FileStream) Close() error {
fmt.Println("Close", fs.path)
if fs.f == nil {
return errors.New("FileStream was never written into")
}
return fs.f.Close()
}
type Meta struct {
req *http.Request
resp *http.Response
err error
t time.Time
sess int64
bodyPath string
from string
}
func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...any) {
if *err != nil {
return
}
var n int
n, *err = fmt.Fprintf(w, pat, a...)
*nr += int64(n)
}
func write(nr *int64, err *error, w io.Writer, b []byte) {
if *err != nil {
return
}
var n int
n, *err = w.Write(b)
*nr += int64(n)
}
func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {
if m.req != nil {
fprintf(&nr, &err, w, "Type: request\r\n")
} else if m.resp != nil {
fprintf(&nr, &err, w, "Type: response\r\n")
}
fprintf(&nr, &err, w, "ReceivedAt: %v\r\n", m.t)
fprintf(&nr, &err, w, "Session: %d\r\n", m.sess)
fprintf(&nr, &err, w, "From: %v\r\n", m.from)
if m.err != nil {
// note the empty response
fprintf(&nr, &err, w, "Error: %v\r\n\r\n\r\n\r\n", m.err)
} else if m.req != nil {
fprintf(&nr, &err, w, "\r\n")
buf, err2 := httputil.DumpRequest(m.req, false)
if err2 != nil {
return nr, err2
}
write(&nr, &err, w, buf)
} else if m.resp != nil {
fprintf(&nr, &err, w, "\r\n")
buf, err2 := httputil.DumpResponse(m.resp, false)
if err2 != nil {
return nr, err2
}
write(&nr, &err, w, buf)
}
return
}
// HttpLogger is an asynchronous HTTP request/response logger. It traces
// requests and responses headers in a "log" file in logger directory and dumps
// their bodies in files prefixed with the session identifiers.
// Close it to ensure pending items are correctly logged.
type HttpLogger struct {
path string
c chan *Meta
errch chan error
}
func NewLogger(basepath string) (*HttpLogger, error) {
f, err := os.Create(path.Join(basepath, "log"))
if err != nil {
return nil, err
}
logger := &HttpLogger{basepath, make(chan *Meta), make(chan error)}
go func() {
for m := range logger.c {
if _, err := m.WriteTo(f); err != nil {
log.Println("Can't write meta", err)
}
}
logger.errch <- f.Close()
}()
return logger, nil
}
func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) {
body := path.Join(logger.path, fmt.Sprintf("%d_resp", ctx.Session))
from := ""
if ctx.UserData != nil {
from = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String()
}
if resp == nil {
resp = emptyResp
} else {
resp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body))
}
logger.LogMeta(&Meta{
resp: resp,
err: ctx.Error,
t: time.Now(),
sess: ctx.Session,
from: from})
}
var emptyResp = &http.Response{}
var emptyReq = &http.Request{}
func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) {
body := path.Join(logger.path, fmt.Sprintf("%d_req", ctx.Session))
if req == nil {
req = emptyReq
} else {
req.Body = NewTeeReadCloser(req.Body, NewFileStream(body))
}
logger.LogMeta(&Meta{
req: req,
err: ctx.Error,
t: time.Now(),
sess: ctx.Session,
from: req.RemoteAddr})
}
func (logger *HttpLogger) LogMeta(m *Meta) {
logger.c <- m
}
func (logger *HttpLogger) Close() error {
close(logger.c)
return <-logger.errch
}
// TeeReadCloser extends io.TeeReader by allowing reader and writer to be
// closed.
type TeeReadCloser struct {
r io.Reader
w io.WriteCloser
c io.Closer
}
func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser {
return &TeeReadCloser{io.TeeReader(r, w), w, r}
}
func (t *TeeReadCloser) Read(b []byte) (int, error) {
return t.r.Read(b)
}
// Close attempts to close the reader and write. It returns an error if both
// failed to Close.
func (t *TeeReadCloser) Close() error {
err1 := t.c.Close()
err2 := t.w.Close()
if err1 != nil {
return err1
}
return err2
}
// stoppableListener serves stoppableConn and tracks their lifetime to notify
// when it is safe to terminate the application.
type stoppableListener struct {
net.Listener
sync.WaitGroup
}
type stoppableConn struct {
net.Conn
wg *sync.WaitGroup
}
func newStoppableListener(l net.Listener) *stoppableListener {
return &stoppableListener{l, sync.WaitGroup{}}
}
func (sl *stoppableListener) Accept() (net.Conn, error) {
c, err := sl.Listener.Accept()
if err != nil {
return c, err
}
sl.Add(1)
return &stoppableConn{c, &sl.WaitGroup}, nil
}
func (sc *stoppableConn) Close() error {
sc.wg.Done()
return sc.Conn.Close()
}
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("l", ":8080", "on which address should the proxy listen")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = *verbose
if err := os.MkdirAll("db", 0755); err != nil {
log.Fatal("Can't create dir", err)
}
logger, err := NewLogger("db")
if err != nil {
log.Fatal("can't open log file", err)
}
tr := transport.Transport{Proxy: transport.ProxyFromEnvironment}
// For every incoming request, override the RoundTripper to extract
// connection information. Store it is session context log it after
// handling the response.
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
ctx.RoundTripper = goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (resp *http.Response, err error) {
ctx.UserData, resp, err = tr.DetailedRoundTrip(req)
return
})
logger.LogReq(req, ctx)
return req, nil
})
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
logger.LogResp(resp, ctx)
return resp
})
l, err := net.Listen("tcp", *addr)
if err != nil {
log.Fatal("listen:", err)
}
sl := newStoppableListener(l)
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt)
go func() {
<-ch
log.Println("Got SIGINT exiting")
sl.Add(1)
sl.Close()
logger.Close()
sl.Done()
}()
log.Println("Starting Proxy")
http.Serve(sl, proxy)
sl.Wait()
log.Println("All connections closed - exit")
}
================================================
FILE: examples/goproxy-transparent/README.md
================================================
# Transparent Proxy
This 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.
## Why not explicit?
Transparent 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.
## Potential Issues
Support 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.
If 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.
## Routing Rules
Example 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.
================================================
FILE: examples/goproxy-transparent/proxy.sh
================================================
#!/bin/sh
# goproxy IP
GOPROXY_SERVER="10.10.10.1"
# goproxy port
GOPROXY_PORT="3129"
GOPROXY_PORT_TLS="3128"
# DO NOT MODIFY BELOW
# Load IPTABLES modules for NAT and IP conntrack support
modprobe ip_conntrack
modprobe ip_conntrack_ftp
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
# Clean old firewall
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Write new rules
iptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT -j ACCEPT
iptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT_TLS -j ACCEPT
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT_TLS
# The following line supports using goproxy as an explicit proxy in addition
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT
iptables -t nat -A POSTROUTING -j MASQUERADE
iptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT -j DROP
iptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT_TLS -j DROP
================================================
FILE: examples/goproxy-transparent/transparent.go
================================================
package main
import (
"bufio"
"bytes"
"context"
"flag"
"fmt"
"log"
"net"
"net/http"
"net/url"
"regexp"
"github.com/elazarl/goproxy"
"github.com/inconshreveable/go-vhost"
)
func orPanic(err error) {
if err != nil {
panic(err)
}
}
func main() {
verbose := flag.Bool("v", true, "should every proxy request be logged to stdout")
http_addr := flag.String("httpaddr", ":3129", "proxy http listen address")
https_addr := flag.String("httpsaddr", ":3128", "proxy https listen address")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = *verbose
if proxy.Verbose {
log.Printf("Server starting up! - configured to listen on http interface %s and https interface %s", *http_addr, *https_addr)
}
proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Host == "" {
fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0")
return
}
req.URL.Scheme = "http"
req.URL.Host = req.Host
proxy.ServeHTTP(w, req)
})
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))).
HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
defer func() {
if e := recover(); e != nil {
ctx.Logf("error connecting to remote: %v", e)
client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
}
client.Close()
}()
clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
remote, err := connectDial(req.Context(), proxy, "tcp", req.URL.Host)
orPanic(err)
remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
for {
req, err := http.ReadRequest(clientBuf.Reader)
orPanic(err)
orPanic(req.Write(remoteBuf))
orPanic(remoteBuf.Flush())
resp, err := http.ReadResponse(remoteBuf.Reader, req)
orPanic(err)
orPanic(resp.Write(clientBuf.Writer))
orPanic(clientBuf.Flush())
}
})
go func() {
log.Fatalln(http.ListenAndServe(*http_addr, proxy))
}()
// listen to the TLS ClientHello but make it a CONNECT request instead
ln, err := net.Listen("tcp", *https_addr)
if err != nil {
log.Fatalf("Error listening for https connections - %v", err)
}
for {
c, err := ln.Accept()
if err != nil {
log.Printf("Error accepting new connection - %v", err)
continue
}
go func(c net.Conn) {
tlsConn, err := vhost.TLS(c)
if err != nil {
log.Printf("Error accepting new connection - %v", err)
}
if tlsConn.Host() == "" {
log.Printf("Cannot support non-SNI enabled clients")
return
}
connectReq := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
Opaque: tlsConn.Host(),
Host: net.JoinHostPort(tlsConn.Host(), "443"),
},
Host: tlsConn.Host(),
Header: make(http.Header),
RemoteAddr: c.RemoteAddr().String(),
}
resp := dumbResponseWriter{tlsConn}
proxy.ServeHTTP(resp, connectReq)
}(c)
}
}
// copied/converted from https.go
func dial(ctx context.Context, proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
if proxy.Tr.DialContext != nil {
return proxy.Tr.DialContext(ctx, network, addr)
}
var d net.Dialer
return d.DialContext(ctx, network, addr)
}
// copied/converted from https.go
func connectDial(ctx context.Context, proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
if proxy.ConnectDial == nil {
return dial(ctx, proxy, network, addr)
}
return proxy.ConnectDial(network, addr)
}
type dumbResponseWriter struct {
net.Conn
}
func (dumb dumbResponseWriter) Header() http.Header {
panic("Header() should not be called on this ResponseWriter")
}
func (dumb dumbResponseWriter) Write(buf []byte) (int, error) {
if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) {
return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request
}
return dumb.Conn.Write(buf)
}
func (dumb dumbResponseWriter) WriteHeader(code int) {
panic("WriteHeader() should not be called on this ResponseWriter")
}
func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil
}
================================================
FILE: examples/hijack/README.md
================================================
# Hijack
In this example we intercept the data of an HTTP request and decide to
modify them before sending to the client.
In this mode, we take over on the raw connection and we could send any
data that we want.
Curl example:
```
$ curl -x localhost:8080 http://google.it -v -k -p
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* CONNECT tunnel: HTTP/1.1 negotiated
* allocate connect buffer
* Establish HTTP proxy tunnel to google.it:80
> CONNECT google.it:80 HTTP/1.1
> Host: google.it:80
> User-Agent: curl/8.9.1
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Ok
<
* CONNECT phase completed
* CONNECT tunnel established, response 200
> GET / HTTP/1.1
> Host: google.it
> User-Agent: curl/8.9.1
> Accept: */*
>
< HTTP/1.1 200 OK
< test: 1234
< Content-Length: 0
```
================================================
FILE: examples/hijack/main.go
================================================
package main
import (
"bufio"
"flag"
"log"
"net"
"net/http"
"regexp"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
// Reject all requests to baidu
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("baidu.*:443$"))).
HandleConnect(goproxy.AlwaysReject)
// Instead of returning the Internet response, send custom data from
// our proxy server, using connection hijack
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
client.Write([]byte("HTTP/1.1 200 Ok\r\n\r\n"))
w := bufio.NewWriter(client)
resp := &http.Response{
StatusCode: http.StatusOK,
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{
"test": {"1234"},
},
}
resp.Write(w)
w.Flush()
client.Close()
})
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
proxy.Verbose = *verbose
log.Fatal(http.ListenAndServe(*addr, proxy))
}
================================================
FILE: examples/html-parser/README.md
================================================
# HTML Parser
`html-parser` starts an HTTP proxy on :8080.
It checks HTML responses, looks for scripts referencing jQuery library
and log warnings if different versions of the library are being used
for a given host.
This is an example of how a proxy can parse the received responses and
manipulate them to do useful actions.
Start the server:
```sh
go build
html-parser
```
Make a test request in another shell:
```sh
http_proxy=http://127.0.0.1:8080 wget -O - \
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
```
Goproxy example homepage contains jQuery and a mix of JQuery plugins.
First the proxy reports the first use of jQuery it detects for the domain.
Then, because the regular expression matching the jQuery sources is imprecise,
it reports a mismatch with a plugin reference:
```sh
2015/04/11 11:23:02 [001] WARN: ripper234.com uses //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
2015/04/11 11:23:02 [001] WARN: In http://ripper234.com/p/introducing-goproxy-light-http-proxy/, \
Contradicting jqueries //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js \
http://ripper234.wpengine.netdna-cdn.com/wp-content/plugins/wp-ajax-edit-comments/js/jquery.colorbox.min.js?ver=5.0.36
```
================================================
FILE: examples/html-parser/jquery1.html
================================================
<!doctype html>
<html>
<head>
<script src="jquery.1.4.js"></script>
</head>
<body/>
</html>
================================================
FILE: examples/html-parser/jquery2.html
================================================
<!doctype html>
<html>
<head>
<script src="jquery.1.3.js"></script>
</head>
<body/>
</html>
================================================
FILE: examples/html-parser/jquery_homepage.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>jQuery: The Write Less, Do More, JavaScript Library</title>
<link rel="stylesheet" href="http://static.jquery.com/files/rocker/css/reset.css" type="text/css" />
<link rel="stylesheet" href="http://static.jquery.com/files/rocker/css/screen.css" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script>!window.jQuery && document.write('<script src="http://code.jquery.com/jquery-1.4.2.min.js"><\/script>');</script>
<script src="http://static.jquery.com/files/rocker/scripts/custom.js"></script>
<link rel="alternate" type="application/rss+xml" title="jQuery Blog" href="http://jquery.com/blog/feed/" />
<link rel="shortcut icon" href="http://static.jquery.com/favicon.ico" type="image/x-icon"/>
</head>
<body>
<div id="jq-siteContain">
<div id="jq-header">
<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>
<div id="jq-primaryNavigation">
<ul>
<li class="jq-jquery jq-current"><a href="http://jquery.com/" title="jQuery Home">jQuery</a></li>
<li class="jq-ui"><a href="http://jqueryui.com/" title="jQuery UI">UI</a></li>
<li class="jq-mobile"><a href="http://jquerymobile.com/" title="jQuery Mobile">Mobile</a></li>
<li class="jq-plugins"><a href="http://plugins.jquery.com/" title="jQuery Plugins">Plugins</a></li>
<li class="jq-meetup"><a href="http://meetups.jquery.com/" title="jQuery Meetups">Meetups</a></li>
<li class="jq-forum"><a href="http://forum.jquery.com/" title="jQuery Forum">Forum</a></li>
<li class="jq-blog"><a href="http://blog.jquery.com/" title="jQuery Blog">Blog</a></li>
<li class="jq-about"><a href="http://jquery.org/about" title="About jQuery">About</a></li>
<li class="jq-donate"><a href="http://jquery.org/donate" title="Donate to jQuery">Donate</a></li>
</ul>
</div><!-- /#primaryNavigation -->
<div id="jq-secondaryNavigation">
<ul>
<li class="jq-download jq-first"><a href="http://docs.jquery.com/Downloading_jQuery">Download</a></li>
<li class="jq-documentation"><a href="http://docs.jquery.com">Documentation</a></li>
<li class="jq-tutorials"><a href="http://docs.jquery.com/Tutorials">Tutorials</a></li>
<li class="jq-bugTracker"><a href="http://dev.jquery.com/">Bug Tracker</a></li>
<li class="jq-discussion jq-last"><a href="http://docs.jquery.com/Discussion">Discussion</a></li>
</ul>
</div><!-- /#secondaryNavigation -->
</div><!-- /#header -->
<div id="jq-content" class="jq-clearfix">
<div id="jq-intro" class="jq-clearfix">
<h2><span class="jq-jquery"><span>jQuery</span></span> is a new kind of JavaScript Library.</h2>
<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>
<ul class="jq-checkpoints jq-clearfix">
<li><a href="http://docs.jquery.com/Tutorials" title="Lightweight Footprint" class="jq-thickbox">Lightweight Footprint</a>
<div class="jq-checkpointSubhead">
<p>About 31KB in size <em>(Minified and Gzipped)</em></p>
</div>
</li>
<li><a href="http://docs.jquery.com/Tutorials" title="CSS3 Compliant" class="jq-thickbox">CSS3 Compliant</a>
<div class="jq-checkpointSubhead">
<p>Supports CSS 1-3 selectors and more!</p>
</div>
</li>
<li><a href="http://docs.jquery.com/Tutorials" title="Cross-browser" class="jq-thickbox">Cross-browser</a>
<div class="jq-checkpointSubhead">
<p>IE 6.0+, FF 3.6+, Safari 5.0+, Opera, Chrome</p>
</div>
</li>
</ul>
</div><!-- /#intro -->
<div id="jq-download">
<h2>Grab the latest version!</h2>
<form action="" method="get">
<fieldset>
<legend>Choose your compression level:</legend>
<div id="jq-compression" class="jq-clearfix">
<input type="radio" name="name" value="http://code.jquery.com/jquery-1.7.2.min.js" id="jq-production" checked="checked" />
<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>
<label for="jq-production">Production <em>(<strong>32KB</strong>, Minified and Gzipped)</em></label>
<input type="radio" name="name" value="http://code.jquery.com/jquery-1.7.2.js" id="jq-development" />
<a class="jq-radioToggle name" href="http://code.jquery.com/jquery-1.7.2.js">jquery-1.7.2.js</a>
<label for="jq-development">Development <em>(<strong>247KB</strong>, Uncompressed Code)</em></label>
</div>
<button type="submit" name="downloadBtn" id="jq-downloadBtn"><span>Download</span></button>
<p class="jq-version"><strong>Current Release:</strong> v1.7.2</p>
</fieldset>
</form>
<script>
jQuery("#jq-download form").submit(function(){
window.location = jQuery(this).find("input:checked").val();
return false;
});
</script>
</div><!-- /#download -->
<div id="jq-whosUsing">
<h2 class="jq-whosUsing">Who's using jQuery?</h2>
<ul class="jq-whosUsing">
<li><a href="http://www.google.com" class="jq-google" title="Google">Google</a></li>
<li><a href="http://www.dell.com" class="jq-dell" title="Dell">Dell</a></li>
<li><a href="http://www.bankofamerica.com" class="jq-boa" title="Bank of America">Bank of America</a></li>
<li><a href="http://www.mlb.com" class="jq-mlb" title="Major League Baseball">Major League Baseball</a></li>
<li><a href="http://www.digg.com" class="jq-digg" title="Digg">Digg</a></li>
<li><a href="http://www.nbc.com" class="jq-nbc" title="NBC">NBC</a></li>
<li><a href="http://www.cbs.com" class="jq-cbs" title="CBS News">CBS News</a></li>
<li><a href="http://www.netflix.com" class="jq-netflix" title="Netflix">Netflix</a></li>
<li><a href="http://www.technorati.com" class="jq-technorati" title="Technorati">Technorati</a></li>
<li><a href="http://www.mozilla.org" class="jq-mozilla" title="Mozilla">Mozilla</a></li>
<li><a href="http://www.wordpress.org" class="jq-wordpress" title="Wordpress">Wordpress</a></li>
<li><a href="http://www.drupal.org" class="jq-drupal" title="Drupal">Drupal</a></li>
</ul>
</div><!-- /#jq-whosUsing -->
<div id="jq-learnjQuery" class="jq-clearfix">
<div id="jq-learnNow">
<h2>Learn <span class="jq-jquery"><span>jQuery</span></span> Now!</h2>
<p>What does jQuery code look like? Here's the quick and dirty:</p>
<div class="jq-codeDemo jq-clearfix">
<pre><code>$("p.neat").addClass("ohmy").show("slow");</code></pre>
<a href="http://docs.jquery.com/Tutorials" class="jq-runCode">Run Code</a>
<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>
</div>
</div><!-- /#learnNow -->
<div id="jq-resources" class="clearfix">
<h2>jQuery Resources</h2>
<div class="jq-gettingStarted">
<h3>Getting Started With jQuery</h3>
<ul>
<li><a href="http://docs.jquery.com/How_jQuery_Works">How jQuery Works</a></li>
<li><a href="http://docs.jquery.com/Tutorials">Tutorials</a></li>
<li><a href="http://docs.jquery.com/Using_jQuery_with_Other_Libraries">Using jQuery with other libraries</a></li>
<li><a href="http://docs.jquery.com/">jQuery Documentation</a></li>
</ul>
</div>
<div class="jq-devResources">
<h3>Developer Resources</h3>
<ul>
<li><a href="http://docs.jquery.com/Discussion">Mailing List</a></li>
<li><a href="http://docs.jquery.com/Downloading_jQuery">Source code / Git</a></li>
<li><a href="http://docs.jquery.com/Plugins/Authoring">Plugin Authoring</a></li>
<li><a href="http://dev.jquery.com/newticket/">Submit a New Bug Report</a></li>
</ul>
</div>
</div><!-- /#resources -->
</div><!-- /#learnjQuery -->
<div id="jq-books" style="width:auto; float: none">
<h2>Books About jQuery</h2>
<ul>
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
<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>
<h3><a href="http://link.packtpub.com/S3Fr9Q">Learning jQuery Third Edition</a></h3>
<div class="jq-author">Karl Swedberg and <br />Jonathan Chaffer</div>
<a href="http://link.packtpub.com/S3Fr9Q" class="jq-buyNow">Buy Now</a>
</li>
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
<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>
<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>
<div class="jq-author">Dan Wellman</div>
<a href="http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15" class="jq-buyNow">Buy Now</a>
</li>
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
<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>
<h3><a href="http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz">jQuery Plugin Development Beginner's Guide</a></h3>
<div class="jq-author">Guilio Bai</div>
<a href="http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz" class="jq-buyNow">Buy Now</a>
</li>
<li class="jq-clearfix" style="width:270px;float:left;clear:left;">
<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>
<h3><a href="http://www.manning.com/affiliate/idevaffiliate.php?id=648_176">jQuery in Action</a></h3>
<div class="jq-author">Bear Bibeault
<br />and Yehuda Katz</div>
<a href="http://www.manning.com/affiliate/idevaffiliate.php?id=648_176" class="jq-buyNow">Buy Now</a>
</li>
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
<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>
<h3><a href="http://jqueryenlightenment.com/">jQuery Enlightenment</a></h3>
<div class="jq-author">Cody Lindley</div>
<a href="http://jqueryenlightenment.com/" class="jq-buyNow">Buy Now</a>
</li>
</ul>
</div><!-- /#news -->
</div><!-- /#content -->
<div id="jq-footer" class="jq-clearfix">
<div id="jq-credits">
<p id="jq-copyright">© 2010 <a href="http://jquery.org/">The jQuery Project</a></p>
<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>
</div>
<div id="jq-footerNavigation">
<ul>
<li class="jq-download jq-first"><a href="http://docs.jquery.com/Downloading_jQuery">Download</a></li>
<li class="jq-documentation"><a href="http://docs.jquery.com">Documentation</a></li>
<li class="jq-tutorials"><a href="http://docs.jquery.com/Tutorials">Tutorials</a></li>
<li class="jq-bugTracker"><a href="http://dev.jquery.com/">Bug Tracker</a></li>
<li class="jq-discussion jq-last"><a href="http://docs.jquery.com/Discussion">Discussion</a></li>
</ul>
</div><!-- /#secondaryNavigation -->
</div><!-- /#footer -->
</div><!-- /#siteContain -->
<script src="http://static.jquery.com/donate/donate.js" type="text/javascript"></script>
<script type="text/javascript">
var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-1076265-1']); _gaq.push(['_trackPageview']); _gaq.push(['_setDomainName', '.jquery.com']);
(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);})();
</script>
</body>
</html>
================================================
FILE: examples/html-parser/jquery_test.go
================================================
package main
import (
"bytes"
"io"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
)
func equal(u, v []string) bool {
if len(u) != len(v) {
return false
}
for i := range u {
if u[i] != v[i] {
return false
}
}
return true
}
func readFile(fname string, t *testing.T) string {
b, err := os.ReadFile(fname)
if err != nil {
t.Fatal("readFile", err)
}
return string(b)
}
func TestDefectiveScriptParser(t *testing.T) {
if l := len(findScriptSrc(`<!DOCTYPE HTML>
<html>
<body>
<video width="320" height="240" controls="controls">
<source src="movie.mp4" type="video/mp4" />
<source src="movie.ogg" type="video/ogg" />
<source src="movie.webm" type="video/webm" />
Your browser does not support the video tag.
</video>
</body>
</html>`)); l != 0 {
t.Fail()
}
urls := findScriptSrc(readFile("w3schools.html", t))
if !equal(urls, []string{"http://partner.googleadservices.com/gampad/google_service.js",
"//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"}) {
t.Error("w3schools.html", "src scripts are not recognized", urls)
}
urls = findScriptSrc(readFile("jquery_homepage.html", t))
if !equal(urls, []string{"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
"http://code.jquery.com/jquery-1.4.2.min.js",
"http://static.jquery.com/files/rocker/scripts/custom.js",
"http://static.jquery.com/donate/donate.js"}) {
t.Error("jquery_homepage.html", "src scripts are not recognized", urls)
}
}
func proxyWithLog() (*http.Client, *bytes.Buffer) {
proxy := NewJQueryVersionProxy()
proxyServer := httptest.NewServer(proxy)
buf := new(bytes.Buffer)
proxy.Logger = log.New(buf, "", 0)
proxyUrl, _ := url.Parse(proxyServer.URL)
tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
client := &http.Client{Transport: tr}
return client, buf
}
func get(t *testing.T, server *httptest.Server, client *http.Client, url string) {
resp, err := client.Get(server.URL + url)
if err != nil {
t.Fatal("cannot get proxy", err)
}
io.ReadAll(resp.Body)
resp.Body.Close()
}
func TestProxyServiceTwoVersions(t *testing.T) {
var fs = httptest.NewServer(http.FileServer(http.Dir(".")))
defer fs.Close()
client, buf := proxyWithLog()
get(t, fs, client, "/w3schools.html")
get(t, fs, client, "/php_man.html")
if buf.String() != "" &&
!strings.Contains(buf.String(), " uses jquery ") {
t.Error("shouldn't warn on a single URL", buf.String())
}
get(t, fs, client, "/jquery1.html")
warnings := buf.String()
if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js") ||
!strings.Contains(warnings, "jquery.1.4.js") ||
!strings.Contains(warnings, "Contradicting") {
t.Error("contradicting jquery versions (php_man.html, w3schools.html) does not issue warning", warnings)
}
}
func TestProxyService(t *testing.T) {
var fs = httptest.NewServer(http.FileServer(http.Dir(".")))
defer fs.Close()
client, buf := proxyWithLog()
get(t, fs, client, "/jquery_homepage.html")
warnings := buf.String()
if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js") ||
!strings.Contains(warnings, "http://code.jquery.com/jquery-1.4.2.min.js") ||
!strings.Contains(warnings, "Contradicting") {
t.Error("contradicting jquery versions does not issue warning")
}
}
================================================
FILE: examples/html-parser/main.go
================================================
package main
import (
"github.com/elazarl/goproxy"
"github.com/elazarl/goproxy/ext/html"
"log"
"net/http"
"regexp"
)
var (
// who said we can't parse HTML with regexp?
scriptMatcher = regexp.MustCompile(`(?i:<script\s+)`)
srcAttrMatcher = regexp.MustCompile(`^(?i:[^>]*\ssrc=["']([^"']*)["'])`)
)
// findScripts returns all sources of HTML script tags found in input text.
func findScriptSrc(html string) []string {
srcs := make([]string, 0)
matches := scriptMatcher.FindAllStringIndex(html, -1)
for _, match := range matches {
// -1 to capture the whitespace at the end of the script tag
srcMatch := srcAttrMatcher.FindStringSubmatch(html[match[1]-1:])
if srcMatch != nil {
srcs = append(srcs, srcMatch[1])
}
}
return srcs
}
// NewJQueryVersionProxy creates a proxy checking responses HTML content, looks
// for scripts referencing jQuery library and emits warnings if different
// versions of the library are being used for a given host.
func NewJQueryVersionProxy() *goproxy.ProxyHttpServer {
proxy := goproxy.NewProxyHttpServer()
m := make(map[string]string)
jqueryMatcher := regexp.MustCompile(`(?i:jquery\.)`)
proxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString(
func(s string, ctx *goproxy.ProxyCtx) string {
for _, src := range findScriptSrc(s) {
if !jqueryMatcher.MatchString(src) {
continue
}
prev, ok := m[ctx.Req.Host]
if ok {
if prev != src {
ctx.Warnf("In %v, Contradicting jqueries %v %v",
ctx.Req.URL, prev, src)
break
}
} else {
ctx.Warnf("%s uses jquery %s", ctx.Req.Host, src)
m[ctx.Req.Host] = src
}
}
return s
}))
return proxy
}
func main() {
proxy := NewJQueryVersionProxy()
log.Fatal(http.ListenAndServe(":8080", proxy))
}
================================================
FILE: examples/html-parser/php_man.html
================================================
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://purl.org/NET/erdf/profile">
<title>PHP: PHP Manual - Manual</title>
<style type="text/css" media="all">
@import url("http://static.php.net/www.php.net/styles/site.css");
@import url("http://static.php.net/www.php.net/styles/phpnet.css");
</style>
<!--[if IE]><![if gte IE 6]><![endif]-->
<style type="text/css" media="print">
@import url("http://static.php.net/www.php.net/styles/print.css");
</style>
<!--[if IE]><![endif]><![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="shortcut icon" href="http://static.php.net/www.php.net/favicon.ico" />
<link rel="contents" href="index.php" />
<link rel="index" href="" />
<link rel="prev" href="" />
<link rel="next" href="" />
<link rel="schema.dc" href="http://purl.org/dc/elements/1.1/" />
<link rel="schema.rdfs" href="http://www.w3.org/2000/01/rdf-schema#" />
<link rev="canonical" rel="self alternate shorter shorturl shortlink" href="http://php.net/index" />
<link rel="license" href="http://creativecommons.org/licenses/by/3.0/" about="#content" />
<link rel="alternate" href="/manual/en/feeds/index.atom" type="application/atom+xml" />
<link rel="canonical" href="http://php.net/manual/en/index.php" />
<script type="text/javascript" src="http://static.php.net/www.php.net/userprefs.js"></script>
<base href="http://www.php.net/manual/en/index.php" />
<meta http-equiv="Content-language" content="en" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var toggleImage = function(elem) {
if ($(elem).hasClass("shown")) {
$(elem).removeClass("shown").addClass("hidden");
$("img", elem).attr("src", "/images/notes-add.gif");
}
else {
$(elem).removeClass("hidden").addClass("shown");
$("img", elem).attr("src", "/images/notes-reject.gif");
}
};
$(".refsect1 h3.title").each(function() {
url = "https://bugs.php.net/report.php?bug_type=Documentation+problem&manpage=" + $(this).parent().parent().attr("id") + "%23" + $(this).parent().attr("id");
$(this).parent().prepend("<div class='reportbug'><a href='" + url + "'>Report a bug</a></div>");
$(this).prepend("<a class='toggler shown' href='#'><img src='/images/notes-reject.gif' alt='reject note' /></a> ");
});
$("#usernotes .head").each(function() {
$(this).prepend("<a class='toggler shown' href='#'><img src='/images/notes-reject.gif' alt='reject note' /></a> ");
});
$(".refsect1 h3.title .toggler").click(function() {
$(this).parent().siblings().slideToggle("slow");
toggleImage(this);
return false;
});
$("#usernotes .head .toggler").click(function() {
$(this).parent().next().slideToggle("slow");
toggleImage(this);
return false;
});
});
</script>
</head>
<body>
<div id="headnav">
<a href="/" rel="home"><img src="http://static.php.net/www.php.net/images/php.gif"
alt="PHP" width="120" height="67" id="phplogo" /></a>
<div id="headmenu">
<a href="/downloads.php">downloads</a> |
<a href="/docs.php">documentation</a> |
<a href="/FAQ.php">faq</a> |
<a href="/support.php">getting help</a> |
<a href="/mailing-lists.php">mailing lists</a> |
<a href="/license">licenses</a> |
<a href="https://wiki.php.net/">wiki</a> |
<a href="https://bugs.php.net/">reporting bugs</a> |
<a href="/sites.php">php.net sites</a> |
<a href="/links.php">links</a> |
<a href="/conferences/">conferences</a> |
<a href="/my.php">my php.net</a>
</div>
</div>
<div id="headsearch">
<form method="post" action="/search.php" id="topsearch">
<p>
<span title="Keyboard shortcut: Alt+S (Win), Ctrl+S (Apple)">
<span class="shortkey">s</span>earch for
</span>
<input type="text" name="pattern" value="" size="30" accesskey="s" />
<span>in the</span>
<select name="show">
<option value="all" >all php.net sites</option>
<option value="local" >this mirror only</option>
<option value="quickref" selected="selected">function list</option>
<option value="manual" >online documentation</option>
<option value="bugdb" >bug database</option>
<option value="news_archive">Site News Archive</option>
<option value="changelogs">All Changelogs</option>
<option value="pear" >just pear.php.net</option>
<option value="pecl" >just pecl.php.net</option>
<option value="talks" >just talks.php.net</option>
<option value="maillist" >general mailing list</option>
<option value="devlist" >developer mailing list</option>
<option value="phpdoc" >documentation mailing list</option>
</select>
<input type="image"
src="http://static.php.net/www.php.net/images/small_submit_white.gif"
class="submit" alt="search" />
<input type="hidden" name="lang" value="en" />
</p>
</form>
</div>
<div id="layout_2">
<div id="leftbar">
<!--UdmComment-->
<ul class="toc">
<li class="header home"><a href="index.php">PHP Manual</a></li>
</ul><!--/UdmComment-->
</div>
<div id="content" class="manual/en">
<!--UdmComment-->
<div class="manualnavbar manualnavbar_top">
<span class="next">
</span>
<span class="prev">
</span>
<hr />
<span class="lastupdated">[<a href="https://edit.php.net/?project=PHP&perm=en/index.php">edit</a>] Last updated: Fri, 23 Mar 2012</span>
<div class="langchooser">
<form action="/manual/change.php" method="get">
<p>view this page in </p><fieldset><select name="page">
<option value="pt_BR/index.php">Brazilian Portuguese</option>
<option value="zh/index.php">Chinese (Simplified)</option>
<option value="fr/index.php">French</option>
<option value="de/index.php">German</option>
<option value="ja/index.php">Japanese</option>
<option value="pl/index.php">Polish</option>
<option value="ro/index.php">Romanian</option>
<option value="ru/index.php">Russian</option>
<option value="fa/index.php">Persian</option>
<option value="es/index.php">Spanish</option>
<option value="tr/index.php">Turkish</option>
<option value="help-translate.php">Other</option>
</select>
<input type="image" src="http://static.php.net/www.php.net/images/small_submit.gif" id="changeLangImage" alt="Change language" />
</fieldset></form>
</div>
</div>
<!--/UdmComment-->
<div id="index" class="set">
<h1 class="title">PHP Manual</h1>
<div class="info">
<div class="authorgroup" id="authors">
<div class="author vcard"><strong class="by">by</strong>:<br />
<span class="personname fn">
<span class="firstname given-name">Mehdi</span> <span class="surname family-name">Achour</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Friedhelm</span> <span class="surname family-name">Betz</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Antony</span> <span class="surname family-name">Dovgal</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Nuno</span> <span class="surname family-name">Lopes</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Hannes</span> <span class="surname family-name">Magnusson</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Georg</span> <span class="surname family-name">Richter</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Damien</span> <span class="surname family-name">Seguy</span>
</span>
</div>
<div class="author vcard">
<span class="personname fn">
<span class="firstname given-name">Jakub</span> <span class="surname family-name">Vrana</span>
</span>
</div>
<div class="othercredit">
<span class="personname fn">
<span class="othername">
<a href="preface.php#contributors" class="link">And several others</a>
</span>
</span>
</div>
</div>
<div class="pubdate">2012-03-23</div>
<div class="authorgroup" id="editors">
<div class="editor vcard"><strong class="editedby">Edited By</strong>:
<span class="personname fn">
<span class="firstname given-name">Philip</span> <span class="surname family-name">Olson</span>
</span>
</div>
</div>
<div class="copyright">©
<span class="year">1997-2012</span>
<span class="holder">the PHP Documentation Group</span>
</div>
</div>
<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 "counter" 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 />
<div id="usernotes">
<div class="head">
<span class="action"><a href="/manual/add-note.php?sect=index&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&redirect=http://www.php.net/manual/en/index.php">add a note</a></small></span>
<small>User Contributed Notes</small>
<strong>PHP Manual</strong>
</div>
<div class="note">There are no user contributed notes for this page.</div></div><br />
</div>
<div class="cleaner"> </div>
</div>
<div id="footnav">
<a href="/source.php?url=/manual/en/index.php">show source</a> |
<a href="/credits.php">credits</a> |
<a href="/stats/">stats</a> |
<a href="/sitemap.php">sitemap</a> |
<a href="/contact.php">contact</a> |
<a href="/contact.php#ads">advertising</a> |
<a href="/mirrors.php">mirror sites</a>
</div>
<div id="pagefooter">
<div id="copyright">
<a href="/copyright.php">Copyright © 2001-2012 The PHP Group</a><br />
All rights reserved.
</div>
<div id="thismirror">
<a href="/mirror.php">This mirror</a> generously provided by:
<a href="http://developer.yahoo.com/">Yahoo! Inc.</a><br />
Last updated: Wed Mar 28 09:41:05 2012 UTC
</div>
</div>
<!--[if IE 6]>
<script type="text/javascript">
/*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; }
var IE6UPDATE_OPTIONS = {
icons_path: "/ie6update/images/"
}
</script>
<script type="text/javascript" src="/ie6update/ie6update.js"></script>
<![endif]-->
</body>
</html>
================================================
FILE: examples/html-parser/w3schools.html
================================================
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>HTML5 Tutorial</title>
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<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" />
<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." />
<script type="text/javascript">
<!--
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
//-->
</script>
<script type="text/javascript">
<!--
function searchfield_focus(obj)
{
obj.style.color=""
obj.style.fontStyle=""
if (obj.value=="Search w3schools.com")
{obj.value=""}
}
var pageTracker = _gat._getTracker("UA-3855518-1");
pageTracker._initData();
pageTracker._trackPageview();
var addr=document.location.href;
function displayError()
{
document.getElementById("err_url").value=addr;
document.getElementById("err_form").style.display="block";
document.getElementById("err_desc").focus();
hideSent();
}
function hideError()
{
document.getElementById("err_form").style.display="none";
}
function hideSent()
{
document.getElementById("err_sent").style.display="none";
}
function sendErr()
{
var xmlhttp;
var err_url=document.getElementById("err_url").value;
var err_email=document.getElementById("err_email").value;
var err_desc=document.getElementById("err_desc").value;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("POST","/err_sup.asp",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("err_url=" + err_url + "&err_email=" + err_email + "&err_desc=" + escape(err_desc));
hideError();
document.getElementById("err_sent").style.display="block";
}
function click_expandingMenuHeader(obj,sectionName)
{
var x=document.getElementById("cssprop_" + sectionName).parentNode.className;
if (x.indexOf("expandingMenuNotSelected")>-1)
{
x=x.replace("expandingMenuNotSelected","expandingMenuSelected");
document.getElementById("cssprop_" + sectionName).parentNode.className=x;
document.getElementById("cssprop_" + sectionName).style.display="block";
}
else
{
x=x.replace("expandingMenuSelected","expandingMenuNotSelected");
document.getElementById("cssprop_" + sectionName).parentNode.className=x;
document.getElementById("cssprop_" + sectionName).style.display="none";
}
}
//-->
</script>
<!--[if lt IE 7]>
<style>
#leftcolumn{margin-left:0}
</style>
<![endif]-->
<link rel="stylesheet" type="text/css" href="/stdtheme.css" />
<style>
#html5headline
{
font-size:24px;
text-align:center;
background-color:#D9D9D9;
margin:0;
padding:0;
height:32px;
}
div.html5header
{
border:5px solid #D9D9D9;
width:625px;
height:379px;
margin:0;
padding:0;
}
div.html5header #header_image
{
width:290px;
height:235px;
float:left;
border:10px solid #D9D9D9;
}
div.html5header #header_image div
{
width:290px;
height:235px;
margin:0;
padding:0;
}
div.html5header #header_text
{
width:275px;
height:235px;
float:left;
margin:0;
padding:0;
padding-left:10px;
padding-right:10px;
border:10px solid #D9D9D9;
}
div.html5header #header_icons
{
width:628px;
height:84px;
clear:both;
}
div.html5header #header_icons ul
{
list-style-type:none;
margin:0;
padding:0;
overflow:hidden;
}
div.html5header #header_icons li
{
float:left;
width:104px;
height:92px;
}
div.html5header #header_icons #header_html5{background:url('img_logo_map.gif') 0 0;background-repeat:no-repeat;}
div.html5header #header_icons #header_multimedia{background:url('img_logo_map.gif') -104px 0;background-repeat:no-repeat;}
div.html5header #header_icons #header_3d{background:url('img_logo_map.gif') -208px 0;background-repeat:no-repeat;}
div.html5header #header_icons #header_offline{background:url('img_logo_map.gif') -312px 0;background-repeat:no-repeat;}
div.html5header #header_icons #header_form{background:url('img_logo_map.gif') -418px 0;background-repeat:no-repeat;}
div.html5header #header_icons #header_css3{background:url('img_logo_map.gif') -521px 0;background-repeat:no-repeat;}
#header_text
{
font-size:120%;
}
#header_text h2
{
text-align:center;
}
div.html5header #header_icons #header_html5
{
background:url('img_logo_map.gif') 0 -92px;
background-repeat:no-repeat;
}
</style>
<script>
selIcon="html5";
function changeHeader(logoId,imgPos)
{
x=document.getElementById("header_text").childNodes;
for (i=0;i<x.length;i++)
{
if(x[i].nodeType==1)
{
x[i].style.display="none";
}
}
x=document.getElementById("header_image").childNodes;
for (i=0;i<x.length;i++)
{
if(x[i].nodeType==1)
{
x[i].style.display="none";
}
}
try
{
document.getElementById('html5_vid').pause();
}
catch(er)
{
}
x=document.getElementById("header_icons_list").childNodes;
document.getElementById("header_html5").style.backgroundPosition="0 0"
document.getElementById("header_multimedia").style.backgroundPosition="-104px 0"
document.getElementById("header_3d").style.backgroundPosition="-208px 0"
document.getElementById("header_offline").style.backgroundPosition="-312px 0"
document.getElementById("header_form").style.backgroundPosition="-418px 0"
document.getElementById("header_css3").style.backgroundPosition="-521px 0"
bgText="header_text_"+logoId;
bgImage="header_image_"+logoId;
bgLogo="header_"+logoId;
document.getElementById(bgImage).style.display="block";
document.getElementById(bgText).style.display="inline";
document.getElementById(bgLogo).style.backgroundPosition=imgPos+"px -92px"
}
function iconHover(logoId,imgPos)
{
bgLogo="header_"+logoId;
document.getElementById(bgLogo).style.backgroundPosition=imgPos+"px -92px"
}
function iconHoverOut(logoId,imgPos)
{
if (selIcon!=logoId)
{
bgLogo="header_"+logoId;
document.getElementById(bgLogo).style.backgroundPosition=imgPos+"px 0"
}
}
function playVideo()
{
try
{
document.getElementById('html5_vid').play()
}
catch(er)
{
document.getElementById('header_image_multimedia').innerHTML="";
document.getElementById('header_image_multimedia').style.backgroundImage="url('img_html5_multimedia.jpg')";
}
}
</script>
</head>
<body>
<div style="position:relative;width:100%;margin-top:0px;margin-bottom:0px;">
<a id="top"></a>
<div style="width:960px;margin-top:5px;margin-left:auto;margin-right:auto">
<div style="width:960px;height:74px;margin:0;padding:0;">
<div style="width:340px;text-align:left;float:left;">
<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>
</div>
<div style="width:300px;float:right;text-align:right;margin-top:20px;margin-right:14px;margin-bottom:5px;">
<div id="google_translate_element" style="display:none"></div>
<div id="translate_link" style="margin-bottom:14px">
<a href="#" class="topnav"
onclick="document.getElementById('google_translate_element').style.display='inline';document.getElementById('translate_link').style.display='none';return false;">
TRANSLATE
</a>
</div>
<form style="font-size:11px;" method="get" name="searchform" action="http://www.google.com/search" target="_blank">
<input type="hidden" name="sitesearch" value="www.w3schools.com" />
<input onfocus="searchfield_focus(this)" style="width:150px;color:#808080;font-style:italic;margin:0;"
type="text" name="as_q" size="20" value="Search w3schools.com" /><input type="submit" style="margin:0;" value="Search" title="Search" />
</form>
</div>
</div>
<div id="topnav" style="clear:both;width:960px;height:25px;">
<div style="float:left;width:400px;word-spacing:12px;font-size:90%;padding-left:15px;padding-top:6px;white-space:nowrap;text-align:left;">
<a class="topnav" href="/default.asp" target="_top">HOME </a>
<a class="topnav" href="/html/default.asp" target="_top">HTML </a>
<a class="topnav" href="/css/default.asp" target="_top">CSS </a>
<a class="topnav" href="/xml/default.asp" target="_top">XML </a>
<a class="topnav" href="/js/default.asp" target="_top">JAVASCRIPT </a>
<a class="topnav" href="/asp/default.asp" target="_top">ASP </a>
<a class="topnav" href="/php/default.asp" target="_top">PHP </a>
<a class="topnav" href="/sql/default.asp" target="_top">SQL </a>
<a class="topnav" href="/sitemap/default.asp" target="_top">MORE...</a>
</div>
<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;">
<a class="topnav" href="/sitemap/default.asp#references" target="_top">REFERENCES</a> |
<a class="topnav" href="/sitemap/default.asp#examples" target="_top">EXAMPLES</a> |
<a class="topnav" href="/forum/default.asp" target="_top">FORUM</a> |
<a class="topnav" href="/about/default.asp" target="_top">ABOUT</a>
</div>
</div>
<div style="width:960px;height:94px;position:relative;margin-left:auto;margin-right:auto;margin:0px;padding:0px;overflow:hidden">
<script type='text/javascript' src='http://partner.googleadservices.com/gampad/google_service.js'>
</script>
<script type='text/javascript'>
GS_googleAddAdSenseService("ca-pub-3440800076797949");
GS_googleEnableAllServices();
</script>
<script type='text/javascript'>
GA_googleAddSlot("ca-pub-3440800076797949", "MainLeaderboard");
GA_googleAddSlot("ca-pub-3440800076797949", "SmallPS");
GA_googleAddSlot("ca-pub-3440800076797949", "LargePS");
GA_googleAddSlot("ca-pub-3440800076797949", "BottomRectangle");
GA_googleAddSlot("ca-pub-3440800076797949", "SkyScraper");
GA_googleAddSlot("ca-pub-3440800076797949", "TopRectangle");
</script>
<script type='text/javascript'>
GA_googleFetchAds();
</script>
<!-- TopRectangle -->
<script type='text/javascript'>
GA_googleFillSlot("TopRectangle");
</script>
<div style="width:728px;height:90px;position:absolute;right:0px;top:0px;margin:0;padding:0;overflow:hidden;">
<!-- MainLeaderboard -->
<script type='text/javascript'>
GA_googleFillSlot("MainLeaderboard");
</script>
</div></div>
<div style="width:960px;padding:0px;margin:0px;">
<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>
<a target="_top" href="default.asp" style='font-weight:bold;'>HTML5 Home</a><br />
<a target="_top" href="html5_intro.asp" >HTML5 Introduction</a><br />
<a target="_top" href="html5_new_elements.asp" >HTML5 New Elements</a><br />
<a target="_top" href="html5_video.asp" >HTML5 Video</a><br />
<a target="_top" href="html5_video_dom.asp" >HTML5 Video/DOM</a><br />
<a target="_top" href="html5_audio.asp" >HTML5 Audio</a><br />
<a target="_top" href="html5_draganddrop.asp" >HTML5 Drag and Drop</a><br />
<a target="_top" href="html5_canvas.asp" >HTML5 Canvas</a><br />
<a target="_top" href="html5_svg.asp" >HTML5 SVG</a><br />
<a target="_top" href="html5_canvas_vs_svg.asp" >HTML5 Canvas vs. SVG</a><br />
<a target="_top" href="html5_geolocation.asp" >HTML5 Geolocation</a><br />
<a target="_top" href="html5_webstorage.asp" >HTML5 Web Storage</a><br />
<a target="_top" href="html5_app_cache.asp" >HTML5 App Cache</a><br />
<a target="_top" href="html5_webworkers.asp" >HTML5 Web Workers</a><br />
<a target="_top" href="html5_serversentevents.asp" >HTML5 SSE</a><br />
<br />
<h2 class="left"><span class="left_h2">HTML5</span> Forms</h2>
<a target="_top" href="html5_form_input_types.asp" >HTML5 Input Types</a><br />
<a target="_top" href="html5_form_elements.asp" >HTML5 Form Elements</a><br />
<a target="_top" href="html5_form_attributes.asp" >HTML5 Form Attributes</a><br />
<br />
<h2 class="left"><span class="left_h2">HTML5</span> Reference</h2>
<a target="_top" href="html5_reference.asp" >HTML5 Tags</a><br />
<a target="_top" href="html5_ref_globalattributes.asp" >HTML5 Attributes</a><br />
<a target="_top" href="html5_ref_eventattributes.asp" >HTML5 Events</a><br />
<a target="_top" href="html5_ref_av_dom.asp" >HTML5 Audio/Video</a><br />
<a target="_top" href="html5_ref_canvas.asp" >HTML5 Canvas 2d</a><br />
<a target="_top" href="html5_ref_dtd.asp" >HTML Valid DTDs</a><br />
<br />
<h2 class="left"><span class="left_h2">HTML5</span> Tags</h2>
<a target="_top" href="tag_comment.asp" ><!--></a><br />
<a target="_top" href="tag_doctype.asp" ><!DOCTYPE></a><br />
<a target="_top" href="tag_a.asp" ><a></a><br />
<a target="_top" href="tag_abbr.asp" ><abbr></a><br />
<a target="_top" href="tag_acronym.asp" ><acronym></a><br />
<a target="_top" href="tag_address.asp" ><address></a><br />
<a target="_top" href="tag_applet.asp" ><applet></a><br />
<a target="_top" href="tag_area.asp" ><area></a><br />
<a target="_top" href="tag_article.asp" ><article></a><br />
<a target="_top" href="tag_aside.asp" ><aside></a><br />
<a target="_top" href="tag_audio.asp" ><audio></a><br />
<a target="_top" href="tag_b.asp" ><b></a><br />
<a target="_top" href="tag_base.asp" ><base></a><br />
<a target="_top" href="tag_basefont.asp" ><basefont></a><br />
<a target="_top" href="tag_bdi.asp" ><bdi></a><br />
<a target="_top" href="tag_bdo.asp" ><bdo></a><br />
<a target="_top" href="tag_big.asp" ><big></a><br />
<a target="_top" href="tag_blockquote.asp" ><blockquote></a><br />
<a target="_top" href="tag_body.asp" ><body></a><br />
<a target="_top" href="tag_br.asp" ><br></a><br />
<a target="_top" href="tag_button.asp" ><button></a><br />
<a target="_top" href="tag_canvas.asp" ><canvas></a><br />
<a target="_top" href="tag_caption.asp" ><caption></a><br />
<a target="_top" href="tag_center.asp" ><center></a><br />
<a target="_top" href="tag_cite.asp" ><cite></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><code></a><br />
<a target="_top" href="tag_col.asp" ><col></a><br />
<a target="_top" href="tag_colgroup.asp" ><colgroup></a><br />
<a target="_top" href="tag_command.asp" ><command></a><br />
<a target="_top" href="tag_datalist.asp" ><datalist></a><br />
<a target="_top" href="tag_dd.asp" ><dd></a><br />
<a target="_top" href="tag_del.asp" ><del></a><br />
<a target="_top" href="tag_details.asp" ><details></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><dfn></a><br />
<a target="_top" href="tag_dir.asp" ><dir></a><br />
<a target="_top" href="tag_div.asp" ><div></a><br />
<a target="_top" href="tag_dl.asp" ><dl></a><br />
<a target="_top" href="tag_dt.asp" ><dt></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><em></a><br />
<a target="_top" href="tag_embed.asp" ><embed></a><br />
<a target="_top" href="tag_fieldset.asp" ><fieldset></a><br />
<a target="_top" href="tag_figcaption.asp" ><figcaption></a><br />
<a target="_top" href="tag_figure.asp" ><figure></a><br />
<a target="_top" href="tag_font.asp" ><font></a><br />
<a target="_top" href="tag_footer.asp" ><footer></a><br />
<a target="_top" href="tag_form.asp" ><form></a><br />
<a target="_top" href="tag_frame.asp" ><frame></a><br />
<a target="_top" href="tag_frameset.asp" ><frameset></a><br />
<a target="_top" href="tag_hn.asp" ><h1> - <h6></a><br />
<a target="_top" href="tag_head.asp" ><head></a><br />
<a target="_top" href="tag_header.asp" ><header></a><br />
<a target="_top" href="tag_hgroup.asp" ><hgroup></a><br />
<a target="_top" href="tag_hr.asp" ><hr></a><br />
<a target="_top" href="tag_html.asp" ><html></a><br />
<a target="_top" href="tag_i.asp" ><i></a><br />
<a target="_top" href="tag_iframe.asp" ><iframe></a><br />
<a target="_top" href="tag_img.asp" ><img></a><br />
<a target="_top" href="tag_input.asp" ><input></a><br />
<a target="_top" href="tag_ins.asp" ><ins></a><br />
<a target="_top" href="tag_keygen.asp" ><keygen></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><kbd></a><br />
<a target="_top" href="tag_label.asp" ><label></a><br />
<a target="_top" href="tag_legend.asp" ><legend></a><br />
<a target="_top" href="tag_li.asp" ><li></a><br />
<a target="_top" href="tag_link.asp" ><link></a><br />
<a target="_top" href="tag_map.asp" ><map></a><br />
<a target="_top" href="tag_mark.asp" ><mark></a><br />
<a target="_top" href="tag_menu.asp" ><menu></a><br />
<a target="_top" href="tag_meta.asp" ><meta></a><br />
<a target="_top" href="tag_meter.asp" ><meter></a><br />
<a target="_top" href="tag_nav.asp" ><nav></a><br />
<a target="_top" href="tag_noframes.asp" ><noframes></a><br />
<a target="_top" href="tag_noscript.asp" ><noscript></a><br />
<a target="_top" href="tag_object.asp" ><object></a><br />
<a target="_top" href="tag_ol.asp" ><ol></a><br />
<a target="_top" href="tag_optgroup.asp" ><optgroup></a><br />
<a target="_top" href="tag_option.asp" ><option></a><br />
<a target="_top" href="tag_output.asp" ><output></a><br />
<a target="_top" href="tag_p.asp" ><p></a><br />
<a target="_top" href="tag_param.asp" ><param></a><br />
<a target="_top" href="tag_pre.asp" ><pre></a><br />
<a target="_top" href="tag_progress.asp" ><progress></a><br />
<a target="_top" href="tag_q.asp" ><q></a><br />
<a target="_top" href="tag_rp.asp" ><rp></a><br />
<a target="_top" href="tag_rt.asp" ><rt></a><br />
<a target="_top" href="tag_ruby.asp" ><ruby></a><br />
<a target="_top" href="tag_s.asp" ><s></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><samp></a><br />
<a target="_top" href="tag_script.asp" ><script></a><br />
<a target="_top" href="tag_section.asp" ><section></a><br />
<a target="_top" href="tag_select.asp" ><select></a><br />
<a target="_top" href="tag_small.asp" ><small></a><br />
<a target="_top" href="tag_source.asp" ><source></a><br />
<a target="_top" href="tag_span.asp" ><span></a><br />
<a target="_top" href="tag_strike.asp" ><strike></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><strong></a><br />
<a target="_top" href="tag_style.asp" ><style></a><br />
<a target="_top" href="tag_sup.asp" ><sub></a><br />
<a target="_top" href="tag_summary.asp" ><summary></a><br />
<a target="_top" href="tag_sup.asp" ><sup></a><br />
<a target="_top" href="tag_table.asp" ><table></a><br />
<a target="_top" href="tag_tbody.asp" ><tbody></a><br />
<a target="_top" href="tag_td.asp" ><td></a><br />
<a target="_top" href="tag_textarea.asp" ><textarea></a><br />
<a target="_top" href="tag_tfoot.asp" ><tfoot></a><br />
<a target="_top" href="tag_th.asp" ><th></a><br />
<a target="_top" href="tag_thead.asp" ><thead></a><br />
<a target="_top" href="tag_time.asp" ><time></a><br />
<a target="_top" href="tag_title.asp" ><title></a><br />
<a target="_top" href="tag_tr.asp" ><tr></a><br />
<a target="_top" href="tag_track.asp" ><track></a><br />
<a target="_top" href="tag_tt.asp" ><tt></a><br />
<a target="_top" href="tag_u.asp" ><u></a><br />
<a target="_top" href="tag_ul.asp" ><ul></a><br />
<a target="_top" href="tag_phrase_elements.asp" ><var></a><br />
<a target="_top" href="tag_video.asp" ><video></a><br />
<a target="_top" href="tag_wbr.asp" ><wbr></a><br />
<br /></div>
<div style="width:634px;margin:0px;padding:0px;background-color:#ffffff;color:#000000;padding-bottom:8px;padding-right:5px;padding-top:4px;float:left;">
<h1>HTML5 <span class="color_h1">Tutorial</span></h1>
<div class="chapter">
<div class="prev"><a class="chapter" href="/default.asp">« W3Schools Home</a></div>
<div class="next"><a class="chapter" href="html5_intro.asp">Next Chapter »</a></div>
</div>
<br />
<div class="html5header">
<div id="html5headline">HTML5 is The New HTML Standard</div>
<div id="header_image">
<div id="header_image_html5" style="background:url('img_html5_html5.gif');"></div>
<div id="header_image_multimedia" style="display:none;margin:0;padding:0;padding-top:20px;height:215px;text-align:center">
<video width="270" controls="controls" id="html5_vid">
<source src="mov_bbb.mp4" type="video/mp4" />
<source src="mov_bbb.ogg" type="video/ogg" />
<source src="mov_bbb.webm" type="video/webm" />
Your browser does not support the video tag.
</video>
<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>
</div>
<div id="header_image_3d" style="display:none;background:url('img_html5_3d.jpg')"></div>
<div id="header_image_offline" style="display:none;background:url('img_html5_offline.jpg')"></div>
<div id="header_image_form" style="display:none;background:url('img_html5_form.jpg')"></div>
<div id="header_image_css3" style="display:none;background:url('img_html5_css3.jpg')"></div>
</div>
<div id="header_text">
<div id="header_text_html5">
<h2>HTML5</h2>
<ul>
<li>New Elements</li>
<li>New Attributes</li>
<li>Full CSS3 Support</li>
<li>Video and Audio</li>
<li>2D/3D Graphics</li>
<li>Local Storage</li>
<li>Local SQL Database</li>
<li>Web Applications</li>
</ul>
</div>
<div id="header_text_multimedia" style="display:none;">
<h2>HTML5 Multimedia</h2>
<p>With HTML5, playing video and audio is easier than ever.</p>
<ul>
<li>HTML5 <a href="html5_video.asp"><video></a></li>
<li>HTML5 <a href="html5_audio.asp"><audio></a></li>
</ul>
</div>
<div id="header_text_offline" style="display:none">
<h2>HTML5 Applications</h2>
<p>With HTML5, web application development is easier than ever.</p>
<ul>
<li>Local data storage</li>
<li>Local file access</li>
<li>Local SQL database</li>
<li>Application cache</li>
<li>Javascript workers</li>
<li>XHTMLHttpRequest 2</li>
</ul>
</div>
<div id="header_text_3d" style="display:none">
<h2>HTML5 Graphics</h2>
<p>With HTML5, drawing graphics is easier than ever:</p>
<ul>
<li>Using the <a href="html5_canvas.asp"><canvas></a> element</li>
<li>Using inline <a href="/svg/default.asp">SVG</a></li>
<li>Using <a href="/css3/default.asp">CSS3 2D/3D</a></li>
</ul>
</div>
<div id="header_text_css3" style="display:none">
<h2>HTML5 uses CSS3</h2>
<ul>
<li>New Selectors</li>
<li>New Properties</li>
<li>Animations</li>
<li>2D/3D Transformations</li>
<li>Rounded Corners</li>
<li>Shadow Effects</li>
<li>Downloadable Fonts</li>
</ul>
<p>Read more in our <a href="/css3/default.asp">CSS3 tutorial.</a></p>
</div>
<div id="header_text_form" style="display:none">
<h2>Semantic Elements</h2>
<p>New elements for headers, footers, menues, sections and articles.</p>
<h2>HTML5 Forms</h2>
<p>New form elements, new attributes, new input types, automatic validation.</p>
</div>
</div>
<div id="header_icons">
<ul id="header_icons_list">
<li id="header_html5" onclick="changeHeader('html5','0');selIcon='html5'" onmouseover="iconHover('html5','0')" onmouseout="iconHoverOut('html5','0')" title="HTML5"></li>
<li id="header_multimedia" onclick="changeHeader('multimedia','-104');selIcon='multimedia';playVideo()" onmouseover="iconHover('multimedia','-104')" onmouseout="iconHoverOut('multimedia','-104')" title="HTML5 Multimedia"></li>
<li id="header_3d" onclick="changeHeader('3d','-208');selIcon='3d'" onmouseover="iconHover('3d','-208')" onmouseout="iconHoverOut('3d','-208')" title="HTML5 Graphics"></li>
<li id="header_offline" onclick="changeHeader('offline','-312');selIcon='offline'" onmouseover="iconHover('offline','-312')" onmouseout="iconHoverOut('offline','-312')" title="HTML5 Local Storage"></li>
<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>
<li id="header_css3" onclick="changeHeader('css3','-521');selIcon='css3'" onmouseover="iconHover('css3','-521')" onmouseout="iconHoverOut('css3','-521')" title="HTML5 CSS3"></li>
</ul>
</div>
</div>
<br />
<h2 class="tutheader">Examples in Each Chapter</h2>
<p>With our HTML editor, you can edit the HTML, and click on a button to view the result.</p>
<div class="example">
<h2 class="example">Example</h2>
<div class="example_code notranslate">
<!DOCTYPE HTML><br />
<html><br />
<body><br />
<br />
<video width="320" height="240" controls="controls"><br />
<source src="movie.mp4" type="video/mp4" /><br />
<source src="movie.ogg" type="video/ogg" /><br />
<source src="movie.webm" type="video/webm" /><br />
Your browser does not support the video tag.<br />
</video><br />
<br />
</body><br />
</html>
</div>
<br />
<a class="tryitbtn" target="_blank" href="tryit.asp?filename=tryhtml5_video_bear">Try it yourself »</a></div>
<p><b>Click on the "Try it yourself" button to see how it works</b></p>
<p><b><a href="html5_intro.asp">Start learning HTML5 now!</a></b></p>
<h2 class="tutheader">HTML5 References</h2>
<p>At W3Schools you will find complete references about tags, global attributes,
standard events, and more.</p>
<p>
<a href="html5_reference.asp">HTML5 Tag Reference</a>
</p>
<br />
<div class="chapter">
<div class="prev"><a class="chapter" href="/default.asp">« W3Schools Home</a></div>
<div class="next"><a class="chapter" href="html5_intro.asp">Next Chapter »</a></div>
</div>
<!-- **** SPOTLIGHTS 1 **** -->
<!-- SmallPS -->
<script type='text/javascript'>
GA_googleFillSlot("SmallPS");
</script>
<!-- **** SPOTLIGHTS 2 **** -->
<!-- LargePS -->
<script type='text/javascript'>
GA_googleFillSlot("LargePS");
</script>
<!-- **** SPOTLIGHTS 3 **** -->
<!-- BottomRectangle -->
<div style="width:340px;margin:auto">
<script type='text/javascript'>
GA_googleFillSlot("BottomRectangle");
</script>
</div>
<!-- BottomBanner -->
<script type='text/javascript'>
GA_googleFillSlot("BottomBanner");
</script>
<div id="err_form" style="display:none">
<h2>Your suggestion:</h2>
<p><label for="err_email">Your E-mail (optional):</label> <input type="text" id="err_email" name="err_email" /></p>
<p><label for="err_url">Page address:</label> <input type="text" disabled="disabled" id="err_url" name="err_url" /></p>
<p><label for="err_desc">Description:</label> <textarea name="err_desc" id="err_desc" cols="92" rows="20"></textarea></p>
<p class="submit"><input type="button" value="Submit" onclick="sendErr()" /></p>
<div class="err_close" onclick="hideError()">Close [X]</div>
</div>
<div id="err_sent" style="display:none">
<h2>Thank you for your support.</h2>
<div class="err_close" onclick="hideSent()">Close [X]</div>
</div>
</div>
<div id="rightcolumn" style="width:150px;margin:0px;padding:0px;float:left">
<table>
<tr><th>WEB HOSTING</th></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.lunarpages.com/id/w3schools/goto/w3schools">Best Web Hosting</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.eukhost.com">PHP MySQL Hosting</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.web-hosting-top.com/coupons">Best Hosting Coupons</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.heartinternet.co.uk/index.shtml">UK Reseller Hosting</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.webhosting.uk.com/cloud-hosting.php">Cloud Hosting</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.justhost.com/track/w3schools/textlink">Top Web Hosting</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.doteasy.com/index.cfm?A=w3text">$3.98 Unlimited Hosting</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.website.com/">Premium Website Design</a>
</td></tr>
</table>
<table>
<tr><th>WEB BUILDING</th></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.altova.com/ref/?s=w3s_text&q=xmlspy">Download XML Editor</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.wix.com/html5/now-on-wix?utm_campaign=ma_w3schools.com&experiment_id=ma_html_w3schools.comlink1">FREE Website BUILDER</a>
</td></tr>
<tr><td>
<a target="_blank" rel="nofollow" href="http://www.dreamtemplate.com/?ref=w3txtfree">Free Website Templates</a>
<a target="_blank" rel="nofollow" href="http://www.dreamtemplate.com/?ref=w3txtfree">Free CSS Templates</a>
</td></tr>
<tr><td>
<a href="http://www.wix.com/html5/now-on-wix?utm_campaign=ma_w3schools.com&experiment_id=ma_html_w3schools.comlink2" rel="nofollow" target="_blank">CREATE HTML Websites</a>
</td></tr>
</table>
<table>
<tr><th>W3SCHOOLS EXAMS</th></tr>
<tr><td>
<a target="_blank" href="/cert/default.asp">Get Certified in:<br />HTML, CSS, JavaScript, XML, PHP, and ASP</a>
</td></tr>
</table>
<table>
<tr><th>W3SCHOOLS BOOKS</th></tr>
<tr><td>
<a target="_blank" href="/books/default.asp">
New Books:<br />HTML, CSS<br />
JavaScript, and Ajax</a>
</td></tr>
</table>
<table>
<tr><th>STATISTICS</th></tr>
<tr><td>
<a target="_top" href="/browsers/browsers_stats.asp">Browser Statistics</a><br />
<a target="_top" href="/browsers/browsers_os.asp">Browser OS</a><br />
<a target="_top" href="/browsers/browsers_display.asp">Browser Display</a>
</td></tr></table>
<script type="text/javascript">
<!--
function sharethis()
{
txt='<a href="http://www.facebook.com/sharer.php?u='+document.URL+'" target="_blank" title="Facebook">'
txt=txt+'<img src="/images/share_facebook.gif" width="16px" height="16px" style="margin-right:4px" /></a>';
txt=txt+'<a href="http://twitter.com/home?status=Currently reading '+document.URL+'" target="_blank" title="Twitter">';
txt=txt+'<img src="/images/share_twitter.gif" width="16px" height="16px" style="margin-right:4px" /></a>';
txt=txt+'<a href="mailto:?&subject='+document.title+'&body=Take%20a%20look%20at%20this%20page%20at%20W3Schools.com:%20'+document.URL+'" target="_blank" title="E-mail">';
txt=txt+'<img src="/images/share_email.gif" width="16px" height="16px" style="margin-right:4px" /></a>';
txt=txt+'<a href="http://delicious.com/save?v=5&noui&jump=close&url='+document.URL+'&title='+document.title+'" target="_blank" title="Delicious">';
txt=txt+'<img src="/images/share_delicious.gif" width="16px" height="16px" style="margin-right:4px" /></a>';
txt=txt+'<a href="http://www.reddit.com/submit?url='+document.URL+'" target="_blank" title="Reddit">';
txt=txt+'<img src="/images/share_reddit.gif" width="16px" height="16px" style="margin-right:4px" /></a>';
txt=txt+'<a href="http://digg.com/submit?url='+document.URL+'&title='+document.title+'" target="_blank" title="Digg">';
txt=txt+'<img src="/images/share_digg.gif" width="16px" height="16px" style="margin-right:4px" /></a>';
txt=txt+'<a href="http://www.stumbleupon.com/submit?url='+document.URL+'%26title%3D'+document.title+'" target="_blank" title="Stumbleupon">';
txt=txt+'<img src="/images/share_stumbleupon.gif" width="16px" height="16px" /></a>';
document.getElementById("sharethis").innerHTML=txt;
}
//--></script>
<table>
<tr><th>SHARE THIS PAGE</th></tr>
<tr>
<td id="sharethis">
<div style="height:16px">
<a href="#" onclick="sharethis();return false;">Share with »</a>
</div>
</td>
</tr>
</table>
<table>
<tr><td><br />
<div style="width:124px;margin:auto">
<!-- SkyScraper -->
<script type='text/javascript'>
GA_googleFillSlot("SkyScraper");
</script>
</div>
</td>
</tr>
</table>
</div>
</div>
<br />
</div>
<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;">
<div id="footer" style="width:960px;margin-left:auto;margin-right:auto;height:110px;">
<div style="float:left;width:200px;text-align:left;padding-left:3px;padding-top:11px;"><a href="http://www.w3schools.com">
<img style="border:0" src="/images/w3schoolscom_gray.gif" alt="W3Schools.com" /></a>
</div>
<div style="word-spacing:6px;font-size:80%;padding-right:12px;padding-top:19px;float:right;width:600px;text-align:right;">
<a href="" onclick="displayError();return false">REPORT ERROR</a> |
<a href="/default.asp" target="_top">HOME</a> |
<a href="#top" target="_top">TOP</a> |
<a href='/html5/default.asp?output=print' target="_blank">PRINT</a> |
<a href="/forum/default.asp" target="_blank">FORUM</a> |
<a href="/about/default.asp" target="_top">ABOUT</a>
</div>
<div style="padding-top:13px;color:#404040;clear:both;">
W3Schools is optimized for learning, testing, and training. Examples might be simplified to improve reading and basic understanding.<br />
Tutorials, references, and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content.<br />
While using this site, you agree to have read and accepted our
<a href="/about/about_copyright.asp">terms of use</a> and
<a href="/about/about_privacy.asp">privacy policy</a>.<br />
<a href="/about/about_copyright.asp">Copyright 1999-2012</a> by Refsnes Data. All Rights Reserved.
</div>
</div>
</div>
</div>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({
pageLanguage: 'en',
autoDisplay: false,
gaTrack: true,
layout: google.translate.TranslateElement.InlineLayout.SIMPLE
}, 'google_translate_element');
}
</script><script src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit" type="text/javascript"></script>
</body>
</html>
================================================
FILE: examples/image-manipulation/README.md
================================================
# Image Manipulation
This example starts a proxy server that manipulate the received images,
to make them appear upside down.
This directly modify the response received from the response server,
and returns the new data to the proxy caller.
================================================
FILE: examples/image-manipulation/main.go
================================================
package main
import (
"github.com/elazarl/goproxy"
"github.com/elazarl/goproxy/ext/image"
"image"
"log"
"net/http"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
newImg := image.NewRGBA(img.Bounds())
for i := 0; i < dx; i++ {
for j := 0; j <= dy; j++ {
newImg.Set(i, j, img.At(i, dy-j-1))
}
}
return newImg
}))
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
================================================
FILE: examples/redirect-https/README.md
================================================
# Redirect HTTPS
`redirect-https` example redirects all the HTTPS request to HTTP endpoint,
by returning a `303 See Other` HTTP response to the client.
The client will then make another request using the HTTP scheme.
================================================
FILE: examples/redirect-https/main.go
================================================
package main
import (
"bytes"
"flag"
"github.com/elazarl/goproxy"
"io"
"log"
"net/http"
)
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if req.URL.Scheme != "https" {
return req, nil
}
req.URL.Scheme = "http"
resp := &http.Response{
StatusCode: http.StatusSeeOther,
ProtoMajor: 1,
ProtoMinor: 1,
Request: req,
Header: http.Header{
"Location": []string{req.URL.String()},
},
Body: io.NopCloser(bytes.NewReader(nil)),
ContentLength: 0,
}
return nil, resp
})
proxy.Verbose = *verbose
log.Fatal(http.ListenAndServe(*addr, proxy))
}
================================================
FILE: examples/remove-https/README.md
================================================
# Remove HTTPS
`remove-https` example forwards all the HTTPS request as HTTP requests,
effectively removing the https schema from the requests.
This example shows you how you can rewrite the request URL, when
needed.
This is important because shows you how to effectively use MITM, to
intercept the request and read its data.
================================================
FILE: examples/remove-https/main.go
================================================
package main
import (
"flag"
"github.com/elazarl/goproxy"
"log"
"net/http"
)
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if req.URL.Scheme == "https" {
req.URL.Scheme = "http"
}
return req, nil
})
proxy.Verbose = *verbose
log.Fatal(http.ListenAndServe(*addr, proxy))
}
================================================
FILE: examples/request-filtering/README.md
================================================
# Request Filtering
`request-filtering` starts an HTTP proxy on :8080. It denies requests
to "www.reddit.com" made between 8am to 5pm inclusive, local server
time.
Start the server:
```sh
$ request-filtering
```
Make a test request in another shell:
```sh
$ http_proxy=http://127.0.0.1:8080 wget -O - http://www.reddit.com
--2015-04-11 16:59:01-- http://www.reddit.com/
Connecting to 127.0.0.1:8080... connected.
Proxy request sent, awaiting response... 403 Forbidden
2015-04-11 16:59:01 ERROR 403: Forbidden.
```
================================================
FILE: examples/request-filtering/noreddit.go
================================================
package main
import (
"github.com/elazarl/goproxy"
"log"
"net/http"
"time"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
h, _, _ := time.Now().Clock()
if h >= 8 && h <= 17 {
return r, goproxy.NewResponse(r,
goproxy.ContentTypeText, http.StatusForbidden,
"Don't waste your time!")
} else {
ctx.Warnf("clock: %d, you can waste your time...", h)
}
return r, nil
})
log.Fatalln(http.ListenAndServe(":8080", proxy))
}
================================================
FILE: examples/socket-keepalive/README.md
================================================
# Socket KeepAlive
`socket-keepalive` example adds a custom net.Dialer that can be configured
by the user, enabling TCP keep alives in this example.
By default, Go already uses 15 seconds TCP keep alives for the connections,
so this example is not strictly required, as it is provided.
TCP keep alives are useful for a connection that can be idle for a while,
to avoid the TCP connection close.
The TCP connection is closed when the request context expires.
================================================
FILE: examples/socket-keepalive/keepalive.go
================================================
package main
import (
"context"
"flag"
"github.com/elazarl/goproxy"
"log"
"net"
"net/http"
)
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
addr := flag.String("addr", ":8080", "proxy listen address")
flag.Parse()
proxy := goproxy.NewProxyHttpServer()
proxy.Tr.DialContext = func(ctx context.Context, network, addr string) (c net.Conn, err error) {
var d net.Dialer
c, err = d.DialContext(ctx, network, addr)
if c, ok := c.(*net.TCPConn); err == nil && ok {
c.SetKeepAlive(true)
go func() {
<-ctx.Done()
c.Close()
}()
}
return
}
proxy.Verbose = *verbose
log.Fatal(http.ListenAndServe(*addr, proxy))
}
================================================
FILE: examples/websockets/README.md
================================================
# Websockets
`websockets` example shows an example of a WebSocket request made
through the proxy server.
The target server continuously send data to the client, and the proxy will
forward them to websocket client.
To run this example, it's enough to just run the main.
Inside this folder there are also some self-signed certificates valid for
localhost, that are automatically used for the WebSocket server.
================================================
FILE: examples/websockets/localhost-key.pem
================================================
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC1vu3JK+Z6WtbpaTL4LquNQVcwSha53sVIxEdcLNG6WoAoGCCqGSM49
AwEHoUQDQgAE8OpaVYv567gc00WZ8nSTDvcic+8tW7dCgGffiJogyesaERxDi+fg
0E/9WiNrF66Pl05I3r4NFD0EoIlcaSbMqw==
-----END EC PRIVATE KEY-----
================================================
FILE: examples/websockets/localhost.pem
================================================
-----BEGIN CERTIFICATE-----
MIICozCCAYugAwIBAgIUEdJ4Tu/Hs4HznpHjnb3Akj7uNKgwDQYJKoZIhvcNAQEL
BQAwDTELMAkGA1UEAxMCQ0EwHhcNMTYwNTA1MjIxMjAwWhcNMjEwNTA0MjIxMjAw
WjBGMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNU2FuIEZyYW5jaXNjbzELMAkGA1UE
BxMCQ0ExEjAQBgNVBAMTCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABPDqWlWL+eu4HNNFmfJ0kw73InPvLVu3QoBn34iaIMnrGhEcQ4vn4NBP/Voj
axeuj5dOSN6+DRQ9BKCJXGkmzKujgYwwgYkwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFD8hwmfkMLy1
9iajA7jsiEzBDT6DMB8GA1UdIwQYMBaAFBZgsrPxVqk7nrwIzeqIEl62Nj3IMBQG
A1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAPYJUCwgNCVLm
oITJdYtroJIOt3LegIQJWWYll4J3/WidfmW4FWD4Qumd0YRjqgIhTcgmxHWhhJXB
KFd+hMD+UUs7jdYRQ80Ch/S/r/PzBNLiHkZ+ILklCU2JG25hJjXX48BB5y1eVF7O
9ds5h6KHJqNwG6B5yfB5iPxpd4/Qk/+6vNYODX5LSQqo9MpGotByjMGywcY4TesF
5AYT7pPoRkxucAifTRjw/8k653Zm2ZA9HCo+i9GrW+kyk3XP5Xk8z0GuvOCPnBIu
p1jiUOH84pXap2cuR0z3vkp76mPAgsBk64RCBjCJJV8LEQbiPW4W9dQjcvlLkW7X
lP5b19hr6g==
-----END CERTIFICATE-----
================================================
FILE: examples/websockets/main.go
================================================
package main
import (
"context"
"crypto/tls"
"github.com/coder/websocket"
"github.com/elazarl/goproxy"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"time"
)
func echo(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, nil)
if err != nil {
log.Printf("upgrade: %v\n", err)
return
}
defer c.Close(websocket.StatusNormalClosure, "")
ctx := context.Background()
for {
mt, message, err := c.Read(ctx)
if err != nil {
log.Printf("read: %v\n", err)
break
}
log.Printf("recv: %s\n", message)
if err := c.Write(ctx, mt, message); err != nil {
log.Printf("write: %v\n", err)
break
}
}
}
func StartEchoServer() {
log.Println("Starting echo server")
go func() {
http.HandleFunc("/", echo)
err := http.ListenAndServeTLS(":12345", "localhost.pem", "localhost-key.pem", nil)
if err != nil {
log.Fatal(err)
}
}()
}
func StartProxy() {
log.Println("Starting proxy server")
go func() {
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.Verbose = true
if err := http.ListenAndServe(":54321", proxy); err != nil {
log.Fatal(err)
}
}()
}
func main() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
StartEchoServer()
StartProxy()
proxyUrl := "http://localhost:54321"
parsedProxy, err := url.Parse(proxyUrl)
if err != nil {
log.Fatal("unable to parse proxy URL")
}
ctx := context.Background()
endpointUrl := "wss://localhost:12345"
c, _, err := websocket.Dial(ctx, endpointUrl, &websocket.DialOptions{
HTTPClient: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
Proxy: http.ProxyURL(parsedProxy),
},
},
Subprotocols: []string{"p1"},
})
if err != nil {
log.Fatal("dial:", err)
}
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.Read(ctx)
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case t := <-ticker.C: // Message send
// Write current time to the websocket client every 1 second
if err := c.Write(ctx, websocket.MessageText, []byte(t.String())); err != nil {
log.Println("write:", err)
return
}
case <-interrupt: // Server shutdown
log.Println("interrupt")
// To cleanly close a connection, a client should send a close
// frame and wait for the server to close the connection.
err := c.Close(websocket.StatusNormalClosure, "")
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
================================================
FILE: ext/auth/basic.go
================================================
package auth
import (
"bytes"
"encoding/base64"
"io"
"net/http"
"strings"
"github.com/elazarl/goproxy"
)
var unauthorizedMsg = []byte("407 Proxy Authentication Required")
func BasicUnauthorized(req *http.Request, realm string) *http.Response {
// TODO(elazar): verify realm is well formed
return &http.Response{
StatusCode: http.StatusProxyAuthRequired,
ProtoMajor: 1,
ProtoMinor: 1,
Request: req,
Header: http.Header{
"Proxy-Authenticate": []string{"Basic realm=" + realm},
"Proxy-Connection": []string{"close"},
},
Body: io.NopCloser(bytes.NewBuffer(unauthorizedMsg)),
ContentLength: int64(len(unauthorizedMsg)),
}
}
var proxyAuthorizationHeader = "Proxy-Authorization"
func auth(req *http.Request, f func(user, passwd string) bool) bool {
authheader := strings.SplitN(req.Header.Get(proxyAuthorizationHeader), " ", 2)
req.Header.Del(proxyAuthorizationHeader)
if len(authheader) != 2 || authheader[0] != "Basic" {
return false
}
userpassraw, err := base64.StdEncoding.DecodeString(authheader[1])
if err != nil {
return false
}
userpass := strings.SplitN(string(userpassraw), ":", 2)
if len(userpass) != 2 {
return false
}
return f(userpass[0], userpass[1])
}
// Basic returns a basic HTTP authentication handler for requests
//
// You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities
func Basic(realm string, f func(user, passwd string) bool) goproxy.ReqHandler {
return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if !auth(req, f) {
return nil, BasicUnauthorized(req, realm)
}
return req, nil
})
}
// BasicConnect returns a basic HTTP authentication handler for CONNECT requests
//
// You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities
func BasicConnect(realm string, f func(user, passwd string) bool) goproxy.HttpsHandler {
return goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
if !auth(ctx.Req, f) {
ctx.Resp = BasicUnauthorized(ctx.Req, realm)
return goproxy.RejectConnect, host
}
return nil, host
})
}
// ProxyBasic will force HTTP authentication before any request to the proxy is processed
func ProxyBasic(proxy *goproxy.ProxyHttpServer, realm string, f func(user, passwd string) bool) {
proxy.OnRequest().Do(Basic(realm, f))
proxy.OnRequest().HandleConnect(BasicConnect(realm, f))
}
================================================
FILE: ext/auth/basic_test.go
================================================
package auth_test
import (
"encoding/base64"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"os/signal"
"sync/atomic"
"testing"
"github.com/elazarl/goproxy"
"github.com/elazarl/goproxy/ext/auth"
)
type ConstantHanlder string
func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, string(h))
}
func oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, s *httptest.Server) {
s = httptest.NewServer(proxy)
proxyUrl, _ := url.Parse(s.URL)
tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
client = &http.Client{Transport: tr}
return
}
func times(n int, s string) string {
r := make([]byte, 0, n*len(s))
for i := 0; i < n; i++ {
r = append(r, s...)
}
return string(r)
}
func TestBasicConnectAuthWithCurl(t *testing.T) {
expected := ":c>"
background := httptest.NewTLSServer(ConstantHanlder(expected))
defer background.Close()
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().HandleConnect(auth.BasicConnect("my_realm", func(user, passwd string) bool {
return user == "user" && passwd == "open sesame"
}))
_, proxyserver := oneShotProxy(proxy)
defer proxyserver.Close()
cmd := exec.Command("curl",
"--silent", "--show-error", "--insecure",
"-x", proxyserver.URL,
"-U", "user:open sesame",
"-p",
"--url", background.URL+"/[1-3]",
)
out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr
if err != nil {
t.Fatal(err, string(out))
}
finalexpected := times(3, expected)
if string(out) != finalexpected {
t.Error("Expected", finalexpected, "got", string(out))
}
}
func TestBasicAuthWithCurl(t *testing.T) {
expected := ":c>"
background := httptest.NewServer(ConstantHanlder(expected))
defer background.Close()
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool {
return user == "user" && passwd == "open sesame"
}))
_, proxyserver := oneShotProxy(proxy)
defer proxyserver.Close()
cmd := exec.Command("curl",
"--silent", "--show-error",
"-x", proxyserver.URL,
"-U", "user:open sesame",
"--url", background.URL+"/[1-3]",
)
out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr
if err != nil {
t.Fatal(err, string(out))
}
finalexpected := times(3, expected)
if string(out) != finalexpected {
t.Error("Expected", finalexpected, "got", string(out))
}
}
func TestBasicAuth(t *testing.T) {
expected := "hello"
background := httptest.NewServer(ConstantHanlder(expected))
defer background.Close()
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool {
return user == "user" && passwd == "open sesame"
}))
client, proxyserver := oneShotProxy(proxy)
defer proxyserver.Close()
// without auth
resp, err := client.Get(background.URL)
if err != nil {
t.Fatal(err)
}
if resp.Header.Get("Proxy-Authenticate") != "Basic realm=my_realm" {
t.Error("Expected Proxy-Authenticate header got", resp.Header.Get("Proxy-Authenticate"))
}
if resp.StatusCode != http.StatusProxyAuthRequired {
t.Error("Expected status 407 Proxy Authentication Required, got", resp.Status)
}
// with auth
req, err := http.NewRequest(http.MethodGet, background.URL, nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte("user:open sesame")))
resp, err = client.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Error("Expected status 200 OK, got", resp.Status)
}
msg, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if string(msg) != "hello" {
t.Errorf("Expected '%s', actual '%s'", expected, string(msg))
}
}
func TestWithBrowser(t *testing.T) {
// an easy way to check if auth works with webserver
// to test, run with
// $ go test -run TestWithBrowser -- server
// configure a browser to use the printed proxy address, use the proxy
// and exit with Ctrl-C. It will throw error if your haven't actually used the proxy
if os.Args[len(os.Args)-1] != "server" {
return
}
proxy := goproxy.NewProxyHttpServer()
println("proxy localhost port 8082")
access := int32(0)
proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool {
atomic.AddInt32(&access, 1)
return user == "user" && passwd == "1234"
}))
l, err := net.Listen("tcp", "localhost:8082")
if err != nil {
t.Fatal(err)
}
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt)
go func() {
<-ch
l.Close()
}()
http.Serve(l, proxy)
if access <= 0 {
t.Error("No one accessed the proxy")
}
}
================================================
FILE: ext/go.mod
================================================
module github.com/elazarl/goproxy/ext
go 1.23.0
require (
github.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa3811c
github.com/stretchr/testify v1.11.1
golang.org/x/net v0.36.0
golang.org/x/text v0.22.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: ext/go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa3811c h1:yWAGp1CjD1mQGLUsADqPn5s1n2AkGAX33XLDUgoXzyo=
github.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa3811c/go.mod h1:P73liMk9TZCyF9fXG/RyMeSizmATvpvy3ZS61/1eXn4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: ext/har/logger.go
================================================
package har
import (
"net/http"
"time"
"github.com/elazarl/goproxy"
)
// ExportFunc is a function type that users can implement to handle exported entries
type ExportFunc func([]Entry)
// Logger implements a HAR logging extension for goproxy
type Logger struct {
exportFunc ExportFunc
exportInterval time.Duration
exportThreshold int
dataCh chan Entry
}
// LoggerOption is a function type for configuring the Logger
type LoggerOption func(*Logger)
// WithExportInterval sets the interval for automatic exports
func WithExportInterval(d time.Duration) LoggerOption {
return func(l *Logger) {
l.exportInterval = d
}
}
// WithExportCount sets the number of requests after which to export entries
func WithExportThreshold(threshold int) LoggerOption {
return func(l *Logger) {
l.exportThreshold = threshold
}
}
// NewLogger creates a new HAR logger instance
func NewLogger(exportFunc ExportFunc, opts ...LoggerOption) *Logger {
l := &Logger{
exportFunc: exportFunc,
exportThreshold: 100, // Default threshold
exportInterval: 0, // Default no interval
dataCh: make(chan Entry),
}
// Apply options
for _, opt := range opts {
opt(l)
}
go l.exportLoop()
return l
}
// OnRequest handles incoming HTTP requests
func (l *Logger) OnRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
ctx.UserData = time.Now()
return req, nil
}
// OnResponse handles HTTP responses
func (l *Logger) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
if resp == nil || ctx.Req == nil || ctx.UserData == nil {
return resp
}
startTime, ok := ctx.UserData.(time.Time)
if !ok {
return resp
}
entry := Entry{
StartedDateTime: startTime,
Time: time.Since(startTime).Milliseconds(),
Request: parseRequest(ctx),
Response: parseResponse(ctx),
Timings: Timings{
Send: 0,
Wait: time.Since(startTime).Milliseconds(),
Receive: 0,
},
}
entry.fillIPAddress(ctx.Req)
l.dataCh <- entry
return resp
}
func (l *Logger) exportLoop() {
var entries []Entry
exportIfNeeded := func() {
if len(entries) > 0 {
go l.exportFunc(entries)
entries = nil
}
}
var tickerC <-chan time.Time
if l.exportInterval > 0 {
ticker := time.NewTicker(l.exportInterval)
defer ticker.Stop()
tickerC = ticker.C
}
for {
select {
case entry, ok := <-l.dataCh:
if !ok {
exportIfNeeded()
return
}
entries = append(entries, entry)
if l.exportThreshold > 0 && len(entries) >= l.exportThreshold {
exportIfNeeded()
}
case <-tickerC:
exportIfNeeded()
}
}
}
func (l *Logger) Stop() {
close(l.dataCh)
}
================================================
FILE: ext/har/logger_test.go
================================================
package har
import (
"context"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync"
"testing"
"time"
"github.com/elazarl/goproxy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ConstantHandler is a simple HTTP handler that returns a constant response
type ConstantHandler string
func (h ConstantHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
io.WriteString(w, string(h))
}
// createTestProxy sets up a test proxy with a HAR logger
func createTestProxy(logger *Logger) *httptest.Server {
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().DoFunc(logger.OnRequest)
proxy.OnResponse().DoFunc(logger.OnResponse)
return httptest.NewServer(proxy)
}
// createProxyClient creates an HTTP client that uses the given proxy
func createProxyClient(proxyURL string) *http.Client {
proxyURLParsed, _ := url.Parse(proxyURL)
tr := &http.Transport{
Proxy: http.ProxyURL(proxyURLParsed),
}
return &http.Client{Transport: tr}
}
func TestHarLoggerBasicFunctionality(t *testing.T) {
testCases := []struct {
name string
method string
body string
contentType string
expectedMethod string
}{
{
name: "GET Request",
method: http.MethodGet,
expectedMethod: http.MethodGet,
},
{
name: "POST Request",
method: http.MethodPost,
body: `{"test":"data"}`,
contentType: "application/json",
expectedMethod: http.MethodPost,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
var exportedEntries []Entry
exportFunc := func(entr
gitextract_ij9ic0cm/ ├── .github/ │ ├── FUNDING.yml │ ├── dependabot.yml │ └── workflows/ │ └── go.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── actions.go ├── all.bash ├── ca.pem ├── certs/ │ ├── openssl-gen.sh │ └── openssl.cnf ├── certs.go ├── ctx.go ├── dispatcher.go ├── dispatcher_test.go ├── doc.go ├── examples/ │ ├── base/ │ │ ├── README.md │ │ └── main.go │ ├── cascadeproxy/ │ │ ├── README.md │ │ └── main.go │ ├── cascadeproxy-socks/ │ │ ├── README.md │ │ ├── socks5proxyserver/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ └── socksproxy.go │ ├── certstorage/ │ │ ├── README.md │ │ ├── cache.go │ │ └── main.go │ ├── customca/ │ │ ├── README.md │ │ ├── cert.go │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── goproxy-httpdump/ │ │ ├── README.md │ │ └── httpdump.go │ ├── goproxy-transparent/ │ │ ├── README.md │ │ ├── proxy.sh │ │ └── transparent.go │ ├── hijack/ │ │ ├── README.md │ │ └── main.go │ ├── html-parser/ │ │ ├── README.md │ │ ├── jquery1.html │ │ ├── jquery2.html │ │ ├── jquery_homepage.html │ │ ├── jquery_test.go │ │ ├── main.go │ │ ├── php_man.html │ │ └── w3schools.html │ ├── image-manipulation/ │ │ ├── README.md │ │ └── main.go │ ├── redirect-https/ │ │ ├── README.md │ │ └── main.go │ ├── remove-https/ │ │ ├── README.md │ │ └── main.go │ ├── request-filtering/ │ │ ├── README.md │ │ └── noreddit.go │ ├── socket-keepalive/ │ │ ├── README.md │ │ └── keepalive.go │ └── websockets/ │ ├── README.md │ ├── localhost-key.pem │ ├── localhost.pem │ └── main.go ├── ext/ │ ├── auth/ │ │ ├── basic.go │ │ └── basic_test.go │ ├── go.mod │ ├── go.sum │ ├── har/ │ │ ├── logger.go │ │ ├── logger_test.go │ │ └── types.go │ ├── html/ │ │ ├── cp1255.html │ │ ├── cp1255.txt │ │ ├── html.go │ │ └── html_test.go │ ├── image/ │ │ ├── image.go │ │ └── image_test.go │ └── limitation/ │ ├── concurrency.go │ └── concurrency_test.go ├── go.mod ├── go.sum ├── h2.go ├── http.go ├── https.go ├── internal/ │ ├── http1parser/ │ │ ├── header.go │ │ ├── header_test.go │ │ ├── request.go │ │ └── request_test.go │ └── signer/ │ ├── counterecryptor.go │ ├── counterecryptor_test.go │ ├── signer.go │ └── signer_test.go ├── key.pem ├── logger.go ├── proxy.go ├── proxy_test.go ├── regretable/ │ ├── regretreader.go │ └── regretreader_test.go ├── responses.go ├── transport/ │ ├── roundtripper.go │ ├── transport.go │ └── util.go ├── websocket.go └── websocket_test.go
SYMBOL INDEX (417 symbols across 56 files)
FILE: actions.go
type ReqHandler (line 10) | type ReqHandler interface
type FuncReqHandler (line 15) | type FuncReqHandler
method Handle (line 18) | func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*htt...
type RespHandler (line 26) | type RespHandler interface
type FuncRespHandler (line 31) | type FuncRespHandler
method Handle (line 34) | func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *h...
type HttpsHandler (line 47) | type HttpsHandler interface
type FuncHttpsHandler (line 52) | type FuncHttpsHandler
method HandleConnect (line 55) | func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*...
FILE: certs.go
function init (line 10) | func init() {
FILE: ctx.go
type ProxyCtx (line 13) | type ProxyCtx struct
method RoundTrip (line 47) | func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, err...
method printf (line 54) | func (ctx *ProxyCtx) printf(msg string, argv ...any) {
method Logf (line 66) | func (ctx *ProxyCtx) Logf(msg string, argv ...any) {
method Warnf (line 83) | func (ctx *ProxyCtx) Warnf(msg string, argv ...any) {
method Charset (line 90) | func (ctx *ProxyCtx) Charset() string {
type RoundTripper (line 33) | type RoundTripper interface
type CertStorage (line 37) | type CertStorage interface
type RoundTripperFunc (line 41) | type RoundTripperFunc
method RoundTrip (line 43) | func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) ...
FILE: dispatcher.go
type ReqCondition (line 14) | type ReqCondition interface
type RespCondition (line 22) | type RespCondition interface
type ReqConditionFunc (line 27) | type ReqConditionFunc
method HandleReq (line 32) | func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) ...
method HandleResp (line 38) | func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCt...
type RespConditionFunc (line 30) | type RespConditionFunc
method HandleResp (line 42) | func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyC...
function UrlHasPrefix (line 50) | func UrlHasPrefix(prefix string) ReqConditionFunc {
function UrlIs (line 70) | func UrlIs(urls ...string) ReqConditionFunc {
function ReqHostMatches (line 84) | func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc {
function ReqHostIs (line 97) | func ReqHostIs(hosts ...string) ReqConditionFunc {
function UrlMatches (line 128) | func UrlMatches(re *regexp.Regexp) ReqConditionFunc {
function DstHostIs (line 136) | func DstHostIs(host string) ReqConditionFunc {
function SrcIpIs (line 161) | func SrcIpIs(ips ...string) ReqCondition {
function Not (line 173) | func Not(r ReqCondition) ReqConditionFunc {
function ContentTypeIs (line 181) | func ContentTypeIs(typ string, types ...string) RespCondition {
function StatusCodeIs (line 199) | func StatusCodeIs(codes ...int) RespCondition {
method OnRequest (line 219) | func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxy...
type ReqProxyConds (line 226) | type ReqProxyConds struct
method DoFunc (line 232) | func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *Prox...
method Do (line 244) | func (pcond *ReqProxyConds) Do(h ReqHandler) {
method HandleConnect (line 268) | func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) {
method HandleConnectFunc (line 293) | func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx ...
method HijackConnect (line 297) | func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, cl...
type ProxyConds (line 312) | type ProxyConds struct
method DoFunc (line 319) | func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *Proxy...
method Do (line 325) | func (pcond *ProxyConds) Do(h RespHandler) {
method OnResponse (line 346) | func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyC...
function HandleBytes (line 370) | func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler {
FILE: dispatcher_test.go
function TestIsLocalHost (line 13) | func TestIsLocalHost(t *testing.T) {
FILE: examples/base/main.go
function main (line 11) | func main() {
FILE: examples/cascadeproxy-socks/socks5proxyserver/main.go
function main (line 11) | func main() {
FILE: examples/cascadeproxy-socks/socksproxy.go
type SocksAuth (line 13) | type SocksAuth struct
function createSocksProxy (line 17) | func createSocksProxy(socksAddr string, auth SocksAuth) func(r *http.Req...
function main (line 30) | func main() {
FILE: examples/cascadeproxy/main.go
constant _proxyAuthHeader (line 16) | _proxyAuthHeader = "Proxy-Authorization"
function SetBasicAuth (line 18) | func SetBasicAuth(username, password string, req *http.Request) {
function main (line 22) | func main() {
FILE: examples/certstorage/cache.go
type CertStorage (line 10) | type CertStorage struct
method Fetch (line 15) | func (cs *CertStorage) Fetch(hostname string, gen func() (*tls.Certifi...
function NewCertStorage (line 35) | func NewCertStorage() *CertStorage {
FILE: examples/certstorage/main.go
function main (line 10) | func main() {
FILE: examples/customca/main.go
function main (line 13) | func main() {
function parseCA (line 34) | func parseCA(caCert, caKey []byte) (*tls.Certificate, error) {
FILE: examples/goproxy-httpdump/httpdump.go
type FileStream (line 22) | type FileStream struct
method Write (line 31) | func (fs *FileStream) Write(b []byte) (nr int, err error) {
method Close (line 41) | func (fs *FileStream) Close() error {
function NewFileStream (line 27) | func NewFileStream(path string) *FileStream {
type Meta (line 49) | type Meta struct
method WriteTo (line 77) | func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {
function fprintf (line 59) | func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...any) {
function write (line 68) | func write(nr *int64, err *error, w io.Writer, b []byte) {
type HttpLogger (line 111) | type HttpLogger struct
method LogResp (line 134) | func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.Pr...
method LogReq (line 156) | func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.Proxy...
method LogMeta (line 171) | func (logger *HttpLogger) LogMeta(m *Meta) {
method Close (line 175) | func (logger *HttpLogger) Close() error {
function NewLogger (line 117) | func NewLogger(basepath string) (*HttpLogger, error) {
type TeeReadCloser (line 182) | type TeeReadCloser struct
method Read (line 192) | func (t *TeeReadCloser) Read(b []byte) (int, error) {
method Close (line 198) | func (t *TeeReadCloser) Close() error {
function NewTeeReadCloser (line 188) | func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser {
type stoppableListener (line 209) | type stoppableListener struct
method Accept (line 223) | func (sl *stoppableListener) Accept() (net.Conn, error) {
type stoppableConn (line 214) | type stoppableConn struct
method Close (line 232) | func (sc *stoppableConn) Close() error {
function newStoppableListener (line 219) | func newStoppableListener(l net.Listener) *stoppableListener {
function main (line 237) | func main() {
FILE: examples/goproxy-transparent/transparent.go
function orPanic (line 19) | func orPanic(err error) {
function main (line 25) | func main() {
function dial (line 115) | func dial(ctx context.Context, proxy *goproxy.ProxyHttpServer, network, ...
function connectDial (line 124) | func connectDial(ctx context.Context, proxy *goproxy.ProxyHttpServer, ne...
type dumbResponseWriter (line 131) | type dumbResponseWriter struct
method Header (line 135) | func (dumb dumbResponseWriter) Header() http.Header {
method Write (line 139) | func (dumb dumbResponseWriter) Write(buf []byte) (int, error) {
method WriteHeader (line 146) | func (dumb dumbResponseWriter) WriteHeader(code int) {
method Hijack (line 150) | func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, ...
FILE: examples/hijack/main.go
function main (line 14) | func main() {
FILE: examples/html-parser/jquery_test.go
function equal (line 15) | func equal(u, v []string) bool {
function readFile (line 27) | func readFile(fname string, t *testing.T) string {
function TestDefectiveScriptParser (line 35) | func TestDefectiveScriptParser(t *testing.T) {
function proxyWithLog (line 65) | func proxyWithLog() (*http.Client, *bytes.Buffer) {
function get (line 76) | func get(t *testing.T, server *httptest.Server, client *http.Client, url...
function TestProxyServiceTwoVersions (line 85) | func TestProxyServiceTwoVersions(t *testing.T) {
function TestProxyService (line 106) | func TestProxyService(t *testing.T) {
FILE: examples/html-parser/main.go
function findScriptSrc (line 18) | func findScriptSrc(html string) []string {
function NewJQueryVersionProxy (line 34) | func NewJQueryVersionProxy() *goproxy.ProxyHttpServer {
function main (line 61) | func main() {
FILE: examples/image-manipulation/main.go
function main (line 11) | func main() {
FILE: examples/redirect-https/main.go
function main (line 12) | func main() {
FILE: examples/remove-https/main.go
function main (line 10) | func main() {
FILE: examples/request-filtering/noreddit.go
function main (line 10) | func main() {
FILE: examples/socket-keepalive/keepalive.go
function main (line 12) | func main() {
FILE: examples/websockets/main.go
function echo (line 16) | func echo(w http.ResponseWriter, r *http.Request) {
function StartEchoServer (line 39) | func StartEchoServer() {
function StartProxy (line 50) | func StartProxy() {
function main (line 63) | func main() {
FILE: ext/auth/basic.go
function BasicUnauthorized (line 15) | func BasicUnauthorized(req *http.Request, realm string) *http.Response {
function auth (line 33) | func auth(req *http.Request, f func(user, passwd string) bool) bool {
function Basic (line 53) | func Basic(realm string, f func(user, passwd string) bool) goproxy.ReqHa...
function BasicConnect (line 65) | func BasicConnect(realm string, f func(user, passwd string) bool) goprox...
function ProxyBasic (line 76) | func ProxyBasic(proxy *goproxy.ProxyHttpServer, realm string, f func(use...
FILE: ext/auth/basic_test.go
type ConstantHanlder (line 20) | type ConstantHanlder
method ServeHTTP (line 22) | func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Requ...
function oneShotProxy (line 26) | func oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, ...
function times (line 35) | func times(n int, s string) string {
function TestBasicConnectAuthWithCurl (line 43) | func TestBasicConnectAuthWithCurl(t *testing.T) {
function TestBasicAuthWithCurl (line 71) | func TestBasicAuthWithCurl(t *testing.T) {
function TestBasicAuth (line 98) | func TestBasicAuth(t *testing.T) {
function TestWithBrowser (line 144) | func TestWithBrowser(t *testing.T) {
FILE: ext/har/logger.go
type ExportFunc (line 11) | type ExportFunc
type Logger (line 14) | type Logger struct
method OnRequest (line 56) | func (l *Logger) OnRequest(req *http.Request, ctx *goproxy.ProxyCtx) (...
method OnResponse (line 62) | func (l *Logger) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx...
method exportLoop (line 88) | func (l *Logger) exportLoop() {
method Stop (line 122) | func (l *Logger) Stop() {
type LoggerOption (line 22) | type LoggerOption
function WithExportInterval (line 25) | func WithExportInterval(d time.Duration) LoggerOption {
function WithExportThreshold (line 32) | func WithExportThreshold(threshold int) LoggerOption {
function NewLogger (line 39) | func NewLogger(exportFunc ExportFunc, opts ...LoggerOption) *Logger {
FILE: ext/har/logger_test.go
type ConstantHandler (line 19) | type ConstantHandler
method ServeHTTP (line 21) | func (h ConstantHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ...
function createTestProxy (line 27) | func createTestProxy(logger *Logger) *httptest.Server {
function createProxyClient (line 35) | func createProxyClient(proxyURL string) *http.Client {
function TestHarLoggerBasicFunctionality (line 43) | func TestHarLoggerBasicFunctionality(t *testing.T) {
function TestLoggerThresholdExport (line 117) | func TestLoggerThresholdExport(t *testing.T) {
function TestHarLoggerExportInterval (line 173) | func TestHarLoggerExportInterval(t *testing.T) {
FILE: ext/har/types.go
type Har (line 18) | type Har struct
type Log (line 22) | type Log struct
function New (line 31) | func New() *Har {
function makeNewEntries (line 46) | func makeNewEntries() []Entry {
type Creator (line 51) | type Creator struct
type Browser (line 57) | type Browser struct
type Page (line 63) | type Page struct
type Entry (line 71) | type Entry struct
method fillIPAddress (line 109) | func (entry *Entry) fillIPAddress(req *http.Request) {
type Cache (line 84) | type Cache struct
type CacheEntry (line 89) | type CacheEntry struct
type Request (line 97) | type Request struct
function readBody (line 120) | func readBody(ctx *goproxy.ProxyCtx, body io.ReadCloser) ([]byte, error) {
function parseMediaType (line 130) | func parseMediaType(ctx *goproxy.ProxyCtx, header http.Header) string {
function parsePostData (line 144) | func parsePostData(ctx *goproxy.ProxyCtx, req *http.Request) *PostData {
type Response (line 177) | type Response struct
function parseResponse (line 190) | func parseResponse(ctx *goproxy.ProxyCtx) *Response {
function parseRequest (line 226) | func parseRequest(ctx *goproxy.ProxyCtx) *Request {
function parseStringArrMap (line 258) | func parseStringArrMap(stringArrMap map[string][]string) []NameValuePair {
function parseCookies (line 285) | func parseCookies(cookies []*http.Cookie) []Cookie {
type Cookie (line 304) | type Cookie struct
type NameValuePair (line 314) | type NameValuePair struct
type PostData (line 319) | type PostData struct
type PostDataParam (line 326) | type PostDataParam struct
type Content (line 334) | type Content struct
type PageTimings (line 343) | type PageTimings struct
type Timings (line 349) | type Timings struct
FILE: ext/html/html.go
function HandleString (line 39) | func HandleString(f func(s string, ctx *goproxy.ProxyCtx) string) goprox...
function HandleStringReader (line 52) | func HandleStringReader(f func(r io.Reader, ctx *goproxy.ProxyCtx) io.Re...
type readFirstCloseBoth (line 82) | type readFirstCloseBoth struct
method Read (line 87) | func (rfcb *readFirstCloseBoth) Read(b []byte) (nr int, err error) {
method Close (line 90) | func (rfcb *readFirstCloseBoth) Close() error {
FILE: ext/html/html_test.go
type ConstantServer (line 13) | type ConstantServer
method ServeHTTP (line 15) | func (s ConstantServer) ServeHTTP(w http.ResponseWriter, r *http.Reque...
function TestCharset (line 20) | func TestCharset(t *testing.T) {
FILE: ext/image/image.go
function HandleImage (line 23) | func HandleImage(f func(img image.Image, ctx *ProxyCtx) image.Image) Res...
FILE: ext/image/image_test.go
function oneShotProxy (line 19) | func oneShotProxy(proxy *goproxy.ProxyHttpServer, t *testing.T) (client ...
function getImage (line 28) | func getImage(file string, t *testing.T) image.Image {
function compareImage (line 40) | func compareImage(eImg, aImg image.Image, t *testing.T) {
function localFile (line 59) | func localFile(url string) string { return fs.URL + "/" + url }
function TestConstantImageHandler (line 61) | func TestConstantImageHandler(t *testing.T) {
function TestImageHandler (line 84) | func TestImageHandler(t *testing.T) {
function fatalOnErr (line 121) | func fatalOnErr(err error, msg string, t *testing.T) {
function get (line 127) | func get(url string, client *http.Client) ([]byte, error) {
function getOrFail (line 140) | func getOrFail(url string, client *http.Client, t *testing.T) []byte {
function TestReplaceImage (line 148) | func TestReplaceImage(t *testing.T) {
FILE: ext/limitation/concurrency.go
function ConcurrentRequests (line 12) | func ConcurrentRequests(limit int) goproxy.ReqHandler {
FILE: ext/limitation/concurrency_test.go
function TestConcurrentRequests (line 13) | func TestConcurrentRequests(t *testing.T) {
FILE: h2.go
type H2Transport (line 21) | type H2Transport struct
method RoundTrip (line 31) | func (r *H2Transport) RoundTrip(_ *http.Request) (*http.Response, erro...
function dial (line 97) | func dial(network, addr string) (c net.Conn, err error) {
function proxyFrame (line 108) | func proxyFrame(fr *http2.Framer) error {
FILE: http.go
method handleHttp (line 10) | func (proxy *ProxyHttpServer) handleHttp(w http.ResponseWriter, r *http....
FILE: https.go
type ConnectActionLiteral (line 22) | type ConnectActionLiteral
constant ConnectAccept (line 25) | ConnectAccept = iota
constant ConnectReject (line 26) | ConnectReject
constant ConnectMitm (line 27) | ConnectMitm
constant ConnectHijack (line 28) | ConnectHijack
constant ConnectHTTPMitm (line 30) | ConnectHTTPMitm
constant ConnectProxyAuthHijack (line 31) | ConnectProxyAuthHijack
constant _tlsRecordTypeHandshake (line 44) | _tlsRecordTypeHandshake = byte(22)
type readBufferedConn (line 46) | type readBufferedConn struct
method Read (line 51) | func (c *readBufferedConn) Read(p []byte) (int, error) {
type ConnectAction (line 59) | type ConnectAction struct
function stripPort (line 65) | func stripPort(s string) string {
method dial (line 87) | func (proxy *ProxyHttpServer) dial(ctx *ProxyCtx, network, addr string) ...
method connectDial (line 102) | func (proxy *ProxyHttpServer) connectDial(ctx *ProxyCtx, network, addr s...
type halfClosable (line 114) | type halfClosable interface
method handleHttps (line 122) | func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http...
function httpError (line 401) | func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) {
function copyOrWarn (line 420) | func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader) error {
function copyAndClose (line 431) | func copyAndClose(ctx *ProxyCtx, dst, src halfClosable, wg *sync.WaitGro...
function dialerFromEnv (line 442) | func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (n...
method NewConnectDialToProxy (line 453) | func (proxy *ProxyHttpServer) NewConnectDialToProxy(httpsProxy string) f...
method NewConnectDialToProxyWithHandler (line 457) | func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(
function TLSConfigFromCA (line 555) | func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCt...
method initializeTLSconnection (line 583) | func (proxy *ProxyHttpServer) initializeTLSconnection(
FILE: internal/http1parser/header.go
function Http1ExtractHeaders (line 15) | func Http1ExtractHeaders(r *textproto.Reader) ([]string, error) {
FILE: internal/http1parser/header_test.go
function TestHttp1ExtractHeaders_Empty (line 14) | func TestHttp1ExtractHeaders_Empty(t *testing.T) {
function TestHttp1ExtractHeaders (line 24) | func TestHttp1ExtractHeaders(t *testing.T) {
function TestHttp1ExtractHeaders_InvalidData (line 41) | func TestHttp1ExtractHeaders_InvalidData(t *testing.T) {
FILE: internal/http1parser/request.go
type RequestReader (line 12) | type RequestReader struct
method IsEOF (line 38) | func (r *RequestReader) IsEOF() bool {
method Reader (line 47) | func (r *RequestReader) Reader() *bufio.Reader {
method ReadRequest (line 51) | func (r *RequestReader) ReadRequest() (*http.Request, error) {
function NewRequestReader (line 19) | func NewRequestReader(preventCanonicalization bool, conn io.Reader) *Req...
function getRequestReader (line 83) | func getRequestReader(r *bufio.Reader, cloned *bytes.Buffer) *textproto....
FILE: internal/http1parser/request_test.go
constant _data (line 19) | _data = "POST /index.html HTTP/1.1\r\n" +
constant _data2 (line 27) | _data2 = "GET /index.html HTTP/1.1\r\n" +
function TestCanonicalRequest (line 34) | func TestCanonicalRequest(t *testing.T) {
function TestNonCanonicalRequest (line 56) | func TestNonCanonicalRequest(t *testing.T) {
function TestMultipleNonCanonicalRequests (line 67) | func TestMultipleNonCanonicalRequests(t *testing.T) {
type reqTest (line 90) | type reqTest struct
function TestReadRequest (line 172) | func TestReadRequest(t *testing.T) {
FILE: internal/signer/counterecryptor.go
type CounterEncryptorRand (line 14) | type CounterEncryptorRand struct
method Seed (line 50) | func (c *CounterEncryptorRand) Seed(b []byte) {
method refill (line 57) | func (c *CounterEncryptorRand) refill() {
method Read (line 67) | func (c *CounterEncryptorRand) Read(b []byte) (n int, err error) {
function NewCounterEncryptorRandFromKey (line 21) | func NewCounterEncryptorRandFromKey(key any, seed []byte) (r CounterEncr...
FILE: internal/signer/counterecryptor_test.go
type RandSeedReader (line 15) | type RandSeedReader struct
method Read (line 19) | func (r *RandSeedReader) Read(b []byte) (n int, err error) {
function fatalOnErr (line 26) | func fatalOnErr(t *testing.T, err error, msg string) {
function TestCounterEncDifferentConsecutive (line 33) | func TestCounterEncDifferentConsecutive(t *testing.T) {
function TestCounterEncIdenticalStreams (line 48) | func TestCounterEncIdenticalStreams(t *testing.T) {
function stddev (line 73) | func stddev(data []int) float64 {
function TestCounterEncStreamHistogram (line 84) | func TestCounterEncStreamHistogram(t *testing.T) {
FILE: internal/signer/signer.go
constant _goproxySignerVersion (line 23) | _goproxySignerVersion = ":goproxy2"
function hashSorted (line 25) | func hashSorted(lst []string) []byte {
function SignHost (line 34) | func SignHost(ca tls.Certificate, hosts []string) (cert *tls.Certificate...
FILE: internal/signer/signer_test.go
function orFatal (line 20) | func orFatal(t *testing.T, msg string, err error) {
type ConstantHanlder (line 27) | type ConstantHanlder
method ServeHTTP (line 29) | func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, _ *http.Requ...
function getBrowser (line 33) | func getBrowser(args []string) string {
function testSignerX509 (line 45) | func testSignerX509(t *testing.T, ca tls.Certificate) {
function testSignerTLS (line 62) | func testSignerTLS(t *testing.T, ca tls.Certificate) {
function TestSignerRsaTls (line 99) | func TestSignerRsaTls(t *testing.T) {
function TestSignerRsaX509 (line 103) | func TestSignerRsaX509(t *testing.T) {
function TestSignerEcdsaTls (line 107) | func TestSignerEcdsaTls(t *testing.T) {
function TestSignerEcdsaX509 (line 111) | func TestSignerEcdsaX509(t *testing.T) {
function BenchmarkSignRsa (line 115) | func BenchmarkSignRsa(b *testing.B) {
function BenchmarkSignEcdsa (line 125) | func BenchmarkSignEcdsa(b *testing.B) {
function init (line 162) | func init() {
FILE: logger.go
type Logger (line 3) | type Logger interface
FILE: proxy.go
type ProxyHttpServer (line 13) | type ProxyHttpServer struct
method filterRequest (line 70) | func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *Prox...
method filterResponse (line 83) | func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ...
method ServeHTTP (line 137) | func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http...
function copyHeaders (line 58) | func copyHeaders(dst, src http.Header, keepDestHeaders bool) {
function RemoveProxyHeaders (line 93) | func RemoveProxyHeaders(ctx *ProxyCtx, r *http.Request) {
type flushWriter (line 122) | type flushWriter struct
method Write (line 126) | func (fw flushWriter) Write(p []byte) (int, error) {
function NewProxyHttpServer (line 146) | func NewProxyHttpServer() *ProxyHttpServer {
FILE: proxy_test.go
type QueryHandler (line 37) | type QueryHandler struct
method ServeHTTP (line 39) | func (QueryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
type HeadersHandler (line 46) | type HeadersHandler struct
method ServeHTTP (line 49) | func (HeadersHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque...
function init (line 62) | func init() {
type ConstantHanlder (line 68) | type ConstantHanlder
method ServeHTTP (line 70) | func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Requ...
function get (line 74) | func get(url string, client *http.Client) ([]byte, error) {
function getOrFail (line 91) | func getOrFail(t *testing.T, url string, client *http.Client) []byte {
function getCert (line 100) | func getCert(t *testing.T, c *tls.Conn) []byte {
function localFile (line 108) | func localFile(url string) string {
function TestSimpleHttpReqWithProxy (line 112) | func TestSimpleHttpReqWithProxy(t *testing.T) {
function oneShotProxy (line 128) | func oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, ...
function TestSimpleHook (line 142) | func TestSimpleHook(t *testing.T) {
function TestAlwaysHook (line 160) | func TestAlwaysHook(t *testing.T) {
function TestReplaceResponse (line 176) | func TestReplaceResponse(t *testing.T) {
function TestReplaceReponseForUrl (line 192) | func TestReplaceReponseForUrl(t *testing.T) {
function TestOneShotFileServer (line 211) | func TestOneShotFileServer(t *testing.T) {
function TestContentType (line 238) | func TestContentType(t *testing.T) {
function panicOnErr (line 277) | func panicOnErr(err error, msg string) {
function TestChangeResp (line 283) | func TestChangeResp(t *testing.T) {
function TestSimpleMitm (line 313) | func TestSimpleMitm(t *testing.T) {
function TestMitmMutateRequest (line 366) | func TestMitmMutateRequest(t *testing.T) {
function TestConnectHandler (line 384) | func TestConnectHandler(t *testing.T) {
function TestMitmIsFiltered (line 399) | func TestMitmIsFiltered(t *testing.T) {
function TestFirstHandlerMatches (line 420) | func TestFirstHandlerMatches(t *testing.T) {
function TestIcyResponse (line 437) | func TestIcyResponse(t *testing.T) {
type VerifyNoProxyHeaders (line 459) | type VerifyNoProxyHeaders struct
method ServeHTTP (line 463) | func (v VerifyNoProxyHeaders) ServeHTTP(w http.ResponseWriter, r *http...
function TestNoProxyHeaders (line 470) | func TestNoProxyHeaders(t *testing.T) {
function TestNoProxyHeadersHttps (line 482) | func TestNoProxyHeadersHttps(t *testing.T) {
type VerifyAcceptEncodingHeader (line 494) | type VerifyAcceptEncodingHeader struct
method ServeHTTP (line 498) | func (v *VerifyAcceptEncodingHeader) ServeHTTP(w http.ResponseWriter, ...
function TestAcceptEncoding (line 502) | func TestAcceptEncoding(t *testing.T) {
function TestHeadReqHasContentLength (line 546) | func TestHeadReqHasContentLength(t *testing.T) {
function TestChunkedResponse (line 562) | func TestChunkedResponse(t *testing.T) {
function TestGoproxyThroughProxy (line 633) | func TestGoproxyThroughProxy(t *testing.T) {
function TestHttpProxyAddrsFromEnv (line 657) | func TestHttpProxyAddrsFromEnv(t *testing.T) {
function TestGoproxyHijackConnect (line 681) | func TestGoproxyHijackConnect(t *testing.T) {
function readResponse (line 717) | func readResponse(buf *bufio.Reader) string {
function writeConnect (line 728) | func writeConnect(w io.Writer) {
function TestCurlMinusP (line 744) | func TestCurlMinusP(t *testing.T) {
function TestSelfRequest (line 775) | func TestSelfRequest(t *testing.T) {
function TestHasGoproxyCA (line 784) | func TestHasGoproxyCA(t *testing.T) {
type TestCertStorage (line 801) | type TestCertStorage struct
method Fetch (line 807) | func (tcs *TestCertStorage) Fetch(hostname string, gen func() (*tls.Ce...
method statHits (line 826) | func (tcs *TestCertStorage) statHits() int {
method statMisses (line 830) | func (tcs *TestCertStorage) statMisses() int {
function newTestCertStorage (line 834) | func newTestCertStorage() *TestCertStorage {
function TestProxyWithCertStorage (line 841) | func TestProxyWithCertStorage(t *testing.T) {
function TestHttpsMitmURLRewrite (line 889) | func TestHttpsMitmURLRewrite(t *testing.T) {
function TestSimpleHttpRequest (line 957) | func TestSimpleHttpRequest(t *testing.T) {
function TestResponseContentLength (line 1018) | func TestResponseContentLength(t *testing.T) {
function TestMITMResponseHTTP2MissingContentLength (line 1057) | func TestMITMResponseHTTP2MissingContentLength(t *testing.T) {
function TestMITMResponseContentLength (line 1122) | func TestMITMResponseContentLength(t *testing.T) {
function TestMITMEmptyBody (line 1144) | func TestMITMEmptyBody(t *testing.T) {
function TestMITMOverwriteAlreadyEmptyBody (line 1168) | func TestMITMOverwriteAlreadyEmptyBody(t *testing.T) {
function TestMITMOverwriteBodyToEmpty (line 1194) | func TestMITMOverwriteBodyToEmpty(t *testing.T) {
function TestMITMRequestCancel (line 1220) | func TestMITMRequestCancel(t *testing.T) {
function TestNewResponseProtoVersion (line 1267) | func TestNewResponseProtoVersion(t *testing.T) {
function TestNewResponseMitmWrite (line 1286) | func TestNewResponseMitmWrite(t *testing.T) {
function TestPersistentMitmRequest (line 1310) | func TestPersistentMitmRequest(t *testing.T) {
FILE: regretable/regretreader.go
type Reader (line 16) | type Reader struct
method Regret (line 27) | func (rb *Reader) Regret() {
method Forget (line 43) | func (rb *Reader) Forget() {
method Read (line 62) | func (rb *Reader) Read(p []byte) (n int, err error) {
constant _defaultBufferSize (line 23) | _defaultBufferSize = 500
function NewRegretableReaderSize (line 52) | func NewRegretableReaderSize(r io.Reader, size int) *Reader {
function NewRegretableReader (line 57) | func NewRegretableReader(r io.Reader) *Reader {
type ReaderCloser (line 81) | type ReaderCloser struct
method Close (line 97) | func (rbc *ReaderCloser) Close() error {
function NewRegretableReaderCloser (line 87) | func NewRegretableReaderCloser(rc io.ReadCloser) *ReaderCloser {
function NewRegretableReaderCloserSize (line 92) | func NewRegretableReaderCloserSize(rc io.ReadCloser, size int) *ReaderCl...
FILE: regretable/regretreader_test.go
function assertEqual (line 12) | func assertEqual(t *testing.T, expected, actual string) {
function assertReadAll (line 19) | func assertReadAll(t *testing.T, r io.Reader) string {
function TestRegretableReader (line 28) | func TestRegretableReader(t *testing.T) {
function TestRegretableEmptyRead (line 44) | func TestRegretableEmptyRead(t *testing.T) {
function TestRegretableAlsoEmptyRead (line 60) | func TestRegretableAlsoEmptyRead(t *testing.T) {
function TestRegretableRegretBeforeRead (line 80) | func TestRegretableRegretBeforeRead(t *testing.T) {
function TestRegretableFullRead (line 96) | func TestRegretableFullRead(t *testing.T) {
function TestRegretableRegretTwice (line 112) | func TestRegretableRegretTwice(t *testing.T) {
type CloseCounter (line 125) | type CloseCounter struct
method Read (line 130) | func (cc *CloseCounter) Read(b []byte) (int, error) {
method Close (line 134) | func (cc *CloseCounter) Close() error {
function TestRegretableCloserSizeRegrets (line 139) | func TestRegretableCloserSizeRegrets(t *testing.T) {
function TestRegretableCloserRegretsClose (line 158) | func TestRegretableCloserRegretsClose(t *testing.T) {
FILE: responses.go
function NewResponse (line 17) | func NewResponse(r *http.Request, contentType string, status int, body s...
constant ContentTypeText (line 35) | ContentTypeText = "text/plain"
constant ContentTypeHtml (line 36) | ContentTypeHtml = "text/html"
function TextResponse (line 40) | func TextResponse(r *http.Request, text string) *http.Response {
FILE: transport/roundtripper.go
type RoundTripper (line 5) | type RoundTripper interface
FILE: transport/transport.go
constant DefaultMaxIdleConnsPerHost (line 40) | DefaultMaxIdleConnsPerHost = 2
type Transport (line 45) | type Transport struct
method DetailedRoundTrip (line 131) | func (t *Transport) DetailedRoundTrip(req *http.Request) (details *Rou...
method RoundTrip (line 170) | func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response,...
method RegisterProtocol (line 181) | func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) {
method CloseIdleConnections (line 200) | func (t *Transport) CloseIdleConnections() {
method connectMethodForRequest (line 225) | func (t *Transport) connectMethodForRequest(treq *transportRequest) (*...
method putIdleConn (line 256) | func (t *Transport) putIdleConn(pconn *persistConn) bool {
method getIdleConn (line 279) | func (t *Transport) getIdleConn(cm *connectMethod) (pconn *persistConn) {
method dial (line 306) | func (t *Transport) dial(network, addr string) (c net.Conn, raddr stri...
method getConn (line 330) | func (t *Transport) getConn(cm *connectMethod) (*persistConn, error) {
function ProxyFromEnvironment (line 84) | func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
function ProxyURL (line 104) | func ProxyURL(fixedURL *url.URL) func(*http.Request) (*url.URL, error) {
type transportRequest (line 112) | type transportRequest struct
method extraHeaders (line 117) | func (tr *transportRequest) extraHeaders() http.Header {
type RoundTripDetails (line 124) | type RoundTripDetails struct
function getenvEitherCase (line 218) | func getenvEitherCase(k string) string {
function useProxy (line 419) | func useProxy(addr string) bool {
type connectMethod (line 474) | type connectMethod struct
method proxyAuth (line 242) | func (cm *connectMethod) proxyAuth() string {
method String (line 480) | func (cm *connectMethod) String() string {
method addr (line 489) | func (cm *connectMethod) addr() string {
method tlsHost (line 498) | func (cm *connectMethod) tlsHost() string {
type persistConn (line 508) | type persistConn struct
method isBroken (line 530) | func (pc *persistConn) isBroken() bool {
method readLoop (line 536) | func (pc *persistConn) readLoop() {
method roundTrip (line 645) | func (pc *persistConn) roundTrip(req *transportRequest) (resp *http.Re...
method close (line 689) | func (pc *persistConn) close() {
method closeLocked (line 695) | func (pc *persistConn) closeLocked() {
type responseAndError (line 630) | type responseAndError struct
type requestAndChan (line 635) | type requestAndChan struct
function canonicalAddr (line 707) | func canonicalAddr(url *url.URL) string {
type bodyEOFSignal (line 718) | type bodyEOFSignal struct
method Read (line 724) | func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
method Close (line 736) | func (es *bodyEOFSignal) Close() (err error) {
type readFirstCloseBoth (line 749) | type readFirstCloseBoth struct
method Close (line 754) | func (r *readFirstCloseBoth) Close() error {
type discardOnCloseReadCloser (line 766) | type discardOnCloseReadCloser struct
method Close (line 770) | func (d *discardOnCloseReadCloser) Close() error {
FILE: transport/util.go
type badStringError (line 8) | type badStringError struct
method Error (line 13) | func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", ...
function hasPort (line 15) | func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings...
FILE: websocket.go
function headerContains (line 10) | func headerContains(header http.Header, name string, value string) bool {
function isWebSocketHandshake (line 21) | func isWebSocketHandshake(header http.Header) bool {
method hijackConnection (line 26) | func (proxy *ProxyHttpServer) hijackConnection(ctx *ProxyCtx, w http.Res...
method proxyWebsocket (line 40) | func (proxy *ProxyHttpServer) proxyWebsocket(ctx *ProxyCtx, remoteConn i...
FILE: websocket_test.go
function TestWebSocketMitm (line 18) | func TestWebSocketMitm(t *testing.T) {
Condensed preview — 103 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (413K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 24,
"preview": "open_collective: goproxy"
},
{
"path": ".github/dependabot.yml",
"chars": 355,
"preview": "version: 2\nupdates:\n # GitHub Actions Pipeline\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:"
},
{
"path": ".github/workflows/go.yml",
"chars": 1107,
"preview": "name: Code Check\n\non:\n workflow_dispatch:\n pull_request:\n types:\n - opened\n - synchronize\n - reopene"
},
{
"path": ".gitignore",
"chars": 10,
"preview": "bin\n*.swp\n"
},
{
"path": ".golangci.yml",
"chars": 3256,
"preview": "version: \"2\"\nrun:\n modules-download-mode: readonly\n\n# List from https://golangci-lint.run/usage/linters/\nlinters:\n ena"
},
{
"path": "LICENSE",
"chars": 1487,
"preview": "Copyright (c) 2012 Elazar Leibovich. All rights reserved.\n\nRedistribution and use in source and binary forms, with or wi"
},
{
"path": "README.md",
"chars": 14446,
"preview": "# GoProxy\n\n\n[\n\nvar GoproxyCa tls.Certificate\n\nfunc init() {\n\t// When we inclu"
},
{
"path": "ctx.go",
"chars": 3289,
"preview": "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, conta"
},
{
"path": "dispatcher.go",
"chars": 13836,
"preview": "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 decid"
},
{
"path": "dispatcher_test.go",
"chars": 841,
"preview": "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\nfun"
},
{
"path": "doc.go",
"chars": 3735,
"preview": "/*\nPackage goproxy provides a customizable HTTP proxy,\nsupporting hijacking HTTPS connection.\n\nThe intent of the proxy, "
},
{
"path": "examples/base/README.md",
"chars": 954,
"preview": "# Simple HTTP Proxy\n\nThis example contains a base HTTP proxy server that listens on port :8080.\nIt only handles explicit"
},
{
"path": "examples/base/main.go",
"chars": 371,
"preview": "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("
},
{
"path": "examples/cascadeproxy/README.md",
"chars": 556,
"preview": "# CascadeProxy\n\n`CascadeProxy` is an example that shows an aggregator server that forwards\nthe requests to another proxy"
},
{
"path": "examples/cascadeproxy/main.go",
"chars": 2524,
"preview": "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/ela"
},
{
"path": "examples/cascadeproxy-socks/README.md",
"chars": 1196,
"preview": "# CascadeSocksProxy\r\n\r\n`CascadeSocksProxy` is an example that shows an aggregator server that forwards\r\nthe requests to "
},
{
"path": "examples/cascadeproxy-socks/socks5proxyserver/go.mod",
"chars": 81,
"preview": "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",
"chars": 574,
"preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/pmezard/go-difflib v1.0.0 h"
},
{
"path": "examples/cascadeproxy-socks/socks5proxyserver/main.go",
"chars": 617,
"preview": "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 SO"
},
{
"path": "examples/cascadeproxy-socks/socksproxy.go",
"chars": 1799,
"preview": "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 SocksA"
},
{
"path": "examples/certstorage/README.md",
"chars": 509,
"preview": "# CertStorage\n\nCertStorage example is important to improve the performance of an\nHTTPS proxy server, which you can build"
},
{
"path": "examples/certstorage/cache.go",
"chars": 671,
"preview": "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"
},
{
"path": "examples/certstorage/main.go",
"chars": 669,
"preview": "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(\""
},
{
"path": "examples/customca/README.md",
"chars": 2853,
"preview": "# CustomCA\n\nThis example shows you how to use a custom CA to sign the HTTPS MITM\nrequests (you can use your own generate"
},
{
"path": "examples/customca/cert.go",
"chars": 3039,
"preview": "package main\n\nvar _caCert = []byte(`-----BEGIN CERTIFICATE-----\nMIIDkzCCAnugAwIBAgIJAKe/ZGdfcHdPMA0GCSqGSIb3DQEBCwUAMGAx"
},
{
"path": "examples/customca/main.go",
"chars": 1088,
"preview": "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 ma"
},
{
"path": "examples/go.mod",
"chars": 400,
"preview": "module github.com/elazarl/goproxy/examples/goproxy-transparent\n\ngo 1.23\n\nrequire (\n\tgithub.com/coder/websocket v1.8.14\n\t"
},
{
"path": "examples/go.sum",
"chars": 1579,
"preview": "github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=\ngithub.com/coder/websocket v1.8.14/go"
},
{
"path": "examples/goproxy-httpdump/README.md",
"chars": 932,
"preview": "# Trace HTTP Requests and Responses\n\n`goproxy-httpdump` starts an HTTP proxy on :8080. It handles explicit CONNECT\nreque"
},
{
"path": "examples/goproxy-httpdump/httpdump.go",
"chars": 6621,
"preview": "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"
},
{
"path": "examples/goproxy-transparent/README.md",
"chars": 1759,
"preview": "# Transparent Proxy\n\nThis transparent example in goproxy is meant to show how to transparent proxy and hijack all http a"
},
{
"path": "examples/goproxy-transparent/proxy.sh",
"chars": 1208,
"preview": "#!/bin/sh\n# goproxy IP\nGOPROXY_SERVER=\"10.10.10.1\"\n# goproxy port\nGOPROXY_PORT=\"3129\"\nGOPROXY_PORT_TLS=\"3128\"\n# DO NOT M"
},
{
"path": "examples/goproxy-transparent/transparent.go",
"chars": 4337,
"preview": "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\"git"
},
{
"path": "examples/hijack/README.md",
"chars": 857,
"preview": "# Hijack\nIn this example we intercept the data of an HTTP request and decide to\nmodify them before sending to the client"
},
{
"path": "examples/hijack/main.go",
"chars": 1110,
"preview": "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("
},
{
"path": "examples/html-parser/README.md",
"chars": 1236,
"preview": "# HTML Parser\n\n`html-parser` starts an HTTP proxy on :8080.\nIt checks HTML responses, looks for scripts referencing jQue"
},
{
"path": "examples/html-parser/jquery1.html",
"chars": 93,
"preview": "<!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",
"chars": 93,
"preview": "<!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",
"chars": 13475,
"preview": "<!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:"
},
{
"path": "examples/html-parser/jquery_test.go",
"chars": 3389,
"preview": "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\n"
},
{
"path": "examples/html-parser/main.go",
"chars": 1779,
"preview": "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\""
},
{
"path": "examples/html-parser/php_man.html",
"chars": 20640,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-s"
},
{
"path": "examples/html-parser/w3schools.html",
"chars": 35204,
"preview": "<!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=\"/favic"
},
{
"path": "examples/image-manipulation/README.md",
"chars": 242,
"preview": "# Image Manipulation\n\nThis example starts a proxy server that manipulate the received images,\nto make them appear upside"
},
{
"path": "examples/image-manipulation/main.go",
"chars": 575,
"preview": "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\""
},
{
"path": "examples/redirect-https/README.md",
"chars": 218,
"preview": "# Redirect HTTPS\n\n`redirect-https` example redirects all the HTTPS request to HTTP endpoint,\nby returning a `303 See Oth"
},
{
"path": "examples/redirect-https/main.go",
"chars": 922,
"preview": "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"
},
{
"path": "examples/remove-https/README.md",
"chars": 328,
"preview": "# Remove HTTPS\n\n`remove-https` example forwards all the HTTPS request as HTTP requests,\neffectively removing the https s"
},
{
"path": "examples/remove-https/main.go",
"chars": 616,
"preview": "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(\""
},
{
"path": "examples/request-filtering/README.md",
"chars": 520,
"preview": "# Request Filtering\n\n`request-filtering` starts an HTTP proxy on :8080. It denies requests\nto \"www.reddit.com\" made betw"
},
{
"path": "examples/request-filtering/noreddit.go",
"chars": 607,
"preview": "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.NewPr"
},
{
"path": "examples/socket-keepalive/README.md",
"chars": 458,
"preview": "# Socket KeepAlive\n\n`socket-keepalive` example adds a custom net.Dialer that can be configured\nby the user, enabling TCP"
},
{
"path": "examples/socket-keepalive/keepalive.go",
"chars": 694,
"preview": "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\tverb"
},
{
"path": "examples/websockets/README.md",
"chars": 409,
"preview": "# Websockets\n`websockets` example shows an example of a WebSocket request made\nthrough the proxy server.\nThe target serv"
},
{
"path": "examples/websockets/localhost-key.pem",
"chars": 227,
"preview": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIC1vu3JK+Z6WtbpaTL4LquNQVcwSha53sVIxEdcLNG6WoAoGCCqGSM49\nAwEHoUQDQgAE8OpaVYv567gc"
},
{
"path": "examples/websockets/localhost.pem",
"chars": 977,
"preview": "-----BEGIN CERTIFICATE-----\nMIICozCCAYugAwIBAgIUEdJ4Tu/Hs4HznpHjnb3Akj7uNKgwDQYJKoZIhvcNAQEL\nBQAwDTELMAkGA1UEAxMCQ0EwHhc"
},
{
"path": "examples/websockets/main.go",
"chars": 2775,
"preview": "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"
},
{
"path": "ext/auth/basic.go",
"chars": 2497,
"preview": "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 un"
},
{
"path": "ext/auth/basic_test.go",
"chars": 4660,
"preview": "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\""
},
{
"path": "ext/go.mod",
"chars": 362,
"preview": "module github.com/elazarl/goproxy/ext\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/elazarl/goproxy v0.0.0-20241217120900-7711dfa381"
},
{
"path": "ext/go.sum",
"chars": 1418,
"preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
},
{
"path": "ext/har/logger.go",
"chars": 3112,
"preview": "package har\n\nimport (\n \"net/http\"\n \"time\"\n\n \"github.com/elazarl/goproxy\"\n)\n\n// ExportFunc is a function type th"
},
{
"path": "ext/har/logger_test.go",
"chars": 6369,
"preview": "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\""
},
{
"path": "ext/har/types.go",
"chars": 9835,
"preview": "// Original implementation from abourget/goproxy, adapted for use as an extension.\n// HAR specification: http://www.soft"
},
{
"path": "ext/html/cp1255.html",
"chars": 24608,
"preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n \r\n<html lang=\""
},
{
"path": "ext/html/cp1255.txt",
"chars": 0,
"preview": ""
},
{
"path": "ext/html/html.go",
"chars": 2971,
"preview": "// extension to goproxy that will allow you to easily filter web browser related content.\npackage goproxy_html\n\nimport ("
},
{
"path": "ext/html/html_test.go",
"chars": 1389,
"preview": "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/htt"
},
{
"path": "ext/image/image.go",
"chars": 2305,
"preview": "package goproxy_image\n\nimport (\n\t\"bytes\"\n\t. \"github.com/elazarl/goproxy\"\n\t\"github.com/elazarl/goproxy/regretable\"\n\t\"imag"
},
{
"path": "ext/image/image_test.go",
"chars": 4459,
"preview": "package goproxy_image_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"github.com/elazarl/goproxy\"\n\tgoproxy_image \"github.com/ela"
},
{
"path": "ext/limitation/concurrency.go",
"chars": 857,
"preview": "package limitation\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\n// ConcurrentRequests implements a mechanism "
},
{
"path": "ext/limitation/concurrency_test.go",
"chars": 2084,
"preview": "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/"
},
{
"path": "go.mod",
"chars": 343,
"preview": "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"
},
{
"path": "go.sum",
"chars": 1364,
"preview": "github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=\ngithub.com/coder/websocket v1.8.14/go"
},
{
"path": "h2.go",
"chars": 5436,
"preview": "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/"
},
{
"path": "http.go",
"chars": 2923,
"preview": "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"
},
{
"path": "https.go",
"chars": 18629,
"preview": "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"
},
{
"path": "internal/http1parser/header.go",
"chars": 1145,
"preview": "package http1parser\n\nimport (\n\t\"errors\"\n\t\"net/textproto\"\n\t\"strings\"\n)\n\nvar ErrBadProto = errors.New(\"bad protocol\")\n\n// "
},
{
"path": "internal/http1parser/header_test.go",
"chars": 1347,
"preview": "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/"
},
{
"path": "internal/http1parser/request.go",
"chars": 2577,
"preview": "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 struc"
},
{
"path": "internal/http1parser/request_test.go",
"chars": 5120,
"preview": "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"
},
{
"path": "internal/signer/counterecryptor.go",
"chars": 1639,
"preview": "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\""
},
{
"path": "internal/signer/counterecryptor_test.go",
"chars": 3081,
"preview": "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."
},
{
"path": "internal/signer/signer.go",
"chars": 3048,
"preview": "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"
},
{
"path": "internal/signer/signer_test.go",
"chars": 4808,
"preview": "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/"
},
{
"path": "key.pem",
"chars": 3243,
"preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF\n0j3dzRMUpAkemC/p94tGES9"
},
{
"path": "logger.go",
"chars": 76,
"preview": "package goproxy\n\ntype Logger interface {\n\tPrintf(format string, v ...any)\n}\n"
},
{
"path": "proxy.go",
"chars": 5540,
"preview": "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.Ha"
},
{
"path": "proxy_test.go",
"chars": 39941,
"preview": "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\""
},
{
"path": "regretable/regretreader.go",
"chars": 2610,
"preview": "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 "
},
{
"path": "regretable/regretreader_test.go",
"chars": 3954,
"preview": "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"
},
{
"path": "responses.go",
"chars": 1355,
"preview": "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 "
},
{
"path": "transport/roundtripper.go",
"chars": 857,
"preview": "package transport\n\nimport \"net/http\"\n\ntype RoundTripper interface {\n\t// RoundTrip executes a single HTTP transaction, re"
},
{
"path": "transport/transport.go",
"chars": 20313,
"preview": "// Copyright 2011 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "transport/util.go",
"chars": 288,
"preview": "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 *badS"
},
{
"path": "websocket.go",
"chars": 1383,
"preview": "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, va"
},
{
"path": "websocket_test.go",
"chars": 1944,
"preview": "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"
}
]
About this extraction
This page contains the full source code of the elazarl/goproxy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 103 files (367.9 KB), approximately 115.5k tokens, and a symbol index with 417 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.