zerobounce-sdk 1.1.2 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -23,19 +23,128 @@ Import
23
23
  require 'zerobounce'
24
24
  ```
25
25
 
26
- Set a valid ZeroBounce API key.
26
+ ## Configuration
27
+
28
+ ### Setting API Key
29
+
30
+ #### Method 1: Using configure block (recommended)
27
31
  ```ruby
28
32
  Zerobounce.configure do |config|
29
33
  config.apikey = '<zerobounce-api-key>'
30
- ...
34
+ # Optional: Set custom API URLs
35
+ config.api_root_url = 'https://api.zerobounce.net/v2'
36
+ config.bulk_api_root_url = 'https://bulkapi.zerobounce.net/v2'
31
37
  end
32
38
  ```
33
- or
39
+
40
+ #### Method 1a: Using ApiUrls constants (convenience)
41
+ ```ruby
42
+ # Global API (default)
43
+ Zerobounce.configure do |config|
44
+ config.apikey = '<zerobounce-api-key>'
45
+ config.api_root_url = Zerobounce::ApiUrls::DEFAULT_URL
46
+ config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
47
+ end
48
+
49
+ # European API (convenience)
50
+ Zerobounce.configure do |config|
51
+ config.apikey = '<zerobounce-api-key>'
52
+ config.api_root_url = Zerobounce::ApiUrls::EU_URL
53
+ config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
54
+ end
55
+
56
+ # US API (convenience)
57
+ Zerobounce.configure do |config|
58
+ config.apikey = '<zerobounce-api-key>'
59
+ config.api_root_url = Zerobounce::ApiUrls::US_URL
60
+ config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
61
+ end
34
62
  ```
63
+
64
+ #### Method 2: Direct configuration
65
+ ```ruby
35
66
  Zerobounce.config.apikey = '<zerobounce-api-key>'
36
- ...
67
+ # Optional: Set custom API URLs
68
+ Zerobounce.config.api_root_url = 'https://api.zerobounce.net/v2'
69
+ Zerobounce.config.bulk_api_root_url = 'https://bulkapi.zerobounce.net/v2'
37
70
  ```
38
71
 
72
+ #### Method 3: Using environment variables
73
+ Create a `.env` file in your project root:
74
+ ```bash
75
+ ZEROBOUNCE_API_KEY=your_api_key_here
76
+ ZEROBOUNCE_API_URL=https://api.zerobounce.net/v2
77
+ ZEROBOUNCE_BULK_API_URL=https://bulkapi.zerobounce.net/v2
78
+ ```
79
+
80
+ The gem will automatically load these environment variables when initialized. No additional configuration needed in your code.
81
+
82
+ #### Method 4: System environment variables
83
+ Set environment variables in your system:
84
+ ```bash
85
+ export ZEROBOUNCE_API_KEY=your_api_key_here
86
+ export ZEROBOUNCE_API_URL=https://api.zerobounce.net/v2
87
+ export ZEROBOUNCE_BULK_API_URL=https://bulkapi.zerobounce.net/v2
88
+ ```
89
+
90
+
91
+ ### API URL Configuration Details
92
+
93
+ #### Default Behavior
94
+ If you don't specify `api_root_url` or `bulk_api_root_url`, the gem will use the following defaults:
95
+ - **Main API**: `https://api.zerobounce.net/v2`
96
+ - **Bulk API**: `https://bulkapi.zerobounce.net/v2`
97
+
98
+ These defaults are defined in the `Zerobounce::ApiUrls` class constants:
99
+ - `Zerobounce::ApiUrls::DEFAULT_URL` - Main API URL (Global)
100
+ - `Zerobounce::ApiUrls::EU_URL` - European API URL
101
+ - `Zerobounce::ApiUrls::US_URL` - US API URL
102
+ - `Zerobounce::ApiUrls::BULK_DEFAULT_URL` - Bulk API URL
103
+
104
+ #### Using ApiUrls Constants
105
+ You can use the predefined constants in your configuration. The EU and US URLs are provided as convenience options:
106
+
107
+ ```ruby
108
+ # Global API (default)
109
+ Zerobounce.configure do |config|
110
+ config.apikey = 'your-api-key'
111
+ config.api_root_url = Zerobounce::ApiUrls::DEFAULT_URL
112
+ config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
113
+ end
114
+
115
+ # European API
116
+ Zerobounce.configure do |config|
117
+ config.apikey = 'your-api-key'
118
+ config.api_root_url = Zerobounce::ApiUrls::EU_URL
119
+ config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
120
+ end
121
+
122
+ # US API
123
+ Zerobounce.configure do |config|
124
+ config.apikey = 'your-api-key'
125
+ config.api_root_url = Zerobounce::ApiUrls::US_URL
126
+ config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
127
+ end
128
+ ```
129
+
130
+ #### Custom API URLs
131
+ You can override the default URLs for custom deployments or testing:
132
+
133
+ ```ruby
134
+ Zerobounce.configure do |config|
135
+ config.apikey = 'your-api-key'
136
+ # Use custom URLs (e.g., for testing or custom deployments)
137
+ config.api_root_url = 'https://custom-api.example.com/v2'
138
+ config.bulk_api_root_url = 'https://custom-bulk-api.example.com/v2'
139
+ end
140
+ ```
141
+
142
+ #### Environment Variable Priority
143
+ The configuration follows this priority order (highest to lowest):
144
+ 1. Explicitly set in code (`config.api_root_url = '...'`)
145
+ 2. Environment variables (`ZEROBOUNCE_API_URL`, `ZEROBOUNCE_BULK_API_URL`)
146
+ 3. Default constants (`ApiUrls::DEFAULT_URL`, `ApiUrls::BULK_DEFAULT_URL`)
147
+
39
148
  Credits
40
149
  ```ruby
41
150
  Zerobounce.credits
@@ -80,10 +189,13 @@ Zerobounce.api_usage(Date.today, Date.today)
80
189
  "sub_status_mailbox_quota_exceeded"=>0,
81
190
  "sub_status_forcible_disconnect"=>0,
82
191
  "sub_status_failed_smtp_connection"=>0,
192
+ "sub_status_accept_all"=>0,
83
193
  "sub_status_mx_forward"=>0,
84
194
  "sub_status_alternate"=>0,
85
- "sub_status_blocked"=>0,
86
195
  "sub_status_allowed"=>0,
196
+ "sub_status_blocked"=>0,
197
+ "sub_status_gold"=>0,
198
+ "sub_status_role_based_accept_all"=>0,
87
199
  "start_date"=>"4/28/2023",
88
200
  "end_date"=>"4/28/2023"}
89
201
  ```
@@ -412,9 +524,10 @@ When `has_header_row: false` is provided to `scoring_file_send()` method, column
412
524
 
413
525
  ### Email Finder
414
526
 
415
- Guess Format
527
+ Guess Format by Domain
416
528
  ```ruby
417
- Zerobounce.guessformat("zerobounce.net")
529
+ # New keyword argument syntax (recommended)
530
+ Zerobounce.guessformat(domain: "zerobounce.net")
418
531
  =>
419
532
  {"email"=>"",
420
533
  "domain"=>"zerobounce.net",
@@ -451,21 +564,166 @@ Zerobounce.guessformat("zerobounce.net")
451
564
  {"format"=>"lastf", "confidence"=>"medium"},
452
565
  {"format"=>"l-first", "confidence"=>"low"},
453
566
  {"format"=>"l_first", "confidence"=>"low"}]}
454
- # Zerobounce.guessformat("zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")
567
+
568
+ # With names for better accuracy (new syntax)
569
+ Zerobounce.guessformat(domain: "zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")
570
+
571
+ # Backwards compatible syntax (still supported)
572
+ Zerobounce.guessformat("zerobounce.net")
573
+ Zerobounce.guessformat("zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")
574
+ ```
575
+
576
+ Guess Format by Company Name
577
+ ```ruby
578
+ # New keyword argument syntax (recommended)
579
+ Zerobounce.guessformat(company_name: "Zero Bounce")
580
+ =>
581
+ {"email"=>"",
582
+ "company_name"=>"Zero Bounce",
583
+ "format"=>"first.last",
584
+ "status"=>"",
585
+ "sub_status"=>"",
586
+ "confidence"=>"high",
587
+ "did_you_mean"=>"",
588
+ "failure_reason"=>"",
589
+ "other_domain_formats"=>[...]}
590
+
591
+ # With names for better accuracy (new syntax)
592
+ Zerobounce.guessformat(first_name: "John", last_name: "Doe", company_name: "Zero Bounce")
593
+ ```
594
+
595
+ Find Email Address
596
+ ```ruby
597
+ # Find email by domain
598
+ Zerobounce.find_email("John", domain: "zerobounce.net")
599
+ =>
600
+ {
601
+ "email": "john@zerobounce.net",
602
+ "email_confidence": "medium",
603
+ "domain": "zerobounce.net",
604
+ "company_name": "ZeroBounce",
605
+ "did_you_mean": "",
606
+ "failure_reason": ""
607
+ }
608
+
609
+ # Find email by company name
610
+ Zerobounce.find_email("John", company_name: "Zero Bounce")
611
+ =>
612
+ {
613
+ "email": "john@zerobounce.net",
614
+ "email_confidence": "medium",
615
+ "domain": "zerobounce.net",
616
+ "company_name": "ZeroBounce",
617
+ "did_you_mean": "",
618
+ "failure_reason": ""
619
+ }
620
+
621
+ # With additional name information for better accuracy
622
+ Zerobounce.find_email("John", domain: "zerobounce.net", middle_name: "Deere", last_name: "Doe")
623
+ ```
624
+
625
+ Find Domain Information
626
+ ```ruby
627
+ # Find domain format by domain
628
+ Zerobounce.find_domain(domain: "zerobounce.net")
629
+ =>
630
+ {
631
+ "domain": "zerobounce.net",
632
+ "company_name": "Hertza, LLC",
633
+ "format": "first.last",
634
+ "confidence": "high",
635
+ "did_you_mean": "",
636
+ "failure_reason": "",
637
+ "other_domain_formats": [
638
+ {"format": "first", "confidence": "high"},
639
+ {"format": "last.first", "confidence": "high"},
640
+ {"format": "lastfirst", "confidence": "high"},
641
+ {"format": "firstl", "confidence": "high"},
642
+ {"format": "lfirst", "confidence": "high"},
643
+ {"format": "firstlast", "confidence": "high"},
644
+ {"format": "last_middle_f", "confidence": "high"},
645
+ {"format": "last", "confidence": "high"},
646
+ {"format": "f.last", "confidence": "medium"},
647
+ {"format": "last-f", "confidence": "medium"},
648
+ {"format": "l.first", "confidence": "medium"},
649
+ {"format": "last_f", "confidence": "medium"},
650
+ {"format": "first.middle.last", "confidence": "medium"},
651
+ {"format": "first-last", "confidence": "medium"},
652
+ {"format": "last.f", "confidence": "medium"},
653
+ {"format": "last_first", "confidence": "medium"},
654
+ {"format": "f-last", "confidence": "medium"},
655
+ {"format": "first.l", "confidence": "medium"},
656
+ {"format": "first-l", "confidence": "medium"},
657
+ {"format": "first_l", "confidence": "medium"},
658
+ {"format": "first_last", "confidence": "medium"},
659
+ {"format": "f_last", "confidence": "medium"},
660
+ {"format": "last-first", "confidence": "medium"},
661
+ {"format": "flast", "confidence": "medium"},
662
+ {"format": "lastf", "confidence": "medium"},
663
+ {"format": "l_first", "confidence": "medium"},
664
+ {"format": "l-first", "confidence": "medium"},
665
+ {"format": "first-middle-last", "confidence": "low"},
666
+ {"format": "firstmlast", "confidence": "low"},
667
+ {"format": "last.middle.first", "confidence": "low"},
668
+ {"format": "last_middle_first", "confidence": "low"},
669
+ {"format": "first_middle_last", "confidence": "low"},
670
+ {"format": "last-middle-first", "confidence": "low"},
671
+ {"format": "first-m-last", "confidence": "low"},
672
+ {"format": "firstmiddlelast", "confidence": "low"},
673
+ {"format": "last.m.first", "confidence": "low"},
674
+ {"format": "lastmfirst", "confidence": "low"},
675
+ {"format": "lastmiddlefirst", "confidence": "low"},
676
+ {"format": "last_m_first", "confidence": "low"},
677
+ {"format": "first.m.last", "confidence": "low"},
678
+ {"format": "first_m_last", "confidence": "low"},
679
+ {"format": "last-m-first", "confidence": "low"}
680
+ ]
681
+ }
682
+
683
+ # Find domain format by company name
684
+ Zerobounce.find_domain(company_name: "Zero Bounce")
685
+ =>
686
+ {
687
+ "domain": "zerobounce.net",
688
+ "company_name": "Zero Bounce",
689
+ "format": "first.last",
690
+ "confidence": "high",
691
+ "did_you_mean": "",
692
+ "failure_reason": "",
693
+ "other_domain_formats": [...]
694
+ }
455
695
  ```
456
696
 
457
697
  ## Development
458
698
 
459
- After checking out the repo run tests
699
+ ### Local setup
700
+ ```bash
701
+ sudo apt install -y rbenv
702
+ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
703
+ rbenv install 3.2.1
704
+ rbenv global 3.2.1
705
+ rbenv rehash
706
+ ruby -v
707
+ gem install bundler -v "~>2.4.6"
708
+ bundle install
709
+ ```
710
+
711
+ ### Run tests with Docker
712
+ From the **parent repository root** (the folder that contains all SDKs and `docker-compose.yml`):
713
+
714
+ ```bash
715
+ docker compose build ruby
716
+ docker compose run --rm ruby
717
+ ```
718
+
719
+ ### Run tests (local)
460
720
  ```bash
461
- rspec --init # if needed
462
721
  bundle exec rspec
463
722
  ```
464
723
 
465
724
  You should see an output like this
466
725
  ```bash
467
- Run options: include {:focus=>true}
468
- running live tests
726
+ running tests
469
727
  .....................................................
470
728
 
471
729
  Finished in 6.81 seconds (files took 0.40587 seconds to load)
@@ -473,18 +731,20 @@ Finished in 6.81 seconds (files took 0.40587 seconds to load)
473
731
  ```
474
732
 
475
733
  ### Test parameters
476
- The tests use the following environment parameters:
477
- TEST {unit|live} influences whether mocked unit tests are run or the live server is used (credits may be used if you choose to do this)
478
- ZEROBOUNCE_API_KEY {<zerobounce-api-key-value>} this key is used to make requests to the live server; it is also used in mock tests as a valid key sample (any value will work for mock tests)
479
- INCORRECT_API_KEY {any non whitespace string value that is not a valid key} used for tests where the requests are meant to fail due to the API key value.
734
+ The tests use the following environment parameter:
735
+ - **ZEROBOUNCE_API_KEY** Your API key; used in mock tests as the valid key sample (any value will work for mock tests).
736
+
737
+ An invalid API key for error-handling tests is hardcoded in the spec; no env var is required.
480
738
 
481
- To set them
739
+ To set your key:
482
740
  ```bash
483
- export ZEROBOUNCE_API_KEY=99e7ef20ceea4480a173b07b1be75371
484
- export INCORRECT_API_KEY=thiskeyisinvalidorotherwiseincorrect
485
- export TEST=unit
741
+ export ZEROBOUNCE_API_KEY=your_api_key_here
486
742
  ```
487
743
 
488
- A .env.sample file is provided.
744
+ A .env.example file is provided.
745
+
746
+ Tests use webmock and vcr for mocking HTTP requests. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.example file for the mock tests to work. If you do not wish to use this key for mocks, you can replace it with any value in the .yml files under spec/cassettes or delete all of them and rerun the tests so that vcr records them with a new key.
747
+
748
+ ## Publish
489
749
 
490
- Mock tests were generated using webmock and vcr. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.sample file for the mock tests to work. If you do not wish to use this key for mocks, you can replace it with any value in the .yml files under spec/cassettes or delete all of them and rerun the tests so that vcr records them with a new key.
750
+ See the [sdk-docs (RubyGems)](../sdk-docs/rubygems/) guide in the SDKs repo for build and `gem push` steps.
data/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you think you've found a security issue, please report it privately instead of opening a public issue.
6
+
7
+ **Email:** integrations@zerobounce.net (use a subject like `[zero-bounce-ruby] Security`).
8
+
9
+ We'll look into reports as we can. If the issue is in the Zero Bounce API or service rather than this SDK, we may forward it to the right team.
10
+
11
+ ## Supported Versions
12
+
13
+ We focus on the current release line for fixes. Using the [latest version](https://rubygems.org/gems/zerobounce-sdk) is recommended.
14
+
15
+ ## Tips for Using This SDK
16
+
17
+ * Don't commit API keys or `.env` files—use environment variables or a secrets manager.
18
+ * Keep dependencies up to date with `bundle install` and upgrade when new versions are released.
19
+ * The client uses HTTPS by default; avoid overriding to non-HTTPS in production.
20
+
21
+ Thanks for helping keep things secure.
data/documentation.md CHANGED
@@ -76,10 +76,13 @@ Zerobounce.api_usage(Date.today, Date.today)
76
76
  "sub_status_mailbox_quota_exceeded"=>0,
77
77
  "sub_status_forcible_disconnect"=>0,
78
78
  "sub_status_failed_smtp_connection"=>0,
79
+ "sub_status_accept_all"=>0,
79
80
  "sub_status_mx_forward"=>0,
80
81
  "sub_status_alternate"=>0,
81
- "sub_status_blocked"=>0,
82
82
  "sub_status_allowed"=>0,
83
+ "sub_status_blocked"=>0,
84
+ "sub_status_gold"=>0,
85
+ "sub_status_role_based_accept_all"=>0,
83
86
  "start_date"=>"4/28/2023",
84
87
  "end_date"=>"4/28/2023"}
85
88
  ```
@@ -400,6 +403,81 @@ Zerobounce.guessformat("zerobounce.net")
400
403
  # Zerobounce.guessformat("zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")
401
404
  ```
402
405
 
406
+ Find Email Address
407
+ ```ruby
408
+ Zerobounce.find_email("John", domain: "zerobounce.net")
409
+ =>
410
+ {
411
+ "email": "john@zerobounce.net",
412
+ "email_confidence": "medium",
413
+ "domain": "zerobounce.net",
414
+ "company_name": "ZeroBounce",
415
+ "did_you_mean": "",
416
+ "failure_reason": ""
417
+ }
418
+
419
+ # With additional name information for better accuracy
420
+ Zerobounce.find_email("John", domain: "zerobounce.net", middle_name: "Deere", last_name: "Doe")
421
+ ```
422
+
423
+ Find Domain Information
424
+ ```ruby
425
+ Zerobounce.find_domain(domain: "zerobounce.net")
426
+ =>
427
+ {
428
+ "domain": "zerobounce.net",
429
+ "company_name": "Hertza, LLC",
430
+ "format": "first.last",
431
+ "confidence": "high",
432
+ "did_you_mean": "",
433
+ "failure_reason": "",
434
+ "other_domain_formats": [
435
+ {"format": "first", "confidence": "high"},
436
+ {"format": "last.first", "confidence": "high"},
437
+ {"format": "lastfirst", "confidence": "high"},
438
+ {"format": "firstl", "confidence": "high"},
439
+ {"format": "lfirst", "confidence": "high"},
440
+ {"format": "firstlast", "confidence": "high"},
441
+ {"format": "last_middle_f", "confidence": "high"},
442
+ {"format": "last", "confidence": "high"},
443
+ {"format": "f.last", "confidence": "medium"},
444
+ {"format": "last-f", "confidence": "medium"},
445
+ {"format": "l.first", "confidence": "medium"},
446
+ {"format": "last_f", "confidence": "medium"},
447
+ {"format": "first.middle.last", "confidence": "medium"},
448
+ {"format": "first-last", "confidence": "medium"},
449
+ {"format": "last.f", "confidence": "medium"},
450
+ {"format": "last_first", "confidence": "medium"},
451
+ {"format": "f-last", "confidence": "medium"},
452
+ {"format": "first.l", "confidence": "medium"},
453
+ {"format": "first-l", "confidence": "medium"},
454
+ {"format": "first_l", "confidence": "medium"},
455
+ {"format": "first_last", "confidence": "medium"},
456
+ {"format": "f_last", "confidence": "medium"},
457
+ {"format": "last-first", "confidence": "medium"},
458
+ {"format": "flast", "confidence": "medium"},
459
+ {"format": "lastf", "confidence": "medium"},
460
+ {"format": "l_first", "confidence": "medium"},
461
+ {"format": "l-first", "confidence": "medium"},
462
+ {"format": "first-middle-last", "confidence": "low"},
463
+ {"format": "firstmlast", "confidence": "low"},
464
+ {"format": "last.middle.first", "confidence": "low"},
465
+ {"format": "last_middle_first", "confidence": "low"},
466
+ {"format": "first_middle_last", "confidence": "low"},
467
+ {"format": "last-middle-first", "confidence": "low"},
468
+ {"format": "first-m-last", "confidence": "low"},
469
+ {"format": "firstmiddlelast", "confidence": "low"},
470
+ {"format": "last.m.first", "confidence": "low"},
471
+ {"format": "lastmfirst", "confidence": "low"},
472
+ {"format": "lastmiddlefirst", "confidence": "low"},
473
+ {"format": "last_m_first", "confidence": "low"},
474
+ {"format": "first.m.last", "confidence": "low"},
475
+ {"format": "first_m_last", "confidence": "low"},
476
+ {"format": "last-m-first", "confidence": "low"}
477
+ ]
478
+ }
479
+ ```
480
+
403
481
 
404
482
  #### Development
405
483
 
@@ -421,17 +499,17 @@ Finished in 6.81 seconds (files took 0.40587 seconds to load)
421
499
 
422
500
  ##### Test parameters
423
501
  The tests use the following environment parameters:
424
- TEST {unit|live} influences whether mocked unit tests are run or the live server is used (credits may be used if you choose to do this)
425
- ZEROBOUNCE_API_KEY {<zerobounce-api-key-value>} this key is used to make requests to the live server; it is also used in mock tests as a valid key sample (any value will work for mock tests)
426
- INCORRECT_API_KEY {any non whitespace string value that is not a valid key} used for tests where the requests are meant to fail due to the API key value.
502
+ - **TEST** {unit|live} Influences whether mocked unit tests are run or the live server is used (credits may be used if you choose to do this).
503
+ - **ZEROBOUNCE_API_KEY** Your API key; used to make requests to the live server and in mock tests as the valid key sample (any value will work for mock tests).
504
+
505
+ An invalid API key for error-handling tests is hardcoded in the spec; no env var is required.
427
506
 
428
- To set them
507
+ To set them:
429
508
  ```bash
430
- export ZEROBOUNCE_API_KEY=99e7ef20ceea4480a173b07b1be75371
431
- export INCORRECT_API_KEY=thiskeyisinvalidorotherwiseincorrect
509
+ export ZEROBOUNCE_API_KEY=your_api_key_here
432
510
  export TEST=unit
433
511
  ```
434
512
 
435
- A .env.sample file is provided.
513
+ A .env.example file is provided.
436
514
 
437
- Mock tests were generated using webmock and vcr. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.sample file for the mock tests to work. If you do not wish to use this key for mocks, you can replace it with any value in the .yml files under spec/cassettes or delete all of them and rerun the tests so that vcr records them with a new key.
515
+ Mock tests were generated using webmock and vcr. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.example file for the mock tests to work. If you do not wish to use this key for mocks, you can replace it with any value in the .yml files under spec/cassettes or delete all of them and rerun the tests so that vcr records them with a new key.
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zerobounce
4
+ # API URL constants for Zerobounce endpoints
5
+ #
6
+ # This class provides constant values for different Zerobounce API endpoints
7
+ # that can be accessed publicly by gem users.
8
+ #
9
+ class ApiUrls
10
+ # API URLs
11
+ DEFAULT_URL = 'https://api.zerobounce.net/v2/'
12
+ EU_URL = 'https://api-eu.zerobounce.net/v2/'
13
+ US_URL = 'https://api-us.zerobounce.net/v2/'
14
+
15
+ # Bulk API URLs
16
+ BULK_DEFAULT_URL = 'https://bulkapi.zerobounce.net/v2/'
17
+ end
18
+ end
@@ -13,6 +13,34 @@ module Zerobounce
13
13
 
14
14
  protected
15
15
 
16
+ # Strips trailing slashes from root URL without using a regex (avoids ReDoS).
17
+ def self.__root_without_trailing_slashes__(root)
18
+ s = root.to_s
19
+ s = s.chomp('/') while s.end_with?('/')
20
+ s
21
+ end
22
+
23
+ # Resolves and validates filepath to prevent path traversal (e.g. ../../etc/passwd).
24
+ # Returns a canonical path only if the file is under the current directory and is a regular file.
25
+ def self.__safe_file_path__(filepath)
26
+ raise ArgumentError, 'File path is required' if filepath.nil? || filepath.to_s.empty?
27
+ filepath = filepath.to_s
28
+ expanded = File.expand_path(filepath)
29
+ base = File.realpath(Dir.pwd)
30
+ base_with_sep = base + File::SEPARATOR
31
+ unless expanded == base || expanded.start_with?(base_with_sep)
32
+ raise ArgumentError, 'File path must be under the current directory'
33
+ end
34
+ canonical = File.realpath(expanded)
35
+ unless canonical == base || canonical.start_with?(base_with_sep)
36
+ raise ArgumentError, 'File path must be under the current directory'
37
+ end
38
+ unless File.file?(canonical)
39
+ raise ArgumentError, 'File path must point to a regular file'
40
+ end
41
+ canonical
42
+ end
43
+
16
44
  def self._get(root, path, params, content_type='application/json')
17
45
 
18
46
  # puts path
@@ -21,7 +49,7 @@ module Zerobounce
21
49
  raise ("API key must be assigned") if not Zerobounce.config.apikey
22
50
 
23
51
  params[:api_key] = Zerobounce.config.apikey
24
- url = "#{root}/#{path}"
52
+ url = "#{Zerobounce::BaseRequest.__root_without_trailing_slashes__(root)}/#{path}"
25
53
 
26
54
  response = RestClient.get(url, {params: params})
27
55
  return response
@@ -32,11 +60,11 @@ module Zerobounce
32
60
  raise ("API key must be assigned") if not Zerobounce.config.apikey
33
61
 
34
62
  params[:api_key] = Zerobounce.config.apikey
35
- url = "#{root}/#{path}"
63
+ url = "#{Zerobounce::BaseRequest.__root_without_trailing_slashes__(root)}/#{path}"
36
64
  response = nil
37
65
 
38
66
  if filepath or content_type == 'multipart/form-data'
39
- params[:file] = File.new(filepath, 'rb')
67
+ params[:file] = File.new(Zerobounce::BaseRequest.__safe_file_path__(filepath), 'rb')
40
68
  params[:multipart] = true
41
69
  response = RestClient.post(url, params)
42
70
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rest-client'
4
4
  require 'dotenv'
5
+ require_relative 'api_urls'
5
6
 
6
7
  module Zerobounce
7
8
  # Configuration object for Zerobounce.
@@ -17,20 +18,23 @@ module Zerobounce
17
18
  # @attr [String] apikey
18
19
  # A Zerobounce API key.
19
20
  #
21
+ # @attr [String] api_root_url
22
+ # The Zerobounce API root URL. Defaults to ApiUrls::DEFAULT_URL.
23
+ #
24
+ # @attr [String] bulk_api_root_url
25
+ # The Zerobounce bulk API root URL.
26
+ #
20
27
  # @attr [Array<Symbol>] valid_statues
21
28
  # The statuses that are considered valid by {Response#valid?}.
22
29
  class Configuration
23
- attr_accessor :host
24
- attr_accessor :headers
25
- attr_accessor :apikey
26
- attr_accessor :valid_statuses
27
- attr_accessor :mock
30
+ attr_accessor :headers, :apikey, :api_root_url, :bulk_api_root_url, :valid_statuses, :mock
28
31
 
29
- def initialize(mock=false)
30
- if File.file?(".env") then Dotenv.load(".env") else Dotenv.load end
31
- self.host = 'https://api.zerobounce.net'
32
+ def initialize(mock = false)
33
+ File.file?('.env') ? Dotenv.load('.env') : Dotenv.load
32
34
  self.apikey = ENV['ZEROBOUNCE_API_KEY']
33
- self.valid_statuses = %i[valid catch_all]
35
+ self.api_root_url = ENV['ZEROBOUNCE_API_URL'] || ApiUrls::DEFAULT_URL
36
+ self.bulk_api_root_url = ENV['ZEROBOUNCE_BULK_API_URL'] || ApiUrls::BULK_DEFAULT_URL
37
+ self.valid_statuses = %i[valid catch_all accept_all]
34
38
  self.headers = { user_agent: "ZerobounceRubyGem/#{Zerobounce::VERSION}" }
35
39
  self.mock = mock
36
40
  end
@@ -8,7 +8,7 @@ module Zerobounce
8
8
  class MockRequest < BaseRequest
9
9
 
10
10
  def self.get(path, params, content_type='application/json')
11
- response = self._get(Zerobounce::API_ROOT_URL, path, params, content_type)
11
+ response = self._get(Zerobounce.configuration.api_root_url, path, params, content_type)
12
12
  if response.headers[:content_type] == 'application/json'
13
13
  response_body = response.body
14
14
  response_body_json = JSON.parse(response_body)
@@ -25,7 +25,7 @@ module Zerobounce
25
25
  end
26
26
 
27
27
  def self.bulk_get(path, params, content_type='application/json')
28
- response = self._get(Zerobounce::BULK_API_ROOT_URL, path, params, content_type)
28
+ response = self._get(Zerobounce.configuration.bulk_api_root_url, path, params, content_type)
29
29
  if response.headers[:content_type] == 'application/json'
30
30
  response_body = response.body
31
31
  response_body_json = JSON.parse(response_body)
@@ -42,7 +42,7 @@ module Zerobounce
42
42
  end
43
43
 
44
44
  def self.bulk_post(path, params, content_type='application/json', filepath=nil)
45
- response = self._post(Zerobounce::BULK_API_ROOT_URL, path, params, \
45
+ response = self._post(Zerobounce.configuration.bulk_api_root_url, path, params, \
46
46
  content_type, filepath)
47
47
  if response.headers[:content_type] == 'application/json'
48
48
  response_body = response.body