func queryServerIDRetriever(w http.ResponseWriter, ids []string) { s := make(chan map[string]string, len(ids)) db.ServerDB.GetHostsAndGameFromIDAPIQuery(s, ids) hostsgames := <-s if len(hostsgames) == 0 { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(models.GetDefaultServerList()); err != nil { writeJSONEncodeError(w, err) } return } serverlist, err := steam.Query(hostsgames) if err != nil { setNotFoundAndLog(w, err) if err := json.NewEncoder(w).Encode(models.GetDefaultServerList()); err != nil { writeJSONEncodeError(w, err) return } return } if err := json.NewEncoder(w).Encode(serverlist); err != nil { writeJSONEncodeError(w, err) logger.LogWebError(err) } }
// Query retrieves the server information for a given set of host to game pairs // and returns it in a format that is presented to the API. It takes a map consisting // of host(s) and their corresponding game names (i.e: k:127.0.0.1:27960, v:"QuakeLive") func Query(hostsgames map[string]string) (*models.APIServerList, error) { hg := make(map[string]filters.Game, len(hostsgames)) needsPlayers := make([]string, len(hostsgames)) needsRules := make([]string, len(hostsgames)) needsInfo := make([]string, len(hostsgames)) for host, game := range hostsgames { fg := filters.GetGameByName(game) hg[host] = fg if !fg.IgnoreRules { needsRules = append(needsRules, host) } if !fg.IgnorePlayers { needsPlayers = append(needsPlayers, host) } if !fg.IgnoreInfo { needsInfo = append(needsInfo, host) } } data := a2sData{ HostsGames: hg, Info: batchInfoQuery(needsInfo), Rules: batchRuleQuery(needsRules), Players: batchPlayerQuery(needsPlayers), } sl, err := buildServerList(data, true) if err != nil { return models.GetDefaultServerList(), logger.LogAppError(err) } return sl, nil }
func queryServerAddrs(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") if !config.Config.WebConfig.AllowDirectUserQueries { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, `{"error": {"code": 400,"message": "Direct server queries are disabled. Use the %s parameter."}}`, qsQueryServerIDs) return } addresses := getQStringValues(r.URL.Query(), qsQueryServerAddrs) logger.WriteDebug("addresses length: %d", len(addresses)) logger.WriteDebug("addresses are: %s", addresses) if addresses == nil { w.WriteHeader(http.StatusOK) logger.WriteDebug("queryServerAddr: Got empty address query. Ignoring.") writeJSONResponse(w, models.GetDefaultServerList()) return } var parsedaddresses []string for _, addr := range addresses { host, err := net.ResolveTCPAddr("tcp4", addr) if err != nil { continue } parsedaddresses = append(parsedaddresses, fmt.Sprintf("%s:%d", host.IP, host.Port)) } if len(parsedaddresses) == 0 { w.WriteHeader(http.StatusOK) logger.WriteDebug("queryServerAddr: No valid addresses for query. Ignoring.") writeJSONResponse(w, models.GetDefaultServerList()) return } if len(parsedaddresses) > config.Config.WebConfig.MaximumHostsPerAPIQuery { logger.WriteDebug("Maximum number of allowed API query hosts exceeded, truncating") parsedaddresses = parsedaddresses[:config.Config.WebConfig.MaximumHostsPerAPIQuery] } queryServerAddrRetriever(w, parsedaddresses) }
func queryServerAddrRetriever(w http.ResponseWriter, addresses []string) { serverlist, err := steam.DirectQuery(addresses) if err != nil { setNotFoundAndLog(w, err) if err := json.NewEncoder(w).Encode(models.GetDefaultServerList()); err != nil { writeJSONEncodeError(w, err) return } return } if err := json.NewEncoder(w).Encode(serverlist); err != nil { writeJSONEncodeError(w, err) } }
// DirectQuery allows a user to query any host even if it is not in the internal // server ID database. It is primarily intended for testing as it has two main // issues: 1) obvious security implications, 2) determining which game a user- // supplied host represents rests on potentially unreliable assumptions, which if // not true would cause games with incomplete support for all three A2S queries // (e.g. Reflex) to always fail. A production environment should use Query() instead. func DirectQuery(hosts []string) (*models.APIServerList, error) { hg := make(map[string]filters.Game, len(hosts)) // Try to account for the fact that we can't determine the game ahead of time // for user-specified direct host queries -- a number of assumptions: // (1) A2S_INFO for game/host, (2) extra data A2S_INFO flag & field w/ appid, //(3) game has been defined in game.go with the correct AppID and A2S ignore flags info := batchInfoQuery(hosts) needsRules := make([]string, len(hosts)) needsPlayers := make([]string, len(hosts)) for _, h := range hosts { logger.WriteDebug("direct query for %s. will try to figure out needed queries", h) if (info[h] != models.SteamServerInfo{}) { logger.WriteDebug("A2S_INFO not empty. got gameid: %d", info[h].ExtraData.GameID) fg := filters.GetGameByAppID(info[h].ExtraData.GameID) hg[h] = fg if !fg.IgnoreRules { logger.WriteDebug("based on game %s for %s, will need to get A2S_RULES", fg.Name, h) needsRules = append(needsRules, h) } if !fg.IgnorePlayers { logger.WriteDebug("based on game %s for %s, will need to get A2S_PLAYERS", fg.Name, h) needsPlayers = append(needsPlayers, h) } } else { logger.WriteDebug("A2S_INFO is nil. game will be unspecified; results may vary") hg[h] = filters.GameUnspecified } } data := a2sData{ HostsGames: hg, Info: info, Rules: batchRuleQuery(needsRules), Players: batchPlayerQuery(needsPlayers), } sl, err := buildServerList(data, true) if err != nil { return models.GetDefaultServerList(), logger.LogAppError(err) } return sl, nil }
func queryServerIDs(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") ids := getQStringValues(r.URL.Query(), qsQueryServerIDs) logger.WriteDebug("queryServerID: ids length: %d", len(ids)) logger.WriteDebug("queryServerID: ids are: %s", ids) if ids == nil { w.WriteHeader(http.StatusOK) logger.WriteDebug("queryServerID: Got empty query. Ignoring.") writeJSONResponse(w, models.GetDefaultServerList()) return } if len(ids) > config.Config.WebConfig.MaximumHostsPerAPIQuery { logger.WriteDebug("Maximum number of allowed API query hosts exceeded, truncating") ids = ids[:config.Config.WebConfig.MaximumHostsPerAPIQuery] } queryServerIDRetriever(w, ids) }
func getServers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") var asl *models.APIServerList if config.Config.DebugConfig.ServerDumpFileAsMasterList { asl = useDumpFileAsMasterList(constants.DumpFileFullPath( config.Config.DebugConfig.ServerDumpFilename)) } else { asl = models.MasterList } // Empty (i.e. during first retrieval/startup) if asl == nil { writeJSONResponse(w, models.GetDefaultServerList()) return } srvfilters := getSrvFilterFromQString(r.URL.Query(), getServersQueryStrings) logger.WriteDebug("server list will be filtered with: %v", srvfilters) list := filterServers(srvfilters, asl) writeJSONResponse(w, list) }
// filterServers takes the server filters and the last retrieved server list and // returns a new, filtered server list based on the matched filters. func filterServers(sqf []slQueryFilter, a *models.APIServerList) *models.APIServerList { if a == nil { return models.GetDefaultServerList() } filtered := make([]models.APIServer, len(a.Servers)) copy(filtered, a.Servers) for _, s := range sqf { filtered = findMatches(s, filtered) } if filtered == nil { // JSON empty array instead of null filtered = make([]models.APIServer, 0) } return &models.APIServerList{ RetrievedAt: a.RetrievedAt, RetrievedTimeStamp: a.RetrievedTimeStamp, Servers: filtered, ServerCount: len(filtered), FailedCount: 0, FailedServers: make([]string, 0), } }