savon-multipart 2.1.2 → 2.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 206ff4ab739562cbbf2e15e23d4b7c3a762b08bc26a2fefb286de605603e18ca
4
- data.tar.gz: e1c187a1fbe40e23be747a620ab8378d1dfec812f80b72ec9ae0964b02ce35bb
3
+ metadata.gz: 4473b669ad70af6dfa4371b373da602fe0a9e982b70e2813473506bdf5d28146
4
+ data.tar.gz: 3270f043522e840c94fc3b0ac7b419ccc35e98aa9071c4f63b04844425724fa6
5
5
  SHA512:
6
- metadata.gz: 3fa992c3e53d971d2ff58ef15a09f4ca96bb2b5f682eb11b40bf5848d2e1e487a2cb088455e923ac02d7da86d50d672a6a1c22ac0a2bc91a7aba5c9c6c5e6a4c
7
- data.tar.gz: b85cef7fdcac332de45e1c58140a9855a18af9df988c2f20aefbc2c7630f88ebdf11f68305b0364d00472b29b2a6a72372ff1e9b9f9bf85776b7ec8493b8f315
6
+ metadata.gz: df2da88ea80eb1d39cbb091d2139f47552e7353938daf76a33534553676140608cf84b5e65fb41b6870c6dc4192e5104875f8c8c0a58947d0963f48979d01169
7
+ data.tar.gz: 336c526a3f42e0d03560aa29d4046ea62b0efa6eba3a0058abf74e90ae80a8ec6165411926ab68595615fd9348acb928ba0e30c02805c5db6a663e41f4123fbb
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a bug report
4
+
5
+ ---
6
+
7
+ <!-- Please don't delete this template or we'll close your issue -->
8
+ <!-- Before creating an issue, please ensure you are using the latest version of savon-multipart. -->
9
+
10
+ # Bug report
11
+
12
+
13
+ **Current behavior:**
14
+
15
+
16
+ **Expected behavior:**
17
+
18
+
19
+ **Steps to reproduce current behavior:**
20
+
21
+ <!-- The more of the following you can give us, the faster we can reproduce and fix it. -->
22
+
23
+ * Client setup and the operation call (the Ruby code you ran):
24
+
25
+ * The request XML built by the client (or just the part that's wrong):
26
+
27
+ * The response you got back (body, and for a transport issue the status and headers):
28
+
29
+ * The WSDL of the service, or a snippet of the operation involved:
30
+
31
+
32
+ **System information:**
33
+
34
+ * ruby version:
35
+ * savon-multipart version:
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new feature for savon-multipart
4
+
5
+ ---
6
+
7
+ <!-- Please don't delete this template or we'll close your issue -->
8
+
9
+ ## Feature request
10
+
11
+ <!-- Issues which contain questions or support requests will be closed. -->
12
+
13
+ **Motivation for this feature:**
14
+
15
+ <!-- Please describe the use case or problem this would solve. -->
16
+
17
+ **Proposed implementation:**
18
+
19
+ <!-- Please describe at least one proposed implementation design. -->
20
+
21
+ **Would this introduce a breaking change?**
22
+
23
+ <!-- If yes, please describe the impact and a migration path for existing applications. -->
24
+
25
+ **Are you willing to work on this yourself?**
26
+ yes
27
+
@@ -0,0 +1,16 @@
1
+ **What kind of change is this?**
2
+
3
+ <!-- E.g. a bugfix, feature, refactoring, build change, etc… -->
4
+
5
+ **Did you add tests for your changes?**
6
+
7
+ <!-- Note that we won't merge your changes if you don't add tests -->
8
+
9
+ **Summary of changes**
10
+
11
+ <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
12
+ <!-- Try to link to an open issue for more information. -->
13
+
14
+ **Other information**
15
+
16
+
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ cooldown:
8
+ default-days: 7
@@ -1,46 +1,81 @@
1
- name: CI
1
+ name: Ruby
2
2
 
3
3
  on:
4
+ push:
5
+ branches: [main]
4
6
  pull_request:
5
7
 
6
- push:
7
- branches: [master]
8
+ permissions:
9
+ contents: read # checkout only, nothing else
8
10
 
9
11
  jobs:
10
- # linting:
11
- # runs-on: ubuntu-latest
12
-
13
- # steps:
14
- # - uses: actions/checkout@v1
12
+ audit:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
16
+ with:
17
+ persist-credentials: false
18
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
19
+ with:
20
+ ruby-version: "3.4"
21
+ bundler-cache: true
22
+ - name: Audit dependencies
23
+ run: bundle exec bundle-audit check --update
24
+ - name: Audit Ruby runtime
25
+ # CVE-2026-41316 is an ERB flaw fixed in Ruby 4.0.3 and not yet backported.
26
+ # Ignored on affected rubies only. See:
27
+ # https://www.ruby-lang.org/en/news/2026/04/21/erb-cve-2026-41316/
28
+ # https://github.com/advisories/GHSA-q339-8rmv-2mhv
29
+ run: |
30
+ ignore=""
31
+ if ruby -e 'exit 1 unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new("4.0.3")'; then
32
+ ignore="--ignore CVE-2026-41316"
33
+ fi
34
+ bundle exec ruby-audit check $ignore
15
35
 
16
- # - name: Set up Ruby 2.7
17
- # uses: ruby/setup-ruby@v1
18
- # with:
19
- # ruby-version: 2.7
20
- # bundler-cache: true
36
+ zizmor:
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40
+ with:
41
+ persist-credentials: false
42
+ - name: Audit workflows
43
+ uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3
44
+ with:
45
+ advanced-security: false # fail the build on findings instead of uploading SARIF
21
46
 
22
- # - name: Rubocop
23
- # run: bundle exec rubocop --format progress
47
+ lint:
48
+ runs-on: ubuntu-latest
49
+ steps:
50
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
51
+ with:
52
+ persist-credentials: false
53
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
54
+ with:
55
+ ruby-version: "3.4"
56
+ bundler-cache: true
57
+ - name: RuboCop
58
+ run: bundle exec rubocop --format github
24
59
 
25
- build:
26
- # When we have linting, this is where we will place that
27
- needs: []
60
+ test:
28
61
  runs-on: ubuntu-latest
29
62
  strategy:
63
+ fail-fast: false
30
64
  matrix:
31
- ruby:
32
- - 2.6
33
- - 2.7
34
- - '3.0'
35
- - 3.1
36
- - jruby-9.2
37
- - jruby-9.3
38
-
65
+ ruby: ['3.0', 3.1, 3.2, 3.3, 3.4, '4.0', truffleruby, head, truffleruby-head]
66
+ # Head builds exist to surface upstream regressions early.
67
+ # They should not block the build.
68
+ continue-on-error: ${{ matrix.ruby == 'head' || matrix.ruby == 'truffleruby-head' }}
69
+ env:
70
+ RAILS_ENV: test
39
71
  steps:
40
- - uses: actions/checkout@v1
41
- - uses: ruby/setup-ruby@v1
42
- with:
43
- ruby-version: ${{ matrix.ruby }}
44
- bundler-cache: true # 'bundle install' and cache
45
- - name: Test
46
- run: bundle exec rake
72
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
73
+ with:
74
+ persist-credentials: false
75
+ - name: Set up Ruby
76
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
77
+ with:
78
+ ruby-version: ${{ matrix.ruby }}
79
+ bundler-cache: true # bundle installs and caches dependencies
80
+ - name: Run tests
81
+ run: bundle exec rake --trace
@@ -0,0 +1,22 @@
1
+ name: Push gem
2
+ on:
3
+ release:
4
+ types: [published]
5
+ jobs:
6
+ push:
7
+ runs-on: ubuntu-latest
8
+ permissions:
9
+ id-token: write
10
+ contents: write
11
+ environment: release
12
+ steps:
13
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
14
+ with:
15
+ persist-credentials: false
16
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
17
+ with:
18
+ ruby-version: "3"
19
+ bundler-cache: false
20
+ - name: Install dependencies
21
+ run: bundle install
22
+ - uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ plugins:
4
+ - rubocop-rake
5
+ - rubocop-rspec
6
+
7
+ AllCops:
8
+ TargetRubyVersion: 3.0
9
+ SuggestExtensions: true
10
+
11
+ Gemspec/RequireMFA:
12
+ Enabled: true
13
+
14
+ Gemspec/RequiredRubyVersion:
15
+ Enabled: false
16
+ # Gemspec floors are intentionally low; raising them is a separate breaking-change decision.
17
+
18
+ Naming/FileName:
19
+ Exclude:
20
+ - "lib/savon-multipart.rb"
21
+ # Entry-point file matches the gem name; renaming it would break `require "savon-multipart"`.
22
+
23
+ Layout/FirstHashElementIndentation:
24
+ EnforcedStyle: consistent
25
+
26
+ Layout/HashAlignment:
27
+ EnforcedHashRocketStyle: table
28
+
29
+ RSpec/ContextWording:
30
+ Enabled: false
31
+
32
+ Style/BlockDelimiters:
33
+ EnforcedStyle: semantic
34
+
35
+ Style/HashSyntax:
36
+ EnforcedStyle: ruby19
37
+ EnforcedShorthandSyntax: never
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,7 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # using RuboCop version 1.86.2.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
+ ## 2.1.3 (2026-09-16)
2
+
3
+ * Fix `xop:Include` parts with percent-encoded Content-IDs not being replaced in the response. ([#21](https://github.com/savonrb/savon-multipart/pull/21))
4
+
1
5
  ## 2.1.2 (2022-01-20)
2
6
 
3
7
  * Loosen dependency on `mail` gem. ([#23](https://github.com/savonrb/savon-multipart/pull/23))
8
+ * Add ability to flatten MTOM XOP parts back into the main XML document ([#17](https://github.com/savonrb/savon-multipart/pull/17))
4
9
 
5
10
  ## 2.1.1
6
11
 
data/Gemfile CHANGED
@@ -1,11 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
  gemspec
3
5
 
4
- if RUBY_VERSION > "3.1"
6
+ if RUBY_VERSION > '3.1'
5
7
  # net-smtp, net-pop and net-imap were removed from ruby 3.1 standard gems. See: https://github.com/rails/rails/pull/42366
6
8
  # Can drop when https://github.com/mikel/mail/pull/1439 is resolved
7
- gem "net-imap", require: false
8
- gem "net-pop", require: false
9
- gem "net-smtp", require: false
9
+ gem 'net-imap', require: false
10
+ gem 'net-pop', require: false
11
+ gem 'net-smtp', require: false
10
12
  end
11
- gem "rubocop", "~> 0.49.1"
13
+
14
+ gem 'rubocop', '~> 1.86', '>= 1.86.2'
15
+ gem 'rubocop-rake', '~> 0.7.1'
16
+ gem 'rubocop-rspec', '~> 3.9'
17
+
18
+ gem 'bundler-audit', '~> 0.9.3', require: false
19
+ gem 'ruby_audit', '~> 3.1', require: false if RUBY_VERSION >= '3.1.0'
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
- Savon Multipart [![CI](https://github.com/savonrb/savon-multipart/actions/workflows/ci.yml/badge.svg)](https://github.com/savonrb/savon-multipart/actions/workflows/ci.yml)
2
- ===============
1
+ # Savon Multipart
3
2
 
4
- Adds multipart support (SOAP with Attachments) to [Savon](https://github.com/savonrb/savon).
5
- Please test and provide feedback so we can support as many multipart-soap messages as possible.
3
+ [![CI](https://github.com/savonrb/savon-multipart/actions/workflows/ci.yml/badge.svg)](https://github.com/savonrb/savon-multipart/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/savon-multipart.svg)](https://rubygems.org/gems/savon-multipart)
5
+
6
+ Adds multipart support (SOAP with Attachments) to [Savon](https://github.com/savonrb/savon).
6
7
 
7
8
  Installation
8
9
  ------------
@@ -16,14 +17,18 @@ $ gem install savon-multipart
16
17
  Or in your `Gemfile`:
17
18
 
18
19
  ```ruby
19
- gem 'savon-multipart'
20
+ gem 'savon-multipart', '~> 2.1'
20
21
  ```
21
22
 
22
23
  Usage
23
24
  ------------
24
25
 
25
- Just require the gem 'savon-multipart', which automatically requires 'savon' as well:
26
+ Just require the gem 'savon-multipart', '~> 2.1', which automatically requires 'savon' as well:
26
27
 
27
28
  ```
28
29
  require 'savon-multipart'
29
30
  ```
31
+
32
+ ## Ruby support
33
+
34
+ Savon Multipart requires Ruby >= 1.9.2.
data/RELEASING.md ADDED
@@ -0,0 +1,19 @@
1
+ _You'll need write access to the repository to create a release. Publishing is handled automatically via [trusted publishing](https://guides.rubygems.org/trusted-publishing/)._
2
+
3
+ ## Cutting a release
4
+
5
+ 1. On your release branch (e.g. `main`, `v2.x`), edit [CHANGELOG.md](https://github.com/savonrb/savon-multipart/blob/main/CHANGELOG.md) to finalize the new version number and list of all changes.
6
+ 2. Bump [version.rb](https://github.com/savonrb/savon-multipart/blob/main/lib/savon/multipart/version.rb) to the version you picked in previous step.
7
+ 3. **Final check**: make sure all tests are green, and that `rake build` succeeds. If not, merge any fixes back to the release branch and go to step 1.
8
+ 4. [Draft a new release](https://github.com/savonrb/savon-multipart/releases/new) on Github.
9
+ - In the "Choose a tag" field, type the version tag - e.g. `v2.12.1` - and select "Create new tag on publish".
10
+ - Use `v[version]` for the release title, and copy the changelog into the release notes.
11
+ - Click "Publish release". Github creates and pushes the tag, which triggers the release workflow.
12
+ 5. Publishing to RubyGems.org is fully automated. The [Push gem workflow](https://github.com/savonrb/savon-multipart/actions/workflows/gem_push.yml) triggers on the published release and requires approval from a release manager via the `release` environment before credentials are issued. **Before approving**: confirm that CI is green on the release commit. Approve the deployment in the Actions UI and the gem will be built and pushed automatically.
13
+
14
+ ## Updating minimum ruby version
15
+
16
+ - Update `required_ruby_version` in [savon-multipart.gemspec](https://github.com/savonrb/savon-multipart/blob/main/savon-multipart.gemspec)
17
+ - Update the test matrix in [ci.yml](https://github.com/savonrb/savon-multipart/blob/main/.github/workflows/ci.yml)
18
+ - Update [README](https://github.com/savonrb/savon-multipart/blob/main/README.md) with the correct support matrix
19
+ - Note the updated requirement in [CHANGELOG.md](https://github.com/savonrb/savon-multipart/blob/main/CHANGELOG.md)
data/Rakefile CHANGED
@@ -1,11 +1,16 @@
1
- require "bundler"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
- require "rspec/core/rake_task"
6
+ require 'rspec/core/rake_task'
5
7
 
6
8
  RSpec::Core::RakeTask.new do |t|
7
- t.rspec_opts = %w(-c)
9
+ t.rspec_opts = %w[-c]
8
10
  end
9
11
 
10
- task :default => :spec
11
- task :test => :spec
12
+ desc 'Run the spec suite'
13
+ task default: :spec
14
+
15
+ desc 'Run the spec suite'
16
+ task test: :spec
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mail'
4
+ require 'cgi'
2
5
 
3
6
  module Savon
4
7
  module Multipart
8
+ # Savon::Response subclass that understands multipart/related SOAP
9
+ # responses, exposing MIME parts, attachments and XOP content.
5
10
  class Response < Savon::Response
6
11
  attr_reader :parts
7
12
 
@@ -20,20 +25,23 @@ module Savon
20
25
  end
21
26
 
22
27
  def xml
23
- if multipart?
24
- parse_body unless @has_parsed_body
25
- if xop?
26
- parse_xop unless @has_parsed_xop
27
- @xop_body
28
- else
29
- @parts.first.body.to_s
30
- end
28
+ return super unless multipart?
29
+
30
+ parse_body unless @has_parsed_body
31
+ multipart_xml
32
+ end
33
+
34
+ private
35
+
36
+ def multipart_xml
37
+ if xop?
38
+ parse_xop unless @has_parsed_xop
39
+ @xop_body
31
40
  else
32
- super
41
+ @parts.first.body.to_s
33
42
  end
34
43
  end
35
44
 
36
- private
37
45
  def multipart?
38
46
  !(http.headers['content-type'] =~ /^multipart/im).nil?
39
47
  end
@@ -41,7 +49,7 @@ module Savon
41
49
  def xop?
42
50
  if multipart?
43
51
  parse_body unless @has_parsed_body
44
- !(@parts.first.header['content-type'].to_s =~ /^application\/xop\+xml/i).nil?
52
+ !(@parts.first.header['content-type'].to_s =~ %r{^application/xop\+xml}i).nil?
45
53
  else
46
54
  false
47
55
  end
@@ -49,34 +57,42 @@ module Savon
49
57
 
50
58
  def boundary
51
59
  return unless multipart?
60
+
52
61
  @boundary ||= Mail::Field.new('content-type', http.headers['content-type']).parameters['boundary']
53
62
  end
54
63
 
55
64
  def parse_body
56
65
  @parts = Mail::Part.new(
57
- :headers => http.headers,
58
- :body => http.body
66
+ headers: http.headers,
67
+ body: http.body
59
68
  ).body.split!(boundary).parts
60
69
  @has_parsed_body = true
61
70
  end
62
71
 
63
72
  def parse_xop
64
- xml = @parts.first.body.to_s
65
- parsed = Nokogiri.XML(xml)
66
- xop_elements = parsed.xpath('//xop:Include', xop: "http://www.w3.org/2004/08/xop/include")
67
- if xop_elements.count == 0
73
+ parsed = Nokogiri.XML(@parts.first.body.to_s)
74
+ xop_elements = parsed.xpath('//xop:Include', xop: 'http://www.w3.org/2004/08/xop/include')
75
+
76
+ if xop_elements.empty?
68
77
  @xop_body = @parts.first.body.to_s
69
- @has_parsed_xop = true
70
- return
78
+ else
79
+ replace_xop_includes(xop_elements)
80
+ @xop_body = parsed.to_s
71
81
  end
82
+ @has_parsed_xop = true
83
+ end
84
+
85
+ def replace_xop_includes(xop_elements)
72
86
  xop_elements.each do |xop_element|
73
87
  href = xop_element.attributes['href'].to_s
74
- cid = href[4..-1]
75
- data = @parts.find { |p| p.header['content-id'].to_s == "<#{cid}>" }.body.to_s
88
+ cid = CGI.unescape(href[4..])
89
+ data = find_part_by_cid(cid).body.to_s
76
90
  xop_element.parent.content = Base64.encode64(data).chomp
77
91
  end
78
- @xop_body = parsed.to_s
79
- @has_parsed_xop = true
92
+ end
93
+
94
+ def find_part_by_cid(cid)
95
+ @parts.find { |part| part.header['content-id'].to_s == "<#{cid}>" }
80
96
  end
81
97
  end
82
98
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Savon
2
4
  module Multipart
3
- VERSION = "2.1.2"
5
+ VERSION = '2.1.3'
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'savon'
2
4
  require 'savon/multipart/version'
3
5
  require 'savon/multipart/response'
@@ -1,26 +1,31 @@
1
- lib = File.expand_path("../lib", __FILE__)
2
- $:.unshift lib unless $:.include? lib
1
+ # frozen_string_literal: true
3
2
 
4
- require "savon/multipart/version"
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
5
+
6
+ require 'savon/multipart/version'
5
7
 
6
8
  Gem::Specification.new do |s|
7
- s.name = "savon-multipart"
9
+ s.name = 'savon-multipart'
8
10
  s.version = Savon::Multipart::VERSION
9
- s.authors = ["Martin Westin", "Daniel Harrington"]
10
- s.email = ["martin@eimermusic.com", "me@rubiii.com"]
11
- s.homepage = "http://savonrb.com"
12
- s.summary = "Heavy metal Ruby SOAP client with multipart support"
13
- s.description = "Adds multipart support (SOAP with Attachments) to Savon"
11
+ s.authors = ['Martin Westin', 'Daniel Harrington']
12
+ s.email = ['martin@eimermusic.com', 'me@rubiii.com']
13
+ s.homepage = 'http://savonrb.com'
14
+ s.summary = 'Heavy metal Ruby SOAP client with multipart support'
15
+ s.description = 'Adds multipart support (SOAP with Attachments) to Savon'
16
+ # NOTE: this floor is intentionally conservative — CI starts at Ruby 3.0, but the gem likely
17
+ # still works on older Rubies. Don't raise it just to satisfy lint or match CI without evidence of breakage.
14
18
  s.required_ruby_version = '>= 1.9.2'
15
19
 
16
- s.add_dependency "savon", "~> 2"
17
- s.add_dependency "mail", "~> 2"
20
+ s.add_dependency 'mail', '~> 2'
21
+ s.add_dependency 'savon', '~> 2'
18
22
 
19
- s.add_development_dependency "rake"
20
- s.add_development_dependency "pry"
21
- s.add_development_dependency "rspec"
22
- s.add_development_dependency "autotest"
23
+ s.add_development_dependency 'autotest'
24
+ s.add_development_dependency 'pry'
25
+ s.add_development_dependency 'rake'
26
+ s.add_development_dependency 'rspec'
23
27
 
24
28
  s.files = `git ls-files`.split("\n")
25
- s.require_path = "lib"
29
+ s.require_path = 'lib'
30
+ s.metadata['rubygems_mfa_required'] = 'true'
26
31
  end
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:wsdl="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><ns1:TransactionID soapenv:actor="" soapenv:mustUnderstand="1" xsi:type="xsd:string" xmlns:ns1="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2">2011012713535811111111111</ns1:TransactionID></soapenv:Header><soapenv:Body><SubmitReq xmlns="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2"><MM7Version>5.3.0</MM7Version><SenderIdentification><VASPID>messaging</VASPID><VASID>ADM</VASID><SenderAddress><ShortCode>1111</ShortCode></SenderAddress></SenderIdentification><Recipients><To><Number>11111111111</Number></To></Recipients><ServiceCode>1</ServiceCode><MessageClass>Personal</MessageClass><ExpiryDate>2011-01-28T13:53:58Z</ExpiryDate><DeliveryReport>false</DeliveryReport><ReadReply>false</ReadReply><Priority>Normal</Priority><Subject>Test MMS via Savon</Subject><ChargedParty>Sender</ChargedParty><Content href="cid:attachment_1" allowAdaptations="true"/></SubmitReq></soapenv:Body></soapenv:Envelope>
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:wsdl="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><ns1:TransactionID soapenv:actor="" soapenv:mustUnderstand="1" xsi:type="xsd:string" xmlns:ns1="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2">2011012713535811111111111</ns1:TransactionID></soapenv:Header><soapenv:Body><SubmitReq xmlns="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2"><MM7Version>5.3.0</MM7Version><SenderIdentification><VASPID>messaging</VASPID><VASID>ADM</VASID><SenderAddress><ShortCode>1111</ShortCode></SenderAddress></SenderIdentification><Recipients><To><Number>11111111111</Number></To></Recipients><ServiceCode>1</ServiceCode><MessageClass>Personal</MessageClass><ExpiryDate>2011-01-28T13:53:58Z</ExpiryDate><DeliveryReport>false</DeliveryReport><ReadReply>false</ReadReply><Priority>Normal</Priority><Subject>Test MMS via Savon</Subject><ChargedParty>Sender</ChargedParty><Content href="cid:attachment_1" allowAdaptations="true"/></SubmitReq></soapenv:Body></soapenv:Envelope>
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'savon-multipart'
4
+
5
+ RSpec.describe Savon::Multipart::Response do
6
+ let(:multipart_content_type) do
7
+ 'multipart/related; boundary="--==_mimepart_4d416ae62fd32_201a8043814c4724"; ' \
8
+ 'charset=UTF-8; type="text/xml"'
9
+ end
10
+ let(:header) { { 'Content-Type' => multipart_content_type } }
11
+ let(:body) { File.read(path) }
12
+ let(:response) { soap_response(headers: header, body: body) }
13
+
14
+ def soap_response(options = {})
15
+ defaults = { code: 200, headers: {}, body: '' }
16
+ response = defaults.merge options
17
+ globals = {
18
+ multipart: true,
19
+ raise_errors: true,
20
+ convert_response_tags_to: ->(tag) { Savon::StringUtils.snakecase(tag).to_sym }
21
+ }
22
+ http = HTTPI::Response.new(response[:code], response[:headers], response[:body])
23
+
24
+ Savon::Multipart::Response.new(http, globals, {})
25
+ end
26
+
27
+ context 'multipart' do
28
+ let(:path) { File.expand_path('../../fixtures/response/multipart.txt', __dir__) }
29
+
30
+ it 'parses the SOAP envelope from the first MIME part' do
31
+ expected = File.expand_path('../../fixtures/response/multipart_expected.xml', __dir__)
32
+ expect(response.xml.strip).to eq(File.read(expected))
33
+ end
34
+
35
+ it 'exposes the top-level MIME parts' do
36
+ expect(response.parts.length).to eq(2)
37
+ end
38
+
39
+ it 'exposes nested MIME parts' do
40
+ expect(response.parts[1].parts.length).to eq(3)
41
+ end
42
+
43
+ it 'decodes attachment bodies' do
44
+ expect(response.parts[1].parts[2].body.decoded.strip).to eq('This is a test message from Github')
45
+ end
46
+
47
+ it 'returns the attachments' do
48
+ expect(response.attachments.size).to eq(1)
49
+ end
50
+
51
+ it 'only parses the SOAP body once' do
52
+ # Referencing `response` here builds it (parsing the body a first time)
53
+ # before the spy is installed, so no further parses may happen below.
54
+ allow(response).to receive(:parse_body).and_call_original
55
+
56
+ 5.times do
57
+ response.attachments
58
+ end
59
+
60
+ expect(response).not_to have_received(:parse_body)
61
+ end
62
+ end
63
+
64
+ context 'simple multipart' do
65
+ let(:path) { File.expand_path('../../fixtures/response/simple_multipart.txt', __dir__) }
66
+
67
+ it 'returns a String from the #xml method' do
68
+ expect(response.xml.class).to eq(String)
69
+ end
70
+
71
+ it 'returns a Hash from the #body method' do
72
+ expect(response.body).to eq({ submit_req: true })
73
+ end
74
+ end
75
+
76
+ context 'simple xop' do
77
+ let(:path) { File.expand_path('../../fixtures/response/simple_xop.txt', __dir__) }
78
+
79
+ it 'returns a String from the #xml method' do
80
+ expect(response.xml.class).to eq(String)
81
+ end
82
+
83
+ it 'returns a Hash from the #body method, include xop data' do
84
+ expect(response.body).to eq({ binary_data: Base64.encode64('BinaryDataGoesHere').chomp })
85
+ end
86
+ end
87
+
88
+ describe 'a multipart response with case sensitive headers' do
89
+ let(:multipart_content_type) do
90
+ 'MuLtIpArT/rElAtEd; boundary="--==_mimepart_4d416ae62fd32_201a8043814c4724"; ' \
91
+ 'charset=UTF-8; type="text/xml"'
92
+ end
93
+ let(:path) { File.expand_path('../../fixtures/response/simple_multipart.txt', __dir__) }
94
+
95
+ it 'does not care about upper or lowercase values for ContentType' do
96
+ expect(response.body).to eq({ submit_req: true })
97
+ end
98
+ end
99
+
100
+ context 'not multipart' do
101
+ let(:path) { File.expand_path('../../fixtures/response/not_multipart.txt', __dir__) }
102
+ let(:header) { { 'Content-Type' => 'text/html; charset=utf-8' } }
103
+
104
+ it 'parses soap messages without attachments too' do
105
+ expected = File.expand_path('../../fixtures/response/not_multipart_expected.xml', __dir__)
106
+ expect(response.xml.chomp).to eq(File.read(expected))
107
+ end
108
+
109
+ it 'has no parts' do
110
+ expect(response.parts.size).to eq(0)
111
+ end
112
+
113
+ it 'has no attachments' do
114
+ expect(response.attachments.size).to eq(0)
115
+ end
116
+ end
117
+
118
+ context 'soap errors' do
119
+ let(:path) { File.expand_path('../../fixtures/response/soap_fault.txt', __dir__) }
120
+
121
+ it 'handles them correctly' do
122
+ expect { response }.to raise_error(Savon::SOAPFault, /The service cannot be found/)
123
+ end
124
+ end
125
+ end
metadata CHANGED
@@ -1,18 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: savon-multipart
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Westin
8
8
  - Daniel Harrington
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2022-01-20 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: savon
14
+ name: mail
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
17
  - - "~>"
@@ -26,7 +25,7 @@ dependencies:
26
25
  - !ruby/object:Gem::Version
27
26
  version: '2'
28
27
  - !ruby/object:Gem::Dependency
29
- name: mail
28
+ name: savon
30
29
  requirement: !ruby/object:Gem::Requirement
31
30
  requirements:
32
31
  - - "~>"
@@ -40,7 +39,7 @@ dependencies:
40
39
  - !ruby/object:Gem::Version
41
40
  version: '2'
42
41
  - !ruby/object:Gem::Dependency
43
- name: rake
42
+ name: autotest
44
43
  requirement: !ruby/object:Gem::Requirement
45
44
  requirements:
46
45
  - - ">="
@@ -68,7 +67,7 @@ dependencies:
68
67
  - !ruby/object:Gem::Version
69
68
  version: '0'
70
69
  - !ruby/object:Gem::Dependency
71
- name: rspec
70
+ name: rake
72
71
  requirement: !ruby/object:Gem::Requirement
73
72
  requirements:
74
73
  - - ">="
@@ -82,7 +81,7 @@ dependencies:
82
81
  - !ruby/object:Gem::Version
83
82
  version: '0'
84
83
  - !ruby/object:Gem::Dependency
85
- name: autotest
84
+ name: rspec
86
85
  requirement: !ruby/object:Gem::Requirement
87
86
  requirements:
88
87
  - - ">="
@@ -103,28 +102,38 @@ executables: []
103
102
  extensions: []
104
103
  extra_rdoc_files: []
105
104
  files:
105
+ - ".github/ISSUE_TEMPLATE/Bug_report.md"
106
+ - ".github/ISSUE_TEMPLATE/Feature_request.md"
107
+ - ".github/PULL_REQUEST_TEMPLATE.md"
108
+ - ".github/dependabot.yml"
106
109
  - ".github/workflows/ci.yml"
110
+ - ".github/workflows/gem_push.yml"
107
111
  - ".gitignore"
108
112
  - ".rspec"
113
+ - ".rubocop.yml"
114
+ - ".rubocop_todo.yml"
109
115
  - CHANGELOG.md
110
116
  - Gemfile
111
117
  - LICENSE
112
118
  - README.md
119
+ - RELEASING.md
113
120
  - Rakefile
114
121
  - lib/savon-multipart.rb
115
122
  - lib/savon/multipart/response.rb
116
123
  - lib/savon/multipart/version.rb
117
124
  - savon-multipart.gemspec
118
125
  - spec/fixtures/response/multipart.txt
126
+ - spec/fixtures/response/multipart_expected.xml
119
127
  - spec/fixtures/response/not_multipart.txt
128
+ - spec/fixtures/response/not_multipart_expected.xml
120
129
  - spec/fixtures/response/simple_multipart.txt
121
130
  - spec/fixtures/response/simple_xop.txt
122
131
  - spec/fixtures/response/soap_fault.txt
123
- - spec/savon/soap/response_spec.rb
132
+ - spec/savon/multipart/response_spec.rb
124
133
  homepage: http://savonrb.com
125
134
  licenses: []
126
- metadata: {}
127
- post_install_message:
135
+ metadata:
136
+ rubygems_mfa_required: 'true'
128
137
  rdoc_options: []
129
138
  require_paths:
130
139
  - lib
@@ -139,8 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
148
  - !ruby/object:Gem::Version
140
149
  version: '0'
141
150
  requirements: []
142
- rubygems_version: 3.2.7
143
- signing_key:
151
+ rubygems_version: 3.6.9
144
152
  specification_version: 4
145
153
  summary: Heavy metal Ruby SOAP client with multipart support
146
154
  test_files: []
@@ -1,96 +0,0 @@
1
- require 'savon-multipart'
2
-
3
- RSpec.describe Savon::Multipart::Response do
4
-
5
- let(:header) {{ "Content-Type" => 'multipart/related; boundary="--==_mimepart_4d416ae62fd32_201a8043814c4724"; charset=UTF-8; type="text/xml"' }}
6
- let(:body) { File.read(path) }
7
- let(:response) { soap_response(:headers => header, :body => body) }
8
-
9
- def soap_response(options = {})
10
- defaults = { :code => 200, :headers => {}, :body => "" }
11
- response = defaults.merge options
12
- globals = {
13
- :multipart => true,
14
- :raise_errors => true,
15
- :convert_response_tags_to => lambda { |tag| tag.snakecase.to_sym}
16
- }
17
- http = HTTPI::Response.new(response[:code], response[:headers], response[:body])
18
-
19
- Savon::Multipart::Response.new(http, globals, {})
20
- end
21
-
22
- context "multipart" do
23
- let(:path) { File.expand_path("../../../fixtures/response/multipart.txt", __FILE__) }
24
-
25
- it "parses without Exception" do
26
- expect(response.xml.strip).to eq('<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:wsdl="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><ns1:TransactionID soapenv:actor="" soapenv:mustUnderstand="1" xsi:type="xsd:string" xmlns:ns1="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2">2011012713535811111111111</ns1:TransactionID></soapenv:Header><soapenv:Body><SubmitReq xmlns="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2"><MM7Version>5.3.0</MM7Version><SenderIdentification><VASPID>messaging</VASPID><VASID>ADM</VASID><SenderAddress><ShortCode>1111</ShortCode></SenderAddress></SenderIdentification><Recipients><To><Number>11111111111</Number></To></Recipients><ServiceCode>1</ServiceCode><MessageClass>Personal</MessageClass><ExpiryDate>2011-01-28T13:53:58Z</ExpiryDate><DeliveryReport>false</DeliveryReport><ReadReply>false</ReadReply><Priority>Normal</Priority><Subject>Test MMS via Savon</Subject><ChargedParty>Sender</ChargedParty><Content href="cid:attachment_1" allowAdaptations="true"/></SubmitReq></soapenv:Body></soapenv:Envelope>')
27
- expect(response.parts.length).to eq(2)
28
- expect(response.parts[1].parts.length).to eq(3)
29
- expect(response.parts[1].parts[2].body.decoded.strip).to eq("This is a test message from Github")
30
- end
31
-
32
- it "returns the attachments" do
33
- expect(response.attachments.size).to eq(1)
34
- end
35
-
36
- it "only parses the SOAP body once" do
37
- Mail::Part.stub(:new).and_return(double(Mail::Part).as_null_object)
38
- expect(Mail::Part).to receive(:new).exactly(1).times
39
- 5.times { response.attachments }
40
- end
41
- end
42
-
43
- context "simple multipart" do
44
- let(:path) { File.expand_path('../../../fixtures/response/simple_multipart.txt', __FILE__) }
45
-
46
- it "returns a String from the #xml method" do
47
- expect(response.xml.class).to eq(String)
48
- end
49
-
50
- it "returns a Hash from the #body method" do
51
- expect(response.body.class).to eq(Hash)
52
- expect(response.body).to eq({:submit_req => true})
53
- end
54
- end
55
-
56
- context "simple xop" do
57
- let(:path) { File.expand_path('../../../fixtures/response/simple_xop.txt', __FILE__) }
58
-
59
- it "returns a String from the #xml method" do
60
- expect(response.xml.class).to eq(String)
61
- end
62
-
63
- it "returns a Hash from the #body method, include xop data" do
64
- expect(response.body.class).to eq(Hash)
65
- expect(response.body).to eq({:binary_data => Base64.encode64('BinaryDataGoesHere').chomp})
66
- end
67
- end
68
-
69
- describe "a multipart response with case sensitive headers" do
70
- let(:header) {{ "Content-Type" => 'MuLtIpArT/rElAtEd; boundary="--==_mimepart_4d416ae62fd32_201a8043814c4724"; charset=UTF-8; type="text/xml"' }}
71
- let(:path) { File.expand_path('../../../fixtures/response/simple_multipart.txt', __FILE__) }
72
-
73
- it "does not care about upper or lowercase values for ContentType" do
74
- expect(response.body).to eq({:submit_req => true})
75
- end
76
- end
77
-
78
- context "not multipart" do
79
- let(:path) { File.expand_path('../../../fixtures/response/not_multipart.txt', __FILE__) }
80
- let(:header) { { 'Content-Type' => 'text/html; charset=utf-8'} }
81
-
82
- it "parses soap messages without attachments too" do
83
- expect(response.xml.chomp).to eq('<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:wsdl="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><ns1:TransactionID soapenv:actor="" soapenv:mustUnderstand="1" xsi:type="xsd:string" xmlns:ns1="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2">2011012713535811111111111</ns1:TransactionID></soapenv:Header><soapenv:Body><SubmitReq xmlns="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2"><MM7Version>5.3.0</MM7Version><SenderIdentification><VASPID>messaging</VASPID><VASID>ADM</VASID><SenderAddress><ShortCode>1111</ShortCode></SenderAddress></SenderIdentification><Recipients><To><Number>11111111111</Number></To></Recipients><ServiceCode>1</ServiceCode><MessageClass>Personal</MessageClass><ExpiryDate>2011-01-28T13:53:58Z</ExpiryDate><DeliveryReport>false</DeliveryReport><ReadReply>false</ReadReply><Priority>Normal</Priority><Subject>Test MMS via Savon</Subject><ChargedParty>Sender</ChargedParty><Content href="cid:attachment_1" allowAdaptations="true"/></SubmitReq></soapenv:Body></soapenv:Envelope>')
84
- expect(response.parts.size).to eq(0)
85
- expect(response.attachments.size).to eq(0)
86
- end
87
- end
88
-
89
- context "soap errors" do
90
- let(:path) { File.expand_path('../../../fixtures/response/soap_fault.txt', __FILE__) }
91
-
92
- it "handles them correctly" do
93
- expect { response }.to raise_error(Savon::SOAPFault, /The service cannot be found/)
94
- end
95
- end
96
- end