You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							301 lines
						
					
					
						
							7.5 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							301 lines
						
					
					
						
							7.5 KiB
						
					
					
				| // Copyright 2015 Matthew Holt and The Caddy Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| //     http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
|  | |
| // NOTE: These tests were adapted from the original | |
| // repository from which this package was forked. | |
| // The tests are slow (~10s) and in dire need of rewriting. | |
| // As such, the tests have been disabled to speed up | |
| // automated builds until they can be properly written. | |
|  | |
| package fastcgi | |
| 
 | |
| import ( | |
| 	"bytes" | |
| 	"crypto/md5" | |
| 	"encoding/binary" | |
| 	"fmt" | |
| 	"io" | |
| 	"io/ioutil" | |
| 	"log" | |
| 	"math/rand" | |
| 	"net" | |
| 	"net/http" | |
| 	"net/http/fcgi" | |
| 	"net/url" | |
| 	"os" | |
| 	"path/filepath" | |
| 	"strconv" | |
| 	"strings" | |
| 	"testing" | |
| 	"time" | |
| ) | |
| 
 | |
| // test fcgi protocol includes: | |
| // Get, Post, Post in multipart/form-data, and Post with files | |
| // each key should be the md5 of the value or the file uploaded | |
| // specify remote fcgi responder ip:port to test with php | |
| // test failed if the remote fcgi(script) failed md5 verification | |
| // and output "FAILED" in response | |
| const ( | |
| 	scriptFile = "/tank/www/fcgic_test.php" | |
| 	//ipPort = "remote-php-serv:59000" | |
| 	ipPort = "127.0.0.1:59000" | |
| ) | |
| 
 | |
| var globalt *testing.T | |
| 
 | |
| type FastCGIServer struct{} | |
| 
 | |
| func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { | |
| 
 | |
| 	if err := req.ParseMultipartForm(100000000); err != nil { | |
| 		log.Printf("[ERROR] failed to parse: %v", err) | |
| 	} | |
| 
 | |
| 	stat := "PASSED" | |
| 	fmt.Fprintln(resp, "-") | |
| 	fileNum := 0 | |
| 	{ | |
| 		length := 0 | |
| 		for k0, v0 := range req.Form { | |
| 			h := md5.New() | |
| 			_, _ = io.WriteString(h, v0[0]) | |
| 			_md5 := fmt.Sprintf("%x", h.Sum(nil)) | |
| 
 | |
| 			length += len(k0) | |
| 			length += len(v0[0]) | |
| 
 | |
| 			// echo error when key != _md5(val) | |
| 			if _md5 != k0 { | |
| 				fmt.Fprintln(resp, "server:err ", _md5, k0) | |
| 				stat = "FAILED" | |
| 			} | |
| 		} | |
| 		if req.MultipartForm != nil { | |
| 			fileNum = len(req.MultipartForm.File) | |
| 			for kn, fns := range req.MultipartForm.File { | |
| 				//fmt.Fprintln(resp, "server:filekey ", kn ) | |
| 				length += len(kn) | |
| 				for _, f := range fns { | |
| 					fd, err := f.Open() | |
| 					if err != nil { | |
| 						log.Println("server:", err) | |
| 						return | |
| 					} | |
| 					h := md5.New() | |
| 					l0, err := io.Copy(h, fd) | |
| 					if err != nil { | |
| 						log.Println(err) | |
| 						return | |
| 					} | |
| 					length += int(l0) | |
| 					defer fd.Close() | |
| 					md5 := fmt.Sprintf("%x", h.Sum(nil)) | |
| 					//fmt.Fprintln(resp, "server:filemd5 ", md5 ) | |
|  | |
| 					if kn != md5 { | |
| 						fmt.Fprintln(resp, "server:err ", md5, kn) | |
| 						stat = "FAILED" | |
| 					} | |
| 					//fmt.Fprintln(resp, "server:filename ", f.Filename ) | |
| 				} | |
| 			} | |
| 		} | |
| 
 | |
| 		fmt.Fprintln(resp, "server:got data length", length) | |
| 	} | |
| 	fmt.Fprintln(resp, "-"+stat+"-POST(", len(req.Form), ")-FILE(", fileNum, ")--") | |
| } | |
| 
 | |
| func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) { | |
| 	fcgi, err := Dial("tcp", ipPort) | |
| 	if err != nil { | |
| 		log.Println("err:", err) | |
| 		return | |
| 	} | |
| 
 | |
| 	length := 0 | |
| 
 | |
| 	var resp *http.Response | |
| 	switch reqType { | |
| 	case 0: | |
| 		if len(data) > 0 { | |
| 			length = len(data) | |
| 			rd := bytes.NewReader(data) | |
| 			resp, err = fcgi.Post(fcgiParams, "", "", rd, int64(rd.Len())) | |
| 		} else if len(posts) > 0 { | |
| 			values := url.Values{} | |
| 			for k, v := range posts { | |
| 				values.Set(k, v) | |
| 				length += len(k) + 2 + len(v) | |
| 			} | |
| 			resp, err = fcgi.PostForm(fcgiParams, values) | |
| 		} else { | |
| 			rd := bytes.NewReader(data) | |
| 			resp, err = fcgi.Get(fcgiParams, rd, int64(rd.Len())) | |
| 		} | |
| 
 | |
| 	default: | |
| 		values := url.Values{} | |
| 		for k, v := range posts { | |
| 			values.Set(k, v) | |
| 			length += len(k) + 2 + len(v) | |
| 		} | |
| 
 | |
| 		for k, v := range files { | |
| 			fi, _ := os.Lstat(v) | |
| 			length += len(k) + int(fi.Size()) | |
| 		} | |
| 		resp, err = fcgi.PostFile(fcgiParams, values, files) | |
| 	} | |
| 
 | |
| 	if err != nil { | |
| 		log.Println("err:", err) | |
| 		return | |
| 	} | |
| 
 | |
| 	defer resp.Body.Close() | |
| 	content, _ = ioutil.ReadAll(resp.Body) | |
| 
 | |
| 	log.Println("c: send data length ≈", length, string(content)) | |
| 	fcgi.Close() | |
| 	time.Sleep(1 * time.Second) | |
| 
 | |
| 	if bytes.Contains(content, []byte("FAILED")) { | |
| 		globalt.Error("Server return failed message") | |
| 	} | |
| 
 | |
| 	return | |
| } | |
| 
 | |
| func generateRandFile(size int) (p string, m string) { | |
| 
 | |
| 	p = filepath.Join(os.TempDir(), "fcgict"+strconv.Itoa(rand.Int())) | |
| 
 | |
| 	// open output file | |
| 	fo, err := os.Create(p) | |
| 	if err != nil { | |
| 		panic(err) | |
| 	} | |
| 	// close fo on exit and check for its returned error | |
| 	defer func() { | |
| 		if err := fo.Close(); err != nil { | |
| 			panic(err) | |
| 		} | |
| 	}() | |
| 
 | |
| 	h := md5.New() | |
| 	for i := 0; i < size/16; i++ { | |
| 		buf := make([]byte, 16) | |
| 		binary.PutVarint(buf, rand.Int63()) | |
| 		if _, err := fo.Write(buf); err != nil { | |
| 			log.Printf("[ERROR] failed to write buffer: %v\n", err) | |
| 		} | |
| 		if _, err := h.Write(buf); err != nil { | |
| 			log.Printf("[ERROR] failed to write buffer: %v\n", err) | |
| 		} | |
| 	} | |
| 	m = fmt.Sprintf("%x", h.Sum(nil)) | |
| 	return | |
| } | |
| 
 | |
| func DisabledTest(t *testing.T) { | |
| 	// TODO: test chunked reader | |
| 	globalt = t | |
| 
 | |
| 	rand.Seed(time.Now().UTC().UnixNano()) | |
| 
 | |
| 	// server | |
| 	go func() { | |
| 		listener, err := net.Listen("tcp", ipPort) | |
| 		if err != nil { | |
| 			log.Println("listener creation failed: ", err) | |
| 		} | |
| 
 | |
| 		srv := new(FastCGIServer) | |
| 		if err := fcgi.Serve(listener, srv); err != nil { | |
| 			log.Print("[ERROR] failed to start server: ", err) | |
| 		} | |
| 	}() | |
| 
 | |
| 	time.Sleep(1 * time.Second) | |
| 
 | |
| 	// init | |
| 	fcgiParams := make(map[string]string) | |
| 	fcgiParams["REQUEST_METHOD"] = "GET" | |
| 	fcgiParams["SERVER_PROTOCOL"] = "HTTP/1.1" | |
| 	//fcgi_params["GATEWAY_INTERFACE"] = "CGI/1.1" | |
| 	fcgiParams["SCRIPT_FILENAME"] = scriptFile | |
| 
 | |
| 	// simple GET | |
| 	log.Println("test:", "get") | |
| 	sendFcgi(0, fcgiParams, nil, nil, nil) | |
| 
 | |
| 	// simple post data | |
| 	log.Println("test:", "post") | |
| 	sendFcgi(0, fcgiParams, []byte("c4ca4238a0b923820dcc509a6f75849b=1&7b8b965ad4bca0e41ab51de7b31363a1=n"), nil, nil) | |
| 
 | |
| 	log.Println("test:", "post data (more than 60KB)") | |
| 	data := "" | |
| 	for i := 0x00; i < 0xff; i++ { | |
| 		v0 := strings.Repeat(string(i), 256) | |
| 		h := md5.New() | |
| 		_, _ = io.WriteString(h, v0) | |
| 		k0 := fmt.Sprintf("%x", h.Sum(nil)) | |
| 		data += k0 + "=" + url.QueryEscape(v0) + "&" | |
| 	} | |
| 	sendFcgi(0, fcgiParams, []byte(data), nil, nil) | |
| 
 | |
| 	log.Println("test:", "post form (use url.Values)") | |
| 	p0 := make(map[string]string, 1) | |
| 	p0["c4ca4238a0b923820dcc509a6f75849b"] = "1" | |
| 	p0["7b8b965ad4bca0e41ab51de7b31363a1"] = "n" | |
| 	sendFcgi(1, fcgiParams, nil, p0, nil) | |
| 
 | |
| 	log.Println("test:", "post forms (256 keys, more than 1MB)") | |
| 	p1 := make(map[string]string, 1) | |
| 	for i := 0x00; i < 0xff; i++ { | |
| 		v0 := strings.Repeat(string(i), 4096) | |
| 		h := md5.New() | |
| 		_, _ = io.WriteString(h, v0) | |
| 		k0 := fmt.Sprintf("%x", h.Sum(nil)) | |
| 		p1[k0] = v0 | |
| 	} | |
| 	sendFcgi(1, fcgiParams, nil, p1, nil) | |
| 
 | |
| 	log.Println("test:", "post file (1 file, 500KB)) ") | |
| 	f0 := make(map[string]string, 1) | |
| 	path0, m0 := generateRandFile(500000) | |
| 	f0[m0] = path0 | |
| 	sendFcgi(1, fcgiParams, nil, p1, f0) | |
| 
 | |
| 	log.Println("test:", "post multiple files (2 files, 5M each) and forms (256 keys, more than 1MB data") | |
| 	path1, m1 := generateRandFile(5000000) | |
| 	f0[m1] = path1 | |
| 	sendFcgi(1, fcgiParams, nil, p1, f0) | |
| 
 | |
| 	log.Println("test:", "post only files (2 files, 5M each)") | |
| 	sendFcgi(1, fcgiParams, nil, nil, f0) | |
| 
 | |
| 	log.Println("test:", "post only 1 file") | |
| 	delete(f0, "m0") | |
| 	sendFcgi(1, fcgiParams, nil, nil, f0) | |
| 
 | |
| 	if err := os.Remove(path0); err != nil { | |
| 		log.Println("[ERROR] failed to remove path: ", err) | |
| 	} | |
| 	if err := os.Remove(path1); err != nil { | |
| 		log.Println("[ERROR] failed to remove path: ", err) | |
| 	} | |
| }
 | |
| 
 |