2019-09-02 23:01:02 -05:00
|
|
|
// 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"
|
|
|
|
"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) {
|
2022-09-02 17:57:55 -05:00
|
|
|
conn, err := net.Dial("tcp", ipPort)
|
2019-09-02 23:01:02 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Println("err:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-02 17:57:55 -05:00
|
|
|
fcgi := client{rwc: conn, reqID: 1}
|
|
|
|
|
2019-09-02 23:01:02 -05:00
|
|
|
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()
|
2021-09-29 12:17:48 -05:00
|
|
|
content, _ = io.ReadAll(resp.Body)
|
2019-09-02 23:01:02 -05:00
|
|
|
|
|
|
|
log.Println("c: send data length ≈", length, string(content))
|
2022-09-02 17:57:55 -05:00
|
|
|
conn.Close()
|
2019-09-02 23:01:02 -05:00
|
|
|
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++ {
|
2020-08-20 15:04:10 -05:00
|
|
|
v0 := strings.Repeat(fmt.Sprint(i), 256)
|
2019-09-02 23:01:02 -05:00
|
|
|
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++ {
|
2020-08-20 15:04:10 -05:00
|
|
|
v0 := strings.Repeat(fmt.Sprint(i), 4096)
|
2019-09-02 23:01:02 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|