github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/gate/common/request.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package common
    20  
    21  import (
    22  	"fmt"
    23  	"net/http"
    24  	"net/url"
    25  	"regexp"
    26  )
    27  
    28  // HTTPRequest is a serializable version of http.Request ( with only usefull fields )
    29  type HTTPRequest struct {
    30  	Method        string
    31  	URL           string
    32  	Header        map[string][]string
    33  	ContentLength int64
    34  	WS            bool
    35  }
    36  
    37  // SerializeHTTPRequest create a new HTTPRequest from a http.Request
    38  func SerializeHTTPRequest(req *http.Request) (r *HTTPRequest) {
    39  	r = &HTTPRequest{
    40  		URL:           req.URL.String(),
    41  		Method:        req.Method,
    42  		Header:        req.Header,
    43  		ContentLength: req.ContentLength,
    44  	}
    45  	return
    46  }
    47  
    48  // UnserializeHTTPRequest create a new http.Request from a HTTPRequest
    49  func UnserializeHTTPRequest(req *HTTPRequest) (r *http.Request, err error) {
    50  	r = new(http.Request)
    51  	r.Method = req.Method
    52  	r.URL, err = url.Parse(req.URL)
    53  	if err != nil {
    54  		return
    55  	}
    56  	r.Header = req.Header
    57  	r.ContentLength = req.ContentLength
    58  	return
    59  }
    60  
    61  // Rule match HTTP requests to allow / deny access
    62  type Rule struct {
    63  	Method  string
    64  	URL     string
    65  	Headers map[string]string
    66  
    67  	methodRegex  *regexp.Regexp
    68  	urlRegex     *regexp.Regexp
    69  	headersRegex map[string]*regexp.Regexp
    70  }
    71  
    72  // NewRule creates a new Rule
    73  func NewRule(method string, url string, headers map[string]string) (rule *Rule, err error) {
    74  	rule = new(Rule)
    75  	rule.Method = method
    76  	rule.URL = url
    77  	if headers != nil {
    78  		rule.Headers = headers
    79  	} else {
    80  		rule.Headers = make(map[string]string)
    81  	}
    82  	err = rule.Compile()
    83  	return
    84  }
    85  
    86  // Compile the regular expressions
    87  func (rule *Rule) Compile() (err error) {
    88  	if rule.Method != "" {
    89  		rule.methodRegex, err = regexp.Compile(rule.Method)
    90  		if err != nil {
    91  			return
    92  		}
    93  	}
    94  	if rule.URL != "" {
    95  		rule.urlRegex, err = regexp.Compile(rule.URL)
    96  		if err != nil {
    97  			return
    98  		}
    99  	}
   100  	rule.headersRegex = make(map[string]*regexp.Regexp)
   101  	for header, regexStr := range rule.Headers {
   102  		var regex *regexp.Regexp
   103  		regex, err = regexp.Compile(regexStr)
   104  		if err != nil {
   105  			return
   106  		}
   107  		rule.headersRegex[header] = regex
   108  	}
   109  	return
   110  }
   111  
   112  // Match returns true if the http.Request matches the Rule
   113  func (rule *Rule) Match(req *http.Request) bool {
   114  	if rule.methodRegex != nil && !rule.methodRegex.MatchString(req.Method) {
   115  		return false
   116  	}
   117  	if rule.urlRegex != nil && !rule.urlRegex.MatchString(req.URL.String()) {
   118  		return false
   119  	}
   120  
   121  	for headerName, regex := range rule.headersRegex {
   122  		if !regex.MatchString(req.Header.Get(headerName)) {
   123  			return false
   124  		}
   125  	}
   126  
   127  	return true
   128  }
   129  
   130  func (rule *Rule) String() string {
   131  	return fmt.Sprintf("%s %s %v", rule.Method, rule.URL, rule.Headers)
   132  }