mirror of
https://github.com/projectdiscovery/httpx
synced 2026-06-08 16:50:17 +00:00
2ea576f175
Add support for storing httpx scan results directly to databases with both CLI flags and YAML config file options. Supported databases: - MongoDB - PostgreSQL - MySQL Features: - Batched writes for performance (configurable batch size) - Auto-flush with configurable interval - Individual columns for each Result field (not JSON blob) - Support for environment variable HTTPX_DB_CONNECTION_STRING - Option to omit raw request/response data (-rdbor) New CLI flags under OUTPUT group: - -rdb, -result-db: enable database storage - -rdbc, -result-db-config: path to YAML config file - -rdbt, -result-db-type: database type (mongodb, postgres, mysql) - -rdbcs, -result-db-conn: connection string - -rdbn, -result-db-name: database name (default: httpx) - -rdbtb, -result-db-table: table/collection name (default: results) - -rdbbs, -result-db-batch-size: batch size (default: 100) - -rdbor, -result-db-omit-raw: omit raw data Closes #1973 Closes #2360 Closes #2361 Closes #2362
128 lines
2.8 KiB
Go
128 lines
2.8 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/projectdiscovery/httpx/runner"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
func init() {
|
|
Register(MongoDB, newMongoDatabase)
|
|
}
|
|
|
|
type mongoDatabase struct {
|
|
cfg *Config
|
|
client *mongo.Client
|
|
database *mongo.Database
|
|
collection *mongo.Collection
|
|
}
|
|
|
|
func newMongoDatabase(cfg *Config) (Database, error) {
|
|
return &mongoDatabase{cfg: cfg}, nil
|
|
}
|
|
|
|
func (m *mongoDatabase) Connect(ctx context.Context) error {
|
|
clientOpts := options.Client().
|
|
ApplyURI(m.cfg.ConnectionString).
|
|
SetConnectTimeout(10 * time.Second).
|
|
SetServerSelectionTimeout(10 * time.Second)
|
|
|
|
client, err := mongo.Connect(ctx, clientOpts)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to MongoDB: %w", err)
|
|
}
|
|
|
|
if err := client.Ping(ctx, nil); err != nil {
|
|
return fmt.Errorf("failed to ping MongoDB: %w", err)
|
|
}
|
|
|
|
m.client = client
|
|
m.database = client.Database(m.cfg.DatabaseName)
|
|
m.collection = m.database.Collection(m.cfg.TableName)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mongoDatabase) Close() error {
|
|
if m.client != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
return m.client.Disconnect(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *mongoDatabase) EnsureSchema(ctx context.Context) error {
|
|
indexes := []mongo.IndexModel{
|
|
{
|
|
Keys: bson.D{{Key: "timestamp", Value: -1}},
|
|
},
|
|
{
|
|
Keys: bson.D{{Key: "url", Value: 1}},
|
|
},
|
|
{
|
|
Keys: bson.D{{Key: "host", Value: 1}},
|
|
},
|
|
{
|
|
Keys: bson.D{{Key: "status_code", Value: 1}},
|
|
},
|
|
{
|
|
Keys: bson.D{{Key: "tech", Value: 1}},
|
|
Options: options.Index().SetSparse(true),
|
|
},
|
|
}
|
|
|
|
_, err := m.collection.Indexes().CreateMany(ctx, indexes)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create indexes: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mongoDatabase) InsertBatch(ctx context.Context, results []runner.Result) error {
|
|
if len(results) == 0 {
|
|
return nil
|
|
}
|
|
|
|
documents := make([]interface{}, len(results))
|
|
for i, r := range results {
|
|
doc, err := m.resultToDocument(r)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to convert result to document: %w", err)
|
|
}
|
|
documents[i] = doc
|
|
}
|
|
|
|
_, err := m.collection.InsertMany(ctx, documents)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert batch: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mongoDatabase) Type() DatabaseType {
|
|
return MongoDB
|
|
}
|
|
|
|
func (m *mongoDatabase) resultToDocument(r runner.Result) (bson.M, error) {
|
|
jsonBytes, err := json.Marshal(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal result to JSON: %w", err)
|
|
}
|
|
|
|
var doc bson.M
|
|
if err := json.Unmarshal(jsonBytes, &doc); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal JSON to BSON: %w", err)
|
|
}
|
|
|
|
return doc, nil
|
|
}
|