You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
5.4 KiB
Go

package addon
import (
"errors"
"io/ioutil"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/lqqyt2423/go-mitmproxy/proxy"
log "github.com/sirupsen/logrus"
)
var httpsRegexp = regexp.MustCompile(`^https://`)
type Mapper struct {
proxy.BaseAddon
reqResMap map[string]*proxy.Response
}
func NewMapper(dirname string) *Mapper {
infos, err := ioutil.ReadDir(dirname)
if err != nil {
panic(err)
}
filenames := make([]string, 0)
for _, info := range infos {
if info.IsDir() {
continue
}
if !strings.HasSuffix(info.Name(), ".map.txt") {
continue
}
filenames = append(filenames, filepath.Join(dirname, info.Name()))
}
if len(filenames) == 0 {
return &Mapper{
reqResMap: make(map[string]*proxy.Response),
}
}
ch := make(chan interface{}, len(filenames))
for _, filename := range filenames {
go func(filename string, ch chan<- interface{}) {
f, err := parseFlowFromFile(filename)
if err != nil {
log.Error(err)
ch <- err
return
}
ch <- f
}(filename, ch)
}
reqResMap := make(map[string]*proxy.Response)
for i := 0; i < len(filenames); i++ {
flowOrErr := <-ch
if f, ok := flowOrErr.(*proxy.Flow); ok {
key := buildReqKey(f.Request)
log.Infof("add request mapper: %v", key)
reqResMap[key] = f.Response
}
}
return &Mapper{
reqResMap: reqResMap,
}
}
func parseFlowFromFile(filename string) (*proxy.Flow, error) {
p, err := newMapperParserFromFile(filename)
if err != nil {
return nil, err
}
return p.parse()
}
func (c *Mapper) Request(f *proxy.Flow) {
key := buildReqKey(f.Request)
if resp, ok := c.reqResMap[key]; ok {
f.Response = resp
}
}
func buildReqKey(req *proxy.Request) string {
url := req.URL.String()
url = httpsRegexp.ReplaceAllString(url, "http://")
key := req.Method + " " + url
return key
}
type mapperParser struct {
lines []string
url string
request *proxy.Request
response *proxy.Response
}
func newMapperParserFromFile(filename string) (*mapperParser, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return newMapperParserFromString(string(bytes))
}
func newMapperParserFromString(content string) (*mapperParser, error) {
content = strings.TrimSpace(content)
lines := strings.Split(content, "\n")
if len(lines) == 0 {
return nil, errors.New("no lines")
}
return &mapperParser{
lines: lines,
}, nil
}
func (p *mapperParser) parse() (*proxy.Flow, error) {
if err := p.parseRequest(); err != nil {
return nil, err
}
if err := p.parseResponse(); err != nil {
return nil, err
}
return &proxy.Flow{
Request: p.request,
Response: p.response,
}, nil
}
func (p *mapperParser) parseRequest() error {
if err := p.parseReqHead(); err != nil {
return err
}
if header, err := p.parseHeader(); err != nil {
return err
} else {
p.request.Header = header
}
// parse url
if !strings.HasPrefix(p.url, "http") {
host := p.request.Header.Get("host")
if host == "" {
return errors.New("no request host")
}
p.url = "http://" + host + p.url
}
url, err := url.Parse(p.url)
if err != nil {
return err
}
p.request.URL = url
p.parseReqBody()
return nil
}
func (p *mapperParser) parseReqHead() error {
line, _ := p.getLine()
re := regexp.MustCompile(`^(GET|POST|PUT|DELETE)\s+?(.+)`)
matches := re.FindStringSubmatch(line)
if len(matches) == 0 {
return errors.New("request head parse error")
}
p.request = &proxy.Request{
Method: matches[1],
}
p.url = matches[2]
return nil
}
func (p *mapperParser) parseHeader() (http.Header, error) {
header := make(http.Header)
re := regexp.MustCompile(`^([\w-]+?):\s*(.+)$`)
for {
line, ok := p.getLine()
if !ok {
break
}
line = strings.TrimSpace(line)
if line == "" {
break
}
matches := re.FindStringSubmatch(line)
if len(matches) == 0 {
return nil, errors.New("request header parse error")
}
key := matches[1]
val := matches[2]
header.Add(key, val)
}
return header, nil
}
func (p *mapperParser) parseReqBody() {
bodyLines := make([]string, 0)
for {
line, ok := p.getLine()
if !ok {
break
}
if len(bodyLines) == 0 {
line = strings.TrimSpace(line)
if line == "" {
continue
}
}
if strings.HasPrefix(line, "HTTP/1.1 ") {
p.lines = append([]string{line}, p.lines...)
break
}
bodyLines = append(bodyLines, line)
}
body := strings.Join(bodyLines, "\n")
body = strings.TrimSpace(body)
p.request.Body = []byte(body)
}
func (p *mapperParser) parseResponse() error {
if err := p.parseResHead(); err != nil {
return err
}
if header, err := p.parseHeader(); err != nil {
return err
} else {
p.response.Header = header
}
// all left content
body := strings.Join(p.lines, "\n")
body = strings.TrimSpace(body)
p.response.Body = []byte(body)
p.response.Header.Set("Content-Length", strconv.Itoa(len(p.response.Body)))
return nil
}
func (p *mapperParser) parseResHead() error {
line, ok := p.getLine()
if !ok {
return errors.New("response no head line")
}
re := regexp.MustCompile(`^HTTP/1\.1\s+?(\d+)`)
matches := re.FindStringSubmatch(line)
if len(matches) == 0 {
return errors.New("response head parse error")
}
code, _ := strconv.Atoi(matches[1])
p.response = &proxy.Response{
StatusCode: code,
}
return nil
}
func (p *mapperParser) getLine() (string, bool) {
if len(p.lines) == 0 {
return "", false
}
line := p.lines[0]
p.lines = p.lines[1:]
return line, true
}