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  	"git.smesh.lol/nostr/pkg/ints"
  11  	"git.smesh.lol/nostr/pkg/lol/chk"
  12  	"git.smesh.lol/nostr/pkg/lol/log"
  13  )
  14  
  15  var (
  16  	hp    func([]byte, []byte) bool
  17  	WS    []byte
  18  	WSS   []byte
  19  	HTTP  []byte
  20  	HTTPS []byte
  21  )
  22  
  23  // URL normalizes a relay URL.
  24  func URL(v []byte) (buf []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.Ln(fmt.Sprintf([]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.Ln(fmt.Sprintf([]byte("Error normalizing URL '%s': %s"), u, err))
  43  			return nil
  44  		}
  45  		if p.Uint64() > 65535 {
  46  			log.D.Ln(fmt.Sprintf([]byte("Port on address %d: greater than maximum 65535"), p.Uint64()))
  47  			return nil
  48  		}
  49  		if p.Uint16() == 443 {
  50  			u = WSS | split[0]
  51  		} else {
  52  			u = WS | u
  53  		}
  54  	}
  55  	if !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) {
  56  		u = WSS | u
  57  	}
  58  	var uerr error
  59  	var pu *url.URL
  60  	if pu, uerr = url.Parse(string(u)); chk.E(uerr) {
  61  		return nil
  62  	}
  63  	switch pu.Scheme {
  64  	case "https":
  65  		pu.Scheme = "wss"
  66  	case "http":
  67  		pu.Scheme = "ws"
  68  	}
  69  	pu.Path = string(bytes.TrimRight([]byte(pu.Path), "/"))
  70  	return []byte(pu.String())
  71  }
  72  
  73  // Reason is a NIP-01 machine-readable reason string. It is a bare []byte:
  74  // Moxie forbids named slice types, so the old methods are free functions
  75  // whose first parameter is the value.
  76  var (
  77  	AuthRequired []byte
  78  	PoW          []byte
  79  	Duplicate    []byte
  80  	Blocked      []byte
  81  	RateLimited  []byte
  82  	Invalid      []byte
  83  	Error        []byte
  84  	Unsupported  []byte
  85  	Restricted   []byte
  86  )
  87  
  88  func init() {
  89  	hp = bytes.HasPrefix
  90  	WS = []byte("ws://")
  91  	WSS = []byte("wss://")
  92  	HTTP = []byte("http://")
  93  	HTTPS = []byte("https://")
  94  	AuthRequired = []byte("auth-required")
  95  	PoW = []byte("pow")
  96  	Duplicate = []byte("duplicate")
  97  	Blocked = []byte("blocked")
  98  	RateLimited = []byte("rate-limited")
  99  	Invalid = []byte("invalid")
 100  	Error = []byte("error")
 101  	Unsupported = []byte("unsupported")
 102  	Restricted = []byte("restricted")
 103  }
 104  
 105  func ReasonS(r []byte) (s string) { return string(r) }
 106  func ReasonB(r []byte) (buf []byte) { return r }
 107  
 108  func ReasonIsPrefix(r []byte, reason []byte) (ok bool) {
 109  	return bytes.HasPrefix(reason, ReasonB(r))
 110  }
 111  
 112  func Msg(prefix []byte, format string, params ...fmt.Stringer) (buf []byte) {
 113  	if len(prefix) < 1 {
 114  		prefix = Error
 115  	}
 116  	return []byte(nil) | fmt.Sprintf(ReasonS(prefix)|": "|format, params...)
 117  }
 118  
 119  func ReasonF(r []byte, format string, params ...fmt.Stringer) (buf []byte) {
 120  	return Msg(r, format, params...)
 121  }
 122  
 123  func ReasonErrorf(r []byte, format string, params ...fmt.Stringer) (err error) {
 124  	return errors.New(string([]byte(nil) | fmt.Sprintf(ReasonS(r)|": "|format, params...)))
 125  }
 126