normalize.mx raw

   1  // Package normalize provides URL and message normalization for Nostr.
   2  package normalize
   3  
   4  import (
   5  	"bytes"
   6  	"errors"
   7  	"fmt"
   8  	"net/url"
   9  
  10  	"smesh.lol/pkg/nostr/ints"
  11  	"smesh.lol/pkg/lol/chk"
  12  	"smesh.lol/pkg/lol/log"
  13  )
  14  
  15  var (
  16  	hp    = bytes.HasPrefix
  17  	WS    = []byte("ws://")
  18  	WSS   = []byte("wss://")
  19  	HTTP  = []byte("http://")
  20  	HTTPS = []byte("https://")
  21  )
  22  
  23  // URL normalizes a relay URL.
  24  func URL(v []byte) []byte {
  25  	u := []byte{:len(v)}
  26  	copy(u, v)
  27  	if len(u) == 0 {
  28  		return nil
  29  	}
  30  	u = bytes.TrimSpace(u)
  31  	u = bytes.ToLower(u)
  32  	if bytes.Contains(u, []byte(":")) &&
  33  		!(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) {
  34  		split := bytes.Split(u, []byte(":"))
  35  		if len(split) != 2 {
  36  			log.D.F([]byte("Error: more than one ':' in URL: '%s'"), u)
  37  			return nil
  38  		}
  39  		p := ints.New(0)
  40  		_, err := p.Unmarshal(split[1])
  41  		if chk.E(err) {
  42  			log.D.F([]byte("Error normalizing URL '%s': %s"), u, err)
  43  			return nil
  44  		}
  45  		if p.Uint64() > 65535 {
  46  			log.D.F([]byte("Port on address %d: greater than maximum 65535"), p.Uint64())
  47  			return nil
  48  		}
  49  		if p.Uint16() == 443 {
  50  			u = append(WSS, split[0]...)
  51  		} else {
  52  			u = append(WS, u...)
  53  		}
  54  	}
  55  	if !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) {
  56  		u = append(WSS, u...)
  57  	}
  58  	var err error
  59  	var p *url.URL
  60  	if p, err = url.Parse(string(u)); chk.E(err) {
  61  		return nil
  62  	}
  63  	switch p.Scheme {
  64  	case "https":
  65  		p.Scheme = "wss"
  66  	case "http":
  67  		p.Scheme = "ws"
  68  	}
  69  	p.Path = string(bytes.TrimRight([]byte(p.Path), "/"))
  70  	return []byte(p.String())
  71  }
  72  
  73  type Reason []byte
  74  
  75  var (
  76  	AuthRequired = Reason("auth-required")
  77  	PoW          = Reason("pow")
  78  	Duplicate    = Reason("duplicate")
  79  	Blocked      = Reason("blocked")
  80  	RateLimited  = Reason("rate-limited")
  81  	Invalid      = Reason("invalid")
  82  	Error        = Reason("error")
  83  	Unsupported  = Reason("unsupported")
  84  	Restricted   = Reason("restricted")
  85  )
  86  
  87  func (r Reason) S() string   { return string(r) }
  88  func (r Reason) B() []byte   { return r }
  89  
  90  func (r Reason) IsPrefix(reason []byte) bool {
  91  	return bytes.HasPrefix(reason, r.B())
  92  }
  93  
  94  func Msg(prefix Reason, format string, params ...any) []byte {
  95  	if len(prefix) < 1 {
  96  		prefix = Error
  97  	}
  98  	return append([]byte(nil), fmt.Sprintf(prefix.S()+": "+format, params...)...)
  99  }
 100  
 101  func (r Reason) F(format string, params ...any) []byte {
 102  	return Msg(r, format, params...)
 103  }
 104  
 105  func (r Reason) Errorf(format string, params ...any) error {
 106  	return errors.New(string(append([]byte(nil), fmt.Sprintf(r.S()+": "+format, params...)...)))
 107  }
 108