diff --git a/internal/handler/fofa.go b/internal/handler/fofa.go index bea0494b..4bfa6156 100644 --- a/internal/handler/fofa.go +++ b/internal/handler/fofa.go @@ -767,7 +767,7 @@ func (h *FofaHandler) searchQuake(c *gin.Context, req fofaSearchRequest, apiKey body["include"] = fields } var apiResp struct { - Code int `json:"code"` + Code interface{} `json:"code"` Message string `json:"message"` TotalCount int `json:"total_count"` Data []map[string]interface{} `json:"data"` @@ -780,7 +780,7 @@ func (h *FofaHandler) searchQuake(c *gin.Context, req fofaSearchRequest, apiKey if !h.doJSONRequest(c, http.MethodPost, u.String(), apiKey, "X-QuakeToken", body, &apiResp, "Quake") { return } - if apiResp.Code != 0 { + if !isZeroSpaceSearchCode(apiResp.Code) { msg := strings.TrimSpace(apiResp.Message) if msg == "" { msg = "Quake 返回错误" @@ -801,6 +801,23 @@ func (h *FofaHandler) searchQuake(c *gin.Context, req fofaSearchRequest, apiKey }) } +func isZeroSpaceSearchCode(code interface{}) bool { + switch v := code.(type) { + case nil: + return false + case int: + return v == 0 + case int64: + return v == 0 + case float64: + return v == 0 + case string: + return strings.TrimSpace(v) == "0" + default: + return false + } +} + func (h *FofaHandler) searchShodan(c *gin.Context, req fofaSearchRequest, apiKey string) { baseURL := strings.TrimRight(h.resolveBaseURL("shodan"), "/") + "/shodan/host/search" u, err := url.Parse(baseURL) diff --git a/internal/handler/fofa_test.go b/internal/handler/fofa_test.go index 200ed461..4fa629c0 100644 --- a/internal/handler/fofa_test.go +++ b/internal/handler/fofa_test.go @@ -155,6 +155,46 @@ func TestShodanSearchReportsShortfallWhenTotalExceedsMatches(t *testing.T) { } } +func TestQuakeSearchHandlesStringErrorCode(t *testing.T) { + gin.SetMode(gin.TestMode) + t.Setenv("QUAKE_API_KEY", "") + + quakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get("X-QuakeToken"); got != "test-quake-key" { + t.Fatalf("Quake token = %q, want test-quake-key", got) + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"code":"q5000","message":"查询语法错误"}`)) + })) + defer quakeServer.Close() + + h := NewFofaHandler(&config.Config{ + Quake: config.SpaceSearchConfig{ + BaseURL: quakeServer.URL, + APIKey: "test-quake-key", + }, + }, zap.NewNop()) + + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + body := `{"provider":"quake","query":"bad query","fields":"ip,port","size":10,"page":1}` + ctx.Request = httptest.NewRequest(http.MethodPost, "/api/fofa/search", strings.NewReader(body)) + ctx.Request.Header.Set("Content-Type", "application/json") + + h.Search(ctx) + + if recorder.Code != http.StatusBadGateway { + t.Fatalf("Search() status = %d, want %d, body = %s", recorder.Code, http.StatusBadGateway, recorder.Body.String()) + } + bodyText := recorder.Body.String() + if !strings.Contains(bodyText, "查询语法错误") { + t.Fatalf("response should include Quake error message, got %s", bodyText) + } + if strings.Contains(bodyText, "cannot unmarshal") { + t.Fatalf("response exposed JSON type decoding failure: %s", bodyText) + } +} + func TestExtractInfoCollectJSONObject(t *testing.T) { t.Parallel() cases := []struct {