httparty 0.22.0 → 0.24.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -0
- data/Gemfile +1 -0
- data/README.md +16 -16
- data/docs/README.md +2 -0
- data/examples/party_foul_mode.rb +90 -0
- data/httparty.gemspec +1 -0
- data/lib/httparty/exceptions.rb +34 -3
- data/lib/httparty/request/body.rb +30 -11
- data/lib/httparty/request/streaming_multipart_body.rb +188 -0
- data/lib/httparty/request.rb +48 -15
- data/lib/httparty/version.rb +1 -1
- data/lib/httparty.rb +10 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7d431b8f544d235a5d5c3521b230d418a94e98cb452f453f3a7890888a5c8d0
|
|
4
|
+
data.tar.gz: 6800438b8d0842331240337f1f088d05fee9d1a4aa7e0e155034aad76c6dd72c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00e476b359eb3ad3d03ac5665697d9591c3aefc81a58c9a74cb1c8ac6bad52627c1a8343b99c24ff8a82d01c128535fbaf660828031283ce29acb9ae81fb111b
|
|
7
|
+
data.tar.gz: 3c8c4a7c12c1c47036c9cc940cca0ec4c0ef48cc085b6ce68768aa37ee67ab93dc8f0e5d970bed0543fabe97470f9a21da9b1a2840b2ffc27176f588a81e74d6
|
data/.github/workflows/ci.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/jnunemaker/httparty/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
Makes http fun again!
|
|
5
|
+
Makes http fun again! Ain't no party like a httparty, because a httparty don't stop.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,9 +12,8 @@ gem install httparty
|
|
|
12
12
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* You like to party!
|
|
15
|
+
- Ruby 2.7.0 or higher
|
|
16
|
+
- You like to party!
|
|
18
17
|
|
|
19
18
|
## Examples
|
|
20
19
|
|
|
@@ -47,7 +46,8 @@ puts stack_exchange.questions
|
|
|
47
46
|
puts stack_exchange.users
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
See the [examples directory](http://github.com/jnunemaker/httparty/tree/
|
|
49
|
+
See the [examples directory](http://github.com/jnunemaker/httparty/tree/main/examples) for even more goodies.
|
|
50
|
+
|
|
51
51
|
## Command Line Interface
|
|
52
52
|
|
|
53
53
|
httparty also includes the executable `httparty` which can be
|
|
@@ -63,17 +63,17 @@ httparty "https://api.stackexchange.com/2.2/questions?site=stackoverflow"
|
|
|
63
63
|
|
|
64
64
|
## Help and Docs
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
- [Docs](https://github.com/jnunemaker/httparty/tree/main/docs)
|
|
67
|
+
- https://github.com/jnunemaker/httparty/discussions
|
|
68
|
+
- https://www.rubydoc.info/github/jnunemaker/httparty
|
|
69
69
|
|
|
70
70
|
## Contributing
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
- Fork the project.
|
|
73
|
+
- Run `bundle`
|
|
74
|
+
- Run `bundle exec rake`
|
|
75
|
+
- Make your feature addition or bug fix.
|
|
76
|
+
- Add tests for it. This is important so I don't break it in a future version unintentionally.
|
|
77
|
+
- Run `bundle exec rake` (No, REALLY :))
|
|
78
|
+
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
|
|
79
|
+
- Send me a pull request. Bonus points for topic branches.
|
data/docs/README.md
CHANGED
|
@@ -36,6 +36,8 @@ You can use this guide to work with SSL certificates.
|
|
|
36
36
|
|
|
37
37
|
```ruby
|
|
38
38
|
# Use this example if you are using a pem file
|
|
39
|
+
# - cert.pem must contain the content of a PEM file having the private key appended (separated from the cert by a newline \n)
|
|
40
|
+
# - Use an empty string for the password if the cert is not password protected
|
|
39
41
|
|
|
40
42
|
class Client
|
|
41
43
|
include HTTParty
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
|
|
3
|
+
class APIClient
|
|
4
|
+
include HTTParty
|
|
5
|
+
base_uri 'api.example.com'
|
|
6
|
+
|
|
7
|
+
def self.fetch_user(id)
|
|
8
|
+
begin
|
|
9
|
+
get("/users/#{id}", foul: true)
|
|
10
|
+
rescue HTTParty::NetworkError => e
|
|
11
|
+
handle_network_error(e)
|
|
12
|
+
rescue HTTParty::ResponseError => e
|
|
13
|
+
handle_api_error(e)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def self.handle_network_error(error)
|
|
20
|
+
case error.cause
|
|
21
|
+
when Errno::ECONNREFUSED
|
|
22
|
+
{
|
|
23
|
+
error: :server_down,
|
|
24
|
+
message: "The API server appears to be down",
|
|
25
|
+
details: error.message
|
|
26
|
+
}
|
|
27
|
+
when Net::OpenTimeout, Timeout::Error
|
|
28
|
+
{
|
|
29
|
+
error: :timeout,
|
|
30
|
+
message: "The request timed out",
|
|
31
|
+
details: error.message
|
|
32
|
+
}
|
|
33
|
+
when SocketError
|
|
34
|
+
{
|
|
35
|
+
error: :network_error,
|
|
36
|
+
message: "Could not connect to the API server",
|
|
37
|
+
details: error.message
|
|
38
|
+
}
|
|
39
|
+
when OpenSSL::SSL::SSLError
|
|
40
|
+
{
|
|
41
|
+
error: :ssl_error,
|
|
42
|
+
message: "SSL certificate verification failed",
|
|
43
|
+
details: error.message
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
{
|
|
47
|
+
error: :unknown_network_error,
|
|
48
|
+
message: "An unexpected network error occurred",
|
|
49
|
+
details: error.message
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.handle_api_error(error)
|
|
55
|
+
{
|
|
56
|
+
error: :api_error,
|
|
57
|
+
message: "API returned error #{error.response.code}",
|
|
58
|
+
details: error.response.body
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Example usage:
|
|
64
|
+
|
|
65
|
+
# 1. When server is down
|
|
66
|
+
result = APIClient.fetch_user(123)
|
|
67
|
+
puts "Server down example:"
|
|
68
|
+
puts result.inspect
|
|
69
|
+
puts
|
|
70
|
+
|
|
71
|
+
# 2. When request times out
|
|
72
|
+
result = APIClient.fetch_user(456)
|
|
73
|
+
puts "Timeout example:"
|
|
74
|
+
puts result.inspect
|
|
75
|
+
puts
|
|
76
|
+
|
|
77
|
+
# 3. When SSL error occurs
|
|
78
|
+
result = APIClient.fetch_user(789)
|
|
79
|
+
puts "SSL error example:"
|
|
80
|
+
puts result.inspect
|
|
81
|
+
puts
|
|
82
|
+
|
|
83
|
+
# 4. Simple example without a wrapper class
|
|
84
|
+
begin
|
|
85
|
+
HTTParty.get('https://api.example.com/users', foul: true)
|
|
86
|
+
rescue HTTParty::Foul => e
|
|
87
|
+
puts "Direct usage example:"
|
|
88
|
+
puts "Error type: #{e.cause.class}"
|
|
89
|
+
puts "Error message: #{e.message}"
|
|
90
|
+
end
|
data/httparty.gemspec
CHANGED
|
@@ -12,6 +12,7 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.homepage = "https://github.com/jnunemaker/httparty"
|
|
13
13
|
s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.'
|
|
14
14
|
s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.'
|
|
15
|
+
s.metadata["changelog_uri"] = 'https://github.com/jnunemaker/httparty/releases'
|
|
15
16
|
|
|
16
17
|
s.required_ruby_version = '>= 2.7.0'
|
|
17
18
|
|
data/lib/httparty/exceptions.rb
CHANGED
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTParty
|
|
4
|
+
COMMON_NETWORK_ERRORS = [
|
|
5
|
+
EOFError,
|
|
6
|
+
Errno::ECONNABORTED,
|
|
7
|
+
Errno::ECONNREFUSED,
|
|
8
|
+
Errno::ECONNRESET,
|
|
9
|
+
Errno::EHOSTUNREACH,
|
|
10
|
+
Errno::EINVAL,
|
|
11
|
+
Errno::ENETUNREACH,
|
|
12
|
+
Errno::ENOTSOCK,
|
|
13
|
+
Errno::EPIPE,
|
|
14
|
+
Errno::ETIMEDOUT,
|
|
15
|
+
Net::HTTPBadResponse,
|
|
16
|
+
Net::HTTPHeaderSyntaxError,
|
|
17
|
+
Net::ProtocolError,
|
|
18
|
+
Net::ReadTimeout,
|
|
19
|
+
OpenSSL::SSL::SSLError,
|
|
20
|
+
SocketError,
|
|
21
|
+
Timeout::Error # Also covers subclasses like Net::OpenTimeout
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
4
24
|
# @abstract Exceptions raised by HTTParty inherit from Error
|
|
5
25
|
class Error < StandardError; end
|
|
6
26
|
|
|
27
|
+
# @abstract Exceptions raised by HTTParty inherit from this because it is funny
|
|
28
|
+
# and if you don't like fun you should be using a different library.
|
|
29
|
+
class Foul < Error; end
|
|
30
|
+
|
|
7
31
|
# Exception raised when you attempt to set a non-existent format
|
|
8
|
-
class UnsupportedFormat <
|
|
32
|
+
class UnsupportedFormat < Foul; end
|
|
9
33
|
|
|
10
34
|
# Exception raised when using a URI scheme other than HTTP or HTTPS
|
|
11
|
-
class UnsupportedURIScheme <
|
|
35
|
+
class UnsupportedURIScheme < Foul; end
|
|
12
36
|
|
|
13
37
|
# @abstract Exceptions which inherit from ResponseError contain the Net::HTTP
|
|
14
38
|
# response object accessible via the {#response} method.
|
|
15
|
-
class ResponseError <
|
|
39
|
+
class ResponseError < Foul
|
|
16
40
|
# Returns the response of the last request
|
|
17
41
|
# @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g.
|
|
18
42
|
# Net::HTTPOK
|
|
@@ -32,4 +56,11 @@ module HTTParty
|
|
|
32
56
|
|
|
33
57
|
# Exception that is raised when request redirects and location header is present more than once
|
|
34
58
|
class DuplicateLocationHeader < ResponseError; end
|
|
59
|
+
|
|
60
|
+
# Exception that is raised when common network errors occur.
|
|
61
|
+
class NetworkError < Foul; end
|
|
62
|
+
|
|
63
|
+
# Exception that is raised when an absolute URI is used that doesn't match
|
|
64
|
+
# the configured base_uri, which could indicate an SSRF attempt.
|
|
65
|
+
class UnsafeURIError < Foul; end
|
|
35
66
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'multipart_boundary'
|
|
4
|
+
require_relative 'streaming_multipart_body'
|
|
4
5
|
|
|
5
6
|
module HTTParty
|
|
6
7
|
class Request
|
|
@@ -30,6 +31,22 @@ module HTTParty
|
|
|
30
31
|
params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
def streaming?
|
|
35
|
+
multipart? && has_file?(params)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_stream
|
|
39
|
+
return nil unless streaming?
|
|
40
|
+
StreamingMultipartBody.new(prepared_parts, boundary)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def prepared_parts
|
|
44
|
+
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
|
45
|
+
normalized_params.map do |key, value|
|
|
46
|
+
[key, value, file?(value)]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
33
50
|
private
|
|
34
51
|
|
|
35
52
|
# https://html.spec.whatwg.org/#multipart-form-data
|
|
@@ -42,20 +59,20 @@ module HTTParty
|
|
|
42
59
|
def generate_multipart
|
|
43
60
|
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
|
44
61
|
|
|
45
|
-
multipart = normalized_params.inject(''.
|
|
46
|
-
memo << "--#{boundary}#{NEWLINE}"
|
|
47
|
-
memo << %(Content-Disposition: form-data; name="#{key}")
|
|
62
|
+
multipart = normalized_params.inject(''.b) do |memo, (key, value)|
|
|
63
|
+
memo << "--#{boundary}#{NEWLINE}".b
|
|
64
|
+
memo << %(Content-Disposition: form-data; name="#{key}").b
|
|
48
65
|
# value.path is used to support ActionDispatch::Http::UploadedFile
|
|
49
66
|
# https://github.com/jnunemaker/httparty/pull/585
|
|
50
|
-
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}") if file?(value)
|
|
51
|
-
memo << NEWLINE
|
|
52
|
-
memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value)
|
|
53
|
-
memo << NEWLINE
|
|
67
|
+
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}").b if file?(value)
|
|
68
|
+
memo << NEWLINE.b
|
|
69
|
+
memo << "Content-Type: #{content_type(value)}#{NEWLINE}".b if file?(value)
|
|
70
|
+
memo << NEWLINE.b
|
|
54
71
|
memo << content_body(value)
|
|
55
|
-
memo << NEWLINE
|
|
72
|
+
memo << NEWLINE.b
|
|
56
73
|
end
|
|
57
74
|
|
|
58
|
-
multipart << "--#{boundary}--#{NEWLINE}"
|
|
75
|
+
multipart << "--#{boundary}--#{NEWLINE}".b
|
|
59
76
|
end
|
|
60
77
|
|
|
61
78
|
def has_file?(value)
|
|
@@ -83,10 +100,12 @@ module HTTParty
|
|
|
83
100
|
def content_body(object)
|
|
84
101
|
if file?(object)
|
|
85
102
|
object = (file = object).read
|
|
103
|
+
object.force_encoding(Encoding::BINARY) if object.respond_to?(:force_encoding)
|
|
86
104
|
file.rewind if file.respond_to?(:rewind)
|
|
105
|
+
object.to_s
|
|
106
|
+
else
|
|
107
|
+
object.to_s.b
|
|
87
108
|
end
|
|
88
|
-
|
|
89
|
-
object.to_s
|
|
90
109
|
end
|
|
91
110
|
|
|
92
111
|
def content_type(object)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTParty
|
|
4
|
+
class Request
|
|
5
|
+
class StreamingMultipartBody
|
|
6
|
+
NEWLINE = "\r\n"
|
|
7
|
+
CHUNK_SIZE = 64 * 1024 # 64 KB chunks
|
|
8
|
+
|
|
9
|
+
def initialize(parts, boundary)
|
|
10
|
+
@parts = parts
|
|
11
|
+
@boundary = boundary
|
|
12
|
+
@part_index = 0
|
|
13
|
+
@state = :header
|
|
14
|
+
@current_file = nil
|
|
15
|
+
@header_buffer = nil
|
|
16
|
+
@header_offset = 0
|
|
17
|
+
@footer_sent = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def size
|
|
21
|
+
@size ||= calculate_size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read(length = nil, outbuf = nil)
|
|
25
|
+
outbuf = outbuf ? outbuf.replace(''.b) : ''.b
|
|
26
|
+
|
|
27
|
+
return read_all(outbuf) if length.nil?
|
|
28
|
+
|
|
29
|
+
while outbuf.bytesize < length
|
|
30
|
+
chunk = read_chunk(length - outbuf.bytesize)
|
|
31
|
+
break if chunk.nil?
|
|
32
|
+
outbuf << chunk
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
outbuf.empty? ? nil : outbuf
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def rewind
|
|
39
|
+
@part_index = 0
|
|
40
|
+
@state = :header
|
|
41
|
+
@current_file = nil
|
|
42
|
+
@header_buffer = nil
|
|
43
|
+
@header_offset = 0
|
|
44
|
+
@footer_sent = false
|
|
45
|
+
@parts.each do |_key, value, _is_file|
|
|
46
|
+
value.rewind if value.respond_to?(:rewind)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def read_all(outbuf)
|
|
53
|
+
while (chunk = read_chunk(CHUNK_SIZE))
|
|
54
|
+
outbuf << chunk
|
|
55
|
+
end
|
|
56
|
+
outbuf.empty? ? nil : outbuf
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def read_chunk(max_length)
|
|
60
|
+
loop do
|
|
61
|
+
return nil if @part_index >= @parts.size && @footer_sent
|
|
62
|
+
|
|
63
|
+
if @part_index >= @parts.size
|
|
64
|
+
@footer_sent = true
|
|
65
|
+
return "--#{@boundary}--#{NEWLINE}".b
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
key, value, is_file = @parts[@part_index]
|
|
69
|
+
|
|
70
|
+
case @state
|
|
71
|
+
when :header
|
|
72
|
+
chunk = read_header_chunk(key, value, is_file, max_length)
|
|
73
|
+
return chunk if chunk
|
|
74
|
+
|
|
75
|
+
when :body
|
|
76
|
+
chunk = read_body_chunk(value, is_file, max_length)
|
|
77
|
+
return chunk if chunk
|
|
78
|
+
|
|
79
|
+
when :newline
|
|
80
|
+
@state = :header
|
|
81
|
+
@part_index += 1
|
|
82
|
+
return NEWLINE.b
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def read_header_chunk(key, value, is_file, max_length)
|
|
88
|
+
if @header_buffer.nil?
|
|
89
|
+
@header_buffer = build_part_header(key, value, is_file)
|
|
90
|
+
@header_offset = 0
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
remaining = @header_buffer.bytesize - @header_offset
|
|
94
|
+
if remaining > 0
|
|
95
|
+
chunk_size = [remaining, max_length].min
|
|
96
|
+
chunk = @header_buffer.byteslice(@header_offset, chunk_size)
|
|
97
|
+
@header_offset += chunk_size
|
|
98
|
+
return chunk
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@header_buffer = nil
|
|
102
|
+
@header_offset = 0
|
|
103
|
+
@state = :body
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def read_body_chunk(value, is_file, max_length)
|
|
108
|
+
if is_file
|
|
109
|
+
chunk = read_file_chunk(value, max_length)
|
|
110
|
+
if chunk
|
|
111
|
+
return chunk
|
|
112
|
+
else
|
|
113
|
+
@current_file = nil
|
|
114
|
+
@state = :newline
|
|
115
|
+
return nil
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
@state = :newline
|
|
119
|
+
return value.to_s.b
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def read_file_chunk(file, max_length)
|
|
124
|
+
chunk_size = [max_length, CHUNK_SIZE].min
|
|
125
|
+
chunk = file.read(chunk_size)
|
|
126
|
+
return nil if chunk.nil?
|
|
127
|
+
chunk.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
|
|
128
|
+
chunk
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_part_header(key, value, is_file)
|
|
132
|
+
header = "--#{@boundary}#{NEWLINE}".b
|
|
133
|
+
header << %(Content-Disposition: form-data; name="#{key}").b
|
|
134
|
+
if is_file
|
|
135
|
+
header << %(; filename="#{file_name(value).gsub(/["\r\n]/, replacement_table)}").b
|
|
136
|
+
header << NEWLINE.b
|
|
137
|
+
header << "Content-Type: #{content_type(value)}#{NEWLINE}".b
|
|
138
|
+
end
|
|
139
|
+
header << NEWLINE.b
|
|
140
|
+
header
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def calculate_size
|
|
144
|
+
total = 0
|
|
145
|
+
@parts.each do |key, value, is_file|
|
|
146
|
+
total += build_part_header(key, value, is_file).bytesize
|
|
147
|
+
total += content_size(value, is_file)
|
|
148
|
+
total += NEWLINE.bytesize
|
|
149
|
+
end
|
|
150
|
+
total += "--#{@boundary}--#{NEWLINE}".bytesize
|
|
151
|
+
total
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def content_size(value, is_file)
|
|
155
|
+
if is_file
|
|
156
|
+
if value.respond_to?(:size)
|
|
157
|
+
value.size
|
|
158
|
+
elsif value.respond_to?(:stat)
|
|
159
|
+
value.stat.size
|
|
160
|
+
else
|
|
161
|
+
value.read.bytesize.tap { value.rewind }
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
value.to_s.b.bytesize
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def content_type(object)
|
|
169
|
+
return object.content_type if object.respond_to?(:content_type)
|
|
170
|
+
require 'mini_mime'
|
|
171
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
|
172
|
+
mime ? mime.content_type : 'application/octet-stream'
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def file_name(object)
|
|
176
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def replacement_table
|
|
180
|
+
@replacement_table ||= {
|
|
181
|
+
'"' => '%22',
|
|
182
|
+
"\r" => '%0D',
|
|
183
|
+
"\n" => '%0A'
|
|
184
|
+
}.freeze
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
data/lib/httparty/request.rb
CHANGED
|
@@ -113,6 +113,8 @@ module HTTParty
|
|
|
113
113
|
new_uri = path.clone
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
validate_uri_safety!(new_uri) unless redirect
|
|
117
|
+
|
|
116
118
|
# avoid double query string on redirects [#12]
|
|
117
119
|
unless redirect
|
|
118
120
|
new_uri.query = query_string(new_uri)
|
|
@@ -153,24 +155,28 @@ module HTTParty
|
|
|
153
155
|
chunked_body = nil
|
|
154
156
|
current_http = http
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
begin
|
|
159
|
+
self.last_response = current_http.request(@raw_request) do |http_response|
|
|
160
|
+
if block
|
|
161
|
+
chunks = []
|
|
159
162
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
http_response.read_body do |fragment|
|
|
164
|
+
encoded_fragment = encode_text(fragment, http_response['content-type'])
|
|
165
|
+
chunks << encoded_fragment if !options[:stream_body]
|
|
166
|
+
block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
|
|
167
|
+
end
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
chunked_body = chunks.join
|
|
170
|
+
end
|
|
167
171
|
end
|
|
168
|
-
end
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
handle_host_redirection if response_redirects?
|
|
174
|
+
result = handle_unauthorized
|
|
175
|
+
result ||= handle_response(chunked_body, &block)
|
|
176
|
+
result
|
|
177
|
+
rescue *COMMON_NETWORK_ERRORS => e
|
|
178
|
+
raise options[:foul] ? HTTParty::NetworkError.new("#{e.class}: #{e.message}") : e
|
|
179
|
+
end
|
|
174
180
|
end
|
|
175
181
|
|
|
176
182
|
def handle_unauthorized(&block)
|
|
@@ -241,8 +247,17 @@ module HTTParty
|
|
|
241
247
|
if body.multipart?
|
|
242
248
|
content_type = "multipart/form-data; boundary=#{body.boundary}"
|
|
243
249
|
@raw_request['Content-Type'] = content_type
|
|
250
|
+
elsif options[:body].respond_to?(:to_hash) && !@raw_request['Content-Type']
|
|
251
|
+
@raw_request['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
if body.streaming? && options[:stream_body] != false
|
|
255
|
+
stream = body.to_stream
|
|
256
|
+
@raw_request.body_stream = stream
|
|
257
|
+
@raw_request['Content-Length'] = stream.size.to_s
|
|
258
|
+
else
|
|
259
|
+
@raw_request.body = body.call
|
|
244
260
|
end
|
|
245
|
-
@raw_request.body = body.call
|
|
246
261
|
end
|
|
247
262
|
|
|
248
263
|
@raw_request.instance_variable_set(:@decode_content, decompress_content?)
|
|
@@ -429,5 +444,23 @@ module HTTParty
|
|
|
429
444
|
assume_utf16_is_big_endian: assume_utf16_is_big_endian
|
|
430
445
|
).call
|
|
431
446
|
end
|
|
447
|
+
|
|
448
|
+
def validate_uri_safety!(new_uri)
|
|
449
|
+
return if options[:skip_uri_validation]
|
|
450
|
+
|
|
451
|
+
configured_base_uri = options[:base_uri]
|
|
452
|
+
return unless configured_base_uri
|
|
453
|
+
|
|
454
|
+
normalized_base = options[:uri_adapter].parse(
|
|
455
|
+
HTTParty.normalize_base_uri(configured_base_uri)
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
return if new_uri.host == normalized_base.host
|
|
459
|
+
|
|
460
|
+
raise UnsafeURIError,
|
|
461
|
+
"Requested URI '#{new_uri}' has host '#{new_uri.host}' but the " \
|
|
462
|
+
"configured base_uri '#{normalized_base}' has host '#{normalized_base.host}'. " \
|
|
463
|
+
"This request could send credentials to an unintended server."
|
|
464
|
+
end
|
|
432
465
|
end
|
|
433
466
|
end
|
data/lib/httparty/version.rb
CHANGED
data/lib/httparty.rb
CHANGED
|
@@ -62,6 +62,16 @@ module HTTParty
|
|
|
62
62
|
# * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
|
|
63
63
|
|
|
64
64
|
module ClassMethods
|
|
65
|
+
# Turns on or off the foul option.
|
|
66
|
+
#
|
|
67
|
+
# class Foo
|
|
68
|
+
# include HTTParty
|
|
69
|
+
# foul true
|
|
70
|
+
# end
|
|
71
|
+
def foul(bool)
|
|
72
|
+
default_options[:foul] = bool
|
|
73
|
+
end
|
|
74
|
+
|
|
65
75
|
# Turns on logging
|
|
66
76
|
#
|
|
67
77
|
# class Foo
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: httparty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Nunemaker
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2025-12-28 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: csv
|
|
@@ -91,6 +91,7 @@ files:
|
|
|
91
91
|
- examples/microsoft_graph.rb
|
|
92
92
|
- examples/multipart.rb
|
|
93
93
|
- examples/nokogiri_html_parser.rb
|
|
94
|
+
- examples/party_foul_mode.rb
|
|
94
95
|
- examples/peer_cert.rb
|
|
95
96
|
- examples/rescue_json.rb
|
|
96
97
|
- examples/rubyurl.rb
|
|
@@ -117,6 +118,7 @@ files:
|
|
|
117
118
|
- lib/httparty/request.rb
|
|
118
119
|
- lib/httparty/request/body.rb
|
|
119
120
|
- lib/httparty/request/multipart_boundary.rb
|
|
121
|
+
- lib/httparty/request/streaming_multipart_body.rb
|
|
120
122
|
- lib/httparty/response.rb
|
|
121
123
|
- lib/httparty/response/headers.rb
|
|
122
124
|
- lib/httparty/response_fragment.rb
|
|
@@ -129,7 +131,8 @@ files:
|
|
|
129
131
|
homepage: https://github.com/jnunemaker/httparty
|
|
130
132
|
licenses:
|
|
131
133
|
- MIT
|
|
132
|
-
metadata:
|
|
134
|
+
metadata:
|
|
135
|
+
changelog_uri: https://github.com/jnunemaker/httparty/releases
|
|
133
136
|
post_install_message: When you HTTParty, you must party hard!
|
|
134
137
|
rdoc_options: []
|
|
135
138
|
require_paths:
|