diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..c3c5275
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,7 @@
+{
+ "rules": {
+ "max-depth": "off",
+ "quote-props": "off",
+ "security/detect-non-literal-regexp": "off"
+ }
+}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 03da80c..9c5bc7d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,7 +1,7 @@
name: Publish
on:
release:
- types: [ published ]
+ types: [ created ]
jobs:
publish:
@@ -10,12 +10,14 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
- name: Setup node
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v2
with:
- node-version: 20
+ node-version: '16'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
@@ -24,5 +26,6 @@ jobs:
- name: Publish to npm
env:
+ GH_TOKEN: ${{ github.token }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm publish --tag ${{ github.event.release.prerelease && 'next' || 'latest' }}
+ run: npm publish
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
deleted file mode 100644
index 3244fa0..0000000
--- a/.github/workflows/test.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Test
-on:
- - pull_request
- - push
-
-jobs:
- test:
- runs-on: macos-latest
- name: Test
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Setup node
- uses: actions/setup-node@v4
- with:
- node-version: 20
- registry-url: 'https://registry.npmjs.org'
-
- - name: Install dependencies
- run: npm ci
- if: steps.node-cache.outputs.cache-hit != 'true'
-
- - name: Run tests
- run: npm test
diff --git a/.gitignore b/.gitignore
index 64b14f6..a48f85c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,34 +1,10 @@
._*
-.DS_Store
-dist
-npm-debug.log
+.DS_Store*
+.nyc_output
+/coverage
+/dist
+/docs
+junit.xml
node_modules
-env.properties
-junit_report.xml
-
-test/TestApp/info.plist
-test/TestApp/build
-test/TestApp/index
-test/TestApp/Logs
-test/TestApp/ModuleCache
-test/TestApp/ModuleCache.noindex
-test/TestApp/TestApp.xcodeproj/xcuserdata
-test/TestApp/TestApp.xcodeproj/project.xcworkspace
-
-test/TestWatchApp/info.plist
-test/TestWatchApp/build
-test/TestWatchApp/index
-test/TestWatchApp/Logs
-test/TestWatchApp/ModuleCache
-test/TestWatchApp/ModuleCache.noindex
-test/TestWatchApp/TestWatchApp.xcodeproj/xcuserdata
-test/TestWatchApp/TestWatchApp.xcodeproj/project.xcworkspace
-
-test/TestWatchApp2/info.plist
-test/TestWatchApp2/build
-test/TestWatchApp2/index
-test/TestWatchApp2/Logs
-test/TestWatchApp2/ModuleCache
-test/TestWatchApp2/ModuleCache.noindex
-test/TestWatchApp2/TestWatchApp2.xcodeproj/xcuserdata
-test/TestWatchApp2/TestWatchApp2.xcodeproj/project.xcworkspace
+npm-debug.log
+yarn-error.log
diff --git a/.npmignore b/.npmignore
index 19dc191..02f0988 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,31 +1,18 @@
._*
+.babelrc
.DS_Store
+.eslintrc
.git*
-/dist
-/node_modules
-/npm-debug.log
-/env.properties
-
-test/TestApp/info.plist
-test/TestApp/build
-test/TestApp/Build
-test/TestApp/Logs
-test/TestApp/ModuleCache
-test/TestApp/TestApp.xcodeproj/xcuserdata
-test/TestApp/TestApp.xcodeproj/project.xcworkspace
-
-test/TestWatchApp/info.plist
-test/TestWatchApp/build
-test/TestWatchApp/Build
-test/TestWatchApp/Logs
-test/TestWatchApp/ModuleCache
-test/TestWatchApp/TestWatchApp.xcodeproj/xcuserdata
-test/TestWatchApp/TestWatchApp.xcodeproj/project.xcworkspace
-
-test/TestWatchApp2/info.plist
-test/TestWatchApp2/build
-test/TestWatchApp2/Build
-test/TestWatchApp2/Logs
-test/TestWatchApp2/ModuleCache
-test/TestWatchApp2/TestWatchApp2.xcodeproj/xcuserdata
-test/TestWatchApp2/TestWatchApp2.xcodeproj/project.xcworkspace
+.nyc_output
+.travis.yml
+/coverage
+/docs
+gulpfile.js
+Jenkinsfile
+junit.xml
+node_modules
+npm-debug.log
+yarn.lock
+yarn-error.log
+/src
+/test
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8dd2c72
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+os: osx
+osx_image: xcode11.6
+xcode_sdk: iphonesimulator13.6
+language: node_js
+node_js:
+ - "10"
+ - "12"
+ - "14"
+sudo: false
+before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH=$HOME/.yarn/bin:$PATH
+cache:
+ yarn: true
+install: yarn
+before_script: sh -c "git log | head -12"
+script: yarn run coverage
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f48a334
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,177 @@
+# v4.2.2 (Jan 5, 2021)
+
+ * chore: Updated dependencies.
+
+# v4.2.1 (Dec 2, 2020)
+
+ * fix: Fixed yarn lockfile.
+
+# v4.2.0 (Dec 2, 2020)
+
+ * chore: Updated dependencies.
+
+# v4.1.0 (Aug 25, 2020)
+
+ * feat(xcode): Added `userLicenseFile` path.
+ * chore: Updated dependencies.
+
+# v4.0.0 (Jun 23, 2020)
+
+ * BREAKING CHANGE: Dropped support for Node.js 10.12 and older. Please use Node.js 10.13.0 LTS or
+ newer.
+ * feat: Added Xcode 12 and iOS 14 to the device pair compatibility table.
+ * feat: Lazy load `node-ios-device` when listing or watching for devices.
+ * chore: Updated dependencies.
+
+# v3.2.5 (Jan 8, 2020)
+
+ * chore: Updated dependencies.
+
+# v3.2.4 (Nov 19, 2019)
+
+ * fix(xcode): Fixed bug where compatible iOS runtime filtering was also being applied to watchOS
+ runtimes causing them to not be listed.
+ [(DAEMON-306)](https://jira.appcelerator.org/browse/DAEMON-306)
+
+# v3.2.3 (Nov 7, 2019)
+
+ * fix(simulator): Fixed watchOS sim semver ranges for device pair compatibility lookup and added a
+ truthiness check in case we ever need to blacklist a version.
+ * fix(simulator): Added `simctl` and `simualator` executables to simulator info handles.
+ * fix(cli): Fixed simulator info to display generated data instead of just the unsorted sims.
+ * fix(xcode): Fix Xcode sim runtime compatiblity lookup.
+ [(TIMOB-27463)](https://jira.appcelerator.org/browse/TIMOB-27463)
+ * chore: Updated dependencies.
+
+# v3.2.2 (Aug 29, 2019)
+
+ * fix: Added support for Apple developer certificates.
+ [(TIMOB-27358)](https://jira.appcelerator.org/browse/TIMOB-27358)
+ * chore: Updated dependencies.
+
+# v3.2.1 (Aug 14, 2019)
+
+ * feat: Registered `ioslib` bin in `package.json`.
+ * chore: Updated dependencies.
+
+# v3.2.0 (Aug 12, 2019)
+
+ * feat: Added `teamId` to certificate info.
+ * chore: Updated dependencies.
+
+# v3.1.1 (Jul 8, 2019)
+
+ * fix: Removed global simulator profiles directory from Xcode `coreSimulatorProfilesPaths`.
+
+# v3.1.0 (Jul 8, 2019)
+
+ * fix: Added new Xcode 11 simulator runtime and device types search paths.
+ * feat: Added `info` and `reset-sims` commands to `ioslib` CLI.
+ * feat: Added `coreSimulatorProfilesPaths` to Xcode info object.
+ [(DAEMON-250)](https://jira.appcelerator.org/browse/DAEMON-250)
+
+# v3.0.0 (Jul 2, 2019)
+
+ * BREAKING CHANGE: Dropped support for Node.js versions before v8.12.0.
+ * BREAKING CHANGE(dep): Upgraded to node-ios-device v2 which dropped support for Node.js 7.x and
+ older.
+ * BREAKING CHANGE(simulator): iOS Simulator watch companion lookup map changed to only have
+ compatible watch simulator UDIDs instead of full descriptor to save on memory.
+ * fix(simulator): Added check for the existence of the simulator device directory before walking.
+ * feat(simulator): Added support for Xcode 11.
+ * chore: Updated dependencies.
+
+# v2.5.1 (Aug 29, 2019)
+
+ * fix: Added support for Apple developer certificates.
+ [(TIMOB-27358)](https://jira.appcelerator.org/browse/TIMOB-27358)
+
+# v2.5.0 (Aug 14, 2019)
+
+ * feat: Added teamId to certificate info.
+ * feat: Added info and reset-sims commands to ioslib CLI.
+ * feat: Added coreSimulatorProfilesPaths to Xcode info object. (DAEMON-250)
+ * feat(simulator): Added support for Xcode 11.
+ * fix: Removed global simulator profiles directory from Xcode coreSimulatorProfilesPaths.
+ * fix: Added new Xcode 11 simulator runtime and device types search paths.
+ * fix(simulator): Added check for the existence of the simulator device directory before walking.
+
+# v2.4.0 (Mar 29, 2019)
+
+ * chore: Updated dependencies.
+
+# v2.3.1 (Jan 25, 2019)
+
+ * chore: Updated dependencies.
+
+# v2.3.0 (Jan 16, 2019)
+
+ * refactor: Upgraded to Gulp 4.
+ * refactor: Refactored promises to use async/await.
+ * fix: Added pluralize dependency since it was removed from snooplogg 2.
+ * chore: Updated dependencies.
+
+# v2.2.3 (Aug 6, 2018)
+
+ * fix: Workaround for sim runtimes that have a bad version number in the runtime's
+ `profile.plist`. [(DAEMON-259)](https://jira.appcelerator.org/browse/DAEMON-259)
+ * refactor: Moved simctl path into executables under xcode info.
+
+# v2.2.2 (Aug 6, 2018)
+
+ * fix: Added path to global Xcode license file.
+ * chore: Updated dependencies.
+
+# v2.2.1 (Jun 11, 2018)
+
+ * feat: Added the `ioslib detect-device-pairs` command.
+ * chore: Updated the device pair compatibility table.
+ * chore: Updated dependencies.
+
+# v2.2.0 (Jun 5, 2018)
+
+ * chore: Added Xcode 10 to device pair lookup.
+ [(TIMOB-26089)](https://jira.appcelerator.org/browse/TIMOB-26089)
+
+# v2.1.0 (May 30, 2018)
+
+ * chore: Updated `ioslib` bin to use `cli-kit`'s help, version, and aliases.
+ * chore: Updated dependencies.
+
+# v2.0.7 (Apr 9, 2018)
+
+ * chore: Updated dependencies.
+
+# v2.0.6 (Dec 14, 2017)
+
+ * fix: Fixed bug where extract teams from provisioning profiles would fail if any provisioning
+ profiles didn't have any associated teams.
+ [(DAEMON-209)](https://jira.appcelerator.org/browse/DAEMON-209)
+
+# v2.0.5 (Dec 12, 2017)
+
+ * chore: Updated dependencies.
+
+# v2.0.4 (Dec 11, 2017)
+
+ * fix: Fixed bug where a failure to parse a cert name would cause no certs to be found and an
+ error to be thrown.
+
+# v2.0.3 (Dec 6, 2017)
+
+ * chore: Updated dependencies.
+
+# v2.0.2 (Nov 22, 2017)
+
+ * chore: Updated dependencies.
+
+# v2.0.1 (Nov 17, 2017)
+
+ * chore: Removed hard coded path that was used for debugging.
+
+# v2.0.0 (Nov 17, 2017)
+
+ * Initial release of the v2 rewrite.
+ * Updated code to ES2015.
+ * Support for detecting Xcode, iOS SDKs, simulators, devices, keychains, certs, provisioning
+ profiles, and teams.
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..b3f24a5
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,7 @@
+#! groovy
+library 'pipeline-library'
+
+runNPMPackage {
+ nodeVersions = [ '10.19.0', '12.18.0', '14.4.0' ]
+ platforms = [ 'osx' ]
+}
diff --git a/LICENSE b/LICENSE
index 904d84b..83175f5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,4 @@
-Copyright TiDev, Inc. 4/7/2022-Present
-Copyright 2014-2020 by Appcelerator, Inc.
+Copyright 2014-2021 by Axway, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -12,34 +11,3 @@ Copyright 2014-2020 by Appcelerator, Inc.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
--------------------------------------------------------------------------------
-
-lib/certs.js contains code from the "forge" project.
-https://github.com/digitalbazaar/forge
-
-New BSD License (3-clause)
-Copyright (c) 2010, Digital Bazaar, Inc.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of Digital Bazaar, Inc. nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index 1ebe74a..cdd190d 100644
--- a/README.md
+++ b/README.md
@@ -1,225 +1,11 @@
# iOS Utility Library
-> This is a library of utilities for dealing programmatically with iOS applications,
-used namely for tools like [Hyperloop](https://github.com/tidev/hyperloop)
-and [Titanium SDK](https://github.com/tidev/titanium-sdk).
-
-ioslib supports Xcode 6 and newer.
-
-## Installation
-
-From NPM:
-
- npm install ioslib
-
-## Examples
-
-### Detect all the connected iOS devices:
-
-```javascript
-var ioslib = require('ioslib');
-
-ioslib.device.detect(function (err, devices) {
- if (err) {
- console.error(err);
- } else {
- console.log(devices);
- }
-});
-```
-
-### Install an application on device
-
-```javascript
-var deviceUDID = null; // string or null to pick first device
-
-ioslib.device.install(deviceUDID, '/path/to/name.app', 'com.company.appname')
- .on('installed', function () {
- console.log('App successfully installed on device');
- })
- .on('appStarted', function () {
- console.log('App has started');
- })
- .on('log', function (msg) {
- console.log('[LOG] ' + msg);
- })
- .on('appQuit', function () {
- console.log('App has quit');
- })
- .on('error', function (err) {
- console.error(err);
- });
-```
-
-### Launch the iOS Simulator
-
-```javascript
-ioslib.simulator.launch(null, function (err, simHandle) {
- console.log('Simulator launched');
- ioslib.simulator.stop(simHandle, function () {
- console.log('Simulator stopped');
- });
-});
-```
-
-### Launch, install, and run an application on simulator
-
-```javascript
-var simUDID = null; // string or null to pick a simulator
-
-ioslib.simulator.launch(simUDID, {
- appPath: '/path/to/name.app'
- })
- .on('launched', function (msg) {
- console.log('Simulator has launched');
- })
- .on('appStarted', function (msg) {
- console.log('App has started');
- })
- .on('log', function (msg) {
- console.log('[LOG] ' + msg);
- })
- .on('error', function (err) {
- console.error(err);
- });
-```
-
-### Force stop an application running on simulator
-
-```javascript
-ioslib.simulator.launch(simUDID, {
- appPath: '/path/to/name.app'
- })
- .on('launched', function (simHandle) {
- console.log('Simulator launched');
- ioslib.simulator.stop(simHandle).on('stopped', function () {
- console.log('Simulator stopped');
- });
- });
-```
-
-### Find a valid device/cert/provisioning profile combination
-
-```javascript
-ioslib.findValidDeviceCertProfileCombos({
- appId: 'com.company.appname'
-}, function (err, results) {
- if (err) {
- console.error(err);
- } else {
- console.log(results);
- }
-});
-```
-
-### Detect everything
-
-```javascript
-ioslib.detect(function (err, info) {
- if (err) {
- console.error(err);
- } else {
- console.log(info);
- }
-});
-```
-
-### Detect iOS certificates
-
-```javascript
-ioslib.certs.detect(function (err, certs) {
- if (err) {
- console.error(err);
- } else {
- console.log(certs);
- }
-});
-```
-
-### Detect provisioning profiles
-
-```javascript
-ioslib.provisioning.detect(function (err, profiles) {
- if (err) {
- console.error(err);
- } else {
- console.log(profiles);
- }
-});
-```
-
-### Detect Xcode installations
-
-```javascript
-ioslib.xcode.detect(function (err, xcodeInfo) {
- if (err) {
- console.error(err);
- } else {
- console.log(xcodeInfo);
- }
-});
-```
-
-## Running Tests
-
-For best results, connect an iOS device.
-
-To run all tests:
-
-```
-npm test
-```
-
-To see debug logging, set the `DEBUG` environment variable:
-
-```
-DEBUG=1 npm test
-```
-
-To run a specific test suite:
-
-```
-npm run-script test-certs
-
-npm run-script test-device
-
-npm run-script test-env
-
-npm run-script test-ioslib
-
-npm run-script test-provisioning
-
-npm run-script test-simulator
-
-npm run-script test-xcode
-```
-
-## Contributing
-
-Interested in contributing? There are several ways you can help contribute to this project.
-
-### New Features, Improvements, Bug Fixes, & Documentation
-
-Source code contributions are always welcome! Before we can accept your pull request, you must sign a Contributor License Agreement (CLA). Please visit https://tidev.io/contribute for more information.
-
-### Donations
-
-Please consider supporting this project by making a charitable [donation](https://tidev.io/donate). The money you donate goes to compensate the skilled engineeers and maintainers that keep this project going.
-
-### Code of Conduct
-
-TiDev wants to provide a safe and welcoming community for everyone to participate. Please see our [Code of Conduct](https://tidev.io/code-of-conduct) that applies to all contributors.
-
-## Security
-
-If you find a security related issue, please send an email to [security@tidev.io](mailto:security@tidev.io) instead of publicly creating a ticket.
-
-## Stay Connected
-
-For the latest information, please find us on Twitter: [Titanium SDK](https://twitter.com/titaniumsdk) and [TiDev](https://twitter.com/tidevio).
-
-Join our growing Slack community by visiting https://slack.tidev.io!
+A suite of iOS development-related functions.
## Legal
-Titanium is a registered trademark of TiDev Inc. All Titanium trademark and patent rights were transferred and assigned to TiDev Inc. on 4/7/2022. Please see the LEGAL information about using our trademarks, privacy policy, terms of usage and other legal information at https://tidev.io/legal.
\ No newline at end of file
+This project is open source under the [Apache Public License v2][1] and is developed by
+[Axway, Inc](http://www.axway.com/) and the community. Please read the [`LICENSE`][1] file included
+in this distribution for more information.
+
+[1]: https://github.com/appcelerator/ioslib/blob/master/LICENSE
diff --git a/bin/ioslib b/bin/ioslib
new file mode 100755
index 0000000..cf081bc
--- /dev/null
+++ b/bin/ioslib
@@ -0,0 +1,316 @@
+#!/usr/bin/env node
+
+const CLI = require('cli-kit').CLI;
+const ioslib = require('../dist/index');
+const pkgJson = require('../package.json');
+const { spawnSync } = require('child_process');
+
+new CLI({
+ banner: `${pkgJson.name}, version ${pkgJson.version}`,
+ commands: {
+ 'detect-device-pairs': {
+ action: detectDevicePairs,
+ args: [
+ {
+ name: 'xcode-path',
+ desc: 'The path to Xcode to use'
+ }
+ ],
+ desc: 'Detects all valid iOS and watchOS simulator pairs'
+ },
+ devices: {
+ async action() {
+ const devices = await ioslib.devices.list();
+ console.log(JSON.stringify(devices, null, ' '));
+ },
+ desc: 'Lists connected devices'
+ },
+ info: {
+ async action({ argv }) {
+ const info = {};
+ const types = argv.types
+ ? Array.from(new Set(argv.types.split(','))).filter(Boolean)
+ : [ 'certs', 'keychains', 'provisioning', 'simulator', 'teams', 'xcode' ];
+ for (const type of types) {
+ switch (type) {
+ case 'certs': info.certs = await ioslib.certs.getCerts(); break;
+ case 'keychains': info.keychains = await ioslib.keychains.getKeychains(); break;
+ case 'provisioning': info.provisioning = await ioslib.provisioning.getProvisioningProfiles(); break;
+ case 'simulator':
+ info.simulator = await ioslib.simulator.generateSimulatorRegistry({
+ simulators: await ioslib.simulator.getSimulators(),
+ xcodes: info.xcode || (info.xcode = (await ioslib.xcode.getXcodes()))
+ });
+ break;
+ case 'teams': info.teams = await ioslib.teams.getTeams(); break;
+ case 'xcode': info.xcode = info.xcode || (await ioslib.xcode.getXcodes()); break;
+ }
+ }
+ console.log(JSON.stringify(info, null, ' '));
+ },
+ aliases: '!detect',
+ args: [
+ {
+ name: 'types',
+ desc: 'Comma-separated list of types to detect'
+ }
+ ],
+ desc: 'Detects installed Xcode and iOS information'
+ },
+ 'reset-sims': {
+ action: resetSims,
+ args: [
+ {
+ name: 'xcode-path',
+ desc: 'The path to Xcode to use'
+ }
+ ],
+ desc: 'Removes all simulators and recreates them'
+ },
+ track: {
+ async action() {
+ const handle = ioslib.devices.trackDevices();
+ handle.on('devices', devices => {
+ console.log(JSON.stringify(devices, null, ' '));
+ console.log();
+ });
+ },
+ aliases: [ '!trackdevices', '!track-devices' ],
+ desc: 'Listens for devices to be connected/disconnected'
+ }
+ },
+ help: true,
+ name: pkgJson.name,
+ version: pkgJson.version
+}).exec()
+ .catch(err => {
+ console.error(err.message);
+ process.exit(err.exitCode || 1);
+ });
+
+function getSimCtl(xcodePath) {
+ const path = require('path');
+ const { existsSync } = require('fs');
+ let last;
+
+ if (xcodePath) {
+ xcodePath = path.join(xcodePath, 'Contents');
+ } else {
+ xcodePath = spawnSync('xcode-select', [ '-p' ]).stdout.toString();
+ }
+
+ while (last !== xcodePath) {
+ if (existsSync(path.join(xcodePath, 'version.plist'))) {
+ simctl = path.join(xcodePath, 'Developer', 'usr', 'bin', 'simctl');
+ break;
+ }
+ last = xcodePath;
+ xcodePath = path.dirname(xcodePath);
+ }
+
+ if (!simctl) {
+ throw new Error('Unable to locate simctl');
+ }
+
+ return simctl;
+}
+
+function detectDevicePairs({ argv }) {
+ const startTime = Date.now();
+ const simctl = getSimCtl(argv.xcodePath);
+ const testSimName = "ioslib_test_sim";
+
+ const getInfo = () => {
+ return JSON.parse(spawnSync(simctl, [ 'list', '--json' ]).stdout.toString());
+ };
+
+ const createSim = (name, deviceTypeId, runtimeId) => {
+ console.log(`Creating ${name} (${deviceTypeId} - ${runtimeId})`);
+ const child = spawnSync(simctl, [ 'create', name, deviceTypeId, runtimeId ]);
+ return child.status === 0 ? child.stdout.toString().trim() : null;
+ };
+
+ const deleteSim = udid => {
+ console.log(`Deleting ${udid}`);
+ return spawnSync(simctl, [ 'delete', udid ]).status === 0;
+ };
+
+ const pair = (watchUdid, phoneUdid) => {
+ process.stdout.write(`Pairing ${watchUdid} -> ${phoneUdid}... `);
+ const child = spawnSync(simctl, [ 'pair', watchUdid, phoneUdid ]);
+ if (child.status === 0) {
+ process.stdout.write('success\n');
+ return child.stdout.toString().trim();
+ }
+ process.stdout.write('failed\n');
+ return null;
+ };
+
+ const unpair = (pairUdid) => {
+ console.log(`Unpairing ${pairUdid}`);
+ return spawnSync(simctl, [ 'unpair', pairUdid ]).status === 0;
+ };
+
+ const cleanup = info => {
+ let deleteCount = 0;
+ for (const ver of Object.keys(info.devices)) {
+ for (const device of info.devices[ver]) {
+ if (device.name.startsWith(testSimName) && deleteSim(device.udid)) {
+ deleteCount++;
+ }
+ }
+ }
+ deleteCount && console.log();
+ };
+
+ const info = getInfo();
+ cleanup(info);
+
+ const iPhoneDeviceTypeRegExp = /\.iPhone-.+$/;
+ const iosSimRuntimeRegExp = /\.iOS-.+$/;
+ const watchDeviceTypeRegExp = /Apple-Watch.+42mm/;
+ const watchSimRuntimeRegExp = /\.watchOS-.+$/;
+
+ const iphoneDeviceTypes = info.devicetypes.filter(s => iPhoneDeviceTypeRegExp.test(s.identifier));
+ const iosRuntimes = info.runtimes.filter(r => iosSimRuntimeRegExp.test(r.identifier));
+ const watchDeviceTypes = info.devicetypes.filter(s => watchDeviceTypeRegExp.test(s.identifier));
+ const watchRuntimes = info.runtimes.filter(r => watchSimRuntimeRegExp.test(r.identifier));
+
+ const watchDevices = [];
+ let results = {};
+
+ const stats = {
+ iPhoneSimsCreated: 0,
+ watchSimsCreated: 0,
+ pairings: 0,
+ pairSuccess: 0
+ };
+
+ // create the watch sims
+ for (const deviceType of watchDeviceTypes) {
+ for (const runtime of watchRuntimes) {
+ const udid = createSim(`${testSimName}_${stats.watchSimsCreated++}`, deviceType.identifier, runtime.identifier);
+ if (udid) {
+ console.log(`Created watch sim ${deviceType.name} + ${runtime.name} (${udid})`);
+ watchDevices.push({
+ udid,
+ deviceType,
+ runtime
+ });
+ }
+ }
+ }
+
+ watchDevices.length && console.log();
+
+ for (const iPhoneDeviceType of iphoneDeviceTypes) {
+ for (const iosRuntime of iosRuntimes) {
+ stats.iPhoneSimsCreated++;
+ const udid = createSim(testSimName, iPhoneDeviceType.identifier, iosRuntime.identifier);
+
+ if (udid) {
+ for (const watch of watchDevices) {
+ stats.pairings++;
+ const pairUdid = pair(watch.udid, udid);
+
+ if (pairUdid) {
+ stats.pairSuccess++;
+ unpair(pairUdid);
+
+ // console.log({
+ // iPhoneDeviceType,
+ // iosRuntime,
+ // watchDeviceType: watch.deviceType,
+ // watchRuntime: watch.runtime
+ // });
+
+ if (!results[iosRuntime.version]) {
+ results[iosRuntime.version] = [];
+ }
+ if (!results[iosRuntime.version].includes(watch.runtime.version)) {
+ results[iosRuntime.version].push(watch.runtime.version);
+ }
+ }
+ }
+
+ deleteSim(udid);
+ }
+ }
+ }
+
+ console.log();
+ cleanup(getInfo());
+
+ // sort the results
+ results = (function (src) {
+ const dest = {};
+ for (const key of Object.keys(src).sort()) {
+ dest[key] = src[key].sort();
+ }
+ return dest;
+ })(results);
+
+ const delta = Date.now() - startTime;
+ const minutes = Math.floor(delta / 60000);
+ const seconds = (delta % 60000) / 1000;
+ console.log(`Completed in ${minutes}m ${seconds}s\n`);
+ console.log(`iPhone Sims Created: ${stats.iPhoneSimsCreated}`);
+ console.log(`Watch Sims Created: ${stats.watchSimsCreated}`);
+ console.log(`Pairings: ${stats.pairings}`);
+ console.log(`Successful Pairings: ${stats.pairSuccess}`);
+ console.log();
+ console.log(results);
+}
+
+function resetSims({ argv }) {
+ const startTime = Date.now();
+ const simctl = getSimCtl(argv.xcodePath);
+ const json = JSON.parse(spawnSync(simctl, [ 'list', '--json' ]).stdout.toString());
+ const stats = {
+ simsRemoved: 0,
+ iPhoneSimsCreated: 0,
+ tvSimsCreated: 0,
+ watchSimsCreated: 0
+ };
+ const runtimeRegExp = /(iOS|tvOS|watchOS)/;
+
+ for (const runtime of Object.keys(json.devices)) {
+ for (const device of json.devices[runtime]) {
+ console.log(`Removing ${device.name} (${device.udid})`);
+ spawnSync(simctl, [ 'delete', device.udid ]);
+ stats.simsRemoved++;
+ }
+ }
+ console.log();
+
+ for (const deviceType of json.devicetypes) {
+ for (const runtime of json.runtimes) {
+ if (runtime.isAvailable || runtime.availability === '(available)') {
+ process.stdout.write(`Creating ${deviceType.name} with ${runtime.name}... `);
+ try {
+ spawnSync(simctl, [ 'create', `${deviceType.name} ${runtime.name}`, deviceType.identifier, runtime.identifier ]);
+ console.log('SUCCESS!');
+
+ const m = runtime.identifier.match(runtimeRegExp);
+ switch (m && m[0]) {
+ case 'iOS': stats.iPhoneSimsCreated++; break;
+ case 'tvOS': stats.tvSimsCreated++; break;
+ case 'watchOS': stats.watchSimsCreated++; break;
+ }
+ } catch (e) {
+ console.log(`FAILED! ${e.toString()}`);
+ }
+ }
+ }
+ }
+ console.log();
+
+ const delta = Date.now() - startTime;
+ const minutes = Math.floor(delta / 60000);
+ const seconds = (delta % 60000) / 1000;
+ console.log(`Completed in ${minutes}m ${seconds}s\n`);
+ console.log(`Sims Removed: ${stats.simsRemoved}`);
+ console.log(`iPhone Sims Created: ${stats.iPhoneSimsCreated}`);
+ console.log(`TV Sims Created: ${stats.tvSimsCreated}`);
+ console.log(`Watch Sims Created: ${stats.watchSimsCreated}`);
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..797c461
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,8 @@
+'use strict';
+
+require('appcd-gulp')({
+ exports,
+ pkgJson: require('./package.json'),
+ template: 'standard',
+ babel: 'node10'
+});
diff --git a/index.js b/index.js
deleted file mode 100644
index c66b04c..0000000
--- a/index.js
+++ /dev/null
@@ -1,208 +0,0 @@
-/**
- * Main namespace for the ioslib.
- *
- * @copyright
- * Copyright (c) 2014-2016 by Appcelerator, Inc. All Rights Reserved.
- *
- * @license
- * Licensed under the terms of the Apache Public License.
- * Please see the LICENSE included with this distribution for details.
- */
-
-const
- async = require('async'),
-
- certs = exports.certs = require('./lib/certs'),
- device = exports.device = require('./lib/device'),
- env = exports.env = require('./lib/env'),
- magik = exports.magik = require('./lib/utilities').magik,
- provisioning = exports.provisioning = require('./lib/provisioning'),
- simulator = exports.simulator = require('./lib/simulator'),
- teams = exports.teams = require('./lib/teams'),
- utilities = exports.utilities = require('./lib/utilities'),
- xcode = exports.xcode = require('./lib/xcode');
-
-var cache;
-
-exports.detect = detect;
-exports.findValidDeviceCertProfileCombos = findValidDeviceCertProfileCombos;
-
-/**
- * Detects the entire iOS environment information.
- *
- * @param {Object} [options] - An object containing various settings.
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects the all iOS information.
- * @param {String} [options.minIosVersion] - The minimum iOS SDK to detect.
- * @param {String} [options.minWatchosVersion] - The minimum WatchOS SDK to detect.
- * @param {String} [options.profileDir=~/Library/Developer/Xcode/UserData/Provisioning Profiles] - The path to search for provisioning profiles.
- * @param {String} [options.security] - Path to the security
executable
- * @param {String} [options.supportedVersions] - A string with a version number or range to check if an Xcode install is supported.
- * @param {String} [options.type] - The type of emulators to return. Can be either "iphone" or "ipad". Defaults to all types.
- * @param {Boolean} [options.validOnly=true] - When true, only returns non-expired, valid certificates.
- * @param {String} [options.xcodeSelect] - Path to the xcode-select
executable
- * @param {Function} [callback(err, info)] - A function to call when all detection tasks have completed.
- */
-function detect(options, callback) {
- return magik(options, callback, function (emitter, options, callback) {
- if (cache && !options.bypassCache) {
- emitter.emit('detected', cache);
- return callback(null, cache);
- }
-
- var results = {
- detectVersion: '5.0',
- issues: []
- };
-
- function mix(src, dest) {
- Object.keys(src).forEach(function (name) {
- if (Array.isArray(src[name])) {
- if (Array.isArray(dest[name])) {
- dest[name] = dest[name].concat(src[name]);
- } else {
- dest[name] = src[name];
- }
- } else if (src[name] !== null && typeof src[name] === 'object') {
- dest[name] || (dest[name] = {});
- Object.keys(src[name]).forEach(function (key) {
- dest[name][key] = src[name][key];
- });
- } else {
- dest[name] = src[name];
- }
- });
- }
-
- async.parallel([
- function detectCertificates(done) {
- certs.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- },
- function detectDevices(done) {
- device.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- },
- function detectEnvironment(done) {
- env.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- },
- function detectProvisioning(done) {
- provisioning.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- },
- function detectSimulator(done) {
- simulator.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- },
- function detectTeams(done) {
- teams.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- },
- function detectXcode(done) {
- xcode.detect(options, function (err, result) {
- err || mix(result, results);
- done(err);
- });
- }
- ], function (err) {
- if (err) {
- emitter.emit('error', err);
- return callback(err);
- } else {
- cache = results;
- emitter.emit('detected', results);
- return callback(null, results);
- }
- });
- });
-};
-
-/**
- * Finds all valid device/cert/provisioning profile combinations. This is handy for quickly
- * finding valid parameters for building an app for an iOS device.
- *
- * @param {Object} [options] - An object containing various settings.
- * @param {String} [options.appId] - The app identifier (com.domain.app) to filter provisioning profiles by.
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects the all iOS information.
- * @param {Boolean} [options.unmanagedProvisioningProfile] - When true, selects an unmanaged provisioning profile.
- * @param {Function} [callback(err, info)] - A function to call when the simulator has launched.
- */
-function findValidDeviceCertProfileCombos(options, callback) {
- if (typeof options === 'function') {
- callback = options;
- options = {};
- } else if (!options) {
- options = {};
- }
- typeof callback === 'function' || (callback = function () {});
-
- // find us a device
- device.detect(function (err, deviceResults) {
- if (!deviceResults.devices.length) {
- // no devices connected
- return callback(new Error('No iOS devices connected'));
- }
-
- // next find us some certs
- certs.detect(function (err, certResults) {
- var certs = [];
- Object.keys(certResults.certs.keychains).forEach(function (keychain) {
- var types = certResults.certs.keychains[keychain];
- Object.keys(types).forEach(function (type) {
- certs = certs.concat(types[type]);
- });
- });
-
- if (!certs.length) {
- return callback(new Error('No iOS certificates'));
- }
-
- // find us a provisioning profile
- provisioning.find({
- appId: options.appId,
- certs: certs,
- devicesUDIDs: deviceResults.devices.map(function (device) { return device.udid; }),
- unmanaged: options.unmanagedProvisioningProfile
- }, function (err, profiles) {
- if (!profiles.length) {
- return callback(new Error('No provisioning profiles found'));
-
- }
-
- var combos = [];
- profiles.forEach(function (profile) {
- deviceResults.devices.forEach(function (device) {
- if (profile.devices && profile.devices.indexOf(device.udid) !== -1) {
- certs.forEach(function (cert) {
- var prefix = cert.pem.replace(/^-----BEGIN CERTIFICATE-----\n/, '').substring(0, 60);
- profile.certs.forEach(function (pcert) {
- if (pcert.indexOf(prefix) === 0) {
- combos.push({
- ppUUID: profile.uuid,
- certName: cert.name,
- deviceUDID: device.udid
- });
- }
- });
- });
- }
- });
- });
-
- callback(null, combos);
- });
- });
- });
-}
diff --git a/lib/certs.js b/lib/certs.js
deleted file mode 100644
index 6c0f514..0000000
--- a/lib/certs.js
+++ /dev/null
@@ -1,1456 +0,0 @@
-/**
- * Detects iOS developer and distribution certificates and the WWDC certificate.
- *
- * @module certs
- *
- * @copyright
- * Copyright (c) 2014-2016 by Appcelerator, Inc. All Rights Reserved.
- *
- * Copyright (c) 2010-2014 Digital Bazaar, Inc.
- * {@link https://github.com/digitalbazaar/forge}
- *
- * @license
- * Licensed under the terms of the Apache Public License.
- * Please see the LICENSE included with this distribution for details.
- */
-
-const
- appc = require('node-appc'),
- async = require('async'),
- env = require('./env'),
- magik = require('./utilities').magik,
- __ = appc.i18n(__dirname).__;
-
-const certRegExp = /^(?:((?:Apple|iOS) Development)|((?:iOS|Apple|iPhone) Distribution)): (.+)$/;
-
-var cache = null,
- watchers = {},
- watchResults = null,
- watchInterval = 60000,
- watchTimer = null;
-
-exports.detect = detect;
-exports.watch = watch;
-exports.unwatch = unwatch;
-
-/**
- * Detects installed certificates.
- *
- * @param {Object} [options] - An object containing various settings.
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects all certificates.
- * @param {Boolean} [options.validOnly=true] - When true, only returns non-expired, valid certificates.
- * @param {Function} [callback(err, results)] - A function to call with the certificate information.
- *
- * @emits module:certs#detected
- * @emits module:certs#error
- *
- * @returns {Handle}
- */
-function detect(options, callback) {
- return magik(options, callback, function (emitter, options, callback) {
- var validOnly = options.validOnly === undefined || options.validOnly === true;
-
- if (cache && !options.bypassCache) {
- emitter.emit('detected', cache);
- return callback(null, cache);
- }
-
- function getCerts(cb) {
- // detect the development environment
- env.detect(options, function (err, env) {
- var results = {
- certs: {
- keychains: {},
- wwdr: false
- },
- issues: []
- };
-
- // if we don't have the security executable, we cannot detect certs
- if (!env.executables.security) {
- return cb(null, results);
- }
-
- appc.subprocess.run(env.executables.security, 'list-keychains', function (code, out, err) {
- if (code) {
- return cb(results);
- }
-
- function parseCerts(src, dest, prefix) {
- var p = 0,
- q = src.indexOf('-----END'),
- pem, cert, validity, expired, invalid, commonName;
-
- while (p !== -1 && q !== -1) {
- pem = src.substring(p, q + 25);
- cert = pem2cert(pem);
- expired = cert.validity.notAfter < now,
- invalid = expired || cert.validity.notBefore > now;
- commonName = cert.subject.getField('CN').value;
- let certName;
-
- if (!prefix) {
- const fullname = appc.encoding.decodeOctalUTF8(commonName);
- if (fullname === 'Apple Worldwide Developer Relations Certification Authority') {
- certName = commonName;
- } else {
- const match = fullname.match(certRegExp);
- if (match) {
- certName = match[3]
- }
- }
- } else {
- certName = appc.encoding.decodeOctalUTF8(commonName.substring(prefix.length)).trim();
- }
-
- if (!validOnly || !invalid) {
- const teamId = cert.subject.attributes.find(attr => attr.name === 'organizationalUnitName');
- dest.push({
- name: certName,
- fullname: appc.encoding.decodeOctalUTF8(commonName).trim(),
- pem: pem,
- before: cert.validity.notBefore,
- after: cert.validity.notAfter,
- expired: expired,
- invalid: invalid,
- teamId: teamId && teamId.value
- });
- }
-
- p = src.indexOf('-----BEGIN', q + 25);
- q = src.indexOf('-----END', p);
- }
- }
-
- var now = new Date,
- tasks = [];
-
- // parse out the keychains and add tasks to find certs for each keychain
- out.split('\n').forEach(function (line) {
- var m = line.match(/[^"]*"([^"]*)"/);
- if (!m) return;
-
- var keychain = m[1].trim(),
- dest = results.certs.keychains[keychain] = {
- developer: [],
- distribution: []
- };
-
- // find all the developer certificates in this keychain
- tasks.push(function (next) {
- appc.subprocess.run(env.executables.security, ['find-certificate', '-c', 'iPhone Developer:', '-a', '-p', keychain], function (code, out, err) {
- if (!code) {
- parseCerts(out, dest.developer, 'iPhone Developer:');
- }
- next();
- });
- });
-
- // find all the developer certificates in this keychain
- tasks.push(function (next) {
- appc.subprocess.run(env.executables.security, ['find-certificate', '-c', 'Development:', '-a', '-p', keychain], function (code, out, err) {
- if (!code) {
- parseCerts(out, dest.developer);
- }
- next();
- });
- });
-
- // find all the distribution certificates in this keychain
- tasks.push(function (next) {
- appc.subprocess.run(env.executables.security, ['find-certificate', '-c', 'Distribution:', '-a', '-p', keychain], function (code, out, err) {
- if (!code) {
- parseCerts(out, dest.distribution);
- }
- next();
- });
- });
-
- // find all the wwdr certificates in this keychain
- tasks.push(function (next) {
- // if we already found it, then skip the remaining keychains
- if (results.certs.wwdr) return next();
-
- appc.subprocess.run(env.executables.security, ['find-certificate', '-c', 'Apple Worldwide Developer Relations Certification Authority', '-a', '-p', keychain], function (code, out, err) {
- if (!code) {
- var tmp = [];
- parseCerts(out, tmp);
- results.certs.wwdr = results.certs.wwdr || (tmp.length && tmp[0].invalid === false);
- }
- next();
- });
- });
- });
-
- // process all cert tasks
- async.parallel(tasks, function () {
- cb(results);
- });
- });
- });
- }
-
- // get all keychains and certs
- getCerts(function (results) {
- detectIssues(results);
- cache = results;
- emitter.emit('detected', results);
- callback(null, results);
- });
- });
-};
-
-function detectIssues(dest) {
- dest.issues = [];
-
- if (!dest.certs.wwdr) {
- dest.issues.push({
- id: 'IOS_NO_WWDR_CERT_FOUND',
- type: 'error',
- message: __('Appleās World Wide Developer Relations (WWDR) intermediate certificate is not installed.') + '\n' +
- __('This will prevent you from building apps for iOS devices or package for distribution.')
- });
- }
-
- if (!Object.keys(dest.certs.keychains).length) {
- // I don't think this is even possible
- dest.issues.push({
- id: 'IOS_NO_KEYCHAINS_FOUND',
- type: 'warning',
- message: __('Unable to find any keychains found.')
- });
- }
-
- var validDevCerts = 0,
- validDistCerts = 0;
-
- Object.keys(dest.certs.keychains).forEach(function (keychain) {
- validDevCerts += (dest.certs.keychains[keychain].developer || []).filter(function (c) {
- return !c.invalid;
- }).length;
-
- validDistCerts += (dest.certs.keychains[keychain].distribution || []).filter(function (c) {
- return !c.invalid;
- }).length;
- });
-
- if (!validDevCerts) {
- dest.issues.push({
- id: 'IOS_NO_VALID_DEV_CERTS_FOUND',
- type: 'warning',
- message: __('Unable to find any valid iOS developer certificates.') + '\n' +
- __('This will prevent you from building apps for iOS devices.')
- });
- }
-
- if (!validDistCerts) {
- dest.issues.push({
- id: 'IOS_NO_VALID_DIST_CERTS_FOUND',
- type: 'warning',
- message: __('Unable to find any valid iOS production distribution certificates.') + '\n' +
- __('This will prevent you from packaging apps for distribution.')
- });
- }
-}
-
-/**
- * Watches for new and changed certificates.
- *
- * @param {Object} [options] - An object containing various settings
- * @param {Boolean} [options.watchInterval=60000] - The number of milliseconds to wait before checking for cert updates
- * @param {Function} [callback(err, results)] - A function to call with the certificate information
- *
- * @returns {Function} A function that unwatches changes.
- */
-function watch(options, callback) {
- if (typeof options === 'function') {
- callback = options;
- options = {};
- } else if (!options) {
- options = {};
- }
-
- watchers[callback] = (watchers[callback] || 0) + 1;
- watchInterval = ~~options.watchInterval || 60000;
-
- // check if already watching or already watching
- if (watchers[callback] === 1 && !watchTimer) {
- options.bypassCache = true;
-
- function check() {
- detect(options, function (err, results) {
- if (!err && (!watchResults || JSON.stringify(watchResults) !== JSON.stringify(results))) {
- watchResults = results;
- return callback(null, results);
- }
- watchTimer = setTimeout(check, watchInterval);
- });
- }
-
- watchTimer = setTimeout(check, watchInterval);
- }
-
- return function () {
- unwatch(callback);
- };
-};
-
-/**
- * Stops watching for certificate changes.
- */
-function unwatch(callback) {
- if (!watchers[callback]) return;
-
- if (--watchers[callback] <= 0) {
- delete watchers[callback];
- }
-
- if (!Object.keys(watchers).length) {
- clearTimeout(watchTimer);
- watchTimer = null;
- }
-};
-
-/*
- * Everything from this point onward is from the forge project (aka node-forge).
- * https://github.com/digitalbazaar/forge
- *
- * New BSD License (3-clause)
- * Copyright (c) 2010, Digital Bazaar, Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of Digital Bazaar, Inc. nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-var typeRegExp = /^(?:X509 |TRUSTED )?CERTIFICATE$/,
- rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g,
- rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/,
- rCRLF = /\r?\n/,
- whitespaceRegExp = /\s/,
- leadingSpaceRegExp = /^\s+/,
- asn1Class = {
- UNIVERSAL: 0x00,
- APPLICATION: 0x40,
- CONTEXT_SPECIFIC: 0x80,
- PRIVATE: 0xC0
- },
- asn1Type = {
- NONE: 0,
- BOOLEAN: 1,
- INTEGER: 2,
- BITSTRING: 3,
- OCTETSTRING: 4,
- NULL: 5,
- OID: 6,
- ODESC: 7,
- EXTERNAL: 8,
- REAL: 9,
- ENUMERATED: 10,
- EMBEDDED: 11,
- UTF8: 12,
- ROID: 13,
- SEQUENCE: 16,
- SET: 17,
- PRINTABLESTRING: 19,
- IA5STRING: 22,
- UTCTIME: 23,
- GENERALIZEDTIME: 24,
- BMPSTRING: 30
- },
- x509CertificateValidator = {
- name: 'Certificate',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- value: [ {
- name: 'Certificate.TBSCertificate',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- captureAsn1: 'tbsCertificate',
- value: [ {
- name: 'Certificate.TBSCertificate.version',
- tagClass: asn1Class.CONTEXT_SPECIFIC,
- type: 0,
- constructed: true,
- optional: true,
- value: [ {
- name: 'Certificate.TBSCertificate.version.integer',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.INTEGER,
- constructed: false,
- capture: 'certVersion'
- } ]
- }, {
- name: 'Certificate.TBSCertificate.serialNumber',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.INTEGER,
- constructed: false,
- capture: 'certSerialNumber'
- }, {
- name: 'Certificate.TBSCertificate.signature',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- value: [ {
- name: 'Certificate.TBSCertificate.signature.algorithm',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.OID,
- constructed: false,
- capture: 'certinfoSignatureOid'
- }, {
- name: 'Certificate.TBSCertificate.signature.parameters',
- tagClass: asn1Class.UNIVERSAL,
- optional: true,
- captureAsn1: 'certinfoSignatureParams'
- } ]
- }, {
- name: 'Certificate.TBSCertificate.issuer',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- captureAsn1: 'certIssuer'
- }, {
- name: 'Certificate.TBSCertificate.validity',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- // Note: UTC and generalized times may both appear so the capture
- // names are based on their detected order, the names used below
- // are only for the common case, which validity time really means
- // "notBefore" and which means "notAfter" will be determined by order
- value: [ {
- // notBefore (Time) (UTC time case)
- name: 'Certificate.TBSCertificate.validity.notBefore (utc)',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.UTCTIME,
- constructed: false,
- optional: true,
- capture: 'certValidity1UTCTime'
- }, {
- // notBefore (Time) (generalized time case)
- name: 'Certificate.TBSCertificate.validity.notBefore (generalized)',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.GENERALIZEDTIME,
- constructed: false,
- optional: true,
- capture: 'certValidity2GeneralizedTime'
- }, {
- // notAfter (Time) (only UTC time is supported)
- name: 'Certificate.TBSCertificate.validity.notAfter (utc)',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.UTCTIME,
- constructed: false,
- optional: true,
- capture: 'certValidity3UTCTime'
- }, {
- // notAfter (Time) (only UTC time is supported)
- name: 'Certificate.TBSCertificate.validity.notAfter (generalized)',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.GENERALIZEDTIME,
- constructed: false,
- optional: true,
- capture: 'certValidity4GeneralizedTime'
- } ]
- }, {
- // Name (subject) (RDNSequence)
- name: 'Certificate.TBSCertificate.subject',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- captureAsn1: 'certSubject'
- }, {
- name: 'SubjectPublicKeyInfo',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- captureAsn1: 'subjectPublicKeyInfo',
- value: [ {
- name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- value: [ {
- name: 'AlgorithmIdentifier.algorithm',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.OID,
- constructed: false,
- capture: 'publicKeyOid'
- } ]
- }, {
- // subjectPublicKey
- name: 'SubjectPublicKeyInfo.subjectPublicKey',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.BITSTRING,
- constructed: false,
- value: [ {
- // RSAPublicKey
- name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- optional: true,
- captureAsn1: 'rsaPublicKey'
- } ]
- } ]
- }, {
- // issuerUniqueID (optional)
- name: 'Certificate.TBSCertificate.issuerUniqueID',
- tagClass: asn1Class.CONTEXT_SPECIFIC,
- type: 1,
- constructed: true,
- optional: true,
- value: [ {
- name: 'Certificate.TBSCertificate.issuerUniqueID.id',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.BITSTRING,
- constructed: false,
- capture: 'certIssuerUniqueId'
- } ]
- }, {
- // subjectUniqueID (optional)
- name: 'Certificate.TBSCertificate.subjectUniqueID',
- tagClass: asn1Class.CONTEXT_SPECIFIC,
- type: 2,
- constructed: true,
- optional: true,
- value: [ {
- name: 'Certificate.TBSCertificate.subjectUniqueID.id',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.BITSTRING,
- constructed: false,
- capture: 'certSubjectUniqueId'
- } ]
- }, {
- // Extensions (optional)
- name: 'Certificate.TBSCertificate.extensions',
- tagClass: asn1Class.CONTEXT_SPECIFIC,
- type: 3,
- constructed: true,
- captureAsn1: 'certExtensions',
- optional: true
- } ]
- }, {
- // AlgorithmIdentifier (signature algorithm)
- name: 'Certificate.signatureAlgorithm',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.SEQUENCE,
- constructed: true,
- value: [ {
- // algorithm
- name: 'Certificate.signatureAlgorithm.algorithm',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.OID,
- constructed: false,
- capture: 'certSignatureOid'
- }, {
- name: 'Certificate.TBSCertificate.signature.parameters',
- tagClass: asn1Class.UNIVERSAL,
- optional: true,
- captureAsn1: 'certSignatureParams'
- } ]
- }, {
- // SignatureValue
- name: 'Certificate.signatureValue',
- tagClass: asn1Class.UNIVERSAL,
- type: asn1Type.BITSTRING,
- constructed: false,
- capture: 'certSignature'
- } ]
- },
- oids = {
- // algorithm OIDs
- '1.2.840.113549.1.1.1': 'rsaEncryption',
- 'rsaEncryption': '1.2.840.113549.1.1.1',
- // Note: md2 & md4 not implemented
- //'1.2.840.113549.1.1.2': 'md2WithRSAEncryption',
- //'md2WithRSAEncryption': '1.2.840.113549.1.1.2',
- //'1.2.840.113549.1.1.3': 'md4WithRSAEncryption',
- //'md4WithRSAEncryption': '1.2.840.113549.1.1.3',
- '1.2.840.113549.1.1.4': 'md5WithRSAEncryption',
- 'md5WithRSAEncryption': '1.2.840.113549.1.1.4',
- '1.2.840.113549.1.1.5': 'sha1WithRSAEncryption',
- 'sha1WithRSAEncryption': '1.2.840.113549.1.1.5',
- '1.2.840.113549.1.1.7': 'RSAES-OAEP',
- 'RSAES-OAEP': '1.2.840.113549.1.1.7',
- '1.2.840.113549.1.1.8': 'mgf1',
- 'mgf1': '1.2.840.113549.1.1.8',
- '1.2.840.113549.1.1.9': 'pSpecified',
- 'pSpecified': '1.2.840.113549.1.1.9',
- '1.2.840.113549.1.1.10': 'RSASSA-PSS',
- 'RSASSA-PSS': '1.2.840.113549.1.1.10',
- '1.2.840.113549.1.1.11': 'sha256WithRSAEncryption',
- 'sha256WithRSAEncryption': '1.2.840.113549.1.1.11',
- '1.2.840.113549.1.1.12': 'sha384WithRSAEncryption',
- 'sha384WithRSAEncryption': '1.2.840.113549.1.1.12',
- '1.2.840.113549.1.1.13': 'sha512WithRSAEncryption',
- 'sha512WithRSAEncryption': '1.2.840.113549.1.1.13',
-
- '1.3.14.3.2.7': 'desCBC',
- 'desCBC': '1.3.14.3.2.7',
-
- '1.3.14.3.2.26': 'sha1',
- 'sha1': '1.3.14.3.2.26',
- '2.16.840.1.101.3.4.2.1': 'sha256',
- 'sha256': '2.16.840.1.101.3.4.2.1',
- '2.16.840.1.101.3.4.2.2': 'sha384',
- 'sha384': '2.16.840.1.101.3.4.2.2',
- '2.16.840.1.101.3.4.2.3': 'sha512',
- 'sha512': '2.16.840.1.101.3.4.2.3',
- '1.2.840.113549.2.5': 'md5',
- 'md5': '1.2.840.113549.2.5',
-
- // pkcs#7 content types
- '1.2.840.113549.1.7.1': 'data',
- 'data': '1.2.840.113549.1.7.1',
- '1.2.840.113549.1.7.2': 'signedData',
- 'signedData': '1.2.840.113549.1.7.2',
- '1.2.840.113549.1.7.3': 'envelopedData',
- 'envelopedData': '1.2.840.113549.1.7.3',
- '1.2.840.113549.1.7.4': 'signedAndEnvelopedData',
- 'signedAndEnvelopedData': '1.2.840.113549.1.7.4',
- '1.2.840.113549.1.7.5': 'digestedData',
- 'digestedData': '1.2.840.113549.1.7.5',
- '1.2.840.113549.1.7.6': 'encryptedData',
- 'encryptedData': '1.2.840.113549.1.7.6',
-
- // pkcs#9 oids
- '1.2.840.113549.1.9.1': 'emailAddress',
- 'emailAddress': '1.2.840.113549.1.9.1',
- '1.2.840.113549.1.9.2': 'unstructuredName',
- 'unstructuredName': '1.2.840.113549.1.9.2',
- '1.2.840.113549.1.9.3': 'contentType',
- 'contentType': '1.2.840.113549.1.9.3',
- '1.2.840.113549.1.9.4': 'messageDigest',
- 'messageDigest': '1.2.840.113549.1.9.4',
- '1.2.840.113549.1.9.5': 'signingTime',
- 'signingTime': '1.2.840.113549.1.9.5',
- '1.2.840.113549.1.9.6': 'counterSignature',
- 'counterSignature': '1.2.840.113549.1.9.6',
- '1.2.840.113549.1.9.7': 'challengePassword',
- 'challengePassword': '1.2.840.113549.1.9.7',
- '1.2.840.113549.1.9.8': 'unstructuredAddress',
- 'unstructuredAddress': '1.2.840.113549.1.9.8',
-
- '1.2.840.113549.1.9.20': 'friendlyName',
- 'friendlyName': '1.2.840.113549.1.9.20',
- '1.2.840.113549.1.9.21': 'localKeyId',
- 'localKeyId': '1.2.840.113549.1.9.21',
- '1.2.840.113549.1.9.22.1': 'x509Certificate',
- 'x509Certificate': '1.2.840.113549.1.9.22.1',
-
- // pkcs#12 safe bags
- '1.2.840.113549.1.12.10.1.1': 'keyBag',
- 'keyBag': '1.2.840.113549.1.12.10.1.1',
- '1.2.840.113549.1.12.10.1.2': 'pkcs8ShroudedKeyBag',
- 'pkcs8ShroudedKeyBag': '1.2.840.113549.1.12.10.1.2',
- '1.2.840.113549.1.12.10.1.3': 'certBag',
- 'certBag': '1.2.840.113549.1.12.10.1.3',
- '1.2.840.113549.1.12.10.1.4': 'crlBag',
- 'crlBag': '1.2.840.113549.1.12.10.1.4',
- '1.2.840.113549.1.12.10.1.5': 'secretBag',
- 'secretBag': '1.2.840.113549.1.12.10.1.5',
- '1.2.840.113549.1.12.10.1.6': 'safeContentsBag',
- 'safeContentsBag': '1.2.840.113549.1.12.10.1.6',
-
- // password-based-encryption for pkcs#12
- '1.2.840.113549.1.5.13': 'pkcs5PBES2',
- 'pkcs5PBES2': '1.2.840.113549.1.5.13',
- '1.2.840.113549.1.5.12': 'pkcs5PBKDF2',
- 'pkcs5PBKDF2': '1.2.840.113549.1.5.12',
-
- '1.2.840.113549.1.12.1.1': 'pbeWithSHAAnd128BitRC4',
- 'pbeWithSHAAnd128BitRC4': '1.2.840.113549.1.12.1.1',
- '1.2.840.113549.1.12.1.2': 'pbeWithSHAAnd40BitRC4',
- 'pbeWithSHAAnd40BitRC4': '1.2.840.113549.1.12.1.2',
- '1.2.840.113549.1.12.1.3': 'pbeWithSHAAnd3-KeyTripleDES-CBC',
- 'pbeWithSHAAnd3-KeyTripleDES-CBC': '1.2.840.113549.1.12.1.3',
- '1.2.840.113549.1.12.1.4': 'pbeWithSHAAnd2-KeyTripleDES-CBC',
- 'pbeWithSHAAnd2-KeyTripleDES-CBC': '1.2.840.113549.1.12.1.4',
- '1.2.840.113549.1.12.1.5': 'pbeWithSHAAnd128BitRC2-CBC',
- 'pbeWithSHAAnd128BitRC2-CBC': '1.2.840.113549.1.12.1.5',
- '1.2.840.113549.1.12.1.6': 'pbewithSHAAnd40BitRC2-CBC',
- 'pbewithSHAAnd40BitRC2-CBC': '1.2.840.113549.1.12.1.6',
-
- // symmetric key algorithm oids
- '1.2.840.113549.3.7': 'des-EDE3-CBC',
- 'des-EDE3-CBC': '1.2.840.113549.3.7',
- '2.16.840.1.101.3.4.1.2': 'aes128-CBC',
- 'aes128-CBC': '2.16.840.1.101.3.4.1.2',
- '2.16.840.1.101.3.4.1.22': 'aes192-CBC',
- 'aes192-CBC': '2.16.840.1.101.3.4.1.22',
- '2.16.840.1.101.3.4.1.42': 'aes256-CBC',
- 'aes256-CBC': '2.16.840.1.101.3.4.1.42',
-
- // certificate issuer/subject OIDs
- '2.5.4.3': 'commonName',
- 'commonName': '2.5.4.3',
- '2.5.4.5': 'serialName',
- 'serialName': '2.5.4.5',
- '2.5.4.6': 'countryName',
- 'countryName': '2.5.4.6',
- '2.5.4.7': 'localityName',
- 'localityName': '2.5.4.7',
- '2.5.4.8': 'stateOrProvinceName',
- 'stateOrProvinceName': '2.5.4.8',
- '2.5.4.10': 'organizationName',
- 'organizationName': '2.5.4.10',
- '2.5.4.11': 'organizationalUnitName',
- 'organizationalUnitName': '2.5.4.11',
-
- // X.509 extension OIDs
- '2.16.840.1.113730.1.1': 'nsCertType',
- 'nsCertType': '2.16.840.1.113730.1.1',
- '2.5.29.1': 'authorityKeyIdentifier', // deprecated, use .35
- '2.5.29.2': 'keyAttributes', // obsolete use .37 or .15
- '2.5.29.3': 'certificatePolicies', // deprecated, use .32
- '2.5.29.4': 'keyUsageRestriction', // obsolete use .37 or .15
- '2.5.29.5': 'policyMapping', // deprecated use .33
- '2.5.29.6': 'subtreesConstraint', // obsolete use .30
- '2.5.29.7': 'subjectAltName', // deprecated use .17
- '2.5.29.8': 'issuerAltName', // deprecated use .18
- '2.5.29.9': 'subjectDirectoryAttributes',
- '2.5.29.10': 'basicConstraints', // deprecated use .19
- '2.5.29.11': 'nameConstraints', // deprecated use .30
- '2.5.29.12': 'policyConstraints', // deprecated use .36
- '2.5.29.13': 'basicConstraints', // deprecated use .19
- '2.5.29.14': 'subjectKeyIdentifier',
- 'subjectKeyIdentifier': '2.5.29.14',
- '2.5.29.15': 'keyUsage',
- 'keyUsage': '2.5.29.15',
- '2.5.29.16': 'privateKeyUsagePeriod',
- '2.5.29.17': 'subjectAltName',
- 'subjectAltName': '2.5.29.17',
- '2.5.29.18': 'issuerAltName',
- 'issuerAltName': '2.5.29.18',
- '2.5.29.19': 'basicConstraints',
- 'basicConstraints': '2.5.29.19',
- '2.5.29.20': 'cRLNumber',
- '2.5.29.21': 'cRLReason',
- '2.5.29.22': 'expirationDate',
- '2.5.29.23': 'instructionCode',
- '2.5.29.24': 'invalidityDate',
- '2.5.29.25': 'cRLDistributionPoints', // deprecated use .31
- '2.5.29.26': 'issuingDistributionPoint', // deprecated use .28
- '2.5.29.27': 'deltaCRLIndicator',
- '2.5.29.28': 'issuingDistributionPoint',
- '2.5.29.29': 'certificateIssuer',
- '2.5.29.30': 'nameConstraints',
- '2.5.29.31': 'cRLDistributionPoints',
- '2.5.29.32': 'certificatePolicies',
- '2.5.29.33': 'policyMappings',
- '2.5.29.34': 'policyConstraints', // deprecated use .36
- '2.5.29.35': 'authorityKeyIdentifier',
- '2.5.29.36': 'policyConstraints',
- '2.5.29.37': 'extKeyUsage',
- 'extKeyUsage': '2.5.29.37',
- '2.5.29.46': 'freshestCRL',
- '2.5.29.54': 'inhibitAnyPolicy',
-
- // extKeyUsage purposes
- '1.3.6.1.5.5.7.3.1': 'serverAuth',
- 'serverAuth': '1.3.6.1.5.5.7.3.1',
- '1.3.6.1.5.5.7.3.2': 'clientAuth',
- 'clientAuth': '1.3.6.1.5.5.7.3.2',
- '1.3.6.1.5.5.7.3.3': 'codeSigning',
- 'codeSigning': '1.3.6.1.5.5.7.3.3',
- '1.3.6.1.5.5.7.3.4': 'emailProtection',
- 'emailProtection': '1.3.6.1.5.5.7.3.4',
- '1.3.6.1.5.5.7.3.8': 'timeStamping',
- 'timeStamping': '1.3.6.1.5.5.7.3.8'
- },
- shortNames = {
- 'CN': oids['commonName'],
- 'commonName': 'CN',
- 'C': oids['countryName'],
- 'countryName': 'C',
- 'L': oids['localityName'],
- 'localityName': 'L',
- 'ST': oids['stateOrProvinceName'],
- 'stateOrProvinceName': 'ST',
- 'O': oids['organizationName'],
- 'organizationName': 'O',
- 'OU': oids['organizationalUnitName'],
- 'organizationalUnitName': 'OU',
- 'E': oids['emailAddress'],
- 'emailAddress': 'E'
- };
-
-function pem2cert(pem) {
- var msg = decodePem(pem)[0];
-
- if (msg.type !== 'CERTIFICATE' && msg.type !== 'X509 CERTIFICATE' && msg.type !== 'TRUSTED CERTIFICATE') {
- throw new Error(__('Could not convert certificate from PEM; PEM header type is "%s", but must be "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".', msg.type));
- }
-
- if (msg.procType && msg.procType.type === 'ENCRYPTED') {
- throw new Error(__('Could not convert certificate from PEM; PEM is encrypted.'));
- }
-
- return asn2cert(der2asn(new ByteStringBuffer(msg.body)));
-}
-
-function decodePem(str) {
- var rval = [],
- match, msg, lines, li, line, nl, next, header, values, vi;
-
- while (true) {
- match = rMessage.exec(str);
- if (!match) {
- break;
- }
-
- rval.push(msg = {
- type: match[1],
- procType: null,
- contentDomain: null,
- dekInfo: null,
- headers: [],
- body: Buffer.from(match[3], 'base64').toString('binary')
- });
-
- // no headers
- if (!match[2]) {
- continue;
- }
-
- // parse headers
- lines = match[2].split(rCRLF);
- for (li = 0; match && li < lines.length; ++li) {
- // get line, trim any rhs whitespace
- line = lines[li].replace(/\s+$/, '');
-
- // RFC2822 unfold any following folded lines
- for (nl = li + 1; nl < lines.length; ++nl) {
- next = lines[nl];
- if (!whitespaceRegExp.test(next[0])) {
- break;
- }
- line += next;
- li = nl;
- }
-
- // parse header
- match = line.match(rHeader);
- if (match) {
- header = {name: match[1], values: []};
- values = match[2].split(',');
- for (vi = 0; vi < values.length; ++vi) {
- header.values.push(values[vi].replace(leadingSpaceRegExp, ''));
- }
-
- // Proc-Type must be the first header
- if (!msg.procType) {
- if (header.name !== 'Proc-Type') {
- throw new Error(__('Invalid PEM formatted message. The first encapsulated header must be "Proc-Type".'));
- } else if (header.values.length !== 2) {
- throw new Error(__('Invalid PEM formatted message. The "Proc-Type" header must have two subfields.'));
- }
- msg.procType = { version: values[0], type: values[1] };
-
- // special-case Content-Domain
- } else if (!msg.contentDomain && header.name === 'Content-Domain') {
- msg.contentDomain = values[0] || '';
-
- // special-case DEK-Info
- } else if (!msg.dekInfo && header.name === 'DEK-Info') {
- if (header.values.length === 0) {
- throw new Error(__('Invalid PEM formatted message. The "DEK-Info" header must have at least one subfield.'));
- }
- msg.dekInfo = { algorithm: values[0], parameters: values[1] || null };
- } else {
- msg.headers.push(header);
- }
- }
- }
-
- if (msg.procType === 'ENCRYPTED' && !msg.dekInfo) {
- throw new Error(__('Invalid PEM formatted message. The "DEK-Info" header must be present if "Proc-Type" is "ENCRYPTED".'));
- }
- }
-
- if (rval.length === 0) {
- throw new Error(__('Invalid PEM formatted message.'));
- }
-
- return rval;
-}
-
-function ByteStringBuffer(str) {
- this.data = str;
- this.read = 0;
-}
-
-ByteStringBuffer.prototype.length = function length() {
- return this.data.length - this.read;
-};
-
-ByteStringBuffer.prototype.getByte = function getByte() {
- return this.data.charCodeAt(this.read++);
-};
-
-ByteStringBuffer.prototype.getInt = function getInt(n) {
- var rval = 0;
- do {
- rval = (rval << 8) + this.data.charCodeAt(this.read++);
- n -= 8;
- } while (n > 0);
- return rval;
-};
-
-ByteStringBuffer.prototype.bytes = function bytes(count) {
- return count === undefined ?
- this.data.slice(this.read) :
- this.data.slice(this.read, this.read + count);
-};
-
-ByteStringBuffer.prototype.getBytes = function getBytes(count) {
- var rval;
- if (count) {
- // read count bytes
- count = Math.min(this.length(), count);
- rval = this.data.slice(this.read, this.read + count);
- this.read += count;
- } else if (count === 0) {
- rval = '';
- } else {
- // read all bytes, optimize to only copy when needed
- rval = this.read === 0 ? this.data : this.data.slice(this.read);
- this.clear();
- }
- return rval;
-};
-
-ByteStringBuffer.prototype.getInt16 = function getInt16() {
- var rval = (this.data.charCodeAt(this.read) << 8 ^ this.data.charCodeAt(this.read + 1));
- this.read += 2;
- return rval;
-};
-
-ByteStringBuffer.prototype.clear = function clear() {
- this.data = '';
- this.read = 0;
- return this;
-};
-
-function der2asn(bytes) {
- // minimum length for ASN.1 DER structure is 2
- if (bytes.length() < 2) {
- throw new Error(__('Too few bytes to parse DER; expected at least 2, got %d', bytes.length()));
- }
-
- // get the first byte
- var b1 = bytes.getByte(),
- // get the tag class
- tagClass = (b1 & 0xC0),
- // get the type (bits 1-5)
- type = b1 & 0x1F,
-
- _getValueLength = function _getValueLength(b) {
- var b2 = b.getByte();
- if (b2 === 0x80) {
- return undefined;
- }
-
- // see if the length is "short form" or "long form" (bit 8 set)
- // if "long form", the number of bytes the length is specified in bits 7 through 1
- // and each length byte is in big-endian base-256
- return b2 & 0x80 ? b.getInt((b2 & 0x7F) << 3) : b2;
- },
-
- // get the value length
- length = _getValueLength(bytes),
- // prepare to get value
- value,
- // constructed flag is bit 6 (32 = 0x20) of the first byte
- constructed = ((b1 & 0x20) === 0x20),
- composed = constructed;
-
- // ensure there are enough bytes to get the value
- if (bytes.length() < length) {
- throw new Error(__('Too few bytes to read ASN.1 value. %d < %d', bytes.length(), length));
- }
-
- // determine if the value is composed of other ASN.1 objects (if its
- // constructed it will be and if its a BITSTRING it may be)
- if (!composed && tagClass === asn1Class.UNIVERSAL && type === asn1Type.BITSTRING && length > 1) {
- /* The first octet gives the number of bits by which the length of the
- bit string is less than the next multiple of eight (this is called
- the "number of unused bits").
-
- The second and following octets give the value of the bit string
- converted to an octet string. */
-
- // if there are no unused bits, maybe the bitstring holds ASN.1 objs
- var read = bytes.read,
- unused = bytes.getByte();
-
- if (unused === 0) {
- // if the first byte indicates UNIVERSAL or CONTEXT_SPECIFIC,
- // and the length is valid, assume we've got an ASN.1 object
- b1 = bytes.getByte();
- var tc = (b1 & 0xC0);
- if (tc === asn1Class.UNIVERSAL || tc === asn1Class.CONTEXT_SPECIFIC) {
- try {
- var len = _getValueLength(bytes);
- composed = (len === length - (bytes.read - read));
- if (composed) {
- // adjust read/length to account for unused bits byte
- ++read;
- --length;
- }
- } catch(ex) {}
- }
- }
- // restore read pointer
- bytes.read = read;
- }
-
- if (composed) {
- // parse child asn1 objects from the value
- value = [];
- if (length === undefined) {
- // asn1 object of indefinite length, read until end tag
- for (;;) {
- if (bytes.bytes(2) === String.fromCharCode(0, 0)) {
- bytes.getBytes(2);
- break;
- }
- value.push(der2asn(bytes));
- }
- } else {
- // parsing asn1 object of definite length
- var start = bytes.length();
- while (length > 0) {
- value.push(der2asn(bytes));
- length -= start - bytes.length();
- start = bytes.length();
- }
- }
- } else {
- // asn1 not composed, get raw value
- // TODO: do DER to OID conversion and vice-versa in .toDer?
-
- if (length === undefined) {
- throw new Error(__('Non-constructed ASN.1 object of indefinite length.'));
- }
-
- if (type === asn1Type.BMPSTRING) {
- value = '';
- for (var i = 0; i < length; i += 2) {
- value += String.fromCharCode(bytes.getInt16());
- }
- } else {
- value = bytes.getBytes(length);
- }
- }
-
- return {
- tagClass: tagClass,
- type: type,
- constructed: constructed,
- composed: constructed || Array.isArray(value),
- value: Array.isArray(value) ? value.filter(function (v) { return v !== undefined; }) : value
- };
-}
-
-function asn1validate(obj, v, capture, errors) {
- var rval = false;
-
- // ensure tag class and type are the same if specified
- if ((obj.tagClass === v.tagClass || v.tagClass === undefined) && (obj.type === v.type || v.type === undefined)) {
- // ensure constructed flag is the same if specified
- if (obj.constructed === v.constructed || v.constructed === undefined) {
- rval = true;
-
- // handle sub values
- if (v.value && Array.isArray(v.value)) {
- var j = 0;
- for (var i = 0; rval && i < v.value.length; ++i) {
- rval = v.value[i].optional || false;
- if (obj.value[j]) {
- rval = asn1validate(obj.value[j], v.value[i], capture, errors);
- if (rval) {
- ++j;
- } else if (v.value[i].optional) {
- rval = true;
- }
- }
- if (!rval && errors) {
- errors.push('[' + v.name + '] Tag class "' + v.tagClass + '", type "' + v.type + '" expected value length "' + v.value.length + '", got "' + obj.value.length + '"');
- }
- }
- }
-
- if (rval && capture) {
- if (v.capture) {
- capture[v.capture] = obj.value;
- }
- if (v.captureAsn1) {
- capture[v.captureAsn1] = obj;
- }
- }
- } else if (errors) {
- errors.push('[' + v.name + '] Expected constructed "' + v.constructed + '", got "' + obj.constructed + '"');
- }
- } else if (errors) {
- if (obj.tagClass !== v.tagClass) {
- errors.push('[' + v.name + '] Expected tag class "' + v.tagClass + '", got "' + obj.tagClass + '"');
- }
- if (obj.type !== v.type) {
- errors.push('[' + v.name + '] Expected type "' + v.type + '", got "' + obj.type + '"');
- }
- }
- return rval;
-}
-
-/**
- * Converts a UTCTime value to a date.
- *
- * Note: GeneralizedTime has 4 digits for the year and is used for X.509
- * dates passed 2049. Parsing that structure hasn't been implemented yet.
- *
- * @param utc the UTCTime value to convert.
- *
- * @return the date.
- */
-function asn1utcTimeToDate(utc) {
- /*
- The following formats can be used:
- YYMMDDhhmmZ
- YYMMDDhhmm+hh'mm'
- YYMMDDhhmm-hh'mm'
- YYMMDDhhmmssZ
- YYMMDDhhmmss+hh'mm'
- YYMMDDhhmmss-hh'mm'
-
- Where:
- YY is the least significant two digits of the year
- MM is the month (01 to 12)
- DD is the day (01 to 31)
- hh is the hour (00 to 23)
- mm are the minutes (00 to 59)
- ss are the seconds (00 to 59)
- Z indicates that local time is GMT, + indicates that local time is
- later than GMT, and - indicates that local time is earlier than GMT
- hh' is the absolute value of the offset from GMT in hours
- mm' is the absolute value of the offset from GMT in minutes
- */
-
- var date = new Date;
-
- // if YY >= 50 use 19xx, if YY < 50 use 20xx
- var year = parseInt(utc.substr(0, 2), 10);
- year = (year >= 50) ? 1900 + year : 2000 + year;
- var MM = parseInt(utc.substr(2, 2), 10) - 1; // use 0-11 for month
- var DD = parseInt(utc.substr(4, 2), 10);
- var hh = parseInt(utc.substr(6, 2), 10);
- var mm = parseInt(utc.substr(8, 2), 10);
- var ss = 0;
-
- // not just YYMMDDhhmmZ
- if (utc.length > 11) {
- // get character after minutes
- var c = utc.charAt(10);
- var end = 10;
-
- // see if seconds are present
- if (c !== '+' && c !== '-') {
- // get seconds
- ss = parseInt(utc.substr(10, 2), 10);
- end += 2;
- }
- }
-
- // update date
- date.setUTCFullYear(year, MM, DD);
- date.setUTCHours(hh, mm, ss, 0);
-
- if (end) {
- // get +/- after end of time
- c = utc.charAt(end);
- if (c === '+' || c === '-') {
- // get hours+minutes offset
- var hhoffset = parseInt(utc.substr(end + 1, 2), 10);
- var mmoffset = parseInt(utc.substr(end + 4, 2), 10);
-
- // calculate offset in milliseconds
- var offset = hhoffset * 60 + mmoffset;
- offset *= 60000;
-
- // apply offset
- if (c === '+') {
- date.setTime(+date - offset);
- } else {
- date.setTime(+date + offset);
- }
- }
- }
-
- return date;
-}
-
-/**
- * Converts a GeneralizedTime value to a date.
- *
- * @param gentime the GeneralizedTime value to convert.
- *
- * @return the date.
- */
-function asn1generalizedTimeToDate(gentime) {
- /*
- The following formats can be used:
- YYYYMMDDHHMMSS
- YYYYMMDDHHMMSS.fff
- YYYYMMDDHHMMSSZ
- YYYYMMDDHHMMSS.fffZ
- YYYYMMDDHHMMSS+hh'mm'
- YYYYMMDDHHMMSS.fff+hh'mm'
- YYYYMMDDHHMMSS-hh'mm'
- YYYYMMDDHHMMSS.fff-hh'mm'
-
- Where:
- YYYY is the year
- MM is the month (01 to 12)
- DD is the day (01 to 31)
- hh is the hour (00 to 23)
- mm are the minutes (00 to 59)
- ss are the seconds (00 to 59)
- .fff is the second fraction, accurate to three decimal places
- Z indicates that local time is GMT, + indicates that local time is
- later than GMT, and - indicates that local time is earlier than GMT
- hh' is the absolute value of the offset from GMT in hours
- mm' is the absolute value of the offset from GMT in minutes
- */
-
- var date = new Date,
- YYYY = parseInt(gentime.substr(0, 4), 10),
- MM = parseInt(gentime.substr(4, 2), 10) - 1, // use 0-11 for month
- DD = parseInt(gentime.substr(6, 2), 10),
- hh = parseInt(gentime.substr(8, 2), 10),
- mm = parseInt(gentime.substr(10, 2), 10),
- ss = parseInt(gentime.substr(12, 2), 10),
- fff = 0,
- offset = 0,
- isUTC = false;
-
- if (gentime.charAt(gentime.length - 1) === 'Z') {
- isUTC = true;
- }
-
- var end = gentime.length - 5,
- c = gentime.charAt(end);
-
- if (c === '+' || c === '-') {
- // get hours+minutes offset
- var hhoffset = parseInt(gentime.substr(end + 1, 2), 10);
- var mmoffset = parseInt(gentime.substr(end + 4, 2), 10);
-
- // calculate offset in milliseconds
- offset = hhoffset * 60 + mmoffset;
- offset *= 60000;
-
- // apply offset
- if(c === '+') {
- offset *= -1;
- }
-
- isUTC = true;
- }
-
- // check for second fraction
- if(gentime.charAt(14) === '.') {
- fff = parseFloat(gentime.substr(14), 10) * 1000;
- }
-
- if(isUTC) {
- date.setUTCFullYear(YYYY, MM, DD);
- date.setUTCHours(hh, mm, ss, fff);
-
- // apply offset
- date.setTime(+date + offset);
- } else {
- date.setFullYear(YYYY, MM, DD);
- date.setHours(hh, mm, ss, fff);
- }
-
- return date;
-}
-
-/**
- * Converts a DER-encoded byte buffer to an OID dot-separated string. The
- * byte buffer should contain only the DER-encoded value, not any tag or
- * length bytes.
- *
- * @param bytes the byte buffer.
- *
- * @return the OID dot-separated string.
- */
-function asn1derToOid(bytes) {
- var oid;
-
- // wrap in buffer if needed
- if (typeof bytes === 'string') {
- bytes = new ByteStringBuffer(bytes);
- }
-
- // first byte is 40 * value1 + value2
- var b = bytes.getByte();
- oid = Math.floor(b / 40) + '.' + (b % 40);
-
- // other bytes are each value in base 128 with 8th bit set except for
- // the last byte for each value
- var value = 0;
- while (bytes.length() > 0) {
- b = bytes.getByte();
- value = value << 7;
- // not the last byte for the value
- if (b & 0x80) {
- value += b & 0x7F;
- } else {
- // last byte
- oid += '.' + (value + b);
- value = 0;
- }
- }
-
- return oid;
-}
-
-/**
- * Converts an RDNSequence of ASN.1 DER-encoded RelativeDistinguishedName
- * sets into an array with objects that have type and value properties.
- *
- * @param rdn the RDNSequence to convert.
- * @param md a message digest to append type and value to if provided.
- */
-function pkiRDNAttributesAsArray(rdn, md) {
- // each value in 'rdn' in is a SET of RelativeDistinguishedName
- var rval = [],
- si, i, set, attr, obj;
- for (si = 0; si < rdn.value.length; ++si) {
- // get the RelativeDistinguishedName set
- set = rdn.value[si];
-
- // each value in the SET is an AttributeTypeAndValue sequence
- // containing first a type (an OID) and second a value (defined by
- // the OID)
- for (i = 0; i < set.value.length; ++i) {
- obj = {};
- attr = set.value[i];
- obj.type = asn1derToOid(attr.value[0].value);
- obj.value = attr.value[1].value;
- obj.valueTagClass = attr.value[1].type;
- // if the OID is known, get its name and short name
- if (obj.type in oids) {
- obj.name = oids[obj.type];
- if (obj.name in shortNames) {
- obj.shortName = shortNames[obj.name];
- }
- }
- if (md) {
- md.update(obj.type);
- md.update(obj.value);
- }
- rval.push(obj);
- }
- }
-
- return rval;
-}
-
-function asn2cert(obj) {
- // validate certificate and capture data
- var capture = {},
- errors = [];
-
- if (!asn1validate(obj, x509CertificateValidator, capture, errors)) {
- var error = new Error(__('Cannot read X.509 certificate. ASN.1 object is not an X509v3 Certificate.'));
- error.errors = errors;
- throw error;
- }
-
- function _getAttribute(obj, options) {
- if (typeof options === 'string') {
- options = { shortName: options };
- }
- var rval = null,
- attr;
- for (var i = 0; rval === null && i < obj.attributes.length; ++i) {
- attr = obj.attributes[i];
- if (options.type && options.type === attr.type) {
- rval = attr;
- } else if (options.name && options.name === attr.name) {
- rval = attr;
- } else if (options.shortName && options.shortName === attr.shortName) {
- rval = attr;
- }
- }
- return rval;
- }
-
- var subject = {
- attributes: pkiRDNAttributesAsArray(capture.certSubject),
- getField: function (sn) {
- return _getAttribute(subject, sn);
- }
- },
- validity = [];
-
- if (capture.certValidity1UTCTime !== undefined) {
- validity.push(asn1utcTimeToDate(capture.certValidity1UTCTime));
- }
- if (capture.certValidity2GeneralizedTime !== undefined) {
- validity.push(asn1generalizedTimeToDate(capture.certValidity2GeneralizedTime));
- }
- if (capture.certValidity3UTCTime !== undefined) {
- validity.push(asn1utcTimeToDate(capture.certValidity3UTCTime));
- }
- if (capture.certValidity4GeneralizedTime !== undefined) {
- validity.push(asn1generalizedTimeToDate(capture.certValidity4GeneralizedTime));
- }
- if (validity.length > 2) {
- throw new Error(__('Cannot read notBefore/notAfter validity times; more than two times were provided in the certificate.'));
- }
- if (validity.length < 2) {
- throw new Error(__('Cannot read notBefore/notAfter validity times; they were not provided as either UTCTime or GeneralizedTime.'));
- }
-
- return {
- validity: {
- notBefore: validity[0],
- notAfter: validity[1]
- },
- subject: subject
- };
-}
-
-/*
- * If the app exits, close all filesystem watchers.
- */
-process.on('exit', function () {
- if (watchTimer) {
- clearTimeout(watchTimer);
- watchTimer = null;
- }
-});
diff --git a/lib/device.js b/lib/device.js
deleted file mode 100644
index b45573c..0000000
--- a/lib/device.js
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * Detects iOS developer and distribution certificates and the WWDC certificate.
- *
- * @module device
- *
- * @copyright
- * Copyright (c) 2014-2016 by Appcelerator, Inc. All Rights Reserved.
- *
- * @license
- * Licensed under the terms of the Apache Public License.
- * Please see the LICENSE included with this distribution for details.
- */
-
-'use strict';
-
-const appc = require('node-appc');
-const async = require('async');
-const magik = require('./utilities').magik;
-const fs = require('fs');
-const iosDevice = require('node-ios-device');
-const path = require('path');
-const __ = appc.i18n(__dirname).__;
-
-var cache;
-
-exports.detect = detect;
-exports.install = install;
-
-/**
- * Detects connected iOS devices.
- *
- * @param {Object} [options] - An object containing various settings.
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects all connected iOS devices.
- * @param {Function} [callback(err, results)] - A function to call with the device information.
- *
- * @emits module:device#detected
- * @emits module:device#error
- *
- * @returns {Handle}
- */
-function detect(options, callback) {
- return magik(options, callback, function (handle, options, callback) {
- if (cache && !options.bypassCache) {
- var dupe = JSON.parse(JSON.stringify(cache));
- handle.emit('detected', dupe);
- return callback(null, dupe);
- }
-
- iosDevice.devices(function (err, devices) {
- if (err) {
- handle.emit('error', err);
- return callback(err);
- }
-
- var results = {
- devices: devices,
- issues: []
- };
-
- // the cache must be a clean copy that we'll clone for subsequent detect() calls
- // because we can't allow the cache to be modified by reference
- cache = JSON.parse(JSON.stringify(results));
-
- handle.emit('detected', results);
- return callback(null, results);
- });
- });
-};
-
-/**
- * Installs the specified app to an iOS device.
- *
- * @param {String} udid - The UDID of the device to install the app to or null if you want ioslib to pick one.
- * @param {String} appPath - The path to the iOS app to install after launching the iOS Simulator.
- * @param {Object} [options] - An object containing various settings.
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects all iOS simulators.
- * @param {Number} [options.logPort] - A port to connect to in the iOS app and relay log messages from.
- * @param {Number} [options.timeout] - Number of milliseconds to wait before timing out.
- *
- * @emits module:device#app-quit - Only omitted when `options.logPort` is specified and app starts a TCP server.
- * @emits module:device#app-started - Only omitted when `options.logPort` is specified and app starts a TCP server.
- * @emits module:device#disconnect - Only omitted when `options.logPort` is specified and app starts a TCP server.
- * @emits module:device#error
- * @emits module:device#installed
- * @emits module:device#log - Only omitted when `options.logPort` is specified and app starts a TCP server.
- *
- * @returns {Handle}
- */
-function install(udid, appPath, options) {
- return magik(options, null, function (handle, options) {
- if (!appPath) {
- return handle.emit('error', new Error(__('Missing app path argument')));
- }
-
- if (!fs.existsSync(appPath)) {
- return handle.emit('error', new Error(__('App path does not exist: ' + appPath)));
- }
-
- handle.stop = function () {}; // for stopping logging
-
- iosDevice.installApp(udid, appPath, function (err) {
- if (err) {
- return handle.emit('error', err);
- }
-
- handle.emit('installed');
-
- if (options.logPort) {
- var logHandle = iosDevice
- .log(udid, options.logPort)
- .on('log', function (msg) {
- handle.emit('log', msg);
- })
- .on('app-started', function () {
- handle.emit('app-started');
- })
- .on('app-quit', function () {
- handle.emit('app-quit');
- })
- .on('disconnect', function () {
- handle.emit('disconnect');
- })
- .on('error', function (err) {
- handle.emit('log-error', err);
- });
-
- handle.stop = function () {
- logHandle.stop();
- };
- }
- });
- });
-}
diff --git a/lib/env.js b/lib/env.js
deleted file mode 100644
index b5e4df6..0000000
--- a/lib/env.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * Detects the iOS development environment.
- *
- * @module env
- *
- * @copyright
- * Copyright (c) 2014-2017 by Appcelerator, Inc. All Rights Reserved.
- *
- * @license
- * Licensed under the terms of the Apache Public License.
- * Please see the LICENSE included with this distribution for details.
- */
-
-const
- appc = require('node-appc'),
- async = require('async'),
- magik = require('./utilities').magik,
- __ = appc.i18n(__dirname).__;
-
-var cache = null;
-
-/**
- * Fired when the developer profiles have been updated.
- * @event module:env#detected
- * @type {Object}
- */
-
-/**
- * Fired when there was an error retreiving the provisioning profiles.
- * @event module:env#error
- * @type {Error}
- */
-
-/**
- * Detects the iOS development enviroment dependencies.
- *
- * @param {Object} [options] - An object containing various settings
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects the development environment dependencies
- * @param {String} [options.security] - Path to the security
executable
- * @param {String} [options.xcodeSelect] - Path to the xcode-select
executable
- * @param {Function} [callback(err, results)] - A function to call with the development environment information
- *
- * @emits module:env#detected
- * @emits module:env#error
- *
- * @returns {Handle}
- */
-exports.detect = function detect(options, callback) {
- return magik(options, callback, function (emitter, options, callback) {
- if (cache && !options.bypassCache) {
- return callback(null, cache);
- }
-
- var results = {
- executables: {
- xcodeSelect: null,
- security: null
- },
- issues: []
- };
-
- async.parallel({
- security: function (next) {
- appc.subprocess.findExecutable([options.security, '/usr/bin/security', 'security'], function (err, result) {
- if (err) {
- results.issues.push({
- id: 'IOS_SECURITY_EXECUTABLE_NOT_FOUND',
- type: 'error',
- message: __("Unable to find the 'security' executable.") + '\n'
- + __('Please verify your system path.') + '\n'
- + __("This program is distributed with macOS and if it's missing, you'll have to restore it from a backup or another computer, or reinstall macOS.")
- });
- } else {
- results.executables.security = result;
- }
- next();
- });
- },
-
- xcodeSelect: function (next) {
- appc.subprocess.findExecutable([options.xcodeSelect, '/usr/bin/xcode-select', 'xcode-select'], function (err, result) {
- if (err) {
- results.issues.push({
- id: 'IOS_XCODE_SELECT_EXECUTABLE_NOT_FOUND',
- type: 'error',
- message: __("Unable to find the 'xcode-select' executable.") + '\n'
- + __('Perhaps Xcode is not installed, your Xcode installation is corrupt, or your system path is incomplete.')
- });
- } else {
- results.executables.xcodeSelect = result;
- }
- next();
- });
- }
- }, function () {
- cache = results;
- emitter.emit('detected', results);
- callback(null, results);
- });
- });
-};
diff --git a/lib/provisioning.js b/lib/provisioning.js
deleted file mode 100644
index 41c0011..0000000
--- a/lib/provisioning.js
+++ /dev/null
@@ -1,413 +0,0 @@
-/**
- * Detects provisioning profiles.
- *
- * @module provisioning
- *
- * @copyright
- * Copyright (c) 2014-2016 by Appcelerator, Inc. All Rights Reserved.
- *
- * @license
- * Licensed under the terms of the Apache Public License.
- * Please see the LICENSE included with this distribution for details.
- *
- * @requires certs
- */
-
-const
- appc = require('node-appc'),
- certs = require('./certs'),
- magik = require('./utilities').magik,
- fs = require('fs'),
- path = require('path'),
- __ = appc.i18n(__dirname).__,
- provisioningProfilesDirectories = [
- '~/Library/Developer/Xcode/UserData/Provisioning Profiles',
- '~/Library/MobileDevice/Provisioning Profiles'
- ]
-
-var cache = null,
- watchers = {};
-
-/**
- * Fired when the provisioning profiles have been detected or updated.
- * @event module:provisioning#detected
- * @type {Object}
- */
-
-/**
- * Fired when there was an error retreiving the provisioning profiles.
- * @event module:provisioning#error
- * @type {Error}
- */
-
-exports.detect = detect;
-exports.find = find;
-exports.watch = watch;
-exports.unwatch = unwatch;
-
-/**
- * Detects installed provisioning profiles.
- *
- * @param {Object} [options] - An object containing various settings.
- * @param {Boolean} [options.bypassCache=false] - When true, re-detects all provisioning profiles.
- * @param {String} [options.profileDir=~/Library/Developer/Xcode/UserData/Provisioning Profiles] - The path to search for provisioning profiles.
- * @param {Boolean} [options.unmanaged] - When true, excludes managed provisioning profiles.
- * @param {Boolean} [options.validOnly=true] - When true, only returns non-expired, valid provisioning profiles.
- * @param {Boolean} [options.watch=false] - If true, watches the specified provisioning profile directory for updates.
- * @param {Function} [callback(err, results)] - A function to call with the provisioning profile information.
- *
- * @emits module:provisioning#detected
- * @emits module:provisioning#error
- *
- * @returns {Handle}
- */
-function detect(options, callback) {
- return magik(options, callback, function (emitter, options, callback) {
- var files = {},
- validOnly = options.validOnly === undefined || options.validOnly === true,
- profileDirs = getExistingProvisioningProfileDirectories(options.profileDir),
- results = {
- provisioning: {
- profileDir: profileDirs[0],
- development: [],
- adhoc: [],
- enterprise: [],
- distribution: [],
- },
- issues: []
- },
- valid = {
- development: 0,
- adhoc: 0,
- enterprise: 0,
- distribution: 0
- },
-
- ppRegExp = /.*\.(mobileprovision|provisionprofile)$/;
-
-
- if (options.watch) {
- var throttleTimer = null;
-
- for (const profileDir of profileDirs) {
- if (!watchers[profileDir]) {
- watchers[profileDir] = {
- handle: fs.watch(profileDir, { persistent: false }, function (event, filename) {
- if (!ppRegExp.test(filename)) {
- // if it's not a provisioning profile, we don't care about it
- return;
- }
-
- var file = path.join(profileDir, filename);
-
- if (event === 'rename') {
- if (files[file]) {
- if (fs.existsSync(file)) {
- // change, reload the provisioning profile
- parseProfile(file);
- } else {
- // delete
- removeProfile(file);
- }
- } else {
- // add
- parseProfile(file);
- }
- } else if (event === 'change') {
- // updated
- parseProfile(file);
- }
-
- clearTimeout(throttleTimer);
-
- throttleTimer = setTimeout(function () {
- detectIssues();
- emitter.emit('detected', results);
- }, 250);
- }),
- count: 0
- };
- }
-
- watchers[profileDir].count++;
- }
- }
-
- if (cache && !options.bypassCache) {
- emitter.emit('detected', cache);
- return callback(null, cache);
- }
-
- function detectIssues() {
- results.issues = [];
-
- if (results.provisioning.development.length > 0 && !valid.development) {
- results.issues.push({
- id: 'IOS_NO_VALID_DEVELOPMENT_PROVISIONING_PROFILES',
- type: 'warning',
- message: __('Unable to find any valid iOS development provisioning profiles.') + '\n' +
- __('This will prevent you from building apps for testing on iOS devices.')
- });
- }
-
- if (results.provisioning.adhoc.length > 0 && !valid.adhoc) {
- results.issues.push({
- id: 'IOS_NO_VALID_ADHOC_PROVISIONING_PROFILES',
- type: 'warning',
- message: __('Unable to find any valid iOS adhoc provisioning profiles.') + '\n' +
- __('This will prevent you from packaging apps for adhoc distribution.')
- });
- }
-
- if (results.provisioning.distribution.length > 0 && !valid.distribution) {
- results.issues.push({
- id: 'IOS_NO_VALID_DISTRIBUTION_PROVISIONING_PROFILES',
- type: 'warning',
- message: __('Unable to find any valid iOS distribution provisioning profiles.') + '\n' +
- __('This will prevent you from packaging apps for AppStore distribution.')
- });
- }
- }
-
- function removeProfile(file) {
- var r = results[files[file]],
- i = 0,
- l = r.length;
- for (; i < l; i++) {
- if (r[i].file === file) {
- r.splice(i, 1);
- break;
- }
- }
- delete files[file];
- }
-
- function parseProfile(file) {
- if (!fs.existsSync(file)) {
- return;
- }
-
- var contents = fs.readFileSync(file).toString(),
- i = contents.indexOf('');
-
- if (j === -1) return;
-
- var plist = new appc.plist().parse(contents.substring(i, j + 8)),
- dest = 'development', // debug
- appPrefix = (plist.ApplicationIdentifierPrefix || []).shift(),
- entitlements = plist.Entitlements || {},
- expired = false;
-
- if (plist.ProvisionedDevices) {
- if (!entitlements['get-task-allow']) {
- dest = 'adhoc';
- }
- } else if (plist.ProvisionsAllDevices) {
- dest = 'enterprise';
- } else {
- dest = 'distribution'; // app store
- }
-
- try {
- if (plist.ExpirationDate) {
- expired = new Date(plist.ExpirationDate) < new Date;
- }
- } catch (e) {}
-
- if (!expired) {
- valid[dest]++;
- }
-
- // store which bucket the provisioning profile is in
- files[file] && removeProfile(file);
- files[file] = dest;
-
- var managed = plist.Name.indexOf('iOS Team Provisioning Profile') !== -1;
-
- if ((!validOnly || !expired) && (!options.unmanaged || !managed)) {
- results.provisioning[dest].push({
- file: file,
- uuid: plist.UUID,
- name: plist.Name,
- managed: managed,
- appPrefix: appPrefix,
- creationDate: plist.CreationDate,
- expirationDate: plist.ExpirationDate,
- expired: expired,
- certs: Array.isArray(plist.DeveloperCertificates)
- ? plist.DeveloperCertificates.map(function (cert) { return cert.value; })
- : null,
- devices: plist.ProvisionedDevices || null,
- team: plist.TeamIdentifier || null,
- entitlements: entitlements,
- // TODO: remove all of the entitlements below and just use the `entitlements` property
- appId: (entitlements['application-identifier'] || entitlements['com.apple.application-identifier'] || '').replace(appPrefix + '.', ''),
- getTaskAllow: !!entitlements['get-task-allow'],
- apsEnvironment: entitlements['aps-environment'] || ''
- });
- }
- }
-
- for (const profileDir of profileDirs) {
- fs.readdirSync(profileDir).forEach(function (name) {
- ppRegExp.test(name) && parseProfile(path.join(profileDir, name));
- });
- }
-
- detectIssues();
- cache = results;
- emitter.emit('detected', results);
- return callback(null, results);
- });
-};
-
-/**
- * Finds all provisioning profiles that match the specified developer cert name
- * and iOS device UDID.
- *
- * @param {Object} [options] - An object containing various settings.
- * @param {String} [options.appId] - The app identifier (com.domain.app) to filter by.
- * @param {Object|Array