├── .buildkite ├── pipeline.yml └── publish-pod.sh ├── .bundle └── config ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── AppledocSettings.plist ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── Package.swift ├── README.md ├── Support ├── Info-Tests.plist └── Info.plist ├── WPXMLRPC ├── WPBase64Utils.h ├── WPBase64Utils.m ├── WPStringUtils.h ├── WPStringUtils.m ├── WPXMLRPC.h ├── WPXMLRPCDataCleaner.h ├── WPXMLRPCDataCleaner.m ├── WPXMLRPCDecoder.h ├── WPXMLRPCDecoder.m ├── WPXMLRPCDecoderDelegate.h ├── WPXMLRPCDecoderDelegate.m ├── WPXMLRPCEncoder.h ├── WPXMLRPCEncoder.m └── include │ ├── WPXMLRPC.h │ ├── WPXMLRPCDecoder.h │ └── WPXMLRPCEncoder.h ├── WPXMLRPCTest ├── Test Data │ ├── AlternativeDateFormatsTestCase.xml │ ├── DefaultTypeTestCase.xml │ ├── EmptyBooleanTestCase.xml │ ├── EmptyDataTestCase.xml │ ├── EmptyDoubleTestCase.xml │ ├── EmptyIntegerTestCase.xml │ ├── EmptyStringTestCase.xml │ ├── EscapingTestCase.xml │ ├── IncompleteXmlTest.xml │ ├── InvalidFault.xml │ ├── NoXmlResponseTestCase.xml │ ├── OverflowInteger32TestCase.xml │ ├── RequestTestCase.xml │ ├── SimpleArrayTestCase.xml │ ├── SimpleStructTestCase.xml │ ├── TestCases.plist │ ├── TestImage.base64 │ ├── TestImage.bin │ ├── entities.xml │ └── entitiesDecoded.xml └── Tests │ ├── Helper.h │ ├── Helper.m │ ├── WPBase64UtilsTest.m │ ├── WPXMLRPCDataCleanerTest.m │ ├── WPXMLRPCDecoderTest.m │ ├── WPXMLRPCEncoderTest.m │ └── WPXMLRPCEntitiesTest.m ├── fastlane └── Fastfile └── wpxmlrpc.podspec /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | # Nodes with values to reuse in the pipeline. 2 | common_params: 3 | plugins: &common_plugins 4 | - automattic/a8c-ci-toolkit#2.17.0 5 | env: &common_env 6 | IMAGE_ID: xcode-15.2 7 | 8 | # This is the default pipeline – it will build and test the pod 9 | steps: 10 | ######################## 11 | # Validate Swift Package 12 | ######################## 13 | - label: "🔬 Validate Swift Package" 14 | key: "test" 15 | command: "validate_swift_package" 16 | env: *common_env 17 | plugins: *common_plugins 18 | 19 | ################# 20 | # Validate Podspec 21 | ################# 22 | - label: "🔬 Validate Podspec" 23 | key: "validate" 24 | command: "validate_podspec" 25 | env: *common_env 26 | plugins: *common_plugins 27 | 28 | ################# 29 | # Lint 30 | ################# 31 | - label: "🧹 Lint" 32 | key: "lint" 33 | command: "lint_pod" 34 | env: *common_env 35 | plugins: *common_plugins 36 | 37 | ################# 38 | # Publish the Podspec (if we're building a tag) 39 | ################# 40 | - label: "⬆️ Publish Podspec" 41 | key: "publish" 42 | command: .buildkite/publish-pod.sh 43 | env: *common_env 44 | plugins: *common_plugins 45 | depends_on: 46 | - "test" 47 | - "validate" 48 | - "lint" 49 | if: build.tag != null 50 | agents: 51 | queue: "mac" 52 | -------------------------------------------------------------------------------- /.buildkite/publish-pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | PODSPEC_PATH="wpxmlrpc.podspec" 4 | SLACK_WEBHOOK=$PODS_SLACK_WEBHOOK 5 | 6 | echo "--- :rubygems: Setting up Gems" 7 | install_gems 8 | 9 | echo "--- :cocoapods: Publishing Pod to CocoaPods CDN" 10 | publish_pod $PODSPEC_PATH 11 | 12 | echo "--- :slack: Notifying Slack" 13 | slack_notify_pod_published $PODSPEC_PATH $SLACK_WEBHOOK 14 | -------------------------------------------------------------------------------- /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 | - [ ] I have considered if this change warrants release notes and have added them to the appropriate section in the `CHANGELOG.md` if necessary. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | .DS_Store 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | WPXMLRPCTest/Pods/* 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/README.md 67 | 68 | /vendor 69 | 70 | .swiftpm 71 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Opt in to new cops by default 2 | AllCops: 3 | NewCops: enable 4 | 5 | # Allow the Podspec filename to match the project 6 | Naming/FileName: 7 | Exclude: 8 | - 'wpxmlrpc.podspec' 9 | 10 | # Override the maximum line length 11 | Layout/LineLength: 12 | Max: 160 13 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /AppledocSettings.plist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | --project-name 7 | WPXMLRPC 8 | --project-company 9 | WordPress 10 | --company-id 11 | org.wordpress 12 | --create-docset 13 | 14 | --install-docset 15 | 16 | --verbose 17 | 4 18 | --logformat 19 | 1 20 | --index-desc 21 | README.md 22 | --input 23 | 24 | WPXMLRPC/WPXMLRPCEncoder.h 25 | WPXMLRPC/WPXMLRPCDecoder.h 26 | 27 | --output 28 | docs 29 | 30 | 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format of this document is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | 32 | 33 | ## Unreleased 34 | 35 | ### Breaking Changes 36 | 37 | - Remove tvOS support [#77] 38 | 39 | ### New Features 40 | 41 | _None._ 42 | 43 | ### Bug Fixes 44 | 45 | _None._ 46 | 47 | ### Internal Changes 48 | 49 | _None._ 50 | 51 | ## [0.10.0](https://github.com/wordpress-mobile/wpxmlrpc/releases/tag/0.10.0) 52 | 53 | ### New Features 54 | 55 | - Add Swift Package Manager support [#66] 56 | 57 | ### Internal Changes 58 | 59 | - Added this changelog file [#67] 60 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'cocoapods', '~> 1.10' 6 | gem 'cocoapods-check', '~> 1.1' 7 | gem 'fastlane', '~> 2.189' 8 | gem 'rubocop', '~> 1.18' 9 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (7.1.3) 7 | base64 8 | bigdecimal 9 | concurrent-ruby (~> 1.0, >= 1.0.2) 10 | connection_pool (>= 2.2.5) 11 | drb 12 | i18n (>= 1.6, < 2) 13 | minitest (>= 5.1) 14 | mutex_m 15 | tzinfo (~> 2.0) 16 | addressable (2.8.6) 17 | public_suffix (>= 2.0.2, < 6.0) 18 | algoliasearch (1.27.5) 19 | httpclient (~> 2.8, >= 2.8.3) 20 | json (>= 1.5.1) 21 | artifactory (3.0.15) 22 | ast (2.4.2) 23 | atomos (0.1.3) 24 | aws-eventstream (1.3.0) 25 | aws-partitions (1.884.0) 26 | aws-sdk-core (3.191.0) 27 | aws-eventstream (~> 1, >= 1.3.0) 28 | aws-partitions (~> 1, >= 1.651.0) 29 | aws-sigv4 (~> 1.8) 30 | jmespath (~> 1, >= 1.6.1) 31 | aws-sdk-kms (1.77.0) 32 | aws-sdk-core (~> 3, >= 3.191.0) 33 | aws-sigv4 (~> 1.1) 34 | aws-sdk-s3 (1.143.0) 35 | aws-sdk-core (~> 3, >= 3.191.0) 36 | aws-sdk-kms (~> 1) 37 | aws-sigv4 (~> 1.8) 38 | aws-sigv4 (1.8.0) 39 | aws-eventstream (~> 1, >= 1.0.2) 40 | babosa (1.0.4) 41 | base64 (0.2.0) 42 | bigdecimal (3.1.6) 43 | claide (1.1.0) 44 | cocoapods (1.15.0) 45 | addressable (~> 2.8) 46 | claide (>= 1.0.2, < 2.0) 47 | cocoapods-core (= 1.15.0) 48 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 49 | cocoapods-downloader (>= 2.1, < 3.0) 50 | cocoapods-plugins (>= 1.0.0, < 2.0) 51 | cocoapods-search (>= 1.0.0, < 2.0) 52 | cocoapods-trunk (>= 1.6.0, < 2.0) 53 | cocoapods-try (>= 1.1.0, < 2.0) 54 | colored2 (~> 3.1) 55 | escape (~> 0.0.4) 56 | fourflusher (>= 2.3.0, < 3.0) 57 | gh_inspector (~> 1.0) 58 | molinillo (~> 0.8.0) 59 | nap (~> 1.0) 60 | ruby-macho (>= 2.3.0, < 3.0) 61 | xcodeproj (>= 1.23.0, < 2.0) 62 | cocoapods-check (1.1.0) 63 | cocoapods (~> 1.0) 64 | cocoapods-core (1.15.0) 65 | activesupport (>= 5.0, < 8) 66 | addressable (~> 2.8) 67 | algoliasearch (~> 1.0) 68 | concurrent-ruby (~> 1.1) 69 | fuzzy_match (~> 2.0.4) 70 | nap (~> 1.0) 71 | netrc (~> 0.11) 72 | public_suffix (~> 4.0) 73 | typhoeus (~> 1.0) 74 | cocoapods-deintegrate (1.0.5) 75 | cocoapods-downloader (2.1) 76 | cocoapods-plugins (1.0.0) 77 | nap 78 | cocoapods-search (1.0.1) 79 | cocoapods-trunk (1.6.0) 80 | nap (>= 0.8, < 2.0) 81 | netrc (~> 0.11) 82 | cocoapods-try (1.2.0) 83 | colored (1.2) 84 | colored2 (3.1.2) 85 | commander (4.6.0) 86 | highline (~> 2.0.0) 87 | concurrent-ruby (1.2.3) 88 | connection_pool (2.4.1) 89 | declarative (0.0.20) 90 | digest-crc (0.6.5) 91 | rake (>= 12.0.0, < 14.0.0) 92 | domain_name (0.6.20240107) 93 | dotenv (2.8.1) 94 | drb (2.2.0) 95 | ruby2_keywords 96 | emoji_regex (3.2.3) 97 | escape (0.0.4) 98 | ethon (0.16.0) 99 | ffi (>= 1.15.0) 100 | excon (0.109.0) 101 | faraday (1.10.3) 102 | faraday-em_http (~> 1.0) 103 | faraday-em_synchrony (~> 1.0) 104 | faraday-excon (~> 1.1) 105 | faraday-httpclient (~> 1.0) 106 | faraday-multipart (~> 1.0) 107 | faraday-net_http (~> 1.0) 108 | faraday-net_http_persistent (~> 1.0) 109 | faraday-patron (~> 1.0) 110 | faraday-rack (~> 1.0) 111 | faraday-retry (~> 1.0) 112 | ruby2_keywords (>= 0.0.4) 113 | faraday-cookie_jar (0.0.7) 114 | faraday (>= 0.8.0) 115 | http-cookie (~> 1.0.0) 116 | faraday-em_http (1.0.0) 117 | faraday-em_synchrony (1.0.0) 118 | faraday-excon (1.1.0) 119 | faraday-httpclient (1.0.1) 120 | faraday-multipart (1.0.4) 121 | multipart-post (~> 2) 122 | faraday-net_http (1.0.1) 123 | faraday-net_http_persistent (1.2.0) 124 | faraday-patron (1.0.0) 125 | faraday-rack (1.0.0) 126 | faraday-retry (1.0.3) 127 | faraday_middleware (1.2.0) 128 | faraday (~> 1.0) 129 | fastimage (2.3.0) 130 | fastlane (2.219.0) 131 | CFPropertyList (>= 2.3, < 4.0.0) 132 | addressable (>= 2.8, < 3.0.0) 133 | artifactory (~> 3.0) 134 | aws-sdk-s3 (~> 1.0) 135 | babosa (>= 1.0.3, < 2.0.0) 136 | bundler (>= 1.12.0, < 3.0.0) 137 | colored 138 | commander (~> 4.6) 139 | dotenv (>= 2.1.1, < 3.0.0) 140 | emoji_regex (>= 0.1, < 4.0) 141 | excon (>= 0.71.0, < 1.0.0) 142 | faraday (~> 1.0) 143 | faraday-cookie_jar (~> 0.0.6) 144 | faraday_middleware (~> 1.0) 145 | fastimage (>= 2.1.0, < 3.0.0) 146 | gh_inspector (>= 1.1.2, < 2.0.0) 147 | google-apis-androidpublisher_v3 (~> 0.3) 148 | google-apis-playcustomapp_v1 (~> 0.1) 149 | google-cloud-env (>= 1.6.0, < 2.0.0) 150 | google-cloud-storage (~> 1.31) 151 | highline (~> 2.0) 152 | http-cookie (~> 1.0.5) 153 | json (< 3.0.0) 154 | jwt (>= 2.1.0, < 3) 155 | mini_magick (>= 4.9.4, < 5.0.0) 156 | multipart-post (>= 2.0.0, < 3.0.0) 157 | naturally (~> 2.2) 158 | optparse (>= 0.1.1) 159 | plist (>= 3.1.0, < 4.0.0) 160 | rubyzip (>= 2.0.0, < 3.0.0) 161 | security (= 0.1.3) 162 | simctl (~> 1.6.3) 163 | terminal-notifier (>= 2.0.0, < 3.0.0) 164 | terminal-table (~> 3) 165 | tty-screen (>= 0.6.3, < 1.0.0) 166 | tty-spinner (>= 0.8.0, < 1.0.0) 167 | word_wrap (~> 1.0.0) 168 | xcodeproj (>= 1.13.0, < 2.0.0) 169 | xcpretty (~> 0.3.0) 170 | xcpretty-travis-formatter (>= 0.0.3) 171 | ffi (1.16.3) 172 | fourflusher (2.3.1) 173 | fuzzy_match (2.0.4) 174 | gh_inspector (1.1.3) 175 | google-apis-androidpublisher_v3 (0.54.0) 176 | google-apis-core (>= 0.11.0, < 2.a) 177 | google-apis-core (0.11.3) 178 | addressable (~> 2.5, >= 2.5.1) 179 | googleauth (>= 0.16.2, < 2.a) 180 | httpclient (>= 2.8.1, < 3.a) 181 | mini_mime (~> 1.0) 182 | representable (~> 3.0) 183 | retriable (>= 2.0, < 4.a) 184 | rexml 185 | google-apis-iamcredentials_v1 (0.17.0) 186 | google-apis-core (>= 0.11.0, < 2.a) 187 | google-apis-playcustomapp_v1 (0.13.0) 188 | google-apis-core (>= 0.11.0, < 2.a) 189 | google-apis-storage_v1 (0.31.0) 190 | google-apis-core (>= 0.11.0, < 2.a) 191 | google-cloud-core (1.6.1) 192 | google-cloud-env (>= 1.0, < 3.a) 193 | google-cloud-errors (~> 1.0) 194 | google-cloud-env (1.6.0) 195 | faraday (>= 0.17.3, < 3.0) 196 | google-cloud-errors (1.3.1) 197 | google-cloud-storage (1.47.0) 198 | addressable (~> 2.8) 199 | digest-crc (~> 0.4) 200 | google-apis-iamcredentials_v1 (~> 0.1) 201 | google-apis-storage_v1 (~> 0.31.0) 202 | google-cloud-core (~> 1.6) 203 | googleauth (>= 0.16.2, < 2.a) 204 | mini_mime (~> 1.0) 205 | googleauth (1.8.1) 206 | faraday (>= 0.17.3, < 3.a) 207 | jwt (>= 1.4, < 3.0) 208 | multi_json (~> 1.11) 209 | os (>= 0.9, < 2.0) 210 | signet (>= 0.16, < 2.a) 211 | highline (2.0.3) 212 | http-cookie (1.0.5) 213 | domain_name (~> 0.5) 214 | httpclient (2.8.3) 215 | i18n (1.14.1) 216 | concurrent-ruby (~> 1.0) 217 | jmespath (1.6.2) 218 | json (2.7.1) 219 | jwt (2.7.1) 220 | language_server-protocol (3.17.0.3) 221 | mini_magick (4.12.0) 222 | mini_mime (1.1.5) 223 | minitest (5.21.2) 224 | molinillo (0.8.0) 225 | multi_json (1.15.0) 226 | multipart-post (2.3.0) 227 | mutex_m (0.2.0) 228 | nanaimo (0.3.0) 229 | nap (1.1.0) 230 | naturally (2.2.1) 231 | netrc (0.11.0) 232 | optparse (0.4.0) 233 | os (1.1.4) 234 | parallel (1.24.0) 235 | parser (3.3.0.5) 236 | ast (~> 2.4.1) 237 | racc 238 | plist (3.7.1) 239 | public_suffix (4.0.7) 240 | racc (1.7.3) 241 | rainbow (3.1.1) 242 | rake (13.1.0) 243 | regexp_parser (2.9.0) 244 | representable (3.2.0) 245 | declarative (< 0.1.0) 246 | trailblazer-option (>= 0.1.1, < 0.2.0) 247 | uber (< 0.2.0) 248 | retriable (3.1.2) 249 | rexml (3.2.6) 250 | rouge (2.0.7) 251 | rubocop (1.60.2) 252 | json (~> 2.3) 253 | language_server-protocol (>= 3.17.0) 254 | parallel (~> 1.10) 255 | parser (>= 3.3.0.2) 256 | rainbow (>= 2.2.2, < 4.0) 257 | regexp_parser (>= 1.8, < 3.0) 258 | rexml (>= 3.2.5, < 4.0) 259 | rubocop-ast (>= 1.30.0, < 2.0) 260 | ruby-progressbar (~> 1.7) 261 | unicode-display_width (>= 2.4.0, < 3.0) 262 | rubocop-ast (1.30.0) 263 | parser (>= 3.2.1.0) 264 | ruby-macho (2.5.1) 265 | ruby-progressbar (1.13.0) 266 | ruby2_keywords (0.0.5) 267 | rubyzip (2.3.2) 268 | security (0.1.3) 269 | signet (0.18.0) 270 | addressable (~> 2.8) 271 | faraday (>= 0.17.5, < 3.a) 272 | jwt (>= 1.5, < 3.0) 273 | multi_json (~> 1.10) 274 | simctl (1.6.10) 275 | CFPropertyList 276 | naturally 277 | terminal-notifier (2.0.0) 278 | terminal-table (3.0.2) 279 | unicode-display_width (>= 1.1.1, < 3) 280 | trailblazer-option (0.1.2) 281 | tty-cursor (0.7.1) 282 | tty-screen (0.8.2) 283 | tty-spinner (0.9.3) 284 | tty-cursor (~> 0.7) 285 | typhoeus (1.4.1) 286 | ethon (>= 0.9.0) 287 | tzinfo (2.0.6) 288 | concurrent-ruby (~> 1.0) 289 | uber (0.1.0) 290 | unicode-display_width (2.5.0) 291 | word_wrap (1.0.0) 292 | xcodeproj (1.24.0) 293 | CFPropertyList (>= 2.3.3, < 4.0) 294 | atomos (~> 0.1.3) 295 | claide (>= 1.0.2, < 2.0) 296 | colored2 (~> 3.1) 297 | nanaimo (~> 0.3.0) 298 | rexml (~> 3.2.4) 299 | xcpretty (0.3.0) 300 | rouge (~> 2.0.7) 301 | xcpretty-travis-formatter (1.0.1) 302 | xcpretty (~> 0.2, >= 0.0.7) 303 | 304 | PLATFORMS 305 | ruby 306 | 307 | DEPENDENCIES 308 | cocoapods (~> 1.10) 309 | cocoapods-check (~> 1.1) 310 | fastlane (~> 2.189) 311 | rubocop (~> 1.18) 312 | 313 | BUNDLED WITH 314 | 2.4.10 315 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ## WordPress XML-RPC library is distributed under the MIT License: 4 | 5 | Copyright (c) 2013 WordPress 6 | Copyright (c) 2012 Eric Czarny 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 | of the Software, and to permit persons to whom the Software is furnished to do 13 | so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "wpxmlrpc", 7 | platforms: [.macOS(.v10_13), .iOS(.v11), .tvOS(.v11)], 8 | products: [ 9 | .library(name: "wpxmlrpc", targets: ["wpxmlrpc"]) 10 | ], 11 | dependencies: [], 12 | targets: [ 13 | .target( 14 | name: "wpxmlrpc", 15 | path: "WPXMLRPC", 16 | linkerSettings: [.linkedLibrary("iconv")] 17 | ), 18 | .testTarget( 19 | name: "Tests", 20 | dependencies: [.target(name: "wpxmlrpc")], 21 | path: "WPXMLRPCTest", 22 | resources: [.process("Test Data")], 23 | cSettings: [.headerSearchPath("../WPXMLRPC")] 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress XML-RPC Framework 2 | 3 | The WordPress XML-RPC library is a lightweight XML-RPC client for iOS 4 | and OS X. 5 | 6 | It's based on Eric Czarny's Cocoa XML-RPC Framework, but without all the 7 | networking code, and a few additions of our own. 8 | 9 | # Installation 10 | 11 | WordPress XML-RPC uses [CocoaPods](http://cocoapods.org/) for easy 12 | dependency management. 13 | 14 | Just add this to your Podfile and run `pod install`: 15 | 16 | pod 'wpxmlrpc' 17 | 18 | Another option, if you don't use CocoaPods, is to copy the `WPXMLRPC` 19 | folder to your project. 20 | 21 | # Usage 22 | 23 | WordPress XML-RPC only provides classes to encode and decode XML-RPC. You are free to use your favorite networking library. 24 | 25 | ## Building a XML-RPC request 26 | 27 | NSURL *URL = [NSURL URLWithString:@"http://example.com/xmlrpc"]; 28 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; 29 | [request setHTTPMethod:@"POST"]; 30 | 31 | WPXMLRPCEncoder *encoder = [[WPXMLRPCEncoder alloc] initWithMethod:@"demo.addTwoNumbers" andParameters:@[@1, @2]]; 32 | [request setHTTPBody:[encoder dataEncodedWithError:nil]]; 33 | 34 | ## Building a XML-RPC request using streaming 35 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 36 | NSString *directory = [paths objectAtIndex:0]; 37 | NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; 38 | NSString *streamingCacheFilePath = [directory stringByAppendingPathComponent:guid]; 39 | 40 | NSURL *URL = [NSURL URLWithString:@"http://example.com/xmlrpc"]; 41 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; 42 | [request setHTTPMethod:@"POST"]; 43 | 44 | NSInputStream *fileStream = [NSInputStream inputStreamWithFileAtPath:filePath]; 45 | WPXMLRPCEncoder *encoder = [[WPXMLRPCEncoder alloc] initWithMethod:@"test.uploadFile" andParameters:@[fileStream]]; 46 | 47 | [encoder encodeToFile:streamingCacheFilePath error:nil]; 48 | 49 | NSError *error = nil; 50 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; 51 | unsigned long contentLength = [[attributes objectForKey:NSFileSize] unsignedIntegerValue]; 52 | NSInputStream * inputStream = [NSInputStream inputStreamWithFileAtPath:filePath]; 53 | 54 | [request setHTTPBodyStream:inputStream]; 55 | [request setValue:[NSString stringWithFormat:@"%lu", contentLength] forHTTPHeaderField:@"Content-Length"]; 56 | 57 | ## Parsing a XML-RPC response 58 | 59 | NSData *responseData = … 60 | WPXMLRPCDecoder *decoder = [[WPXMLRPCDecoder alloc] initWithData:responseData]; 61 | if ([decoder isFault]) { 62 | NSLog(@"XML-RPC error %@: %@", [decoder faultCode], [decoder faultString]); 63 | } else { 64 | NSLog(@"XML-RPC response: %@", [decoder object]); 65 | } 66 | 67 | # Acknowledgments 68 | 69 | The Base64 encoder/decoder found in NSData+Base64 is created by [Matt Gallagher](http://cocoawithlove.com/2009/06/base64-encoding-options-on-mac-and.html). 70 | 71 | The original Cocoa XML-RPC Framework was developed by [Eric Czarny](https://github.com/eczarny/xmlrpc) and now lives at [github.com/corristo/xmlrpc](https://github.com/corristo/xmlrpc) 72 | -------------------------------------------------------------------------------- /Support/Info-Tests.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /WPXMLRPC/WPBase64Utils.h: -------------------------------------------------------------------------------- 1 | // WPBase64Utils.h 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | @interface WPBase64Utils : NSObject 27 | + (NSString *)encodeData:(NSData *)data; 28 | + (void)encodeInputStream:(NSInputStream *)stream withChunkHandler:(void (^)(NSString *chunk))chunkHandler; 29 | + (void)encodeFileHandle:(NSFileHandle *)fileHandle withChunkHandler:(void (^)(NSString *chunk))chunkHandler; 30 | + (NSData *)decodeString:(NSString *)string; 31 | @end 32 | -------------------------------------------------------------------------------- /WPXMLRPC/WPBase64Utils.m: -------------------------------------------------------------------------------- 1 | // WPBase64Utils.m 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "WPBase64Utils.h" 25 | 26 | // Base64 encoding/decoding functions created by Matt Gallagher on 2009/06/03. 27 | // Copyright 2009 Matt Gallagher. All rights reserved. 28 | // 29 | // This software is provided 'as-is', without any express or implied 30 | // warranty. In no event will the authors be held liable for any damages 31 | // arising from the use of this software. Permission is granted to anyone to 32 | // use this software for any purpose, including commercial applications, and to 33 | // alter it and redistribute it freely, subject to the following restrictions: 34 | // 35 | // 1. The origin of this software must not be misrepresented; you must not 36 | // claim that you wrote the original software. If you use this software 37 | // in a product, an acknowledgment in the product documentation would be 38 | // appreciated but is not required. 39 | // 2. Altered source versions must be plainly marked as such, and must not be 40 | // misrepresented as being the original software. 41 | // 3. This notice may not be removed or altered from any source 42 | // distribution. 43 | // 44 | 45 | 46 | // 47 | // Mapping from 6 bit pattern to ASCII character. 48 | // 49 | static unsigned char base64EncodeLookup[65] = 50 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 51 | 52 | // 53 | // Definition for "masked-out" areas of the base64DecodeLookup mapping 54 | // 55 | #define xx 65 56 | 57 | // 58 | // Mapping from ASCII character to 6 bit pattern. 59 | // 60 | static unsigned char base64DecodeLookup[256] = 61 | { 62 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 63 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 64 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, 65 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, 66 | xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 67 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, 68 | xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 69 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, 70 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 71 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 72 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 73 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 74 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 75 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 76 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 77 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 78 | }; 79 | 80 | // 81 | // Fundamental sizes of the binary and base64 encode/decode units in bytes 82 | // 83 | #define BINARY_UNIT_SIZE 3 84 | #define BASE64_UNIT_SIZE 4 85 | 86 | // 87 | // NewBase64Decode 88 | // 89 | // Decodes the base64 ASCII string in the inputBuffer to a newly malloced 90 | // output buffer. 91 | // 92 | // inputBuffer - the source ASCII string for the decode 93 | // length - the length of the string or -1 (to specify strlen should be used) 94 | // outputLength - if not-NULL, on output will contain the decoded length 95 | // 96 | // returns the decoded buffer. Must be free'd by caller. Length is given by 97 | // outputLength. 98 | // 99 | void *NewBase64Decode( 100 | const char *inputBuffer, 101 | size_t length, 102 | size_t *outputLength) 103 | { 104 | if (length == -1) 105 | { 106 | length = strlen(inputBuffer); 107 | } 108 | 109 | size_t outputBufferSize = 110 | ((length+BASE64_UNIT_SIZE-1) / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE; 111 | unsigned char *outputBuffer = (unsigned char *)malloc(outputBufferSize); 112 | 113 | size_t i = 0; 114 | size_t j = 0; 115 | while (i < length) 116 | { 117 | // 118 | // Accumulate 4 valid characters (ignore everything else) 119 | // 120 | unsigned char accumulated[BASE64_UNIT_SIZE]; 121 | size_t accumulateIndex = 0; 122 | while (i < length) 123 | { 124 | unsigned char decode = base64DecodeLookup[inputBuffer[i++]]; 125 | if (decode != xx) 126 | { 127 | accumulated[accumulateIndex] = decode; 128 | accumulateIndex++; 129 | 130 | if (accumulateIndex == BASE64_UNIT_SIZE) 131 | { 132 | break; 133 | } 134 | } 135 | } 136 | 137 | // 138 | // Store the 6 bits from each of the 4 characters as 3 bytes 139 | // 140 | // (Uses improved bounds checking suggested by Alexandre Colucci) 141 | // 142 | if(accumulateIndex >= 2) 143 | outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4); 144 | if(accumulateIndex >= 3) 145 | outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2); 146 | if(accumulateIndex >= 4) 147 | outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3]; 148 | 149 | // We should increment j only with a positive accumulateIndex to prevent integer overflow 150 | if (accumulateIndex > 0) 151 | j += accumulateIndex - 1; 152 | } 153 | 154 | if (outputLength) 155 | { 156 | *outputLength = j; 157 | } 158 | return outputBuffer; 159 | } 160 | 161 | // 162 | // NewBase64Encode 163 | // 164 | // Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced 165 | // output buffer. 166 | // 167 | // inputBuffer - the source data for the encode 168 | // length - the length of the input in bytes 169 | // separateLines - if zero, no CR/LF characters will be added. Otherwise 170 | // a CR/LF pair will be added every 64 encoded chars. 171 | // outputLength - if not-NULL, on output will contain the encoded length 172 | // (not including terminating 0 char) 173 | // 174 | // returns the encoded buffer. Must be free'd by caller. Length is given by 175 | // outputLength. 176 | // 177 | char *NewBase64Encode( 178 | const void *buffer, 179 | size_t length, 180 | bool separateLines, 181 | size_t *outputLength) 182 | { 183 | const unsigned char *inputBuffer = (const unsigned char *)buffer; 184 | 185 | #define MAX_NUM_PADDING_CHARS 2 186 | #define OUTPUT_LINE_LENGTH 64 187 | #define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE) 188 | #define CR_LF_SIZE 2 189 | 190 | // 191 | // Byte accurate calculation of final buffer size 192 | // 193 | size_t outputBufferSize = 194 | ((length / BINARY_UNIT_SIZE) 195 | + ((length % BINARY_UNIT_SIZE) ? 1 : 0)) 196 | * BASE64_UNIT_SIZE; 197 | if (separateLines) 198 | { 199 | outputBufferSize += 200 | (outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE; 201 | } 202 | 203 | // 204 | // Include space for a terminating zero 205 | // 206 | outputBufferSize += 1; 207 | 208 | // 209 | // Allocate the output buffer 210 | // 211 | char *outputBuffer = (char *)malloc(outputBufferSize); 212 | if (!outputBuffer) 213 | { 214 | return NULL; 215 | } 216 | 217 | size_t i = 0; 218 | size_t j = 0; 219 | const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length; 220 | size_t lineEnd = lineLength; 221 | 222 | while (true) 223 | { 224 | if (lineEnd > length) 225 | { 226 | lineEnd = length; 227 | } 228 | 229 | for (; i + BINARY_UNIT_SIZE - 1 < lineEnd; i += BINARY_UNIT_SIZE) 230 | { 231 | // 232 | // Inner loop: turn 48 bytes into 64 base64 characters 233 | // 234 | outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; 235 | outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) 236 | | ((inputBuffer[i + 1] & 0xF0) >> 4)]; 237 | outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2) 238 | | ((inputBuffer[i + 2] & 0xC0) >> 6)]; 239 | outputBuffer[j++] = base64EncodeLookup[inputBuffer[i + 2] & 0x3F]; 240 | } 241 | 242 | if (lineEnd == length) 243 | { 244 | break; 245 | } 246 | 247 | // 248 | // Add the newline 249 | // 250 | outputBuffer[j++] = '\r'; 251 | outputBuffer[j++] = '\n'; 252 | lineEnd += lineLength; 253 | } 254 | 255 | if (i + 1 < length) 256 | { 257 | // 258 | // Handle the single '=' case 259 | // 260 | outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; 261 | outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) 262 | | ((inputBuffer[i + 1] & 0xF0) >> 4)]; 263 | outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2]; 264 | outputBuffer[j++] = '='; 265 | } 266 | else if (i < length) 267 | { 268 | // 269 | // Handle the double '=' case 270 | // 271 | outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; 272 | outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0x03) << 4]; 273 | outputBuffer[j++] = '='; 274 | outputBuffer[j++] = '='; 275 | } 276 | outputBuffer[j] = 0; 277 | 278 | // 279 | // Set the output length and return the buffer 280 | // 281 | if (outputLength) 282 | { 283 | *outputLength = j; 284 | } 285 | return outputBuffer; 286 | } 287 | 288 | #pragma mark - 289 | 290 | // If you change this, be sure it's a multiple of 90. 291 | // The base 64 encoding adds a new line every 90 characters. 292 | NSUInteger const WPBase64UtilsChunkSize = 18000; 293 | 294 | @implementation WPBase64Utils 295 | + (NSString *)encodeData:(NSData *)data { 296 | size_t outputLength; 297 | char *outputBuffer = NewBase64Encode([data bytes], [data length], false, &outputLength); 298 | 299 | NSString *result = [[NSString alloc] initWithBytes:outputBuffer 300 | length:outputLength 301 | encoding:NSASCIIStringEncoding]; 302 | 303 | free(outputBuffer); 304 | return result; 305 | } 306 | 307 | + (void)encodeInputStream:(NSInputStream *)stream withChunkHandler:(void (^)(NSString *chunk))chunkHandler { 308 | [stream open]; 309 | 310 | while ([stream hasBytesAvailable]) { 311 | uint8_t buf[WPBase64UtilsChunkSize]; 312 | NSInteger len = 0; 313 | 314 | len = [stream read:buf maxLength:WPBase64UtilsChunkSize]; 315 | if (len) { 316 | @autoreleasepool { 317 | NSData *chunk = [NSData dataWithBytes:buf length:len]; 318 | NSString *encodedChunk = [self encodeData:chunk]; 319 | chunkHandler(encodedChunk); 320 | } 321 | } 322 | } 323 | 324 | [stream close]; 325 | } 326 | 327 | + (void)encodeFileHandle:(NSFileHandle *)fileHandle withChunkHandler:(void (^)(NSString *chunk))chunkHandler { 328 | NSData *chunk = [fileHandle readDataOfLength:WPBase64UtilsChunkSize]; 329 | while ([chunk length] > 0) { 330 | NSString *encodedChunk = [self encodeData:chunk]; 331 | chunkHandler(encodedChunk); 332 | chunk = [fileHandle readDataOfLength:WPBase64UtilsChunkSize]; 333 | } 334 | } 335 | 336 | + (NSData *)decodeString:(NSString *)string { 337 | NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding]; 338 | size_t outputLength; 339 | void *outputBuffer = NewBase64Decode([data bytes], [data length], &outputLength); 340 | NSData *result = [NSData dataWithBytes:outputBuffer length:outputLength]; 341 | free(outputBuffer); 342 | return result; 343 | } 344 | @end 345 | -------------------------------------------------------------------------------- /WPXMLRPC/WPStringUtils.h: -------------------------------------------------------------------------------- 1 | // WPStringUtils.h 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | @interface WPStringUtils : NSObject 27 | + (NSString *)unescapedStringWithString:(NSString *)string; 28 | + (NSString *)escapedStringWithString:(NSString *)string; 29 | @end 30 | -------------------------------------------------------------------------------- /WPXMLRPC/WPStringUtils.m: -------------------------------------------------------------------------------- 1 | // WPStringUtils.m 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "WPStringUtils.h" 25 | 26 | @implementation WPStringUtils 27 | 28 | + (NSString *)unescapedStringWithString:(NSString *)aString { 29 | if (!aString) { 30 | return nil; 31 | } 32 | 33 | NSMutableString *string = [NSMutableString stringWithString:aString]; 34 | 35 | [string replaceOccurrencesOfString:@""" withString:@"\"" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 36 | [string replaceOccurrencesOfString:@"'" withString:@"'" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 37 | [string replaceOccurrencesOfString:@"9" withString:@"'" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 38 | [string replaceOccurrencesOfString:@"’" withString:@"'" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 39 | [string replaceOccurrencesOfString:@"–" withString:@"'" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 40 | [string replaceOccurrencesOfString:@">" withString:@">" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 41 | [string replaceOccurrencesOfString:@"<" withString:@"<" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 42 | [string replaceOccurrencesOfString:@"&" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 43 | 44 | return [NSString stringWithString:string]; 45 | } 46 | 47 | + (NSString *)escapedStringWithString:(NSString *)aString { 48 | if (!aString) { 49 | return nil; 50 | } 51 | 52 | NSMutableString *string = [NSMutableString stringWithString:aString]; 53 | 54 | // NOTE:we use unicode entities instead of & > < etc. since some hosts (powweb, fatcow, and similar) 55 | // have a weird PHP/libxml2 combination that ignores regular entities 56 | [string replaceOccurrencesOfString:@"&" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 57 | [string replaceOccurrencesOfString:@">" withString:@">" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 58 | [string replaceOccurrencesOfString:@"<" withString:@"<" options:NSLiteralSearch range:NSMakeRange(0, [string length])]; 59 | 60 | return [NSString stringWithString:string]; 61 | } 62 | 63 | @end -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPC.h: -------------------------------------------------------------------------------- 1 | // WPXMLRPC.h 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | #ifndef _WPXMLRPC 26 | #define _WPXMLRPC 27 | #import "WPXMLRPCEncoder.h" 28 | #import "WPXMLRPCDecoder.h" 29 | #endif /* _WPXMLRPC */ 30 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCDataCleaner.h: -------------------------------------------------------------------------------- 1 | // WPXMLRPCDataCleaner.h 2 | // Based on code from WordPress for iOS http://ios.wordpress.org/ 3 | // 4 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 5 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import 26 | 27 | @interface WPXMLRPCDataCleaner : NSObject 28 | - (id)initWithData:(NSData *)data; 29 | 30 | - (NSData *)cleanData; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCDataCleaner.m: -------------------------------------------------------------------------------- 1 | // WPXMLRPCDataCleaner.m 2 | // Based on code from WordPress for iOS http://ios.wordpress.org/ 3 | // 4 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 5 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import "WPXMLRPCDataCleaner.h" 26 | #import 27 | 28 | @interface WPXMLRPCDataCleaner (CleaningSteps) 29 | - (NSData *)cleanInvalidUTF8:(NSData *)str; 30 | - (NSString *)cleanCharactersBeforePreamble:(NSString *)str; 31 | - (NSString *)cleanInvalidXMLCharacters:(NSString *)str; 32 | - (NSString *)cleanWithTidyIfPresent:(NSString *)str; 33 | - (NSString *)cleanClosingTagIfNeeded:(NSString *)str lengthOfCharactersPrecedingPreamble:(NSInteger)length; 34 | @end 35 | 36 | @implementation WPXMLRPCDataCleaner { 37 | NSData *xmlData; 38 | } 39 | 40 | #pragma mark - initializers 41 | 42 | - (id)initWithData:(NSData *)data { 43 | self = [super init]; 44 | if (self) { 45 | xmlData = data; 46 | } 47 | return self; 48 | } 49 | 50 | 51 | #pragma mark - clean output 52 | 53 | - (NSData *)cleanData { 54 | if (xmlData == nil) 55 | return nil; 56 | 57 | NSData *cleanData = [self cleanInvalidUTF8:xmlData]; 58 | NSString *cleanString = [[NSString alloc] initWithData:cleanData encoding:NSUTF8StringEncoding]; 59 | 60 | if (cleanString == nil) { 61 | // Although it shouldn't happen, fall back to Latin1 if data is not proper UTF-8 62 | cleanString = [[NSString alloc] initWithData:cleanData encoding:NSISOLatin1StringEncoding]; 63 | } 64 | NSInteger startinglength = [cleanString length]; 65 | cleanString = [self cleanCharactersBeforePreamble:cleanString]; 66 | NSInteger len = startinglength - [cleanString length]; 67 | cleanString = [self cleanInvalidXMLCharacters:cleanString]; 68 | cleanString = [self cleanWithTidyIfPresent:cleanString]; 69 | 70 | cleanString = [self cleanClosingTagIfNeeded:cleanString lengthOfCharactersPrecedingPreamble:len]; 71 | 72 | cleanData = [cleanString dataUsingEncoding:NSUTF8StringEncoding]; 73 | 74 | return cleanData; 75 | } 76 | 77 | @end 78 | 79 | @implementation WPXMLRPCDataCleaner (CleaningSteps) 80 | 81 | /** 82 | Runs the given data through iconv to discard any invalid UTF-8 characters 83 | */ 84 | - (NSData *)cleanInvalidUTF8:(NSData *)data { 85 | NSData *result; 86 | iconv_t cd = iconv_open("UTF-8", "UTF-8"); // convert to UTF-8 from UTF-8 87 | int one = 1; 88 | iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &one); // discard invalid characters 89 | 90 | size_t inbytesleft, outbytesleft; 91 | inbytesleft = outbytesleft = data.length; 92 | 93 | char *inbuf = (char *)data.bytes; 94 | char *outbuf = malloc(sizeof(char) * data.length); 95 | char *outptr = outbuf; 96 | 97 | if (iconv(cd, &inbuf, &inbytesleft, &outptr, &outbytesleft) == (size_t)-1) { 98 | // Failed iconv, possible errors to encounter in `errno`: 99 | // 100 | // E2BIG - There is not sufficient room at *outbuf. 101 | // EILSEQ - An invalid multibyte sequence has been encountered in the input. 102 | // EINVAL - An incomplete multibyte sequence has been encountered in the input. 103 | // 104 | // It should never happen since we've told iconv to discard anything invalid 105 | 106 | result = data; 107 | } else { 108 | result = [NSData dataWithBytes:outbuf length:data.length - outbytesleft]; 109 | } 110 | 111 | iconv_close(cd); 112 | free(outbuf); 113 | 114 | return result; 115 | } 116 | 117 | /** 118 | Remove any text before the XML preamble ` 0 && range.location > 0) { 124 | str = [str substringFromIndex:range.location]; 125 | } 126 | return str; 127 | } 128 | 129 | /** 130 | Remove invalid characters as specified by the XML 1.0 standard 131 | 132 | Based on http://benjchristensen.com/2008/02/07/how-to-strip-invalid-xml-characters/ 133 | */ 134 | - (NSString *)cleanInvalidXMLCharacters:(NSString *)str { 135 | NSUInteger len = [str length]; 136 | 137 | NSMutableString *result = [NSMutableString stringWithCapacity:len]; 138 | for( int charIndex = 0; charIndex < len; charIndex++) { 139 | unichar testChar = [str characterAtIndex:charIndex]; 140 | if((testChar == 0x9) || 141 | (testChar == 0xA) || 142 | (testChar == 0xD) || 143 | ((testChar >= 0x20) && (testChar <= 0xD7FF)) || 144 | ((testChar >= 0xE000) && (testChar <= 0xFFFD)) 145 | ) { 146 | NSString *validCharacter = [NSString stringWithFormat:@"%C", testChar]; 147 | [result appendString:validCharacter]; 148 | } 149 | } 150 | return result; 151 | } 152 | 153 | /** 154 | If CTidy is available, run the cleaned XML through tidy as a last fix before parsing 155 | */ 156 | - (NSString *)cleanWithTidyIfPresent:(NSString *)str { 157 | /* 158 | The conditional code we're executing is the equivalent of: 159 | 160 | [[CTidy tidy] tidyString:str inputFormat:TidyFormat_XML outputFormat:TidyFormat_XML diagnostics:NULL error:&err]; 161 | */ 162 | id _CTidyClass = NSClassFromString(@"CTidy"); 163 | SEL _CTidySelector = NSSelectorFromString(@"tidy"); 164 | SEL _CTidyTidyStringSelector = NSSelectorFromString(@"tidyString:inputFormat:outputFormat:encoding:diagnostics:error:"); 165 | 166 | if (_CTidyClass && [_CTidyClass respondsToSelector:_CTidySelector]) { 167 | #pragma clang diagnostic push 168 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 169 | id _CTidyInstance = [_CTidyClass performSelector:_CTidySelector]; 170 | #pragma clang diagnostic pop 171 | 172 | if (_CTidyInstance && [_CTidyInstance respondsToSelector:_CTidyTidyStringSelector]) { 173 | typedef NSString *(*_CTidyTidyStringMethodType)(id, SEL, NSString *, int, int, NSString *, NSError **); 174 | _CTidyTidyStringMethodType _CTidyTidyStringMethod; 175 | _CTidyTidyStringMethod = (_CTidyTidyStringMethodType)[_CTidyInstance methodForSelector:_CTidyTidyStringSelector]; 176 | 177 | NSError *err = nil; 178 | NSString *result = _CTidyTidyStringMethod(_CTidyInstance, _CTidyTidyStringSelector, str, 1, 1, @"utf8", &err); 179 | 180 | if (result) 181 | return result; 182 | } 183 | } 184 | 185 | // If we reach this point, something failed. Return the original string 186 | return str; 187 | } 188 | 189 | /** 190 | In certain situations the xml response can be truncated due to an inaccurate 191 | content-length header. This can happen, for example, due to a BOM or whitespace 192 | preceeding an opening php tag. In these cases the xmlrpc response can be truncated 193 | the length of these extra leading characters. 194 | Check for a good closing tag, and try to repair a broken closing tag. 195 | */ 196 | - (NSString *)cleanClosingTagIfNeeded:(NSString *)str lengthOfCharactersPrecedingPreamble:(NSInteger)length 197 | { 198 | // Clean responses, but not requests (for now). 199 | if ([str rangeOfString:@"methodResponse"].location == NSNotFound) { 200 | return str; 201 | } 202 | 203 | // Check for breakage. 204 | if ([str rangeOfString:@""].location != NSNotFound) { 205 | // Nothing broken. All is well. 206 | return str; 207 | } 208 | 209 | // Get the proper closing tags for the request. 210 | NSString *closingTags = [self cloingTagsForString:str]; 211 | 212 | // If the length of the content cleaned from before the preamble is greater 213 | // than the length of the closing tags, then the xml is (probably) too damaged to repair. 214 | if (length > [closingTags length]) { 215 | // we can't fix this 216 | return str; 217 | } 218 | 219 | // Repair a truncated reponse where the ending markup is a partial match to 220 | // the closing tags. This should be the majority of cases. 221 | NSString *repairedStr = [self repairTruncatedClosingTags:closingTags inResponseString:str]; 222 | if (repairedStr) { 223 | return repairedStr; 224 | } 225 | 226 | // No partial match found so append the closing tags safely. 227 | repairedStr = [self appendClosingTags:closingTags toResponseString:str]; 228 | return repairedStr; 229 | } 230 | 231 | - (NSString *)cloingTagsForString:(NSString *)str 232 | { 233 | if ([str rangeOfString:@""].location != NSNotFound) { 234 | return @""; 235 | } else { 236 | return @""; 237 | } 238 | } 239 | 240 | - (NSRange)rangeOfLastValidClosingTagInString:(NSString *)str 241 | { 242 | static NSRegularExpression *regex; 243 | static dispatch_once_t onceToken; 244 | dispatch_once(&onceToken, ^{ 245 | NSError *error; 246 | regex = [NSRegularExpression regularExpressionWithPattern:@"]+>[^>]*$" options:NSRegularExpressionCaseInsensitive error:&error]; 247 | }); 248 | 249 | return [regex rangeOfFirstMatchInString:str options:NSMatchingReportCompletion range:NSMakeRange(0, [str length])]; 250 | } 251 | 252 | - (NSString *)repairTruncatedClosingTags:(NSString *)closingTags inResponseString:(NSString *)str 253 | { 254 | // Find the last valid closing tag. 255 | NSRange rangeOfLastValidTag = [self rangeOfLastValidClosingTagInString:str]; 256 | if (rangeOfLastValidTag.location == NSNotFound) { 257 | // There was no valid closing tag to be found. Strange! 258 | return str; 259 | } 260 | NSString *lastClosingTag = [str substringWithRange:rangeOfLastValidTag]; 261 | NSRange range = [closingTags rangeOfString:lastClosingTag]; 262 | if (range.location == NSNotFound) { 263 | return nil; 264 | } 265 | 266 | // Partial match found 267 | NSString *s1 = [str substringToIndex:rangeOfLastValidTag.location]; // get the string up to the start of the last closing tag. 268 | NSString *s2 = [closingTags substringFromIndex:range.location]; 269 | return [NSString stringWithFormat:@"%@%@", s1, s2]; 270 | } 271 | 272 | - (NSString *)appendClosingTags:(NSString *)closingTags toResponseString:(NSString *)str 273 | { 274 | // Find the start of the last end tag 275 | NSRange range = [str rangeOfString:@"<" options:NSBackwardsSearch]; 276 | if (range.location == NSNotFound) { 277 | // This should never happen, but handle the one in a million case. 278 | return str; 279 | } 280 | 281 | // Find the ending stub, if there is one, as a safety precaution. 282 | NSInteger index = 0; 283 | NSString *stub = [str substringFromIndex:range.location]; 284 | range = [closingTags rangeOfString:stub]; 285 | if (range.location != NSNotFound) { 286 | index = range.location + range.length; 287 | } 288 | 289 | // Append the missing part of the closing tags 290 | return [NSString stringWithFormat:@"%@%@", str, [closingTags substringFromIndex:index]]; 291 | 292 | } 293 | 294 | @end -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCDecoder.h: -------------------------------------------------------------------------------- 1 | // WPXMLRPCDecoder.h 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | extern NSString * const WPXMLRPCFaultErrorDomain; 29 | extern NSString * const WPXMLRPCErrorDomain; 30 | 31 | typedef NS_ENUM(NSInteger, WPXMLRPCError) { 32 | WPXMLRPCInvalidInputError, // The data passed doesn't look like a XML-RPC response 33 | WPXMLRPCOutOfMemoryError, // Response was a PHP out of memory fatal error 34 | }; 35 | 36 | /** 37 | `WPXMLRPCEncoder` encodes a XML-RPC response 38 | */ 39 | @interface WPXMLRPCDecoder : NSObject 40 | 41 | /** 42 | Initializes a `WPXMLRPCDecoder` object with the specified response data. 43 | 44 | @param data the data returned by your XML-RPC request 45 | 46 | @return The newly-initialized XML-RPC response 47 | */ 48 | - (nullable instancetype)initWithData:(NSData *)data; 49 | 50 | ///----------------------- 51 | /// @name Error management 52 | ///----------------------- 53 | 54 | /** 55 | Returns YES if the response contains a XML-RPC error 56 | */ 57 | - (BOOL)isFault; 58 | 59 | /** 60 | The XML-RPC error code 61 | */ 62 | - (NSInteger)faultCode; 63 | 64 | /** 65 | The XML-RPC error message 66 | */ 67 | - (nullable NSString *)faultString; 68 | 69 | /** 70 | Returns an error if there was a problem decoding the data, or if it's a XML-RPC error 71 | */ 72 | - (nullable NSError *)error; 73 | 74 | ///------------------------------------- 75 | /// @name Accessing the decoded response 76 | ///------------------------------------- 77 | 78 | /** 79 | The decoded object 80 | 81 | Check isFault before trying to do anything with this object. 82 | */ 83 | - (nullable id)object; 84 | 85 | @end 86 | 87 | NS_ASSUME_NONNULL_END 88 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCDecoder.m: -------------------------------------------------------------------------------- 1 | // WPXMLRPCDecoder.m 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "WPXMLRPCDecoder.h" 25 | #import "WPXMLRPCDecoderDelegate.h" 26 | #import "WPXMLRPCDataCleaner.h" 27 | 28 | NSString *const WPXMLRPCFaultErrorDomain = @"WPXMLRPCFaultError"; 29 | NSString *const WPXMLRPCErrorDomain = @"WPXMLRPCError"; 30 | 31 | @interface WPXMLRPCDecoder () 32 | @end 33 | 34 | @implementation WPXMLRPCDecoder { 35 | NSXMLParser *_parser; 36 | WPXMLRPCDecoderDelegate *_delegate; 37 | BOOL _isFault; 38 | NSData *_body; 39 | NSData *_originalData; 40 | id _object; 41 | NSMutableString *_methodName; 42 | } 43 | 44 | - (instancetype)initWithData:(NSData *)data { 45 | if (!data) { 46 | return nil; 47 | } 48 | 49 | if (self = [self init]) { 50 | _body = data; 51 | _originalData = data; 52 | _parser = [[NSXMLParser alloc] initWithData:data]; 53 | _delegate = nil; 54 | _isFault = NO; 55 | [self parse]; 56 | } 57 | 58 | return self; 59 | } 60 | 61 | #pragma mark - 62 | 63 | - (void)parse { 64 | [_parser setDelegate:self]; 65 | 66 | [_parser parse]; 67 | 68 | if ([_parser parserError]) { 69 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] initWithData:_originalData]; 70 | _body = [cleaner cleanData]; 71 | _parser = [[NSXMLParser alloc] initWithData:_body]; 72 | [_parser setDelegate:self]; 73 | [_parser parse]; 74 | } 75 | 76 | if ([_parser parserError]) 77 | return; 78 | 79 | if (_methodName) { 80 | _object = @{@"methodName": _methodName, @"params": [_delegate elementValue]}; 81 | } else { 82 | _object = [_delegate elementValue]; 83 | } 84 | } 85 | 86 | - (void)abortParsing { 87 | [_parser abortParsing]; 88 | } 89 | 90 | #pragma mark - 91 | 92 | - (BOOL)isFault { 93 | return _isFault; 94 | } 95 | 96 | - (NSInteger)faultCode { 97 | if ([self isFault]) { 98 | return [[_object objectForKey: @"faultCode"] integerValue]; 99 | } 100 | 101 | return 0; 102 | } 103 | 104 | - (NSString *)faultString { 105 | if ([self isFault]) { 106 | return [_object objectForKey: @"faultString"]; 107 | } 108 | 109 | return nil; 110 | } 111 | 112 | - (NSError *)error { 113 | if ([_parser parserError]) { 114 | NSString *response = [[NSString alloc] initWithData:_originalData encoding:NSUTF8StringEncoding]; 115 | if (response) { 116 | // Search for known PHP errors 117 | NSError *error = NULL; 118 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"Allowed memory size of \\d+ bytes exhausted" options:NSRegularExpressionCaseInsensitive error:&error]; 119 | NSUInteger numberOfMatches = [regex numberOfMatchesInString:response options:0 range:NSMakeRange(0, [response length])]; 120 | if (numberOfMatches > 0) { 121 | return [NSError errorWithDomain:WPXMLRPCErrorDomain code:WPXMLRPCOutOfMemoryError userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Your site ran out of memory while processing this request", @"WPXMLRPC", nil)}]; 122 | } 123 | } 124 | return [_parser parserError]; 125 | } 126 | 127 | if ([self isFault]) { 128 | if ([self faultString] == nil || [_object objectForKey: @"faultCode"] == nil) { 129 | return [NSError errorWithDomain:WPXMLRPCErrorDomain 130 | code:WPXMLRPCInvalidInputError 131 | userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"The data doesn't look like a valid XML-RPC Fault response", @"WPXMLRPC failed to read fault response from server")}]; 132 | } else { 133 | return [NSError errorWithDomain:WPXMLRPCFaultErrorDomain code:[self faultCode] userInfo:@{NSLocalizedDescriptionKey: [self faultString]}]; 134 | } 135 | } 136 | 137 | if (_object == nil) { 138 | return [NSError errorWithDomain:WPXMLRPCErrorDomain code:WPXMLRPCInvalidInputError userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"The data doesn't look like a valid XML-RPC response", @"WPXMLRPC", nil)}]; 139 | } 140 | 141 | return nil; 142 | } 143 | 144 | #pragma mark - 145 | 146 | - (id)object { 147 | return _object; 148 | } 149 | 150 | @end 151 | 152 | #pragma mark - 153 | 154 | @implementation WPXMLRPCDecoder (NSXMLParserDelegate) 155 | 156 | - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)element namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributes { 157 | if ([element isEqualToString:@"fault"]) { 158 | _isFault = YES; 159 | } else if ([element isEqualToString:@"value"]) { 160 | _delegate = [[WPXMLRPCDecoderDelegate alloc] initWithParent:nil]; 161 | 162 | [_parser setDelegate:_delegate]; 163 | } else if ([element isEqualToString:@"methodName"]) { 164 | _methodName = [NSMutableString string]; 165 | } 166 | } 167 | 168 | - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { 169 | if ([elementName isEqualToString:@"methodName"]) { 170 | _delegate = [[WPXMLRPCDecoderDelegate alloc] initWithParent:nil]; 171 | 172 | [_parser setDelegate:_delegate]; 173 | } 174 | } 175 | 176 | - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 177 | [_methodName appendString:string]; 178 | } 179 | 180 | - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { 181 | [self abortParsing]; 182 | } 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCDecoderDelegate.h: -------------------------------------------------------------------------------- 1 | // WPXMLRPCDecoderDelegate.h 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | typedef enum { 27 | WPXMLRPCElementTypeArray, 28 | WPXMLRPCElementTypeDictionary, 29 | WPXMLRPCElementTypeMember, 30 | WPXMLRPCElementTypeName, 31 | WPXMLRPCElementTypeInteger, 32 | WPXMLRPCElementTypeDouble, 33 | WPXMLRPCElementTypeBoolean, 34 | WPXMLRPCElementTypeString, 35 | WPXMLRPCElementTypeDate, 36 | WPXMLRPCElementTypeData 37 | } WPXMLRPCElementType; 38 | 39 | #pragma mark - 40 | 41 | @interface WPXMLRPCDecoderDelegate : NSObject { 42 | __weak WPXMLRPCDecoderDelegate *myParent; 43 | NSMutableArray *myChildren; 44 | WPXMLRPCElementType myElementType; 45 | NSString *myElementKey; 46 | id myElementValue; 47 | } 48 | 49 | - (id)initWithParent:(WPXMLRPCDecoderDelegate *)parent; 50 | 51 | #pragma mark - 52 | 53 | - (void)setParent:(WPXMLRPCDecoderDelegate *)parent; 54 | 55 | - (WPXMLRPCDecoderDelegate *)parent; 56 | 57 | #pragma mark - 58 | 59 | - (void)setElementType:(WPXMLRPCElementType)elementType; 60 | 61 | - (WPXMLRPCElementType)elementType; 62 | 63 | #pragma mark - 64 | 65 | - (void)setElementKey:(NSString *)elementKey; 66 | 67 | - (NSString *)elementKey; 68 | 69 | #pragma mark - 70 | 71 | - (void)setElementValue:(id)elementValue; 72 | 73 | - (id)elementValue; 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCDecoderDelegate.m: -------------------------------------------------------------------------------- 1 | // WPXMLRPCDecoderDelegate.m 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "WPXMLRPCDecoderDelegate.h" 25 | #import "WPBase64Utils.h" 26 | 27 | @interface WPXMLRPCDecoderDelegate (WPXMLRPCEventBasedParserDelegatePrivate) 28 | 29 | - (BOOL)isDictionaryElementType:(WPXMLRPCElementType)elementType; 30 | 31 | #pragma mark - 32 | 33 | - (void)addElementValueToParent; 34 | 35 | #pragma mark - 36 | 37 | - (NSDate *)parseDateString:(NSString *)dateString withFormat:(NSString *)format; 38 | 39 | #pragma mark - 40 | 41 | - (NSNumber *)parseInteger:(NSString *)value; 42 | 43 | - (NSNumber *)parseDouble:(NSString *)value; 44 | 45 | - (NSNumber *)parseBoolean:(NSString *)value; 46 | 47 | - (NSString *)parseString:(NSString *)value; 48 | 49 | - (NSDate *)parseDate:(NSString *)value; 50 | 51 | - (NSData *)parseData:(NSString *)value; 52 | 53 | @end 54 | 55 | #pragma mark - 56 | 57 | @implementation WPXMLRPCDecoderDelegate 58 | 59 | - (id)initWithParent:(WPXMLRPCDecoderDelegate *)parent { 60 | self = [super init]; 61 | if (self) { 62 | myParent = parent; 63 | myChildren = [[NSMutableArray alloc] initWithCapacity:1]; 64 | myElementType = WPXMLRPCElementTypeString; 65 | myElementKey = nil; 66 | myElementValue = [[NSMutableString alloc] init]; 67 | } 68 | 69 | return self; 70 | } 71 | 72 | #pragma mark - 73 | 74 | - (void)setParent:(WPXMLRPCDecoderDelegate *)parent { 75 | 76 | 77 | myParent = parent; 78 | } 79 | 80 | - (WPXMLRPCDecoderDelegate *)parent { 81 | return myParent; 82 | } 83 | 84 | #pragma mark - 85 | 86 | - (void)setElementType:(WPXMLRPCElementType)elementType { 87 | myElementType = elementType; 88 | } 89 | 90 | - (WPXMLRPCElementType)elementType { 91 | return myElementType; 92 | } 93 | 94 | #pragma mark - 95 | 96 | - (void)setElementKey:(NSString *)elementKey { 97 | 98 | 99 | myElementKey = elementKey; 100 | } 101 | 102 | - (NSString *)elementKey { 103 | return myElementKey; 104 | } 105 | 106 | #pragma mark - 107 | 108 | - (void)setElementValue:(id)elementValue { 109 | 110 | 111 | myElementValue = elementValue; 112 | } 113 | 114 | - (id)elementValue { 115 | return myElementValue; 116 | } 117 | 118 | #pragma mark - 119 | 120 | 121 | @end 122 | 123 | #pragma mark - 124 | 125 | @implementation WPXMLRPCDecoderDelegate (NSXMLParserDelegate) 126 | 127 | - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)element namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributes { 128 | if ([element isEqualToString:@"value"] || [element isEqualToString:@"member"] || [element isEqualToString:@"name"]) { 129 | WPXMLRPCDecoderDelegate *parserDelegate = [[WPXMLRPCDecoderDelegate alloc] initWithParent:self]; 130 | 131 | if ([element isEqualToString:@"member"]) { 132 | [parserDelegate setElementType:WPXMLRPCElementTypeMember]; 133 | } else if ([element isEqualToString:@"name"]) { 134 | [parserDelegate setElementType:WPXMLRPCElementTypeName]; 135 | } 136 | 137 | [myChildren addObject:parserDelegate]; 138 | 139 | [parser setDelegate:parserDelegate]; 140 | 141 | 142 | return; 143 | } 144 | 145 | if ([element isEqualToString:@"array"] || [element isEqualToString:@"params"]) { 146 | NSMutableArray *array = [[NSMutableArray alloc] init]; 147 | 148 | [self setElementValue:array]; 149 | 150 | 151 | [self setElementType:WPXMLRPCElementTypeArray]; 152 | } else if ([element isEqualToString:@"struct"]) { 153 | NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; 154 | 155 | [self setElementValue:dictionary]; 156 | 157 | 158 | [self setElementType:WPXMLRPCElementTypeDictionary]; 159 | } else if ([element isEqualToString:@"int"] || [element isEqualToString:@"i4"]) { 160 | [self setElementType:WPXMLRPCElementTypeInteger]; 161 | } else if ([element isEqualToString:@"double"]) { 162 | [self setElementType:WPXMLRPCElementTypeDouble]; 163 | } else if ([element isEqualToString:@"boolean"]) { 164 | [self setElementType:WPXMLRPCElementTypeBoolean]; 165 | } else if ([element isEqualToString:@"string"]) { 166 | [self setElementType:WPXMLRPCElementTypeString]; 167 | } else if ([element isEqualToString:@"dateTime.iso8601"]) { 168 | [self setElementType:WPXMLRPCElementTypeDate]; 169 | } else if ([element isEqualToString:@"base64"]) { 170 | [self setElementType:WPXMLRPCElementTypeData]; 171 | } 172 | } 173 | 174 | - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)element namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName { 175 | if ([element isEqualToString:@"value"] || [element isEqualToString:@"member"] || [element isEqualToString:@"name"]) { 176 | NSString *elementValue = nil; 177 | 178 | if ((myElementType != WPXMLRPCElementTypeArray) && ![self isDictionaryElementType:myElementType]) { 179 | elementValue = [self parseString:myElementValue]; 180 | 181 | 182 | myElementValue = nil; 183 | } 184 | 185 | switch (myElementType) { 186 | case WPXMLRPCElementTypeInteger: 187 | myElementValue = [self parseInteger:elementValue]; 188 | 189 | 190 | break; 191 | case WPXMLRPCElementTypeDouble: 192 | myElementValue = [self parseDouble:elementValue]; 193 | 194 | 195 | break; 196 | case WPXMLRPCElementTypeBoolean: 197 | myElementValue = [self parseBoolean:elementValue]; 198 | 199 | 200 | break; 201 | case WPXMLRPCElementTypeString: 202 | case WPXMLRPCElementTypeName: 203 | myElementValue = elementValue; 204 | 205 | 206 | break; 207 | case WPXMLRPCElementTypeDate: 208 | myElementValue = [self parseDate:elementValue]; 209 | 210 | 211 | break; 212 | case WPXMLRPCElementTypeData: 213 | myElementValue = [self parseData:elementValue]; 214 | 215 | 216 | break; 217 | default: 218 | break; 219 | } 220 | 221 | if (myParent && myElementValue) { 222 | [self addElementValueToParent]; 223 | } 224 | 225 | [parser setDelegate:myParent]; 226 | } 227 | } 228 | 229 | - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 230 | if ((myElementType == WPXMLRPCElementTypeArray) || [self isDictionaryElementType:myElementType]) { 231 | return; 232 | } 233 | 234 | if (!myElementValue) { 235 | myElementValue = [[NSMutableString alloc] initWithString:string]; 236 | } else { 237 | [myElementValue appendString:string]; 238 | } 239 | } 240 | 241 | - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { 242 | [parser abortParsing]; 243 | } 244 | 245 | @end 246 | 247 | #pragma mark - 248 | 249 | @implementation WPXMLRPCDecoderDelegate (WPXMLRPCEventBasedParserDelegatePrivate) 250 | 251 | - (BOOL)isDictionaryElementType:(WPXMLRPCElementType)elementType { 252 | if ((myElementType == WPXMLRPCElementTypeDictionary) || (myElementType == WPXMLRPCElementTypeMember)) { 253 | return YES; 254 | } 255 | 256 | return NO; 257 | } 258 | 259 | #pragma mark - 260 | 261 | - (void)addElementValueToParent { 262 | id parentElementValue = [myParent elementValue]; 263 | 264 | switch ([myParent elementType]) { 265 | case WPXMLRPCElementTypeArray: 266 | [parentElementValue addObject:myElementValue]; 267 | 268 | break; 269 | case WPXMLRPCElementTypeDictionary: 270 | if ([myElementValue isEqual:[NSNull null]]) { 271 | [parentElementValue removeObjectForKey:myElementKey]; 272 | } else { 273 | [parentElementValue setObject:myElementValue forKey:myElementKey]; 274 | } 275 | 276 | break; 277 | case WPXMLRPCElementTypeMember: 278 | if (myElementType == WPXMLRPCElementTypeName) { 279 | [myParent setElementKey:myElementValue]; 280 | } else { 281 | [myParent setElementValue:myElementValue]; 282 | } 283 | 284 | break; 285 | default: 286 | break; 287 | } 288 | } 289 | 290 | #pragma mark - 291 | 292 | - (NSDate *)parseDateString:(NSString *)dateString withFormat:(NSString *)format { 293 | // Set the local to POSIX format so changing date formats on the device doesn't affect the format being passed in 294 | // Since we're assuming GMT the timeZone should be set but this currently breaks WordPress for iOS so leaving for now 295 | NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 296 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 297 | dateFormatter.locale = enUSPOSIXLocale; 298 | // Forcing to GMT time zone to prevent issues with converting from/to time zones 299 | dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; 300 | 301 | NSDate *result = nil; 302 | 303 | [dateFormatter setDateFormat:format]; 304 | 305 | result = [dateFormatter dateFromString:dateString]; 306 | 307 | return result; 308 | } 309 | 310 | #pragma mark - 311 | 312 | - (NSNumber *)parseInteger:(NSString *)value { 313 | return [NSNumber numberWithLongLong:[value longLongValue]]; 314 | } 315 | 316 | - (NSNumber *)parseDouble:(NSString *)value { 317 | return [NSNumber numberWithDouble:[value doubleValue]]; 318 | } 319 | 320 | - (NSNumber *)parseBoolean:(NSString *)value { 321 | if ([value isEqualToString:@"1"]) { 322 | return [NSNumber numberWithBool:YES]; 323 | } 324 | 325 | return [NSNumber numberWithBool:NO]; 326 | } 327 | 328 | - (NSString *)parseString:(NSString *)value { 329 | return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 330 | } 331 | 332 | - (NSDate *)parseDate:(NSString *)value { 333 | NSDate *result = nil; 334 | 335 | result = [self parseDateString:value withFormat:@"yyyyMMdd'T'HH:mm:ss"]; 336 | 337 | if (!result) { 338 | result = [self parseDateString:value withFormat:@"yyyy'-'MM'-'dd'T'HH:mm:ss"]; 339 | } 340 | 341 | if (!result) { 342 | result = (NSDate *)[NSNull null]; 343 | } 344 | 345 | return result; 346 | } 347 | 348 | - (NSData *)parseData:(NSString *)value { 349 | return [WPBase64Utils decodeString:value]; 350 | } 351 | 352 | @end 353 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCEncoder.h: -------------------------------------------------------------------------------- 1 | // WPXMLRPCEncoder.h 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | @import Foundation; 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | /** 29 | `WPXMLRPCEncoder` encodes a XML-RPC request 30 | */ 31 | @interface WPXMLRPCEncoder : NSObject 32 | 33 | - (instancetype)init NS_UNAVAILABLE; 34 | 35 | /** 36 | Initializes a `WPXMLRPCEncoder` object with the specified method and parameters. 37 | 38 | @param method the XML-RPC method for this request 39 | @param parameters an array containing the parameters for the request. If you want to support streaming, you can use either `NSInputStream` or `NSFileHandle` to encode binary data 40 | 41 | @return The newly-initialized XML-RPC request 42 | */ 43 | - (instancetype)initWithMethod:(NSString *)method andParameters:(nullable NSArray *)parameters NS_DESIGNATED_INITIALIZER; 44 | 45 | /** 46 | Initializes a `WPXMLRPCEncoder` object with the specified response params. 47 | 48 | @warning The response encoder is for testing purposes only, and hasn't been tested to implement a XML-RPC server 49 | 50 | @param params an array containing the result parameters for the response 51 | 52 | @return The newly-initialized XML-RPC response 53 | */ 54 | - (instancetype)initWithResponseParams:(nullable NSArray *)params NS_DESIGNATED_INITIALIZER; 55 | 56 | /** 57 | Initializes a `WPXMLRPCEncoder` object with the specified response fault. 58 | 59 | @warning The response encoder is for testing purposes only, and hasn't been tested to implement a XML-RPC server 60 | 61 | @param faultCode the fault code 62 | @param faultString the fault message string 63 | 64 | @return The newly-initialized XML-RPC response 65 | */ 66 | - (instancetype)initWithResponseFaultCode:(NSNumber *)faultCode andString:(NSString *)faultString NS_DESIGNATED_INITIALIZER; 67 | 68 | /** 69 | The XML-RPC method for this request. 70 | 71 | This is a *read-only* property, as requests can't be reused. 72 | */ 73 | @property (nonatomic, readonly) NSString * method; 74 | 75 | /** 76 | The XML-RPC parameters for this request. 77 | 78 | This is a *read-only* property, as requests can't be reused. 79 | */ 80 | @property (nonatomic, readonly, nullable) NSArray * parameters; 81 | 82 | ///------------------------------------ 83 | /// @name Accessing the encoded request 84 | ///------------------------------------ 85 | 86 | /** 87 | The encoded request as a `NSData` 88 | 89 | You should pass this to `[NSMutableRequest setHTTPBody:]` 90 | @warning This method is now deprecated you should use dataEncodedWithError:(NSError *)error; 91 | 92 | @return A NSData object with the encoded method and paramaters, nil if there was an error. 93 | */ 94 | @property (nonatomic, readonly, nullable) NSData * body DEPRECATED_ATTRIBUTE; 95 | 96 | /** 97 | The encoded request as a `NSData` object. 98 | 99 | You should pass this to `[NSMutableRequest setHTTPBody:]` 100 | 101 | @param error On input, a pointer to an error object. If an error occurs, this pointer is set to an actual error object containing the error information. You may specify nil for this parameter if you do not want the error information. 102 | 103 | @return A NSData object with the encoded method and paramaters, nil if there was an error. 104 | */ 105 | - (nullable NSData *) dataEncodedWithError:(NSError *_Nullable*_Nullable) error; 106 | 107 | /** 108 | Encodes the request to the filePath. 109 | 110 | The caller is responsible to manage the resulting file after the request is finished. 111 | 112 | @param error On input, a pointer to an error object. If an error occurs, this pointer is set to an actual error object containing the error information. You may specify nil for this parameter if you do not want the error information. 113 | 114 | @return BOOL, YES if the request was completed with success, NO if some error occurred. 115 | */ 116 | - (BOOL)encodeToFile:(NSString *)filePath error:(NSError *_Nullable*_Nullable) error; 117 | 118 | @end 119 | 120 | NS_ASSUME_NONNULL_END 121 | -------------------------------------------------------------------------------- /WPXMLRPC/WPXMLRPCEncoder.m: -------------------------------------------------------------------------------- 1 | // WPXMLRPCEncoder.m 2 | // 3 | // Copyright (c) 2013 WordPress - http://wordpress.org/ 4 | // Based on Eric Czarny's xmlrpc library - https://github.com/eczarny/xmlrpc 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "WPXMLRPCEncoder.h" 25 | #import "WPBase64Utils.h" 26 | #import "WPStringUtils.h" 27 | 28 | #pragma mark - 29 | 30 | @implementation WPXMLRPCEncoder { 31 | NSString *_method; 32 | NSArray *_parameters; 33 | NSFileHandle *_streamingCacheFile; 34 | BOOL _isResponse; 35 | BOOL _isFault; 36 | NSNumber *_faultCode; 37 | NSString *_faultString; 38 | } 39 | 40 | - (instancetype)initWithMethod:(NSString *)method andParameters:(NSArray *)parameters { 41 | self = [super init]; 42 | if (self) { 43 | _method = method; 44 | _parameters = parameters; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithResponseParams:(NSArray *)params { 51 | self = [super init]; 52 | if (self) { 53 | _parameters = params; 54 | _isResponse = YES; 55 | } 56 | return self; 57 | } 58 | 59 | - (instancetype)initWithResponseFaultCode:(NSNumber *)faultCode andString:(NSString *)faultString { 60 | self = [super init]; 61 | if (self) { 62 | _faultCode = faultCode; 63 | _faultString = faultString; 64 | _isResponse = YES; 65 | _isFault = YES; 66 | } 67 | return self; 68 | } 69 | 70 | 71 | #pragma mark - Public methods 72 | 73 | - (NSData *)body { 74 | return [self dataEncodedWithError:nil]; 75 | } 76 | 77 | - (NSData *)dataEncodedWithError:(NSError **) error { 78 | NSString * filePath = [self tmpFilePathForCache]; 79 | if (![self encodeToFile:filePath error:error]){ 80 | return nil; 81 | } 82 | 83 | NSData * encodedData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingUncached error:error]; 84 | 85 | [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; 86 | 87 | return encodedData; 88 | } 89 | 90 | - (BOOL)encodeToFile:(NSString *)filePath error:(NSError **) error { 91 | NSFileManager *fileManager = [NSFileManager defaultManager]; 92 | [fileManager createFileAtPath:filePath contents:nil attributes:nil]; 93 | _streamingCacheFile = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:filePath] error:error]; 94 | if (!_streamingCacheFile){ 95 | return NO; 96 | } 97 | 98 | [self encodeForStreaming]; 99 | 100 | return YES; 101 | } 102 | 103 | #pragma mark - Private methods 104 | 105 | - (void)encodeForStreaming { 106 | [self appendString:@""]; 107 | if (_isResponse) { 108 | [self appendString:@""]; 109 | if (_isFault) { 110 | [self appendString:@""]; 111 | [self encodeDictionary:@{@"faultCode": _faultCode, @"faultString": _faultString}]; 112 | [self appendString:@""]; 113 | } else { 114 | [self appendString:@""]; 115 | } 116 | } else { 117 | [self appendString:@""]; 118 | [self encodeString:_method omitTag:YES]; 119 | [self appendString:@""]; 120 | } 121 | 122 | if (_parameters) { 123 | NSEnumerator *enumerator = [_parameters objectEnumerator]; 124 | id parameter = nil; 125 | 126 | while ((parameter = [enumerator nextObject])) { 127 | [self appendString:@""]; 128 | [self encodeObject:parameter]; 129 | [self appendString:@""]; 130 | } 131 | } 132 | 133 | if (_isResponse) { 134 | if (!_isFault) { 135 | [self appendString:@""]; 136 | } 137 | [self appendString:@""]; 138 | } else { 139 | [self appendString:@""]; 140 | [self appendString:@""]; 141 | } 142 | 143 | [_streamingCacheFile synchronizeFile]; 144 | } 145 | 146 | - (void)valueTag:(NSString *)tag value:(NSString *)value { 147 | [self appendFormat:@"<%@>%@", tag, value, tag]; 148 | } 149 | 150 | - (void)encodeObject:(id)object { 151 | if (!object) { 152 | return; 153 | } 154 | 155 | if ([object isKindOfClass:[NSArray class]]) { 156 | [self encodeArray:object]; 157 | } else if ([object isKindOfClass:[NSDictionary class]]) { 158 | [self encodeDictionary:object]; 159 | } else if (((__bridge CFBooleanRef)object == kCFBooleanTrue) || ((__bridge CFBooleanRef)object == kCFBooleanFalse)) { 160 | [self encodeBoolean:(CFBooleanRef)object]; 161 | } else if ([object isKindOfClass:[NSNumber class]]) { 162 | [self encodeNumber:object]; 163 | } else if ([object isKindOfClass:[NSString class]]) { 164 | [self encodeString:object omitTag:NO]; 165 | } else if ([object isKindOfClass:[NSDate class]]) { 166 | [self encodeDate:object]; 167 | } else if ([object isKindOfClass:[NSData class]]) { 168 | [self encodeData:object]; 169 | } else if ([object isKindOfClass:[NSInputStream class]]) { 170 | [self encodeInputStream:object]; 171 | } else if ([object isKindOfClass:[NSFileHandle class]]) { 172 | [self encodeFileHandle:object]; 173 | } else { 174 | [self encodeString:object omitTag:NO]; 175 | } 176 | } 177 | 178 | - (void)encodeArray:(NSArray *)array { 179 | NSEnumerator *enumerator = [array objectEnumerator]; 180 | 181 | [self appendString:@""]; 182 | 183 | id object = nil; 184 | 185 | while (object = [enumerator nextObject]) { 186 | [self encodeObject:object]; 187 | } 188 | 189 | [self appendString:@""]; 190 | } 191 | 192 | - (void)encodeDictionary:(NSDictionary *)dictionary { 193 | NSEnumerator *enumerator = [dictionary keyEnumerator]; 194 | 195 | [self appendString:@""]; 196 | 197 | NSString *key = nil; 198 | 199 | while (key = [enumerator nextObject]) { 200 | [self appendString:@""]; 201 | [self appendString:@""]; 202 | [self encodeString:key omitTag:YES]; 203 | [self appendString:@""]; 204 | [self encodeObject:[dictionary objectForKey:key]]; 205 | [self appendString:@""]; 206 | } 207 | 208 | [self appendString:@""]; 209 | } 210 | 211 | - (void)encodeBoolean:(CFBooleanRef)boolean { 212 | if (boolean == kCFBooleanTrue) { 213 | [self valueTag:@"boolean" value:@"1"]; 214 | } else { 215 | [self valueTag:@"boolean" value:@"0"]; 216 | } 217 | } 218 | 219 | - (void)encodeNumber:(NSNumber *)number { 220 | NSString *numberType = [NSString stringWithCString:[number objCType] encoding:NSUTF8StringEncoding]; 221 | 222 | if ([numberType isEqualToString:@"d"]) { 223 | [self valueTag:@"double" value:[number stringValue]]; 224 | } else { 225 | [self valueTag:@"i4" value:[number stringValue]]; 226 | } 227 | } 228 | 229 | - (void)encodeString:(NSString *)string omitTag:(BOOL)omitTag { 230 | if (omitTag) 231 | [self appendString:[WPStringUtils escapedStringWithString:string]]; 232 | else 233 | [self valueTag:@"string" value:[WPStringUtils escapedStringWithString:string]]; 234 | } 235 | 236 | - (void)encodeDate:(NSDate *)date { 237 | unsigned components = kCFCalendarUnitYear | kCFCalendarUnitMonth | kCFCalendarUnitDay | kCFCalendarUnitHour | kCFCalendarUnitMinute | kCFCalendarUnitSecond; 238 | NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; 239 | [calendar setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]]; 240 | NSDateComponents *dateComponents = [calendar components:components fromDate:date]; 241 | NSString *buffer = [NSString stringWithFormat:@"%.4ld%.2d%.2dT%.2d:%.2ld:%.2ld%@", (long)[dateComponents year], (int)[dateComponents month], (int)[dateComponents day], (int)[dateComponents hour], (long)[dateComponents minute], (long)[dateComponents second], @"Z", nil]; 242 | 243 | [self valueTag:@"dateTime.iso8601" value:buffer]; 244 | } 245 | 246 | - (void)encodeData:(NSData *)data { 247 | [self valueTag:@"base64" value:[WPBase64Utils encodeData:data]]; 248 | } 249 | 250 | - (void)encodeInputStream:(NSInputStream *)stream { 251 | [self appendString:@""]; 252 | 253 | [WPBase64Utils encodeInputStream:stream withChunkHandler:^(NSString *chunk) { 254 | [self appendString:chunk]; 255 | }]; 256 | 257 | [self appendString:@""]; 258 | } 259 | 260 | - (void)encodeFileHandle:(NSFileHandle *)handle { 261 | [self appendString:@""]; 262 | 263 | [WPBase64Utils encodeFileHandle:handle withChunkHandler:^(NSString *chunk) { 264 | [self appendString:chunk]; 265 | }]; 266 | 267 | [self appendString:@""]; 268 | } 269 | 270 | - (void)appendString:(NSString *)aString { 271 | [_streamingCacheFile writeData:[aString dataUsingEncoding:NSUTF8StringEncoding]]; 272 | } 273 | 274 | - (void)appendFormat:(NSString *)format, ... { 275 | va_list ap; 276 | va_start(ap, format); 277 | NSString *message = [[NSString alloc] initWithFormat:format arguments:ap]; 278 | 279 | [self appendString:message]; 280 | } 281 | 282 | - (NSString *)tmpFilePathForCache { 283 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 284 | NSString *directory = [paths objectAtIndex:0]; 285 | NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; 286 | NSString * tmpPath = [directory stringByAppendingPathComponent:guid]; 287 | return tmpPath; 288 | } 289 | 290 | @end 291 | -------------------------------------------------------------------------------- /WPXMLRPC/include/WPXMLRPC.h: -------------------------------------------------------------------------------- 1 | ../WPXMLRPC.h -------------------------------------------------------------------------------- /WPXMLRPC/include/WPXMLRPCDecoder.h: -------------------------------------------------------------------------------- 1 | ../WPXMLRPCDecoder.h -------------------------------------------------------------------------------- /WPXMLRPC/include/WPXMLRPCEncoder.h: -------------------------------------------------------------------------------- 1 | ../WPXMLRPCEncoder.h -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/AlternativeDateFormatsTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 20091201T20:49:00 9 | 2009-12-01T20:50:00 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/DefaultTypeTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World! 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/EmptyBooleanTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/EmptyDataTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/EmptyDoubleTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/EmptyIntegerTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/EmptyStringTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/EscapingTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Are < & > escaped correctly? 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/IncompleteXmlTest.xml: -------------------------------------------------------------------------------- 1 | PHP Notice: Some garbage before preamble 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hello World! 10 | 42 11 | 3.14 12 | 1 13 | 20090718T21:34:00 14 | eW91IGNhbid0IHJlYWQgdGhpcyE= -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/InvalidFault.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/NoXmlResponseTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Why Duda, why? 11 | 12 | 13 | 14 |

Don't mess with my XML-RPC bro!

15 | 16 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/OverflowInteger32TestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9223372036854775807 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/RequestTestCase.xml: -------------------------------------------------------------------------------- 1 | wp.getUsersBlogsusernamepassword -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/SimpleArrayTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello World! 9 | 42 10 | 3.14 11 | 1 12 | 20090718T21:34:00 13 | eW91IGNhbid0IHJlYWQgdGhpcyE= 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/SimpleStructTestCase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Name 9 | Eric Czarny 10 | 11 | 12 | Birthday 13 | 1984-04-15T00:00:00 14 | 15 | 16 | Age 17 | 25 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/TestCases.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EscapingTestCase 6 | Are < & > escaped correctly? 7 | AlternativeDateFormatsTestCase 8 | 9 | 2009-12-01T20:49:00Z 10 | 2009-12-01T20:50:00Z 11 | 12 | DefaultTypeTestCase 13 | Hello World! 14 | EmptyBooleanTestCase 15 | 0 16 | EmptyDataTestCase 17 | 18 | EmptyDoubleTestCase 19 | 0 20 | EmptyIntegerTestCase 21 | 0 22 | EmptyStringTestCase 23 | 24 | SimpleArrayTestCase 25 | 26 | Hello World! 27 | 42 28 | 3.14 29 | 1 30 | 2009-07-18T21:34:00Z 31 | eW91IGNhbid0IHJlYWQgdGhpcyE= 32 | 33 | RequestTestCase 34 | 35 | methodName 36 | wp.getUsersBlogs 37 | params 38 | 39 | username 40 | password 41 | 42 | 43 | SimpleStructTestCase 44 | 45 | Name 46 | Eric Czarny 47 | Birthday 48 | 1984-04-15T00:00:00Z 49 | Age 50 | 25 51 | 52 | IncompleteXmlTest 53 | 54 | Hello World! 55 | 42 56 | 3.14 57 | 1 58 | 2009-07-18T21:34:00Z 59 | eW91IGNhbid0IHJlYWQgdGhpcyE= 60 | 61 | OverflowInteger32TestCase 62 | 9223372036854775807 63 | 64 | 65 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/TestImage.base64: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/TestImage.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wordpress-mobile/wpxmlrpc/810695e0455396cfc5fad460f16f64eefd8b5087/WPXMLRPCTest/Test Data/TestImage.bin -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/entities.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | dateCreated20120126T20:38:14 7 | userid1 8 | postid37 9 | description<h2>Reserved Characters in HTML</h2> 10 | Some characters are reserved in HTML and XHTML. For example, you cannot use 11 | the greater than or less than signs within your text because the browser could 12 | mistake them for markup. 13 | 14 | HTML and XHTML processors must support the five special characters listed in 15 | the table below: 16 | <table class="reference"> 17 | <tbody> 18 | <tr> 19 | <th align="left">Character</th> 20 | <th align="left">Entity Number</th> 21 | <th align="left">Entity Name</th> 22 | <th align="left">Description</th> 23 | </tr> 24 | <tr> 25 | <td>"</td> 26 | <td>&amp;#34;</td> 27 | <td>&amp;quot;</td> 28 | <td>quotation mark</td> 29 | </tr> 30 | <tr> 31 | <td>'</td> 32 | <td>&amp;#39;</td> 33 | <td>&amp;apos;</td> 34 | <td>apostrophe</td> 35 | </tr> 36 | <tr> 37 | <td>&amp;</td> 38 | <td>&amp;#38;</td> 39 | <td>&amp;amp;</td> 40 | <td>ampersand</td> 41 | </tr> 42 | <tr> 43 | <td>&lt;</td> 44 | <td>&amp;#60;</td> 45 | <td>&amp;lt;</td> 46 | <td>less-than</td> 47 | </tr> 48 | <tr> 49 | <td>&gt;</td> 50 | <td>&amp;#62;</td> 51 | <td>&amp;gt;</td> 52 | <td>greater-than</td> 53 | </tr> 54 | </tbody> 55 | </table> 56 | titleEntities everywhere 57 | linkhttp://xmlfixtestcom.powweb.com/blog/2012/01/26/entities-everywhere/ 58 | permaLinkhttp://xmlfixtestcom.powweb.com/blog/2012/01/26/entities-everywhere/ 59 | categories 60 | Uncategorized 61 | 62 | mt_excerpt 63 | mt_text_more 64 | mt_allow_comments1 65 | mt_allow_pings1 66 | mt_keywords 67 | wp_slugentities-everywhere 68 | wp_password 69 | wp_author_id1 70 | wp_author_display_nameq 71 | date_created_gmt20120126T20:38:14 72 | post_statuspublish 73 | custom_fields 74 | 75 | wp_post_formatstandard 76 | sticky0 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Test Data/entitiesDecoded.xml: -------------------------------------------------------------------------------- 1 |

Reserved Characters in HTML

2 | Some characters are reserved in HTML and XHTML. For example, you cannot use 3 | the greater than or less than signs within your text because the browser could 4 | mistake them for markup. 5 | 6 | HTML and XHTML processors must support the five special characters listed in 7 | the table below: 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
CharacterEntity NumberEntity NameDescription
"&#34;&quot;quotation mark
'&#39;&apos;apostrophe
&&#38;&amp;ampersand
<&#60;&lt;less-than
>&#62;&gt;greater-than
-------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/Helper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSBundle (Helper) 4 | 5 | + (NSBundle *)wpxmlrpc_assetsBundle; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/Helper.m: -------------------------------------------------------------------------------- 1 | #import "Helper.h" 2 | 3 | #if !defined(SWIFT_PACKAGE) 4 | @interface WPXMLRPCAssetsBundleFinder: NSObject 5 | @end 6 | 7 | @implementation WPXMLRPCAssetsBundleFinder 8 | @end 9 | #endif 10 | 11 | @implementation NSBundle (Helper) 12 | 13 | + (NSBundle *)wpxmlrpc_assetsBundle { 14 | #if SWIFT_PACKAGE 15 | return SWIFTPM_MODULE_BUNDLE; 16 | #else 17 | return [NSBundle bundleForClass:[WPXMLRPCAssetsBundleFinder class]]; 18 | #endif 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/WPBase64UtilsTest.m: -------------------------------------------------------------------------------- 1 | #import "WPBase64Utils.h" 2 | #import "Helper.h" 3 | 4 | #import 5 | 6 | @interface WPBase64UtilsTest : XCTestCase 7 | 8 | @end 9 | 10 | @implementation WPBase64UtilsTest { 11 | NSString *expectedEncoded; 12 | NSData *expectedDecoded; 13 | NSData *expectedDecodedControlCharacters; 14 | NSString *encodedFilePath; 15 | NSString *encodedControlCharacters; 16 | } 17 | 18 | - (void)setUp { 19 | expectedEncoded = [NSString stringWithContentsOfFile:[[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"TestImage" ofType:@"base64"] encoding:NSASCIIStringEncoding error:nil]; 20 | encodedFilePath = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"TestImage" ofType:@"bin"]; 21 | expectedDecoded = [NSData dataWithContentsOfFile:encodedFilePath]; 22 | XCTAssertEqual(expectedDecoded.length, 4326); 23 | 24 | encodedControlCharacters = @"\r\n"; 25 | expectedDecodedControlCharacters = [@"" dataUsingEncoding:NSASCIIStringEncoding]; 26 | } 27 | 28 | - (void)testEncodeWithData { 29 | NSString *parsedEncoded = [WPBase64Utils encodeData:expectedDecoded]; 30 | XCTAssertEqualObjects(expectedEncoded, parsedEncoded); 31 | } 32 | 33 | - (void)testDecodeWithData { 34 | NSData *parsedDecoded = [WPBase64Utils decodeString:expectedEncoded]; 35 | XCTAssertEqualObjects(expectedDecoded, parsedDecoded); 36 | } 37 | 38 | - (void)testDecodeControlCharactersString { 39 | NSData *parsedDecoded = [WPBase64Utils decodeString:encodedControlCharacters]; 40 | XCTAssertEqualObjects(expectedDecodedControlCharacters, parsedDecoded); 41 | } 42 | 43 | - (void)testEncodeWithInputStream { 44 | NSMutableString *parsedEncoded = [NSMutableString string]; 45 | [WPBase64Utils encodeInputStream:[NSInputStream inputStreamWithFileAtPath:encodedFilePath] withChunkHandler:^(NSString *chunk) { 46 | [parsedEncoded appendString:chunk]; 47 | }]; 48 | XCTAssertEqualObjects(expectedEncoded, parsedEncoded); 49 | } 50 | 51 | - (void)testEncodeWithFileHandle { 52 | NSMutableString *parsedEncoded = [NSMutableString string]; 53 | [WPBase64Utils encodeFileHandle:[NSFileHandle fileHandleForReadingAtPath:encodedFilePath] withChunkHandler:^(NSString *chunk) { 54 | [parsedEncoded appendString:chunk]; 55 | }]; 56 | XCTAssertEqualObjects(expectedEncoded, parsedEncoded); 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/WPXMLRPCDataCleanerTest.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "WPXMLRPCDataCleaner.h" 3 | 4 | static NSString * const CompleteResponse = @"Hello!"; 5 | static NSString * const CompleteFault = @"faultCode403faultStringIncorrect username or password."; 6 | 7 | @interface WPXMLRPCDataCleaner (CleaningSteps) 8 | - (NSString *)cleanClosingTagIfNeeded:(NSString *)str lengthOfCharactersPrecedingPreamble:(NSInteger)length; 9 | @end 10 | 11 | @interface WPXMLRPCDataCleanerTest : XCTestCase 12 | @end 13 | 14 | @implementation WPXMLRPCDataCleanerTest 15 | 16 | 17 | 18 | - (void)testClosingTagRepaired 19 | { 20 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 21 | 22 | // Test a whole response 23 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:CompleteResponse lengthOfCharactersPrecedingPreamble:0]; 24 | XCTAssertEqualObjects(CompleteResponse, cleanedString, @"A good reponse should just be returned intact."); 25 | } 26 | 27 | - (void)testReturnsGoodStringWhenPreambleWasCleanedOfJunk 28 | { 29 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 30 | // Test a whole response but some info was cleaned from before the preamble 31 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:CompleteResponse lengthOfCharactersPrecedingPreamble:35]; 32 | XCTAssertEqualObjects(CompleteResponse, cleanedString, @"Info cleaned from the preamble should not affect a good response."); 33 | } 34 | 35 | - (void)testReturnsOriginalStringWhenPreambleHadTooMuchJunk 36 | { 37 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 38 | // Test a truncated response where too much info was cleaned from before the preamble. 39 | NSString *truncatedResponse = [CompleteResponse substringWithRange:NSMakeRange(0, [CompleteResponse length] - 35)]; 40 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedResponse lengthOfCharactersPrecedingPreamble:35]; 41 | XCTAssertEqualObjects(truncatedResponse, cleanedString, @"Should not try to fix a response where too much junk was removed from the preamble."); 42 | } 43 | 44 | - (void)testCleansResponseTurncatedBeforeParam 45 | { 46 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 47 | // Test a response truncated just before the param tag 48 | NSString *truncatedResponse = [CompleteResponse substringWithRange:NSMakeRange(0, [CompleteResponse length] - 34)]; 49 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedResponse lengthOfCharactersPrecedingPreamble:0]; 50 | XCTAssertEqualObjects(CompleteResponse, cleanedString, @"Failed to repair a damanged closing param tag."); 51 | } 52 | 53 | - (void)testCleansResponseTurncatedInParam 54 | { 55 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 56 | // Test a response truncated in the param tag 57 | NSString *truncatedResponse = [CompleteResponse substringWithRange:NSMakeRange(0, [CompleteResponse length] - 33)]; 58 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedResponse lengthOfCharactersPrecedingPreamble:0]; 59 | XCTAssertEqualObjects(CompleteResponse, cleanedString, @"Failed to repair a damanged closing param tag."); 60 | } 61 | 62 | - (void)testCleansResponseTurncatedInParams 63 | { 64 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 65 | // test a response truncated in the params tag 66 | NSString *truncatedResponse = [CompleteResponse substringWithRange:NSMakeRange(0, [CompleteResponse length] - 20)]; 67 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedResponse lengthOfCharactersPrecedingPreamble:0]; 68 | XCTAssertEqualObjects(CompleteResponse, cleanedString, @"Failed to repair a damanged closing params tag."); 69 | } 70 | 71 | - (void)testCleansResponseTurncatedInMethodResponse 72 | { 73 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 74 | // test a response truncated in the methodResponse tag 75 | NSString *truncatedResponse = [CompleteResponse substringWithRange:NSMakeRange(0, [CompleteResponse length] - 10)]; 76 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedResponse lengthOfCharactersPrecedingPreamble:0]; 77 | XCTAssertEqualObjects(CompleteResponse, cleanedString, @"Failed to repair a damanged closing methodResponse tag."); 78 | } 79 | 80 | - (void)testCleansFaultTurncateBeforeValue 81 | { 82 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 83 | // Test a fault truncated at the value tag 84 | NSString *truncatedFault = [CompleteFault substringWithRange:NSMakeRange(0, [CompleteFault length] - 33)]; 85 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedFault lengthOfCharactersPrecedingPreamble:0]; 86 | XCTAssertEqualObjects(CompleteFault, cleanedString, @"Failed to repair a damanged closing value tag."); 87 | } 88 | 89 | - (void)testCleansFaultTurncatedIValue 90 | { 91 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 92 | // Test a fault truncated in the value tag 93 | NSString *truncatedFault = [CompleteFault substringWithRange:NSMakeRange(0, [CompleteFault length] - 30)]; 94 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedFault lengthOfCharactersPrecedingPreamble:0]; 95 | XCTAssertEqualObjects(CompleteFault, cleanedString, @"Failed to repair a damanged closing value tag."); 96 | } 97 | 98 | - (void)testCleansFaultTurncatedInFault 99 | { 100 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 101 | // Test a fault truncated in the fault tag 102 | NSString *truncatedFault = [CompleteFault substringWithRange:NSMakeRange(0, [CompleteFault length] - 20)]; 103 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedFault lengthOfCharactersPrecedingPreamble:0]; 104 | XCTAssertEqualObjects(CompleteFault, cleanedString, @"Failed to repair a damanged closing fault tag."); 105 | } 106 | 107 | - (void)testCleansFaultTurncatedInMethodResponse 108 | { 109 | WPXMLRPCDataCleaner *cleaner = [[WPXMLRPCDataCleaner alloc] init]; 110 | // Test a fault truncated in the method response tag 111 | NSString *truncatedFault = [CompleteFault substringWithRange:NSMakeRange(0, [CompleteFault length] - 10)]; 112 | NSString *cleanedString = [cleaner cleanClosingTagIfNeeded:truncatedFault lengthOfCharactersPrecedingPreamble:0]; 113 | XCTAssertEqualObjects(CompleteFault, cleanedString, @"Failed to repair a damanged closing methodResponse tag."); 114 | } 115 | 116 | @end 117 | 118 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/WPXMLRPCDecoderTest.m: -------------------------------------------------------------------------------- 1 | #import "WPXMLRPCDecoder.h" 2 | #import "WPStringUtils.h" 3 | #import "Helper.h" 4 | #import 5 | 6 | @interface WPXMLRPCDecoderTest : XCTestCase 7 | @end 8 | 9 | @implementation WPXMLRPCDecoderTest { 10 | NSDictionary *myTestCases; 11 | } 12 | 13 | - (void)setUp { 14 | myTestCases = [self testCases]; 15 | XCTAssertNotNil(myTestCases); 16 | [NSTimeZone setDefaultTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; 17 | } 18 | 19 | - (void)testEventBasedParser { 20 | NSEnumerator *testCaseEnumerator = [myTestCases keyEnumerator]; 21 | id testCaseName; 22 | 23 | while (testCaseName = [testCaseEnumerator nextObject]) { 24 | if ([testCaseName isEqualToString:@"IncompleteXmlTest"]) { 25 | continue; 26 | } 27 | NSString *testCase = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:testCaseName ofType:@"xml"]; 28 | NSData *testCaseData =[[NSData alloc] initWithContentsOfFile:testCase]; 29 | WPXMLRPCDecoder *decoder = [[WPXMLRPCDecoder alloc] initWithData:testCaseData]; 30 | id testCaseResult = [myTestCases objectForKey:testCaseName]; 31 | id parsedResult = [decoder object]; 32 | 33 | XCTAssertEqualObjects(parsedResult, testCaseResult); 34 | } 35 | } 36 | 37 | - (void)testCTidyDataCleaner { 38 | // Only run if CTidy is available 39 | if (!NSClassFromString(@"CTidy")) { 40 | return; 41 | } 42 | NSString *testCaseName = @"IncompleteXmlTest"; 43 | NSString *testCase = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:testCaseName ofType:@"xml"]; 44 | NSData *testCaseData =[[NSData alloc] initWithContentsOfFile:testCase]; 45 | WPXMLRPCDecoder *decoder = [[WPXMLRPCDecoder alloc] initWithData:testCaseData]; 46 | id testCaseResult = [myTestCases objectForKey:testCaseName]; 47 | id parsedResult = [decoder object]; 48 | 49 | XCTAssertEqualObjects(parsedResult, testCaseResult); 50 | } 51 | 52 | - (void)testNoXmlThrowsError { 53 | NSString *testCase = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"NoXmlResponseTestCase" ofType:@"xml"]; 54 | NSData *testCaseData =[[NSData alloc] initWithContentsOfFile:testCase]; 55 | WPXMLRPCDecoder *decoder = [[WPXMLRPCDecoder alloc] initWithData:testCaseData]; 56 | XCTAssertNil([decoder object]); 57 | XCTAssertNotNil([decoder error]); 58 | NSError *error = [decoder error]; 59 | XCTAssertEqualObjects([error domain], WPXMLRPCErrorDomain); 60 | XCTAssertEqual([error code], WPXMLRPCInvalidInputError); 61 | } 62 | 63 | - (void)testInvalidFaultXmlThrowsError { 64 | NSString *testCase = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"InvalidFault" ofType:@"xml"]; 65 | NSData *testCaseData =[[NSData alloc] initWithContentsOfFile:testCase]; 66 | WPXMLRPCDecoder *decoder = [[WPXMLRPCDecoder alloc] initWithData:testCaseData]; 67 | XCTAssertNotNil([decoder object]); 68 | XCTAssertNotNil([decoder error]); 69 | NSError *error = [decoder error]; 70 | XCTAssertEqualObjects([error domain], WPXMLRPCErrorDomain); 71 | XCTAssertEqual([error code], WPXMLRPCInvalidInputError); 72 | } 73 | 74 | #pragma mark - 75 | 76 | - (NSDictionary *)testCases { 77 | NSString *file = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"TestCases" ofType:@"plist"]; 78 | NSDictionary *testCases = [[NSDictionary alloc] initWithContentsOfFile:file]; 79 | 80 | return testCases; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/WPXMLRPCEncoderTest.m: -------------------------------------------------------------------------------- 1 | #import "WPXMLRPCEncoder.h" 2 | #import "Helper.h" 3 | #import 4 | 5 | @interface WPXMLRPCEncoderTest : XCTestCase 6 | 7 | @end 8 | 9 | @implementation WPXMLRPCEncoderTest 10 | 11 | - (void)testRequestEncoder { 12 | WPXMLRPCEncoder *encoder = [[WPXMLRPCEncoder alloc] initWithMethod:@"wp.getUsersBlogs" andParameters:@[@"username", @"password"]]; 13 | NSString *testCase = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"RequestTestCase" ofType:@"xml"]; 14 | NSString *testCaseData = [[NSString alloc] initWithContentsOfFile:testCase encoding:NSUTF8StringEncoding error:nil]; 15 | NSString *parsedResult = [[NSString alloc] initWithData:[encoder dataEncodedWithError:nil] encoding:NSUTF8StringEncoding]; 16 | XCTAssertEqualObjects(parsedResult, testCaseData); 17 | } 18 | 19 | /* 20 | This is meant to test https://github.com/wordpress-mobile/wpxmlrpc/issues/15 21 | 22 | I haven't found a way to change the locale for testing, so I had to switch the calendar manually on the simulator settings 23 | */ 24 | - (void)testDateEncoder { 25 | WPXMLRPCEncoder *encoder = [[WPXMLRPCEncoder alloc] initWithMethod:@"wp.getUsersBlogs" andParameters:@[[NSDate dateWithTimeIntervalSince1970:0]]]; 26 | NSString *result = [[NSString alloc] initWithData:[encoder dataEncodedWithError:nil] encoding:NSUTF8StringEncoding]; 27 | NSString *expected = @"wp.getUsersBlogs19700101T00:00:00Z"; 28 | XCTAssertEqualObjects(expected, result); 29 | } 30 | 31 | - (void)testStreamingEncoder { 32 | NSString * filePath = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"TestImage" ofType:@"bin"]; 33 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 34 | NSString *directory = [paths objectAtIndex:0]; 35 | NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; 36 | NSString *cacheFilePath = [directory stringByAppendingPathComponent:guid]; 37 | WPXMLRPCEncoder *encoder = [[WPXMLRPCEncoder alloc] initWithMethod:@"wp.getUsersBlogs" andParameters:@[@"username", @"password", @{@"bits": [NSInputStream inputStreamWithFileAtPath:filePath]}]]; 38 | 39 | NSError * error = nil; 40 | 41 | [encoder encodeToFile:cacheFilePath error:&error]; 42 | encoder = nil; 43 | 44 | XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheFilePath isDirectory:nil],@"Cache File must be present"); 45 | 46 | XCTAssertTrue([[NSFileManager defaultManager] removeItemAtPath:cacheFilePath error:nil], @"It must be possible to remove the file"); 47 | 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /WPXMLRPCTest/Tests/WPXMLRPCEntitiesTest.m: -------------------------------------------------------------------------------- 1 | #import "WPXMLRPCDecoder.h" 2 | #import "WPXMLRPCEncoder.h" 3 | #import "WPStringUtils.h" 4 | #import "Helper.h" 5 | 6 | #import 7 | 8 | @interface WPXMLRPCEntitiesTest : XCTestCase 9 | 10 | @end 11 | 12 | @implementation WPXMLRPCEntitiesTest 13 | 14 | - (void)testXMLEntitiesDecoding { 15 | NSString *testCase = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"entities" ofType:@"xml"]; 16 | NSData *testCaseData =[[NSData alloc] initWithContentsOfFile:testCase]; 17 | WPXMLRPCDecoder *decoder = [[WPXMLRPCDecoder alloc] initWithData:testCaseData]; 18 | NSString *decoded = [[decoder object] objectForKey:@"description"]; 19 | 20 | NSString *expectedPath = [[NSBundle wpxmlrpc_assetsBundle] pathForResource:@"entitiesDecoded" ofType:@"xml"]; 21 | NSData *expectedData =[[NSData alloc] initWithContentsOfFile:expectedPath]; 22 | NSString *expected = [[NSString alloc] initWithData:expectedData encoding:NSUTF8StringEncoding]; 23 | XCTAssertEqualObjects(decoded, expected); 24 | 25 | decoded = [WPStringUtils unescapedStringWithString:@"<td>&gt;</td><td>&amp;#62;</td><td>&amp;gt;</td>"]; 26 | expected = @">&#62;&gt;"; 27 | XCTAssertEqualObjects(decoded, expected); 28 | } 29 | 30 | - (void)testXmlEntitiesEncoding { 31 | WPXMLRPCEncoder *encoder = [[WPXMLRPCEncoder alloc] initWithMethod:@"fake.test" andParameters:@[@"<b> tag & "other" \"tags\""]]; 32 | NSString *encoded = [[NSString alloc] initWithData:[encoder dataEncodedWithError:nil] encoding:NSUTF8StringEncoding]; 33 | NSString *expected = @"fake.test<b>&lt;b&gt;</b> tag &amp; &quot;other&quot; \"tags\""; 34 | XCTAssertEqualObjects(encoded, expected); 35 | } 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | default_platform(:ios) 4 | 5 | platform :ios do 6 | desc 'Builds the project and runs tests' 7 | lane :test do 8 | run_tests( 9 | package_path: '.', 10 | scheme: 'wpxmlrpc', 11 | device: 'iPhone 14', 12 | prelaunch_simulator: true, 13 | buildlog_path: File.join(__dir__, '.build', 'logs'), 14 | derived_data_path: File.join(__dir__, '.build', 'derived-data') 15 | ) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /wpxmlrpc.podspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Pod::Spec.new do |s| 4 | s.name = 'wpxmlrpc' 5 | s.version = '0.10.0' 6 | 7 | s.summary = 'Lightweight XML-RPC library.' 8 | s.description = <<-DESC 9 | This framework contains a very lightweight XML-RPC library, allowing you to encode and decode XML-RPC request payloads. 10 | DESC 11 | 12 | s.homepage = 'https://github.com/wordpress-mobile/wpxmlrpc' 13 | s.license = { type: 'MIT', file: 'LICENSE.md' } 14 | s.author = { 'The WordPress Mobile Team' => 'mobile@wordpress.org' } 15 | 16 | s.ios.deployment_target = '11.0' 17 | s.osx.deployment_target = '10.13' 18 | s.swift_version = '5.0' 19 | 20 | s.source = { git: 'https://github.com/wordpress-mobile/wpxmlrpc.git', tag: s.version.to_s } 21 | s.source_files = 'WPXMLRPC' 22 | s.public_header_files = ['WPXMLRPC/WPXMLRPC.h', 'WPXMLRPC/WPXMLRPCEncoder.h', 'WPXMLRPC/WPXMLRPCDecoder.h'] 23 | s.libraries = 'iconv' 24 | 25 | s.test_spec do |test| 26 | test.source_files = 'WPXMLRPCTest/Tests/*.{h,m}' 27 | test.resources = 'WPXMLRPCTest/Test Data/*' 28 | end 29 | end 30 | --------------------------------------------------------------------------------