package api import ( "io/ioutil" "net/http" "os" "path" "reflect" "regexp" "strings" "time" "github.com/juju/errors" "github.com/rs/zerolog" "git.pyer.club/kingecg/goonvif/onvif" "git.pyer.club/kingecg/goonvif/onvif/gosoap" "git.pyer.club/kingecg/goonvif/onvif/networking" wsdiscovery "git.pyer.club/kingecg/goonvif/onvif/ws-discovery" "github.com/beevik/etree" "github.com/gin-gonic/gin" ) var ( // LoggerContext is the builder of a zerolog.Logger that is exposed to the application so that // options at the CLI might alter the formatting and the output of the logs. LoggerContext = zerolog. New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}). With().Timestamp() // Logger is a zerolog logger, that can be safely used from any part of the application. // It gathers the format and the output. Logger = LoggerContext.Logger() ) func RunApi() { router := gin.Default() router.POST("/:service/:method", func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") //c.Header("Access-Control-Allow-Headers", "access-control-allow-origin, access-control-allow-headers") serviceName := c.Param("service") methodName := c.Param("method") username := c.GetHeader("username") pass := c.GetHeader("password") xaddr := c.GetHeader("xaddr") acceptedData, err := c.GetRawData() if err != nil { Logger.Debug().Err(err).Msg("Failed to get rawx data") } message, err := callNecessaryMethod(serviceName, methodName, string(acceptedData), username, pass, xaddr) if err != nil { c.XML(http.StatusBadRequest, err.Error()) } else { c.String(http.StatusOK, message) } }) router.GET("/discovery", func(context *gin.Context) { context.Header("Access-Control-Allow-Origin", "*") context.Header("Access-Control-Allow-Headers", "access-control-allow-origin, access-control-allow-headers") interfaceName := context.GetHeader("interface") devices, err := wsdiscovery.SendProbe(interfaceName, nil, []string{"dn:NetworkVideoTransmitter"}, map[string]string{"dn": "http://www.onvif.org/ver10/network/wsdl"}) if err != nil { context.String(http.StatusInternalServerError, "error") } else { response := "[" for _, j := range devices { doc := etree.NewDocument() if err := doc.ReadFromString(j); err != nil { context.XML(http.StatusBadRequest, err.Error()) } else { endpoints := doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch/XAddrs") scopes := doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch/Scopes") flag := false for _, xaddr := range endpoints { xaddr := strings.Split(strings.Split(xaddr.Text(), " ")[0], "/")[2] if strings.Contains(response, xaddr) { flag = true break } response += "{" response += `"url":"` + xaddr + `",` } if flag { break } for _, scope := range scopes { re := regexp.MustCompile(`onvif:\/\/www\.onvif\.org\/name\/[A-Za-z0-9-]+`) match := re.FindStringSubmatch(scope.Text()) response += `"name":"` + path.Base(match[0]) + `"` } response += "}," } } response = strings.TrimRight(response, ",") response += "]" context.String(http.StatusOK, response) } }) router.Run() } func callNecessaryMethod(serviceName, methodName, acceptedData, username, password, xaddr string) (string, error) { var methodStruct interface{} var err error switch strings.ToLower(serviceName) { case "device": methodStruct, err = getDeviceStructByName(methodName) case "ptz": methodStruct, err = getPTZStructByName(methodName) case "media": methodStruct, err = getMediaStructByName(methodName) default: return "", errors.New("there is no such service") } if err != nil { //done return "", errors.Annotate(err, "getStructByName") } resp, err := xmlAnalize(methodStruct, &acceptedData) if err != nil { return "", errors.Annotate(err, "xmlAnalize") } endpoint, err := getEndpoint(serviceName, xaddr) if err != nil { return "", errors.Annotate(err, "getEndpoint") } soap := gosoap.NewEmptySOAP() soap.AddStringBodyContent(*resp) soap.AddRootNamespaces(onvif.Xlmns) soap.AddWSSecurity(username, password) servResp, err := networking.SendSoap(new(http.Client), endpoint, soap.String()) if err != nil { return "", errors.Annotate(err, "SendSoap") } rsp, err := ioutil.ReadAll(servResp.Body) if err != nil { return "", errors.Annotate(err, "ReadAll") } servResp.Body.Close() return string(rsp), nil } func getEndpoint(service, xaddr string) (string, error) { dev, err := onvif.NewDevice(onvif.DeviceParams{Xaddr: xaddr}) if err != nil { return "", errors.Annotate(err, "NewDevice") } pkg := strings.ToLower(service) var endpoint string switch pkg { case "device": endpoint = dev.GetEndpoint("Device") case "event": endpoint = dev.GetEndpoint("Event") case "imaging": endpoint = dev.GetEndpoint("Imaging") case "media": endpoint = dev.GetEndpoint("Media") case "ptz": endpoint = dev.GetEndpoint("PTZ") } return endpoint, nil } func xmlAnalize(methodStruct interface{}, acceptedData *string) (*string, error) { test := make([]map[string]string, 0) //tags testunMarshal := make([][]interface{}, 0) //data var mas []string //idnt soapHandling(methodStruct, &test) test = mapProcessing(test) doc := etree.NewDocument() if err := doc.ReadFromString(*acceptedData); err != nil { return nil, errors.Annotate(err, "readFromString") } etr := doc.FindElements("./*") xmlUnmarshal(etr, &testunMarshal, &mas) ident(&mas) document := etree.NewDocument() var el *etree.Element var idntIndex = 0 for lstIndex := 0; lstIndex < len(testunMarshal); { lst := (testunMarshal)[lstIndex] elemName, attr, value, err := xmlMaker(&lst, &test, lstIndex) if err != nil { return nil, errors.Annotate(err, "xmlMarker") } if mas[lstIndex] == "Push" && lstIndex == 0 { //done el = document.CreateElement(elemName) el.SetText(value) if len(attr) != 0 { for key, value := range attr { el.CreateAttr(key, value) } } } else if mas[idntIndex] == "Push" { pushTmp := etree.NewElement(elemName) pushTmp.SetText(value) if len(attr) != 0 { for key, value := range attr { pushTmp.CreateAttr(key, value) } } el.AddChild(pushTmp) el = pushTmp } else if mas[idntIndex] == "PushPop" { popTmp := etree.NewElement(elemName) popTmp.SetText(value) if len(attr) != 0 { for key, value := range attr { popTmp.CreateAttr(key, value) } } if el == nil { document.AddChild(popTmp) } else { el.AddChild(popTmp) } } else if mas[idntIndex] == "Pop" { el = el.Parent() lstIndex -= 1 } idntIndex += 1 lstIndex += 1 } resp, err := document.WriteToString() if err != nil { return nil, errors.Annotate(err, "writeToString") } return &resp, nil } func xmlMaker(lst *[]interface{}, tags *[]map[string]string, lstIndex int) (string, map[string]string, string, error) { var elemName, value string attr := make(map[string]string) for tgIndx, tg := range *tags { if tgIndx == lstIndex { for index, elem := range *lst { if reflect.TypeOf(elem).String() == "[]etree.Attr" { conversion := elem.([]etree.Attr) for _, i := range conversion { attr[i.Key] = i.Value } } else { conversion := elem.(string) if index == 0 && lstIndex == 0 { res, err := xmlProcessing(tg["XMLName"]) if err != nil { return "", nil, "", errors.Annotate(err, "xmlProcessing") } elemName = res } else if index == 0 { res, err := xmlProcessing(tg[conversion]) if err != nil { return "", nil, "", errors.Annotate(err, "xmlProcessing") } elemName = res } else { value = conversion } } } } } return elemName, attr, value, nil } func xmlProcessing(tg string) (string, error) { r, _ := regexp.Compile(`"(.*?)"`) str := r.FindStringSubmatch(tg) if len(str) == 0 { return "", errors.New("out of range") } attr := strings.Index(str[1], ",attr") omit := strings.Index(str[1], ",omitempty") attrOmit := strings.Index(str[1], ",attr,omitempty") omitAttr := strings.Index(str[1], ",omitempty,attr") if attr > -1 && attrOmit == -1 && omitAttr == -1 { return str[1][0:attr], nil } else if omit > -1 && attrOmit == -1 && omitAttr == -1 { return str[1][0:omit], nil } else if attr == -1 && omit == -1 { return str[1], nil } else if attrOmit > -1 { return str[1][0:attrOmit], nil } else { return str[1][0:omitAttr], nil } } func mapProcessing(mapVar []map[string]string) []map[string]string { for indx := 0; indx < len(mapVar); indx++ { element := mapVar[indx] for _, value := range element { if value == "" { mapVar = append(mapVar[:indx], mapVar[indx+1:]...) indx-- } if strings.Index(value, ",attr") != -1 { mapVar = append(mapVar[:indx], mapVar[indx+1:]...) indx-- } } } return mapVar } func soapHandling(tp interface{}, tags *[]map[string]string) { s := reflect.ValueOf(tp).Elem() typeOfT := s.Type() if s.Kind() != reflect.Struct { return } for i := 0; i < s.NumField(); i++ { f := s.Field(i) tmp, ok := typeOfT.FieldByName(typeOfT.Field(i).Name) if !ok { Logger.Debug().Str("field", typeOfT.Field(i).Name).Msg("reflection failed") } *tags = append(*tags, map[string]string{typeOfT.Field(i).Name: string(tmp.Tag)}) subStruct := reflect.New(reflect.TypeOf(f.Interface())) soapHandling(subStruct.Interface(), tags) } } func xmlUnmarshal(elems []*etree.Element, data *[][]interface{}, mas *[]string) { for _, elem := range elems { *data = append(*data, []interface{}{elem.Tag, elem.Attr, elem.Text()}) *mas = append(*mas, "Push") xmlUnmarshal(elem.FindElements("./*"), data, mas) *mas = append(*mas, "Pop") } } func ident(mas *[]string) { var buffer string for _, j := range *mas { buffer += j + " " } buffer = strings.Replace(buffer, "Push Pop ", "PushPop ", -1) buffer = strings.TrimSpace(buffer) *mas = strings.Split(buffer, " ") }