package storage import ( "encoding/json" "errors" "fmt" "io" "os" "github.com/qdm12/gluetun/internal/models" ) // readFromFile reads the servers from server.json. // It only reads servers that have the same version as the hardcoded servers version // to avoid JSON unmarshaling errors. func (s *Storage) readFromFile(filepath string, hardcoded models.AllServers) ( servers models.AllServers, err error) { file, err := os.Open(filepath) if os.IsNotExist(err) { return servers, nil } else if err != nil { return servers, err } b, err := io.ReadAll(file) if err != nil { return servers, err } if err := file.Close(); err != nil { return servers, err } return s.extractServersFromBytes(b, hardcoded) } var ( errDecodeVersions = errors.New("cannot decode versions") errDecodeServers = errors.New("cannot decode servers") errDecodeProvider = errors.New("cannot decode servers for provider") ) func (s *Storage) extractServersFromBytes(b []byte, hardcoded models.AllServers) ( //nolint:gocognit,gocyclo servers models.AllServers, err error) { var versions allVersions if err := json.Unmarshal(b, &versions); err != nil { return servers, fmt.Errorf("%w: %s", errDecodeVersions, err) } var rawMessages allJSONRawMessages if err := json.Unmarshal(b, &rawMessages); err != nil { return servers, fmt.Errorf("%w: %s", errDecodeServers, err) } // TODO simplify with generics in Go 1.18 if hardcoded.Cyberghost.Version != versions.Cyberghost.Version { s.logVersionDiff("Cyberghost", hardcoded.Cyberghost.Version, versions.Cyberghost.Version) } else { err = json.Unmarshal(rawMessages.Cyberghost, &servers.Cyberghost) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Cyberghost", err) } } if hardcoded.Expressvpn.Version != versions.Expressvpn.Version { s.logVersionDiff("Expressvpn", hardcoded.Expressvpn.Version, versions.Expressvpn.Version) } else { err = json.Unmarshal(rawMessages.Expressvpn, &servers.Expressvpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Expressvpn", err) } } if hardcoded.Fastestvpn.Version != versions.Fastestvpn.Version { s.logVersionDiff("Fastestvpn", hardcoded.Fastestvpn.Version, versions.Fastestvpn.Version) } else { err = json.Unmarshal(rawMessages.Fastestvpn, &servers.Fastestvpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Fastestvpn", err) } } if hardcoded.HideMyAss.Version != versions.HideMyAss.Version { s.logVersionDiff("HideMyAss", hardcoded.HideMyAss.Version, versions.HideMyAss.Version) } else { err = json.Unmarshal(rawMessages.HideMyAss, &servers.HideMyAss) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "HideMyAss", err) } } if hardcoded.Ipvanish.Version != versions.Ipvanish.Version { s.logVersionDiff("Ipvanish", hardcoded.Ipvanish.Version, versions.Ipvanish.Version) } else { err = json.Unmarshal(rawMessages.Ipvanish, &servers.Ipvanish) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Ipvanish", err) } } if hardcoded.Ivpn.Version != versions.Ivpn.Version { s.logVersionDiff("Ivpn", hardcoded.Ivpn.Version, versions.Ivpn.Version) } else { err = json.Unmarshal(rawMessages.Ivpn, &servers.Ivpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Ivpn", err) } } if hardcoded.Mullvad.Version != versions.Mullvad.Version { s.logVersionDiff("Mullvad", hardcoded.Mullvad.Version, versions.Mullvad.Version) } else { err = json.Unmarshal(rawMessages.Mullvad, &servers.Mullvad) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Mullvad", err) } } if hardcoded.Nordvpn.Version != versions.Nordvpn.Version { s.logVersionDiff("Nordvpn", hardcoded.Nordvpn.Version, versions.Nordvpn.Version) } else { err = json.Unmarshal(rawMessages.Nordvpn, &servers.Nordvpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Nordvpn", err) } } if hardcoded.Privado.Version != versions.Privado.Version { s.logVersionDiff("Privado", hardcoded.Privado.Version, versions.Privado.Version) } else { err = json.Unmarshal(rawMessages.Privado, &servers.Privado) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Privado", err) } } if hardcoded.Pia.Version != versions.Pia.Version { s.logVersionDiff("Pia", hardcoded.Pia.Version, versions.Pia.Version) } else { err = json.Unmarshal(rawMessages.Pia, &servers.Pia) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Pia", err) } } if hardcoded.Privatevpn.Version != versions.Privatevpn.Version { s.logVersionDiff("Privatevpn", hardcoded.Privatevpn.Version, versions.Privatevpn.Version) } else { err = json.Unmarshal(rawMessages.Privatevpn, &servers.Privatevpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Privatevpn", err) } } if hardcoded.Protonvpn.Version != versions.Protonvpn.Version { s.logVersionDiff("Protonvpn", hardcoded.Protonvpn.Version, versions.Protonvpn.Version) } else { err = json.Unmarshal(rawMessages.Protonvpn, &servers.Protonvpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Protonvpn", err) } } if hardcoded.Purevpn.Version != versions.Purevpn.Version { s.logVersionDiff("Purevpn", hardcoded.Purevpn.Version, versions.Purevpn.Version) } else { err = json.Unmarshal(rawMessages.Purevpn, &servers.Purevpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Purevpn", err) } } if hardcoded.Surfshark.Version != versions.Surfshark.Version { s.logVersionDiff("Surfshark", hardcoded.Surfshark.Version, versions.Surfshark.Version) } else { err = json.Unmarshal(rawMessages.Surfshark, &servers.Surfshark) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Surfshark", err) } } if hardcoded.Torguard.Version != versions.Torguard.Version { s.logVersionDiff("Torguard", hardcoded.Torguard.Version, versions.Torguard.Version) } else { err = json.Unmarshal(rawMessages.Torguard, &servers.Torguard) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Torguard", err) } } if hardcoded.VPNUnlimited.Version != versions.VPNUnlimited.Version { s.logVersionDiff("VPNUnlimited", hardcoded.VPNUnlimited.Version, versions.VPNUnlimited.Version) } else { err = json.Unmarshal(rawMessages.VPNUnlimited, &servers.VPNUnlimited) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "VPNUnlimited", err) } } if hardcoded.Vyprvpn.Version != versions.Vyprvpn.Version { s.logVersionDiff("Vyprvpn", hardcoded.Vyprvpn.Version, versions.Vyprvpn.Version) } else { err = json.Unmarshal(rawMessages.Vyprvpn, &servers.Vyprvpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Vyprvpn", err) } } if hardcoded.Wevpn.Version != versions.Wevpn.Version { s.logVersionDiff("Wevpn", hardcoded.Wevpn.Version, versions.Wevpn.Version) } else { err = json.Unmarshal(rawMessages.Wevpn, &servers.Wevpn) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Wevpn", err) } } if hardcoded.Windscribe.Version != versions.Windscribe.Version { s.logVersionDiff("Windscribe", hardcoded.Windscribe.Version, versions.Windscribe.Version) } else { err = json.Unmarshal(rawMessages.Windscribe, &servers.Windscribe) if err != nil { return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Windscribe", err) } } return servers, nil } // allVersions is a subset of models.AllServers structure used to track // versions to avoid unmarshaling errors. type allVersions struct { Version uint16 `json:"version"` // used for migration of the top level scheme Cyberghost serverVersion `json:"cyberghost"` Expressvpn serverVersion `json:"expressvpn"` Fastestvpn serverVersion `json:"fastestvpn"` HideMyAss serverVersion `json:"hidemyass"` Ipvanish serverVersion `json:"ipvanish"` Ivpn serverVersion `json:"ivpn"` Mullvad serverVersion `json:"mullvad"` Nordvpn serverVersion `json:"nordvpn"` Privado serverVersion `json:"privado"` Pia serverVersion `json:"pia"` Privatevpn serverVersion `json:"privatevpn"` Protonvpn serverVersion `json:"protonvpn"` Purevpn serverVersion `json:"purevpn"` Surfshark serverVersion `json:"surfshark"` Torguard serverVersion `json:"torguard"` VPNUnlimited serverVersion `json:"vpnunlimited"` Vyprvpn serverVersion `json:"vyprvpn"` Wevpn serverVersion `json:"wevpn"` Windscribe serverVersion `json:"windscribe"` } type serverVersion struct { Version uint16 `json:"version"` } // allJSONRawMessages is to delay decoding of each provider servers. type allJSONRawMessages struct { Version uint16 `json:"version"` // used for migration of the top level scheme Cyberghost json.RawMessage `json:"cyberghost"` Expressvpn json.RawMessage `json:"expressvpn"` Fastestvpn json.RawMessage `json:"fastestvpn"` HideMyAss json.RawMessage `json:"hidemyass"` Ipvanish json.RawMessage `json:"ipvanish"` Ivpn json.RawMessage `json:"ivpn"` Mullvad json.RawMessage `json:"mullvad"` Nordvpn json.RawMessage `json:"nordvpn"` Privado json.RawMessage `json:"privado"` Pia json.RawMessage `json:"pia"` Privatevpn json.RawMessage `json:"privatevpn"` Protonvpn json.RawMessage `json:"protonvpn"` Purevpn json.RawMessage `json:"purevpn"` Surfshark json.RawMessage `json:"surfshark"` Torguard json.RawMessage `json:"torguard"` VPNUnlimited json.RawMessage `json:"vpnunlimited"` Vyprvpn json.RawMessage `json:"vyprvpn"` Wevpn json.RawMessage `json:"wevpn"` Windscribe json.RawMessage `json:"windscribe"` }