| 75 | } |
| 76 | |
| 77 | func validateWebpushEndpoint(rawEndpoint string) error { |
| 78 | endpoint, err := url.Parse(rawEndpoint) |
| 79 | if err != nil { |
| 80 | return xerrors.Errorf("parse endpoint URL: %w", err) |
| 81 | } |
| 82 | if !endpoint.IsAbs() { |
| 83 | return xerrors.New("endpoint must be an absolute URL") |
| 84 | } |
| 85 | if endpoint.Scheme != "https" { |
| 86 | return xerrors.New("endpoint URL scheme must be https") |
| 87 | } |
| 88 | if endpoint.Host == "" { |
| 89 | return xerrors.New("endpoint host is required") |
| 90 | } |
| 91 | if endpoint.User != nil { |
| 92 | return xerrors.New("endpoint URL must not include userinfo") |
| 93 | } |
| 94 | |
| 95 | hostname := strings.ToLower(endpoint.Hostname()) |
| 96 | if hostname == "" { |
| 97 | return xerrors.New("endpoint hostname is required") |
| 98 | } |
| 99 | if hostname == "localhost" || strings.HasSuffix(hostname, ".localhost") { |
| 100 | return xerrors.New("endpoint hostname must not be localhost") |
| 101 | } |
| 102 | |
| 103 | if ip, err := netip.ParseAddr(hostname); err == nil && |
| 104 | (ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || |
| 105 | ip.IsLinkLocalMulticast() || ip.IsMulticast() || |
| 106 | ip.IsUnspecified()) { |
| 107 | return xerrors.New("endpoint IP must not be private, loopback, link-local, multicast, or unspecified") |
| 108 | } |
| 109 | |
| 110 | return nil |
| 111 | } |
| 112 | |
| 113 | // @Summary Delete user webpush subscription |
| 114 | // @ID delete-user-webpush-subscription |