├── README.md ├── go.mod ├── go.sum └── proxy.go /README.md: -------------------------------------------------------------------------------- 1 | # TLS-Fingerprint-API 2 | 3 | NOTE: Most of the cool TLS Fingerprinting evasion stuff is done in my other repo. This repo is a wrapper over my net/http client that allows it to be deployed as a proxy that can be used with other languages: 4 | https://github.com/Carcraftz/cclient 5 | 6 | A server that proxies requests and uses my fork of cclient & fhttp (fork of net/http) to prevent your requests from being fingerprinted. Built on open source software, this repo is a simple yet effective solution to companies violating your privacy. It uses cclient to spoof tls fingerprints, and fhttp to enable mimicry of chrome http/2 connection settings, header order, pseudo header order, and enable push. 7 | 8 | ## Support 9 | 10 | I decided to make this after being tired of similar software being gatekept. If you like my work, any support would be greatly appreciated ❤️ 11 | https://paypal.me/carcraftz 12 | 13 | ## How to use: 14 | 15 | Note: If you're using this in a language other than go, then use this repo. But if you're using this in go, I would reccomend using my fork of cclient instead (https://github.com/Carcraftz/cclient), which lets you access the request library directly instead of deploying a HTTP server on the localhost. The only difference is it doesn't set header order for you, you have to specify the header order yourself in the request. 16 | 17 | Deploy this server somewhere. Localhost is preferrable to reduce latency. The go source code is given if you want to build it yourself on any platform (windows, macos, linux). I will also be attaching a prebuilt windows exe if you don't want to build it yourself 18 | 19 | Modify your code to make requests to the server INSTEAD of the endpoint you want to request. Ex: If running on localhost, make requests to http://127.0.0.1:8082. Make sure to also remove any code that uses a proxy in the request. 20 | 21 | Add the request header "poptls-url", and set it equal to the endpoint you want to request. For example, if you want to request https://httpbin.org/get, you would add the header "poptls-url" = "https://httpbin.org/get" 22 | 23 | Optional: Add the request header "poptls-proxy" and set it equal to the URL for the proxy you want to use (format: http://user:pass@host:port or http://host:port). This will make the server use your proxy for the request. 24 | 25 | Optional: Add the request header "poptls-allowredirect" and set it to true or false to enable/disable redirects. Redirects are enabled by default. 26 | 27 | Optional: Add the request header "poptls-timeout" and set it to an integer (in seconds) to specify the max timeout to wait for a request. 28 | 29 | ## Run on a different Port: 30 | 31 | By default the program runs on port 8082. You can specify another port by passing a flag --port=PORTNUM 32 | 33 | ## Examples: 34 | 35 | ### Node.js 36 | 37 | To call this in node.js, lets say with node-fetch, you could do 38 | 39 | ```` 40 | fetch("http://localhost:8082",{ 41 | headers:{ 42 | "poptls-url":"https://httpbin.org/get", 43 | "poptls-proxy":"https://user:pass@ip:port", //optional 44 | "poptls-allowredirect:"true" //optional (TRUE by default) 45 | } 46 | })``` 47 | ```` 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module tlsapi 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/Carcraftz/cclient v0.0.0-20210907233050-29fc0b0a880a 7 | github.com/Carcraftz/fhttp v0.0.0-20210902023210-28df3f3a12b5 8 | github.com/Carcraftz/utls v0.0.0-20210907185630-32782f880d54 9 | github.com/andybalholm/brotli v1.0.3 10 | ) 11 | 12 | require ( 13 | github.com/dsnet/compress v0.0.1 // indirect 14 | github.com/fatih/color v1.13.0 // indirect 15 | github.com/mattn/go-colorable v0.1.9 // indirect 16 | github.com/mattn/go-isatty v0.0.14 // indirect 17 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect 18 | gitlab.com/yawning/utls.git v0.0.12-1 // indirect 19 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect 20 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b // indirect 21 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 22 | golang.org/x/text v0.3.6 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Carcraftz/cclient v0.0.0-20210907233050-29fc0b0a880a h1:QSXhbGGcuTeTM/9vpX4FvU258jNBLL1urkYk3CiCd4U= 2 | github.com/Carcraftz/cclient v0.0.0-20210907233050-29fc0b0a880a/go.mod h1:kpZ5+5p4q3CH/N4hNQ9KNskbODppFoggsY3LtWVkum8= 3 | github.com/Carcraftz/fhttp v0.0.0-20210902023210-28df3f3a12b5 h1:3PC+6kPd6PEgQwnTckBhPIXrzY7MD7hPkUdFU2Q7rEA= 4 | github.com/Carcraftz/fhttp v0.0.0-20210902023210-28df3f3a12b5/go.mod h1:13BabbJ/YWQFq8iGu8FVYQQbFuP9jsUyqGnDg9UuxHQ= 5 | github.com/Carcraftz/utls v0.0.0-20210907185630-32782f880d54 h1:V/84tXJdc3wi5/kBU4iVNCxUU+XC3+wSGKFf/tRG+k4= 6 | github.com/Carcraftz/utls v0.0.0-20210907185630-32782f880d54/go.mod h1:YMKKxFhs/MzFaQP80rFaWsO78e/pYjtjgrlCbu5Rpps= 7 | github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM= 8 | github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 9 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 10 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 11 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 12 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 13 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 14 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 15 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 16 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 17 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 18 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 19 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 20 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 21 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 22 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= 23 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= 24 | gitlab.com/yawning/utls.git v0.0.12-1 h1:RL6O0MP2YI0KghuEU/uGN6+8b4183eqNWoYgx7CXD0U= 25 | gitlab.com/yawning/utls.git v0.0.12-1/go.mod h1:3ONKiSFR9Im/c3t5RKmMJTVdmZN496FNyk3mjrY1dyo= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 28 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= 29 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 30 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 31 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 32 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 33 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= 34 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 35 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 44 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 46 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 48 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 49 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 52 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 53 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 54 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 55 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 56 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "compress/zlib" 5 | "flag" 6 | "fmt" 7 | "github.com/fatih/color" 8 | "io/ioutil" 9 | "log" 10 | "strconv" 11 | "time" 12 | 13 | "bytes" 14 | "compress/gzip" 15 | "net/url" 16 | "strings" 17 | 18 | "github.com/Carcraftz/cclient" 19 | "github.com/andybalholm/brotli" 20 | 21 | http "github.com/Carcraftz/fhttp" 22 | 23 | tls "github.com/Carcraftz/utls" 24 | ) 25 | 26 | //var client http.Client 27 | 28 | func main() { 29 | port := flag.String("port", "8082", "A port number (default 8082)") 30 | flag.Parse() 31 | fmt.Println("Hosting a TLS API on port " + *port) 32 | fmt.Println("If you like this API, all donations are appreciated! https://paypal.me/carcraftz") 33 | http.HandleFunc("/", handleReq) 34 | err := http.ListenAndServe(":"+string(*port), nil) 35 | if err != nil { 36 | log.Fatalln("Error starting the HTTP server:", err) 37 | } 38 | } 39 | 40 | func handleReq(w http.ResponseWriter, r *http.Request) { 41 | defer r.Body.Close() 42 | // Ensure page URL header is provided 43 | pageURL := r.Header.Get("Poptls-Url") 44 | if pageURL == "" { 45 | http.Error(w, "ERROR: No Page URL Provided", http.StatusBadRequest) 46 | return 47 | } 48 | // Remove header to ignore later 49 | r.Header.Del("Poptls-Url") 50 | 51 | // Ensure user agent header is provided 52 | userAgent := r.Header.Get("User-Agent") 53 | if userAgent == "" { 54 | http.Error(w, "ERROR: No User Agent Provided", http.StatusBadRequest) 55 | return 56 | } 57 | 58 | //Handle Proxy (http://host:port or http://user:pass@host:port) 59 | proxy := r.Header.Get("Poptls-Proxy") 60 | if proxy != "" { 61 | r.Header.Del("Poptls-Proxy") 62 | } 63 | //handle redirects and timeouts 64 | redirectVal := r.Header.Get("Poptls-Allowredirect") 65 | allowRedirect := true 66 | if redirectVal != "" { 67 | if redirectVal == "false" { 68 | allowRedirect = false 69 | } 70 | } 71 | if redirectVal != "" { 72 | r.Header.Del("Poptls-Allowredirect") 73 | } 74 | timeoutraw := r.Header.Get("Poptls-Timeout") 75 | timeout, err := strconv.Atoi(timeoutraw) 76 | if err != nil { 77 | //default timeout of 6 78 | timeout = 6 79 | } 80 | if timeout > 60 { 81 | http.Error(w, "ERROR: Timeout cannot be longer than 60 seconds", http.StatusBadRequest) 82 | return 83 | } 84 | // Change JA3 85 | var tlsClient tls.ClientHelloID 86 | if strings.Contains(strings.ToLower(userAgent), "chrome") { 87 | tlsClient = tls.HelloChrome_Auto 88 | } else if strings.Contains(strings.ToLower(userAgent), "firefox") { 89 | tlsClient = tls.HelloFirefox_Auto 90 | } else { 91 | tlsClient = tls.HelloIOS_Auto 92 | } 93 | client, err := cclient.NewClient(tlsClient, proxy, allowRedirect, time.Duration(timeout)) 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | 98 | // Forward query params 99 | var addedQuery string 100 | for k, v := range r.URL.Query() { 101 | addedQuery += "&" + k + "=" + v[0] 102 | } 103 | 104 | endpoint := pageURL 105 | if len(addedQuery) != 0 { 106 | endpoint = pageURL + "?" + addedQuery 107 | if strings.Contains(pageURL, "?") { 108 | endpoint = pageURL + addedQuery 109 | } else if addedQuery != "" { 110 | endpoint = pageURL + "?" + addedQuery[1:] 111 | } 112 | } 113 | req, err := http.NewRequest(r.Method, ""+endpoint, r.Body) 114 | if err != nil { 115 | panic(err) 116 | } 117 | //master header order, all your headers will be ordered based on this list and anything extra will be appended to the end 118 | //if your site has any custom headers, see the header order chrome uses and then add those headers to this list 119 | masterheaderorder := []string{ 120 | "host", 121 | "connection", 122 | "cache-control", 123 | "device-memory", 124 | "viewport-width", 125 | "rtt", 126 | "downlink", 127 | "ect", 128 | "sec-ch-ua", 129 | "sec-ch-ua-mobile", 130 | "sec-ch-ua-full-version", 131 | "sec-ch-ua-arch", 132 | "sec-ch-ua-platform", 133 | "sec-ch-ua-platform-version", 134 | "sec-ch-ua-model", 135 | "upgrade-insecure-requests", 136 | "user-agent", 137 | "accept", 138 | "sec-fetch-site", 139 | "sec-fetch-mode", 140 | "sec-fetch-user", 141 | "sec-fetch-dest", 142 | "referer", 143 | "accept-encoding", 144 | "accept-language", 145 | "cookie", 146 | } 147 | headermap := make(map[string]string) 148 | //TODO: REDUCE TIME COMPLEXITY (This code is very bad) 149 | headerorderkey := []string{} 150 | for _, key := range masterheaderorder { 151 | for k, v := range r.Header { 152 | lowercasekey := strings.ToLower(k) 153 | if key == lowercasekey { 154 | headermap[k] = v[0] 155 | headerorderkey = append(headerorderkey, lowercasekey) 156 | } 157 | } 158 | 159 | } 160 | for k, v := range req.Header { 161 | if _, ok := headermap[k]; !ok { 162 | headermap[k] = v[0] 163 | headerorderkey = append(headerorderkey, strings.ToLower(k)) 164 | } 165 | } 166 | 167 | //ordering the pseudo headers and our normal headers 168 | req.Header = http.Header{ 169 | http.HeaderOrderKey: headerorderkey, 170 | http.PHeaderOrderKey: {":method", ":authority", ":scheme", ":path"}, 171 | } 172 | //set our Host header 173 | u, err := url.Parse(endpoint) 174 | if err != nil { 175 | panic(err) 176 | } 177 | //append our normal headers 178 | for k := range r.Header { 179 | if k != "Content-Length" && !strings.Contains(k, "Poptls") { 180 | v := r.Header.Get(k) 181 | req.Header.Set(k, v) 182 | } 183 | } 184 | req.Header.Set("Host", u.Host) 185 | resp, err := client.Do(req) 186 | if err != nil { 187 | fmt.Printf("[%s][%s][%s]\r\n", color.YellowString("%s", time.Now().Format("2012-11-01T22:08:41+00:00")), color.BlueString("%s", pageURL), color.RedString("Connection Failed")) 188 | hj, ok := w.(http.Hijacker) 189 | if !ok { 190 | panic(err) 191 | } 192 | conn, _, err := hj.Hijack() 193 | if err != nil { 194 | panic(err) 195 | } 196 | if err := conn.Close(); err != nil { 197 | panic(err) 198 | } 199 | return 200 | } 201 | defer resp.Body.Close() 202 | 203 | //req.Close = true 204 | 205 | //forward response headers 206 | for k, v := range resp.Header { 207 | if k != "Content-Length" && k != "Content-Encoding" { 208 | for _, kv := range v { 209 | w.Header().Add(k, kv) 210 | } 211 | } 212 | } 213 | w.WriteHeader(resp.StatusCode) 214 | var status string 215 | if resp.StatusCode > 302 { 216 | status = color.RedString("%s", resp.Status) 217 | } else { 218 | status = color.GreenString("%s", resp.Status) 219 | } 220 | fmt.Printf("[%s][%s][%s]\r\n", color.YellowString("%s", time.Now().Format("2012-11-01T22:08:41+00:00")), color.BlueString("%s", pageURL), status) 221 | 222 | //forward decoded response body 223 | encoding := resp.Header["Content-Encoding"] 224 | body, err := ioutil.ReadAll(resp.Body) 225 | finalres := "" 226 | if err != nil { 227 | panic(err) 228 | } 229 | finalres = string(body) 230 | if len(encoding) > 0 { 231 | if encoding[0] == "gzip" { 232 | unz, err := gUnzipData(body) 233 | if err != nil { 234 | panic(err) 235 | } 236 | finalres = string(unz) 237 | } else if encoding[0] == "deflate" { 238 | unz, err := enflateData(body) 239 | if err != nil { 240 | panic(err) 241 | } 242 | finalres = string(unz) 243 | } else if encoding[0] == "br" { 244 | unz, err := unBrotliData(body) 245 | if err != nil { 246 | panic(err) 247 | } 248 | finalres = string(unz) 249 | } else { 250 | fmt.Println("UNKNOWN ENCODING: " + encoding[0]) 251 | finalres = string(body) 252 | } 253 | } else { 254 | finalres = string(body) 255 | } 256 | if _, err := fmt.Fprint(w, finalres); err != nil { 257 | log.Println("Error writing body:", err) 258 | } 259 | } 260 | 261 | func gUnzipData(data []byte) (resData []byte, err error) { 262 | gz, _ := gzip.NewReader(bytes.NewReader(data)) 263 | defer gz.Close() 264 | respBody, err := ioutil.ReadAll(gz) 265 | return respBody, err 266 | } 267 | func enflateData(data []byte) (resData []byte, err error) { 268 | zr, _ := zlib.NewReader(bytes.NewReader(data)) 269 | defer zr.Close() 270 | enflated, err := ioutil.ReadAll(zr) 271 | return enflated, err 272 | } 273 | func unBrotliData(data []byte) (resData []byte, err error) { 274 | br := brotli.NewReader(bytes.NewReader(data)) 275 | respBody, err := ioutil.ReadAll(br) 276 | return respBody, err 277 | } 278 | --------------------------------------------------------------------------------