├── go.mod ├── .gitignore ├── Makefile ├── go.sum ├── .github └── workflows │ ├── stale.yml │ └── build.yml ├── init.go ├── license.go ├── CONTRIBUTING.md ├── LICENSE ├── main.go ├── README.md ├── CODE_OF_CONDUCT.md ├── helper.go └── main_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Shivam010/bypass-cors 2 | 3 | go 1.12 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | cmd.txt 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Different IDE files & directories 17 | .idea/ 18 | .vscode/ 19 | .vs/ 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO = GO111MODULE=on go 2 | 3 | fmt: 4 | ${GO} fmt ./... 5 | 6 | vet: fmt 7 | ${GO} vet ./... 8 | 9 | clean: vet 10 | rm -rf ./bin 11 | ${GO} mod tidy 12 | 13 | build: clean 14 | ${GO} build -o ./bin/bypass-cors ./... 15 | 16 | test: build 17 | ${GO} test -v -cover ./... 18 | 19 | run: clean 20 | ${GO} run ./... -p 80 21 | 22 | .PHONY: run build clean vet fmt 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 2 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 3 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 4 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 6 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | stale: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/stale@v1 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | stale-pr-message: 'Due to no activity in past days, the PR is been marked as stale' 18 | days-before-close: 1 19 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | const ( 8 | shortHand = " (short hand)" 9 | 10 | defaultPort = "8080" 11 | usagePort = "PORT at which the server will run" 12 | ) 13 | 14 | var ( 15 | // PORT at which the server will run (default: 8080), 16 | // can be modified using flags: 17 | // `-port 80` or `-p 80` 18 | PORT string 19 | ) 20 | 21 | func init() { 22 | flag.StringVar(&PORT, "port", defaultPort, usagePort) 23 | flag.StringVar(&PORT, "p", defaultPort, usagePort+shortHand) 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.12 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.12 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Build and Test 20 | # `test` firsts triggers `build` which triggers `clean` 21 | # which in turns triggers `vet` and `fmt` as a series 22 | # of operations 23 | run: make test 24 | 25 | 26 | -------------------------------------------------------------------------------- /license.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | licenseKey = "license" 10 | licenseLen = len(licenseKey) 11 | licenseUrl = "https://github.com/Shivam010/bypass-cors/blob/master/LICENSE" 12 | ) 13 | 14 | func License(h http.Handler) http.Handler { 15 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | w.Header().Add(licenseKey, licenseUrl) 17 | r.Header.Add(licenseKey, licenseUrl) 18 | path := strings.ToLower(r.URL.Path) 19 | // redirect to license if URL path is "/license*" 20 | if len(path) > licenseLen && path[1:licenseLen+1] == licenseKey { 21 | http.Redirect(w, r, licenseUrl, http.StatusPermanentRedirect) 22 | return 23 | } 24 | h.ServeHTTP(w, r) 25 | w.Header().Add(licenseKey, licenseUrl) 26 | r.Header.Add(licenseKey, licenseUrl) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to this project and its packages, which are hosted in the name of [Shivam010](https://github.com/Shivam010) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | - Ensure any install or build dependencies are removed before the end of the layer when doing a build. 8 | - Update the README.md with details of changes, if required and possible, suggested for interface changes. 9 | - Check for the other Work in Progress PRs and try to incorporate all the changes and keep one steady branch for the dedicated changes. 10 | 11 | Most important of all follow all the Code of Conducts specified in [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) and [LICENSE](./LICENSE). 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Shivam Rathore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | type handler struct{} 13 | 14 | func (*handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 15 | defer fmt.Println() 16 | 17 | fmt.Printf("Proxy Request Over: %s - %s \n", r.Method, r.URL.String()) 18 | 19 | pfr := addHeaders(w, r) 20 | if pfr { // pre-flight request 21 | return 22 | } 23 | 24 | URL := getRequestURL(w, r) 25 | if URL == nil { // invalid URL 26 | return 27 | } 28 | 29 | // extract request body 30 | b, err := ioutil.ReadAll(r.Body) 31 | if err != nil { 32 | fmt.Println("Invalid Body:", err) 33 | Return(w, &Error{ 34 | Code: http.StatusPreconditionFailed, 35 | Message: err.Error(), 36 | Detail: map[string]interface{}{ 37 | "method": r.Method, 38 | "requestedURL": URL.String(), 39 | }, 40 | }) 41 | return 42 | } 43 | 44 | // create proxy request 45 | req, err := http.NewRequest(r.Method, URL.String(), bytes.NewReader(b)) 46 | if err != nil { 47 | fmt.Println("Request cannot be created:", err) 48 | Return(w, &Error{ 49 | Code: http.StatusPreconditionFailed, 50 | Message: err.Error(), 51 | Detail: map[string]interface{}{ 52 | "body": b, 53 | "method": r.Method, 54 | "requestedURL": URL.String(), 55 | }, 56 | }) 57 | return 58 | } 59 | 60 | fmt.Println("UserClient --> bypass-cors -->", req.URL.Host) 61 | 62 | // Populate the rest of the header 63 | for k, v := range r.Header { 64 | req.Header.Add(k, v[0]) 65 | } 66 | 67 | res, err := http.DefaultClient.Do(req) 68 | if err != nil { 69 | fmt.Println("Request Failed:", err) 70 | Return(w, &Error{ 71 | Code: http.StatusUnprocessableEntity, 72 | Message: err.Error(), 73 | Detail: map[string]interface{}{ 74 | "body": b, 75 | "method": r.Method, 76 | "requestedURL": URL.String(), 77 | "response": res, 78 | }, 79 | }) 80 | return 81 | } 82 | 83 | body, err := ioutil.ReadAll(res.Body) 84 | if err != nil { 85 | fmt.Println("Failed to read:", err) 86 | Return(w, &Error{ 87 | Code: res.StatusCode, 88 | Message: err.Error(), 89 | Detail: map[string]interface{}{ 90 | "method": r.Method, 91 | "requestedURL": URL.String(), 92 | "body": b, 93 | "response": res, 94 | "responseCode": res.StatusCode, 95 | }, 96 | }) 97 | return 98 | } 99 | 100 | Return(w, &ValuerStruct{res.StatusCode, string(body)}) 101 | } 102 | 103 | func main() { 104 | 105 | // parse all flags set in `init` 106 | flag.Parse() 107 | 108 | fmt.Printf("\nStarting Proxy ByPass-Cors Server at port(:%v)...\n\n", PORT) 109 | 110 | if err := http.ListenAndServe(":"+PORT, License(&handler{})); err != nil { 111 | log.Println("\n\nPanics", err) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Note: This repository and project is for educational and testing purposes only, any illegal or bad use of project or its hosted service `https://non-cors.herokuapp.com` is solely the responsibility of the users using it. Me or the contributors has nothing to do with that. 2 | 3 | > Also, the hosted service https://non-cors.herokuapp.com has been disabled for now, for safety. Sorry for any inconvenience. 4 | 5 | # bypass-cors 6 | >a proxy server to bypass CORS (Cross-Origin Resource Sharing) enabled servers 7 | 8 | Proxy your requests just by prefixing your request URL with: `https://non-cors.herokuapp.com/` with all the headers and payload as it is. 9 | 10 | ### _CORS_ ? 11 | Browsers enforce same origin policy (CORS) to protect the users from XSS (Cross Site Scripting) among several other types of attacks. 12 | 13 | In short, browsers blocks all the requests which are not originated from the same origin as that of the web-page. 14 | 15 | But this feature banes the developer from accessing data from different servers. Luckily, there's a way out of this, and that's what `bypass-cors` uses. 16 | 17 | ### How does `bypass-cors` proxy _CORS_ ? 18 | Whenever we make a cross origin request, browsers set a request header called `Origin`, to give the information about the requester, and uses the `CORS policy` to block the non-origin requests. 19 | And checks the Response Header for `Access-Control` header values according to `CORS policy`. 20 | 21 | Hence, if we can change the response headers in our favor than we can easily tricks the browser, and this is what the `bypass-cors proxy server` does. 22 | 23 | It acts as a middleware and make request to the destined server, obtain response, and adds/sets some relevant headers with the response and sends it back to you, tricking the browser. 24 | 25 | ### Examples 26 | Proxy your requests just by prefixing your request URL with: `https://non-cors.herokuapp.com/` with all the headers and payload as it is. 27 | **bypass-cors** supports all types of `http.Method` requests. 28 | 29 | > [`https://non-cors.herokuapp.com/`](https://non-cors.herokuapp.com/) + Request Header & Body (if any) 30 | 31 | ```http request 32 | GET https://joke-api-strict-cors.appspot.com/jokes/ten ==> GET https://non-cors.herokuapp.com/https://joke-api-strict-cors.appspot.com/jokes/ten 33 | 34 | ``` 35 | ### Request for Contribution 36 | Changes and improvements are more than welcome! 37 | 38 | Feel free to fork, create issues or pull a request [here.](https://github.com/Shivam010/bypass-cors/issues) 39 | 40 | Please make your changes in a specific branch and request to pull into master! If you can please make sure that the changes work properly and does not affect the functioning of the website before sending a Pull Request, as that will help speed up the process. 41 | 42 | ### License 43 | The application, its design and its code all are licensed under the [MIT license.](https://github.com/Shivam010/bypass-cors/blob/master/LICENSE) 44 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at shivam.rathore010@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | // Valuer provides an interface for Return method to write response 12 | type Valuer interface { 13 | // StatusCode must returns a HTTP Status Code for the response 14 | StatusCode() int 15 | // Value must returns the value of the object to write response 16 | Value() interface{} 17 | } 18 | 19 | // Return returns and adds the corresponding Valuer to the ResponseWriter 20 | func Return(w http.ResponseWriter, res Valuer) { 21 | fmt.Printf("Served with: %d-%v \n", res.StatusCode(), http.StatusText(res.StatusCode())) 22 | 23 | //w.Header().Set("Content-Type", "application/json; charset=utf-8") 24 | w.WriteHeader(res.StatusCode()) 25 | _, _ = fmt.Fprintln(w, res.Value()) 26 | } 27 | 28 | // ValuerStruct is a custom wrapper for any struct to implement Valuer 29 | type ValuerStruct struct { 30 | Code int 31 | Resp interface{} 32 | } 33 | 34 | func (w *ValuerStruct) StatusCode() int { 35 | return w.Code 36 | } 37 | 38 | func (w *ValuerStruct) Value() interface{} { 39 | return w.Resp 40 | } 41 | 42 | // Error is a custom error object that also provides relevant information and 43 | // implement Valuer interface 44 | type Error struct { 45 | Code int 46 | Message string 47 | Detail map[string]interface{} 48 | } 49 | 50 | func (e *Error) Error() string { 51 | s, _ := json.Marshal(map[string]interface{}{"error": e}) 52 | return string(s) 53 | } 54 | 55 | func (e *Error) Value() interface{} { 56 | return e.Error() 57 | } 58 | 59 | func (e *Error) StatusCode() int { 60 | return e.Code 61 | } 62 | 63 | const ( 64 | // headers 65 | VaryHeader = "Vary" 66 | OriginHeader = "Origin" 67 | QuoteHeader = "quote" 68 | // Access Control headers 69 | AllowOrigin = "Access-Control-Allow-Origin" 70 | AllowMethods = "Access-Control-Allow-Methods" 71 | AllowHeaders = "Access-Control-Allow-Headers" 72 | AllowCredentials = "Access-Control-Allow-Credentials" 73 | // Access control request headers 74 | RequestMethod = "Access-Control-Request-Method" 75 | RequestHeaders = "Access-Control-Request-Headers" 76 | ) 77 | 78 | // defaultHeaders handles a general request and add/set corresponding headers 79 | func defaultHeaders(w http.ResponseWriter, r *http.Request) { 80 | headers := w.Header() 81 | origin := r.Header.Get(OriginHeader) 82 | 83 | // Adding Vary header - for http cache 84 | headers.Add(VaryHeader, OriginHeader) 85 | 86 | // quote 87 | headers.Set(QuoteHeader, "Be Happy :)") 88 | 89 | // Allowing only the requester - can be set to "*" too 90 | headers.Set(AllowOrigin, origin) 91 | // Always allowing credentials - just for the sake of proxy request 92 | headers.Set(AllowCredentials, "true") 93 | } 94 | 95 | // headersForPreflight handles the pre-flight cors request and add/set the 96 | // corresponding headers 97 | func headersForPreflight(w http.ResponseWriter, r *http.Request) { 98 | headers := w.Header() 99 | reqMethod := r.Header.Get(RequestMethod) 100 | reqHeaders := r.Header.Get(RequestHeaders) 101 | 102 | // Vary header - for http cache 103 | headers.Add(VaryHeader, RequestMethod) 104 | headers.Add(VaryHeader, RequestHeaders) 105 | 106 | // Allowing the requested method 107 | headers.Set(AllowMethods, strings.ToUpper(reqMethod)) 108 | // Allowing the requested headers 109 | headers.Set(AllowHeaders, reqHeaders) 110 | } 111 | 112 | // addHeaders handles request and set headers accordingly. It returns true if 113 | // request is pre-flight with some Access-Control-Request-Method else false. 114 | func addHeaders(w http.ResponseWriter, r *http.Request) bool { 115 | 116 | defaultHeaders(w, r) 117 | 118 | if r.Method == http.MethodOptions && r.Header.Get(RequestMethod) != "" { 119 | headersForPreflight(w, r) 120 | Return(w, &ValuerStruct{Code: http.StatusOK}) 121 | return true 122 | } 123 | 124 | return false 125 | } 126 | 127 | // getRequestURL returns the requested URL to bypass-cors 128 | func getRequestURL(w http.ResponseWriter, r *http.Request) *url.URL { 129 | 130 | if r.URL.Path == "" || r.URL.Path == "/" { 131 | fmt.Println("Root Request") 132 | Return(w, &Error{ 133 | Code: http.StatusPreconditionFailed, 134 | Message: "URL not provided", 135 | Detail: map[string]interface{}{ 136 | "method": r.Method, 137 | "requestedURL": r.URL.String(), 138 | }, 139 | }) 140 | return nil 141 | } 142 | 143 | p := r.URL.Path[1:] 144 | if !strings.HasPrefix(p, "http") { 145 | p = "http://" + p 146 | } 147 | 148 | URL, err := url.ParseRequestURI(p) 149 | if err != nil { 150 | fmt.Println("Invalid Request:", err) 151 | Return(w, &Error{ 152 | Code: http.StatusPreconditionFailed, 153 | Message: err.Error(), 154 | Detail: map[string]interface{}{ 155 | "method": r.Method, 156 | "requestedURL": p, 157 | }, 158 | }) 159 | return nil 160 | } 161 | 162 | return URL 163 | } 164 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "github.com/google/go-cmp/cmp" 8 | "net/http" 9 | "net/http/httptest" 10 | "os" 11 | "testing" 12 | ) 13 | 14 | // args - required arguments for any request to serve 15 | type args struct { 16 | w *httptest.ResponseRecorder 17 | r *http.Request 18 | // a dummy server 19 | srv *http.Server 20 | } 21 | 22 | // resChecker - response checker for checking test response 23 | type resChecker struct { 24 | code int 25 | body string 26 | // set noBodyCheck to true if do not want to check body 27 | noBodyCheck bool 28 | // list header keys which should be present in response 29 | headers []string 30 | } 31 | 32 | // defineTest - defines a single unit test 33 | type defineTest struct { 34 | name string 35 | args *args 36 | resChr *resChecker 37 | setup func(*args, *resChecker) 38 | } 39 | 40 | func changeStdOut(s string) *os.File { 41 | tmp := os.Stdout 42 | l, _ := os.Create("./bin/logs_" + s) 43 | os.Stdout = l 44 | return tmp 45 | } 46 | 47 | func resetStdOut(tmp *os.File) { 48 | os.Stdout = tmp 49 | } 50 | 51 | // NOTE: Test_RootRequest should be changed after the error for Root 52 | // request is successfully replaced with the documentation or landing 53 | // page. Follow Issue: Shivam010/bypass-cors#3 54 | func Test_RootRequest(t *testing.T) { 55 | tmp := changeStdOut(t.Name()) 56 | defer resetStdOut(tmp) 57 | test := defineTest{ 58 | name: "Root Request", 59 | args: &args{ 60 | w: httptest.NewRecorder(), 61 | r: nil, 62 | }, 63 | resChr: &resChecker{}, 64 | setup: func(ar *args, rc *resChecker) { 65 | ar.r, _ = http.NewRequest("GET", "/", &bytes.Buffer{}) 66 | rc.code = http.StatusPreconditionFailed 67 | rc.body = `{"error":{"Code":412,"Message":"URL not provided","Detail":{"method":"GET","requestedURL":"/"}}}` + "\n" 68 | }, 69 | } 70 | t.Run(test.name, func(t *testing.T) { 71 | ha := &handler{} 72 | test.setup(test.args, test.resChr) 73 | ha.ServeHTTP(test.args.w, test.args.r) 74 | if test.args.w.Code != test.resChr.code { 75 | t.Fatalf("Status code mismatched got: %v, want: %v", test.args.w.Code, test.resChr.code) 76 | } 77 | if !test.resChr.noBodyCheck && !cmp.Equal(test.args.w.Body.String(), test.resChr.body) { 78 | t.Fatalf("Body mismatched got: %s, want: %s", test.args.w.Body.String(), test.resChr.body) 79 | } 80 | }) 81 | } 82 | 83 | func Test_Success(t *testing.T) { 84 | tmp := changeStdOut(t.Name()) 85 | defer resetStdOut(tmp) 86 | tests := []defineTest{ 87 | { 88 | name: "GET-Request", 89 | args: &args{ 90 | w: httptest.NewRecorder(), 91 | r: nil, 92 | srv: &http.Server{Addr: ":8181", Handler: http.NotFoundHandler()}, 93 | }, 94 | resChr: &resChecker{ 95 | headers: []string{ 96 | VaryHeader, QuoteHeader, 97 | AllowOrigin, AllowCredentials, 98 | }, 99 | }, 100 | setup: func(ar *args, rc *resChecker) { 101 | ar.srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 102 | w.WriteHeader(http.StatusOK) 103 | _, _ = fmt.Fprintf(w, "Success") 104 | }) 105 | go ar.srv.ListenAndServe() 106 | ar.r, _ = http.NewRequest("GET", "/localhost"+ar.srv.Addr, &bytes.Buffer{}) 107 | rc.code = http.StatusOK 108 | rc.body = fmt.Sprintln("Success") 109 | }, 110 | }, 111 | { 112 | name: "OPTIONS-Request", 113 | args: &args{ 114 | w: httptest.NewRecorder(), 115 | r: nil, 116 | srv: &http.Server{Addr: ":8181", Handler: http.NotFoundHandler()}, 117 | }, 118 | resChr: &resChecker{ 119 | headers: []string{ 120 | VaryHeader, QuoteHeader, 121 | AllowOrigin, AllowCredentials, 122 | }, 123 | }, 124 | setup: func(ar *args, rc *resChecker) { 125 | ar.srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 126 | w.WriteHeader(http.StatusOK) 127 | _, _ = fmt.Fprintf(w, "Success") 128 | }) 129 | go ar.srv.ListenAndServe() 130 | ar.r, _ = http.NewRequest("OPTIONS", "/localhost"+ar.srv.Addr, &bytes.Buffer{}) 131 | rc.code = http.StatusOK 132 | rc.body = fmt.Sprintln("Success") 133 | }, 134 | }, 135 | } 136 | for _, tt := range tests { 137 | t.Run(tt.name, func(t *testing.T) { 138 | 139 | tt.setup(tt.args, tt.resChr) 140 | ctx, cancel := context.WithTimeout(context.Background(), 1000) 141 | defer cancel() 142 | defer tt.args.srv.Shutdown(ctx) 143 | 144 | ha := &handler{} 145 | ha.ServeHTTP(tt.args.w, tt.args.r) 146 | 147 | if tt.args.w.Code != tt.resChr.code { 148 | t.Fatalf("Status code mismatched got: %v, want: %v", tt.args.w.Code, tt.resChr.code) 149 | } 150 | if !tt.resChr.noBodyCheck && !cmp.Equal(tt.args.w.Body.String(), tt.resChr.body) { 151 | t.Fatalf("Body mismatched got: %s, want: %s", tt.args.w.Body.String(), tt.resChr.body) 152 | } 153 | }) 154 | } 155 | } 156 | 157 | func Test_OtherRequests(t *testing.T) { 158 | tmp := changeStdOut(t.Name()) 159 | defer resetStdOut(tmp) 160 | tests := []defineTest{ 161 | { 162 | name: "Can not Process", 163 | args: &args{ 164 | w: httptest.NewRecorder(), 165 | r: nil, 166 | }, 167 | resChr: &resChecker{}, 168 | setup: func(ar *args, rc *resChecker) { 169 | ar.r, _ = http.NewRequest("GET", "/invalid-request", &bytes.Buffer{}) 170 | rc.code = http.StatusUnprocessableEntity 171 | // Note: the error message `Get http://invalid-request: dial tcp: lookup invalid-request: no such host` 172 | // varies from environment to environment and hence, omitting the check 173 | rc.noBodyCheck = true 174 | //rc.body = `{"error":{"Code":422,"Message":"Get http://invalid-request: dial tcp: lookup invalid-request: no such host","Detail":{"body":"","method":"GET","requestedURL":"http://invalid-request","response":null}}}` + "\n" 175 | }, 176 | }, 177 | { 178 | name: "Invalid Request", 179 | args: &args{ 180 | w: httptest.NewRecorder(), 181 | r: nil, 182 | }, 183 | resChr: &resChecker{}, 184 | setup: func(ar *args, rc *resChecker) { 185 | ar.r, _ = http.NewRequest("GET", "", &bytes.Buffer{}) 186 | ar.r.URL.Path = "%invalid%" 187 | rc.code = http.StatusPreconditionFailed 188 | rc.body = `{"error":{"Code":412,"Message":"parse http://invalid%: invalid URL escape \"%\"","Detail":{"method":"GET","requestedURL":"http://invalid%"}}}` + "\n" 189 | }, 190 | }, 191 | { 192 | name: "URL not Provided", 193 | args: &args{ 194 | w: httptest.NewRecorder(), 195 | r: nil, 196 | }, 197 | resChr: &resChecker{}, 198 | setup: func(ar *args, rc *resChecker) { 199 | ar.r, _ = http.NewRequest("GET", "", &bytes.Buffer{}) 200 | rc.code = http.StatusPreconditionFailed 201 | rc.body = `{"error":{"Code":412,"Message":"URL not provided","Detail":{"method":"GET","requestedURL":""}}}` + "\n" 202 | }, 203 | }, 204 | { 205 | name: "Invalid Method", 206 | args: &args{ 207 | w: httptest.NewRecorder(), 208 | r: nil, 209 | }, 210 | resChr: &resChecker{}, 211 | setup: func(ar *args, rc *resChecker) { 212 | ar.r, _ = http.NewRequest("GET", "/localhost", &bytes.Buffer{}) 213 | ar.r.Method += "/" 214 | rc.code = http.StatusPreconditionFailed 215 | rc.body = `{"error":{"Code":412,"Message":"net/http: invalid method \"GET/\"","Detail":{"body":"","method":"GET/","requestedURL":"http://localhost"}}}` + "\n" 216 | }, 217 | }, 218 | } 219 | for _, tt := range tests { 220 | t.Run(tt.name, func(t *testing.T) { 221 | ha := &handler{} 222 | tt.setup(tt.args, tt.resChr) 223 | ha.ServeHTTP(tt.args.w, tt.args.r) 224 | if tt.args.w.Code != tt.resChr.code { 225 | t.Fatalf("Status code mismatched got: %v, want: %v", tt.args.w.Code, tt.resChr.code) 226 | } 227 | if !tt.resChr.noBodyCheck && !cmp.Equal(tt.args.w.Body.String(), tt.resChr.body) { 228 | t.Fatalf("Body mismatched got: %s, want: %s", tt.args.w.Body.String(), tt.resChr.body) 229 | } 230 | }) 231 | } 232 | } 233 | --------------------------------------------------------------------------------