From 962764c034e410eddc69dfa5cb2dac4f7ea650ac Mon Sep 17 00:00:00 2001 From: jefklak Date: Thu, 6 Feb 2014 19:23:06 +0100 Subject: [PATCH] initial commit --- .DS_Store | Bin 0 -> 6148 bytes cam/index.html | 67 + cam/index.json | 1 + cam/lazyload.js | 239 +++ clientcam.html | 73 + compress.js | 6 + ftpindex.js | 32 + image.jpg | Bin 0 -> 339417 bytes index.json | 1 + node_modules/.bin/wscat | 1 + node_modules/jsftp/.idea/.name | 1 + .../jsftp/.idea/codeStyleSettings.xml | 13 + node_modules/jsftp/.idea/encodings.xml | 5 + .../inspectionProfiles/Project_Default.xml | 12 + .../inspectionProfiles/profiles_settings.xml | 7 + .../jsftp/.idea/jsLibraryMappings.xml | 9 + node_modules/jsftp/.idea/jsftp.iml | 11 + .../Node_js_Dependencies_for_jsftp.xml | 13 + node_modules/jsftp/.idea/misc.xml | 22 + node_modules/jsftp/.idea/modules.xml | 9 + .../jsftp/.idea/scopes/scope_settings.xml | 5 + node_modules/jsftp/.idea/vcs.xml | 7 + node_modules/jsftp/.idea/workspace.xml | 588 ++++++ node_modules/jsftp/.npmignore | 5 + node_modules/jsftp/.travis.yml | 9 + node_modules/jsftp/LICENSE | 22 + node_modules/jsftp/Makefile | 31 + node_modules/jsftp/README.md | 235 +++ node_modules/jsftp/index.js | 2 + node_modules/jsftp/lib/jsftp.js | 650 +++++++ node_modules/jsftp/lib/lib/jsftp.js | 6 + node_modules/jsftp/lib/lib/response.js | 6 + node_modules/jsftp/lib/lib/utils.js | 6 + node_modules/jsftp/lib/response.js | 45 + node_modules/jsftp/lib/utils.js | 101 + node_modules/jsftp/node_modules/async/LICENSE | 19 + .../jsftp/node_modules/async/README.md | 1414 ++++++++++++++ .../jsftp/node_modules/async/component.json | 11 + .../jsftp/node_modules/async/lib/async.js | 955 +++++++++ .../jsftp/node_modules/async/package.json | 44 + .../node_modules/event-stream/.npmignore | 3 + .../node_modules/event-stream/.travis.yml | 4 + .../jsftp/node_modules/event-stream/LICENCE | 22 + .../event-stream/examples/pretty.js | 25 + .../jsftp/node_modules/event-stream/index.js | 302 +++ .../node_modules/duplexer/.npmignore | 3 + .../node_modules/duplexer/.travis.yml | 3 + .../node_modules/duplexer/LICENCE | 19 + .../node_modules/duplexer/Makefile | 4 + .../node_modules/duplexer/README.md | 36 + .../node_modules/duplexer/index.js | 77 + .../node_modules/duplexer/package.json | 44 + .../node_modules/duplexer/test.js | 27 + .../event-stream/node_modules/from/.npmignore | 1 + .../node_modules/from/LICENSE.APACHE2 | 15 + .../node_modules/from/LICENSE.MIT | 24 + .../event-stream/node_modules/from/index.js | 68 + .../node_modules/from/package.json | 36 + .../node_modules/from/readme.markdown | 38 + .../node_modules/from/test/index.js | 141 ++ .../node_modules/map-stream/.npmignore | 3 + .../node_modules/map-stream/.travis.yml | 4 + .../node_modules/map-stream/LICENCE | 22 + .../map-stream/examples/pretty.js | 26 + .../node_modules/map-stream/index.js | 105 + .../node_modules/map-stream/package.json | 33 + .../node_modules/map-stream/readme.markdown | 35 + .../map-stream/test/simple-map.asynct.js | 270 +++ .../node_modules/pause-stream/.npmignore | 3 + .../node_modules/pause-stream/index.js | 3 + .../node_modules/pause-stream/package.json | 45 + .../node_modules/pause-stream/readme.markdown | 29 + .../node_modules/pause-stream/test/index.js | 17 + .../pause-stream/test/pause-end.js | 33 + .../node_modules/split/.npmignore | 3 + .../node_modules/split/.travis.yml | 4 + .../event-stream/node_modules/split/LICENCE | 22 + .../node_modules/split/examples/pretty.js | 26 + .../event-stream/node_modules/split/index.js | 59 + .../node_modules/split/package.json | 38 + .../node_modules/split/readme.markdown | 39 + .../split/test/partitioned_unicode.js | 34 + .../node_modules/split/test/split.asynct.js | 85 + .../split/test/try_catch.asynct.js | 51 + .../node_modules/stream-combiner/.npmignore | 3 + .../node_modules/stream-combiner/.travis.yml | 4 + .../node_modules/stream-combiner/LICENSE | 22 + .../node_modules/stream-combiner/README.md | 29 + .../node_modules/stream-combiner/index.js | 39 + .../node_modules/duplexer/.npmignore | 3 + .../node_modules/duplexer/.travis.yml | 3 + .../node_modules/duplexer/LICENCE | 19 + .../node_modules/duplexer/Makefile | 4 + .../node_modules/duplexer/README.md | 36 + .../node_modules/duplexer/index.js | 87 + .../node_modules/duplexer/package.json | 44 + .../node_modules/duplexer/test.js | 27 + .../node_modules/stream-combiner/package.json | 32 + .../stream-combiner/test/index.js | 52 + .../node_modules/through/.travis.yml | 5 + .../node_modules/through/LICENSE.APACHE2 | 15 + .../node_modules/through/LICENSE.MIT | 24 + .../node_modules/through/index.js | 108 ++ .../node_modules/through/package.json | 45 + .../node_modules/through/readme.markdown | 63 + .../node_modules/through/test/auto-destroy.js | 30 + .../node_modules/through/test/buffering.js | 71 + .../node_modules/through/test/end.js | 45 + .../node_modules/through/test/index.js | 114 ++ .../node_modules/event-stream/package.json | 62 + .../node_modules/event-stream/readme.markdown | 291 +++ .../event-stream/test/connect.asynct.js | 86 + .../event-stream/test/helper/index.js | 12 + .../event-stream/test/merge.asynct.js | 21 + .../event-stream/test/pause.asynct.js | 39 + .../event-stream/test/pipeline.asynct.js | 52 + .../event-stream/test/readArray.asynct.js | 89 + .../event-stream/test/readable.asynct.js | 175 ++ .../event-stream/test/replace.asynct.js | 51 + .../event-stream/test/simple-map.asynct.js | 343 ++++ .../event-stream/test/spec.asynct.js | 86 + .../event-stream/test/split.asynct.js | 47 + .../event-stream/test/stringify.js | 15 + .../event-stream/test/writeArray.asynct.js | 31 + .../parse-listing/..gitignore.un~ | Bin 0 -> 1400 bytes .../parse-listing/..travis.yml.un~ | Bin 0 -> 2243 bytes .../node_modules/parse-listing/.Makefile.un~ | Bin 0 -> 34267 bytes .../node_modules/parse-listing/.README.md.un~ | Bin 0 -> 6885 bytes .../node_modules/parse-listing/.idea/.name | 1 + .../parse-listing/.idea/encodings.xml | 5 + .../inspectionProfiles/Project_Default.xml | 11 + .../inspectionProfiles/profiles_settings.xml | 7 + .../node_modules/parse-listing/.idea/misc.xml | 22 + .../parse-listing/.idea/modules.xml | 9 + .../parse-listing/.idea/parse-listing.iml | 9 + .../.idea/scopes/scope_settings.xml | 5 + .../node_modules/parse-listing/.idea/vcs.xml | 7 + .../parse-listing/.idea/workspace.xml | 358 ++++ .../node_modules/parse-listing/.npmignore | 3 + .../parse-listing/.package.json.un~ | Bin 0 -> 11300 bytes .../node_modules/parse-listing/.parser.js.un~ | Bin 0 -> 970 bytes .../parse-listing/.parset_test.js.un~ | Bin 0 -> 17400 bytes .../node_modules/parse-listing/.travis.yml | 7 + .../jsftp/node_modules/parse-listing/Makefile | 39 + .../node_modules/parse-listing/README.md | 115 ++ .../node_modules/parse-listing/package.json | 55 + .../parse-listing/src/.parser.js.un~ | Bin 0 -> 27249 bytes .../node_modules/parse-listing/src/parser.js | 281 +++ .../parse-listing/test/.parser.test.js.un~ | Bin 0 -> 599 bytes .../parse-listing/test/.parser_test.js.un~ | Bin 0 -> 28357 bytes .../parse-listing/test/parser.test.js | 804 ++++++++ node_modules/jsftp/package.json | 59 + node_modules/jsftp/test.js | 78 + node_modules/jsftp/test/jsftp_test.js | 698 +++++++ node_modules/jsftp/test/keycert.pem | 32 + node_modules/jsftp/test/mockup_test.js | 0 node_modules/jsftp/test/test_c9/testfile.txt | 1 + node_modules/jsftp/test/tls_ftpd.py | 27 + node_modules/ws/.npmignore | 6 + node_modules/ws/.travis.yml | 6 + node_modules/ws/History.md | 312 +++ node_modules/ws/Makefile | 40 + node_modules/ws/README.md | 171 ++ node_modules/ws/bench/parser.benchmark.js | 115 ++ node_modules/ws/bench/sender.benchmark.js | 66 + node_modules/ws/bench/speed.js | 105 + node_modules/ws/bench/util.js | 105 + node_modules/ws/bin/wscat | 190 ++ node_modules/ws/binding.gyp | 16 + node_modules/ws/build/Makefile | 364 ++++ .../Release/.deps/Release/bufferutil.node.d | 1 + .../obj.target/bufferutil/src/bufferutil.o.d | 33 + .../obj.target/validation/src/validation.o.d | 33 + .../Release/.deps/Release/validation.node.d | 1 + node_modules/ws/build/Release/bufferutil.node | Bin 0 -> 18268 bytes node_modules/ws/build/Release/linker.lock | 0 .../obj.target/bufferutil/src/bufferutil.o | Bin 0 -> 77760 bytes .../obj.target/validation/src/validation.o | Bin 0 -> 74376 bytes node_modules/ws/build/Release/validation.node | Bin 0 -> 18484 bytes node_modules/ws/build/binding.Makefile | 6 + node_modules/ws/build/bufferutil.target.mk | 149 ++ node_modules/ws/build/config.gypi | 102 + node_modules/ws/build/gyp-mac-tool | 210 ++ node_modules/ws/build/validation.target.mk | 149 ++ node_modules/ws/builderror.log | 2 + node_modules/ws/doc/ws.md | 181 ++ node_modules/ws/examples/fileapi/.npmignore | 1 + node_modules/ws/examples/fileapi/package.json | 18 + .../ws/examples/fileapi/public/app.js | 39 + .../ws/examples/fileapi/public/index.html | 22 + .../ws/examples/fileapi/public/uploader.js | 55 + node_modules/ws/examples/fileapi/server.js | 103 + .../serverstats-express_3/package.json | 17 + .../serverstats-express_3/public/index.html | 33 + .../examples/serverstats-express_3/server.js | 21 + .../ws/examples/serverstats/package.json | 17 + .../ws/examples/serverstats/public/index.html | 33 + .../ws/examples/serverstats/server.js | 19 + node_modules/ws/examples/ssl.js | 59 + node_modules/ws/index.js | 26 + node_modules/ws/lib/BufferPool.js | 59 + node_modules/ws/lib/BufferUtil.fallback.js | 47 + node_modules/ws/lib/BufferUtil.js | 16 + node_modules/ws/lib/ErrorCodes.js | 24 + node_modules/ws/lib/Receiver.hixie.js | 180 ++ node_modules/ws/lib/Receiver.js | 591 ++++++ node_modules/ws/lib/Sender.hixie.js | 123 ++ node_modules/ws/lib/Sender.js | 227 +++ node_modules/ws/lib/Validation.fallback.js | 12 + node_modules/ws/lib/Validation.js | 16 + node_modules/ws/lib/WebSocket.js | 818 ++++++++ node_modules/ws/lib/WebSocketServer.js | 460 +++++ node_modules/ws/lib/browser.js | 43 + .../ws/node_modules/commander/.npmignore | 4 + .../ws/node_modules/commander/.travis.yml | 4 + .../ws/node_modules/commander/History.md | 107 + .../ws/node_modules/commander/Makefile | 7 + .../ws/node_modules/commander/Readme.md | 262 +++ .../ws/node_modules/commander/index.js | 2 + .../node_modules/commander/lib/commander.js | 1026 ++++++++++ .../ws/node_modules/commander/package.json | 37 + node_modules/ws/node_modules/nan/.index.js | 1 + node_modules/ws/node_modules/nan/LICENSE | 43 + node_modules/ws/node_modules/nan/README.md | 705 +++++++ node_modules/ws/node_modules/nan/nan.h | 884 +++++++++ node_modules/ws/node_modules/nan/package.json | 34 + .../ws/node_modules/options/.npmignore | 5 + node_modules/ws/node_modules/options/Makefile | 12 + .../ws/node_modules/options/README.md | 28 + .../ws/node_modules/options/lib/options.js | 86 + .../ws/node_modules/options/package.json | 31 + .../options/test/fixtures/test.conf | 4 + .../node_modules/options/test/options.test.js | 140 ++ .../ws/node_modules/tinycolor/.npmignore | 5 + .../ws/node_modules/tinycolor/README.md | 3 + .../ws/node_modules/tinycolor/example.js | 3 + .../ws/node_modules/tinycolor/package.json | 26 + .../ws/node_modules/tinycolor/tinycolor.js | 31 + node_modules/ws/package.json | 59 + node_modules/ws/src/bufferutil.cc | 117 ++ node_modules/ws/src/validation.cc | 145 ++ node_modules/ws/test/BufferPool.test.js | 63 + node_modules/ws/test/Receiver.hixie.test.js | 158 ++ node_modules/ws/test/Receiver.test.js | 255 +++ node_modules/ws/test/Sender.hixie.test.js | 134 ++ node_modules/ws/test/Sender.test.js | 24 + node_modules/ws/test/Validation.test.js | 23 + node_modules/ws/test/WebSocket.integration.js | 44 + node_modules/ws/test/WebSocket.test.js | 1724 +++++++++++++++++ node_modules/ws/test/WebSocketServer.test.js | 1103 +++++++++++ node_modules/ws/test/autobahn-server.js | 29 + node_modules/ws/test/autobahn.js | 52 + node_modules/ws/test/fixtures/agent1-cert.pem | 16 + node_modules/ws/test/fixtures/agent1-key.pem | 15 + node_modules/ws/test/fixtures/ca1-cert.pem | 15 + node_modules/ws/test/fixtures/ca1-key.pem | 17 + node_modules/ws/test/fixtures/certificate.pem | 13 + node_modules/ws/test/fixtures/key.pem | 15 + node_modules/ws/test/fixtures/request.pem | 11 + node_modules/ws/test/fixtures/textfile | 9 + node_modules/ws/test/hybi-common.js | 99 + node_modules/ws/test/testserver.js | 180 ++ server.js | 102 + start.sh | 2 + 264 files changed, 24877 insertions(+) create mode 100644 .DS_Store create mode 100644 cam/index.html create mode 100644 cam/index.json create mode 100644 cam/lazyload.js create mode 100755 clientcam.html create mode 100644 compress.js create mode 100644 ftpindex.js create mode 100644 image.jpg create mode 100644 index.json create mode 120000 node_modules/.bin/wscat create mode 100644 node_modules/jsftp/.idea/.name create mode 100644 node_modules/jsftp/.idea/codeStyleSettings.xml create mode 100644 node_modules/jsftp/.idea/encodings.xml create mode 100644 node_modules/jsftp/.idea/inspectionProfiles/Project_Default.xml create mode 100644 node_modules/jsftp/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 node_modules/jsftp/.idea/jsLibraryMappings.xml create mode 100644 node_modules/jsftp/.idea/jsftp.iml create mode 100644 node_modules/jsftp/.idea/libraries/Node_js_Dependencies_for_jsftp.xml create mode 100644 node_modules/jsftp/.idea/misc.xml create mode 100644 node_modules/jsftp/.idea/modules.xml create mode 100644 node_modules/jsftp/.idea/scopes/scope_settings.xml create mode 100644 node_modules/jsftp/.idea/vcs.xml create mode 100644 node_modules/jsftp/.idea/workspace.xml create mode 100644 node_modules/jsftp/.npmignore create mode 100644 node_modules/jsftp/.travis.yml create mode 100644 node_modules/jsftp/LICENSE create mode 100644 node_modules/jsftp/Makefile create mode 100644 node_modules/jsftp/README.md create mode 100644 node_modules/jsftp/index.js create mode 100644 node_modules/jsftp/lib/jsftp.js create mode 100644 node_modules/jsftp/lib/lib/jsftp.js create mode 100644 node_modules/jsftp/lib/lib/response.js create mode 100644 node_modules/jsftp/lib/lib/utils.js create mode 100644 node_modules/jsftp/lib/response.js create mode 100644 node_modules/jsftp/lib/utils.js create mode 100644 node_modules/jsftp/node_modules/async/LICENSE create mode 100644 node_modules/jsftp/node_modules/async/README.md create mode 100644 node_modules/jsftp/node_modules/async/component.json create mode 100755 node_modules/jsftp/node_modules/async/lib/async.js create mode 100644 node_modules/jsftp/node_modules/async/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/LICENCE create mode 100644 node_modules/jsftp/node_modules/event-stream/examples/pretty.js create mode 100644 node_modules/jsftp/node_modules/event-stream/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/LICENCE create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/Makefile create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/README.md create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/test.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.APACHE2 create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.MIT create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/readme.markdown create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/from/test/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/LICENCE create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/examples/pretty.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/readme.markdown create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/test/simple-map.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/readme.markdown create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/pause-end.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/LICENCE create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/examples/pretty.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/readme.markdown create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/test/partitioned_unicode.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/test/split.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/split/test/try_catch.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/LICENSE create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/README.md create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.npmignore create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/LICENCE create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/Makefile create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/README.md create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/test.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/test/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/.travis.yml create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.APACHE2 create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.MIT create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/readme.markdown create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/test/auto-destroy.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/test/buffering.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/test/end.js create mode 100644 node_modules/jsftp/node_modules/event-stream/node_modules/through/test/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/package.json create mode 100644 node_modules/jsftp/node_modules/event-stream/readme.markdown create mode 100644 node_modules/jsftp/node_modules/event-stream/test/connect.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/helper/index.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/merge.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/pause.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/pipeline.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/readArray.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/readable.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/replace.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/simple-map.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/spec.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/split.asynct.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/stringify.js create mode 100644 node_modules/jsftp/node_modules/event-stream/test/writeArray.asynct.js create mode 100644 node_modules/jsftp/node_modules/parse-listing/..gitignore.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/..travis.yml.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/.Makefile.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/.README.md.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/.name create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/encodings.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/Project_Default.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/misc.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/modules.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/parse-listing.iml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/scopes/scope_settings.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/vcs.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.idea/workspace.xml create mode 100644 node_modules/jsftp/node_modules/parse-listing/.npmignore create mode 100644 node_modules/jsftp/node_modules/parse-listing/.package.json.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/.parser.js.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/.parset_test.js.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/.travis.yml create mode 100644 node_modules/jsftp/node_modules/parse-listing/Makefile create mode 100644 node_modules/jsftp/node_modules/parse-listing/README.md create mode 100644 node_modules/jsftp/node_modules/parse-listing/package.json create mode 100644 node_modules/jsftp/node_modules/parse-listing/src/.parser.js.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/src/parser.js create mode 100644 node_modules/jsftp/node_modules/parse-listing/test/.parser.test.js.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/test/.parser_test.js.un~ create mode 100644 node_modules/jsftp/node_modules/parse-listing/test/parser.test.js create mode 100644 node_modules/jsftp/package.json create mode 100644 node_modules/jsftp/test.js create mode 100755 node_modules/jsftp/test/jsftp_test.js create mode 100644 node_modules/jsftp/test/keycert.pem create mode 100644 node_modules/jsftp/test/mockup_test.js create mode 100644 node_modules/jsftp/test/test_c9/testfile.txt create mode 100644 node_modules/jsftp/test/tls_ftpd.py create mode 100644 node_modules/ws/.npmignore create mode 100644 node_modules/ws/.travis.yml create mode 100644 node_modules/ws/History.md create mode 100644 node_modules/ws/Makefile create mode 100644 node_modules/ws/README.md create mode 100644 node_modules/ws/bench/parser.benchmark.js create mode 100644 node_modules/ws/bench/sender.benchmark.js create mode 100644 node_modules/ws/bench/speed.js create mode 100644 node_modules/ws/bench/util.js create mode 100755 node_modules/ws/bin/wscat create mode 100644 node_modules/ws/binding.gyp create mode 100644 node_modules/ws/build/Makefile create mode 100644 node_modules/ws/build/Release/.deps/Release/bufferutil.node.d create mode 100644 node_modules/ws/build/Release/.deps/Release/obj.target/bufferutil/src/bufferutil.o.d create mode 100644 node_modules/ws/build/Release/.deps/Release/obj.target/validation/src/validation.o.d create mode 100644 node_modules/ws/build/Release/.deps/Release/validation.node.d create mode 100755 node_modules/ws/build/Release/bufferutil.node create mode 100644 node_modules/ws/build/Release/linker.lock create mode 100644 node_modules/ws/build/Release/obj.target/bufferutil/src/bufferutil.o create mode 100644 node_modules/ws/build/Release/obj.target/validation/src/validation.o create mode 100755 node_modules/ws/build/Release/validation.node create mode 100644 node_modules/ws/build/binding.Makefile create mode 100644 node_modules/ws/build/bufferutil.target.mk create mode 100644 node_modules/ws/build/config.gypi create mode 100755 node_modules/ws/build/gyp-mac-tool create mode 100644 node_modules/ws/build/validation.target.mk create mode 100644 node_modules/ws/builderror.log create mode 100644 node_modules/ws/doc/ws.md create mode 100644 node_modules/ws/examples/fileapi/.npmignore create mode 100644 node_modules/ws/examples/fileapi/package.json create mode 100644 node_modules/ws/examples/fileapi/public/app.js create mode 100644 node_modules/ws/examples/fileapi/public/index.html create mode 100644 node_modules/ws/examples/fileapi/public/uploader.js create mode 100644 node_modules/ws/examples/fileapi/server.js create mode 100644 node_modules/ws/examples/serverstats-express_3/package.json create mode 100644 node_modules/ws/examples/serverstats-express_3/public/index.html create mode 100644 node_modules/ws/examples/serverstats-express_3/server.js create mode 100644 node_modules/ws/examples/serverstats/package.json create mode 100644 node_modules/ws/examples/serverstats/public/index.html create mode 100644 node_modules/ws/examples/serverstats/server.js create mode 100644 node_modules/ws/examples/ssl.js create mode 100644 node_modules/ws/index.js create mode 100644 node_modules/ws/lib/BufferPool.js create mode 100644 node_modules/ws/lib/BufferUtil.fallback.js create mode 100644 node_modules/ws/lib/BufferUtil.js create mode 100644 node_modules/ws/lib/ErrorCodes.js create mode 100644 node_modules/ws/lib/Receiver.hixie.js create mode 100644 node_modules/ws/lib/Receiver.js create mode 100644 node_modules/ws/lib/Sender.hixie.js create mode 100644 node_modules/ws/lib/Sender.js create mode 100644 node_modules/ws/lib/Validation.fallback.js create mode 100644 node_modules/ws/lib/Validation.js create mode 100644 node_modules/ws/lib/WebSocket.js create mode 100644 node_modules/ws/lib/WebSocketServer.js create mode 100644 node_modules/ws/lib/browser.js create mode 100644 node_modules/ws/node_modules/commander/.npmignore create mode 100644 node_modules/ws/node_modules/commander/.travis.yml create mode 100644 node_modules/ws/node_modules/commander/History.md create mode 100644 node_modules/ws/node_modules/commander/Makefile create mode 100644 node_modules/ws/node_modules/commander/Readme.md create mode 100644 node_modules/ws/node_modules/commander/index.js create mode 100644 node_modules/ws/node_modules/commander/lib/commander.js create mode 100644 node_modules/ws/node_modules/commander/package.json create mode 100644 node_modules/ws/node_modules/nan/.index.js create mode 100644 node_modules/ws/node_modules/nan/LICENSE create mode 100644 node_modules/ws/node_modules/nan/README.md create mode 100644 node_modules/ws/node_modules/nan/nan.h create mode 100644 node_modules/ws/node_modules/nan/package.json create mode 100644 node_modules/ws/node_modules/options/.npmignore create mode 100644 node_modules/ws/node_modules/options/Makefile create mode 100644 node_modules/ws/node_modules/options/README.md create mode 100644 node_modules/ws/node_modules/options/lib/options.js create mode 100644 node_modules/ws/node_modules/options/package.json create mode 100644 node_modules/ws/node_modules/options/test/fixtures/test.conf create mode 100644 node_modules/ws/node_modules/options/test/options.test.js create mode 100644 node_modules/ws/node_modules/tinycolor/.npmignore create mode 100644 node_modules/ws/node_modules/tinycolor/README.md create mode 100644 node_modules/ws/node_modules/tinycolor/example.js create mode 100644 node_modules/ws/node_modules/tinycolor/package.json create mode 100644 node_modules/ws/node_modules/tinycolor/tinycolor.js create mode 100644 node_modules/ws/package.json create mode 100644 node_modules/ws/src/bufferutil.cc create mode 100644 node_modules/ws/src/validation.cc create mode 100644 node_modules/ws/test/BufferPool.test.js create mode 100644 node_modules/ws/test/Receiver.hixie.test.js create mode 100644 node_modules/ws/test/Receiver.test.js create mode 100644 node_modules/ws/test/Sender.hixie.test.js create mode 100644 node_modules/ws/test/Sender.test.js create mode 100644 node_modules/ws/test/Validation.test.js create mode 100644 node_modules/ws/test/WebSocket.integration.js create mode 100644 node_modules/ws/test/WebSocket.test.js create mode 100644 node_modules/ws/test/WebSocketServer.test.js create mode 100644 node_modules/ws/test/autobahn-server.js create mode 100644 node_modules/ws/test/autobahn.js create mode 100644 node_modules/ws/test/fixtures/agent1-cert.pem create mode 100644 node_modules/ws/test/fixtures/agent1-key.pem create mode 100644 node_modules/ws/test/fixtures/ca1-cert.pem create mode 100644 node_modules/ws/test/fixtures/ca1-key.pem create mode 100644 node_modules/ws/test/fixtures/certificate.pem create mode 100644 node_modules/ws/test/fixtures/key.pem create mode 100644 node_modules/ws/test/fixtures/request.pem create mode 100644 node_modules/ws/test/fixtures/textfile create mode 100644 node_modules/ws/test/hybi-common.js create mode 100644 node_modules/ws/test/testserver.js create mode 100644 server.js create mode 100755 start.sh diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + + + + + + + + + +

Cam capture live feed

+ cam +

+ DO NOT REFRESH page, happens automatically every 60 seconds.
+ Last update:

+

+
+

HOURLY Cam history

+ +
+ Fetching... Please wait. +
+
+ + + + diff --git a/cam/index.json b/cam/index.json new file mode 100644 index 0000000..6765dbb --- /dev/null +++ b/cam/index.json @@ -0,0 +1 @@ +{"files":[{"name":"image.jpg","type":0,"time":1381927500000,"size":"490610","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}}]} \ No newline at end of file diff --git a/cam/lazyload.js b/cam/lazyload.js new file mode 100644 index 0000000..26cc801 --- /dev/null +++ b/cam/lazyload.js @@ -0,0 +1,239 @@ +/* + * Lazy Load - jQuery plugin for lazy loading images + * + * Copyright (c) 2007-2013 Mika Tuupola + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Project home: + * http://www.appelsiini.net/projects/lazyload + * + * Version: 1.9.0 + * + */ + +(function($, window, document, undefined) { + var $window = $(window); + + $.fn.lazyload = function(options) { + var elements = this; + var $container; + var settings = { + threshold : 0, + failure_limit : 0, + event : "scroll", + effect : "show", + container : window, + data_attribute : "original", + skip_invisible : true, + appear : null, + load : null, + placeholder : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" + }; + + function update() { + var counter = 0; + + elements.each(function() { + var $this = $(this); + if (settings.skip_invisible && !$this.is(":visible")) { + return; + } + if ($.abovethetop(this, settings) || + $.leftofbegin(this, settings)) { + /* Nothing. */ + } else if (!$.belowthefold(this, settings) && + !$.rightoffold(this, settings)) { + $this.trigger("appear"); + /* if we found an image we'll load, reset the counter */ + counter = 0; + } else { + if (++counter > settings.failure_limit) { + return false; + } + } + }); + + } + + if(options) { + /* Maintain BC for a couple of versions. */ + if (undefined !== options.failurelimit) { + options.failure_limit = options.failurelimit; + delete options.failurelimit; + } + if (undefined !== options.effectspeed) { + options.effect_speed = options.effectspeed; + delete options.effectspeed; + } + + $.extend(settings, options); + } + + /* Cache container as jQuery as object. */ + $container = (settings.container === undefined || + settings.container === window) ? $window : $(settings.container); + + /* Fire one scroll event per scroll. Not one scroll event per image. */ + if (0 === settings.event.indexOf("scroll")) { + $container.bind(settings.event, function() { + return update(); + }); + } + + this.each(function() { + var self = this; + var $self = $(self); + + self.loaded = false; + + /* If no src attribute given use data:uri. */ + if ($self.attr("src") === undefined || $self.attr("src") === false) { + $self.attr("src", settings.placeholder); + } + + /* When appear is triggered load original image. */ + $self.one("appear", function() { + if (!this.loaded) { + if (settings.appear) { + var elements_left = elements.length; + settings.appear.call(self, elements_left, settings); + } + $("") + .bind("load", function() { + var original = $self.data(settings.data_attribute); + $self.hide(); + if ($self.is("img")) { + $self.attr("src", original); + } else { + $self.css("background-image", "url('" + original + "')"); + } + $self[settings.effect](settings.effect_speed); + + self.loaded = true; + + /* Remove image from array so it is not looped next time. */ + var temp = $.grep(elements, function(element) { + return !element.loaded; + }); + elements = $(temp); + + if (settings.load) { + var elements_left = elements.length; + settings.load.call(self, elements_left, settings); + } + }) + .attr("src", $self.data(settings.data_attribute)); + } + }); + + /* When wanted event is triggered load original image */ + /* by triggering appear. */ + if (0 !== settings.event.indexOf("scroll")) { + $self.bind(settings.event, function() { + if (!self.loaded) { + $self.trigger("appear"); + } + }); + } + }); + + /* Check if something appears when window is resized. */ + $window.bind("resize", function() { + update(); + }); + + /* With IOS5 force loading images when navigating with back button. */ + /* Non optimal workaround. */ + if ((/iphone|ipod|ipad.*os 5/gi).test(navigator.appVersion)) { + $window.bind("pageshow", function(event) { + if (event.originalEvent && event.originalEvent.persisted) { + elements.each(function() { + $(this).trigger("appear"); + }); + } + }); + } + + /* Force initial check if images should appear. */ + $(document).ready(function() { + update(); + }); + + return this; + }; + + /* Convenience methods in jQuery namespace. */ + /* Use as $.belowthefold(element, {threshold : 100, container : window}) */ + + $.belowthefold = function(element, settings) { + var fold; + + if (settings.container === undefined || settings.container === window) { + fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop(); + } else { + fold = $(settings.container).offset().top + $(settings.container).height(); + } + + return fold <= $(element).offset().top - settings.threshold; + }; + + $.rightoffold = function(element, settings) { + var fold; + + if (settings.container === undefined || settings.container === window) { + fold = $window.width() + $window.scrollLeft(); + } else { + fold = $(settings.container).offset().left + $(settings.container).width(); + } + + return fold <= $(element).offset().left - settings.threshold; + }; + + $.abovethetop = function(element, settings) { + var fold; + + if (settings.container === undefined || settings.container === window) { + fold = $window.scrollTop(); + } else { + fold = $(settings.container).offset().top; + } + + return fold >= $(element).offset().top + settings.threshold + $(element).height(); + }; + + $.leftofbegin = function(element, settings) { + var fold; + + if (settings.container === undefined || settings.container === window) { + fold = $window.scrollLeft(); + } else { + fold = $(settings.container).offset().left; + } + + return fold >= $(element).offset().left + settings.threshold + $(element).width(); + }; + + $.inviewport = function(element, settings) { + return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) && + !$.belowthefold(element, settings) && !$.abovethetop(element, settings); + }; + + /* Custom selectors for your convenience. */ + /* Use as $("img:below-the-fold").something() or */ + /* $("img").filter(":below-the-fold").something() which is faster */ + + $.extend($.expr[":"], { + "below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0}); }, + "above-the-top" : function(a) { return !$.belowthefold(a, {threshold : 0}); }, + "right-of-screen": function(a) { return $.rightoffold(a, {threshold : 0}); }, + "left-of-screen" : function(a) { return !$.rightoffold(a, {threshold : 0}); }, + "in-viewport" : function(a) { return $.inviewport(a, {threshold : 0}); }, + /* Maintain BC for couple of versions. */ + "above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0}); }, + "right-of-fold" : function(a) { return $.rightoffold(a, {threshold : 0}); }, + "left-of-fold" : function(a) { return !$.rightoffold(a, {threshold : 0}); } + }); + +})(jQuery, window, document); diff --git a/clientcam.html b/clientcam.html new file mode 100755 index 0000000..7f1d18b --- /dev/null +++ b/clientcam.html @@ -0,0 +1,73 @@ + + + + + + + + +

hello!

+ +klik hier om video te capturen.
+ +
+ +

Streamed image:

+ + \ No newline at end of file diff --git a/compress.js b/compress.js new file mode 100644 index 0000000..7ed67fd --- /dev/null +++ b/compress.js @@ -0,0 +1,6 @@ +var gm = require("gm").subClass({ ImageMagick : true }); + +gm("image.jpg").quality(60).compress().write("compressed.jpg", function(e) { + if(e) throw e; // Error: spawn ENOENT ? ImageMagick niet op PATH. + console.log("done"); +}); diff --git a/ftpindex.js b/ftpindex.js new file mode 100644 index 0000000..e671e5b --- /dev/null +++ b/ftpindex.js @@ -0,0 +1,32 @@ +var Ftp = require("jsftp"), fs = require("fs"); + +var user = "FILL ME IN"; +var pass = "FILL ME IN"; +var root = "/domains/brainbaking.com/public_html"; + +var ftp = new Ftp({ + host: "ftp.brainbaking.com", + port: 21 +}); + +ftp.auth(user, pass, function(err, res) { + var images = { files: [] }; + ftp.ls(root + "/cam", function(err, list) { + + images.files = list.filter(function(file) { + return file.name.indexOf('.jpg') > 0; + }); + + console.log("writing " + images); + fs.writeFile("index.json", JSON.stringify(images), function(e) { + if(e) throw e; + + ftp.put("index.json", root + "/cam/index.json", function(e) { + if(e) throw e; + console.log("written index JSON file."); + }); + }); + + }); + +}); diff --git a/image.jpg b/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e893490cc3f491c0fdf66706b4712a1f839050ba GIT binary patch literal 339417 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 z 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

_d3La0Y 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%0A#}N>+&8|jM>3E? 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 literal 0 HcmV?d00001 diff --git a/index.json b/index.json new file mode 100644 index 0000000..04abe9d --- /dev/null +++ b/index.json @@ -0,0 +1 @@ +{"files":[{"name":"1381937362359_image.jpg","type":0,"time":1381937340000,"size":"550409","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1381937569375_image.jpg","type":0,"time":1381937520000,"size":"586258","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1381989036320_image.jpg","type":0,"time":1381989000000,"size":"533100","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1381989636744_image.jpg","type":0,"time":1381989600000,"size":"633357","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1381993235977_image.jpg","type":0,"time":1381993200000,"size":"545913","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1381996835944_image.jpg","type":0,"time":1381996800000,"size":"543081","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382000435066_image.jpg","type":0,"time":1382000400000,"size":"534940","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382004035158_image.jpg","type":0,"time":1382004000000,"size":"543900","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382007635067_image.jpg","type":0,"time":1382007600000,"size":"534479","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382011234993_image.jpg","type":0,"time":1382011200000,"size":"535949","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382014835242_image.jpg","type":0,"time":1382014800000,"size":"550919","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382018434860_image.jpg","type":0,"time":1382018400000,"size":"523887","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}},{"name":"1382022034866_image.jpg","type":0,"time":1382022000000,"size":"520789","owner":"ftp","group":"ftp","userPermissions":{"read":true,"write":true,"exec":false},"groupPermissions":{"read":true,"write":false,"exec":false},"otherPermissions":{"read":true,"write":false,"exec":false}}]} \ No newline at end of file diff --git a/node_modules/.bin/wscat b/node_modules/.bin/wscat new file mode 120000 index 0000000..06c3b74 --- /dev/null +++ b/node_modules/.bin/wscat @@ -0,0 +1 @@ +../ws/bin/wscat \ No newline at end of file diff --git a/node_modules/jsftp/.idea/.name b/node_modules/jsftp/.idea/.name new file mode 100644 index 0000000..2d9be14 --- /dev/null +++ b/node_modules/jsftp/.idea/.name @@ -0,0 +1 @@ +jsftp \ No newline at end of file diff --git a/node_modules/jsftp/.idea/codeStyleSettings.xml b/node_modules/jsftp/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..9178b38 --- /dev/null +++ b/node_modules/jsftp/.idea/codeStyleSettings.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/node_modules/jsftp/.idea/encodings.xml b/node_modules/jsftp/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/node_modules/jsftp/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/node_modules/jsftp/.idea/inspectionProfiles/Project_Default.xml b/node_modules/jsftp/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..d036fa9 --- /dev/null +++ b/node_modules/jsftp/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/node_modules/jsftp/.idea/inspectionProfiles/profiles_settings.xml b/node_modules/jsftp/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/node_modules/jsftp/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/node_modules/jsftp/.idea/jsLibraryMappings.xml b/node_modules/jsftp/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..2350bfd --- /dev/null +++ b/node_modules/jsftp/.idea/jsLibraryMappings.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/node_modules/jsftp/.idea/jsftp.iml b/node_modules/jsftp/.idea/jsftp.iml new file mode 100644 index 0000000..bd9a540 --- /dev/null +++ b/node_modules/jsftp/.idea/jsftp.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/node_modules/jsftp/.idea/libraries/Node_js_Dependencies_for_jsftp.xml b/node_modules/jsftp/.idea/libraries/Node_js_Dependencies_for_jsftp.xml new file mode 100644 index 0000000..493fb38 --- /dev/null +++ b/node_modules/jsftp/.idea/libraries/Node_js_Dependencies_for_jsftp.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/node_modules/jsftp/.idea/misc.xml b/node_modules/jsftp/.idea/misc.xml new file mode 100644 index 0000000..d16ceba --- /dev/null +++ b/node_modules/jsftp/.idea/misc.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/node_modules/jsftp/.idea/modules.xml b/node_modules/jsftp/.idea/modules.xml new file mode 100644 index 0000000..4b6a084 --- /dev/null +++ b/node_modules/jsftp/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/node_modules/jsftp/.idea/scopes/scope_settings.xml b/node_modules/jsftp/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/node_modules/jsftp/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/node_modules/jsftp/.idea/vcs.xml b/node_modules/jsftp/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/node_modules/jsftp/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/node_modules/jsftp/.idea/workspace.xml b/node_modules/jsftp/.idea/workspace.xml new file mode 100644 index 0000000..f4b7292 --- /dev/null +++ b/node_modules/jsftp/.idea/workspace.xml @@ -0,0 +1,588 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bitwise operation issuesJavaScript + + + Control flow issuesJavaScript + + + GeneralJavaScript + + + JavaScript + + + Potentially confusing code constructsJavaScript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1366213834349 + 1366213834349 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/node_modules/jsftp/.npmignore b/node_modules/jsftp/.npmignore new file mode 100644 index 0000000..eb33546 --- /dev/null +++ b/node_modules/jsftp/.npmignore @@ -0,0 +1,5 @@ +coverage.html +*.un~ +lib-cov +reports +.c9revisions diff --git a/node_modules/jsftp/.travis.yml b/node_modules/jsftp/.travis.yml new file mode 100644 index 0000000..27aafa2 --- /dev/null +++ b/node_modules/jsftp/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - "0.10" + - "0.8" + - "0.6" + +before_script: + - "npm install --dev" + diff --git a/node_modules/jsftp/LICENSE b/node_modules/jsftp/LICENSE new file mode 100644 index 0000000..8a34f6d --- /dev/null +++ b/node_modules/jsftp/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright(c) 2011 Ajax.org B.V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/node_modules/jsftp/Makefile b/node_modules/jsftp/Makefile new file mode 100644 index 0000000..78f4696 --- /dev/null +++ b/node_modules/jsftp/Makefile @@ -0,0 +1,31 @@ +# use the tools as dev dependencies rather than installing them globaly +# it lets you handle specific versions of the tooling for each of your projects +MOCHA=node_modules/.bin/mocha +ISTANBUL=node_modules/.bin/istanbul +JSHINT=node_modules/.bin/jshint + +# test files must end with ".test.js" +TESTS=$(shell find test/ -name "*.test.js") + +clean: + rm -rf reports + +test: + $(MOCHA) -R spec $(TESTS) + +_MOCHA="node_modules/.bin/_mocha" +coverage: + @# check if reports folder exists, if not create it + @test -d reports || mkdir reports + $(ISTANBUL) cover --report lcovonly --dir ./reports $(_MOCHA) -- -R spec $(TESTS) + genhtml reports/lcov.info --output-directory reports/ + +jshint: + $(JSHINT) lib test --show-non-errors + +checkstyle: + @# check if reports folder exists, if not create it + @test -d reports || mkdir reports + $(JSHINT) lib test --reporter=checkstyle > reports/checkstyle.xml + +.PHONY: clean test coverage jshint checkstyle diff --git a/node_modules/jsftp/README.md b/node_modules/jsftp/README.md new file mode 100644 index 0000000..51d9fae --- /dev/null +++ b/node_modules/jsftp/README.md @@ -0,0 +1,235 @@ +jsftp Flattr this +===== + +A client FTP library for NodeJS that focuses on correctness, clarity +and conciseness. It doesn't get in the way and plays nice with streaming APIs. + +[![NPM](https://nodei.co/npm/jsftp.png)](https://nodei.co/npm/jsftp/) + +**Warning: The latest version (1.0.0) of jsftp breaks API compatibility with previous +versions, it is NOT a drop-in replacement. Please be careful when upgrading. The +API changes are not drastic at all and it is all documented below. If you do not +want to upgrade yet you should stay with version 0.6.0, the last one before the +upgrade. The API docs below are updated for 1.0.** + +Starting it up +-------------- + +```javascript +var JSFtp = require("jsftp"); + +var Ftp = new JSFtp({ + host: "myserver.com", + port: 3331, // defaults to 21 + user: "user", // defaults to "anonymous" + pass: "1234" // defaults to "@anonymous" +}; +``` + +jsftp gives you access to all the raw commands of the FTP protocol in form of +methods in the `Ftp` object. It also provides several convenience methods for +actions that require complex chains of commands (e.g. uploading and retrieving +files, passive operations), as shown below. + +When raw commands succeed they always pass the response of the server to the +callback, in the form of an object that contains two properties: `code`, which +is the response code of the FTP operation, and `text`, which is the complete +text of the response. + +Raw (or native) commands are accessible in the form `Ftp.raw["command"](params, callback)` + +Thus, a command like `QUIT` will be called like this: + +```javascript +Ftp.raw.quit(function(err, data) { + if (err) return console.error(err); + + console.log("Bye!"); +}); +``` + +and a command like `MKD` (make directory), which accepts parameters, looks like this: + +```javascript +Ftp.raw.mkd("/new_dir", function(err, data) { + if (err) return console.error(err); + + console.log(data.text); // Show the FTP response text to the user + console.log(data.code); // Show the FTP response code to the user +}); +``` + +API and examples +---------------- + +#### new Ftp(options) + - `options` is an object with the following properties: + + ```javascript + { + host: 'localhost', // Host name for the current FTP server. + port: 3333, // Port number for the current FTP server (defaults to 21). + user: 'user', // Username + pass: 'pass', // Password + } + ``` + +Creates a new Ftp instance. + + +#### Ftp.host + +Host name for the current FTP server. + +#### Ftp.port + +Port number for the current FTP server (defaults to 21). + +#### Ftp.socket + +NodeJS socket for the current FTP server. + +#### Ftp.features + +Array of feature names for the current FTP server. It is +generated when the user authenticates with the `auth` method. + +#### Ftp.system + +Contains the system identification string for the remote FTP server. + + +### Methods + +#### Ftp.raw.FTP_COMMAND([params], callback) +All the standard FTP commands are available under the `raw` namespace. These +commands might accept parameters or not, but they always accept a callback +with the signature `err, data`, in which `err` is the error response coming +from the server (usually a 4xx or 5xx error code) and the data is an object +that contains two properties: `code` and `text`. `code` is an integer indicating +the response code of the response and `text` is the response string itself. + +#### Ftp.auth(username, password, callback) +Authenticates the user with the given username and password. If null or empty +values are passed for those, `auth` will use anonymous credentials. `callback` +will be called with the response text in case of successful login or with an +error as a first parameter, in normal Node fashion. + +#### Ftp.ls(filePath, callback) +Lists information about files or directories and yields an array of file objects +with parsed file properties to the `callback`. You should use this function +instead of `stat` or `list` in case you need to do something with the individual +file properties. + +```javascript +ftp.ls(".", function(err, res) { + res.forEach(function(file) { + console.log(file.name); + }); +}); +``` + +#### Ftp.list(filePath, callback) +Lists `filePath` contents using a passive connection. Calls callback with an +array of strings with complete file information. + +```javascript +ftp.list(remoteCWD, function(err, res) { + res.forEach(function(file) { + console.log(file.name); + }); + // Prints something like + // -rw-r--r-- 1 sergi staff 4 Jun 03 09:32 testfile1.txt + // -rw-r--r-- 1 sergi staff 4 Jun 03 09:31 testfile2.txt + // -rw-r--r-- 1 sergi staff 0 May 29 13:05 testfile3.txt + // ... +}); +``` + +#### Ftp.get(remotePath, callback) +Gives back a paused socket with the file contents ready to be streamed, +or calls the callback with an error if not successful. + +```javascript + var str = ""; // Will store the contents of the file + ftp.get('remote/path/file.txt', function(err, socket) { + if (err) return; + + socket.on("data", function(d) { str += d.toString(); }) + socket.on("close", function(hadErr) { + if (hadErr) + console.error('There was an error retrieving the file.'); + }); + socket.resume(); + }); +``` + +#### Ftp.get(remotePath, localPath, callback) +Stores the remote file directly in the given local path. + +```javascript + ftp.get('remote/file.txt, 'local/file.txt, function(hadErr) { + if (hadErr) + console.error('There was an error retrieving the file.'); + else + console.log('File copied successfully!'); + }); +``` + +#### Ftp.put(source, remotePath, callback) +Uploads a file to `filePath`. It accepts a string with the local path for the +file or a `Buffer` as a `source` parameter. + +```javascript +ftp.put(buffer, 'path/to/remote/file.txt', function(hadError) { + if (!hadError) + console.log("File transferred successfully!"); +}); +``` + +#### Ftp.rename(from, to, callback) +Renames a file in the server. `from` and `to` are both filepaths. + +```javascript +ftp.rename(from, to, function(err, res) { + if (!err) + console.log("Renaming successful!"); +}); +``` + +#### Ftp.keepAlive() +Refreshes the interval thats keep the server connection active. + +You can find more usage examples in the [unit tests](https://github.com/sergi/jsftp/blob/master/test/jsftp_test.js). This documentation +will grow as jsftp evolves. + +Installation +------------ + + npm install jsftp + +Test coverage +------------- + +In order to run coverage reports: + + npm install --dev + make coverage + + Current overall coverage rate: + lines......: 92.1% (316 of 343 lines) + functions..: 91.0% (71 of 78 functions) + + +Tests +----- + +To run tests: + + npm install --dev + make test + +License +------- + +See LICENSE. diff --git a/node_modules/jsftp/index.js b/node_modules/jsftp/index.js new file mode 100644 index 0000000..f03f905 --- /dev/null +++ b/node_modules/jsftp/index.js @@ -0,0 +1,2 @@ +var libpath = process.env['VFS_FTP_COV'] ? './lib-cov' : './lib'; +module.exports = require(libpath + "/jsftp"); diff --git a/node_modules/jsftp/lib/jsftp.js b/node_modules/jsftp/lib/jsftp.js new file mode 100644 index 0000000..eee2505 --- /dev/null +++ b/node_modules/jsftp/lib/jsftp.js @@ -0,0 +1,650 @@ +/* vim:set ts=2 sw=2 sts=2 expandtab */ +/*global require: true module: true */ +/* + * @package jsftp + * @copyright Copyright(c) 2012 Ajax.org B.V. + * @author Sergi Mansilla + * @license https://github.com/sergi/jsFTP/blob/master/LICENSE MIT License + */ + +var Net = require("net"); +var EventEmitter = require("events").EventEmitter; +var es = require("event-stream"); +var responseHandler = require("./response"); +var Utils = require("./utils"); +var util = require("util"); +var fs = require("fs"); + +var FTP_PORT = 21; +var DEBUG_MODE = false; +var TIMEOUT = 10 * 60 * 1000; +var IDLE_TIME = 30000; +var COMMANDS = [ + // Commands without parameters + "abor", "pwd", "cdup", "feat", "noop", "quit", "pasv", "syst", + // Commands with one or more parameters + "cwd", "dele", "list", "mdtm", "mkd", "mode", "nlst", "pass", "retr", "rmd", + "rnfr", "rnto", "site", "stat", "stor", "type", "user", "pass", "xrmd", "opts", + // Extended features + "chmod", "size" +]; + +var Cmds = {}; +COMMANDS.forEach(function(cmd) { + cmd = cmd.toLowerCase(); + Cmds[cmd] = function() { + var callback = function() {}; + var completeCmd = cmd; + if (arguments.length) { + var args = Array.prototype.slice.call(arguments); + if (typeof args[args.length - 1] === "function") + callback = args.pop(); + + completeCmd += " " + args.join(" "); + } + this.execute(completeCmd.trim(), callback); + }; +}); + +function once(fn) { + var returnValue, called = false; + return function() { + if (!called) { + called = true; + returnValue = fn.apply(this, arguments); + } + return returnValue; + }; +} + +var Ftp = module.exports = function(cfg) { + "use strict"; + + Object.keys(cfg).forEach(function(opt) { + if (!this[opt]) this[opt] = cfg[opt]; + }, this); + + EventEmitter.call(this); + + // True if the server doesn't support the `stat` command. Since listing a + // directory or retrieving file properties is quite a common operation, it is + // more efficient to avoid the round-trip to the server. + this.useList = false; + this.port = this.port || FTP_PORT; + this.pending = []; // Pending requests + this.cmdBuffer_ = []; + this.responseHandler = responseHandler(); + + // Generate generic methods from parameter names. they can easily be + // overriden if we need special behavior. they accept any parameters given, + // it is the responsability of the user to validate the parameters. + var raw = this.raw = {}; + COMMANDS.forEach(function(cmd) { raw[cmd] = Cmds[cmd].bind(this); }, this); + + this.socket = this._createSocket(this.port, this.host); +}; + +util.inherits(Ftp, EventEmitter); + +Ftp.prototype.reemit = function(event) { + var self = this; + return function(data) { self.emit(event, data); } +}; + +Ftp.prototype._createSocket = function(port, host, firstAction) { + if (this.socket && this.socket.destroy) this.socket.destroy(); + + this.authenticated = false; + var socket = Net.createConnection(port, host); + socket.on("connect", this.reemit("connect")); + socket.on("timeout", this.reemit("timeout")); + + if (firstAction) + socket.once("connect", firstAction); + + this._createStreams(socket); + + return socket; +}; + +Ftp.prototype._createStreams = function(socket) { + this.pipeline = es.pipeline( + socket, + es.split(), + es.mapSync(this.responseHandler)); + + var self = this; + this.pipeline.on('data', function(data) { + self.emit('data', data); + self.parseResponse.call(self, data) + }); + this.pipeline.on("error", this.reemit("error")); +}; + +Ftp.prototype.parseResponse = function(data) { + if (!this.cmdBuffer_.length) + return; + + if ([220].indexOf(data.code) > -1) + return; + + var next = this.cmdBuffer_[0][1]; + if (Utils.isMark(data.code)) { + // If we receive a Mark and it is not expected, we ignore + // that command + if (!next.expectsMark || next.expectsMark.marks.indexOf(data.code) === -1) + return; + // We might have to ignore the command that comes after the + // mark. + if (next.expectsMark.ignore) + this.ignoreCmdCode = next.expectsMark.ignore; + } + + if (this.ignoreCmdCode && this.ignoreCmdCode === data.code) { + this.ignoreCmdCode = null; + return; + } + + this.parse(data, this.cmdBuffer_.shift()); +}; + +/** + * Writes a new command to the server. + * + * @param {String} command Command to write in the FTP socket + * @returns void + */ +Ftp.prototype.send = function(command) { + if (!command || typeof command !== "string") + return; + + this.emit("cmdSend", command); + this.pipeline.write(command + "\r\n"); +}; + +Ftp.prototype.nextCmd = function() { + if (!this.inProgress && this.cmdBuffer_[0]) { + this.send(this.cmdBuffer_[0][0]); + this.inProgress = true; + } +}; + +/** + * Check whether the ftp user is authenticated at the moment of the + * enqueing. ideally this should happen in the `push` method, just + * before writing to the socket, but that would be complicated, + * since we would have to 'unshift' the auth chain into the queue + * or play the raw auth commands (that is, without enqueuing in + * order to not mess up the queue order. ideally, that would be + * built into the queue object. all this explanation to justify a + * slight slopiness in the code flow. + * + * @param {string} action + * @param {function} callback + * @return void + */ +Ftp.prototype.execute = function(action, callback) { + if (!callback) callback = function() {}; + + if (this.socket && this.socket.writable) { + this._executeCommand(action, callback); + } else { + var self = this; + this.authenticated = false; + this.socket = this._createSocket(this.port, this.host, function() { + self._executeCommand(action, callback); + }); + } +}; + +Ftp.prototype._executeCommand = function(action, callback) { + var self = this; + + function executeCmd() { + self.cmdBuffer_.push([action, callback]); + self.nextCmd(); + } + + if (self.authenticated || /feat|syst|user|pass/.test(action)) { + executeCmd(); + } else { + this.getFeatures(function() { + self.auth(self.user, self.pass, executeCmd); + }); + } +}; + +/** + * Parse is called each time that a comand and a request are paired + * together. That is, each time that there is a round trip of actions + * between the client and the server. The `action` param contains an array + * with the response from the server as a first element (text) and an array + * with the command executed and the callback (if any) as the second + * element. + * + * @param action {Array} Contains server response and client command info. + */ +Ftp.prototype.parse = function(response, command) { + // In FTP every response code above 399 means error in some way. + // Since the RFC is not respected by many servers, we are going to + // overgeneralize and consider every value above 399 as an error. + var err = null; + if (response.code > 399) { + err = new Error(response.text || "Unknown FTP error."); + err.code = response.code; + } + + command[1](err, response); + this.inProgress = false; + this.nextCmd(); +}; + +/** + * Returns true if the current server has the requested feature. False otherwise. + * + * @param {String} feature Feature to look for + * @returns {Boolean} Whether the current server has the feature + */ +Ftp.prototype.hasFeat = function(feature) { + if (feature) + return this.features.indexOf(feature.toLowerCase()) > -1; +}; + +/** + * Returns an array of features supported by the current FTP server + * + * @param {String} features Server response for the 'FEAT' command + * @returns {String[]} Array of feature names + */ +Ftp.prototype._parseFeats = function(features) { + // Ignore header and footer + return features.split(/\r\n|\n/).slice(1, -1).map(function(feat) { + return (/^\s*(\w*)\s*/).exec(feat)[1].trim().toLowerCase(); + }); +}; + + +// Below this point all the methods are action helpers for FTP that compose +// several actions in one command + +Ftp.prototype.getFeatures = function(callback) { + var self = this; + if (!this.features) + this.raw.feat(function(err, response) { + self.features = err ? [] : self._parseFeats(response.text); + self.raw.syst(function(err, res) { + if (!err && res.code === 215) + self.system = res.text.toLowerCase(); + + callback(null, self.features); + }); + }); + else + callback(null, self.features); +}; + +/** + * Authenticates the user. + * + * @param user {String} Username + * @param pass {String} Password + * @param callback {Function} Follow-up function. + */ +Ftp.prototype.auth = function(user, pass, callback) { + this.pending.push(callback); + + var self = this; + + function notifyAll(err, res) { + var cb; + while (cb = self.pending.shift()) + cb(err, res); + } + + if (this.authenticating) return; + + if (!user) user = "anonymous"; + if (!pass) pass = "@anonymous"; + + this.authenticating = true; + self.raw.user(user, function(err, res) { + if (!err && [230, 331, 332].indexOf(res.code) > -1) { + self.raw.pass(pass, function(err, res) { + self.authenticating = false; + + if (err) + notifyAll(new Error("Login not accepted")); + + if ([230, 202].indexOf(res.code) > -1) { + self.authenticated = true; + self.user = user; + self.pass = pass; + self.raw.type("I", function() { + notifyAll(null, res); + }); + } else if (res.code === 332) { + self.raw.acct(""); // ACCT not really supported + } + }); + } else { + self.authenticating = false; + notifyAll(new Error("Login not accepted")); + } + }); +}; + +Ftp.prototype.setType = function(type, callback) { + if (this.type === type) + callback(null); + + var self = this; + this.raw.type(type, function(err, data) { + if (!err) self.type = type; + + callback(err, data); + }); +}; + +/** + * Lists a folder's contents using a passive connection. + * + * @param {String} [path] Remote path for the file/folder to retrieve + * @param {Function} callback Function to call with errors or results + */ +Ftp.prototype.list = function(path, callback) { + if (arguments.length === 1) { + callback = arguments[0]; + path = ""; + } + + var self = this; + var cb = function(err, listing) { + self.setType("I", once(function() { + callback(err, listing); + })); + }; + cb.expectsMark = { + marks: [125, 150], + ignore: 226 + }; + + var listing = ""; + this.setType("A", function() { + self.getPasvSocket(function(err, socket) { + socket.on("data", function(data) { + listing += data; + }); + socket.on("close", function(err) { + cb(err || null, listing); + }); + socket.on("error", cb); + + self.send("list " + (path || "")); + }); + }); +}; + +Ftp.prototype.emitProgress = function(data) { + this.emit('progress', { + filename: data.filename, + action: data.action, + total: data.totalSize || 0, + transferred: data.socket[ + data.action === 'get' ? 'bytesRead' : 'bytesWritten'] + }); +}; + +/** + * Depending on the number of parameters, returns the content of the specified + * file or directly saves a file into the specified destination. In the latter + * case, an optional callback can be provided, which will receive the error in + * case the operation was not successful. + * + * @param {String} remotePath File to be retrieved from the FTP server + * @param {String} localPath Local path where the new file will be created + * @param {Function} [callback] Gets called on either success or failure + */ +Ftp.prototype.get = function(remotePath, localPath, callback) { + var self = this; + if (arguments.length === 2) { + callback = once(localPath || function() {}); + this.getGetSocket(remotePath, callback); + } else { + callback = once(callback || function() {}); + this.getGetSocket(remotePath, function(err, socket) { + if (err) { + callback(err); + } + + var writeStream = fs.createWriteStream(localPath); + writeStream.on('error', callback); + + socket.on('readable', function() { + self.emitProgress({ + filename: remotePath, + action: 'get', + socket: this + }); + }); + socket.on('end', callback); + socket.pipe(writeStream); + socket.resume(); + }) + } +}; + +/** + * Returns a socket for a get (RETR) on a path. The socket is ready to be + * streamed, but it is returned in a paused state. It is left to the user to + * resume it. + * + * @param path {String} Path to the file to be retrieved + * @param callback {Function} Function to call when finalized, with the socket as a parameter + */ +Ftp.prototype.getGetSocket = function(path, callback) { + var self = this; + callback = once(callback); + this.getPasvSocket(function(err, socket) { + if (err) return cmdCallback(err); + + socket.pause(); + + function cmdCallback(err, res) { + if (err) return callback(err); + + if (res.code === 150) + callback(null, socket); + else + callback(new Error("Unexpected command " + res.text)); + } + + cmdCallback.expectsMark = { + marks: [125, 150], + ignore: 226 + }; + self.execute("retr " + path, cmdCallback); + }); +}; + +/** + * Uploads contents on a FTP server. The `from` parameter can be a Buffer or the + * path for a local file to be uploaded. + * + * @param {String|Buffer} from Contents to be uploaded. + * @param {String} to path for the remote destination. + * @param {Function} callback Function to execute on error or success. + */ +Ftp.prototype.put = function(from, to, callback) { + if (from instanceof Buffer) { + this.getPutSocket(to, function(err, socket) { + if (!err) socket.end(from); + }, callback); + } else { + var self = this; + fs.exists(from, function(exists) { + if (!exists) + return callback(new Error("Local file doesn't exist.")); + + self.getPutSocket(to, function(err, socket) { + if (err) return; + + fs.stat(from, function(err, stats) { + var totalSize = err ? 0 : stats.size; + var read = fs.createReadStream(from, { + bufferSize: 4 * 1024 + }); + read.pipe(socket); + read.on('readable', function() { + self.emitProgress({ + filename: to, + action: 'put', + socket: read, + totalSize: totalSize + }); + }); + }); + }, callback); + }); + } +}; + +Ftp.prototype.getPutSocket = function(path, callback, doneCallback) { + if (!callback) throw new Error("A callback argument is required."); + + doneCallback = once(doneCallback || function() {}); + var _callback = once(function(err, _socket) { + if (err) { + callback(err); + return doneCallback(err); + } + return callback(err, _socket); + }); + + var self = this; + this.getPasvSocket(function(err, socket) { + if (err) return _callback(err); + + var putCallback = once(function putCallback(err, res) { + if (err) return _callback(err); + + // Mark 150 indicates that the 'STOR' socket is ready to receive data. + // Anything else is not relevant. + if (res.code === 150) { + socket.on('close', doneCallback); + socket.on('error', doneCallback); + _callback(null, socket); + } else { + return _callback(new Error("Unexpected command " + res.text)); + } + }); + putCallback.expectsMark = { + marks: [125, 150], + ignore: 226 + }; + self.execute("stor " + path, putCallback); + }); +}; + +Ftp.prototype.getPasvSocket = function(callback) { + var timeout = this.timeout; + callback = once(callback || function() {}); + this.execute("pasv", function(err, res) { + if (err) return callback(err); + + var pasvRes = Utils.getPasvPort(res.text); + if (pasvRes === false) + return callback(new Error("PASV: Bad host/port combination")); + + var host = pasvRes[0]; + var port = pasvRes[1]; + var socket = Net.createConnection(port, host); + socket.setTimeout(timeout || TIMEOUT); + callback(null, socket); + }); +}; + +/** + * Provides information about files. It lists a directory contents or + * a single file and yields an array of file objects. The file objects + * contain several properties. The main difference between this method and + * 'list' or 'stat' is that it returns objects with the file properties + * already parsed. + * + * Example of file object: + * + * { + * name: 'README.txt', + * type: 0, + * time: 996052680000, + * size: '2582', + * owner: 'sergi', + * group: 'staff', + * userPermissions: { read: true, write: true, exec: false }, + * groupPermissions: { read: true, write: false, exec: false }, + * otherPermissions: { read: true, write: false, exec: false } + * } + * + * The constants used in the object are defined in ftpParser.js + * + * @param filePath {String} Path to the file or directory to list + * @param callback {Function} Function to call with the proper data when + * the listing is finished. + */ +Ftp.prototype.ls = function(filePath, callback) { + function entriesToList(err, entries) { + if (err) { + return callback(err); + } + callback(null, Utils.parseEntry(entries.text || entries)); + } + + if (this.useList) { + this.list(filePath, entriesToList); + } else { + var self = this; + this.raw.stat(filePath, function(err, data) { + // We might be connected to a server that doesn't support the + // 'STAT' command, which is set as default. We use 'LIST' instead, + // and we set the variable `useList` to true, to avoid extra round + // trips to the server to check. + if ((err && (err.code === 502 || err.code === 500)) || + (self.system && self.system.indexOf("hummingbird") > -1)) + // Not sure if the "hummingbird" system check ^^^ is still + // necessary. If they support any standards, the 500 error + // should have us covered. Let's leave it for now. + { + self.useList = true; + self.list(filePath, entriesToList); + } else { + entriesToList(err, data); + } + }); + } +}; + +Ftp.prototype.rename = function(from, to, callback) { + var self = this; + this.raw.rnfr(from, function(err, res) { + if (err) return callback(err); + self.raw.rnto(to, function(err, res) { + callback(err, res); + }); + }); +}; + +Ftp.prototype.keepAlive = function() { + var self = this; + if (this._keepAliveInterval) + clearInterval(this._keepAliveInterval); + + this._keepAliveInterval = setInterval(self.raw.noop, IDLE_TIME); +}; + +Ftp.prototype.destroy = function() { + if (this._keepAliveInterval) + clearInterval(this._keepAliveInterval); + + this.socket.destroy(); + this.features = null; + this.authenticated = false; +}; diff --git a/node_modules/jsftp/lib/lib/jsftp.js b/node_modules/jsftp/lib/lib/jsftp.js new file mode 100644 index 0000000..6901348 --- /dev/null +++ b/node_modules/jsftp/lib/lib/jsftp.js @@ -0,0 +1,6 @@ +if (typeof __coverage__ === 'undefined') { __coverage__ = {}; } +if (!__coverage__['/Users/sergi/programming/jsftp/lib/jsftp.js']) { + __coverage__['/Users/sergi/programming/jsftp/lib/jsftp.js'] = {"path":"/Users/sergi/programming/jsftp/lib/jsftp.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0,"122":0,"123":0,"124":0,"125":0,"126":0,"127":0,"128":0,"129":0,"130":0,"131":0,"132":0,"133":0,"134":0,"135":0,"136":0,"137":0,"138":0,"139":0,"140":0,"141":0,"142":0,"143":0,"144":0,"145":0,"146":0,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":0,"155":0,"156":0,"157":0,"158":0,"159":0,"160":0,"161":0,"162":0,"163":0,"164":0,"165":0,"166":0,"167":0,"168":0,"169":0,"170":0,"171":0,"172":0,"173":0,"174":0,"175":0,"176":0,"177":0,"178":0,"179":0,"180":0,"181":0,"182":0,"183":0,"184":0,"185":0,"186":0,"187":0,"188":0,"189":0,"190":0,"191":0,"192":0,"193":0,"194":0,"195":0,"196":0,"197":0,"198":0,"199":0,"200":0,"201":0,"202":0,"203":0,"204":0,"205":0,"206":0,"207":0,"208":0,"209":0,"210":0,"211":0,"212":0,"213":0,"214":0,"215":0,"216":0,"217":0,"218":0,"219":0,"220":0,"221":0,"222":0,"223":0,"224":0,"225":0,"226":0,"227":0,"228":0,"229":0,"230":0,"231":0,"232":0,"233":0,"234":0,"235":0,"236":0,"237":0,"238":0,"239":0,"240":0,"241":0,"242":0,"243":0,"244":0,"245":0,"246":0,"247":0,"248":0,"249":0,"250":0,"251":0,"252":0,"253":0,"254":0,"255":0,"256":0,"257":0,"258":0,"259":0,"260":0,"261":0,"262":0,"263":0,"264":0,"265":0,"266":0,"267":0,"268":0,"269":0,"270":0,"271":0,"272":0,"273":0,"274":0,"275":0,"276":0,"277":0,"278":0,"279":0,"280":0,"281":0,"282":0,"283":0,"284":0,"285":0,"286":0,"287":0,"288":0,"289":0,"290":0,"291":0,"292":0,"293":0,"294":0,"295":0,"296":0,"297":0,"298":0,"299":0,"300":0,"301":0,"302":0,"303":0,"304":0,"305":0,"306":0,"307":0,"308":0,"309":0,"310":0,"311":0,"312":0,"313":0,"314":0,"315":0,"316":0,"317":0,"318":0,"319":0,"320":0,"321":0,"322":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0],"23":[0,0],"24":[0,0],"25":[0,0],"26":[0,0],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0],"33":[0,0],"34":[0,0],"35":[0,0],"36":[0,0],"37":[0,0],"38":[0,0],"39":[0,0],"40":[0,0],"41":[0,0],"42":[0,0],"43":[0,0],"44":[0,0],"45":[0,0],"46":[0,0],"47":[0,0],"48":[0,0],"49":[0,0],"50":[0,0],"51":[0,0],"52":[0,0],"53":[0,0],"54":[0,0],"55":[0,0],"56":[0,0],"57":[0,0],"58":[0,0],"59":[0,0],"60":[0,0],"61":[0,0],"62":[0,0],"63":[0,0],"64":[0,0],"65":[0,0],"66":[0,0],"67":[0,0],"68":[0,0],"69":[0,0],"70":[0,0],"71":[0,0],"72":[0,0],"73":[0,0],"74":[0,0],"75":[0,0,0,0,0],"76":[0,0],"77":[0,0],"78":[0,0]},"f":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0},"fnMap":{"1":{"name":"(anonymous_1)","line":34,"loc":{"start":{"line":34,"column":17},"end":{"line":34,"column":31}}},"2":{"name":"(anonymous_2)","line":36,"loc":{"start":{"line":36,"column":14},"end":{"line":36,"column":25}}},"3":{"name":"(anonymous_3)","line":37,"loc":{"start":{"line":37,"column":19},"end":{"line":37,"column":30}}},"4":{"name":"once","line":50,"loc":{"start":{"line":50,"column":0},"end":{"line":50,"column":18}}},"5":{"name":"(anonymous_5)","line":52,"loc":{"start":{"line":52,"column":9},"end":{"line":52,"column":20}}},"6":{"name":"(anonymous_6)","line":61,"loc":{"start":{"line":61,"column":27},"end":{"line":61,"column":41}}},"7":{"name":"(anonymous_7)","line":64,"loc":{"start":{"line":64,"column":27},"end":{"line":64,"column":41}}},"8":{"name":"(anonymous_8)","line":83,"loc":{"start":{"line":83,"column":19},"end":{"line":83,"column":33}}},"9":{"name":"(anonymous_9)","line":85,"loc":{"start":{"line":85,"column":19},"end":{"line":85,"column":33}}},"10":{"name":"(anonymous_10)","line":95,"loc":{"start":{"line":95,"column":33},"end":{"line":95,"column":44}}},"11":{"name":"(anonymous_11)","line":101,"loc":{"start":{"line":101,"column":31},"end":{"line":101,"column":45}}},"12":{"name":"(anonymous_12)","line":107,"loc":{"start":{"line":107,"column":33},"end":{"line":107,"column":44}}},"13":{"name":"(anonymous_13)","line":113,"loc":{"start":{"line":113,"column":30},"end":{"line":113,"column":51}}},"14":{"name":"(anonymous_14)","line":129,"loc":{"start":{"line":129,"column":31},"end":{"line":129,"column":48}}},"15":{"name":"(anonymous_15)","line":136,"loc":{"start":{"line":136,"column":27},"end":{"line":136,"column":42}}},"16":{"name":"(anonymous_16)","line":144,"loc":{"start":{"line":144,"column":30},"end":{"line":144,"column":45}}},"17":{"name":"(anonymous_17)","line":178,"loc":{"start":{"line":178,"column":21},"end":{"line":178,"column":39}}},"18":{"name":"(anonymous_18)","line":188,"loc":{"start":{"line":188,"column":24},"end":{"line":188,"column":35}}},"19":{"name":"(anonymous_19)","line":207,"loc":{"start":{"line":207,"column":25},"end":{"line":207,"column":52}}},"20":{"name":"(anonymous_20)","line":208,"loc":{"start":{"line":208,"column":28},"end":{"line":208,"column":39}}},"21":{"name":"execute","line":220,"loc":{"start":{"line":220,"column":2},"end":{"line":220,"column":21}}},"22":{"name":"executeCmd","line":221,"loc":{"start":{"line":221,"column":4},"end":{"line":221,"column":26}}},"23":{"name":"(anonymous_23)","line":230,"loc":{"start":{"line":230,"column":23},"end":{"line":230,"column":34}}},"24":{"name":"(anonymous_24)","line":247,"loc":{"start":{"line":247,"column":22},"end":{"line":247,"column":39}}},"25":{"name":"(anonymous_25)","line":273,"loc":{"start":{"line":273,"column":24},"end":{"line":273,"column":42}}},"26":{"name":"(anonymous_26)","line":286,"loc":{"start":{"line":286,"column":28},"end":{"line":286,"column":47}}},"27":{"name":"(anonymous_27)","line":288,"loc":{"start":{"line":288,"column":52},"end":{"line":288,"column":67}}},"28":{"name":"(anonymous_28)","line":297,"loc":{"start":{"line":297,"column":28},"end":{"line":297,"column":47}}},"29":{"name":"(anonymous_29)","line":300,"loc":{"start":{"line":300,"column":18},"end":{"line":300,"column":42}}},"30":{"name":"(anonymous_30)","line":302,"loc":{"start":{"line":302,"column":20},"end":{"line":302,"column":39}}},"31":{"name":"(anonymous_31)","line":320,"loc":{"start":{"line":320,"column":21},"end":{"line":320,"column":52}}},"32":{"name":"notifyAll","line":325,"loc":{"start":{"line":325,"column":2},"end":{"line":325,"column":31}}},"33":{"name":"(anonymous_33)","line":337,"loc":{"start":{"line":337,"column":22},"end":{"line":337,"column":41}}},"34":{"name":"(anonymous_34)","line":339,"loc":{"start":{"line":339,"column":26},"end":{"line":339,"column":45}}},"35":{"name":"(anonymous_35)","line":349,"loc":{"start":{"line":349,"column":29},"end":{"line":349,"column":40}}},"36":{"name":"(anonymous_36)","line":363,"loc":{"start":{"line":363,"column":24},"end":{"line":363,"column":49}}},"37":{"name":"(anonymous_37)","line":368,"loc":{"start":{"line":368,"column":22},"end":{"line":368,"column":42}}},"38":{"name":"(anonymous_38)","line":381,"loc":{"start":{"line":381,"column":21},"end":{"line":381,"column":46}}},"39":{"name":"(anonymous_39)","line":388,"loc":{"start":{"line":388,"column":11},"end":{"line":388,"column":34}}},"40":{"name":"(anonymous_40)","line":389,"loc":{"start":{"line":389,"column":27},"end":{"line":389,"column":38}}},"41":{"name":"(anonymous_41)","line":396,"loc":{"start":{"line":396,"column":20},"end":{"line":396,"column":31}}},"42":{"name":"(anonymous_42)","line":397,"loc":{"start":{"line":397,"column":23},"end":{"line":397,"column":45}}},"43":{"name":"(anonymous_43)","line":398,"loc":{"start":{"line":398,"column":24},"end":{"line":398,"column":39}}},"44":{"name":"(anonymous_44)","line":399,"loc":{"start":{"line":399,"column":25},"end":{"line":399,"column":39}}},"45":{"name":"(anonymous_45)","line":418,"loc":{"start":{"line":418,"column":20},"end":{"line":418,"column":62}}},"46":{"name":"(anonymous_46)","line":420,"loc":{"start":{"line":420,"column":33},"end":{"line":420,"column":44}}},"47":{"name":"(anonymous_47)","line":422,"loc":{"start":{"line":422,"column":34},"end":{"line":422,"column":56}}},"48":{"name":"(anonymous_48)","line":428,"loc":{"start":{"line":428,"column":32},"end":{"line":428,"column":43}}},"49":{"name":"(anonymous_49)","line":429,"loc":{"start":{"line":429,"column":34},"end":{"line":429,"column":56}}},"50":{"name":"onError","line":431,"loc":{"start":{"line":431,"column":6},"end":{"line":431,"column":28}}},"51":{"name":"(anonymous_51)","line":439,"loc":{"start":{"line":439,"column":23},"end":{"line":439,"column":34}}},"52":{"name":"(anonymous_52)","line":454,"loc":{"start":{"line":454,"column":29},"end":{"line":454,"column":54}}},"53":{"name":"(anonymous_53)","line":457,"loc":{"start":{"line":457,"column":21},"end":{"line":457,"column":43}}},"54":{"name":"cmdCallback","line":462,"loc":{"start":{"line":462,"column":4},"end":{"line":462,"column":35}}},"55":{"name":"(anonymous_55)","line":484,"loc":{"start":{"line":484,"column":20},"end":{"line":484,"column":49}}},"56":{"name":"(anonymous_56)","line":486,"loc":{"start":{"line":486,"column":26},"end":{"line":486,"column":48}}},"57":{"name":"(anonymous_57)","line":492,"loc":{"start":{"line":492,"column":20},"end":{"line":492,"column":37}}},"58":{"name":"(anonymous_58)","line":496,"loc":{"start":{"line":496,"column":28},"end":{"line":496,"column":50}}},"59":{"name":"(anonymous_59)","line":506,"loc":{"start":{"line":506,"column":29},"end":{"line":506,"column":68}}},"60":{"name":"(anonymous_60)","line":509,"loc":{"start":{"line":509,"column":38},"end":{"line":509,"column":49}}},"61":{"name":"(anonymous_61)","line":510,"loc":{"start":{"line":510,"column":23},"end":{"line":510,"column":46}}},"62":{"name":"(anonymous_62)","line":519,"loc":{"start":{"line":519,"column":21},"end":{"line":519,"column":43}}},"63":{"name":"putCallback","line":522,"loc":{"start":{"line":522,"column":27},"end":{"line":522,"column":58}}},"64":{"name":"(anonymous_64)","line":541,"loc":{"start":{"line":541,"column":30},"end":{"line":541,"column":49}}},"65":{"name":"(anonymous_65)","line":543,"loc":{"start":{"line":543,"column":30},"end":{"line":543,"column":41}}},"66":{"name":"(anonymous_66)","line":544,"loc":{"start":{"line":544,"column":24},"end":{"line":544,"column":43}}},"67":{"name":"(anonymous_67)","line":586,"loc":{"start":{"line":586,"column":19},"end":{"line":586,"column":48}}},"68":{"name":"entriesToList","line":587,"loc":{"start":{"line":587,"column":2},"end":{"line":587,"column":39}}},"69":{"name":"(anonymous_69)","line":597,"loc":{"start":{"line":597,"column":28},"end":{"line":597,"column":48}}},"70":{"name":"(anonymous_70)","line":618,"loc":{"start":{"line":618,"column":23},"end":{"line":618,"column":52}}},"71":{"name":"(anonymous_71)","line":620,"loc":{"start":{"line":620,"column":22},"end":{"line":620,"column":41}}},"72":{"name":"(anonymous_72)","line":622,"loc":{"start":{"line":622,"column":22},"end":{"line":622,"column":41}}},"73":{"name":"(anonymous_73)","line":626,"loc":{"start":{"line":626,"column":26},"end":{"line":626,"column":37}}},"74":{"name":"(anonymous_74)","line":634,"loc":{"start":{"line":634,"column":24},"end":{"line":634,"column":35}}}},"statementMap":{"1":{"start":{"line":10,"column":0},"end":{"line":10,"column":25}},"2":{"start":{"line":11,"column":0},"end":{"line":11,"column":50}},"3":{"start":{"line":12,"column":0},"end":{"line":12,"column":31}},"4":{"start":{"line":13,"column":0},"end":{"line":13,"column":33}},"5":{"start":{"line":14,"column":0},"end":{"line":14,"column":44}},"6":{"start":{"line":15,"column":0},"end":{"line":15,"column":31}},"7":{"start":{"line":16,"column":0},"end":{"line":16,"column":27}},"8":{"start":{"line":17,"column":0},"end":{"line":17,"column":23}},"9":{"start":{"line":19,"column":0},"end":{"line":19,"column":18}},"10":{"start":{"line":20,"column":0},"end":{"line":20,"column":23}},"11":{"start":{"line":21,"column":0},"end":{"line":21,"column":29}},"12":{"start":{"line":22,"column":0},"end":{"line":22,"column":22}},"13":{"start":{"line":23,"column":0},"end":{"line":31,"column":2}},"14":{"start":{"line":33,"column":0},"end":{"line":33,"column":14}},"15":{"start":{"line":34,"column":0},"end":{"line":48,"column":3}},"16":{"start":{"line":35,"column":2},"end":{"line":35,"column":26}},"17":{"start":{"line":36,"column":2},"end":{"line":47,"column":4}},"18":{"start":{"line":37,"column":4},"end":{"line":37,"column":33}},"19":{"start":{"line":38,"column":4},"end":{"line":38,"column":26}},"20":{"start":{"line":39,"column":4},"end":{"line":45,"column":5}},"21":{"start":{"line":40,"column":6},"end":{"line":40,"column":55}},"22":{"start":{"line":41,"column":6},"end":{"line":42,"column":30}},"23":{"start":{"line":42,"column":8},"end":{"line":42,"column":30}},"24":{"start":{"line":44,"column":6},"end":{"line":44,"column":42}},"25":{"start":{"line":46,"column":4},"end":{"line":46,"column":48}},"26":{"start":{"line":50,"column":0},"end":{"line":59,"column":1}},"27":{"start":{"line":51,"column":2},"end":{"line":51,"column":34}},"28":{"start":{"line":52,"column":2},"end":{"line":58,"column":4}},"29":{"start":{"line":53,"column":4},"end":{"line":56,"column":5}},"30":{"start":{"line":54,"column":6},"end":{"line":54,"column":20}},"31":{"start":{"line":55,"column":6},"end":{"line":55,"column":46}},"32":{"start":{"line":57,"column":4},"end":{"line":57,"column":23}},"33":{"start":{"line":61,"column":0},"end":{"line":91,"column":2}},"34":{"start":{"line":64,"column":2},"end":{"line":66,"column":11}},"35":{"start":{"line":65,"column":4},"end":{"line":65,"column":41}},"36":{"start":{"line":65,"column":20},"end":{"line":65,"column":41}},"37":{"start":{"line":68,"column":2},"end":{"line":68,"column":26}},"38":{"start":{"line":73,"column":2},"end":{"line":73,"column":23}},"39":{"start":{"line":74,"column":2},"end":{"line":74,"column":36}},"40":{"start":{"line":75,"column":2},"end":{"line":75,"column":20}},"41":{"start":{"line":76,"column":2},"end":{"line":76,"column":23}},"42":{"start":{"line":77,"column":2},"end":{"line":77,"column":43}},"43":{"start":{"line":82,"column":2},"end":{"line":82,"column":26}},"44":{"start":{"line":83,"column":2},"end":{"line":83,"column":77}},"45":{"start":{"line":83,"column":35},"end":{"line":83,"column":67}},"46":{"start":{"line":85,"column":2},"end":{"line":88,"column":5}},"47":{"start":{"line":86,"column":4},"end":{"line":87,"column":23}},"48":{"start":{"line":87,"column":6},"end":{"line":87,"column":23}},"49":{"start":{"line":90,"column":2},"end":{"line":90,"column":43}},"50":{"start":{"line":93,"column":0},"end":{"line":93,"column":33}},"51":{"start":{"line":95,"column":0},"end":{"line":99,"column":2}},"52":{"start":{"line":96,"column":2},"end":{"line":96,"column":34}},"53":{"start":{"line":97,"column":2},"end":{"line":97,"column":24}},"54":{"start":{"line":98,"column":2},"end":{"line":98,"column":17}},"55":{"start":{"line":101,"column":0},"end":{"line":105,"column":2}},"56":{"start":{"line":102,"column":2},"end":{"line":102,"column":48}},"57":{"start":{"line":103,"column":2},"end":{"line":103,"column":32}},"58":{"start":{"line":104,"column":2},"end":{"line":104,"column":24}},"59":{"start":{"line":107,"column":0},"end":{"line":111,"column":2}},"60":{"start":{"line":108,"column":2},"end":{"line":108,"column":36}},"61":{"start":{"line":109,"column":2},"end":{"line":109,"column":26}},"62":{"start":{"line":110,"column":2},"end":{"line":110,"column":25}},"63":{"start":{"line":113,"column":0},"end":{"line":127,"column":2}},"64":{"start":{"line":114,"column":2},"end":{"line":114,"column":41}},"65":{"start":{"line":114,"column":19},"end":{"line":114,"column":41}},"66":{"start":{"line":116,"column":2},"end":{"line":116,"column":25}},"67":{"start":{"line":117,"column":2},"end":{"line":117,"column":29}},"68":{"start":{"line":119,"column":2},"end":{"line":119,"column":62}},"69":{"start":{"line":120,"column":2},"end":{"line":121,"column":38}},"70":{"start":{"line":122,"column":2},"end":{"line":122,"column":53}},"71":{"start":{"line":123,"column":2},"end":{"line":123,"column":57}},"72":{"start":{"line":125,"column":2},"end":{"line":125,"column":35}},"73":{"start":{"line":126,"column":2},"end":{"line":126,"column":24}},"74":{"start":{"line":129,"column":0},"end":{"line":142,"column":2}},"75":{"start":{"line":130,"column":2},"end":{"line":133,"column":38}},"76":{"start":{"line":135,"column":2},"end":{"line":135,"column":18}},"77":{"start":{"line":136,"column":2},"end":{"line":141,"column":5}},"78":{"start":{"line":137,"column":4},"end":{"line":138,"column":46}},"79":{"start":{"line":138,"column":6},"end":{"line":138,"column":46}},"80":{"start":{"line":140,"column":4},"end":{"line":141,"column":2}},"81":{"start":{"line":144,"column":0},"end":{"line":170,"column":2}},"82":{"start":{"line":145,"column":2},"end":{"line":146,"column":11}},"83":{"start":{"line":146,"column":4},"end":{"line":146,"column":11}},"84":{"start":{"line":148,"column":2},"end":{"line":149,"column":11}},"85":{"start":{"line":149,"column":4},"end":{"line":149,"column":11}},"86":{"start":{"line":151,"column":2},"end":{"line":151,"column":38}},"87":{"start":{"line":152,"column":2},"end":{"line":162,"column":3}},"88":{"start":{"line":155,"column":4},"end":{"line":156,"column":13}},"89":{"start":{"line":156,"column":6},"end":{"line":156,"column":13}},"90":{"start":{"line":160,"column":4},"end":{"line":161,"column":54}},"91":{"start":{"line":161,"column":6},"end":{"line":161,"column":54}},"92":{"start":{"line":164,"column":2},"end":{"line":167,"column":3}},"93":{"start":{"line":165,"column":4},"end":{"line":165,"column":30}},"94":{"start":{"line":166,"column":4},"end":{"line":166,"column":11}},"95":{"start":{"line":169,"column":2},"end":{"line":169,"column":46}},"96":{"start":{"line":178,"column":0},"end":{"line":186,"column":2}},"97":{"start":{"line":179,"column":2},"end":{"line":180,"column":11}},"98":{"start":{"line":180,"column":4},"end":{"line":180,"column":11}},"99":{"start":{"line":182,"column":2},"end":{"line":183,"column":58}},"100":{"start":{"line":183,"column":4},"end":{"line":183,"column":58}},"101":{"start":{"line":185,"column":2},"end":{"line":185,"column":40}},"102":{"start":{"line":188,"column":0},"end":{"line":191,"column":2}},"103":{"start":{"line":189,"column":2},"end":{"line":190,"column":37}},"104":{"start":{"line":190,"column":4},"end":{"line":190,"column":37}},"105":{"start":{"line":207,"column":0},"end":{"line":235,"column":2}},"106":{"start":{"line":208,"column":2},"end":{"line":208,"column":42}},"107":{"start":{"line":208,"column":17},"end":{"line":208,"column":42}},"108":{"start":{"line":210,"column":2},"end":{"line":210,"column":18}},"109":{"start":{"line":211,"column":2},"end":{"line":218,"column":3}},"110":{"start":{"line":212,"column":4},"end":{"line":212,"column":14}},"111":{"start":{"line":214,"column":7},"end":{"line":218,"column":3}},"112":{"start":{"line":215,"column":4},"end":{"line":215,"column":31}},"113":{"start":{"line":216,"column":4},"end":{"line":216,"column":34}},"114":{"start":{"line":217,"column":4},"end":{"line":217,"column":45}},"115":{"start":{"line":220,"column":2},"end":{"line":234,"column":3}},"116":{"start":{"line":221,"column":4},"end":{"line":224,"column":5}},"117":{"start":{"line":222,"column":6},"end":{"line":222,"column":47}},"118":{"start":{"line":223,"column":6},"end":{"line":223,"column":21}},"119":{"start":{"line":226,"column":4},"end":{"line":233,"column":5}},"120":{"start":{"line":227,"column":6},"end":{"line":227,"column":19}},"121":{"start":{"line":230,"column":6},"end":{"line":232,"column":9}},"122":{"start":{"line":231,"column":8},"end":{"line":231,"column":52}},"123":{"start":{"line":247,"column":0},"end":{"line":265,"column":2}},"124":{"start":{"line":248,"column":2},"end":{"line":248,"column":27}},"125":{"start":{"line":249,"column":2},"end":{"line":249,"column":26}},"126":{"start":{"line":250,"column":2},"end":{"line":250,"column":22}},"127":{"start":{"line":252,"column":2},"end":{"line":253,"column":46}},"128":{"start":{"line":253,"column":4},"end":{"line":253,"column":46}},"129":{"start":{"line":258,"column":2},"end":{"line":258,"column":17}},"130":{"start":{"line":259,"column":2},"end":{"line":262,"column":3}},"131":{"start":{"line":260,"column":4},"end":{"line":260,"column":59}},"132":{"start":{"line":261,"column":4},"end":{"line":261,"column":29}},"133":{"start":{"line":264,"column":2},"end":{"line":264,"column":20}},"134":{"start":{"line":273,"column":0},"end":{"line":278,"column":2}},"135":{"start":{"line":274,"column":2},"end":{"line":275,"column":61}},"136":{"start":{"line":275,"column":4},"end":{"line":275,"column":61}},"137":{"start":{"line":277,"column":2},"end":{"line":278,"column":0}},"138":{"start":{"line":286,"column":0},"end":{"line":291,"column":2}},"139":{"start":{"line":288,"column":2},"end":{"line":290,"column":5}},"140":{"start":{"line":289,"column":4},"end":{"line":289,"column":63}},"141":{"start":{"line":297,"column":0},"end":{"line":311,"column":2}},"142":{"start":{"line":298,"column":2},"end":{"line":298,"column":18}},"143":{"start":{"line":299,"column":2},"end":{"line":310,"column":34}},"144":{"start":{"line":300,"column":4},"end":{"line":308,"column":7}},"145":{"start":{"line":301,"column":6},"end":{"line":301,"column":65}},"146":{"start":{"line":302,"column":6},"end":{"line":307,"column":9}},"147":{"start":{"line":303,"column":8},"end":{"line":304,"column":47}},"148":{"start":{"line":304,"column":10},"end":{"line":304,"column":47}},"149":{"start":{"line":306,"column":8},"end":{"line":306,"column":38}},"150":{"start":{"line":310,"column":4},"end":{"line":310,"column":34}},"151":{"start":{"line":320,"column":0},"end":{"line":361,"column":2}},"152":{"start":{"line":321,"column":2},"end":{"line":321,"column":30}},"153":{"start":{"line":323,"column":2},"end":{"line":323,"column":18}},"154":{"start":{"line":325,"column":2},"end":{"line":329,"column":3}},"155":{"start":{"line":326,"column":4},"end":{"line":326,"column":11}},"156":{"start":{"line":327,"column":4},"end":{"line":328,"column":19}},"157":{"start":{"line":328,"column":6},"end":{"line":328,"column":19}},"158":{"start":{"line":331,"column":2},"end":{"line":331,"column":34}},"159":{"start":{"line":331,"column":27},"end":{"line":331,"column":34}},"160":{"start":{"line":333,"column":2},"end":{"line":333,"column":32}},"161":{"start":{"line":333,"column":13},"end":{"line":333,"column":32}},"162":{"start":{"line":334,"column":2},"end":{"line":334,"column":33}},"163":{"start":{"line":334,"column":13},"end":{"line":334,"column":33}},"164":{"start":{"line":336,"column":2},"end":{"line":336,"column":29}},"165":{"start":{"line":337,"column":2},"end":{"line":360,"column":5}},"166":{"start":{"line":338,"column":4},"end":{"line":359,"column":5}},"167":{"start":{"line":339,"column":6},"end":{"line":354,"column":9}},"168":{"start":{"line":340,"column":8},"end":{"line":340,"column":36}},"169":{"start":{"line":342,"column":8},"end":{"line":343,"column":53}},"170":{"start":{"line":343,"column":10},"end":{"line":343,"column":53}},"171":{"start":{"line":345,"column":8},"end":{"line":353,"column":9}},"172":{"start":{"line":346,"column":10},"end":{"line":346,"column":36}},"173":{"start":{"line":347,"column":10},"end":{"line":347,"column":27}},"174":{"start":{"line":348,"column":10},"end":{"line":348,"column":27}},"175":{"start":{"line":349,"column":10},"end":{"line":349,"column":67}},"176":{"start":{"line":349,"column":42},"end":{"line":349,"column":63}},"177":{"start":{"line":351,"column":13},"end":{"line":353,"column":9}},"178":{"start":{"line":352,"column":10},"end":{"line":352,"column":28}},"179":{"start":{"line":357,"column":6},"end":{"line":357,"column":34}},"180":{"start":{"line":358,"column":6},"end":{"line":358,"column":49}},"181":{"start":{"line":363,"column":0},"end":{"line":373,"column":2}},"182":{"start":{"line":364,"column":2},"end":{"line":365,"column":19}},"183":{"start":{"line":365,"column":4},"end":{"line":365,"column":19}},"184":{"start":{"line":367,"column":2},"end":{"line":367,"column":18}},"185":{"start":{"line":368,"column":2},"end":{"line":372,"column":5}},"186":{"start":{"line":369,"column":4},"end":{"line":369,"column":31}},"187":{"start":{"line":369,"column":14},"end":{"line":369,"column":31}},"188":{"start":{"line":371,"column":4},"end":{"line":371,"column":24}},"189":{"start":{"line":381,"column":0},"end":{"line":405,"column":2}},"190":{"start":{"line":382,"column":2},"end":{"line":385,"column":3}},"191":{"start":{"line":383,"column":4},"end":{"line":383,"column":28}},"192":{"start":{"line":384,"column":4},"end":{"line":384,"column":14}},"193":{"start":{"line":387,"column":2},"end":{"line":387,"column":18}},"194":{"start":{"line":388,"column":2},"end":{"line":392,"column":4}},"195":{"start":{"line":389,"column":4},"end":{"line":391,"column":8}},"196":{"start":{"line":390,"column":6},"end":{"line":390,"column":29}},"197":{"start":{"line":393,"column":2},"end":{"line":393,"column":54}},"198":{"start":{"line":395,"column":2},"end":{"line":395,"column":19}},"199":{"start":{"line":396,"column":2},"end":{"line":404,"column":5}},"200":{"start":{"line":397,"column":4},"end":{"line":403,"column":7}},"201":{"start":{"line":398,"column":6},"end":{"line":398,"column":61}},"202":{"start":{"line":398,"column":41},"end":{"line":398,"column":57}},"203":{"start":{"line":399,"column":6},"end":{"line":399,"column":70}},"204":{"start":{"line":399,"column":41},"end":{"line":399,"column":66}},"205":{"start":{"line":400,"column":6},"end":{"line":400,"column":29}},"206":{"start":{"line":402,"column":6},"end":{"line":402,"column":40}},"207":{"start":{"line":418,"column":0},"end":{"line":444,"column":2}},"208":{"start":{"line":419,"column":2},"end":{"line":443,"column":3}},"209":{"start":{"line":420,"column":4},"end":{"line":420,"column":48}},"210":{"start":{"line":422,"column":4},"end":{"line":425,"column":7}},"211":{"start":{"line":423,"column":6},"end":{"line":423,"column":36}},"212":{"start":{"line":423,"column":15},"end":{"line":423,"column":36}},"213":{"start":{"line":424,"column":6},"end":{"line":424,"column":28}},"214":{"start":{"line":428,"column":4},"end":{"line":428,"column":47}},"215":{"start":{"line":429,"column":4},"end":{"line":443,"column":2}},"216":{"start":{"line":430,"column":6},"end":{"line":430,"column":32}},"217":{"start":{"line":430,"column":17},"end":{"line":430,"column":30}},"218":{"start":{"line":431,"column":6},"end":{"line":434,"column":7}},"219":{"start":{"line":432,"column":8},"end":{"line":432,"column":67}},"220":{"start":{"line":433,"column":8},"end":{"line":433,"column":22}},"221":{"start":{"line":436,"column":6},"end":{"line":436,"column":56}},"222":{"start":{"line":437,"column":6},"end":{"line":437,"column":39}},"223":{"start":{"line":439,"column":6},"end":{"line":439,"column":55}},"224":{"start":{"line":439,"column":36},"end":{"line":439,"column":51}},"225":{"start":{"line":440,"column":6},"end":{"line":440,"column":31}},"226":{"start":{"line":441,"column":6},"end":{"line":441,"column":22}},"227":{"start":{"line":454,"column":0},"end":{"line":474,"column":2}},"228":{"start":{"line":455,"column":2},"end":{"line":455,"column":18}},"229":{"start":{"line":456,"column":2},"end":{"line":456,"column":28}},"230":{"start":{"line":457,"column":2},"end":{"line":473,"column":5}},"231":{"start":{"line":458,"column":4},"end":{"line":458,"column":37}},"232":{"start":{"line":458,"column":13},"end":{"line":458,"column":37}},"233":{"start":{"line":460,"column":4},"end":{"line":460,"column":19}},"234":{"start":{"line":462,"column":4},"end":{"line":469,"column":5}},"235":{"start":{"line":463,"column":6},"end":{"line":463,"column":36}},"236":{"start":{"line":463,"column":15},"end":{"line":463,"column":36}},"237":{"start":{"line":465,"column":6},"end":{"line":468,"column":62}},"238":{"start":{"line":466,"column":8},"end":{"line":466,"column":31}},"239":{"start":{"line":468,"column":8},"end":{"line":468,"column":62}},"240":{"start":{"line":471,"column":4},"end":{"line":471,"column":65}},"241":{"start":{"line":472,"column":4},"end":{"line":472,"column":47}},"242":{"start":{"line":484,"column":0},"end":{"line":504,"column":2}},"243":{"start":{"line":485,"column":2},"end":{"line":503,"column":3}},"244":{"start":{"line":486,"column":4},"end":{"line":488,"column":17}},"245":{"start":{"line":487,"column":6},"end":{"line":487,"column":33}},"246":{"start":{"line":487,"column":16},"end":{"line":487,"column":33}},"247":{"start":{"line":491,"column":4},"end":{"line":491,"column":20}},"248":{"start":{"line":492,"column":4},"end":{"line":502,"column":7}},"249":{"start":{"line":493,"column":6},"end":{"line":494,"column":64}},"250":{"start":{"line":494,"column":8},"end":{"line":494,"column":64}},"251":{"start":{"line":496,"column":6},"end":{"line":501,"column":19}},"252":{"start":{"line":497,"column":8},"end":{"line":500,"column":9}},"253":{"start":{"line":498,"column":10},"end":{"line":498,"column":74}},"254":{"start":{"line":499,"column":10},"end":{"line":499,"column":30}},"255":{"start":{"line":506,"column":0},"end":{"line":539,"column":2}},"256":{"start":{"line":507,"column":2},"end":{"line":507,"column":69}},"257":{"start":{"line":507,"column":17},"end":{"line":507,"column":69}},"258":{"start":{"line":509,"column":2},"end":{"line":509,"column":53}},"259":{"start":{"line":510,"column":2},"end":{"line":516,"column":5}},"260":{"start":{"line":511,"column":4},"end":{"line":514,"column":5}},"261":{"start":{"line":512,"column":6},"end":{"line":512,"column":20}},"262":{"start":{"line":513,"column":6},"end":{"line":513,"column":31}},"263":{"start":{"line":515,"column":4},"end":{"line":515,"column":34}},"264":{"start":{"line":518,"column":2},"end":{"line":518,"column":18}},"265":{"start":{"line":519,"column":2},"end":{"line":538,"column":5}},"266":{"start":{"line":520,"column":4},"end":{"line":520,"column":35}},"267":{"start":{"line":520,"column":13},"end":{"line":520,"column":35}},"268":{"start":{"line":522,"column":4},"end":{"line":535,"column":7}},"269":{"start":{"line":523,"column":6},"end":{"line":523,"column":37}},"270":{"start":{"line":523,"column":15},"end":{"line":523,"column":37}},"271":{"start":{"line":527,"column":6},"end":{"line":534,"column":7}},"272":{"start":{"line":528,"column":8},"end":{"line":528,"column":41}},"273":{"start":{"line":529,"column":8},"end":{"line":529,"column":41}},"274":{"start":{"line":530,"column":8},"end":{"line":530,"column":32}},"275":{"start":{"line":533,"column":8},"end":{"line":533,"column":70}},"276":{"start":{"line":536,"column":4},"end":{"line":536,"column":65}},"277":{"start":{"line":537,"column":4},"end":{"line":537,"column":47}},"278":{"start":{"line":541,"column":0},"end":{"line":557,"column":2}},"279":{"start":{"line":542,"column":2},"end":{"line":542,"column":29}},"280":{"start":{"line":543,"column":2},"end":{"line":543,"column":45}},"281":{"start":{"line":544,"column":2},"end":{"line":556,"column":5}},"282":{"start":{"line":545,"column":4},"end":{"line":545,"column":34}},"283":{"start":{"line":545,"column":13},"end":{"line":545,"column":34}},"284":{"start":{"line":547,"column":4},"end":{"line":547,"column":46}},"285":{"start":{"line":548,"column":4},"end":{"line":549,"column":68}},"286":{"start":{"line":549,"column":6},"end":{"line":549,"column":68}},"287":{"start":{"line":551,"column":4},"end":{"line":551,"column":26}},"288":{"start":{"line":552,"column":4},"end":{"line":552,"column":26}},"289":{"start":{"line":553,"column":4},"end":{"line":553,"column":50}},"290":{"start":{"line":554,"column":4},"end":{"line":554,"column":42}},"291":{"start":{"line":555,"column":4},"end":{"line":555,"column":27}},"292":{"start":{"line":586,"column":0},"end":{"line":616,"column":2}},"293":{"start":{"line":587,"column":2},"end":{"line":590,"column":3}},"294":{"start":{"line":588,"column":4},"end":{"line":588,"column":38}},"295":{"start":{"line":588,"column":15},"end":{"line":588,"column":36}},"296":{"start":{"line":589,"column":4},"end":{"line":589,"column":62}},"297":{"start":{"line":592,"column":2},"end":{"line":615,"column":3}},"298":{"start":{"line":593,"column":4},"end":{"line":593,"column":39}},"299":{"start":{"line":596,"column":4},"end":{"line":596,"column":20}},"300":{"start":{"line":597,"column":4},"end":{"line":614,"column":7}},"301":{"start":{"line":602,"column":6},"end":{"line":613,"column":7}},"302":{"start":{"line":608,"column":8},"end":{"line":608,"column":28}},"303":{"start":{"line":609,"column":8},"end":{"line":609,"column":43}},"304":{"start":{"line":612,"column":8},"end":{"line":612,"column":33}},"305":{"start":{"line":618,"column":0},"end":{"line":624,"column":2}},"306":{"start":{"line":619,"column":2},"end":{"line":619,"column":18}},"307":{"start":{"line":620,"column":2},"end":{"line":623,"column":5}},"308":{"start":{"line":621,"column":4},"end":{"line":621,"column":34}},"309":{"start":{"line":621,"column":13},"end":{"line":621,"column":34}},"310":{"start":{"line":622,"column":4},"end":{"line":622,"column":66}},"311":{"start":{"line":622,"column":43},"end":{"line":622,"column":62}},"312":{"start":{"line":626,"column":0},"end":{"line":632,"column":2}},"313":{"start":{"line":627,"column":2},"end":{"line":627,"column":18}},"314":{"start":{"line":628,"column":2},"end":{"line":629,"column":43}},"315":{"start":{"line":629,"column":4},"end":{"line":629,"column":43}},"316":{"start":{"line":631,"column":2},"end":{"line":631,"column":66}},"317":{"start":{"line":634,"column":0},"end":{"line":641,"column":2}},"318":{"start":{"line":635,"column":2},"end":{"line":636,"column":43}},"319":{"start":{"line":636,"column":4},"end":{"line":636,"column":43}},"320":{"start":{"line":638,"column":2},"end":{"line":638,"column":24}},"321":{"start":{"line":639,"column":2},"end":{"line":639,"column":23}},"322":{"start":{"line":640,"column":2},"end":{"line":640,"column":29}}},"branchMap":{"1":{"line":39,"type":"if","locations":[{"start":{"line":39,"column":4},"end":{"line":39,"column":4}},{"start":{"line":39,"column":4},"end":{"line":39,"column":4}}]},"2":{"line":41,"type":"if","locations":[{"start":{"line":41,"column":6},"end":{"line":41,"column":6}},{"start":{"line":41,"column":6},"end":{"line":41,"column":6}}]},"3":{"line":53,"type":"if","locations":[{"start":{"line":53,"column":4},"end":{"line":53,"column":4}},{"start":{"line":53,"column":4},"end":{"line":53,"column":4}}]},"4":{"line":65,"type":"if","locations":[{"start":{"line":65,"column":4},"end":{"line":65,"column":4}},{"start":{"line":65,"column":4},"end":{"line":65,"column":4}}]},"5":{"line":74,"type":"binary-expr","locations":[{"start":{"line":74,"column":14},"end":{"line":74,"column":23}},{"start":{"line":74,"column":27},"end":{"line":74,"column":35}}]},"6":{"line":86,"type":"if","locations":[{"start":{"line":86,"column":4},"end":{"line":86,"column":4}},{"start":{"line":86,"column":4},"end":{"line":86,"column":4}}]},"7":{"line":114,"type":"if","locations":[{"start":{"line":114,"column":2},"end":{"line":114,"column":2}},{"start":{"line":114,"column":2},"end":{"line":114,"column":2}}]},"8":{"line":120,"type":"binary-expr","locations":[{"start":{"line":120,"column":20},"end":{"line":120,"column":32}},{"start":{"line":120,"column":36},"end":{"line":120,"column":43}}]},"9":{"line":137,"type":"if","locations":[{"start":{"line":137,"column":4},"end":{"line":137,"column":4}},{"start":{"line":137,"column":4},"end":{"line":137,"column":4}}]},"10":{"line":145,"type":"if","locations":[{"start":{"line":145,"column":2},"end":{"line":145,"column":2}},{"start":{"line":145,"column":2},"end":{"line":145,"column":2}}]},"11":{"line":148,"type":"if","locations":[{"start":{"line":148,"column":2},"end":{"line":148,"column":2}},{"start":{"line":148,"column":2},"end":{"line":148,"column":2}}]},"12":{"line":152,"type":"if","locations":[{"start":{"line":152,"column":2},"end":{"line":152,"column":2}},{"start":{"line":152,"column":2},"end":{"line":152,"column":2}}]},"13":{"line":155,"type":"if","locations":[{"start":{"line":155,"column":4},"end":{"line":155,"column":4}},{"start":{"line":155,"column":4},"end":{"line":155,"column":4}}]},"14":{"line":155,"type":"binary-expr","locations":[{"start":{"line":155,"column":8},"end":{"line":155,"column":28}},{"start":{"line":155,"column":32},"end":{"line":155,"column":83}}]},"15":{"line":160,"type":"if","locations":[{"start":{"line":160,"column":4},"end":{"line":160,"column":4}},{"start":{"line":160,"column":4},"end":{"line":160,"column":4}}]},"16":{"line":164,"type":"if","locations":[{"start":{"line":164,"column":2},"end":{"line":164,"column":2}},{"start":{"line":164,"column":2},"end":{"line":164,"column":2}}]},"17":{"line":164,"type":"binary-expr","locations":[{"start":{"line":164,"column":6},"end":{"line":164,"column":24}},{"start":{"line":164,"column":28},"end":{"line":164,"column":60}}]},"18":{"line":179,"type":"if","locations":[{"start":{"line":179,"column":2},"end":{"line":179,"column":2}},{"start":{"line":179,"column":2},"end":{"line":179,"column":2}}]},"19":{"line":179,"type":"binary-expr","locations":[{"start":{"line":179,"column":6},"end":{"line":179,"column":14}},{"start":{"line":179,"column":18},"end":{"line":179,"column":45}}]},"20":{"line":182,"type":"if","locations":[{"start":{"line":182,"column":2},"end":{"line":182,"column":2}},{"start":{"line":182,"column":2},"end":{"line":182,"column":2}}]},"21":{"line":189,"type":"if","locations":[{"start":{"line":189,"column":2},"end":{"line":189,"column":2}},{"start":{"line":189,"column":2},"end":{"line":189,"column":2}}]},"22":{"line":208,"type":"if","locations":[{"start":{"line":208,"column":2},"end":{"line":208,"column":2}},{"start":{"line":208,"column":2},"end":{"line":208,"column":2}}]},"23":{"line":211,"type":"if","locations":[{"start":{"line":211,"column":2},"end":{"line":211,"column":2}},{"start":{"line":211,"column":2},"end":{"line":211,"column":2}}]},"24":{"line":211,"type":"binary-expr","locations":[{"start":{"line":211,"column":6},"end":{"line":211,"column":17}},{"start":{"line":211,"column":21},"end":{"line":211,"column":41}}]},"25":{"line":214,"type":"if","locations":[{"start":{"line":214,"column":7},"end":{"line":214,"column":7}},{"start":{"line":214,"column":7},"end":{"line":214,"column":7}}]},"26":{"line":226,"type":"if","locations":[{"start":{"line":226,"column":4},"end":{"line":226,"column":4}},{"start":{"line":226,"column":4},"end":{"line":226,"column":4}}]},"27":{"line":226,"type":"binary-expr","locations":[{"start":{"line":226,"column":8},"end":{"line":226,"column":26}},{"start":{"line":226,"column":30},"end":{"line":226,"column":64}}]},"28":{"line":252,"type":"if","locations":[{"start":{"line":252,"column":2},"end":{"line":252,"column":2}},{"start":{"line":252,"column":2},"end":{"line":252,"column":2}}]},"29":{"line":259,"type":"if","locations":[{"start":{"line":259,"column":2},"end":{"line":259,"column":2}},{"start":{"line":259,"column":2},"end":{"line":259,"column":2}}]},"30":{"line":260,"type":"binary-expr","locations":[{"start":{"line":260,"column":20},"end":{"line":260,"column":33}},{"start":{"line":260,"column":37},"end":{"line":260,"column":57}}]},"31":{"line":274,"type":"if","locations":[{"start":{"line":274,"column":2},"end":{"line":274,"column":2}},{"start":{"line":274,"column":2},"end":{"line":274,"column":2}}]},"32":{"line":299,"type":"if","locations":[{"start":{"line":299,"column":2},"end":{"line":299,"column":2}},{"start":{"line":299,"column":2},"end":{"line":299,"column":2}}]},"33":{"line":301,"type":"cond-expr","locations":[{"start":{"line":301,"column":28},"end":{"line":301,"column":30}},{"start":{"line":301,"column":33},"end":{"line":301,"column":64}}]},"34":{"line":303,"type":"if","locations":[{"start":{"line":303,"column":8},"end":{"line":303,"column":8}},{"start":{"line":303,"column":8},"end":{"line":303,"column":8}}]},"35":{"line":303,"type":"binary-expr","locations":[{"start":{"line":303,"column":12},"end":{"line":303,"column":16}},{"start":{"line":303,"column":20},"end":{"line":303,"column":36}}]},"36":{"line":331,"type":"if","locations":[{"start":{"line":331,"column":2},"end":{"line":331,"column":2}},{"start":{"line":331,"column":2},"end":{"line":331,"column":2}}]},"37":{"line":333,"type":"if","locations":[{"start":{"line":333,"column":2},"end":{"line":333,"column":2}},{"start":{"line":333,"column":2},"end":{"line":333,"column":2}}]},"38":{"line":334,"type":"if","locations":[{"start":{"line":334,"column":2},"end":{"line":334,"column":2}},{"start":{"line":334,"column":2},"end":{"line":334,"column":2}}]},"39":{"line":338,"type":"if","locations":[{"start":{"line":338,"column":4},"end":{"line":338,"column":4}},{"start":{"line":338,"column":4},"end":{"line":338,"column":4}}]},"40":{"line":338,"type":"binary-expr","locations":[{"start":{"line":338,"column":8},"end":{"line":338,"column":12}},{"start":{"line":338,"column":16},"end":{"line":338,"column":54}}]},"41":{"line":342,"type":"if","locations":[{"start":{"line":342,"column":8},"end":{"line":342,"column":8}},{"start":{"line":342,"column":8},"end":{"line":342,"column":8}}]},"42":{"line":345,"type":"if","locations":[{"start":{"line":345,"column":8},"end":{"line":345,"column":8}},{"start":{"line":345,"column":8},"end":{"line":345,"column":8}}]},"43":{"line":351,"type":"if","locations":[{"start":{"line":351,"column":13},"end":{"line":351,"column":13}},{"start":{"line":351,"column":13},"end":{"line":351,"column":13}}]},"44":{"line":364,"type":"if","locations":[{"start":{"line":364,"column":2},"end":{"line":364,"column":2}},{"start":{"line":364,"column":2},"end":{"line":364,"column":2}}]},"45":{"line":369,"type":"if","locations":[{"start":{"line":369,"column":4},"end":{"line":369,"column":4}},{"start":{"line":369,"column":4},"end":{"line":369,"column":4}}]},"46":{"line":382,"type":"if","locations":[{"start":{"line":382,"column":2},"end":{"line":382,"column":2}},{"start":{"line":382,"column":2},"end":{"line":382,"column":2}}]},"47":{"line":399,"type":"binary-expr","locations":[{"start":{"line":399,"column":44},"end":{"line":399,"column":47}},{"start":{"line":399,"column":51},"end":{"line":399,"column":55}}]},"48":{"line":402,"type":"binary-expr","locations":[{"start":{"line":402,"column":27},"end":{"line":402,"column":31}},{"start":{"line":402,"column":35},"end":{"line":402,"column":37}}]},"49":{"line":419,"type":"if","locations":[{"start":{"line":419,"column":2},"end":{"line":419,"column":2}},{"start":{"line":419,"column":2},"end":{"line":419,"column":2}}]},"50":{"line":420,"type":"binary-expr","locations":[{"start":{"line":420,"column":20},"end":{"line":420,"column":29}},{"start":{"line":420,"column":33},"end":{"line":420,"column":46}}]},"51":{"line":423,"type":"if","locations":[{"start":{"line":423,"column":6},"end":{"line":423,"column":6}},{"start":{"line":423,"column":6},"end":{"line":423,"column":6}}]},"52":{"line":428,"type":"binary-expr","locations":[{"start":{"line":428,"column":20},"end":{"line":428,"column":28}},{"start":{"line":428,"column":32},"end":{"line":428,"column":45}}]},"53":{"line":430,"type":"if","locations":[{"start":{"line":430,"column":6},"end":{"line":430,"column":6}},{"start":{"line":430,"column":6},"end":{"line":430,"column":6}}]},"54":{"line":458,"type":"if","locations":[{"start":{"line":458,"column":4},"end":{"line":458,"column":4}},{"start":{"line":458,"column":4},"end":{"line":458,"column":4}}]},"55":{"line":463,"type":"if","locations":[{"start":{"line":463,"column":6},"end":{"line":463,"column":6}},{"start":{"line":463,"column":6},"end":{"line":463,"column":6}}]},"56":{"line":465,"type":"if","locations":[{"start":{"line":465,"column":6},"end":{"line":465,"column":6}},{"start":{"line":465,"column":6},"end":{"line":465,"column":6}}]},"57":{"line":485,"type":"if","locations":[{"start":{"line":485,"column":2},"end":{"line":485,"column":2}},{"start":{"line":485,"column":2},"end":{"line":485,"column":2}}]},"58":{"line":487,"type":"if","locations":[{"start":{"line":487,"column":6},"end":{"line":487,"column":6}},{"start":{"line":487,"column":6},"end":{"line":487,"column":6}}]},"59":{"line":493,"type":"if","locations":[{"start":{"line":493,"column":6},"end":{"line":493,"column":6}},{"start":{"line":493,"column":6},"end":{"line":493,"column":6}}]},"60":{"line":497,"type":"if","locations":[{"start":{"line":497,"column":8},"end":{"line":497,"column":8}},{"start":{"line":497,"column":8},"end":{"line":497,"column":8}}]},"61":{"line":507,"type":"if","locations":[{"start":{"line":507,"column":2},"end":{"line":507,"column":2}},{"start":{"line":507,"column":2},"end":{"line":507,"column":2}}]},"62":{"line":509,"type":"binary-expr","locations":[{"start":{"line":509,"column":22},"end":{"line":509,"column":34}},{"start":{"line":509,"column":38},"end":{"line":509,"column":51}}]},"63":{"line":511,"type":"if","locations":[{"start":{"line":511,"column":4},"end":{"line":511,"column":4}},{"start":{"line":511,"column":4},"end":{"line":511,"column":4}}]},"64":{"line":520,"type":"if","locations":[{"start":{"line":520,"column":4},"end":{"line":520,"column":4}},{"start":{"line":520,"column":4},"end":{"line":520,"column":4}}]},"65":{"line":523,"type":"if","locations":[{"start":{"line":523,"column":6},"end":{"line":523,"column":6}},{"start":{"line":523,"column":6},"end":{"line":523,"column":6}}]},"66":{"line":527,"type":"if","locations":[{"start":{"line":527,"column":6},"end":{"line":527,"column":6}},{"start":{"line":527,"column":6},"end":{"line":527,"column":6}}]},"67":{"line":543,"type":"binary-expr","locations":[{"start":{"line":543,"column":18},"end":{"line":543,"column":26}},{"start":{"line":543,"column":30},"end":{"line":543,"column":43}}]},"68":{"line":545,"type":"if","locations":[{"start":{"line":545,"column":4},"end":{"line":545,"column":4}},{"start":{"line":545,"column":4},"end":{"line":545,"column":4}}]},"69":{"line":548,"type":"if","locations":[{"start":{"line":548,"column":4},"end":{"line":548,"column":4}},{"start":{"line":548,"column":4},"end":{"line":548,"column":4}}]},"70":{"line":554,"type":"binary-expr","locations":[{"start":{"line":554,"column":22},"end":{"line":554,"column":29}},{"start":{"line":554,"column":33},"end":{"line":554,"column":40}}]},"71":{"line":588,"type":"if","locations":[{"start":{"line":588,"column":4},"end":{"line":588,"column":4}},{"start":{"line":588,"column":4},"end":{"line":588,"column":4}}]},"72":{"line":589,"type":"binary-expr","locations":[{"start":{"line":589,"column":36},"end":{"line":589,"column":48}},{"start":{"line":589,"column":52},"end":{"line":589,"column":59}}]},"73":{"line":592,"type":"if","locations":[{"start":{"line":592,"column":2},"end":{"line":592,"column":2}},{"start":{"line":592,"column":2},"end":{"line":592,"column":2}}]},"74":{"line":602,"type":"if","locations":[{"start":{"line":602,"column":6},"end":{"line":602,"column":6}},{"start":{"line":602,"column":6},"end":{"line":602,"column":6}}]},"75":{"line":602,"type":"binary-expr","locations":[{"start":{"line":602,"column":11},"end":{"line":602,"column":14}},{"start":{"line":602,"column":19},"end":{"line":602,"column":35}},{"start":{"line":602,"column":39},"end":{"line":602,"column":55}},{"start":{"line":603,"column":9},"end":{"line":603,"column":20}},{"start":{"line":603,"column":24},"end":{"line":603,"column":63}}]},"76":{"line":621,"type":"if","locations":[{"start":{"line":621,"column":4},"end":{"line":621,"column":4}},{"start":{"line":621,"column":4},"end":{"line":621,"column":4}}]},"77":{"line":628,"type":"if","locations":[{"start":{"line":628,"column":2},"end":{"line":628,"column":2}},{"start":{"line":628,"column":2},"end":{"line":628,"column":2}}]},"78":{"line":635,"type":"if","locations":[{"start":{"line":635,"column":2},"end":{"line":635,"column":2}},{"start":{"line":635,"column":2},"end":{"line":635,"column":2}}]}}}; +} +var __cov_0Zs5Q6XMeQ4aT8ak62bnkw = __coverage__['/Users/sergi/programming/jsftp/lib/jsftp.js']; +__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['1']++;var Net=require('net');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['2']++;var EventEmitter=require('events').EventEmitter;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['3']++;var Stream=require('stream');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['4']++;var es=require('event-stream');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['5']++;var responseHandler=require('./response');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['6']++;var Utils=require('./utils');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['7']++;var util=require('util');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['8']++;var fs=require('fs');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['9']++;var FTP_PORT=21;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['10']++;var DEBUG_MODE=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['11']++;var TIMEOUT=10*60*1000;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['12']++;var IDLE_TIME=30000;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['13']++;var COMMANDS=['abor','pwd','cdup','feat','noop','quit','pasv','syst','cwd','dele','list','mdtm','mkd','mode','nlst','pass','retr','rmd','rnfr','rnto','site','stat','stor','type','user','pass','xrmd','opts','chmod','size'];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['14']++;var Cmds={};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['15']++;COMMANDS.forEach(function(cmd){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['1']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['16']++;cmd=cmd.toLowerCase();__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['17']++;Cmds[cmd]=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['2']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['18']++;var callback=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['3']++;};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['19']++;var completeCmd=cmd;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['20']++;if(arguments.length){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['1'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['21']++;var args=Array.prototype.slice.call(arguments);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['22']++;if(typeof args[args.length-1]==='function'){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['2'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['23']++;callback=args.pop();}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['2'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['24']++;completeCmd+=' '+args.join(' ');}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['1'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['25']++;this.execute(completeCmd.trim(),callback);};});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['26']++;function once(fn){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['4']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['27']++;var returnValue,called=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['28']++;return function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['5']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['29']++;if(!called){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['3'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['30']++;called=true;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['31']++;returnValue=fn.apply(this,arguments);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['3'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['32']++;return returnValue;};}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['33']++;var Ftp=module.exports=function(cfg){'use strict';__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['6']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['34']++;Object.keys(cfg).forEach(function(opt){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['7']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['35']++;if(!this[opt]){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['4'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['36']++;this[opt]=cfg[opt];}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['4'][1]++;}},this);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['37']++;EventEmitter.call(this);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['38']++;this.useList=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['39']++;this.port=(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['5'][0]++,this.port)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['5'][1]++,FTP_PORT);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['40']++;this.pending=[];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['41']++;this.cmdBuffer_=[];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['42']++;this.responseHandler=responseHandler();__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['43']++;var raw=this.raw={};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['44']++;COMMANDS.forEach(function(cmd){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['8']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['45']++;raw[cmd]=Cmds[cmd].bind(this);},this);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['46']++;this.on('error',function(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['9']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['47']++;if(DEBUG_MODE===true){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['6'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['48']++;console.log(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['6'][1]++;}});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['49']++;this._createSocket(this.port,this.host);};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['50']++;util.inherits(Ftp,EventEmitter);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['51']++;Ftp.prototype._socketOnTimeout=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['10']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['52']++;this.emit('timeout',errString);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['53']++;this.pipeline.pause();__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['54']++;this.destroy();};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['55']++;Ftp.prototype._socketOnError=function(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['11']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['56']++;var errString='Error on ftp socket: '+err;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['57']++;this.emit('error',errString);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['58']++;this.pipeline.pause();};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['59']++;Ftp.prototype._socketOnConnect=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['12']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['60']++;this.emit('connect',this.socket);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['61']++;this.connecting=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['62']++;this.pipeline.resume();};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['63']++;Ftp.prototype._createSocket=function(port,host){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['13']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['64']++;if(this.socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['7'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['65']++;this.socket.destroy();}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['7'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['66']++;this.connecting=true;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['67']++;this.authenticated=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['68']++;var socket=this.socket=Net.createConnection(port,host);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['69']++;socket.setTimeout((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['8'][0]++,this.timeout)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['8'][1]++,TIMEOUT),this._socketOnTimeout.bind(this));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['70']++;socket.on('error',this._socketOnError.bind(this));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['71']++;socket.on('connect',this._socketOnConnect.bind(this));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['72']++;this._createStreams(this.socket);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['73']++;this.pipeline.pause();};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['74']++;Ftp.prototype._createStreams=function(socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['14']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['75']++;this.pipeline=es.pipeline(socket,es.split(),es.mapSync(this.responseHandler));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['76']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['77']++;this.pipeline.on('data',function(data){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['15']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['78']++;if(DEBUG_MODE===true){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['9'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['79']++;console.log(' ',data.code,data.text);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['9'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['80']++;self.parseResponse.call(self,data);});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['81']++;Ftp.prototype.parseResponse=function(data){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['16']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['82']++;if(!this.cmdBuffer_.length){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['10'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['83']++;return;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['10'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['84']++;if([220].indexOf(data.code)>-1){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['11'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['85']++;return;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['11'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['86']++;var nextCmd=this.cmdBuffer_[0][1];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['87']++;if(Utils.isMark(data.code)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['12'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['88']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['14'][0]++,!nextCmd.expectsMark)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['14'][1]++,nextCmd.expectsMark.marks.indexOf(data.code)===-1)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['13'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['89']++;return;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['13'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['90']++;if(nextCmd.expectsMark.ignore){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['15'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['91']++;this.ignoreCmdCode=nextCmd.expectsMark.ignore;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['15'][1]++;}}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['12'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['92']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['17'][0]++,this.ignoreCmdCode)&&(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['17'][1]++,this.ignoreCmdCode===data.code)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['16'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['93']++;this.ignoreCmdCode=null;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['94']++;return;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['16'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['95']++;this.parse([data,this.cmdBuffer_.shift()]);};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['96']++;Ftp.prototype.send=function(command){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['17']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['97']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['19'][0]++,!command)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['19'][1]++,typeof command!=='string')){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['18'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['98']++;return;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['18'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['99']++;if(DEBUG_MODE===true){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['20'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['100']++;console.log('\nSEND',command,this.cmdBuffer_,'\n');}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['20'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['101']++;this.pipeline.write(command+'\r\n');};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['102']++;Ftp.prototype.nextCmd=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['18']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['103']++;if(this.cmdBuffer_[0]){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['21'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['104']++;this.send(this.cmdBuffer_[0][0]);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['21'][1]++;}};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['105']++;Ftp.prototype.execute=function(action,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['19']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['106']++;if(!callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['22'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['107']++;callback=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['20']++;};}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['22'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['108']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['109']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['24'][0]++,this.socket)&&(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['24'][1]++,this.socket.writable)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['23'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['110']++;execute();}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['23'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['111']++;if(!this.connecting){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['25'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['112']++;this.authenticated=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['113']++;this.once('connect',execute);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['114']++;this._createSocket(this.port,this.host);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['25'][1]++;}}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['115']++;function execute(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['21']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['116']++;function executeCmd(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['22']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['117']++;self.cmdBuffer_.push([action,callback]);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['118']++;self.nextCmd();}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['119']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['27'][0]++,self.authenticated)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['27'][1]++,/feat|syst|user|pass/.test(action))){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['26'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['120']++;executeCmd();}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['26'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['121']++;self.getFeatures(function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['23']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['122']++;self.auth(self.user,self.pass,executeCmd);});}}};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['123']++;Ftp.prototype.parse=function(action){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['24']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['124']++;var response=action[0];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['125']++;var command=action[1];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['126']++;var cb=command[1];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['127']++;if(!response){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['28'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['128']++;cb(new Error('FTP response not defined'));}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['28'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['129']++;var err=null;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['130']++;if(response.code>399){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['29'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['131']++;err=new Error((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['30'][0]++,response.text)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['30'][1]++,'Unknown FTP error.'));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['132']++;err.code=response.code;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['29'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['133']++;cb(err,response);};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['134']++;Ftp.prototype.hasFeat=function(feature){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['25']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['135']++;if(feature){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['31'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['136']++;return this.features.indexOf(feature.toLowerCase())>-1;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['31'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['137']++;return false;};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['138']++;Ftp.prototype._parseFeats=function(features){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['26']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['139']++;return features.split(/\r\n|\n/).slice(1,-1).map(function(feat){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['27']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['140']++;return/^\s*(\w*)\s*/.exec(feat)[1].trim().toLowerCase();});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['141']++;Ftp.prototype.getFeatures=function(callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['28']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['142']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['143']++;if(!this.features){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['32'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['144']++;this.raw.feat(function(err,response){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['29']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['145']++;self.features=err?(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['33'][0]++,[]):(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['33'][1]++,self._parseFeats(response.text));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['146']++;self.raw.syst(function(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['30']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['147']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['35'][0]++,!err)&&(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['35'][1]++,res.code===215)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['34'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['148']++;self.system=res.text.toLowerCase();}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['34'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['149']++;callback(null,self.features);});});}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['32'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['150']++;callback(null,self.features);}};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['151']++;Ftp.prototype.auth=function(user,pass,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['31']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['152']++;this.pending.push(callback);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['153']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['154']++;function notifyAll(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['32']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['155']++;var cb;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['156']++;while(cb=self.pending.shift()){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['157']++;cb(err,res);}}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['158']++;if(this.authenticating){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['36'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['159']++;return;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['36'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['160']++;if(!user){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['37'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['161']++;user='anonymous';}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['37'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['162']++;if(!pass){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['38'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['163']++;pass='@anonymous';}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['38'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['164']++;this.authenticating=true;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['165']++;self.raw.user(user,function(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['33']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['166']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['40'][0]++,!err)&&(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['40'][1]++,[230,331,332].indexOf(res.code)>-1)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['39'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['167']++;self.raw.pass(pass,function(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['34']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['168']++;self.authenticating=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['169']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['41'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['170']++;notifyAll(new Error('Login not accepted'));}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['41'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['171']++;if([230,202].indexOf(res.code)>-1){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['42'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['172']++;self.authenticated=true;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['173']++;self.user=user;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['174']++;self.pass=pass;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['175']++;self.raw.type('I',function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['35']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['176']++;notifyAll(null,res);});}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['42'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['177']++;if(res.code===332){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['43'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['178']++;self.raw.acct('');}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['43'][1]++;}}});}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['39'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['179']++;self.authenticating=false;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['180']++;notifyAll(new Error('Login not accepted'));}});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['181']++;Ftp.prototype.setType=function(type,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['36']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['182']++;if(this.type===type){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['44'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['183']++;callback(null);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['44'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['184']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['185']++;this.raw.type(type,function(err,data){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['37']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['186']++;if(!err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['45'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['187']++;self.type=type;}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['45'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['188']++;callback(err,data);});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['189']++;Ftp.prototype.list=function(path,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['38']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['190']++;if(arguments.length===1){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['46'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['191']++;callback=arguments[0];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['192']++;path='';}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['46'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['193']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['194']++;var cb=function(err,listing){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['39']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['195']++;self.setType('I',once(function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['40']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['196']++;callback(err,listing);}));};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['197']++;cb.expectsMark={marks:[125,150],ignore:226};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['198']++;var listing='';__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['199']++;this.setType('A',function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['41']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['200']++;self.getPasvSocket(function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['42']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['201']++;socket.on('data',function(data){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['43']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['202']++;listing+=data;});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['203']++;socket.on('close',function(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['44']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['204']++;cb((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['47'][0]++,err)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['47'][1]++,null),listing);});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['205']++;socket.on('error',cb);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['206']++;self.send('list '+((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['48'][0]++,path)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['48'][1]++,'')));});});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['207']++;Ftp.prototype.get=function(remotePath,localPath,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['45']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['208']++;if(arguments.length===2){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['49'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['209']++;callback=once((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['50'][0]++,localPath)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['50'][1]++,function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['46']++;}));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['210']++;this.getGetSocket(remotePath,function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['47']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['211']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['51'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['212']++;return callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['51'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['213']++;callback(err,socket);});}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['49'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['214']++;callback=once((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['52'][0]++,callback)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['52'][1]++,function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['48']++;}));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['215']++;this.getGetSocket(remotePath,function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['49']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['216']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['53'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['217']++;onError(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['53'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['218']++;function onError(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['50']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['219']++;console.error('There was an error transferring the file.');__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['220']++;callback(err);}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['221']++;var writeStream=fs.createWriteStream(localPath);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['222']++;writeStream.on('error',onError);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['223']++;socket.on('end',function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['51']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['224']++;callback(null);});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['225']++;socket.pipe(writeStream);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['226']++;socket.resume();});}};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['227']++;Ftp.prototype.getGetSocket=function(path,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['52']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['228']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['229']++;callback=once(callback);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['230']++;this.getPasvSocket(function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['53']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['231']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['54'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['232']++;return cmdCallback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['54'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['233']++;socket.pause();__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['234']++;function cmdCallback(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['54']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['235']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['55'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['236']++;return callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['55'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['237']++;if(res.code===150){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['56'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['238']++;callback(null,socket);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['56'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['239']++;callback(new Error('Unexpected command '+res.text));}}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['240']++;cmdCallback.expectsMark={marks:[125,150],ignore:226};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['241']++;self.execute('retr '+path,cmdCallback);});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['242']++;Ftp.prototype.put=function(from,to,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['55']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['243']++;if(from instanceof Buffer){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['57'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['244']++;this.getPutSocket(to,function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['56']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['245']++;if(!err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['58'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['246']++;socket.end(from);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['58'][1]++;}},callback);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['57'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['247']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['248']++;fs.exists(from,function(exists){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['57']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['249']++;if(!exists){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['59'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['250']++;return callback(new Error('Local file doesn\'t exist.'));}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['59'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['251']++;self.getPutSocket(to,function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['58']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['252']++;if(!err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['60'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['253']++;var origin=fs.createReadStream(from,{bufferSize:4*1024});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['254']++;origin.pipe(socket);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['60'][1]++;}},callback);});}};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['255']++;Ftp.prototype.getPutSocket=function(path,callback,doneCallback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['59']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['256']++;if(!callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['61'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['257']++;throw new Error('A callback argument is required.');}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['61'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['258']++;doneCallback=once((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['62'][0]++,doneCallback)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['62'][1]++,function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['60']++;}));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['259']++;var _callback=once(function(err,_socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['61']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['260']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['63'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['261']++;callback(err);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['262']++;return doneCallback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['63'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['263']++;return callback(err,_socket);});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['264']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['265']++;this.getPasvSocket(function(err,socket){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['62']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['266']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['64'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['267']++;return _callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['64'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['268']++;var putCallback=once(function putCallback(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['63']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['269']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['65'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['270']++;return _callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['65'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['271']++;if(res.code===150){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['66'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['272']++;socket.on('close',doneCallback);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['273']++;socket.on('error',doneCallback);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['274']++;_callback(null,socket);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['66'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['275']++;return _callback(new Error('Unexpected command '+res.text));}});__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['276']++;putCallback.expectsMark={marks:[125,150],ignore:226};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['277']++;self.execute('stor '+path,putCallback);});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['278']++;Ftp.prototype.getPasvSocket=function(callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['64']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['279']++;var timeout=this.timeout;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['280']++;callback=once((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['67'][0]++,callback)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['67'][1]++,function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['65']++;}));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['281']++;this.execute('pasv',function(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['66']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['282']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['68'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['283']++;return callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['68'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['284']++;var pasvRes=Utils.getPasvPort(res.text);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['285']++;if(pasvRes===false){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['69'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['286']++;return callback(new Error('PASV: Bad host/port combination'));}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['69'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['287']++;var host=pasvRes[0];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['288']++;var port=pasvRes[1];__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['289']++;var socket=Net.createConnection(port,host);__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['290']++;socket.setTimeout((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['70'][0]++,timeout)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['70'][1]++,TIMEOUT));__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['291']++;callback(null,socket);});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['292']++;Ftp.prototype.ls=function(filePath,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['67']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['293']++;function entriesToList(err,entries){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['68']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['294']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['71'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['295']++;return callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['71'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['296']++;callback(null,Utils.parseEntry((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['72'][0]++,entries.text)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['72'][1]++,entries)));}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['297']++;if(this.useList){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['73'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['298']++;this.list(filePath,entriesToList);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['73'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['299']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['300']++;this.raw.stat(filePath,function(err,data){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['69']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['301']++;if((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['75'][0]++,err)&&((__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['75'][1]++,err.code===502)||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['75'][2]++,err.code===500))||(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['75'][3]++,self.system)&&(__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['75'][4]++,self.system.indexOf('hummingbird')>-1)){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['74'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['302']++;self.useList=true;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['303']++;self.list(filePath,entriesToList);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['74'][1]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['304']++;entriesToList(err,data);}});}};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['305']++;Ftp.prototype.rename=function(from,to,callback){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['70']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['306']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['307']++;this.raw.rnfr(from,function(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['71']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['308']++;if(err){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['76'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['309']++;return callback(err);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['76'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['310']++;self.raw.rnto(to,function(err,res){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['72']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['311']++;callback(err,res);});});};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['312']++;Ftp.prototype.keepAlive=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['73']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['313']++;var self=this;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['314']++;if(this._keepAliveInterval){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['77'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['315']++;clearInterval(this._keepAliveInterval);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['77'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['316']++;this._keepAliveInterval=setInterval(self.raw.noop,IDLE_TIME);};__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['317']++;Ftp.prototype.destroy=function(){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.f['74']++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['318']++;if(this._keepAliveInterval){__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['78'][0]++;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['319']++;clearInterval(this._keepAliveInterval);}else{__cov_0Zs5Q6XMeQ4aT8ak62bnkw.b['78'][1]++;}__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['320']++;this.socket.destroy();__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['321']++;this.features=null;__cov_0Zs5Q6XMeQ4aT8ak62bnkw.s['322']++;this.authenticated=false;}; diff --git a/node_modules/jsftp/lib/lib/response.js b/node_modules/jsftp/lib/lib/response.js new file mode 100644 index 0000000..e660f48 --- /dev/null +++ b/node_modules/jsftp/lib/lib/response.js @@ -0,0 +1,6 @@ +if (typeof __coverage__ === 'undefined') { __coverage__ = {}; } +if (!__coverage__['/Users/sergi/programming/jsftp/lib/response.js']) { + __coverage__['/Users/sergi/programming/jsftp/lib/response.js'] = {"path":"/Users/sergi/programming/jsftp/lib/response.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0]},"f":{"1":0,"2":0},"fnMap":{"1":{"name":"responseHandler","line":12,"loc":{"start":{"line":12,"column":0},"end":{"line":12,"column":27}}},"2":{"name":"(anonymous_2)","line":16,"loc":{"start":{"line":16,"column":9},"end":{"line":16,"column":24}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}},"2":{"start":{"line":3,"column":0},"end":{"line":3,"column":31}},"3":{"start":{"line":4,"column":0},"end":{"line":4,"column":28}},"4":{"start":{"line":12,"column":0},"end":{"line":60,"column":1}},"5":{"start":{"line":13,"column":2},"end":{"line":13,"column":18}},"6":{"start":{"line":14,"column":2},"end":{"line":14,"column":22}},"7":{"start":{"line":16,"column":2},"end":{"line":60,"column":0}},"8":{"start":{"line":17,"column":4},"end":{"line":17,"column":38}},"9":{"start":{"line":18,"column":4},"end":{"line":18,"column":17}},"10":{"start":{"line":20,"column":4},"end":{"line":20,"column":13}},"11":{"start":{"line":21,"column":4},"end":{"line":58,"column":5}},"12":{"start":{"line":22,"column":6},"end":{"line":22,"column":40}},"13":{"start":{"line":24,"column":6},"end":{"line":32,"column":7}},"14":{"start":{"line":25,"column":8},"end":{"line":25,"column":26}},"15":{"start":{"line":27,"column":8},"end":{"line":31,"column":9}},"16":{"start":{"line":28,"column":10},"end":{"line":28,"column":35}},"17":{"start":{"line":29,"column":10},"end":{"line":29,"column":22}},"18":{"start":{"line":30,"column":10},"end":{"line":30,"column":26}},"19":{"start":{"line":34,"column":6},"end":{"line":34,"column":40}},"20":{"start":{"line":37,"column":6},"end":{"line":56,"column":7}},"21":{"start":{"line":38,"column":8},"end":{"line":38,"column":48}},"22":{"start":{"line":40,"column":11},"end":{"line":56,"column":7}},"23":{"start":{"line":57,"column":6},"end":{"line":57,"column":35}},"24":{"start":{"line":62,"column":0},"end":{"line":62,"column":33}}},"branchMap":{"1":{"line":21,"type":"if","locations":[{"start":{"line":21,"column":4},"end":{"line":21,"column":4}},{"start":{"line":21,"column":4},"end":{"line":21,"column":4}}]},"2":{"line":24,"type":"if","locations":[{"start":{"line":24,"column":6},"end":{"line":24,"column":6}},{"start":{"line":24,"column":6},"end":{"line":24,"column":6}}]},"3":{"line":27,"type":"if","locations":[{"start":{"line":27,"column":8},"end":{"line":27,"column":8}},{"start":{"line":27,"column":8},"end":{"line":27,"column":8}}]},"4":{"line":37,"type":"if","locations":[{"start":{"line":37,"column":6},"end":{"line":37,"column":6}},{"start":{"line":37,"column":6},"end":{"line":37,"column":6}}]},"5":{"line":37,"type":"binary-expr","locations":[{"start":{"line":37,"column":10},"end":{"line":37,"column":24}},{"start":{"line":37,"column":29},"end":{"line":37,"column":59}}]},"6":{"line":40,"type":"if","locations":[{"start":{"line":40,"column":11},"end":{"line":40,"column":11}},{"start":{"line":40,"column":11},"end":{"line":40,"column":11}}]}}}; +} +var __cov_Cbo7IaZZ3Ln4aLUw25DLOQ = __coverage__['/Users/sergi/programming/jsftp/lib/response.js']; +__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['1']++;'use strict';__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['2']++;var RE_RES=/^(\d\d\d)\s(.*)/;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['3']++;var RE_MULTI=/^(\d\d\d)-/;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['4']++;function responseHandler(){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.f['1']++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['5']++;var buffer=[];__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['6']++;var currentCode=0;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['7']++;return function(line){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.f['2']++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['8']++;var simpleRes=RE_RES.exec(line);__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['9']++;var multiRes;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['10']++;var code;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['11']++;if(simpleRes){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['1'][0]++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['12']++;code=parseInt(simpleRes[1],10);__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['13']++;if(buffer.length){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['2'][0]++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['14']++;buffer.push(line);__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['15']++;if(currentCode===code){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['3'][0]++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['16']++;line=buffer.join('\n');__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['17']++;buffer=[];__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['18']++;currentCode=0;}else{__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['3'][1]++;}}else{__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['2'][1]++;}__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['19']++;return{code:code,text:line};}else{__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['1'][1]++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['20']++;if((__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['5'][0]++,!buffer.length)&&(__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['5'][1]++,multiRes=RE_MULTI.exec(line))){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['4'][0]++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['21']++;currentCode=parseInt(multiRes[1],10);}else{__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['4'][1]++;__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['22']++;if(buffer.length){__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['6'][0]++;}else{__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.b['6'][1]++;}}__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['23']++;buffer.push(line.toString());}};}__cov_Cbo7IaZZ3Ln4aLUw25DLOQ.s['24']++;module.exports=responseHandler; diff --git a/node_modules/jsftp/lib/lib/utils.js b/node_modules/jsftp/lib/lib/utils.js new file mode 100644 index 0000000..35ed10b --- /dev/null +++ b/node_modules/jsftp/lib/lib/utils.js @@ -0,0 +1,6 @@ +if (typeof __coverage__ === 'undefined') { __coverage__ = {}; } +if (!__coverage__['/Users/sergi/programming/jsftp/lib/utils.js']) { + __coverage__['/Users/sergi/programming/jsftp/lib/utils.js'] = {"path":"/Users/sergi/programming/jsftp/lib/utils.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0]},"f":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0},"fnMap":{"1":{"name":"(anonymous_1)","line":10,"loc":{"start":{"line":10,"column":10},"end":{"line":10,"column":25}}},"2":{"name":"(anonymous_2)","line":21,"loc":{"start":{"line":21,"column":14},"end":{"line":21,"column":32}}},"3":{"name":"(anonymous_3)","line":26,"loc":{"start":{"line":26,"column":35},"end":{"line":26,"column":57}}},"4":{"name":"_next","line":27,"loc":{"start":{"line":27,"column":6},"end":{"line":27,"column":23}}},"5":{"name":"(anonymous_5)","line":74,"loc":{"start":{"line":74,"column":15},"end":{"line":74,"column":30}}},"6":{"name":"(anonymous_6)","line":91,"loc":{"start":{"line":91,"column":12},"end":{"line":91,"column":26}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":1,"column":38}},"2":{"start":{"line":2,"column":0},"end":{"line":2,"column":29}},"3":{"start":{"line":4,"column":0},"end":{"line":4,"column":31}},"4":{"start":{"line":5,"column":0},"end":{"line":5,"column":28}},"5":{"start":{"line":6,"column":0},"end":{"line":6,"column":41}},"6":{"start":{"line":8,"column":0},"end":{"line":103,"column":0}},"7":{"start":{"line":11,"column":4},"end":{"line":11,"column":30}},"8":{"start":{"line":12,"column":4},"end":{"line":12,"column":36}},"9":{"start":{"line":22,"column":4},"end":{"line":22,"column":23}},"10":{"start":{"line":23,"column":4},"end":{"line":23,"column":14}},"11":{"start":{"line":24,"column":4},"end":{"line":24,"column":20}},"12":{"start":{"line":25,"column":4},"end":{"line":25,"column":48}},"13":{"start":{"line":26,"column":4},"end":{"line":69,"column":7}},"14":{"start":{"line":27,"column":6},"end":{"line":30,"column":7}},"15":{"start":{"line":28,"column":8},"end":{"line":28,"column":15}},"16":{"start":{"line":29,"column":8},"end":{"line":29,"column":15}},"17":{"start":{"line":34,"column":6},"end":{"line":35,"column":32}},"18":{"start":{"line":35,"column":8},"end":{"line":35,"column":32}},"19":{"start":{"line":37,"column":6},"end":{"line":37,"column":27}},"20":{"start":{"line":42,"column":6},"end":{"line":45,"column":7}},"21":{"start":{"line":44,"column":8},"end":{"line":44,"column":23}},"22":{"start":{"line":47,"column":6},"end":{"line":47,"column":45}},"23":{"start":{"line":48,"column":6},"end":{"line":67,"column":7}},"24":{"start":{"line":49,"column":8},"end":{"line":55,"column":9}},"25":{"start":{"line":50,"column":10},"end":{"line":50,"column":61}},"26":{"start":{"line":51,"column":10},"end":{"line":54,"column":11}},"27":{"start":{"line":52,"column":12},"end":{"line":52,"column":62}},"28":{"start":{"line":53,"column":12},"end":{"line":53,"column":27}},"29":{"start":{"line":57,"column":8},"end":{"line":62,"column":9}},"30":{"start":{"line":58,"column":10},"end":{"line":58,"column":61}},"31":{"start":{"line":59,"column":10},"end":{"line":61,"column":11}},"32":{"start":{"line":60,"column":12},"end":{"line":60,"column":42}},"33":{"start":{"line":65,"column":8},"end":{"line":67,"column":6}},"34":{"start":{"line":66,"column":10},"end":{"line":67,"column":6}},"35":{"start":{"line":68,"column":6},"end":{"line":68,"column":14}},"36":{"start":{"line":71,"column":4},"end":{"line":71,"column":18}},"37":{"start":{"line":75,"column":4},"end":{"line":75,"column":68}},"38":{"start":{"line":76,"column":4},"end":{"line":76,"column":35}},"39":{"start":{"line":77,"column":4},"end":{"line":77,"column":29}},"40":{"start":{"line":77,"column":16},"end":{"line":77,"column":29}},"41":{"start":{"line":80,"column":4},"end":{"line":81,"column":77}},"42":{"start":{"line":92,"column":4},"end":{"line":92,"column":24}},"43":{"start":{"line":92,"column":14},"end":{"line":92,"column":24}},"44":{"start":{"line":94,"column":4},"end":{"line":94,"column":31}},"45":{"start":{"line":95,"column":4},"end":{"line":96,"column":51}},"46":{"start":{"line":96,"column":6},"end":{"line":96,"column":51}},"47":{"start":{"line":98,"column":4},"end":{"line":98,"column":15}}},"branchMap":{"1":{"line":12,"type":"binary-expr","locations":[{"start":{"line":12,"column":11},"end":{"line":12,"column":21}},{"start":{"line":12,"column":25},"end":{"line":12,"column":35}}]},"2":{"line":34,"type":"if","locations":[{"start":{"line":34,"column":6},"end":{"line":34,"column":6}},{"start":{"line":34,"column":6},"end":{"line":34,"column":6}}]},"3":{"line":42,"type":"if","locations":[{"start":{"line":42,"column":6},"end":{"line":42,"column":6}},{"start":{"line":42,"column":6},"end":{"line":42,"column":6}}]},"4":{"line":42,"type":"binary-expr","locations":[{"start":{"line":42,"column":10},"end":{"line":42,"column":40}},{"start":{"line":43,"column":8},"end":{"line":43,"column":26}},{"start":{"line":43,"column":30},"end":{"line":43,"column":50}}]},"5":{"line":48,"type":"if","locations":[{"start":{"line":48,"column":6},"end":{"line":48,"column":6}},{"start":{"line":48,"column":6},"end":{"line":48,"column":6}}]},"6":{"line":49,"type":"if","locations":[{"start":{"line":49,"column":8},"end":{"line":49,"column":8}},{"start":{"line":49,"column":8},"end":{"line":49,"column":8}}]},"7":{"line":51,"type":"if","locations":[{"start":{"line":51,"column":10},"end":{"line":51,"column":10}},{"start":{"line":51,"column":10},"end":{"line":51,"column":10}}]},"8":{"line":57,"type":"if","locations":[{"start":{"line":57,"column":8},"end":{"line":57,"column":8}},{"start":{"line":57,"column":8},"end":{"line":57,"column":8}}]},"9":{"line":57,"type":"binary-expr","locations":[{"start":{"line":57,"column":12},"end":{"line":57,"column":31}},{"start":{"line":57,"column":35},"end":{"line":57,"column":52}}]},"10":{"line":59,"type":"if","locations":[{"start":{"line":59,"column":10},"end":{"line":59,"column":10}},{"start":{"line":59,"column":10},"end":{"line":59,"column":10}}]},"11":{"line":65,"type":"if","locations":[{"start":{"line":65,"column":8},"end":{"line":65,"column":8}},{"start":{"line":65,"column":8},"end":{"line":65,"column":8}}]},"12":{"line":77,"type":"if","locations":[{"start":{"line":77,"column":4},"end":{"line":77,"column":4}},{"start":{"line":77,"column":4},"end":{"line":77,"column":4}}]},"13":{"line":92,"type":"if","locations":[{"start":{"line":92,"column":4},"end":{"line":92,"column":4}},{"start":{"line":92,"column":4},"end":{"line":92,"column":4}}]},"14":{"line":95,"type":"if","locations":[{"start":{"line":95,"column":4},"end":{"line":95,"column":4}},{"start":{"line":95,"column":4},"end":{"line":95,"column":4}}]}}}; +} +var __cov_YktIlyn0AIPXiguqHg3XXw = __coverage__['/Users/sergi/programming/jsftp/lib/utils.js']; +__cov_YktIlyn0AIPXiguqHg3XXw.s['1']++;var Parser=require('parse-listing');__cov_YktIlyn0AIPXiguqHg3XXw.s['2']++;var async=require('async');__cov_YktIlyn0AIPXiguqHg3XXw.s['3']++;var RE_RES=/^(\d\d\d)\s(.*)/;__cov_YktIlyn0AIPXiguqHg3XXw.s['4']++;var RE_MULTI=/^(\d\d\d)-/;__cov_YktIlyn0AIPXiguqHg3XXw.s['5']++;var RE_SERVER_RESPONSE=/^(\d\d\d)(.*)/;__cov_YktIlyn0AIPXiguqHg3XXw.s['6']++;var Utils=module.exports={isMark:function(code){__cov_YktIlyn0AIPXiguqHg3XXw.f['1']++;__cov_YktIlyn0AIPXiguqHg3XXw.s['7']++;code=parseInt(code,10);__cov_YktIlyn0AIPXiguqHg3XXw.s['8']++;return(__cov_YktIlyn0AIPXiguqHg3XXw.b['1'][0]++,code>100)&&(__cov_YktIlyn0AIPXiguqHg3XXw.b['1'][1]++,code<200);},parseEntry:function(listing){__cov_YktIlyn0AIPXiguqHg3XXw.f['2']++;__cov_YktIlyn0AIPXiguqHg3XXw.s['9']++;var t,parsedEntry;__cov_YktIlyn0AIPXiguqHg3XXw.s['10']++;var i=0;__cov_YktIlyn0AIPXiguqHg3XXw.s['11']++;var parsed=[];__cov_YktIlyn0AIPXiguqHg3XXw.s['12']++;var splitEntries=listing.split(/\r\n|\n/);__cov_YktIlyn0AIPXiguqHg3XXw.s['13']++;async.eachSeries(splitEntries,function(entry,next){__cov_YktIlyn0AIPXiguqHg3XXw.f['3']++;__cov_YktIlyn0AIPXiguqHg3XXw.s['14']++;function _next(){__cov_YktIlyn0AIPXiguqHg3XXw.f['4']++;__cov_YktIlyn0AIPXiguqHg3XXw.s['15']++;i+=1;__cov_YktIlyn0AIPXiguqHg3XXw.s['16']++;next();}__cov_YktIlyn0AIPXiguqHg3XXw.s['17']++;if(RE_MULTI.test(entry)){__cov_YktIlyn0AIPXiguqHg3XXw.b['2'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['18']++;entry=entry.substr(3);}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['2'][1]++;}__cov_YktIlyn0AIPXiguqHg3XXw.s['19']++;entry=entry.trim();__cov_YktIlyn0AIPXiguqHg3XXw.s['20']++;if((__cov_YktIlyn0AIPXiguqHg3XXw.b['4'][0]++,RE_SERVER_RESPONSE.test(entry))||(__cov_YktIlyn0AIPXiguqHg3XXw.b['4'][1]++,RE_RES.test(entry))||(__cov_YktIlyn0AIPXiguqHg3XXw.b['4'][2]++,RE_MULTI.test(entry))){__cov_YktIlyn0AIPXiguqHg3XXw.b['3'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['21']++;return _next();}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['3'][1]++;}__cov_YktIlyn0AIPXiguqHg3XXw.s['22']++;parsedEntry=Parser.parseEntry(entry);__cov_YktIlyn0AIPXiguqHg3XXw.s['23']++;if(parsedEntry===null){__cov_YktIlyn0AIPXiguqHg3XXw.b['5'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['24']++;if(splitEntries[i+1]){__cov_YktIlyn0AIPXiguqHg3XXw.b['6'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['25']++;t=Parser.parseEntry(entry+splitEntries[i+1]);__cov_YktIlyn0AIPXiguqHg3XXw.s['26']++;if(t!==null){__cov_YktIlyn0AIPXiguqHg3XXw.b['7'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['27']++;splitEntries[i+1]=entry+splitEntries[i+1];__cov_YktIlyn0AIPXiguqHg3XXw.s['28']++;return _next();}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['7'][1]++;}}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['6'][1]++;}__cov_YktIlyn0AIPXiguqHg3XXw.s['29']++;if((__cov_YktIlyn0AIPXiguqHg3XXw.b['9'][0]++,splitEntries[i-1])&&(__cov_YktIlyn0AIPXiguqHg3XXw.b['9'][1]++,parsed.length>0)){__cov_YktIlyn0AIPXiguqHg3XXw.b['8'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['30']++;t=Parser.parseEntry(splitEntries[i-1]+entry);__cov_YktIlyn0AIPXiguqHg3XXw.s['31']++;if(t!==null){__cov_YktIlyn0AIPXiguqHg3XXw.b['10'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['32']++;parsed[parsed.length-1]=t;}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['10'][1]++;}}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['8'][1]++;}}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['5'][1]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['33']++;if(parsedEntry){__cov_YktIlyn0AIPXiguqHg3XXw.b['11'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['34']++;parsed.push(parsedEntry);}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['11'][1]++;}}__cov_YktIlyn0AIPXiguqHg3XXw.s['35']++;_next();});__cov_YktIlyn0AIPXiguqHg3XXw.s['36']++;return parsed;},getPasvPort:function(text){__cov_YktIlyn0AIPXiguqHg3XXw.f['5']++;__cov_YktIlyn0AIPXiguqHg3XXw.s['37']++;var RE_PASV=/([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/;__cov_YktIlyn0AIPXiguqHg3XXw.s['38']++;var match=RE_PASV.exec(text);__cov_YktIlyn0AIPXiguqHg3XXw.s['39']++;if(!match){__cov_YktIlyn0AIPXiguqHg3XXw.b['12'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['40']++;return false;}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['12'][1]++;}__cov_YktIlyn0AIPXiguqHg3XXw.s['41']++;return[match[1].replace(/,/g,'.'),(parseInt(match[2],10)&255)*256+(parseInt(match[3],10)&255)];},sanitize:function(cmd){__cov_YktIlyn0AIPXiguqHg3XXw.f['6']++;__cov_YktIlyn0AIPXiguqHg3XXw.s['42']++;if(!cmd){__cov_YktIlyn0AIPXiguqHg3XXw.b['13'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['43']++;return'';}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['13'][1]++;}__cov_YktIlyn0AIPXiguqHg3XXw.s['44']++;var _cmd=cmd.slice(0,5);__cov_YktIlyn0AIPXiguqHg3XXw.s['45']++;if(_cmd==='pass '){__cov_YktIlyn0AIPXiguqHg3XXw.b['14'][0]++;__cov_YktIlyn0AIPXiguqHg3XXw.s['46']++;cmd=_cmd+Array(cmd.length-5).join('*');}else{__cov_YktIlyn0AIPXiguqHg3XXw.b['14'][1]++;}__cov_YktIlyn0AIPXiguqHg3XXw.s['47']++;return cmd;}}; diff --git a/node_modules/jsftp/lib/response.js b/node_modules/jsftp/lib/response.js new file mode 100644 index 0000000..7a5664d --- /dev/null +++ b/node_modules/jsftp/lib/response.js @@ -0,0 +1,45 @@ +"use strict"; + +var RE_RES = /^(\d\d\d)\s(.*)/; +var RE_MULTI = /^(\d\d\d)-/; + +/** + * Receives a stream of responses from the server and filters + * them before pushing them back into the stream. The filtering is + * necessary to detect multiline responses, in which several responses from + * the server belong to a single command. + */ +function responseHandler() { + var buffer = []; + var currentCode = 0; + + return function(line) { + var simpleRes = RE_RES.exec(line); + var multiRes; + + var code; + if (simpleRes) { + code = parseInt(simpleRes[1], 10); + + if (buffer.length) { + buffer.push(line); + + if (currentCode === code) { + line = buffer.join("\n"); + buffer = []; + currentCode = 0; + } + } + + return { code: code, text: line }; + } + else { + if (!buffer.length && (multiRes = RE_MULTI.exec(line))) { + currentCode = parseInt(multiRes[1], 10); + } + buffer.push(line.toString()); + } + } +} + +module.exports = responseHandler; diff --git a/node_modules/jsftp/lib/utils.js b/node_modules/jsftp/lib/utils.js new file mode 100644 index 0000000..d569e99 --- /dev/null +++ b/node_modules/jsftp/lib/utils.js @@ -0,0 +1,101 @@ +var Parser = require("parse-listing"); +var async = require("async"); + +var RE_RES = /^(\d\d\d)\s(.*)/; +var RE_MULTI = /^(\d\d\d)-/; +var RE_SERVER_RESPONSE = /^(\d\d\d)(.*)/; + +var Utils = module.exports = { + // Codes from 100 to 200 are FTP marks + isMark: function(code) { + code = parseInt(code, 10); + return code > 100 && code < 200; + }, + + /** + * Parse raw output of a file listing, trying in to clean up broken listings in + * the process + * @param {String} listing Raw file listing coming from a 'list' or 'stat' + * @returns {Object[]} + */ + parseEntry: function(listing) { + var t, parsedEntry; + var i = 0; + var parsed = []; + var splitEntries = listing.split(/\r\n|\n/); + async.eachSeries(splitEntries, function(entry, next) { + function _next() { + i += 1; + next(); + } + + // Some servers include an official code-multiline sign at the beginning of + // every string. We must strip it if that's the case. + if (RE_MULTI.test(entry)) + entry = entry.substr(3); + + entry = entry.trim(); + + // Filter file-listing results from 'STAT' command, since they include + // server responses before and after the file listing. + // Issue: https://github.com/sergi/jsftp/issues/3 + if (RE_SERVER_RESPONSE.test(entry) || + RE_RES.test(entry) || RE_MULTI.test(entry)) { + return _next(); + } + + parsedEntry = Parser.parseEntry(entry); + if (parsedEntry === null) { + if (splitEntries[i + 1]) { + t = Parser.parseEntry(entry + splitEntries[i + 1]); + if (t !== null) { + splitEntries[i + 1] = entry + splitEntries[i + 1]; + return _next(); + } + } + + if (splitEntries[i - 1] && parsed.length > 0) { + t = Parser.parseEntry(splitEntries[i - 1] + entry); + if (t !== null) { + parsed[parsed.length - 1] = t; + } + } + } + else { + if (parsedEntry) + parsed.push(parsedEntry) + } + _next(); + }); + + return parsed; + }, + + getPasvPort: function(text) { + var RE_PASV = /([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/; + var match = RE_PASV.exec(text); + if (!match) return false; + + // Array containing the passive host and the port number + return [match[1].replace(/,/g, "."), + (parseInt(match[2], 10) & 255) * 256 + (parseInt(match[3], 10) & 255)]; + }, + + /** + * Cleans up commands with potentially insecure data in them, such as + * passwords, personal info, etc. + * + * @param cmd {String} Command to be sanitized + * @returns {String} Sanitized command + */ + sanitize: function(cmd) { + if (!cmd) return ""; + + var _cmd = cmd.slice(0, 5); + if (_cmd === "pass ") + cmd = _cmd + Array(cmd.length - 5).join("*"); + + return cmd; + } +} + diff --git a/node_modules/jsftp/node_modules/async/LICENSE b/node_modules/jsftp/node_modules/async/LICENSE new file mode 100644 index 0000000..b7f9d50 --- /dev/null +++ b/node_modules/jsftp/node_modules/async/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Caolan McMahon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/jsftp/node_modules/async/README.md b/node_modules/jsftp/node_modules/async/README.md new file mode 100644 index 0000000..9ff1acf --- /dev/null +++ b/node_modules/jsftp/node_modules/async/README.md @@ -0,0 +1,1414 @@ +# Async.js + +Async is a utility module which provides straight-forward, powerful functions +for working with asynchronous JavaScript. Although originally designed for +use with [node.js](http://nodejs.org), it can also be used directly in the +browser. Also supports [component](https://github.com/component/component). + +Async provides around 20 functions that include the usual 'functional' +suspects (map, reduce, filter, each…) as well as some common patterns +for asynchronous control flow (parallel, series, waterfall…). All these +functions assume you follow the node.js convention of providing a single +callback as the last argument of your async function. + + +## Quick Examples + +```javascript +async.map(['file1','file2','file3'], fs.stat, function(err, results){ + // results is now an array of stats for each file +}); + +async.filter(['file1','file2','file3'], fs.exists, function(results){ + // results now equals an array of the existing files +}); + +async.parallel([ + function(){ ... }, + function(){ ... } +], callback); + +async.series([ + function(){ ... }, + function(){ ... } +]); +``` + +There are many more functions available so take a look at the docs below for a +full list. This module aims to be comprehensive, so if you feel anything is +missing please create a GitHub issue for it. + +## Common Pitfalls + +### Binding a context to an iterator + +This section is really about bind, not about async. If you are wondering how to +make async execute your iterators in a given context, or are confused as to why +a method of another library isn't working as an iterator, study this example: + +```js +// Here is a simple object with an (unnecessarily roundabout) squaring method +var AsyncSquaringLibrary = { + squareExponent: 2, + square: function(number, callback){ + var result = Math.pow(number, this.squareExponent); + setTimeout(function(){ + callback(null, result); + }, 200); + } +}; + +async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ + // result is [NaN, NaN, NaN] + // This fails because the `this.squareExponent` expression in the square + // function is not evaluated in the context of AsyncSquaringLibrary, and is + // therefore undefined. +}); + +async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){ + // result is [1, 4, 9] + // With the help of bind we can attach a context to the iterator before + // passing it to async. Now the square function will be executed in its + // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent` + // will be as expected. +}); +``` + +## Download + +The source is available for download from +[GitHub](http://github.com/caolan/async). +Alternatively, you can install using Node Package Manager (npm): + + npm install async + +__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed + +## In the Browser + +So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage: + +```html + + +``` + +## Documentation + +### Collections + +* [each](#each) +* [map](#map) +* [filter](#filter) +* [reject](#reject) +* [reduce](#reduce) +* [detect](#detect) +* [sortBy](#sortBy) +* [some](#some) +* [every](#every) +* [concat](#concat) + +### Control Flow + +* [series](#series) +* [parallel](#parallel) +* [whilst](#whilst) +* [doWhilst](#doWhilst) +* [until](#until) +* [doUntil](#doUntil) +* [forever](#forever) +* [waterfall](#waterfall) +* [compose](#compose) +* [applyEach](#applyEach) +* [queue](#queue) +* [cargo](#cargo) +* [auto](#auto) +* [iterator](#iterator) +* [apply](#apply) +* [nextTick](#nextTick) +* [times](#times) +* [timesSeries](#timesSeries) + +### Utils + +* [memoize](#memoize) +* [unmemoize](#unmemoize) +* [log](#log) +* [dir](#dir) +* [noConflict](#noConflict) + + +## Collections + + + +### each(arr, iterator, callback) + +Applies an iterator function to each item in an array, in parallel. +The iterator is called with an item from the list and a callback for when it +has finished. If the iterator passes an error to this callback, the main +callback for the each function is immediately called with the error. + +Note, that since this function applies the iterator to each item in parallel +there is no guarantee that the iterator functions will complete in order. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err) which must be called once it has + completed. If no error has occured, the callback should be run without + arguments or with an explicit null argument. +* callback(err) - A callback which is called after all the iterator functions + have finished, or an error has occurred. + +__Example__ + +```js +// assuming openFiles is an array of file names and saveFile is a function +// to save the modified contents of that file: + +async.each(openFiles, saveFile, function(err){ + // if any of the saves produced an error, err would equal that error +}); +``` + +--------------------------------------- + + + +### eachSeries(arr, iterator, callback) + +The same as each only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. This means the iterator functions will complete in order. + + +--------------------------------------- + + + +### eachLimit(arr, limit, iterator, callback) + +The same as each only no more than "limit" iterators will be simultaneously +running at any time. + +Note that the items are not processed in batches, so there is no guarantee that + the first "limit" iterator functions will complete before any others are +started. + +__Arguments__ + +* arr - An array to iterate over. +* limit - The maximum number of iterators to run at any time. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err) which must be called once it has + completed. If no error has occured, the callback should be run without + arguments or with an explicit null argument. +* callback(err) - A callback which is called after all the iterator functions + have finished, or an error has occurred. + +__Example__ + +```js +// Assume documents is an array of JSON objects and requestApi is a +// function that interacts with a rate-limited REST api. + +async.eachLimit(documents, 20, requestApi, function(err){ + // if any of the saves produced an error, err would equal that error +}); +``` + +--------------------------------------- + + +### map(arr, iterator, callback) + +Produces a new array of values by mapping each value in the given array through +the iterator function. The iterator is called with an item from the array and a +callback for when it has finished processing. The callback takes 2 arguments, +an error and the transformed item from the array. If the iterator passes an +error to this callback, the main callback for the map function is immediately +called with the error. + +Note, that since this function applies the iterator to each item in parallel +there is no guarantee that the iterator functions will complete in order, however +the results array will be in the same order as the original array. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, transformed) which must be called once + it has completed with an error (which can be null) and a transformed item. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is an array of the + transformed items from the original array. + +__Example__ + +```js +async.map(['file1','file2','file3'], fs.stat, function(err, results){ + // results is now an array of stats for each file +}); +``` + +--------------------------------------- + + +### mapSeries(arr, iterator, callback) + +The same as map only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. The results array will be in the same order as the original. + + +--------------------------------------- + + +### mapLimit(arr, limit, iterator, callback) + +The same as map only no more than "limit" iterators will be simultaneously +running at any time. + +Note that the items are not processed in batches, so there is no guarantee that + the first "limit" iterator functions will complete before any others are +started. + +__Arguments__ + +* arr - An array to iterate over. +* limit - The maximum number of iterators to run at any time. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, transformed) which must be called once + it has completed with an error (which can be null) and a transformed item. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is an array of the + transformed items from the original array. + +__Example__ + +```js +async.map(['file1','file2','file3'], 1, fs.stat, function(err, results){ + // results is now an array of stats for each file +}); +``` + +--------------------------------------- + + +### filter(arr, iterator, callback) + +__Alias:__ select + +Returns a new array of all the values which pass an async truth test. +_The callback for each iterator call only accepts a single argument of true or +false, it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like fs.exists. This operation is +performed in parallel, but the results array will be in the same order as the +original. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(results) - A callback which is called after all the iterator + functions have finished. + +__Example__ + +```js +async.filter(['file1','file2','file3'], fs.exists, function(results){ + // results now equals an array of the existing files +}); +``` + +--------------------------------------- + + +### filterSeries(arr, iterator, callback) + +__alias:__ selectSeries + +The same as filter only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. The results array will be in the same order as the original. + +--------------------------------------- + + +### reject(arr, iterator, callback) + +The opposite of filter. Removes values that pass an async truth test. + +--------------------------------------- + + +### rejectSeries(arr, iterator, callback) + +The same as reject, only the iterator is applied to each item in the array +in series. + + +--------------------------------------- + + +### reduce(arr, memo, iterator, callback) + +__aliases:__ inject, foldl + +Reduces a list of values into a single value using an async iterator to return +each successive step. Memo is the initial state of the reduction. This +function only operates in series. For performance reasons, it may make sense to +split a call to this function into a parallel map, then use the normal +Array.prototype.reduce on the results. This function is for situations where +each step in the reduction needs to be async, if you can get the data before +reducing it then it's probably a good idea to do so. + +__Arguments__ + +* arr - An array to iterate over. +* memo - The initial state of the reduction. +* iterator(memo, item, callback) - A function applied to each item in the + array to produce the next step in the reduction. The iterator is passed a + callback(err, reduction) which accepts an optional error as its first + argument, and the state of the reduction as the second. If an error is + passed to the callback, the reduction is stopped and the main callback is + immediately called with the error. +* callback(err, result) - A callback which is called after all the iterator + functions have finished. Result is the reduced value. + +__Example__ + +```js +async.reduce([1,2,3], 0, function(memo, item, callback){ + // pointless async: + process.nextTick(function(){ + callback(null, memo + item) + }); +}, function(err, result){ + // result is now equal to the last value of memo, which is 6 +}); +``` + +--------------------------------------- + + +### reduceRight(arr, memo, iterator, callback) + +__Alias:__ foldr + +Same as reduce, only operates on the items in the array in reverse order. + + +--------------------------------------- + + +### detect(arr, iterator, callback) + +Returns the first value in a list that passes an async truth test. The +iterator is applied in parallel, meaning the first iterator to return true will +fire the detect callback with that result. That means the result might not be +the first item in the original array (in terms of order) that passes the test. + +If order within the original array is important then look at detectSeries. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(result) - A callback which is called as soon as any iterator returns + true, or after all the iterator functions have finished. Result will be + the first item in the array that passes the truth test (iterator) or the + value undefined if none passed. + +__Example__ + +```js +async.detect(['file1','file2','file3'], fs.exists, function(result){ + // result now equals the first file in the list that exists +}); +``` + +--------------------------------------- + + +### detectSeries(arr, iterator, callback) + +The same as detect, only the iterator is applied to each item in the array +in series. This means the result is always the first in the original array (in +terms of array order) that passes the truth test. + + +--------------------------------------- + + +### sortBy(arr, iterator, callback) + +Sorts a list by the results of running each value through an async iterator. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, sortValue) which must be called once it + has completed with an error (which can be null) and a value to use as the sort + criteria. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is the items from + the original array sorted by the values returned by the iterator calls. + +__Example__ + +```js +async.sortBy(['file1','file2','file3'], function(file, callback){ + fs.stat(file, function(err, stats){ + callback(err, stats.mtime); + }); +}, function(err, results){ + // results is now the original array of files sorted by + // modified date +}); +``` + +--------------------------------------- + + +### some(arr, iterator, callback) + +__Alias:__ any + +Returns true if at least one element in the array satisfies an async test. +_The callback for each iterator call only accepts a single argument of true or +false, it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like fs.exists. Once any iterator +call returns true, the main callback is immediately called. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(result) - A callback which is called as soon as any iterator returns + true, or after all the iterator functions have finished. Result will be + either true or false depending on the values of the async tests. + +__Example__ + +```js +async.some(['file1','file2','file3'], fs.exists, function(result){ + // if result is true then at least one of the files exists +}); +``` + +--------------------------------------- + + +### every(arr, iterator, callback) + +__Alias:__ all + +Returns true if every element in the array satisfies an async test. +_The callback for each iterator call only accepts a single argument of true or +false, it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like fs.exists. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(result) - A callback which is called after all the iterator + functions have finished. Result will be either true or false depending on + the values of the async tests. + +__Example__ + +```js +async.every(['file1','file2','file3'], fs.exists, function(result){ + // if result is true then every file exists +}); +``` + +--------------------------------------- + + +### concat(arr, iterator, callback) + +Applies an iterator to each item in a list, concatenating the results. Returns the +concatenated list. The iterators are called in parallel, and the results are +concatenated as they return. There is no guarantee that the results array will +be returned in the original order of the arguments passed to the iterator function. + +__Arguments__ + +* arr - An array to iterate over +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, results) which must be called once it + has completed with an error (which can be null) and an array of results. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is an array containing + the concatenated results of the iterator function. + +__Example__ + +```js +async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){ + // files is now a list of filenames that exist in the 3 directories +}); +``` + +--------------------------------------- + + +### concatSeries(arr, iterator, callback) + +Same as async.concat, but executes in series instead of parallel. + + +## Control Flow + + +### series(tasks, [callback]) + +Run an array of functions in series, each one running once the previous +function has completed. If any functions in the series pass an error to its +callback, no more functions are run and the callback for the series is +immediately called with the value of the error. Once the tasks have completed, +the results are passed to the final callback as an array. + +It is also possible to use an object instead of an array. Each property will be +run as a function and the results will be passed to the final callback as an object +instead of an array. This can be a more readable way of handling results from +async.series. + + +__Arguments__ + +* tasks - An array or object containing functions to run, each function is passed + a callback(err, result) it must call on completion with an error (which can + be null) and an optional result value. +* callback(err, results) - An optional callback to run once all the functions + have completed. This function gets a results array (or object) containing all + the result arguments passed to the task callbacks. + +__Example__ + +```js +async.series([ + function(callback){ + // do some stuff ... + callback(null, 'one'); + }, + function(callback){ + // do some more stuff ... + callback(null, 'two'); + } +], +// optional callback +function(err, results){ + // results is now equal to ['one', 'two'] +}); + + +// an example using an object instead of an array +async.series({ + one: function(callback){ + setTimeout(function(){ + callback(null, 1); + }, 200); + }, + two: function(callback){ + setTimeout(function(){ + callback(null, 2); + }, 100); + } +}, +function(err, results) { + // results is now equal to: {one: 1, two: 2} +}); +``` + +--------------------------------------- + + +### parallel(tasks, [callback]) + +Run an array of functions in parallel, without waiting until the previous +function has completed. If any of the functions pass an error to its +callback, the main callback is immediately called with the value of the error. +Once the tasks have completed, the results are passed to the final callback as an +array. + +It is also possible to use an object instead of an array. Each property will be +run as a function and the results will be passed to the final callback as an object +instead of an array. This can be a more readable way of handling results from +async.parallel. + + +__Arguments__ + +* tasks - An array or object containing functions to run, each function is passed + a callback(err, result) it must call on completion with an error (which can + be null) and an optional result value. +* callback(err, results) - An optional callback to run once all the functions + have completed. This function gets a results array (or object) containing all + the result arguments passed to the task callbacks. + +__Example__ + +```js +async.parallel([ + function(callback){ + setTimeout(function(){ + callback(null, 'one'); + }, 200); + }, + function(callback){ + setTimeout(function(){ + callback(null, 'two'); + }, 100); + } +], +// optional callback +function(err, results){ + // the results array will equal ['one','two'] even though + // the second function had a shorter timeout. +}); + + +// an example using an object instead of an array +async.parallel({ + one: function(callback){ + setTimeout(function(){ + callback(null, 1); + }, 200); + }, + two: function(callback){ + setTimeout(function(){ + callback(null, 2); + }, 100); + } +}, +function(err, results) { + // results is now equals to: {one: 1, two: 2} +}); +``` + +--------------------------------------- + + +### parallelLimit(tasks, limit, [callback]) + +The same as parallel only the tasks are executed in parallel with a maximum of "limit" +tasks executing at any time. + +Note that the tasks are not executed in batches, so there is no guarantee that +the first "limit" tasks will complete before any others are started. + +__Arguments__ + +* tasks - An array or object containing functions to run, each function is passed + a callback(err, result) it must call on completion with an error (which can + be null) and an optional result value. +* limit - The maximum number of tasks to run at any time. +* callback(err, results) - An optional callback to run once all the functions + have completed. This function gets a results array (or object) containing all + the result arguments passed to the task callbacks. + +--------------------------------------- + + +### whilst(test, fn, callback) + +Repeatedly call fn, while test returns true. Calls the callback when stopped, +or an error occurs. + +__Arguments__ + +* test() - synchronous truth test to perform before each execution of fn. +* fn(callback) - A function to call each time the test passes. The function is + passed a callback(err) which must be called once it has completed with an + optional error argument. +* callback(err) - A callback which is called after the test fails and repeated + execution of fn has stopped. + +__Example__ + +```js +var count = 0; + +async.whilst( + function () { return count < 5; }, + function (callback) { + count++; + setTimeout(callback, 1000); + }, + function (err) { + // 5 seconds have passed + } +); +``` + +--------------------------------------- + + +### doWhilst(fn, test, callback) + +The post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. + +--------------------------------------- + + +### until(test, fn, callback) + +Repeatedly call fn, until test returns true. Calls the callback when stopped, +or an error occurs. + +The inverse of async.whilst. + +--------------------------------------- + + +### doUntil(fn, test, callback) + +Like doWhilst except the test is inverted. Note the argument ordering differs from `until`. + +--------------------------------------- + + +### forever(fn, callback) + +Calls the asynchronous function 'fn' repeatedly, in series, indefinitely. +If an error is passed to fn's callback then 'callback' is called with the +error, otherwise it will never be called. + +--------------------------------------- + + +### waterfall(tasks, [callback]) + +Runs an array of functions in series, each passing their results to the next in +the array. However, if any of the functions pass an error to the callback, the +next function is not executed and the main callback is immediately called with +the error. + +__Arguments__ + +* tasks - An array of functions to run, each function is passed a + callback(err, result1, result2, ...) it must call on completion. The first + argument is an error (which can be null) and any further arguments will be + passed as arguments in order to the next task. +* callback(err, [results]) - An optional callback to run once all the functions + have completed. This will be passed the results of the last task's callback. + + + +__Example__ + +```js +async.waterfall([ + function(callback){ + callback(null, 'one', 'two'); + }, + function(arg1, arg2, callback){ + callback(null, 'three'); + }, + function(arg1, callback){ + // arg1 now equals 'three' + callback(null, 'done'); + } +], function (err, result) { + // result now equals 'done' +}); +``` + +--------------------------------------- + +### compose(fn1, fn2...) + +Creates a function which is a composition of the passed asynchronous +functions. Each function consumes the return value of the function that +follows. Composing functions f(), g() and h() would produce the result of +f(g(h())), only this version uses callbacks to obtain the return values. + +Each function is executed with the `this` binding of the composed function. + +__Arguments__ + +* functions... - the asynchronous functions to compose + + +__Example__ + +```js +function add1(n, callback) { + setTimeout(function () { + callback(null, n + 1); + }, 10); +} + +function mul3(n, callback) { + setTimeout(function () { + callback(null, n * 3); + }, 10); +} + +var add1mul3 = async.compose(mul3, add1); + +add1mul3(4, function (err, result) { + // result now equals 15 +}); +``` + +--------------------------------------- + +### applyEach(fns, args..., callback) + +Applies the provided arguments to each function in the array, calling the +callback after all functions have completed. If you only provide the first +argument then it will return a function which lets you pass in the +arguments as if it were a single function call. + +__Arguments__ + +* fns - the asynchronous functions to all call with the same arguments +* args... - any number of separate arguments to pass to the function +* callback - the final argument should be the callback, called when all + functions have completed processing + + +__Example__ + +```js +async.applyEach([enableSearch, updateSchema], 'bucket', callback); + +// partial application example: +async.each( + buckets, + async.applyEach([enableSearch, updateSchema]), + callback +); +``` + +--------------------------------------- + + +### applyEachSeries(arr, iterator, callback) + +The same as applyEach only the functions are applied in series. + +--------------------------------------- + + +### queue(worker, concurrency) + +Creates a queue object with the specified concurrency. Tasks added to the +queue will be processed in parallel (up to the concurrency limit). If all +workers are in progress, the task is queued until one is available. Once +a worker has completed a task, the task's callback is called. + +__Arguments__ + +* worker(task, callback) - An asynchronous function for processing a queued + task, which must call its callback(err) argument when finished, with an + optional error as an argument. +* concurrency - An integer for determining how many worker functions should be + run in parallel. + +__Queue objects__ + +The queue object returned by this function has the following properties and +methods: + +* length() - a function returning the number of items waiting to be processed. +* concurrency - an integer for determining how many worker functions should be + run in parallel. This property can be changed after a queue is created to + alter the concurrency on-the-fly. +* push(task, [callback]) - add a new task to the queue, the callback is called + once the worker has finished processing the task. + instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list. +* unshift(task, [callback]) - add a new task to the front of the queue. +* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued +* empty - a callback that is called when the last item from the queue is given to a worker +* drain - a callback that is called when the last item from the queue has returned from the worker + +__Example__ + +```js +// create a queue object with concurrency 2 + +var q = async.queue(function (task, callback) { + console.log('hello ' + task.name); + callback(); +}, 2); + + +// assign a callback +q.drain = function() { + console.log('all items have been processed'); +} + +// add some items to the queue + +q.push({name: 'foo'}, function (err) { + console.log('finished processing foo'); +}); +q.push({name: 'bar'}, function (err) { + console.log('finished processing bar'); +}); + +// add some items to the queue (batch-wise) + +q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) { + console.log('finished processing bar'); +}); + +// add some items to the front of the queue + +q.unshift({name: 'bar'}, function (err) { + console.log('finished processing bar'); +}); +``` + +--------------------------------------- + + +### cargo(worker, [payload]) + +Creates a cargo object with the specified payload. Tasks added to the +cargo will be processed altogether (up to the payload limit). If the +worker is in progress, the task is queued until it is available. Once +the worker has completed some tasks, each callback of those tasks is called. + +__Arguments__ + +* worker(tasks, callback) - An asynchronous function for processing an array of + queued tasks, which must call its callback(err) argument when finished, with + an optional error as an argument. +* payload - An optional integer for determining how many tasks should be + processed per round; if omitted, the default is unlimited. + +__Cargo objects__ + +The cargo object returned by this function has the following properties and +methods: + +* length() - a function returning the number of items waiting to be processed. +* payload - an integer for determining how many tasks should be + process per round. This property can be changed after a cargo is created to + alter the payload on-the-fly. +* push(task, [callback]) - add a new task to the queue, the callback is called + once the worker has finished processing the task. + instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list. +* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued +* empty - a callback that is called when the last item from the queue is given to a worker +* drain - a callback that is called when the last item from the queue has returned from the worker + +__Example__ + +```js +// create a cargo object with payload 2 + +var cargo = async.cargo(function (tasks, callback) { + for(var i=0; i +### auto(tasks, [callback]) + +Determines the best order for running functions based on their requirements. +Each function can optionally depend on other functions being completed first, +and each function is run as soon as its requirements are satisfied. If any of +the functions pass an error to their callback, that function will not complete +(so any other functions depending on it will not run) and the main callback +will be called immediately with the error. Functions also receive an object +containing the results of functions which have completed so far. + +Note, all functions are called with a results object as a second argument, +so it is unsafe to pass functions in the tasks object which cannot handle the +extra argument. For example, this snippet of code: + +```js +async.auto({ + readData: async.apply(fs.readFile, 'data.txt', 'utf-8'); +}, callback); +``` + +will have the effect of calling readFile with the results object as the last +argument, which will fail: + +```js +fs.readFile('data.txt', 'utf-8', cb, {}); +``` + +Instead, wrap the call to readFile in a function which does not forward the +results object: + +```js +async.auto({ + readData: function(cb, results){ + fs.readFile('data.txt', 'utf-8', cb); + } +}, callback); +``` + +__Arguments__ + +* tasks - An object literal containing named functions or an array of + requirements, with the function itself the last item in the array. The key + used for each function or array is used when specifying requirements. The + function receives two arguments: (1) a callback(err, result) which must be + called when finished, passing an error (which can be null) and the result of + the function's execution, and (2) a results object, containing the results of + the previously executed functions. +* callback(err, results) - An optional callback which is called when all the + tasks have been completed. The callback will receive an error as an argument + if any tasks pass an error to their callback. Results will always be passed + but if an error occurred, no other tasks will be performed, and the results + object will only contain partial results. + + +__Example__ + +```js +async.auto({ + get_data: function(callback){ + // async code to get some data + }, + make_folder: function(callback){ + // async code to create a directory to store a file in + // this is run at the same time as getting the data + }, + write_file: ['get_data', 'make_folder', function(callback){ + // once there is some data and the directory exists, + // write the data to a file in the directory + callback(null, filename); + }], + email_link: ['write_file', function(callback, results){ + // once the file is written let's email a link to it... + // results.write_file contains the filename returned by write_file. + }] +}); +``` + +This is a fairly trivial example, but to do this using the basic parallel and +series functions would look like this: + +```js +async.parallel([ + function(callback){ + // async code to get some data + }, + function(callback){ + // async code to create a directory to store a file in + // this is run at the same time as getting the data + } +], +function(err, results){ + async.series([ + function(callback){ + // once there is some data and the directory exists, + // write the data to a file in the directory + }, + function(callback){ + // once the file is written let's email a link to it... + } + ]); +}); +``` + +For a complicated series of async tasks using the auto function makes adding +new tasks much easier and makes the code more readable. + + +--------------------------------------- + + +### iterator(tasks) + +Creates an iterator function which calls the next function in the array, +returning a continuation to call the next one after that. It's also possible to +'peek' the next iterator by doing iterator.next(). + +This function is used internally by the async module but can be useful when +you want to manually control the flow of functions in series. + +__Arguments__ + +* tasks - An array of functions to run. + +__Example__ + +```js +var iterator = async.iterator([ + function(){ sys.p('one'); }, + function(){ sys.p('two'); }, + function(){ sys.p('three'); } +]); + +node> var iterator2 = iterator(); +'one' +node> var iterator3 = iterator2(); +'two' +node> iterator3(); +'three' +node> var nextfn = iterator2.next(); +node> nextfn(); +'three' +``` + +--------------------------------------- + + +### apply(function, arguments..) + +Creates a continuation function with some arguments already applied, a useful +shorthand when combined with other control flow functions. Any arguments +passed to the returned function are added to the arguments originally passed +to apply. + +__Arguments__ + +* function - The function you want to eventually apply all arguments to. +* arguments... - Any number of arguments to automatically apply when the + continuation is called. + +__Example__ + +```js +// using apply + +async.parallel([ + async.apply(fs.writeFile, 'testfile1', 'test1'), + async.apply(fs.writeFile, 'testfile2', 'test2'), +]); + + +// the same process without using apply + +async.parallel([ + function(callback){ + fs.writeFile('testfile1', 'test1', callback); + }, + function(callback){ + fs.writeFile('testfile2', 'test2', callback); + } +]); +``` + +It's possible to pass any number of additional arguments when calling the +continuation: + +```js +node> var fn = async.apply(sys.puts, 'one'); +node> fn('two', 'three'); +one +two +three +``` + +--------------------------------------- + + +### nextTick(callback) + +Calls the callback on a later loop around the event loop. In node.js this just +calls process.nextTick, in the browser it falls back to setImmediate(callback) +if available, otherwise setTimeout(callback, 0), which means other higher priority +events may precede the execution of the callback. + +This is used internally for browser-compatibility purposes. + +__Arguments__ + +* callback - The function to call on a later loop around the event loop. + +__Example__ + +```js +var call_order = []; +async.nextTick(function(){ + call_order.push('two'); + // call_order now equals ['one','two'] +}); +call_order.push('one') +``` + + +### times(n, callback) + +Calls the callback n times and accumulates results in the same manner +you would use with async.map. + +__Arguments__ + +* n - The number of times to run the function. +* callback - The function to call n times. + +__Example__ + +```js +// Pretend this is some complicated async factory +var createUser = function(id, callback) { + callback(null, { + id: 'user' + id + }) +} +// generate 5 users +async.times(5, function(n, next){ + createUser(n, function(err, user) { + next(err, user) + }) +}, function(err, users) { + // we should now have 5 users +}); +``` + + +### timesSeries(n, callback) + +The same as times only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. The results array will be in the same order as the original. + + +## Utils + + +### memoize(fn, [hasher]) + +Caches the results of an async function. When creating a hash to store function +results against, the callback is omitted from the hash and an optional hash +function can be used. + +The cache of results is exposed as the `memo` property of the function returned +by `memoize`. + +__Arguments__ + +* fn - the function you to proxy and cache results from. +* hasher - an optional function for generating a custom hash for storing + results, it has all the arguments applied to it apart from the callback, and + must be synchronous. + +__Example__ + +```js +var slow_fn = function (name, callback) { + // do something + callback(null, result); +}; +var fn = async.memoize(slow_fn); + +// fn can now be used as if it were slow_fn +fn('some name', function () { + // callback +}); +``` + + +### unmemoize(fn) + +Undoes a memoized function, reverting it to the original, unmemoized +form. Comes handy in tests. + +__Arguments__ + +* fn - the memoized function + + +### log(function, arguments) + +Logs the result of an async function to the console. Only works in node.js or +in browsers that support console.log and console.error (such as FF and Chrome). +If multiple arguments are returned from the async function, console.log is +called on each argument in order. + +__Arguments__ + +* function - The function you want to eventually apply all arguments to. +* arguments... - Any number of arguments to apply to the function. + +__Example__ + +```js +var hello = function(name, callback){ + setTimeout(function(){ + callback(null, 'hello ' + name); + }, 1000); +}; +``` +```js +node> async.log(hello, 'world'); +'hello world' +``` + +--------------------------------------- + + +### dir(function, arguments) + +Logs the result of an async function to the console using console.dir to +display the properties of the resulting object. Only works in node.js or +in browsers that support console.dir and console.error (such as FF and Chrome). +If multiple arguments are returned from the async function, console.dir is +called on each argument in order. + +__Arguments__ + +* function - The function you want to eventually apply all arguments to. +* arguments... - Any number of arguments to apply to the function. + +__Example__ + +```js +var hello = function(name, callback){ + setTimeout(function(){ + callback(null, {hello: name}); + }, 1000); +}; +``` +```js +node> async.dir(hello, 'world'); +{hello: 'world'} +``` + +--------------------------------------- + + +### noConflict() + +Changes the value of async back to its original value, returning a reference to the +async object. diff --git a/node_modules/jsftp/node_modules/async/component.json b/node_modules/jsftp/node_modules/async/component.json new file mode 100644 index 0000000..bbb0115 --- /dev/null +++ b/node_modules/jsftp/node_modules/async/component.json @@ -0,0 +1,11 @@ +{ + "name": "async", + "repo": "caolan/async", + "description": "Higher-order functions and common patterns for asynchronous code", + "version": "0.1.23", + "keywords": [], + "dependencies": {}, + "development": {}, + "main": "lib/async.js", + "scripts": [ "lib/async.js" ] +} diff --git a/node_modules/jsftp/node_modules/async/lib/async.js b/node_modules/jsftp/node_modules/async/lib/async.js new file mode 100755 index 0000000..cb6320d --- /dev/null +++ b/node_modules/jsftp/node_modules/async/lib/async.js @@ -0,0 +1,955 @@ +/*global setImmediate: false, setTimeout: false, console: false */ +(function () { + + var async = {}; + + // global on the server, window in the browser + var root, previous_async; + + root = this; + if (root != null) { + previous_async = root.async; + } + + async.noConflict = function () { + root.async = previous_async; + return async; + }; + + function only_once(fn) { + var called = false; + return function() { + if (called) throw new Error("Callback was already called."); + called = true; + fn.apply(root, arguments); + } + } + + //// cross-browser compatiblity functions //// + + var _each = function (arr, iterator) { + if (arr.forEach) { + return arr.forEach(iterator); + } + for (var i = 0; i < arr.length; i += 1) { + iterator(arr[i], i, arr); + } + }; + + var _map = function (arr, iterator) { + if (arr.map) { + return arr.map(iterator); + } + var results = []; + _each(arr, function (x, i, a) { + results.push(iterator(x, i, a)); + }); + return results; + }; + + var _reduce = function (arr, iterator, memo) { + if (arr.reduce) { + return arr.reduce(iterator, memo); + } + _each(arr, function (x, i, a) { + memo = iterator(memo, x, i, a); + }); + return memo; + }; + + var _keys = function (obj) { + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + keys.push(k); + } + } + return keys; + }; + + //// exported async module functions //// + + //// nextTick implementation with browser-compatible fallback //// + if (typeof process === 'undefined' || !(process.nextTick)) { + if (typeof setImmediate === 'function') { + async.nextTick = function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + async.setImmediate = async.nextTick; + } + else { + async.nextTick = function (fn) { + setTimeout(fn, 0); + }; + async.setImmediate = async.nextTick; + } + } + else { + async.nextTick = process.nextTick; + if (typeof setImmediate !== 'undefined') { + async.setImmediate = setImmediate; + } + else { + async.setImmediate = async.nextTick; + } + } + + async.each = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + _each(arr, function (x) { + iterator(x, only_once(function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(null); + } + } + })); + }); + }; + async.forEach = async.each; + + async.eachSeries = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + var iterate = function () { + iterator(arr[completed], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(null); + } + else { + iterate(); + } + } + }); + }; + iterate(); + }; + async.forEachSeries = async.eachSeries; + + async.eachLimit = function (arr, limit, iterator, callback) { + var fn = _eachLimit(limit); + fn.apply(null, [arr, iterator, callback]); + }; + async.forEachLimit = async.eachLimit; + + var _eachLimit = function (limit) { + + return function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length || limit <= 0) { + return callback(); + } + var completed = 0; + var started = 0; + var running = 0; + + (function replenish () { + if (completed >= arr.length) { + return callback(); + } + + while (running < limit && started < arr.length) { + started += 1; + running += 1; + iterator(arr[started - 1], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + running -= 1; + if (completed >= arr.length) { + callback(); + } + else { + replenish(); + } + } + }); + } + })(); + }; + }; + + + var doParallel = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.each].concat(args)); + }; + }; + var doParallelLimit = function(limit, fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [_eachLimit(limit)].concat(args)); + }; + }; + var doSeries = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.eachSeries].concat(args)); + }; + }; + + + var _asyncMap = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (err, v) { + results[x.index] = v; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + }; + async.map = doParallel(_asyncMap); + async.mapSeries = doSeries(_asyncMap); + async.mapLimit = function (arr, limit, iterator, callback) { + return _mapLimit(limit)(arr, iterator, callback); + }; + + var _mapLimit = function(limit) { + return doParallelLimit(limit, _asyncMap); + }; + + // reduce only has a series version, as doing reduce in parallel won't + // work in many situations. + async.reduce = function (arr, memo, iterator, callback) { + async.eachSeries(arr, function (x, callback) { + iterator(memo, x, function (err, v) { + memo = v; + callback(err); + }); + }, function (err) { + callback(err, memo); + }); + }; + // inject alias + async.inject = async.reduce; + // foldl alias + async.foldl = async.reduce; + + async.reduceRight = function (arr, memo, iterator, callback) { + var reversed = _map(arr, function (x) { + return x; + }).reverse(); + async.reduce(reversed, memo, iterator, callback); + }; + // foldr alias + async.foldr = async.reduceRight; + + var _filter = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.filter = doParallel(_filter); + async.filterSeries = doSeries(_filter); + // select alias + async.select = async.filter; + async.selectSeries = async.filterSeries; + + var _reject = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (!v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.reject = doParallel(_reject); + async.rejectSeries = doSeries(_reject); + + var _detect = function (eachfn, arr, iterator, main_callback) { + eachfn(arr, function (x, callback) { + iterator(x, function (result) { + if (result) { + main_callback(x); + main_callback = function () {}; + } + else { + callback(); + } + }); + }, function (err) { + main_callback(); + }); + }; + async.detect = doParallel(_detect); + async.detectSeries = doSeries(_detect); + + async.some = function (arr, iterator, main_callback) { + async.each(arr, function (x, callback) { + iterator(x, function (v) { + if (v) { + main_callback(true); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(false); + }); + }; + // any alias + async.any = async.some; + + async.every = function (arr, iterator, main_callback) { + async.each(arr, function (x, callback) { + iterator(x, function (v) { + if (!v) { + main_callback(false); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(true); + }); + }; + // all alias + async.all = async.every; + + async.sortBy = function (arr, iterator, callback) { + async.map(arr, function (x, callback) { + iterator(x, function (err, criteria) { + if (err) { + callback(err); + } + else { + callback(null, {value: x, criteria: criteria}); + } + }); + }, function (err, results) { + if (err) { + return callback(err); + } + else { + var fn = function (left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }; + callback(null, _map(results.sort(fn), function (x) { + return x.value; + })); + } + }); + }; + + async.auto = function (tasks, callback) { + callback = callback || function () {}; + var keys = _keys(tasks); + if (!keys.length) { + return callback(null); + } + + var results = {}; + + var listeners = []; + var addListener = function (fn) { + listeners.unshift(fn); + }; + var removeListener = function (fn) { + for (var i = 0; i < listeners.length; i += 1) { + if (listeners[i] === fn) { + listeners.splice(i, 1); + return; + } + } + }; + var taskComplete = function () { + _each(listeners.slice(0), function (fn) { + fn(); + }); + }; + + addListener(function () { + if (_keys(results).length === keys.length) { + callback(null, results); + callback = function () {}; + } + }); + + _each(keys, function (k) { + var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; + var taskCallback = function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + if (err) { + var safeResults = {}; + _each(_keys(results), function(rkey) { + safeResults[rkey] = results[rkey]; + }); + safeResults[k] = args; + callback(err, safeResults); + // stop subsequent errors hitting callback multiple times + callback = function () {}; + } + else { + results[k] = args; + async.setImmediate(taskComplete); + } + }; + var requires = task.slice(0, Math.abs(task.length - 1)) || []; + var ready = function () { + return _reduce(requires, function (a, x) { + return (a && results.hasOwnProperty(x)); + }, true) && !results.hasOwnProperty(k); + }; + if (ready()) { + task[task.length - 1](taskCallback, results); + } + else { + var listener = function () { + if (ready()) { + removeListener(listener); + task[task.length - 1](taskCallback, results); + } + }; + addListener(listener); + } + }); + }; + + async.waterfall = function (tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor !== Array) { + var err = new Error('First argument to waterfall must be an array of functions'); + return callback(err); + } + if (!tasks.length) { + return callback(); + } + var wrapIterator = function (iterator) { + return function (err) { + if (err) { + callback.apply(null, arguments); + callback = function () {}; + } + else { + var args = Array.prototype.slice.call(arguments, 1); + var next = iterator.next(); + if (next) { + args.push(wrapIterator(next)); + } + else { + args.push(callback); + } + async.setImmediate(function () { + iterator.apply(null, args); + }); + } + }; + }; + wrapIterator(async.iterator(tasks))(); + }; + + var _parallel = function(eachfn, tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor === Array) { + eachfn.map(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args); + }); + } + }, callback); + } + else { + var results = {}; + eachfn.each(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.parallel = function (tasks, callback) { + _parallel({ map: async.map, each: async.each }, tasks, callback); + }; + + async.parallelLimit = function(tasks, limit, callback) { + _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); + }; + + async.series = function (tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor === Array) { + async.mapSeries(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args); + }); + } + }, callback); + } + else { + var results = {}; + async.eachSeries(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.iterator = function (tasks) { + var makeCallback = function (index) { + var fn = function () { + if (tasks.length) { + tasks[index].apply(null, arguments); + } + return fn.next(); + }; + fn.next = function () { + return (index < tasks.length - 1) ? makeCallback(index + 1): null; + }; + return fn; + }; + return makeCallback(0); + }; + + async.apply = function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + return function () { + return fn.apply( + null, args.concat(Array.prototype.slice.call(arguments)) + ); + }; + }; + + var _concat = function (eachfn, arr, fn, callback) { + var r = []; + eachfn(arr, function (x, cb) { + fn(x, function (err, y) { + r = r.concat(y || []); + cb(err); + }); + }, function (err) { + callback(err, r); + }); + }; + async.concat = doParallel(_concat); + async.concatSeries = doSeries(_concat); + + async.whilst = function (test, iterator, callback) { + if (test()) { + iterator(function (err) { + if (err) { + return callback(err); + } + async.whilst(test, iterator, callback); + }); + } + else { + callback(); + } + }; + + async.doWhilst = function (iterator, test, callback) { + iterator(function (err) { + if (err) { + return callback(err); + } + if (test()) { + async.doWhilst(iterator, test, callback); + } + else { + callback(); + } + }); + }; + + async.until = function (test, iterator, callback) { + if (!test()) { + iterator(function (err) { + if (err) { + return callback(err); + } + async.until(test, iterator, callback); + }); + } + else { + callback(); + } + }; + + async.doUntil = function (iterator, test, callback) { + iterator(function (err) { + if (err) { + return callback(err); + } + if (!test()) { + async.doUntil(iterator, test, callback); + } + else { + callback(); + } + }); + }; + + async.queue = function (worker, concurrency) { + if (concurrency === undefined) { + concurrency = 1; + } + function _insert(q, data, pos, callback) { + if(data.constructor !== Array) { + data = [data]; + } + _each(data, function(task) { + var item = { + data: task, + callback: typeof callback === 'function' ? callback : null + }; + + if (pos) { + q.tasks.unshift(item); + } else { + q.tasks.push(item); + } + + if (q.saturated && q.tasks.length === concurrency) { + q.saturated(); + } + async.setImmediate(q.process); + }); + } + + var workers = 0; + var q = { + tasks: [], + concurrency: concurrency, + saturated: null, + empty: null, + drain: null, + push: function (data, callback) { + _insert(q, data, false, callback); + }, + unshift: function (data, callback) { + _insert(q, data, true, callback); + }, + process: function () { + if (workers < q.concurrency && q.tasks.length) { + var task = q.tasks.shift(); + if (q.empty && q.tasks.length === 0) { + q.empty(); + } + workers += 1; + var next = function () { + workers -= 1; + if (task.callback) { + task.callback.apply(task, arguments); + } + if (q.drain && q.tasks.length + workers === 0) { + q.drain(); + } + q.process(); + }; + var cb = only_once(next); + worker(task.data, cb); + } + }, + length: function () { + return q.tasks.length; + }, + running: function () { + return workers; + } + }; + return q; + }; + + async.cargo = function (worker, payload) { + var working = false, + tasks = []; + + var cargo = { + tasks: tasks, + payload: payload, + saturated: null, + empty: null, + drain: null, + push: function (data, callback) { + if(data.constructor !== Array) { + data = [data]; + } + _each(data, function(task) { + tasks.push({ + data: task, + callback: typeof callback === 'function' ? callback : null + }); + if (cargo.saturated && tasks.length === payload) { + cargo.saturated(); + } + }); + async.setImmediate(cargo.process); + }, + process: function process() { + if (working) return; + if (tasks.length === 0) { + if(cargo.drain) cargo.drain(); + return; + } + + var ts = typeof payload === 'number' + ? tasks.splice(0, payload) + : tasks.splice(0); + + var ds = _map(ts, function (task) { + return task.data; + }); + + if(cargo.empty) cargo.empty(); + working = true; + worker(ds, function () { + working = false; + + var args = arguments; + _each(ts, function (data) { + if (data.callback) { + data.callback.apply(null, args); + } + }); + + process(); + }); + }, + length: function () { + return tasks.length; + }, + running: function () { + return working; + } + }; + return cargo; + }; + + var _console_fn = function (name) { + return function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + fn.apply(null, args.concat([function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (typeof console !== 'undefined') { + if (err) { + if (console.error) { + console.error(err); + } + } + else if (console[name]) { + _each(args, function (x) { + console[name](x); + }); + } + } + }])); + }; + }; + async.log = _console_fn('log'); + async.dir = _console_fn('dir'); + /*async.info = _console_fn('info'); + async.warn = _console_fn('warn'); + async.error = _console_fn('error');*/ + + async.memoize = function (fn, hasher) { + var memo = {}; + var queues = {}; + hasher = hasher || function (x) { + return x; + }; + var memoized = function () { + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + var key = hasher.apply(null, args); + if (key in memo) { + callback.apply(null, memo[key]); + } + else if (key in queues) { + queues[key].push(callback); + } + else { + queues[key] = [callback]; + fn.apply(null, args.concat([function () { + memo[key] = arguments; + var q = queues[key]; + delete queues[key]; + for (var i = 0, l = q.length; i < l; i++) { + q[i].apply(null, arguments); + } + }])); + } + }; + memoized.memo = memo; + memoized.unmemoized = fn; + return memoized; + }; + + async.unmemoize = function (fn) { + return function () { + return (fn.unmemoized || fn).apply(null, arguments); + }; + }; + + async.times = function (count, iterator, callback) { + var counter = []; + for (var i = 0; i < count; i++) { + counter.push(i); + } + return async.map(counter, iterator, callback); + }; + + async.timesSeries = function (count, iterator, callback) { + var counter = []; + for (var i = 0; i < count; i++) { + counter.push(i); + } + return async.mapSeries(counter, iterator, callback); + }; + + async.compose = function (/* functions... */) { + var fns = Array.prototype.reverse.call(arguments); + return function () { + var that = this; + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + async.reduce(fns, args, function (newargs, fn, cb) { + fn.apply(that, newargs.concat([function () { + var err = arguments[0]; + var nextargs = Array.prototype.slice.call(arguments, 1); + cb(err, nextargs); + }])) + }, + function (err, results) { + callback.apply(that, [err].concat(results)); + }); + }; + }; + + var _applyEach = function (eachfn, fns /*args...*/) { + var go = function () { + var that = this; + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + return eachfn(fns, function (fn, cb) { + fn.apply(that, args.concat([cb])); + }, + callback); + }; + if (arguments.length > 2) { + var args = Array.prototype.slice.call(arguments, 2); + return go.apply(this, args); + } + else { + return go; + } + }; + async.applyEach = doParallel(_applyEach); + async.applyEachSeries = doSeries(_applyEach); + + async.forever = function (fn, callback) { + function next(err) { + if (err) { + if (callback) { + return callback(err); + } + throw err; + } + fn(next); + } + next(); + }; + + // AMD / RequireJS + if (typeof define !== 'undefined' && define.amd) { + define([], function () { + return async; + }); + } + // Node.js + else if (typeof module !== 'undefined' && module.exports) { + module.exports = async; + } + // included directly via \n\n```\n\n## Documentation\n\n### Collections\n\n* [each](#each)\n* [map](#map)\n* [filter](#filter)\n* [reject](#reject)\n* [reduce](#reduce)\n* [detect](#detect)\n* [sortBy](#sortBy)\n* [some](#some)\n* [every](#every)\n* [concat](#concat)\n\n### Control Flow\n\n* [series](#series)\n* [parallel](#parallel)\n* [whilst](#whilst)\n* [doWhilst](#doWhilst)\n* [until](#until)\n* [doUntil](#doUntil)\n* [forever](#forever)\n* [waterfall](#waterfall)\n* [compose](#compose)\n* [applyEach](#applyEach)\n* [queue](#queue)\n* [cargo](#cargo)\n* [auto](#auto)\n* [iterator](#iterator)\n* [apply](#apply)\n* [nextTick](#nextTick)\n* [times](#times)\n* [timesSeries](#timesSeries)\n\n### Utils\n\n* [memoize](#memoize)\n* [unmemoize](#unmemoize)\n* [log](#log)\n* [dir](#dir)\n* [noConflict](#noConflict)\n\n\n## Collections\n\n\n\n### each(arr, iterator, callback)\n\nApplies an iterator function to each item in an array, in parallel.\nThe iterator is called with an item from the list and a callback for when it\nhas finished. If the iterator passes an error to this callback, the main\ncallback for the each function is immediately called with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err) which must be called once it has \n completed. If no error has occured, the callback should be run without \n arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n have finished, or an error has occurred.\n\n__Example__\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n\n\n### eachSeries(arr, iterator, callback)\n\nThe same as each only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. This means the iterator functions will complete in order.\n\n\n---------------------------------------\n\n\n\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as each only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err) which must be called once it has \n completed. If no error has occured, the callback should be run without \n arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n have finished, or an error has occurred.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in the given array through\nthe iterator function. The iterator is called with an item from the array and a\ncallback for when it has finished processing. The callback takes 2 arguments, \nan error and the transformed item from the array. If the iterator passes an\nerror to this callback, the main callback for the map function is immediately\ncalled with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order, however\nthe results array will be in the same order as the original array.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, transformed) which must be called once \n it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array of the\n transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n\n### mapSeries(arr, iterator, callback)\n\nThe same as map only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n---------------------------------------\n\n\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as map only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, transformed) which must be called once \n it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array of the\n transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], 1, fs.stat, function(err, results){\n // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n\n### filter(arr, iterator, callback)\n\n__Alias:__ select\n\nReturns a new array of all the values which pass an async truth test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(results) - A callback which is called after all the iterator\n functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n\n### filterSeries(arr, iterator, callback)\n\n__alias:__ selectSeries\n\nThe same as filter only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n---------------------------------------\n\n\n### reject(arr, iterator, callback)\n\nThe opposite of filter. Removes values that pass an async truth test.\n\n---------------------------------------\n\n\n### rejectSeries(arr, iterator, callback)\n\nThe same as reject, only the iterator is applied to each item in the array\nin series.\n\n\n---------------------------------------\n\n\n### reduce(arr, memo, iterator, callback)\n\n__aliases:__ inject, foldl\n\nReduces a list of values into a single value using an async iterator to return\neach successive step. Memo is the initial state of the reduction. This\nfunction only operates in series. For performance reasons, it may make sense to\nsplit a call to this function into a parallel map, then use the normal\nArray.prototype.reduce on the results. This function is for situations where\neach step in the reduction needs to be async, if you can get the data before\nreducing it then it's probably a good idea to do so.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* memo - The initial state of the reduction.\n* iterator(memo, item, callback) - A function applied to each item in the\n array to produce the next step in the reduction. The iterator is passed a\n callback(err, reduction) which accepts an optional error as its first \n argument, and the state of the reduction as the second. If an error is \n passed to the callback, the reduction is stopped and the main callback is \n immediately called with the error.\n* callback(err, result) - A callback which is called after all the iterator\n functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n // pointless async:\n process.nextTick(function(){\n callback(null, memo + item)\n });\n}, function(err, result){\n // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ foldr\n\nSame as reduce, only operates on the items in the array in reverse order.\n\n\n---------------------------------------\n\n\n### detect(arr, iterator, callback)\n\nReturns the first value in a list that passes an async truth test. The\niterator is applied in parallel, meaning the first iterator to return true will\nfire the detect callback with that result. That means the result might not be\nthe first item in the original array (in terms of order) that passes the test.\n\nIf order within the original array is important then look at detectSeries.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n true, or after all the iterator functions have finished. Result will be\n the first item in the array that passes the truth test (iterator) or the\n value undefined if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n\n### detectSeries(arr, iterator, callback)\n\nThe same as detect, only the iterator is applied to each item in the array\nin series. This means the result is always the first in the original array (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each value through an async iterator.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, sortValue) which must be called once it\n has completed with an error (which can be null) and a value to use as the sort\n criteria.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is the items from\n the original array sorted by the values returned by the iterator calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n fs.stat(file, function(err, stats){\n callback(err, stats.mtime);\n });\n}, function(err, results){\n // results is now the original array of files sorted by\n // modified date\n});\n```\n\n---------------------------------------\n\n\n### some(arr, iterator, callback)\n\n__Alias:__ any\n\nReturns true if at least one element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. Once any iterator\ncall returns true, the main callback is immediately called.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n true, or after all the iterator functions have finished. Result will be\n either true or false depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n\n### every(arr, iterator, callback)\n\n__Alias:__ all\n\nReturns true if every element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called after all the iterator\n functions have finished. Result will be either true or false depending on\n the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n\n### concat(arr, iterator, callback)\n\nApplies an iterator to each item in a list, concatenating the results. Returns the\nconcatenated list. The iterators are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of the arguments passed to the iterator function.\n\n__Arguments__\n\n* arr - An array to iterate over\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, results) which must be called once it \n has completed with an error (which can be null) and an array of results.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array containing\n the concatenated results of the iterator function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n\n### concatSeries(arr, iterator, callback)\n\nSame as async.concat, but executes in series instead of parallel.\n\n\n## Control Flow\n\n\n### series(tasks, [callback])\n\nRun an array of functions in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run and the callback for the series is\nimmediately called with the value of the error. Once the tasks have completed,\nthe results are passed to the final callback as an array.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.series.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed\n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.series([\n function(callback){\n // do some stuff ...\n callback(null, 'one');\n },\n function(callback){\n // do some more stuff ...\n callback(null, 'two');\n }\n],\n// optional callback\nfunction(err, results){\n // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n one: function(callback){\n setTimeout(function(){\n callback(null, 1);\n }, 200);\n },\n two: function(callback){\n setTimeout(function(){\n callback(null, 2);\n }, 100);\n }\n},\nfunction(err, results) {\n // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n\n### parallel(tasks, [callback])\n\nRun an array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main callback is immediately called with the value of the error.\nOnce the tasks have completed, the results are passed to the final callback as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.parallel.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n function(callback){\n setTimeout(function(){\n callback(null, 'one');\n }, 200);\n },\n function(callback){\n setTimeout(function(){\n callback(null, 'two');\n }, 100);\n }\n],\n// optional callback\nfunction(err, results){\n // the results array will equal ['one','two'] even though\n // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n one: function(callback){\n setTimeout(function(){\n callback(null, 1);\n }, 200);\n },\n two: function(callback){\n setTimeout(function(){\n callback(null, 2);\n }, 100);\n }\n},\nfunction(err, results) {\n // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n\n### parallelLimit(tasks, limit, [callback])\n\nThe same as parallel only the tasks are executed in parallel with a maximum of \"limit\" \ntasks executing at any time.\n\nNote that the tasks are not executed in batches, so there is no guarantee that \nthe first \"limit\" tasks will complete before any others are started.\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* limit - The maximum number of tasks to run at any time.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n---------------------------------------\n\n\n### whilst(test, fn, callback)\n\nRepeatedly call fn, while test returns true. Calls the callback when stopped,\nor an error occurs.\n\n__Arguments__\n\n* test() - synchronous truth test to perform before each execution of fn.\n* fn(callback) - A function to call each time the test passes. The function is\n passed a callback(err) which must be called once it has completed with an \n optional error argument.\n* callback(err) - A callback which is called after the test fails and repeated\n execution of fn has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n function () { return count < 5; },\n function (callback) {\n count++;\n setTimeout(callback, 1000);\n },\n function (err) {\n // 5 seconds have passed\n }\n);\n```\n\n---------------------------------------\n\n\n### doWhilst(fn, test, callback)\n\nThe post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n\n### until(test, fn, callback)\n\nRepeatedly call fn, until test returns true. Calls the callback when stopped,\nor an error occurs.\n\nThe inverse of async.whilst.\n\n---------------------------------------\n\n\n### doUntil(fn, test, callback)\n\nLike doWhilst except the test is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n\n### forever(fn, callback)\n\nCalls the asynchronous function 'fn' repeatedly, in series, indefinitely.\nIf an error is passed to fn's callback then 'callback' is called with the\nerror, otherwise it will never be called.\n\n---------------------------------------\n\n\n### waterfall(tasks, [callback])\n\nRuns an array of functions in series, each passing their results to the next in\nthe array. However, if any of the functions pass an error to the callback, the\nnext function is not executed and the main callback is immediately called with\nthe error.\n\n__Arguments__\n\n* tasks - An array of functions to run, each function is passed a \n callback(err, result1, result2, ...) it must call on completion. The first\n argument is an error (which can be null) and any further arguments will be \n passed as arguments in order to the next task.\n* callback(err, [results]) - An optional callback to run once all the functions\n have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n function(callback){\n callback(null, 'one', 'two');\n },\n function(arg1, arg2, callback){\n callback(null, 'three');\n },\n function(arg1, callback){\n // arg1 now equals 'three'\n callback(null, 'done');\n }\n], function (err, result) {\n // result now equals 'done' \n});\n```\n\n---------------------------------------\n\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions f(), g() and h() would produce the result of\nf(g(h())), only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n setTimeout(function () {\n callback(null, n + 1);\n }, 10);\n}\n\nfunction mul3(n, callback) {\n setTimeout(function () {\n callback(null, n * 3);\n }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n // result now equals 15\n});\n```\n\n---------------------------------------\n\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling the\ncallback after all functions have completed. If you only provide the first\nargument then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* fns - the asynchronous functions to all call with the same arguments\n* args... - any number of separate arguments to pass to the function\n* callback - the final argument should be the callback, called when all\n functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n buckets,\n async.applyEach([enableSearch, updateSchema]),\n callback\n);\n```\n\n---------------------------------------\n\n\n### applyEachSeries(arr, iterator, callback)\n\nThe same as applyEach only the functions are applied in series.\n\n---------------------------------------\n\n\n### queue(worker, concurrency)\n\nCreates a queue object with the specified concurrency. Tasks added to the\nqueue will be processed in parallel (up to the concurrency limit). If all\nworkers are in progress, the task is queued until one is available. Once\na worker has completed a task, the task's callback is called.\n\n__Arguments__\n\n* worker(task, callback) - An asynchronous function for processing a queued\n task, which must call its callback(err) argument when finished, with an \n optional error as an argument.\n* concurrency - An integer for determining how many worker functions should be\n run in parallel.\n\n__Queue objects__\n\nThe queue object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* concurrency - an integer for determining how many worker functions should be\n run in parallel. This property can be changed after a queue is created to\n alter the concurrency on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n once the worker has finished processing the task.\n instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* unshift(task, [callback]) - add a new task to the front of the queue.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n console.log('hello ' + task.name);\n callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n console.log('finished processing bar');\n});\n```\n\n---------------------------------------\n\n\n### cargo(worker, [payload])\n\nCreates a cargo object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the payload limit). If the\nworker is in progress, the task is queued until it is available. Once\nthe worker has completed some tasks, each callback of those tasks is called.\n\n__Arguments__\n\n* worker(tasks, callback) - An asynchronous function for processing an array of\n queued tasks, which must call its callback(err) argument when finished, with \n an optional error as an argument.\n* payload - An optional integer for determining how many tasks should be\n processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe cargo object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* payload - an integer for determining how many tasks should be\n process per round. This property can be changed after a cargo is created to\n alter the payload on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n once the worker has finished processing the task.\n instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n for(var i=0; i\n### auto(tasks, [callback])\n\nDetermines the best order for running functions based on their requirements.\nEach function can optionally depend on other functions being completed first,\nand each function is run as soon as its requirements are satisfied. If any of\nthe functions pass an error to their callback, that function will not complete\n(so any other functions depending on it will not run) and the main callback\nwill be called immediately with the error. Functions also receive an object\ncontaining the results of functions which have completed so far.\n\nNote, all functions are called with a results object as a second argument, \nso it is unsafe to pass functions in the tasks object which cannot handle the\nextra argument. For example, this snippet of code:\n\n```js\nasync.auto({\n readData: async.apply(fs.readFile, 'data.txt', 'utf-8');\n}, callback);\n```\n\nwill have the effect of calling readFile with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to readFile in a function which does not forward the \nresults object:\n\n```js\nasync.auto({\n readData: function(cb, results){\n fs.readFile('data.txt', 'utf-8', cb);\n }\n}, callback);\n```\n\n__Arguments__\n\n* tasks - An object literal containing named functions or an array of\n requirements, with the function itself the last item in the array. The key\n used for each function or array is used when specifying requirements. The \n function receives two arguments: (1) a callback(err, result) which must be \n called when finished, passing an error (which can be null) and the result of \n the function's execution, and (2) a results object, containing the results of\n the previously executed functions.\n* callback(err, results) - An optional callback which is called when all the\n tasks have been completed. The callback will receive an error as an argument\n if any tasks pass an error to their callback. Results will always be passed\n\tbut if an error occurred, no other tasks will be performed, and the results\n\tobject will only contain partial results.\n \n\n__Example__\n\n```js\nasync.auto({\n get_data: function(callback){\n // async code to get some data\n },\n make_folder: function(callback){\n // async code to create a directory to store a file in\n // this is run at the same time as getting the data\n },\n write_file: ['get_data', 'make_folder', function(callback){\n // once there is some data and the directory exists,\n // write the data to a file in the directory\n callback(null, filename);\n }],\n email_link: ['write_file', function(callback, results){\n // once the file is written let's email a link to it...\n // results.write_file contains the filename returned by write_file.\n }]\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n function(callback){\n // async code to get some data\n },\n function(callback){\n // async code to create a directory to store a file in\n // this is run at the same time as getting the data\n }\n],\nfunction(err, results){\n async.series([\n function(callback){\n // once there is some data and the directory exists,\n // write the data to a file in the directory\n },\n function(callback){\n // once the file is written let's email a link to it...\n }\n ]);\n});\n```\n\nFor a complicated series of async tasks using the auto function makes adding\nnew tasks much easier and makes the code more readable.\n\n\n---------------------------------------\n\n\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the array,\nreturning a continuation to call the next one after that. It's also possible to\n'peek' the next iterator by doing iterator.next().\n\nThis function is used internally by the async module but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* tasks - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n function(){ sys.p('one'); },\n function(){ sys.p('two'); },\n function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied, a useful\nshorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to automatically apply when the\n continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n async.apply(fs.writeFile, 'testfile1', 'test1'),\n async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n function(callback){\n fs.writeFile('testfile1', 'test1', callback);\n },\n function(callback){\n fs.writeFile('testfile2', 'test2', callback);\n }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n\n### nextTick(callback)\n\nCalls the callback on a later loop around the event loop. In node.js this just\ncalls process.nextTick, in the browser it falls back to setImmediate(callback)\nif available, otherwise setTimeout(callback, 0), which means other higher priority\nevents may precede the execution of the callback.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* callback - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n call_order.push('two');\n // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n\n### times(n, callback)\n\nCalls the callback n times and accumulates results in the same manner\nyou would use with async.map.\n\n__Arguments__\n\n* n - The number of times to run the function.\n* callback - The function to call n times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n callback(null, {\n id: 'user' + id\n })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n createUser(n, function(err, user) {\n next(err, user)\n })\n}, function(err, users) {\n // we should now have 5 users\n});\n```\n\n\n### timesSeries(n, callback)\n\nThe same as times only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n## Utils\n\n\n### memoize(fn, [hasher])\n\nCaches the results of an async function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* fn - the function you to proxy and cache results from.\n* hasher - an optional function for generating a custom hash for storing\n results, it has all the arguments applied to it apart from the callback, and\n must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n // do something\n callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n // callback\n});\n```\n\n\n### unmemoize(fn)\n\nUndoes a memoized function, reverting it to the original, unmemoized\nform. Comes handy in tests.\n\n__Arguments__\n\n* fn - the memoized function\n\n\n### log(function, arguments)\n\nLogs the result of an async function to the console. Only works in node.js or\nin browsers that support console.log and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.log is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n setTimeout(function(){\n callback(null, 'hello ' + name);\n }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n\n### dir(function, arguments)\n\nLogs the result of an async function to the console using console.dir to\ndisplay the properties of the resulting object. Only works in node.js or\nin browsers that support console.dir and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.dir is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n setTimeout(function(){\n callback(null, {hello: name});\n }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n\n### noConflict()\n\nChanges the value of async back to its original value, returning a reference to the\nasync object.\n", + "_id": "async@0.2.9", + "dist": { + "shasum": "0c284b6d60cdb1069d75c3db6108c8a7f0d64798" + }, + "_from": "async@~0.2.9" +} diff --git a/node_modules/jsftp/node_modules/event-stream/.npmignore b/node_modules/jsftp/node_modules/event-stream/.npmignore new file mode 100644 index 0000000..13abef4 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/.npmignore @@ -0,0 +1,3 @@ +node_modules +node_modules/* +npm_debug.log diff --git a/node_modules/jsftp/node_modules/event-stream/.travis.yml b/node_modules/jsftp/node_modules/event-stream/.travis.yml new file mode 100644 index 0000000..895dbd3 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 + - 0.8 diff --git a/node_modules/jsftp/node_modules/event-stream/LICENCE b/node_modules/jsftp/node_modules/event-stream/LICENCE new file mode 100644 index 0000000..171dd97 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/LICENCE @@ -0,0 +1,22 @@ +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/examples/pretty.js b/node_modules/jsftp/node_modules/event-stream/examples/pretty.js new file mode 100644 index 0000000..af04340 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/examples/pretty.js @@ -0,0 +1,25 @@ + +var inspect = require('util').inspect + +if(!module.parent) { + var es = require('..') //load event-stream + es.pipe( //pipe joins streams together + process.openStdin(), //open stdin + es.split(), //split stream to break on newlines + es.map(function (data, callback) {//turn this async function into a stream + var j + try { + j = JSON.parse(data) //try to parse input into json + } catch (err) { + return callback(null, data) //if it fails just pass it anyway + } + callback(null, inspect(j)) //render it nicely + }), + process.stdout // pipe it to stdout ! + ) + } + +// run this +// +// curl -sS registry.npmjs.org/event-stream | node pretty.js +// diff --git a/node_modules/jsftp/node_modules/event-stream/index.js b/node_modules/jsftp/node_modules/event-stream/index.js new file mode 100644 index 0000000..a7c05bb --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/index.js @@ -0,0 +1,302 @@ +//filter will reemit the data if cb(err,pass) pass is truthy + +// reduce is more tricky +// maybe we want to group the reductions or emit progress updates occasionally +// the most basic reduce just emits one 'data' event after it has recieved 'end' + +var Stream = require('stream').Stream + , es = exports + , through = require('through') + , from = require('from') + , duplex = require('duplexer') + , map = require('map-stream') + , pause = require('pause-stream') + , split = require('split') + , pipeline = require('stream-combiner') + +es.Stream = Stream //re-export Stream from core +es.through = through +es.from = from +es.duplex = duplex +es.map = map +es.pause = pause +es.split = split +es.pipeline = es.connect = es.pipe = pipeline +// merge / concat +// +// combine multiple streams into a single stream. +// will emit end only once + +es.concat = //actually this should be called concat +es.merge = function (/*streams...*/) { + var toMerge = [].slice.call(arguments) + var stream = new Stream() + var endCount = 0 + stream.writable = stream.readable = true + + toMerge.forEach(function (e) { + e.pipe(stream, {end: false}) + var ended = false + e.on('end', function () { + if(ended) return + ended = true + endCount ++ + if(endCount == toMerge.length) + stream.emit('end') + }) + }) + stream.write = function (data) { + this.emit('data', data) + } + stream.destroy = function () { + merge.forEach(function (e) { + if(e.destroy) e.destroy() + }) + } + return stream +} + + +// writable stream, collects all events into an array +// and calls back when 'end' occurs +// mainly I'm using this to test the other functions + +es.writeArray = function (done) { + if ('function' !== typeof done) + throw new Error('function writeArray (done): done must be function') + + var a = new Stream () + , array = [], isDone = false + a.write = function (l) { + array.push(l) + } + a.end = function () { + isDone = true + done(null, array) + } + a.writable = true + a.readable = false + a.destroy = function () { + a.writable = a.readable = false + if(isDone) return + done(new Error('destroyed before end'), array) + } + return a +} + +//return a Stream that reads the properties of an object +//respecting pause() and resume() + +es.readArray = function (array) { + var stream = new Stream() + , i = 0 + , paused = false + , ended = false + + stream.readable = true + stream.writable = false + + if(!Array.isArray(array)) + throw new Error('event-stream.read expects an array') + + stream.resume = function () { + if(ended) return + paused = false + var l = array.length + while(i < l && !paused && !ended) { + stream.emit('data', array[i++]) + } + if(i == l && !ended) + ended = true, stream.readable = false, stream.emit('end') + } + process.nextTick(stream.resume) + stream.pause = function () { + paused = true + } + stream.destroy = function () { + ended = true + stream.emit('close') + } + return stream +} + +// +// readable (asyncFunction) +// return a stream that calls an async function while the stream is not paused. +// +// the function must take: (count, callback) {... +// + +es.readable = +function (func, continueOnError) { + var stream = new Stream() + , i = 0 + , paused = false + , ended = false + , reading = false + + stream.readable = true + stream.writable = false + + if('function' !== typeof func) + throw new Error('event-stream.readable expects async function') + + stream.on('end', function () { ended = true }) + + function get (err, data) { + + if(err) { + stream.emit('error', err) + if(!continueOnError) stream.emit('end') + } else if (arguments.length > 1) + stream.emit('data', data) + + process.nextTick(function () { + if(ended || paused || reading) return + try { + reading = true + func.call(stream, i++, function () { + reading = false + get.apply(null, arguments) + }) + } catch (err) { + stream.emit('error', err) + } + }) + } + stream.resume = function () { + paused = false + get() + } + process.nextTick(get) + stream.pause = function () { + paused = true + } + stream.destroy = function () { + stream.emit('end') + stream.emit('close') + ended = true + } + return stream +} + + +// +// map sync +// + +es.mapSync = function (sync) { + return es.through(function write(data) { + var mappedData = sync(data) + if (typeof mappedData !== 'undefined') + this.emit('data', mappedData) + }) +} + +// +// log just print out what is coming through the stream, for debugging +// + +es.log = function (name) { + return es.through(function (data) { + var args = [].slice.call(arguments) + if(name) console.error(name, data) + else console.error(data) + this.emit('data', data) + }) +} + + +// +// child -- pipe through a child process +// + +es.child = function (child) { + + return es.duplex(child.stdin, child.stdout) + +} + +// +// parse +// +// must be used after es.split() to ensure that each chunk represents a line +// source.pipe(es.split()).pipe(es.parse()) + +es.parse = function () { + return es.through(function (data) { + var obj + try { + if(data) //ignore empty lines + obj = JSON.parse(data.toString()) + } catch (err) { + return console.error(err, 'attemping to parse:', data) + } + //ignore lines that where only whitespace. + if(obj !== undefined) + this.emit('data', obj) + }) +} +// +// stringify +// + +es.stringify = function () { + var Buffer = require('buffer').Buffer + return es.mapSync(function (e){ + return JSON.stringify(Buffer.isBuffer(e) ? e.toString() : e) + '\n' + }) +} + +// +// replace a string within a stream. +// +// warn: just concatenates the string and then does str.split().join(). +// probably not optimal. +// for smallish responses, who cares? +// I need this for shadow-npm so it's only relatively small json files. + +es.replace = function (from, to) { + return es.pipeline(es.split(from), es.join(to)) +} + +// +// join chunks with a joiner. just like Array#join +// also accepts a callback that is passed the chunks appended together +// this is still supported for legacy reasons. +// + +es.join = function (str) { + + //legacy api + if('function' === typeof str) + return es.wait(str) + + var first = true + return es.through(function (data) { + if(!first) + this.emit('data', str) + first = false + this.emit('data', data) + return true + }) +} + + +// +// wait. callback when 'end' is emitted, with all chunks appended as string. +// + +es.wait = function (callback) { + var body = '' + return es.through(function (data) { body += data }, + function () { + this.emit('data', body) + this.emit('end') + if(callback) callback(null, body) + }) +} + +es.pipeable = function () { + throw new Error('[EVENT-STREAM] es.pipeable is deprecated') +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.npmignore new file mode 100644 index 0000000..062c11e --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.npmignore @@ -0,0 +1,3 @@ +node_modules +*.log +*.err \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.travis.yml b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.travis.yml new file mode 100644 index 0000000..c2ba3f9 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 0.8 \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/LICENCE b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/LICENCE new file mode 100644 index 0000000..a23e08a --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/LICENCE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Raynos. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/Makefile b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/Makefile new file mode 100644 index 0000000..1f8985d --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/Makefile @@ -0,0 +1,4 @@ +test: + node test.js + +.PHONY: test \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/README.md b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/README.md new file mode 100644 index 0000000..a24cecb --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/README.md @@ -0,0 +1,36 @@ +# duplexer [![build status][1]][2] + +Creates a duplex stream + +Taken from [event-stream][3] + +## duplex (writeStream, readStream) + +Takes a writable stream and a readable stream and makes them appear as a readable writable stream. + +It is assumed that the two streams are connected to each other in some way. + +## Example + + var grep = cp.exec('grep Stream') + + duplex(grep.stdin, grep.stdout) + +## Installation + +`npm install duplexer` + +## Tests + +`make test` + +## Contributors + + - Dominictarr + - Raynos + +## MIT Licenced + + [1]: https://secure.travis-ci.org/Raynos/duplexer.png + [2]: http://travis-ci.org/Raynos/duplexer + [3]: https://github.com/dominictarr/event-stream#duplex-writestream-readstream \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/index.js new file mode 100644 index 0000000..22e0d51 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/index.js @@ -0,0 +1,77 @@ +var Stream = require("stream") + , writeMethods = ["write", "end", "destroy"] + , readMethods = ["resume", "pause"] + , readEvents = ["data", "close"] + , slice = Array.prototype.slice + +module.exports = duplex + +function duplex(writer, reader) { + var stream = new Stream() + , ended = false + + writeMethods.forEach(proxyWriter) + + readMethods.forEach(proxyReader) + + readEvents.forEach(proxyStream) + + reader.on("end", handleEnd) + + writer.on("drain", function() { + stream.emit("drain") + }) + + writer.on("error", reemit) + reader.on("error", reemit) + + stream.writable = writer.writable + stream.readable = reader.readable + + return stream + + function proxyWriter(methodName) { + stream[methodName] = method + + function method() { + return writer[methodName].apply(writer, arguments) + } + } + + function proxyReader(methodName) { + stream[methodName] = method + + function method() { + stream.emit(methodName) + var func = reader[methodName] + if (func) { + return func.apply(reader, arguments) + } + reader.emit(methodName) + } + } + + function proxyStream(methodName) { + reader.on(methodName, reemit) + + function reemit() { + var args = slice.call(arguments) + args.unshift(methodName) + stream.emit.apply(stream, args) + } + } + + function handleEnd() { + if (ended) { + return + } + ended = true + var args = slice.call(arguments) + args.unshift("end") + stream.emit.apply(stream, args) + } + + function reemit(err) { + stream.emit("error", err) + } +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/package.json new file mode 100644 index 0000000..b0cb5e3 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/package.json @@ -0,0 +1,44 @@ +{ + "name": "duplexer", + "version": "0.0.4", + "description": "Creates a duplex stream", + "keywords": [], + "author": { + "name": "Raynos", + "email": "raynos2@gmail.com" + }, + "repository": { + "type": "git", + "url": "git://github.com/Raynos/duplexer.git" + }, + "main": "index", + "homepage": "https://github.com/Raynos/duplexer", + "contributors": [ + { + "name": "Jake Verbaten" + } + ], + "bugs": { + "url": "https://github.com/Raynos/duplexer/issues", + "email": "raynos2@gmail.com" + }, + "dependencies": {}, + "devDependencies": { + "through": "~0.1.4" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://github.com/Raynos/duplexer/raw/master/LICENSE" + } + ], + "scripts": { + "test": "make test" + }, + "readme": "# duplexer [![build status][1]][2]\n\nCreates a duplex stream\n\nTaken from [event-stream][3]\n\n## duplex (writeStream, readStream)\n\nTakes a writable stream and a readable stream and makes them appear as a readable writable stream.\n\nIt is assumed that the two streams are connected to each other in some way.\n\n## Example\n\n var grep = cp.exec('grep Stream')\n\n duplex(grep.stdin, grep.stdout)\n\n## Installation\n\n`npm install duplexer`\n\n## Tests\n\n`make test`\n\n## Contributors\n\n - Dominictarr\n - Raynos\n\n## MIT Licenced\n\n [1]: https://secure.travis-ci.org/Raynos/duplexer.png\n [2]: http://travis-ci.org/Raynos/duplexer\n [3]: https://github.com/dominictarr/event-stream#duplex-writestream-readstream", + "_id": "duplexer@0.0.4", + "dist": { + "shasum": "49e7655791cdb887f4ce1616756bbb5f65d96157" + }, + "_from": "duplexer@~0.0.2" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/test.js b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/test.js new file mode 100644 index 0000000..e06a864 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/duplexer/test.js @@ -0,0 +1,27 @@ +var duplex = require("./index") + , assert = require("assert") + , through = require("through") + +var readable = through() + , writable = through(write) + , written = 0 + , data = 0 + +var stream = duplex(writable, readable) + +function write() { + written++ +} + +stream.on("data", ondata) + +function ondata() { + data++ +} + +stream.write() +readable.emit("data") + +assert.equal(written, 1) +assert.equal(data, 1) +console.log("DONE") \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/from/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.APACHE2 b/node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.APACHE2 new file mode 100644 index 0000000..6366c04 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.APACHE2 @@ -0,0 +1,15 @@ +Apache License, Version 2.0 + +Copyright (c) 2011 Dominic Tarr + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.MIT b/node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.MIT new file mode 100644 index 0000000..6eafbd7 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/LICENSE.MIT @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/from/index.js new file mode 100644 index 0000000..bba40da --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/index.js @@ -0,0 +1,68 @@ + +'use strict'; + +var Stream = require('stream') + +// from +// +// a stream that reads from an source. +// source may be an array, or a function. +// from handles pause behaviour for you. + +module.exports = +function from (source) { + if(Array.isArray(source)) { + source = source.slice() + return from (function (i) { + if(source.length) + this.emit('data', source.shift()) + else + this.emit('end') + return true + }) + } + var s = new Stream(), i = 0 + s.ended = false + s.started = false + s.readable = true + s.writable = false + s.paused = false + s.ended = false + s.pause = function () { + s.started = true + s.paused = true + } + function next () { + s.started = true + if(s.ended) return + while(!s.ended && !s.paused && source.call(s, i++, function () { + if(!s.ended && !s.paused) + next() + })) + ; + } + s.resume = function () { + s.started = true + s.paused = false + next() + } + s.on('end', function () { + s.ended = true + s.readable = false + process.nextTick(s.destroy) + }) + s.destroy = function () { + s.ended = true + s.emit('close') + } + /* + by default, the stream will start emitting at nextTick + if you want, you can pause it, after pipeing. + you can also resume before next tick, and that will also + work. + */ + process.nextTick(function () { + if(!s.started) s.resume() + }) + return s +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/from/package.json new file mode 100644 index 0000000..114327f --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/package.json @@ -0,0 +1,36 @@ +{ + "name": "from", + "version": "0.1.3", + "description": "Easy way to make a Readable Stream", + "main": "index.js", + "scripts": { + "test": "asynct test/*.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/dominictarr/from.git" + }, + "keywords": [ + "stream", + "streams", + "readable", + "easy" + ], + "devDependencies": { + "asynct": "1", + "stream-spec": "0", + "assertions": "~2.3.0" + }, + "author": { + "name": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "dominictarr.com" + }, + "license": "MIT", + "readme": "# from\n\nAn easy way to create a `readable Stream`.\n\n## from(function getChunk(count, next))\n\nfrom takes a `getChunk` function and returns a stream. \n\n`getChunk` is called again and again, after each time the user calls `next()`, \nuntil the user emits `'end'`\n\nif `pause()` is called, the `getChunk` won't be called again untill `resume()` is called.\n\n\n```js\nvar from = require('from')\n\nvar stream = \n from(function getChunk(count, next) {\n //do some sort of data\n this.emit('data', whatever)\n \n if(itsOver)\n this.emit('end')\n\n //ready to handle the next chunk\n next()\n //or, if it's sync:\n return true \n })\n```\n\n## from(array)\n\nfrom also takes an `Array` whose elements it emits one after another.\n\n## License\nMIT / Apache2\n", + "_id": "from@0.1.3", + "dist": { + "shasum": "0ac5c9c9018c3f249f72e92396f3e5f25aa6fdbd" + }, + "_from": "from@~0" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/readme.markdown b/node_modules/jsftp/node_modules/event-stream/node_modules/from/readme.markdown new file mode 100644 index 0000000..a619163 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/readme.markdown @@ -0,0 +1,38 @@ +# from + +An easy way to create a `readable Stream`. + +## from(function getChunk(count, next)) + +from takes a `getChunk` function and returns a stream. + +`getChunk` is called again and again, after each time the user calls `next()`, +until the user emits `'end'` + +if `pause()` is called, the `getChunk` won't be called again untill `resume()` is called. + + +```js +var from = require('from') + +var stream = + from(function getChunk(count, next) { + //do some sort of data + this.emit('data', whatever) + + if(itsOver) + this.emit('end') + + //ready to handle the next chunk + next() + //or, if it's sync: + return true + }) +``` + +## from(array) + +from also takes an `Array` whose elements it emits one after another. + +## License +MIT / Apache2 diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/from/test/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/from/test/index.js new file mode 100644 index 0000000..fafea57 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/from/test/index.js @@ -0,0 +1,141 @@ +var from = require('..') +var spec = require('stream-spec') +var a = require('assertions') + +function read(stream, callback) { + var actual = [] + stream.on('data', function (data) { + actual.push(data) + }) + stream.once('end', function () { + callback(null, actual) + }) + stream.once('error', function (err) { + callback(err) + }) +} + +function pause(stream) { + stream.on('data', function () { + if(Math.random() > 0.1) return + stream.pause() + process.nextTick(function () { + stream.resume() + }) + }) +} + +exports['inc'] = function (test) { + + var fs = from(function (i) { + this.emit('data', i) + if(i >= 99) + return this.emit('end') + return true + }) + + spec(fs).readable().validateOnExit() + + read(fs, function (err, arr) { + test.equal(arr.length, 100) + test.done() + }) +} + +exports['simple'] = function (test) { + + var l = 1000 + , expected = [] + + while(l--) expected.push(l * Math.random()) + + var t = from(expected.slice()) + + spec(t) + .readable() + .pausable({strict: true}) + .validateOnExit() + + read(t, function (err, actual) { + if(err) test.error(err) //fail + a.deepEqual(actual, expected) + test.done() + }) + +} + +exports['simple pausable'] = function (test) { + + var l = 1000 + , expected = [] + + while(l--) expected.push(l * Math.random()) + + var t = from(expected.slice()) + + spec(t) + .readable() + .pausable({strict: true}) + .validateOnExit() + + pause(t) + + read(t, function (err, actual) { + if(err) test.error(err) //fail + a.deepEqual(actual, expected) + test.done() + }) + +} + +exports['simple (not strictly pausable) setTimeout'] = function (test) { + + var l = 10 + , expected = [] + while(l--) expected.push(l * Math.random()) + + + var _expected = expected.slice() + var t = from(function (i, n) { + var self = this + setTimeout(function () { + if(_expected.length) + self.emit('data', _expected.shift()) + else + self.emit('end') + n() + }, 3) + }) + + /* + using from in this way will not be strictly pausable. + it could be extended to buffer outputs, but I think a better + way would be to use a PauseStream that implements strict pause. + */ + + spec(t) + .readable() + .pausable({strict: false }) + .validateOnExit() + + //pause(t) + var paused = false + var i = setInterval(function () { + if(!paused) t.pause() + else t.resume() + paused = !paused + }, 2) + + t.on('end', function () { + clearInterval(i) + }) + + read(t, function (err, actual) { + if(err) test.error(err) //fail + a.deepEqual(actual, expected) + test.done() + }) + +} + + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.npmignore new file mode 100644 index 0000000..13abef4 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.npmignore @@ -0,0 +1,3 @@ +node_modules +node_modules/* +npm_debug.log diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.travis.yml b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.travis.yml new file mode 100644 index 0000000..895dbd3 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 + - 0.8 diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/LICENCE b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/LICENCE new file mode 100644 index 0000000..171dd97 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/LICENCE @@ -0,0 +1,22 @@ +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/examples/pretty.js b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/examples/pretty.js new file mode 100644 index 0000000..ab07398 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/examples/pretty.js @@ -0,0 +1,26 @@ + +var inspect = require('util').inspect + +if(!module.parent) { + var map = require('..') //load map-stream + var es = require('event-stream') //load event-stream + es.pipe( //pipe joins streams together + process.openStdin(), //open stdin + es.split(), //split stream to break on newlines + map(function (data, callback) { //turn this async function into a stream + var j + try { + j = JSON.parse(data) //try to parse input into json + } catch (err) { + return callback(null, data) //if it fails just pass it anyway + } + callback(null, inspect(j)) //render it nicely + }), + process.stdout // pipe it to stdout ! + ) + } + +// run this +// +// curl -sS registry.npmjs.org/event-stream | node pretty.js +// diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/index.js new file mode 100644 index 0000000..d3a7fd9 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/index.js @@ -0,0 +1,105 @@ +//filter will reemit the data if cb(err,pass) pass is truthy + +// reduce is more tricky +// maybe we want to group the reductions or emit progress updates occasionally +// the most basic reduce just emits one 'data' event after it has recieved 'end' + + +var Stream = require('stream').Stream + + +//create an event stream and apply function to each .write +//emitting each response as data +//unless it's an empty callback + +module.exports = function (mapper) { + var stream = new Stream() + , inputs = 0 + , outputs = 0 + , ended = false + , paused = false + , destroyed = false + + stream.writable = true + stream.readable = true + + stream.write = function () { + if(ended) throw new Error('map stream is not writable') + inputs ++ + var args = [].slice.call(arguments) + , r + , inNext = false + //pipe only allows one argument. so, do not + function next (err) { + if(destroyed) return + inNext = true + outputs ++ + var args = [].slice.call(arguments) + if(err) { + args.unshift('error') + return inNext = false, stream.emit.apply(stream, args) + } + args.shift() //drop err + if (args.length) { + args.unshift('data') + r = stream.emit.apply(stream, args) + } + if(inputs == outputs) { + if(paused) paused = false, stream.emit('drain') //written all the incoming events + if(ended) end() + } + inNext = false + } + args.push(next) + + try { + //catch sync errors and handle them like async errors + var written = mapper.apply(null, args) + paused = (written === false) + return !paused + } catch (err) { + //if the callback has been called syncronously, and the error + //has occured in an listener, throw it again. + if(inNext) + throw err + next(err) + return !paused + } + } + + function end (data) { + //if end was called with args, write it, + ended = true //write will emit 'end' if ended is true + stream.writable = false + if(data !== undefined) + return stream.write(data) + else if (inputs == outputs) //wait for processing + stream.readable = false, stream.emit('end'), stream.destroy() + } + + stream.end = function (data) { + if(ended) return + end() + } + + stream.destroy = function () { + ended = destroyed = true + stream.writable = stream.readable = paused = false + process.nextTick(function () { + stream.emit('close') + }) + } + stream.pause = function () { + paused = true + } + + stream.resume = function () { + paused = false + } + + return stream +} + + + + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/package.json new file mode 100644 index 0000000..90bdd63 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/package.json @@ -0,0 +1,33 @@ +{ + "name": "map-stream", + "version": "0.0.2", + "description": "construct pipes of streams of events", + "homepage": "http://github.com/dominictarr/map-stream", + "repository": { + "type": "git", + "url": "git://github.com/dominictarr/map-stream.git" + }, + "dependencies": {}, + "devDependencies": { + "asynct": "*", + "it-is": "1", + "ubelt": "~2.9", + "stream-spec": "~0.2", + "event-stream": "~2.1", + "from": "0.0.2" + }, + "scripts": { + "test": "asynct test/" + }, + "author": { + "name": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "http://dominictarr.com" + }, + "readme": "# MapStream\n\nRefactored out of [event-stream](https://github.com/dominictarr/event-stream)\n\n##map (asyncFunction)\n\nCreate a through stream from an asyncronous function. \n\n``` js\nvar map = require('map-stream')\n\nmap(function (data, callback) {\n //transform data\n // ...\n callback(null, data)\n})\n\n```\n\nEach map MUST call the callback. It may callback with data, with an error or with no arguments, \n\n * `callback()` drop this data. \n this makes the map work like `filter`, \n note:`callback(null,null)` is not the same, and will emit `null`\n\n * `callback(null, newData)` turn data into newData\n \n * `callback(error)` emit an error for this item.\n\n>Note: if a callback is not called, `map` will think that it is still being processed, \n>every call must be answered or the stream will not know when to end. \n>\n>Also, if the callback is called more than once, every call but the first will be ignored.\n\n\n", + "_id": "map-stream@0.0.2", + "dist": { + "shasum": "8c7fc337e11ea95e1e6edc5b18b7e4a2944e2f0f" + }, + "_from": "map-stream@0.0.2" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/readme.markdown b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/readme.markdown new file mode 100644 index 0000000..7e54504 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/readme.markdown @@ -0,0 +1,35 @@ +# MapStream + +Refactored out of [event-stream](https://github.com/dominictarr/event-stream) + +##map (asyncFunction) + +Create a through stream from an asyncronous function. + +``` js +var map = require('map-stream') + +map(function (data, callback) { + //transform data + // ... + callback(null, data) +}) + +``` + +Each map MUST call the callback. It may callback with data, with an error or with no arguments, + + * `callback()` drop this data. + this makes the map work like `filter`, + note:`callback(null,null)` is not the same, and will emit `null` + + * `callback(null, newData)` turn data into newData + + * `callback(error)` emit an error for this item. + +>Note: if a callback is not called, `map` will think that it is still being processed, +>every call must be answered or the stream will not know when to end. +> +>Also, if the callback is called more than once, every call but the first will be ignored. + + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/test/simple-map.asynct.js b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/test/simple-map.asynct.js new file mode 100644 index 0000000..b04d953 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/map-stream/test/simple-map.asynct.js @@ -0,0 +1,270 @@ +'use strict'; + +var map = require('../') + , it = require('it-is') + , u = require('ubelt') + , spec = require('stream-spec') + , from = require('from') + , Stream = require('stream') + , es = require('event-stream') + +//REFACTOR THIS TEST TO USE es.readArray and es.writeArray + +function writeArray(array, stream) { + + array.forEach( function (j) { + stream.write(j) + }) + stream.end() + +} + +function readStream(stream, done) { + + var array = [] + stream.on('data', function (data) { + array.push(data) + }) + stream.on('error', done) + stream.on('end', function (data) { + done(null, array) + }) + +} + +//call sink on each write, +//and complete when finished. + +function pauseStream (prob, delay) { + var pauseIf = ( + 'number' == typeof prob + ? function () { + return Math.random() < prob + } + : 'function' == typeof prob + ? prob + : 0.1 + ) + var delayer = ( + !delay + ? process.nextTick + : 'number' == typeof delay + ? function (next) { setTimeout(next, delay) } + : delay + ) + + return es.through(function (data) { + if(!this.paused && pauseIf()) { + console.log('PAUSE STREAM PAUSING') + this.pause() + var self = this + delayer(function () { + console.log('PAUSE STREAM RESUMING') + self.resume() + }) + } + console.log("emit ('data', " + data + ')') + this.emit('data', data) + }) +} + +exports ['simple map applied to a stream'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + //create event stream from + + var doubler = map(function (data, cb) { + cb(null, data * 2) + }) + + spec(doubler).through().validateOnExit() + + //a map is only a middle man, so it is both readable and writable + + it(doubler).has({ + readable: true, + writable: true, + }) + + readStream(doubler, function (err, output) { + it(output).deepEqual(input.map(function (j) { + return j * 2 + })) +// process.nextTick(x.validate) + test.done() + }) + + writeArray(input, doubler) + +} + +exports['pipe two maps together'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + //create event stream from + function dd (data, cb) { + cb(null, data * 2) + } + var doubler1 = map(dd), doubler2 = map(dd) + + doubler1.pipe(doubler2) + + spec(doubler1).through().validateOnExit() + spec(doubler2).through().validateOnExit() + + readStream(doubler2, function (err, output) { + it(output).deepEqual(input.map(function (j) { + return j * 4 + })) + test.done() + }) + + writeArray(input, doubler1) + +} + +//next: +// +// test pause, resume and drian. +// + +// then make a pipe joiner: +// +// plumber (evStr1, evStr2, evStr3, evStr4, evStr5) +// +// will return a single stream that write goes to the first + +exports ['map will not call end until the callback'] = function (test) { + + var ticker = map(function (data, cb) { + process.nextTick(function () { + cb(null, data * 2) + }) + }) + + spec(ticker).through().validateOnExit() + + ticker.write('x') + ticker.end() + + ticker.on('end', function () { + test.done() + }) +} + + +exports ['emit error thrown'] = function (test) { + + var err = new Error('INTENSIONAL ERROR') + , mapper = + map(function () { + throw err + }) + + mapper.on('error', function (_err) { + it(_err).equal(err) + test.done() + }) + + mapper.write('hello') + +} + +exports ['emit error calledback'] = function (test) { + + var err = new Error('INTENSIONAL ERROR') + , mapper = + map(function (data, callback) { + callback(err) + }) + + mapper.on('error', function (_err) { + it(_err).equal(err) + test.done() + }) + + mapper.write('hello') + +} + +exports ['do not emit drain if not paused'] = function (test) { + + var maps = map(function (data, callback) { + u.delay(callback)(null, 1) + return true + }) + + spec(maps).through().pausable().validateOnExit() + + maps.on('drain', function () { + it(false).ok('should not emit drain unless the stream is paused') + }) + + it(maps.write('hello')).equal(true) + it(maps.write('hello')).equal(true) + it(maps.write('hello')).equal(true) + setTimeout(function () {maps.end()},10) + maps.on('end', test.done) +} + +exports ['emits drain if paused, when all '] = function (test) { + var active = 0 + var drained = false + var maps = map(function (data, callback) { + active ++ + u.delay(function () { + active -- + callback(null, 1) + })() + console.log('WRITE', false) + return false + }) + + spec(maps).through().validateOnExit() + + maps.on('drain', function () { + drained = true + it(active).equal(0, 'should emit drain when all maps are done') + }) + + it(maps.write('hello')).equal(false) + it(maps.write('hello')).equal(false) + it(maps.write('hello')).equal(false) + + process.nextTick(function () {maps.end()},10) + + maps.on('end', function () { + console.log('end') + it(drained).ok('shoud have emitted drain before end') + test.done() + }) + +} + +exports ['map applied to a stream with filtering'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + + var doubler = map(function (data, callback) { + if (data % 2) + callback(null, data * 2) + else + callback() + }) + + readStream(doubler, function (err, output) { + it(output).deepEqual(input.filter(function (j) { + return j % 2 + }).map(function (j) { + return j * 2 + })) + test.done() + }) + + spec(doubler).through().validateOnExit() + + writeArray(input, doubler) + +} + + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/.npmignore new file mode 100644 index 0000000..13abef4 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/.npmignore @@ -0,0 +1,3 @@ +node_modules +node_modules/* +npm_debug.log diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/index.js new file mode 100644 index 0000000..0e0bf96 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/index.js @@ -0,0 +1,3 @@ +//through@2 handles this by default! +module.exports = require('through') + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/package.json new file mode 100644 index 0000000..cc11408 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/package.json @@ -0,0 +1,45 @@ +{ + "name": "pause-stream", + "version": "0.0.10", + "description": "a ThroughStream that strictly buffers all readable events when paused.", + "main": "index.js", + "directories": { + "test": "test" + }, + "devDependencies": { + "stream-tester": "0.0.2", + "stream-spec": "~0.2.0" + }, + "scripts": { + "test": "node test/index.js && node test/pause-end.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/dominictarr/pause-stream.git" + }, + "keywords": [ + "stream", + "pipe", + "pause", + "drain", + "buffer" + ], + "author": { + "name": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "dominictarr.com" + }, + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + }, + "readme": "# PauseStream\n\nThis is a `Stream` that will strictly buffer when paused.\nConnect it to anything you need buffered.\n\n``` js\n var ps = require('pause-stream')();\n\n badlyBehavedStream.pipe(ps.pause())\n\n aLittleLater(function (err, data) {\n ps.pipe(createAnotherStream(data))\n ps.resume()\n })\n```\n\n`PauseStream` will buffer whenever paused.\nit will buffer when yau have called `pause` manually.\nbut also when it's downstream `dest.write()===false`.\nit will attempt to drain the buffer when you call resume\nor the downstream emits `'drain'`\n\n`PauseStream` is tested using [stream-spec](https://github.com/dominictarr/stream-spec)\nand [stream-tester](https://github.com/dominictarr/stream-tester)\n\nThis is now the default case of \n[through](https://github.com/dominictarr/through)\n\nhttps://github.com/dominictarr/pause-stream/commit/4a6fe3dc2c11091b1efbfde912e0473719ed9cc0\n", + "_id": "pause-stream@0.0.10", + "dist": { + "shasum": "0b28365a705bb18fecd89d4722e11275c109eb66" + }, + "_from": "pause-stream@0.0.10" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/readme.markdown b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/readme.markdown new file mode 100644 index 0000000..2366939 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/readme.markdown @@ -0,0 +1,29 @@ +# PauseStream + +This is a `Stream` that will strictly buffer when paused. +Connect it to anything you need buffered. + +``` js + var ps = require('pause-stream')(); + + badlyBehavedStream.pipe(ps.pause()) + + aLittleLater(function (err, data) { + ps.pipe(createAnotherStream(data)) + ps.resume() + }) +``` + +`PauseStream` will buffer whenever paused. +it will buffer when yau have called `pause` manually. +but also when it's downstream `dest.write()===false`. +it will attempt to drain the buffer when you call resume +or the downstream emits `'drain'` + +`PauseStream` is tested using [stream-spec](https://github.com/dominictarr/stream-spec) +and [stream-tester](https://github.com/dominictarr/stream-tester) + +This is now the default case of +[through](https://github.com/dominictarr/through) + +https://github.com/dominictarr/pause-stream/commit/4a6fe3dc2c11091b1efbfde912e0473719ed9cc0 diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/index.js new file mode 100644 index 0000000..db8778d --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/index.js @@ -0,0 +1,17 @@ +var spec = require('stream-spec') +var tester = require('stream-tester') +var ps = require('..')() + +spec(ps) + .through({strict: false}) + .validateOnExit() + +var master = tester.createConsistent + +tester.createRandomStream(1000) //1k random numbers + .pipe(master = tester.createConsistentStream()) + .pipe(tester.createUnpauseStream()) + .pipe(ps) + .pipe(tester.createPauseStream()) + .pipe(master.createSlave()) + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/pause-end.js b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/pause-end.js new file mode 100644 index 0000000..a6c27ef --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/pause-stream/test/pause-end.js @@ -0,0 +1,33 @@ + +var pause = require('..') +var assert = require('assert') + +var ps = pause() +var read = [], ended = false + +ps.on('data', function (i) { + read.push(i) +}) + +ps.on('end', function () { + ended = true +}) + +assert.deepEqual(read, []) + +ps.write(0) +ps.write(1) +ps.write(2) + +assert.deepEqual(read, [0, 1, 2]) + +ps.pause() + +assert.deepEqual(read, [0, 1, 2]) + +ps.end() +assert.equal(ended, false) +ps.resume() +assert.equal(ended, true) + + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/split/.npmignore new file mode 100644 index 0000000..13abef4 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/.npmignore @@ -0,0 +1,3 @@ +node_modules +node_modules/* +npm_debug.log diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/.travis.yml b/node_modules/jsftp/node_modules/event-stream/node_modules/split/.travis.yml new file mode 100644 index 0000000..dad2273 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.8 + - "0.10" diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/LICENCE b/node_modules/jsftp/node_modules/event-stream/node_modules/split/LICENCE new file mode 100644 index 0000000..171dd97 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/LICENCE @@ -0,0 +1,22 @@ +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/examples/pretty.js b/node_modules/jsftp/node_modules/event-stream/node_modules/split/examples/pretty.js new file mode 100644 index 0000000..2e89131 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/examples/pretty.js @@ -0,0 +1,26 @@ + +var inspect = require('util').inspect +var es = require('event-stream') //load event-stream +var split = require('../') + +if(!module.parent) { + es.pipe( //pipe joins streams together + process.openStdin(), //open stdin + split(), //split stream to break on newlines + es.map(function (data, callback) {//turn this async function into a stream + var j + try { + j = JSON.parse(data) //try to parse input into json + } catch (err) { + return callback(null, data) //if it fails just pass it anyway + } + callback(null, inspect(j)) //render it nicely + }), + process.stdout // pipe it to stdout ! + ) + } + +// run this +// +// curl -sS registry.npmjs.org/event-stream | node pretty.js +// diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/split/index.js new file mode 100644 index 0000000..ca57e0f --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/index.js @@ -0,0 +1,59 @@ +//filter will reemit the data if cb(err,pass) pass is truthy + +// reduce is more tricky +// maybe we want to group the reductions or emit progress updates occasionally +// the most basic reduce just emits one 'data' event after it has recieved 'end' + + +var through = require('through') +var Decoder = require('string_decoder').StringDecoder + +module.exports = split + +//TODO pass in a function to map across the lines. + +function split (matcher, mapper) { + var decoder = new Decoder() + var soFar = '' + if('function' === typeof matcher) + mapper = matcher, matcher = null + if (!matcher) + matcher = /\r?\n/ + + function emit(stream, piece) { + if(mapper) { + try { + piece = mapper(piece) + } + catch (err) { + return stream.emit('error', err) + } + if('undefined' !== typeof piece) + stream.queue(piece) + } + else + stream.queue(piece) + } + + function next (stream, buffer) { + var pieces = (soFar + buffer).split(matcher) + soFar = pieces.pop() + + for (var i = 0; i < pieces.length; i++) { + var piece = pieces[i] + emit(stream, piece) + } + } + + return through(function (b) { + next(this, decoder.write(b)) + }, + function () { + if(decoder.end) + next(this, decoder.end()) + if(soFar != null) + emit(this, soFar) + this.queue(null) + }) +} + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/split/package.json new file mode 100644 index 0000000..55a77ee --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/package.json @@ -0,0 +1,38 @@ +{ + "name": "split", + "version": "0.2.10", + "description": "split a Text Stream into a Line Stream", + "homepage": "http://github.com/dominictarr/split", + "repository": { + "type": "git", + "url": "git://github.com/dominictarr/split.git" + }, + "dependencies": { + "through": "2" + }, + "devDependencies": { + "asynct": "*", + "it-is": "1", + "ubelt": "~2.9", + "stream-spec": "~0.2", + "event-stream": "~3.0.2" + }, + "scripts": { + "test": "asynct test/" + }, + "author": { + "name": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "http://bit.ly/dominictarr" + }, + "optionalDependencies": {}, + "engines": { + "node": "*" + }, + "readme": "# Split (matcher)\n\n[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)\n\nBreak up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` \n\nExample, read every line in a file ...\n\n``` js\n fs.createReadStream(file)\n .pipe(split())\n .on('data', function (line) {\n //each chunk now is a seperate line!\n })\n\n```\n\n`split` takes the same arguments as `string.split` except it defaults to '/\\r?\\n/' instead of ',', and the optional `limit` paremeter is ignored.\n[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)\n\n# NDJ - Newline Delimited Json\n\n`split` accepts a function which transforms each line.\n\n``` js\nfs.createReadStream(file)\n .pipe(split(JSON.parse))\n .on('data', function (obj) {\n //each chunk now is a a js object\n })\n .on('error', function (err) {\n //syntax errors will land here\n //note, this ends the stream.\n })\n```\n\n# License\n\nMIT\n", + "_id": "split@0.2.10", + "dist": { + "shasum": "f3df4e839e7f9dc513d5463b1c8ad95d4bf75e85" + }, + "_from": "split@0.2" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/readme.markdown b/node_modules/jsftp/node_modules/event-stream/node_modules/split/readme.markdown new file mode 100644 index 0000000..55b8ba8 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/readme.markdown @@ -0,0 +1,39 @@ +# Split (matcher) + +[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split) + +Break up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` + +Example, read every line in a file ... + +``` js + fs.createReadStream(file) + .pipe(split()) + .on('data', function (line) { + //each chunk now is a seperate line! + }) + +``` + +`split` takes the same arguments as `string.split` except it defaults to '/\r?\n/' instead of ',', and the optional `limit` paremeter is ignored. +[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split) + +# NDJ - Newline Delimited Json + +`split` accepts a function which transforms each line. + +``` js +fs.createReadStream(file) + .pipe(split(JSON.parse)) + .on('data', function (obj) { + //each chunk now is a a js object + }) + .on('error', function (err) { + //syntax errors will land here + //note, this ends the stream. + }) +``` + +# License + +MIT diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/partitioned_unicode.js b/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/partitioned_unicode.js new file mode 100644 index 0000000..aff3d5d --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/partitioned_unicode.js @@ -0,0 +1,34 @@ +var it = require('it-is').style('colour') + , split = require('..') + +exports ['split data with partitioned unicode character'] = function (test) { + var s = split(/,/g) + , caughtError = false + , rows = [] + + s.on('error', function (err) { + caughtError = true + }) + + s.on('data', function (row) { rows.push(row) }) + + var x = 'テスト試験今日とても,よい天気で' + unicodeData = new Buffer(x); + + // partition of 日 + piece1 = unicodeData.slice(0, 20); + piece2 = unicodeData.slice(20, unicodeData.length); + + s.write(piece1); + s.write(piece2); + + s.end() + + it(caughtError).equal(false) + + it(rows).deepEqual(['テスト試験今日とても', 'よい天気で']); + + it(rows).deepEqual(x.split(',')) + + test.done() +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/split.asynct.js b/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/split.asynct.js new file mode 100644 index 0000000..fb15b28 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/split.asynct.js @@ -0,0 +1,85 @@ +var es = require('event-stream') + , it = require('it-is').style('colour') + , d = require('ubelt') + , split = require('..') + , join = require('path').join + , fs = require('fs') + , Stream = require('stream').Stream + , spec = require('stream-spec') + +exports ['split() works like String#split'] = function (test) { + var readme = join(__filename) + , expected = fs.readFileSync(readme, 'utf-8').split('\n') + , cs = split() + , actual = [] + , ended = false + , x = spec(cs).through() + + var a = new Stream () + + a.write = function (l) { + actual.push(l.trim()) + } + a.end = function () { + + ended = true + expected.forEach(function (v,k) { + //String.split will append an empty string '' + //if the string ends in a split pattern. + //es.split doesn't which was breaking this test. + //clearly, appending the empty string is correct. + //tests are passing though. which is the current job. + if(v) + it(actual[k]).like(v) + }) + //give the stream time to close + process.nextTick(function () { + test.done() + x.validate() + }) + } + a.writable = true + + fs.createReadStream(readme, {flags: 'r'}).pipe(cs) + cs.pipe(a) + +} + +exports ['split() takes mapper function'] = function (test) { + var readme = join(__filename) + , expected = fs.readFileSync(readme, 'utf-8').split('\n') + , cs = split(function (line) { return line.toUpperCase() }) + , actual = [] + , ended = false + , x = spec(cs).through() + + var a = new Stream () + + a.write = function (l) { + actual.push(l.trim()) + } + a.end = function () { + + ended = true + expected.forEach(function (v,k) { + //String.split will append an empty string '' + //if the string ends in a split pattern. + //es.split doesn't which was breaking this test. + //clearly, appending the empty string is correct. + //tests are passing though. which is the current job. + if(v) + it(actual[k]).equal(v.trim().toUpperCase()) + }) + //give the stream time to close + process.nextTick(function () { + test.done() + x.validate() + }) + } + a.writable = true + + fs.createReadStream(readme, {flags: 'r'}).pipe(cs) + cs.pipe(a) + +} + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/try_catch.asynct.js b/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/try_catch.asynct.js new file mode 100644 index 0000000..39e49f7 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/split/test/try_catch.asynct.js @@ -0,0 +1,51 @@ +var it = require('it-is').style('colour') + , split = require('..') + +exports ['emit mapper exceptions as error events'] = function (test) { + var s = split(JSON.parse) + , caughtError = false + , rows = [] + + s.on('error', function (err) { + caughtError = true + }) + + s.on('data', function (row) { rows.push(row) }) + + s.write('{"a":1}\n{"') + it(caughtError).equal(false) + it(rows).deepEqual([ { a: 1 } ]) + + s.write('b":2}\n{"c":}\n') + it(caughtError).equal(true) + it(rows).deepEqual([ { a: 1 }, { b: 2 } ]) + + s.end() + test.done() +} + +exports ['mapper error events on trailing chunks'] = function (test) { + var s = split(JSON.parse) + , caughtError = false + , rows = [] + + s.on('error', function (err) { + caughtError = true + }) + + s.on('data', function (row) { rows.push(row) }) + + s.write('{"a":1}\n{"') + it(caughtError).equal(false) + it(rows).deepEqual([ { a: 1 } ]) + + s.write('b":2}\n{"c":}') + it(caughtError).equal(false) + it(rows).deepEqual([ { a: 1 }, { b: 2 } ]) + + s.end() + it(caughtError).equal(true) + it(rows).deepEqual([ { a: 1 }, { b: 2 } ]) + + test.done() +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.npmignore new file mode 100644 index 0000000..13abef4 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.npmignore @@ -0,0 +1,3 @@ +node_modules +node_modules/* +npm_debug.log diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.travis.yml b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.travis.yml new file mode 100644 index 0000000..895dbd3 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 + - 0.8 diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/LICENSE b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/LICENSE new file mode 100644 index 0000000..6d03581 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 'Dominic Tarr' + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/README.md b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/README.md new file mode 100644 index 0000000..d0c129c --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/README.md @@ -0,0 +1,29 @@ +# stream-combiner + + + +## pipeline (stream1,...,streamN) + +Turn a pipeline into a single stream. `pipeline` returns a stream that writes to the first stream +and reads from the last stream. + +Listening for 'error' will recieve errors from all streams inside the pipe. + +> `connect` is an alias for `pipeline`. + +``` js + + es.pipeline( //connect streams together with `pipe` + process.openStdin(), //open stdin + es.split(), //split stream to break on newlines + es.map(function (data, callback) {//turn this async function into a stream + callback(null + , inspect(JSON.parse(data))) //render it nicely + }), + process.stdout // pipe it to stdout ! + ) +``` + +## License + +MIT diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/index.js new file mode 100644 index 0000000..99cf5b6 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/index.js @@ -0,0 +1,39 @@ +var duplexer = require('duplexer') + +module.exports = function () { + + var streams = [].slice.call(arguments) + , first = streams[0] + , last = streams[streams.length - 1] + , thepipe = duplexer(first, last) + + if(streams.length == 1) + return streams[0] + else if (!streams.length) + throw new Error('connect called with empty args') + + //pipe all the streams together + + function recurse (streams) { + if(streams.length < 2) + return + streams[0].pipe(streams[1]) + recurse(streams.slice(1)) + } + + recurse(streams) + + function onerror () { + var args = [].slice.call(arguments) + args.unshift('error') + thepipe.emit.apply(thepipe, args) + } + + //es.duplex already reemits the error from the first and last stream. + //add a listener for the inner streams in the pipeline. + for(var i = 1; i < streams.length - 1; i ++) + streams[i].on('error', onerror) + + return thepipe +} + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.npmignore b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.npmignore new file mode 100644 index 0000000..062c11e --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.npmignore @@ -0,0 +1,3 @@ +node_modules +*.log +*.err \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.travis.yml b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.travis.yml new file mode 100644 index 0000000..c2ba3f9 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 0.8 \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/LICENCE b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/LICENCE new file mode 100644 index 0000000..a23e08a --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/LICENCE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Raynos. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/Makefile b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/Makefile new file mode 100644 index 0000000..1f8985d --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/Makefile @@ -0,0 +1,4 @@ +test: + node test.js + +.PHONY: test \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/README.md b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/README.md new file mode 100644 index 0000000..a24cecb --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/README.md @@ -0,0 +1,36 @@ +# duplexer [![build status][1]][2] + +Creates a duplex stream + +Taken from [event-stream][3] + +## duplex (writeStream, readStream) + +Takes a writable stream and a readable stream and makes them appear as a readable writable stream. + +It is assumed that the two streams are connected to each other in some way. + +## Example + + var grep = cp.exec('grep Stream') + + duplex(grep.stdin, grep.stdout) + +## Installation + +`npm install duplexer` + +## Tests + +`make test` + +## Contributors + + - Dominictarr + - Raynos + +## MIT Licenced + + [1]: https://secure.travis-ci.org/Raynos/duplexer.png + [2]: http://travis-ci.org/Raynos/duplexer + [3]: https://github.com/dominictarr/event-stream#duplex-writestream-readstream \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/index.js new file mode 100644 index 0000000..fee581d --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/index.js @@ -0,0 +1,87 @@ +var Stream = require("stream") + , writeMethods = ["write", "end", "destroy"] + , readMethods = ["resume", "pause"] + , readEvents = ["data", "close"] + , slice = Array.prototype.slice + +module.exports = duplex + +function duplex(writer, reader) { + var stream = new Stream() + , ended = false + + Object.defineProperties(stream, { + writable: { + get: getWritable + } + , readable: { + get: getReadable + } + }) + + writeMethods.forEach(proxyWriter) + + readMethods.forEach(proxyReader) + + readEvents.forEach(proxyStream) + + reader.on("end", handleEnd) + + writer.on("error", reemit) + reader.on("error", reemit) + + return stream + + function getWritable() { + return writer.writable + } + + function getReadable() { + return reader.readable + } + + function proxyWriter(methodName) { + stream[methodName] = method + + function method() { + return writer[methodName].apply(writer, arguments) + } + } + + function proxyReader(methodName) { + stream[methodName] = method + + function method() { + stream.emit(methodName) + var func = reader[methodName] + if (func) { + return func.apply(reader, arguments) + } + reader.emit(methodName) + } + } + + function proxyStream(methodName) { + reader.on(methodName, reemit) + + function reemit() { + var args = slice.call(arguments) + args.unshift(methodName) + stream.emit.apply(stream, args) + } + } + + function handleEnd() { + if (ended) { + return + } + ended = true + var args = slice.call(arguments) + args.unshift("end") + stream.emit.apply(stream, args) + } + + function reemit(err) { + stream.emit("error", err) + } +} \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/package.json new file mode 100644 index 0000000..c04e5f6 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/package.json @@ -0,0 +1,44 @@ +{ + "name": "duplexer", + "version": "0.0.2", + "description": "Creates a duplex stream", + "keywords": [], + "author": { + "name": "Raynos", + "email": "raynos2@gmail.com" + }, + "repository": { + "type": "git", + "url": "git://github.com/Raynos/duplexer.git" + }, + "main": "index", + "homepage": "https://github.com/Raynos/duplexer", + "contributors": [ + { + "name": "Jake Verbaten" + } + ], + "bugs": { + "url": "https://github.com/Raynos/duplexer/issues", + "email": "raynos2@gmail.com" + }, + "dependencies": {}, + "devDependencies": { + "through": "~0.1.4" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://github.com/Raynos/duplexer/raw/master/LICENSE" + } + ], + "scripts": { + "test": "make test" + }, + "readme": "# duplexer [![build status][1]][2]\n\nCreates a duplex stream\n\nTaken from [event-stream][3]\n\n## duplex (writeStream, readStream)\n\nTakes a writable stream and a readable stream and makes them appear as a readable writable stream.\n\nIt is assumed that the two streams are connected to each other in some way.\n\n## Example\n\n var grep = cp.exec('grep Stream')\n\n duplex(grep.stdin, grep.stdout)\n\n## Installation\n\n`npm install duplexer`\n\n## Tests\n\n`make test`\n\n## Contributors\n\n - Dominictarr\n - Raynos\n\n## MIT Licenced\n\n [1]: https://secure.travis-ci.org/Raynos/duplexer.png\n [2]: http://travis-ci.org/Raynos/duplexer\n [3]: https://github.com/dominictarr/event-stream#duplex-writestream-readstream", + "_id": "duplexer@0.0.2", + "dist": { + "shasum": "21b2a2e78861eea18bb080cb9491cbcd193ead08" + }, + "_from": "duplexer@0.0.2" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/test.js b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/test.js new file mode 100644 index 0000000..e06a864 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/node_modules/duplexer/test.js @@ -0,0 +1,27 @@ +var duplex = require("./index") + , assert = require("assert") + , through = require("through") + +var readable = through() + , writable = through(write) + , written = 0 + , data = 0 + +var stream = duplex(writable, readable) + +function write() { + written++ +} + +stream.on("data", ondata) + +function ondata() { + data++ +} + +stream.write() +readable.emit("data") + +assert.equal(written, 1) +assert.equal(data, 1) +console.log("DONE") \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/package.json new file mode 100644 index 0000000..4b60b98 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/package.json @@ -0,0 +1,32 @@ +{ + "name": "stream-combiner", + "version": "0.0.0", + "homepage": "https://github.com/dominictarr/stream-combiner", + "repository": { + "type": "git", + "url": "git://github.com/dominictarr/stream-combiner.git" + }, + "dependencies": { + "duplexer": "0.0.2" + }, + "devDependencies": { + "tape": "0.0.4", + "event-stream": "~3.0.7" + }, + "scripts": { + "test": "set -e; for t in test/*.js; do node $t; done" + }, + "author": { + "name": "'Dominic Tarr'", + "email": "dominic.tarr@gmail.com", + "url": "http://dominictarr.com" + }, + "license": "MIT", + "readme": "# stream-combiner\n\n\n\n## pipeline (stream1,...,streamN)\n\nTurn a pipeline into a single stream. `pipeline` returns a stream that writes to the first stream\nand reads from the last stream. \n\nListening for 'error' will recieve errors from all streams inside the pipe.\n\n> `connect` is an alias for `pipeline`.\n\n``` js\n\n es.pipeline( //connect streams together with `pipe`\n process.openStdin(), //open stdin\n es.split(), //split stream to break on newlines\n es.map(function (data, callback) {//turn this async function into a stream\n callback(null\n , inspect(JSON.parse(data))) //render it nicely\n }),\n process.stdout // pipe it to stdout !\n )\n```\n\n## License\n\nMIT\n", + "_id": "stream-combiner@0.0.0", + "description": "", + "dist": { + "shasum": "e0b7200a986c630eef402100cef434714b4db89c" + }, + "_from": "stream-combiner@0.0.0" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/test/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/test/index.js new file mode 100644 index 0000000..d17ddad --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/stream-combiner/test/index.js @@ -0,0 +1,52 @@ +var es = require('event-stream') +var combine = require('..') +var test = require('tape') + +test('do not duplicate errors', function (test) { + + var errors = 0; + var pipe = combine( + es.through(function(data) { + return this.emit('data', data); + }), + es.through(function(data) { + return this.emit('error', new Error(data)); + }) + ) + + pipe.on('error', function(err) { + errors++ + test.ok(errors, 'expected error count') + process.nextTick(function () { + return test.end(); + }) + }) + + return pipe.write('meh'); +}) + +test('3 pipe do not duplicate errors', function (test) { + + var errors = 0; + var pipe = combine( + es.through(function(data) { + return this.emit('data', data); + }), + es.through(function(data) { + return this.emit('error', new Error(data)); + }), + es.through() + ) + + pipe.on('error', function(err) { + errors++ + test.ok(errors, 'expected error count') + process.nextTick(function () { + return test.end(); + }) + }) + + return pipe.write('meh'); + +}) + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/.travis.yml b/node_modules/jsftp/node_modules/event-stream/node_modules/through/.travis.yml new file mode 100644 index 0000000..c693a93 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 0.6 + - 0.8 + - "0.10" diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.APACHE2 b/node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.APACHE2 new file mode 100644 index 0000000..6366c04 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.APACHE2 @@ -0,0 +1,15 @@ +Apache License, Version 2.0 + +Copyright (c) 2011 Dominic Tarr + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.MIT b/node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.MIT new file mode 100644 index 0000000..6eafbd7 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/LICENSE.MIT @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/through/index.js new file mode 100644 index 0000000..7b935bf --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/index.js @@ -0,0 +1,108 @@ +var Stream = require('stream') + +// through +// +// a stream that does nothing but re-emit the input. +// useful for aggregating a series of changing but not ending streams into one stream) + +exports = module.exports = through +through.through = through + +//create a readable writable stream. + +function through (write, end, opts) { + write = write || function (data) { this.queue(data) } + end = end || function () { this.queue(null) } + + var ended = false, destroyed = false, buffer = [], _ended = false + var stream = new Stream() + stream.readable = stream.writable = true + stream.paused = false + +// stream.autoPause = !(opts && opts.autoPause === false) + stream.autoDestroy = !(opts && opts.autoDestroy === false) + + stream.write = function (data) { + write.call(this, data) + return !stream.paused + } + + function drain() { + while(buffer.length && !stream.paused) { + var data = buffer.shift() + if(null === data) + return stream.emit('end') + else + stream.emit('data', data) + } + } + + stream.queue = stream.push = function (data) { +// console.error(ended) + if(_ended) return stream + if(data == null) _ended = true + buffer.push(data) + drain() + return stream + } + + //this will be registered as the first 'end' listener + //must call destroy next tick, to make sure we're after any + //stream piped from here. + //this is only a problem if end is not emitted synchronously. + //a nicer way to do this is to make sure this is the last listener for 'end' + + stream.on('end', function () { + stream.readable = false + if(!stream.writable && stream.autoDestroy) + process.nextTick(function () { + stream.destroy() + }) + }) + + function _end () { + stream.writable = false + end.call(stream) + if(!stream.readable && stream.autoDestroy) + stream.destroy() + } + + stream.end = function (data) { + if(ended) return + ended = true + if(arguments.length) stream.write(data) + _end() // will emit or queue + return stream + } + + stream.destroy = function () { + if(destroyed) return + destroyed = true + ended = true + buffer.length = 0 + stream.writable = stream.readable = false + stream.emit('close') + return stream + } + + stream.pause = function () { + if(stream.paused) return + stream.paused = true + return stream + } + + stream.resume = function () { + if(stream.paused) { + stream.paused = false + stream.emit('resume') + } + drain() + //may have become paused again, + //as drain emits 'data'. + if(!stream.paused) + stream.emit('drain') + return stream + } + return stream +} + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/package.json b/node_modules/jsftp/node_modules/event-stream/node_modules/through/package.json new file mode 100644 index 0000000..833c49e --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/package.json @@ -0,0 +1,45 @@ +{ + "name": "through", + "version": "2.3.4", + "description": "simplified stream contsruction", + "main": "index.js", + "scripts": { + "test": "set -e; for t in test/*.js; do node $t; done" + }, + "devDependencies": { + "stream-spec": "~0.3.5", + "tape": "~0.2.2" + }, + "keywords": [ + "stream", + "streams", + "user-streams", + "pipe" + ], + "author": { + "name": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "dominictarr.com" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/dominictarr/through.git" + }, + "homepage": "http://github.com/dominictarr/through", + "testling": { + "browsers": [ + "ie/8..latest", + "ff/15..latest", + "chrome/20..latest", + "safari/5.1..latest" + ], + "files": "test/*.js" + }, + "readme": "#through\n\n[![build status](https://secure.travis-ci.org/dominictarr/through.png)](http://travis-ci.org/dominictarr/through)\n\nEasy way to create a `Stream` that is both `readable` and `writable`. \n\n* Pass in optional `write` and `end` methods.\n* `through` takes care of pause/resume logic if you use `this.queue(data)` instead of `this.emit('data', data)`.\n* Use `this.pause()` and `this.resume()` to manage flow.\n* Check `this.paused` to see current flow state. (`write` always returns `!this.paused`).\n\nThis function is the basis for most of the synchronous streams in \n[event-stream](http://github.com/dominictarr/event-stream).\n\n``` js\nvar through = require('through')\n\nthrough(function write(data) {\n this.queue(data) //data *must* not be null\n },\n function end () { //optional\n this.queue(null)\n })\n```\n\nOr, can also be used _without_ buffering on pause, use `this.emit('data', data)`,\nand this.emit('end')\n\n``` js\nvar through = require('through')\n\nthrough(function write(data) {\n this.emit('data', data)\n //this.pause() \n },\n function end () { //optional\n this.emit('end')\n })\n```\n\n## Extended Options\n\nYou will probably not need these 99% of the time.\n\n### autoDestroy=false\n\nBy default, `through` emits close when the writable\nand readable side of the stream has ended.\nIf that is not desired, set `autoDestroy=false`.\n\n``` js\nvar through = require('through')\n\n//like this\nvar ts = through(write, end, {autoDestroy: false})\n//or like this\nvar ts = through(write, end)\nts.autoDestroy = false\n```\n\n## License\n\nMIT / Apache2\n", + "_id": "through@2.3.4", + "dist": { + "shasum": "b0b7e25d5785ed1c45add69ad718a3ab3bc1f81a" + }, + "_from": "through@~2.3.1" +} diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/readme.markdown b/node_modules/jsftp/node_modules/event-stream/node_modules/through/readme.markdown new file mode 100644 index 0000000..b20bc9a --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/readme.markdown @@ -0,0 +1,63 @@ +#through + +[![build status](https://secure.travis-ci.org/dominictarr/through.png)](http://travis-ci.org/dominictarr/through) + +Easy way to create a `Stream` that is both `readable` and `writable`. + +* Pass in optional `write` and `end` methods. +* `through` takes care of pause/resume logic if you use `this.queue(data)` instead of `this.emit('data', data)`. +* Use `this.pause()` and `this.resume()` to manage flow. +* Check `this.paused` to see current flow state. (`write` always returns `!this.paused`). + +This function is the basis for most of the synchronous streams in +[event-stream](http://github.com/dominictarr/event-stream). + +``` js +var through = require('through') + +through(function write(data) { + this.queue(data) //data *must* not be null + }, + function end () { //optional + this.queue(null) + }) +``` + +Or, can also be used _without_ buffering on pause, use `this.emit('data', data)`, +and this.emit('end') + +``` js +var through = require('through') + +through(function write(data) { + this.emit('data', data) + //this.pause() + }, + function end () { //optional + this.emit('end') + }) +``` + +## Extended Options + +You will probably not need these 99% of the time. + +### autoDestroy=false + +By default, `through` emits close when the writable +and readable side of the stream has ended. +If that is not desired, set `autoDestroy=false`. + +``` js +var through = require('through') + +//like this +var ts = through(write, end, {autoDestroy: false}) +//or like this +var ts = through(write, end) +ts.autoDestroy = false +``` + +## License + +MIT / Apache2 diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/auto-destroy.js b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/auto-destroy.js new file mode 100644 index 0000000..9a8fd00 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/auto-destroy.js @@ -0,0 +1,30 @@ +var test = require('tape') +var through = require('../') + +// must emit end before close. + +test('end before close', function (assert) { + var ts = through() + ts.autoDestroy = false + var ended = false, closed = false + + ts.on('end', function () { + assert.ok(!closed) + ended = true + }) + ts.on('close', function () { + assert.ok(ended) + closed = true + }) + + ts.write(1) + ts.write(2) + ts.write(3) + ts.end() + assert.ok(ended) + assert.notOk(closed) + ts.destroy() + assert.ok(closed) + assert.end() +}) + diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/buffering.js b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/buffering.js new file mode 100644 index 0000000..b0084bf --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/buffering.js @@ -0,0 +1,71 @@ +var test = require('tape') +var through = require('../') + +// must emit end before close. + +test('buffering', function(assert) { + var ts = through(function (data) { + this.queue(data) + }, function () { + this.queue(null) + }) + + var ended = false, actual = [] + + ts.on('data', actual.push.bind(actual)) + ts.on('end', function () { + ended = true + }) + + ts.write(1) + ts.write(2) + ts.write(3) + assert.deepEqual(actual, [1, 2, 3]) + ts.pause() + ts.write(4) + ts.write(5) + ts.write(6) + assert.deepEqual(actual, [1, 2, 3]) + ts.resume() + assert.deepEqual(actual, [1, 2, 3, 4, 5, 6]) + ts.pause() + ts.end() + assert.ok(!ended) + ts.resume() + assert.ok(ended) + assert.end() +}) + +test('buffering has data in queue, when ends', function (assert) { + + /* + * If stream ends while paused with data in the queue, + * stream should still emit end after all data is written + * on resume. + */ + + var ts = through(function (data) { + this.queue(data) + }, function () { + this.queue(null) + }) + + var ended = false, actual = [] + + ts.on('data', actual.push.bind(actual)) + ts.on('end', function () { + ended = true + }) + + ts.pause() + ts.write(1) + ts.write(2) + ts.write(3) + ts.end() + assert.deepEqual(actual, [], 'no data written yet, still paused') + assert.ok(!ended, 'end not emitted yet, still paused') + ts.resume() + assert.deepEqual(actual, [1, 2, 3], 'resumed, all data should be delivered') + assert.ok(ended, 'end should be emitted once all data was delivered') + assert.end(); +}) diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/end.js b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/end.js new file mode 100644 index 0000000..fa113f5 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/end.js @@ -0,0 +1,45 @@ +var test = require('tape') +var through = require('../') + +// must emit end before close. + +test('end before close', function (assert) { + var ts = through() + var ended = false, closed = false + + ts.on('end', function () { + assert.ok(!closed) + ended = true + }) + ts.on('close', function () { + assert.ok(ended) + closed = true + }) + + ts.write(1) + ts.write(2) + ts.write(3) + ts.end() + assert.ok(ended) + assert.ok(closed) + assert.end() +}) + +test('end only once', function (t) { + + var ts = through() + var ended = false, closed = false + + ts.on('end', function () { + t.equal(ended, false) + ended = true + }) + + ts.queue(null) + ts.queue(null) + ts.queue(null) + + ts.resume() + + t.end() +}) diff --git a/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/index.js b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/index.js new file mode 100644 index 0000000..33e33f9 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/node_modules/through/test/index.js @@ -0,0 +1,114 @@ + +var test = require('tape') +var spec = require('stream-spec') +var through = require('../') + +/* + I'm using these two functions, and not streams and pipe + so there is less to break. if this test fails it must be + the implementation of _through_ +*/ + +function write(array, stream) { + array = array.slice() + function next() { + while(array.length) + if(stream.write(array.shift()) === false) + return stream.once('drain', next) + + stream.end() + } + + next() +} + +function read(stream, callback) { + var actual = [] + stream.on('data', function (data) { + actual.push(data) + }) + stream.once('end', function () { + callback(null, actual) + }) + stream.once('error', function (err) { + callback(err) + }) +} + +test('simple defaults', function(assert) { + + var l = 1000 + , expected = [] + + while(l--) expected.push(l * Math.random()) + + var t = through() + spec(t) + .through() + .pausable() + .validateOnExit() + + read(t, function (err, actual) { + assert.ifError(err) + assert.deepEqual(actual, expected) + assert.end() + }) + + write(expected, t) +}); + +test('simple functions', function(assert) { + + var l = 1000 + , expected = [] + + while(l--) expected.push(l * Math.random()) + + var t = through(function (data) { + this.emit('data', data*2) + }) + spec(t) + .through() + .pausable() + .validateOnExit() + + read(t, function (err, actual) { + assert.ifError(err) + assert.deepEqual(actual, expected.map(function (data) { + return data*2 + })) + assert.end() + }) + + write(expected, t) +}) + +test('pauses', function(assert) { + + var l = 1000 + , expected = [] + + while(l--) expected.push(l) //Math.random()) + + var t = through() + spec(t) + .through() + .pausable() + .validateOnExit() + + t.on('data', function () { + if(Math.random() > 0.1) return + t.pause() + process.nextTick(function () { + t.resume() + }) + }) + + read(t, function (err, actual) { + assert.ifError(err) + assert.deepEqual(actual, expected) + assert.end() + }) + + write(expected, t) +}) diff --git a/node_modules/jsftp/node_modules/event-stream/package.json b/node_modules/jsftp/node_modules/event-stream/package.json new file mode 100644 index 0000000..40c8504 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/package.json @@ -0,0 +1,62 @@ +{ + "name": "event-stream", + "version": "3.0.16", + "description": "construct pipes of streams of events", + "homepage": "http://github.com/dominictarr/event-stream", + "repository": { + "type": "git", + "url": "git://github.com/dominictarr/event-stream.git" + }, + "dependencies": { + "through": "~2.3.1", + "duplexer": "~0.0.2", + "from": "~0", + "map-stream": "0.0.2", + "pause-stream": "0.0.10", + "split": "0.2", + "stream-combiner": "0.0.0" + }, + "devDependencies": { + "asynct": "*", + "it-is": "1", + "ubelt": "~2.9", + "stream-spec": "~0.2", + "tape": "~0.1.5" + }, + "scripts": { + "test": "asynct test/", + "test_tap": "set -e; for t in test/*.js; do node $t; done" + }, + "testling": { + "files": "test/*.js", + "browsers": { + "ie": [ + 8, + 9 + ], + "firefox": [ + 13 + ], + "chrome": [ + 20 + ], + "safari": [ + 5.1 + ], + "opera": [ + 12 + ] + } + }, + "author": { + "name": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "http://bit.ly/dominictarr" + }, + "readme": "# EventStream\n\n\n\n[![browser status](http://ci.testling.com/dominictarr/event-stream.png)]\n(http://ci.testling.com/dominictarr/event-stream)\n\n[Streams](http://nodejs.org/api/strems.html \"Stream\") are node's best and most misunderstood idea, and \n_EventStream_ is a toolkit to make creating and working with streams easy. \n\nNormally, streams are only used of IO, \nbut in event stream we send all kinds of objects down the pipe. \nIf your application's input and output are streams, \nshouldn't the throughput be a stream too? \n\nThe *EventStream* functions resemble the array functions, \nbecause Streams are like Arrays, but laid out in time, rather than in memory. \n\nAll the `event-stream` functions return instances of `Stream`.\n\n`event-stream` creates \n[0.8 streams](https://github.com/joyent/node/blob/v0.8/doc/api/stream.markdown)\n, which are compatible with [0.10 streams](http://nodejs.org/api/stream.html \"Stream\")\n\n>NOTE: I shall use the term \"through stream\" to refer to a stream that is writable and readable. \n\n###[simple example](https://github.com/dominictarr/event-stream/blob/master/examples/pretty.js):\n\n``` js\n\n//pretty.js\n\nif(!module.parent) {\n var es = require('event-stream')\n es.pipeline( //connect streams together with `pipe`\n process.openStdin(), //open stdin\n es.split(), //split stream to break on newlines\n es.map(function (data, callback) {//turn this async function into a stream\n callback(null\n , inspect(JSON.parse(data))) //render it nicely\n }),\n process.stdout // pipe it to stdout !\n )\n }\n```\nrun it ...\n\n``` bash \ncurl -sS registry.npmjs.org/event-stream | node pretty.js\n```\n \n[node Stream documentation](http://nodejs.org/api/stream.html)\n\n## through (write?, end?)\n\nReemits data synchronously. Easy way to create syncronous through streams.\nPass in an optional `write` and `end` methods. They will be called in the \ncontext of the stream. Use `this.pause()` and `this.resume()` to manage flow.\nCheck `this.paused` to see current flow state. (write always returns `!this.paused`)\n\nthis function is the basis for most of the syncronous streams in `event-stream`.\n\n``` js\n\nes.through(function write(data) {\n this.emit('data', data)\n //this.pause() \n },\n function end () { //optional\n this.emit('end')\n })\n\n```\n\n##map (asyncFunction)\n\nCreate a through stream from an asyncronous function. \n\n``` js\nvar es = require('event-stream')\n\nes.map(function (data, callback) {\n //transform data\n // ...\n callback(null, data)\n})\n\n```\n\nEach map MUST call the callback. It may callback with data, with an error or with no arguments, \n\n * `callback()` drop this data. \n this makes the map work like `filter`, \n note:`callback(null,null)` is not the same, and will emit `null`\n\n * `callback(null, newData)` turn data into newData\n \n * `callback(error)` emit an error for this item.\n\n>Note: if a callback is not called, `map` will think that it is still being processed, \n>every call must be answered or the stream will not know when to end. \n>\n>Also, if the callback is called more than once, every call but the first will be ignored.\n\n## mapSync (syncFunction)\n\nSame as `map`, but the callback is called synchronously. Based on `es.through`\n\n## split (matcher)\n\nBreak up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` \n\nExample, read every line in a file ...\n\n``` js\n es.pipeline(\n fs.createReadStream(file, {flags: 'r'}),\n es.split(),\n es.map(function (line, cb) {\n //do something with the line \n cb(null, line)\n })\n )\n\n```\n\n`split` takes the same arguments as `string.split` except it defaults to '\\n' instead of ',', and the optional `limit` paremeter is ignored.\n[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)\n\n## join (separator)\n\ncreate a through stream that emits `separator` between each chunk, just like Array#join.\n\n(for legacy reasons, if you pass a callback instead of a string, join is a synonym for `es.wait`)\n\n## replace (from, to)\n\nReplace all occurences of `from` with `to`. `from` may be a `String` or a `RegExp`. \nWorks just like `string.split(from).join(to)`, but streaming.\n\n\n## parse\n\nConvienience function for parsing JSON chunks. For newline separated JSON,\nuse with `es.split`\n\n``` js\nfs.createReadStream(filename)\n .pipe(es.split()) //defaults to lines.\n .pipe(es.parse())\n```\n\n## stringify\n\nconvert javascript objects into lines of text. The text will have whitespace escaped and have a `\\n` appended, so it will be compatible with `es.parse`\n\n``` js\nobjectStream\n .pipe(es.stringify())\n .pipe(fs.createWriteStream(filename))\n```\n\n##readable (asyncFunction) \n\ncreate a readable stream (that respects pause) from an async function. \nwhile the stream is not paused, \nthe function will be polled with `(count, callback)`, \nand `this` will be the readable stream.\n\n``` js\n\nes.readable(function (count, callback) {\n if(streamHasEnded)\n return this.emit('end')\n \n //...\n \n this.emit('data', data) //use this way to emit multiple chunks per call.\n \n callback() // you MUST always call the callback eventually.\n // the function will not be called again until you do this.\n})\n```\nyou can also pass the data and the error to the callback. \nyou may only call the callback once. \ncalling the same callback more than once will have no effect. \n\n##readArray (array)\n\nCreate a readable stream from an Array.\n\nJust emit each item as a data event, respecting `pause` and `resume`.\n\n``` js\n var es = require('event-stream')\n , reader = es.readArray([1,2,3])\n\n reader.pipe(...)\n```\n\n## writeArray (callback)\n\ncreate a writeable stream from a callback, \nall `data` events are stored in an array, which is passed to the callback when the stream ends.\n\n``` js\n var es = require('event-stream')\n , reader = es.readArray([1, 2, 3])\n , writer = es.writeArray(function (err, array){\n //array deepEqual [1, 2, 3]\n })\n\n reader.pipe(writer)\n```\n\n## pipeline (stream1,...,streamN)\n\nTurn a pipeline into a single stream. `pipeline` returns a stream that writes to the first stream\nand reads from the last stream. \n\nListening for 'error' will recieve errors from all streams inside the pipe.\n\n> `connect` is an alias for `pipeline`.\n\n``` js\n\n es.pipeline( //connect streams together with `pipe`\n process.openStdin(), //open stdin\n es.split(), //split stream to break on newlines\n es.map(function (data, callback) {//turn this async function into a stream\n callback(null\n , inspect(JSON.parse(data))) //render it nicely\n }),\n process.stdout // pipe it to stdout !\n )\n```\n\n## pause () \n\nA stream that buffers all chunks when paused.\n\n\n``` js\n var ps = es.pause()\n ps.pause() //buffer the stream, also do not allow 'end' \n ps.resume() //allow chunks through\n```\n\n## duplex (writeStream, readStream)\n\nTakes a writable stream and a readable stream and makes them appear as a readable writable stream.\n\nIt is assumed that the two streams are connected to each other in some way. \n\n(This is used by `pipeline` and `child`.)\n\n``` js\n var grep = cp.exec('grep Stream')\n\n es.duplex(grep.stdin, grep.stdout)\n```\n\n## child (child_process)\n\nCreate a through stream from a child process ...\n\n``` js\n var cp = require('child_process')\n\n es.child(cp.exec('grep Stream')) // a through stream\n\n```\n\n## wait (callback)\n\nwaits for stream to emit 'end'.\njoins chunks of a stream into a single string. \ntakes an optional callback, which will be passed the \ncomplete string when it receives the 'end' event.\n\nalso, emits a single 'data' event.\n\n``` js\n\nreadStream.pipe(es.join(function (err, text) {\n // have complete text here.\n}))\n\n```\n\n\n", + "_id": "event-stream@3.0.16", + "dist": { + "shasum": "0b040584bf4f31ca2d250d6a2ee285fea3e8e358" + }, + "_from": "event-stream@~3.0.14" +} diff --git a/node_modules/jsftp/node_modules/event-stream/readme.markdown b/node_modules/jsftp/node_modules/event-stream/readme.markdown new file mode 100644 index 0000000..4674c4e --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/readme.markdown @@ -0,0 +1,291 @@ +# EventStream + + + +[![browser status](http://ci.testling.com/dominictarr/event-stream.png)] +(http://ci.testling.com/dominictarr/event-stream) + +[Streams](http://nodejs.org/api/strems.html "Stream") are node's best and most misunderstood idea, and +_EventStream_ is a toolkit to make creating and working with streams easy. + +Normally, streams are only used of IO, +but in event stream we send all kinds of objects down the pipe. +If your application's input and output are streams, +shouldn't the throughput be a stream too? + +The *EventStream* functions resemble the array functions, +because Streams are like Arrays, but laid out in time, rather than in memory. + +All the `event-stream` functions return instances of `Stream`. + +`event-stream` creates +[0.8 streams](https://github.com/joyent/node/blob/v0.8/doc/api/stream.markdown) +, which are compatible with [0.10 streams](http://nodejs.org/api/stream.html "Stream") + +>NOTE: I shall use the term "through stream" to refer to a stream that is writable and readable. + +###[simple example](https://github.com/dominictarr/event-stream/blob/master/examples/pretty.js): + +``` js + +//pretty.js + +if(!module.parent) { + var es = require('event-stream') + es.pipeline( //connect streams together with `pipe` + process.openStdin(), //open stdin + es.split(), //split stream to break on newlines + es.map(function (data, callback) {//turn this async function into a stream + callback(null + , inspect(JSON.parse(data))) //render it nicely + }), + process.stdout // pipe it to stdout ! + ) + } +``` +run it ... + +``` bash +curl -sS registry.npmjs.org/event-stream | node pretty.js +``` + +[node Stream documentation](http://nodejs.org/api/stream.html) + +## through (write?, end?) + +Reemits data synchronously. Easy way to create syncronous through streams. +Pass in an optional `write` and `end` methods. They will be called in the +context of the stream. Use `this.pause()` and `this.resume()` to manage flow. +Check `this.paused` to see current flow state. (write always returns `!this.paused`) + +this function is the basis for most of the syncronous streams in `event-stream`. + +``` js + +es.through(function write(data) { + this.emit('data', data) + //this.pause() + }, + function end () { //optional + this.emit('end') + }) + +``` + +##map (asyncFunction) + +Create a through stream from an asyncronous function. + +``` js +var es = require('event-stream') + +es.map(function (data, callback) { + //transform data + // ... + callback(null, data) +}) + +``` + +Each map MUST call the callback. It may callback with data, with an error or with no arguments, + + * `callback()` drop this data. + this makes the map work like `filter`, + note:`callback(null,null)` is not the same, and will emit `null` + + * `callback(null, newData)` turn data into newData + + * `callback(error)` emit an error for this item. + +>Note: if a callback is not called, `map` will think that it is still being processed, +>every call must be answered or the stream will not know when to end. +> +>Also, if the callback is called more than once, every call but the first will be ignored. + +## mapSync (syncFunction) + +Same as `map`, but the callback is called synchronously. Based on `es.through` + +## split (matcher) + +Break up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` + +Example, read every line in a file ... + +``` js + es.pipeline( + fs.createReadStream(file, {flags: 'r'}), + es.split(), + es.map(function (line, cb) { + //do something with the line + cb(null, line) + }) + ) + +``` + +`split` takes the same arguments as `string.split` except it defaults to '\n' instead of ',', and the optional `limit` paremeter is ignored. +[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split) + +## join (separator) + +create a through stream that emits `separator` between each chunk, just like Array#join. + +(for legacy reasons, if you pass a callback instead of a string, join is a synonym for `es.wait`) + +## replace (from, to) + +Replace all occurences of `from` with `to`. `from` may be a `String` or a `RegExp`. +Works just like `string.split(from).join(to)`, but streaming. + + +## parse + +Convienience function for parsing JSON chunks. For newline separated JSON, +use with `es.split` + +``` js +fs.createReadStream(filename) + .pipe(es.split()) //defaults to lines. + .pipe(es.parse()) +``` + +## stringify + +convert javascript objects into lines of text. The text will have whitespace escaped and have a `\n` appended, so it will be compatible with `es.parse` + +``` js +objectStream + .pipe(es.stringify()) + .pipe(fs.createWriteStream(filename)) +``` + +##readable (asyncFunction) + +create a readable stream (that respects pause) from an async function. +while the stream is not paused, +the function will be polled with `(count, callback)`, +and `this` will be the readable stream. + +``` js + +es.readable(function (count, callback) { + if(streamHasEnded) + return this.emit('end') + + //... + + this.emit('data', data) //use this way to emit multiple chunks per call. + + callback() // you MUST always call the callback eventually. + // the function will not be called again until you do this. +}) +``` +you can also pass the data and the error to the callback. +you may only call the callback once. +calling the same callback more than once will have no effect. + +##readArray (array) + +Create a readable stream from an Array. + +Just emit each item as a data event, respecting `pause` and `resume`. + +``` js + var es = require('event-stream') + , reader = es.readArray([1,2,3]) + + reader.pipe(...) +``` + +## writeArray (callback) + +create a writeable stream from a callback, +all `data` events are stored in an array, which is passed to the callback when the stream ends. + +``` js + var es = require('event-stream') + , reader = es.readArray([1, 2, 3]) + , writer = es.writeArray(function (err, array){ + //array deepEqual [1, 2, 3] + }) + + reader.pipe(writer) +``` + +## pipeline (stream1,...,streamN) + +Turn a pipeline into a single stream. `pipeline` returns a stream that writes to the first stream +and reads from the last stream. + +Listening for 'error' will recieve errors from all streams inside the pipe. + +> `connect` is an alias for `pipeline`. + +``` js + + es.pipeline( //connect streams together with `pipe` + process.openStdin(), //open stdin + es.split(), //split stream to break on newlines + es.map(function (data, callback) {//turn this async function into a stream + callback(null + , inspect(JSON.parse(data))) //render it nicely + }), + process.stdout // pipe it to stdout ! + ) +``` + +## pause () + +A stream that buffers all chunks when paused. + + +``` js + var ps = es.pause() + ps.pause() //buffer the stream, also do not allow 'end' + ps.resume() //allow chunks through +``` + +## duplex (writeStream, readStream) + +Takes a writable stream and a readable stream and makes them appear as a readable writable stream. + +It is assumed that the two streams are connected to each other in some way. + +(This is used by `pipeline` and `child`.) + +``` js + var grep = cp.exec('grep Stream') + + es.duplex(grep.stdin, grep.stdout) +``` + +## child (child_process) + +Create a through stream from a child process ... + +``` js + var cp = require('child_process') + + es.child(cp.exec('grep Stream')) // a through stream + +``` + +## wait (callback) + +waits for stream to emit 'end'. +joins chunks of a stream into a single string. +takes an optional callback, which will be passed the +complete string when it receives the 'end' event. + +also, emits a single 'data' event. + +``` js + +readStream.pipe(es.join(function (err, text) { + // have complete text here. +})) + +``` + + diff --git a/node_modules/jsftp/node_modules/event-stream/test/connect.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/connect.asynct.js new file mode 100644 index 0000000..e6a442b --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/connect.asynct.js @@ -0,0 +1,86 @@ +var es = require('../') + , it = require('it-is').style('colour') + , d = require('ubelt') + +function makeExamplePipe() { + + return es.connect( + es.map(function (data, callback) { + callback(null, data * 2) + }), + es.map(function (data, callback) { + d.delay(callback)(null, data) + }), + es.map(function (data, callback) { + callback(null, data + 2) + })) +} + +exports['simple pipe'] = function (test) { + + var pipe = makeExamplePipe() + + pipe.on('data', function (data) { + it(data).equal(18) + test.done() + }) + + pipe.write(8) + +} + +exports['read array then map'] = function (test) { + + var readThis = d.map(3, 6, 100, d.id) //array of multiples of 3 < 100 + , first = es.readArray(readThis) + , read = [] + , pipe = + es.connect( + first, + es.map(function (data, callback) { + callback(null, {data: data}) + }), + es.map(function (data, callback) { + callback(null, {data: data}) + }), + es.writeArray(function (err, array) { + it(array).deepEqual(d.map(readThis, function (data) { + return {data: {data: data}} + })) + test.done() + }) + ) +} + +exports ['connect returns a stream'] = function (test) { + + var rw = + es.connect( + es.map(function (data, callback) { + callback(null, data * 2) + }), + es.map(function (data, callback) { + callback(null, data * 5) + }) + ) + + it(rw).has({readable: true, writable: true}) + + var array = [190, 24, 6, 7, 40, 57, 4, 6] + , _array = [] + , c = + es.connect( + es.readArray(array), + rw, + es.log('after rw:'), + es.writeArray(function (err, _array) { + it(_array).deepEqual(array.map(function (e) { return e * 10 })) + test.done() + }) + ) + +} + + +require('./helper')(module) + diff --git a/node_modules/jsftp/node_modules/event-stream/test/helper/index.js b/node_modules/jsftp/node_modules/event-stream/test/helper/index.js new file mode 100644 index 0000000..96abaeb --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/helper/index.js @@ -0,0 +1,12 @@ +var tape = require('tape') + +module.exports = function (m) { + if(m.parent) return + for(var name in m.exports) { + tape(name, function (t) { + console.log('start', name) + t.done = t.end + m.exports[name](t) + }) + } +} diff --git a/node_modules/jsftp/node_modules/event-stream/test/merge.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/merge.asynct.js new file mode 100644 index 0000000..6ac65f3 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/merge.asynct.js @@ -0,0 +1,21 @@ +var es = require('../') + , it = require('it-is').style('colour') + , d = require('ubelt') + +exports.merge = function (t) { + var odd = d.map(1, 3, 100, d.id) //array of multiples of 3 < 100 + var even = d.map(2, 4, 100, d.id) //array of multiples of 3 < 100 + + var r1 = es.readArray(even) + var r2 = es.readArray(odd) + + var writer = es.writeArray(function (err, array){ + if(err) throw err //unpossible + it(array.sort()).deepEqual(even.concat(odd).sort()) + t.done() + }) + + es.merge(r1, r2).pipe(writer) + +} +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/pause.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/pause.asynct.js new file mode 100644 index 0000000..b073604 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/pause.asynct.js @@ -0,0 +1,39 @@ + +var es = require('../') + , it = require('it-is') + , d = require('ubelt') + +exports ['gate buffers when shut'] = function (test) { + + var hundy = d.map(1,100, d.id) + , gate = es.pause() + , ten = 10 + es.connect( + es.readArray(hundy), + es.log('after readArray'), + gate, + //es.log('after gate'), + es.map(function (num, next) { + //stick a map in here to check that gate never emits when open + it(gate.paused).equal(false) + console.log('data', num) + if(!--ten) { + console.log('PAUSE') + gate.pause()//.resume() + d.delay(gate.resume.bind(gate), 10)() + ten = 10 + } + + next(null, num) + }), + es.writeArray(function (err, array) { //just realized that I should remove the error param. errors will be emitted + console.log('eonuhoenuoecbulc') + it(array).deepEqual(hundy) + test.done() + }) + ) + + gate.resume() + +} +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/pipeline.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/pipeline.asynct.js new file mode 100644 index 0000000..f07c6fa --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/pipeline.asynct.js @@ -0,0 +1,52 @@ +var es = require('..') + +exports['do not duplicate errors'] = function (test) { + + var errors = 0; + var pipe = es.pipeline( + es.through(function(data) { + return this.emit('data', data); + }), + es.through(function(data) { + return this.emit('error', new Error(data)); + }) + ) + + pipe.on('error', function(err) { + errors++ + console.log('error count', errors) + process.nextTick(function () { + return test.done(); + }) + }) + + return pipe.write('meh'); + +} + +exports['3 pipe do not duplicate errors'] = function (test) { + + var errors = 0; + var pipe = es.pipeline( + es.through(function(data) { + return this.emit('data', data); + }), + es.through(function(data) { + return this.emit('error', new Error(data)); + }), + es.through() + ) + + pipe.on('error', function(err) { + errors++ + console.log('error count', errors) + process.nextTick(function () { + return test.done(); + }) + }) + + return pipe.write('meh'); + +} + +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/readArray.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/readArray.asynct.js new file mode 100644 index 0000000..3eeb90a --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/readArray.asynct.js @@ -0,0 +1,89 @@ + +var es = require('../') + , it = require('it-is').style('colour') + , d = require('ubelt') + +function readStream(stream, pauseAt, done) { + if(!done) done = pauseAt, pauseAt = -1 + var array = [] + stream.on('data', function (data) { + array.push(data) + if(!--pauseAt ) + stream.pause(), done(null, array) + }) + stream.on('error', done) + stream.on('end', function (data) { + done(null, array) + }) + +} + +exports ['read an array'] = function (test) { + + var readThis = d.map(3, 6, 100, d.id) //array of multiples of 3 < 100 + + var reader = es.readArray(readThis) + + var writer = es.writeArray(function (err, array){ + if(err) throw err //unpossible + it(array).deepEqual(readThis) + test.done() + }) + + reader.pipe(writer) +} + +exports ['read an array and pause it.'] = function (test) { + + var readThis = d.map(3, 6, 100, d.id) //array of multiples of 3 < 100 + + var reader = es.readArray(readThis) + + readStream(reader, 10, function (err, data) { + if(err) throw err + it(data).deepEqual([3, 6, 9, 12, 15, 18, 21, 24, 27, 30]) + readStream(reader, 10, function (err, data) { + it(data).deepEqual([33, 36, 39, 42, 45, 48, 51, 54, 57, 60]) + test.done() + }) + reader.resume() + }) + +} + +exports ['reader is readable, but not writeable'] = function (test) { + var reader = es.readArray([1]) + it(reader).has({ + readable: true, + writable: false + }) + + test.done() +} + + +exports ['read one item per tick'] = function (test) { + var readThis = d.map(3, 6, 100, d.id) //array of multiples of 3 < 100 + var drains = 0 + var reader = es.readArray(readThis) + var tickMapper = es.map(function (data,callback) { + process.nextTick(function () { + callback(null, data) + }) + //since tickMapper is returning false + //pipe should pause the writer until a drain occurs + return false + }) + reader.pipe(tickMapper) + readStream(tickMapper, function (err, array) { + it(array).deepEqual(readThis) + it(array.length).deepEqual(readThis.length) + it(drains).equal(readThis.length) + test.done() + }) + tickMapper.on('drain', function () { + drains ++ + }) + +} +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/readable.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/readable.asynct.js new file mode 100644 index 0000000..a33988c --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/readable.asynct.js @@ -0,0 +1,175 @@ + +var es = require('../') + , it = require('it-is').style('colour') + , u = require('ubelt') + +exports ['read an array'] = function (test) { + + + console.log('readable') + return test.end() + var readThis = u.map(3, 6, 100, u.id) //array of multiples of 3 < 100 + + console.log('readable') + + var reader = + es.readable(function (i, callback) { + if(i >= readThis.length) + return this.emit('end') + console.log('readable') + callback(null, readThis[i]) + }) + + var writer = es.writeArray(function (err, array){ + if(err) throw err + it(array).deepEqual(readThis) + test.done() + }) + + reader.pipe(writer) +} + +exports ['read an array - async'] = function (test) { + + var readThis = u.map(3, 6, 100, u.id) //array of multiples of 3 < 100 + + var reader = + es.readable(function (i, callback) { + if(i >= readThis.length) + return this.emit('end') + u.delay(callback)(null, readThis[i]) + }) + + var writer = es.writeArray(function (err, array){ + if(err) throw err + it(array).deepEqual(readThis) + test.done() + }) + + reader.pipe(writer) +} + + +exports ['emit data then call next() also works'] = function (test) { + + var readThis = u.map(3, 6, 100, u.id) //array of multiples of 3 < 100 + + var reader = + es.readable(function (i, next) { + if(i >= readThis.length) + return this.emit('end') + this.emit('data', readThis[i]) + next() + }) + + var writer = es.writeArray(function (err, array){ + if(err) throw err + it(array).deepEqual(readThis) + test.done() + }) + + reader.pipe(writer) +} + + +exports ['callback emits error, then stops'] = function (test) { + + var err = new Error('INTENSIONAL ERROR') + , called = 0 + + var reader = + es.readable(function (i, callback) { + if(called++) + return + callback(err) + }) + + reader.on('error', function (_err){ + it(_err).deepEqual(err) + u.delay(function() { + it(called).equal(1) + test.done() + }, 50)() + }) +} + +exports['readable does not call read concurrently'] = function (test) { + + var current = 0 + var source = es.readable(function(count, cb){ + current ++ + if(count > 100) + return this.emit('end') + u.delay(function(){ + current -- + it(current).equal(0) + cb(null, {ok: true, n: count}); + })(); + }); + + var destination = es.map(function(data, cb){ + //console.info(data); + cb(); + }); + + var all = es.connect(source, destination); + + destination.on('end', test.done) +} + + +// +// emitting multiple errors is not supported by stream. +// +// I do not think that this is a good idea, at least, there should be an option to pipe to +// continue on error. it makes alot ef sense, if you are using Stream like I am, to be able to emit multiple errors. +// an error might not necessarily mean the end of the stream. it depends on the error, at least. +// +// I will start a thread on the mailing list. I'd rather that than use a custom `pipe` implementation. +// +// basically, I want to be able use pipe to transform objects, and if one object is invalid, +// the next might still be good, so I should get to choose if it's gonna stop. +// re-enstate this test when this issue progresses. +// +// hmm. I could add this to es.connect by deregistering the error listener, +// but I would rather it be an option in core. + +/* +exports ['emit multiple errors, with 2nd parameter (continueOnError)'] = function (test) { + + var readThis = d.map(1, 100, d.id) + , errors = 0 + var reader = + es.readable(function (i, callback) { + console.log(i, readThis.length) + if(i >= readThis.length) + return this.emit('end') + if(!(readThis[i] % 7)) + return callback(readThis[i]) + callback(null, readThis[i]) + }, true) + + var writer = es.writeArray(function (err, array) { + if(err) throw err + it(array).every(function (u){ + it(u % 7).notEqual(0) + }).property('length', 80) + it(errors).equal(14) + test.done() + }) + + reader.on('error', function (u) { + errors ++ + console.log(u) + if('number' !== typeof u) + throw u + + it(u % 7).equal(0) + + }) + + reader.pipe(writer) +} +*/ + +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/replace.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/replace.asynct.js new file mode 100644 index 0000000..382082f --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/replace.asynct.js @@ -0,0 +1,51 @@ +var es = require('../') + , it = require('it-is').style('colour') + , d = require('ubelt') + , spec = require('stream-spec') + +var next = process.nextTick + +var fizzbuzz = '12F4BF78FB11F1314FB1617F19BF2223FB26F2829FB3132F34BF3738FB41F4344FB4647F49BF5253FB56F5859FB6162F64BF6768FB71F7374FB7677F79BF8283FB86F8889FB9192F94BF9798FB' + , fizz7buzz = '12F4BFseven8FB11F1314FB161sevenF19BF2223FB26F2829FB3132F34BF3seven38FB41F4344FB464sevenF49BF5253FB56F5859FB6162F64BF6seven68FBseven1Fseven3seven4FBseven6sevensevenFseven9BF8283FB86F8889FB9192F94BF9seven98FB' + +exports ['fizz buzz'] = function (test) { + + var readThis = d.map(1, 100, function (i) { + return ( + ! (i % 3 || i % 5) ? "FB" : + !(i % 3) ? "F" : + !(i % 5) ? "B" : + ''+i + ) + }) //array of multiples of 3 < 100 + + var reader = es.readArray(readThis) + var join = es.wait(function (err, string){ + it(string).equal(fizzbuzz) + test.done() + }) + reader.pipe(join) + +} + + +exports ['fizz buzz replace'] = function (test) { + var split = es.split(/(1)/) + var replace = es.replace('7', 'seven') + var x = spec(replace).through() + split + .pipe(replace) + .pipe(es.join(function (err, string) { + it(string).equal(fizz7buzz) + })) + + replace.on('close', function () { + x.validate() + test.done() + }) + + split.write(fizzbuzz) + split.end() + +} +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/simple-map.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/simple-map.asynct.js new file mode 100644 index 0000000..25121f9 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/simple-map.asynct.js @@ -0,0 +1,343 @@ +'use strict'; + +var es = require('../') + , it = require('it-is') + , u = require('ubelt') + , spec = require('stream-spec') + , Stream = require('stream') + , from = require('from') + , through = require('through') + +//REFACTOR THIS TEST TO USE es.readArray and es.writeArray + +function writeArray(array, stream) { + + array.forEach( function (j) { + stream.write(j) + }) + stream.end() + +} + +function readStream(stream, done) { + + var array = [] + stream.on('data', function (data) { + array.push(data) + }) + stream.on('error', done) + stream.on('end', function (data) { + done(null, array) + }) + +} + +//call sink on each write, +//and complete when finished. + +function pauseStream (prob, delay) { + var pauseIf = ( + 'number' == typeof prob + ? function () { + return Math.random() < prob + } + : 'function' == typeof prob + ? prob + : 0.1 + ) + var delayer = ( + !delay + ? process.nextTick + : 'number' == typeof delay + ? function (next) { setTimeout(next, delay) } + : delay + ) + + return es.through(function (data) { + if(!this.paused && pauseIf()) { + console.log('PAUSE STREAM PAUSING') + this.pause() + var self = this + delayer(function () { + console.log('PAUSE STREAM RESUMING') + self.resume() + }) + } + console.log("emit ('data', " + data + ')') + this.emit('data', data) + }) +} + +exports ['simple map'] = function (test) { + + var input = u.map(1, 1000, function () { + return Math.random() + }) + var expected = input.map(function (v) { + return v * 2 + }) + + var pause = pauseStream(0.1) + var fs = from(input) + var ts = es.writeArray(function (err, ar) { + it(ar).deepEqual(expected) + test.done() + }) + var map = es.through(function (data) { + this.emit('data', data * 2) + }) + + spec(map).through().validateOnExit() + spec(pause).through().validateOnExit() + + fs.pipe(map).pipe(pause).pipe(ts) +} + +exports ['simple map applied to a stream'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + //create event stream from + + var doubler = es.map(function (data, cb) { + cb(null, data * 2) + }) + + spec(doubler).through().validateOnExit() + + //a map is only a middle man, so it is both readable and writable + + it(doubler).has({ + readable: true, + writable: true, + }) + + readStream(doubler, function (err, output) { + it(output).deepEqual(input.map(function (j) { + return j * 2 + })) +// process.nextTick(x.validate) + test.done() + }) + + writeArray(input, doubler) + +} + +exports['pipe two maps together'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + //create event stream from + function dd (data, cb) { + cb(null, data * 2) + } + var doubler1 = es.map(dd), doubler2 = es.map(dd) + + doubler1.pipe(doubler2) + + spec(doubler1).through().validateOnExit() + spec(doubler2).through().validateOnExit() + + readStream(doubler2, function (err, output) { + it(output).deepEqual(input.map(function (j) { + return j * 4 + })) + test.done() + }) + + writeArray(input, doubler1) + +} + +//next: +// +// test pause, resume and drian. +// + +// then make a pipe joiner: +// +// plumber (evStr1, evStr2, evStr3, evStr4, evStr5) +// +// will return a single stream that write goes to the first + +exports ['map will not call end until the callback'] = function (test) { + + var ticker = es.map(function (data, cb) { + process.nextTick(function () { + cb(null, data * 2) + }) + }) + + spec(ticker).through().validateOnExit() + + ticker.write('x') + ticker.end() + + ticker.on('end', function () { + test.done() + }) +} + + +exports ['emit error thrown'] = function (test) { + + var err = new Error('INTENSIONAL ERROR') + , mapper = + es.map(function () { + throw err + }) + + mapper.on('error', function (_err) { + it(_err).equal(err) + test.done() + }) + +// onExit(spec(mapper).basic().validate) +//need spec that says stream may error. + + mapper.write('hello') + +} + +exports ['emit error calledback'] = function (test) { + + var err = new Error('INTENSIONAL ERROR') + , mapper = + es.map(function (data, callback) { + callback(err) + }) + + mapper.on('error', function (_err) { + it(_err).equal(err) + test.done() + }) + + mapper.write('hello') + +} + +exports ['do not emit drain if not paused'] = function (test) { + + var map = es.map(function (data, callback) { + u.delay(callback)(null, 1) + return true + }) + + spec(map).through().pausable().validateOnExit() + + map.on('drain', function () { + it(false).ok('should not emit drain unless the stream is paused') + }) + + it(map.write('hello')).equal(true) + it(map.write('hello')).equal(true) + it(map.write('hello')).equal(true) + setTimeout(function () {map.end()},10) + map.on('end', test.done) +} + +exports ['emits drain if paused, when all '] = function (test) { + var active = 0 + var drained = false + var map = es.map(function (data, callback) { + active ++ + u.delay(function () { + active -- + callback(null, 1) + })() + console.log('WRITE', false) + return false + }) + + spec(map).through().validateOnExit() + + map.on('drain', function () { + drained = true + it(active).equal(0, 'should emit drain when all maps are done') + }) + + it(map.write('hello')).equal(false) + it(map.write('hello')).equal(false) + it(map.write('hello')).equal(false) + + process.nextTick(function () {map.end()},10) + + map.on('end', function () { + console.log('end') + it(drained).ok('shoud have emitted drain before end') + test.done() + }) + +} + +exports ['map applied to a stream with filtering'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + + var doubler = es.map(function (data, callback) { + if (data % 2) + callback(null, data * 2) + else + callback() + }) + + readStream(doubler, function (err, output) { + it(output).deepEqual(input.filter(function (j) { + return j % 2 + }).map(function (j) { + return j * 2 + })) + test.done() + }) + + spec(doubler).through().validateOnExit() + + writeArray(input, doubler) + +} + +exports ['simple mapSync applied to a stream'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + + var doubler = es.mapSync(function (data) { + return data * 2 + }) + + readStream(doubler, function (err, output) { + it(output).deepEqual(input.map(function (j) { + return j * 2 + })) + test.done() + }) + + spec(doubler).through().validateOnExit() + + writeArray(input, doubler) + +} + +exports ['mapSync applied to a stream with filtering'] = function (test) { + + var input = [1,2,3,7,5,3,1,9,0,2,4,6] + + var doubler = es.mapSync(function (data) { + if (data % 2) + return data * 2 + }) + + readStream(doubler, function (err, output) { + it(output).deepEqual(input.filter(function (j) { + return j % 2 + }).map(function (j) { + return j * 2 + })) + test.done() + }) + + spec(doubler).through().validateOnExit() + + writeArray(input, doubler) + +} + +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/spec.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/spec.asynct.js new file mode 100644 index 0000000..6d5fb6b --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/spec.asynct.js @@ -0,0 +1,86 @@ +/* + assert that data is called many times + assert that end is called eventually + + assert that when stream enters pause state, + on drain is emitted eventually. +*/ + +var es = require('..') +var it = require('it-is').style('colour') +var spec = require('stream-spec') + +exports['simple stream'] = function (test) { + + var stream = es.through() + var x = spec(stream).basic().pausable() + + stream.write(1) + stream.write(1) + stream.pause() + stream.write(1) + stream.resume() + stream.write(1) + stream.end(2) //this will call write() + + process.nextTick(function (){ + x.validate() + test.done() + }) +} + +exports['throw on write when !writable'] = function (test) { + + var stream = es.through() + var x = spec(stream).basic().pausable() + + stream.write(1) + stream.write(1) + stream.end(2) //this will call write() + stream.write(1) //this will be throwing..., but the spec will catch it. + + process.nextTick(function () { + x.validate() + test.done() + }) + +} + +exports['end fast'] = function (test) { + + var stream = es.through() + var x = spec(stream).basic().pausable() + + stream.end() //this will call write() + + process.nextTick(function () { + x.validate() + test.done() + }) + +} + + +/* + okay, that was easy enough, whats next? + + say, after you call paused(), write should return false + until resume is called. + + simple way to implement this: + write must return !paused + after pause() paused = true + after resume() paused = false + + on resume, if !paused drain is emitted again. + after drain, !paused + + there are lots of subtle ordering bugs in streams. + + example, set !paused before emitting drain. + + the stream api is stateful. +*/ + + +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/split.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/split.asynct.js new file mode 100644 index 0000000..7d44bac --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/split.asynct.js @@ -0,0 +1,47 @@ +var es = require('../') + , it = require('it-is').style('colour') + , d = require('ubelt') + , join = require('path').join + , fs = require('fs') + , Stream = require('stream').Stream + , spec = require('stream-spec') + +exports ['es.split() works like String#split'] = function (test) { + var readme = join(__filename) + , expected = fs.readFileSync(readme, 'utf-8').split('\n') + , cs = es.split() + , actual = [] + , ended = false + , x = spec(cs).through() + + var a = new Stream () + + a.write = function (l) { + actual.push(l.trim()) + } + a.end = function () { + + ended = true + expected.forEach(function (v,k) { + //String.split will append an empty string '' + //if the string ends in a split pattern. + //es.split doesn't which was breaking this test. + //clearly, appending the empty string is correct. + //tests are passing though. which is the current job. + if(v) + it(actual[k]).like(v) + }) + //give the stream time to close + process.nextTick(function () { + test.done() + x.validate() + }) + } + a.writable = true + + fs.createReadStream(readme, {flags: 'r'}).pipe(cs) + cs.pipe(a) + +} + +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/stringify.js b/node_modules/jsftp/node_modules/event-stream/test/stringify.js new file mode 100644 index 0000000..d158185 --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/stringify.js @@ -0,0 +1,15 @@ + + + +var es = require('../') + +exports['handle buffer'] = function (t) { + + + es.stringify().on('data', function (d) { + t.equal(d.trim(), JSON.stringify('HELLO')) + t.end() + }).write(new Buffer('HELLO')) + +} +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/event-stream/test/writeArray.asynct.js b/node_modules/jsftp/node_modules/event-stream/test/writeArray.asynct.js new file mode 100644 index 0000000..5d705de --- /dev/null +++ b/node_modules/jsftp/node_modules/event-stream/test/writeArray.asynct.js @@ -0,0 +1,31 @@ + +var es = require('../') + , it = require('it-is').style('colour') + , d = require('ubelt') + +exports ['write an array'] = function (test) { + + var readThis = d.map(3, 6, 100, d.id) //array of multiples of 3 < 100 + + var writer = es.writeArray(function (err, array){ + if(err) throw err //unpossible + it(array).deepEqual(readThis) + test.done() + }) + + d.each(readThis, writer.write.bind(writer)) + writer.end() + +} + + +exports ['writer is writable, but not readable'] = function (test) { + var reader = es.writeArray(function () {}) + it(reader).has({ + readable: false, + writable: true + }) + + test.done() +} +require('./helper')(module) diff --git a/node_modules/jsftp/node_modules/parse-listing/..gitignore.un~ b/node_modules/jsftp/node_modules/parse-listing/..gitignore.un~ new file mode 100644 index 0000000000000000000000000000000000000000..ca0ee26151c63259f0668d0753afc452afc76130 GIT binary patch literal 1400 zcmWH`%$*;a=aT=FfytXsb9$2TxuT>)KJ%Kp_B(vnpsw2hLi^femJ3h%%TqK3@d@iw-w< literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/..travis.yml.un~ b/node_modules/jsftp/node_modules/parse-listing/..travis.yml.un~ new file mode 100644 index 0000000000000000000000000000000000000000..4612490c04fd474e0e47942109b426514ad5e8fa GIT binary patch literal 2243 zcmWH`%$*;a=aT=Ff$3jCfvCap^OJ-29y1iu)ymcm`=GXW?W9c&!P)2i*1vwmz`(!` z#4x}KWH16TD^vhP!!SeO=ICWCAX!F+_zO@4Oi&s`L8PDp|AByk8A*1ON#zg#M#mg* zbTBY{l|VBa6cr4njo@ekv5_MJ6lEYj7Z5XmqY11U#2W?F4*_5_EkuqcP|$<26gb;K zqX-lwEJ#*^)POK3JB~&XC=h50fKjv!2z@tlsC8)fIwG4$w1FS2^wRtScJI? z6nmpF1`7?E(}ZG-3m#*z2t&?ZAm@RyAR7>a#Yam)P>|3P03&QEwCw<{tYEX7GfHRx literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/.Makefile.un~ b/node_modules/jsftp/node_modules/parse-listing/.Makefile.un~ new file mode 100644 index 0000000000000000000000000000000000000000..80d06e74435ba64ac9680fa85ac00f73af8ab88d GIT binary patch literal 34267 zcmeI5OKcm*8OJHxQOvq|*s+r|uWm%SjZ=|K+{kTQE7psCq=_v`@*}wz6h&E#CDI^Q zsSE@KQuNdZ&}$EE&}%P6%Ue%<06q24Q;tE=HVxWa+ZaVsH0}2v&PV*ka+h3D)6;yw zXg=+7AM^d@o86t6{q|~g^PMZT`TF~*ecyZi>ra1j?c9hZQXKU%X zLw`8@+i(5+JKy~%l}deH(z)Eih0B*-I~|laDy3QwR+?e3y;ZA*LAm~BrBS+B5oM*) z2)7!g;Em>bwI*xUE9G0w@XkghXx3|`Mo_NWy*(uPK1t^#L6B=o6^xiy#6-h49v)?Br=Ru3AL+x14+%xfc^!zxP>ZLYG}Rizlb^;WQXYqi?Y1r{X0r6r9@f&eN&9>G7} zC1C}ZQfw{09|tm%&J0Qtx;oay*?G7$wjhqSFOdkCF`HRuz;V z#Gy>!lpyzMI$Kc19nS+;=9^bo!KDbvzZjASdf)MgQx1H~19bP%$pR~$QOtGMqa@2X zQH!mm6?V$*XOu$RGs?tzxQVBcK_67W_I@e`xO>)Q5VxwZF2}K+k76y4qe)Q%t!a&I zk$~N!cFULc-VgO$6zb%pLH0p9G^l&hWDvJ%Khm>Nq!YU|z#gW;0J;ZF263zQ13eQ3 zI%(4luBM;DmIur|XSZ-^@BKJO_8KR=83R@f`ltZ8$E?L1(>0NsDLN@)H76zIL(W#D;dM^ zP@5s#2fnHd$J2uk6agK!R{>V z)xzS`^9kBu9eR|_jR3e-B7d#5Msb%3^k4RZuGiX^o_zApt1aYG%2`=&g`z&^A1<(N+$%BRw zoQi<{y#amS&e3ZfIT^Z5u>K6JU!alzdFRa=-}IAZcB8_Q2-^U`gj!6vgj|($42od> zJNq-b&1H-sFcD= zPz`0f0pd%@uL|kwS1w&#zH#p2D}|{s4JTqE!eq@Ux9A5nG6+`3!W2NrXOXU_!4aJe_)U0*G+%e-Sr**>6s z!1axY>wmzR z#sUhmD?zhS4zfLB1&L?lglw%qEy&@2E2trzUL9X6)GWKqJC>B~`H72bMLWKiy<S_?KYw$g@F5B3u)y#FFQr-$nE7C@Xb@QWzndJ+z;h8j) zsj>0#g=_Qqc2(jnvnO*W^DEWbY59TtaI78K&+gXN6x58oBo)+}PPdS+DQcEo<_aB~ zvraYTT8i5b{7B7@X8zf7Rl_)H1V;_r*d4La0tLY5B&h(d?$KYj)hfHq86xWpbiV<5 zfBTld8>qI>!16qu1(0D*xsPr_M%lH>Xjo@VkF(!BY)ubOf#1bPH%T~$9BfZYQUQ0* zTCFi`&U{%Pn00J)lZ2>&_KQ>^VE3rWAVaB)m-T^B$3-_uNE&3(kD~(Wp0rwH*qrII z&ZM6(4fq4?>uh%e9uwUpA!vX_pSlX5d(di)VRMGdI)naW+?9II-t+?3Q&b!<_ngTf zL#fP`^?^A@_LV1eXJGvj6$QvWW-`c7Dx+n6V9b%xO_IH~394VFl7P6UOa>WBWwNX@ z@?4zQ+x}rX+*ZT%7{bCNA#{ax%_A~>$+oW< zq^2aPfFk0GVHC`hgAs<2POrV}gmotOA(P|4eIS=0ZaT=lB9NivKVgy_u>vHM#7zgfvvjt=3Wq$1Wqw2tRv1DUAM1qj`Us=!ZcY!TZ&qrE$Z6Y9 z;F+cq0y^Nrq(F}gMi@pI5$lZW5rgZ&c0Xb_u8iX9Ov(7)4>nnnAU8uNJ1sieP%}r& zJ04lai7KBWoMy+5U5>KOwt zJQq;So;_4GjjLRs{;1ny8ERZ>yu)L6eC*w_ML(blI+D;I==%ZHBE#nJKNZ%gH{#mA zm~HoykM!tveLpesi_!PUf$p^jL)YJ^By?_=H;ufPTFW@gzU}1OAN=^gV!RG9 literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/.README.md.un~ b/node_modules/jsftp/node_modules/parse-listing/.README.md.un~ new file mode 100644 index 0000000000000000000000000000000000000000..1fc33a34958cde295fad47d2dd9337f2cf5f922f GIT binary patch literal 6885 zcmeI1&rcIU6vvkef?!c12ECB6iAL0xmQa)?8e{aJ#zSNDLJ?NFEKACEv%9SbUbuQu zuU3A51)WA}xOA8rqNRy~2<$L&@(nMFJ>S*8pD1NO z3BByvp%N%#4L+j`L_w#KGOkEvbnX8juzobk=+M@`-f@&dpol&e1E>WGk!BQ zqH>#xc|AoCv>N?w@J%wy?7W^L^oi}>T)8)SDoT2xPQF&k5pbAfLlkrxy*2jmWW!v* z0K-G52?Ob`|=cm5}ZiGVs literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/.name b/node_modules/jsftp/node_modules/parse-listing/.idea/.name new file mode 100644 index 0000000..89accd2 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/.name @@ -0,0 +1 @@ +parse-listing \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/encodings.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/Project_Default.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..c66df00 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/profiles_settings.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/misc.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/misc.xml new file mode 100644 index 0000000..d16ceba --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/misc.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/modules.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/modules.xml new file mode 100644 index 0000000..44e569a --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/parse-listing.iml b/node_modules/jsftp/node_modules/parse-listing/.idea/parse-listing.iml new file mode 100644 index 0000000..6b8184f --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/parse-listing.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/scopes/scope_settings.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/vcs.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/.idea/workspace.xml b/node_modules/jsftp/node_modules/parse-listing/.idea/workspace.xml new file mode 100644 index 0000000..269558c --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.idea/workspace.xml @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GeneralJavaScript + + + JavaScript + + + Potentially confusing code constructsJavaScript + + + Probable bugsJavaScript + + + Spelling + + + + + Spelling + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1370715995211 + 1370715995211 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/.npmignore b/node_modules/jsftp/node_modules/parse-listing/.npmignore new file mode 100644 index 0000000..8214859 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/.npmignore @@ -0,0 +1,3 @@ +node_modules +reports +lcov-report diff --git a/node_modules/jsftp/node_modules/parse-listing/.package.json.un~ b/node_modules/jsftp/node_modules/parse-listing/.package.json.un~ new file mode 100644 index 0000000000000000000000000000000000000000..db0166b9d7446cb19d61c2803ac756aa5ca2c0ca GIT binary patch literal 11300 zcmeI2-)|IE6vvm+0#aIN`B6X%lSM^LceV?(N~1(0RZFB{t@=VLncbak2WDqBGqaV3 z(1Ztl@sBXZCw(x+HAWx&A9(b|kQj+E@s(%&e$U+7Uffb^!uT{NxqD~k{+#o<=iDrE-=@rQ0*qwewGdwuHRD?`c%%0MNA@pK?*zdS)M<64OF|kKNcK?!o#Dg z`)830f~mWLh{q-b;$j^UL9`A`U(NF;rvV`PL=g3*h)$tvt$q^|vGg0m64@bro@3IQ z$SSXPN4_>KiK;(_q^+snf{2Z=&s4Y#wxB=?8>A)Syb={Yc~=%J_*fJk1EMhs@>5Mfj6)L zkLsro-q_~wCKNr=+QYg^NRsSxdRily=Aiey(A$;LBRQ&{LVDvH^h!=#YDZzqi`@pz z@WrBLNM=v5g%a|li)G5)CzX(?fMH1(?nxPvA0z(fkT!Rp)9{`e9%1c8(n65ydtvTA zwOX>utKCEW0?&8^&|?X}+c-Op?JsazF3qm(M6p})y*TlLW^n?gM^WmDN%ocOP_P-^QOD{Q5ZF1jCM^;wm4xGebm4&deOPe-YPC7ZC>dF-Yw>hFfLk) zoZtdFb2`a#7;)kYZq)RwD^3u5zVBGC#5}iK>io54%kg}>9=0x8FDxfXd!}5L8oC0D z&d3~JLbuCd%WXSNQ73E1sNVFFj(ASuO3_X|lZJ~y*bpQaU!An4 z?XwlLKekDizSh7gnL^@Xed5k#Dpu)TD{i}WtCUz%lar53=m0wKy}BF3GU_!Ff^KnD zR)U2-uFSnBH}|$&COBT*-dZTv{IFJTIdS4f<#*=Zn4Mpk)$?D(P&@A6M?^(YblQXJ z5;HpV6G$UG$(=a0kfp$hkD5+=9D|)i;(Iynz?&~{#g44@A!#AUXOQyfNoyq29PGwS zc28q8^3&|LaBK=7fejgRBupA#{s?0zCA&iaJZ%CbJgT3vx$#}tczHlp!%{W{~S@+_{VN}k7&}CQ?b&Y zK5$)x+{kK(XE`fsAxij0EEr~jRi9&5;E+BVR z$PK5v96cq~Pob5a5G#A!7Xlrsm@y{ZCbvC?Dmg?bA$vin@`Y)BW}MlF;r9{ZOwDRJ uH(d+o|MJ%ip{8cdFT&jLxGr*eSuezxo?iC2hw5 literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/.parser.js.un~ b/node_modules/jsftp/node_modules/parse-listing/.parser.js.un~ new file mode 100644 index 0000000000000000000000000000000000000000..d83cf006bb34f37fef968ea335f4e56cddb97221 GIT binary patch literal 970 zcmWH`%$*;a=aT=FfobuzMR5-gKZuBzbk60EUQ=oqu{VTG>P))Sf5`+RC2tl621afM zRKN)2GC_qvGz>EYZstD70+EfsfTRG*WB^HlFc|y?0tRLnZ)aVKPcd3a`RJ4 hb5iwED+=<9N)&7rQu9iRDgzRWic^cMO&g!D0sv2WF!KNa literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/.parset_test.js.un~ b/node_modules/jsftp/node_modules/parse-listing/.parset_test.js.un~ new file mode 100644 index 0000000000000000000000000000000000000000..de15bb67e442f8634dcabd7a2c9d35689e8063e2 GIT binary patch literal 17400 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 reports/checkstyle.xml + +.PHONY: clean test coverage jshint checkstyle diff --git a/node_modules/jsftp/node_modules/parse-listing/README.md b/node_modules/jsftp/node_modules/parse-listing/README.md new file mode 100644 index 0000000..1cddefd --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/README.md @@ -0,0 +1,115 @@ +## parse-listing [![Build Status](https://secure.travis-ci.org/sergi/parse-listing.png)](http://travis-ci.org/sergi/parse-listing) + +Simple library that parses file listing input for different platforms. An +example file listing is the output of the `ls` command in unix, or the `dir` +command in DOS/Windows platforms. + +## Example + +```javascript +var Parser = require("parse-listing"); + +// Yeah yeah, multiline strings would make Crockford mad, I know. +var str = "drwxr-xr-x 5 1001 1001 4096 Jan 09 11:52 .\r\n\ +drwxr-xr-x 4 0 0 4096 Sep 19 13:50 ..\r\n\ +-rw------- 1 1001 1001 1118 Jan 09 12:09 .bash_history\r\n\ +-rw------- 1 1001 1001 943 Jan 09 11:52 .viminfo\r\n\ +drwxrwxr-x 5 1001 1001 4096 Jan 09 11:52 inaccessible\r\n\ +drwxrwxrwx 2 1001 1001 4096 Sep 21 11:20 project1\r\n\ +drwx------ 2 1001 1001 4096 Oct 19 16:17 project2\r\n"; + +Parser.parseEntries(str, function(err, entryArray) { + entryArray.forEach(function(entry, i) { + console.log("Name:",entry.name); + console.log("Type", entry.type); + console.log("Size:", entry.size); + }); +}); +``` + +The example above will print the name, type and size for every entry in the +listing. A parsed line becomes a JavaScript object like this: + +```javascript + { + type: 1, + size: 4096, + name: "project2", + time: 1382192220000, + owner: "1001", + group: "1001", + + userPermissions: { + read: true, + write: true, + exec: true + }, + + groupPermissions: { + read: false, + write: false, + exec: false + }, + + otherPermissions: { + read: false, + write: false, + exec: false + } + } +``` + +## Methods + +### parseEntries(string | array, callback) + +Asynchronously parses a list of entries either in a single string (entries will +be split by newlines) or in an array of strings. Invokes the callback when all +the entries have been processed, and passes an array of JavaScript objects. + +### parseEntry(string) + +Parses a single string such as: + +`drwxrwxrwx 2 1001 1001 4096 Sep 21 11:20 project` + +and returns the JavaScript object for it. There is no need to specify whether the +entry is in UNIX or MS-DOS/Windows format. The parser will find out by itself. + +## Installation + +With NPM: + + npm install parse-listing + +From GitHub: + + git clone https://github.com/sergi/parse-listing.git + +## License + +The MIT License + +Copyright(c) 2013 Sergi Mansilla + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + + diff --git a/node_modules/jsftp/node_modules/parse-listing/package.json b/node_modules/jsftp/node_modules/parse-listing/package.json new file mode 100644 index 0000000..706fee8 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/package.json @@ -0,0 +1,55 @@ +{ + "name": "parse-listing", + "id": "parse-listing", + "version": "1.0.1", + "description": "Small library to parse file listings into JavaScript objects", + "keywords": [ + "parse", + "ls", + "list", + "listing", + "ftp", + "unix", + "dos" + ], + "author": { + "name": "Sergi Mansilla", + "email": "sergi.mansilla@gmail.com", + "url": "http://sergimansilla.com" + }, + "homepage": "https://github.com/sergi/parse-listing", + "repository": { + "type": "git", + "url": "https://github.com/sergi/parse-listing.git" + }, + "bugs": { + "url": "https://github.com/sergi/parse-listing/issues" + }, + "dependencies": { + "async": "~0.2.9" + }, + "devDependencies": { + "mocha": "~1.10.0", + "istanbul": "~0.1.36", + "mocha-istanbul": "~0.2.0" + }, + "main": "src/parser.js", + "engines": { + "node": ">=0.6.21" + }, + "scripts": { + "test": "mocha -R spec -t 5000" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/sergi/parse-listing/blob/master/LICENSE" + } + ], + "readme": "## parse-listing [![Build Status](https://secure.travis-ci.org/sergi/parse-listing.png)](http://travis-ci.org/sergi/parse-listing)\n\nSimple library that parses file listing input for different platforms. An\nexample file listing is the output of the `ls` command in unix, or the `dir`\ncommand in DOS/Windows platforms.\n\n## Example\n\n```javascript\nvar Parser = require(\"parse-listing\");\n\n// Yeah yeah, multiline strings would make Crockford mad, I know.\nvar str = \"drwxr-xr-x 5 1001 1001 4096 Jan 09 11:52 .\\r\\n\\\ndrwxr-xr-x 4 0 0 4096 Sep 19 13:50 ..\\r\\n\\\n-rw------- 1 1001 1001 1118 Jan 09 12:09 .bash_history\\r\\n\\\n-rw------- 1 1001 1001 943 Jan 09 11:52 .viminfo\\r\\n\\\ndrwxrwxr-x 5 1001 1001 4096 Jan 09 11:52 inaccessible\\r\\n\\\ndrwxrwxrwx 2 1001 1001 4096 Sep 21 11:20 project1\\r\\n\\\ndrwx------ 2 1001 1001 4096 Oct 19 16:17 project2\\r\\n\";\n\nParser.parseEntries(str, function(err, entryArray) {\n entryArray.forEach(function(entry, i) {\n console.log(\"Name:\",entry.name);\n console.log(\"Type\", entry.type);\n console.log(\"Size:\", entry.size);\n });\n});\n```\n\nThe example above will print the name, type and size for every entry in the\nlisting. A parsed line becomes a JavaScript object like this:\n\n```javascript\n {\n type: 1,\n size: 4096,\n name: \"project2\",\n time: 1382192220000,\n owner: \"1001\",\n group: \"1001\",\n\n userPermissions: {\n read: true,\n write: true,\n exec: true\n },\n\n groupPermissions: {\n read: false,\n write: false,\n exec: false\n },\n\n otherPermissions: {\n read: false,\n write: false,\n exec: false\n }\n }\n```\n\n## Methods\n\n### parseEntries(string | array, callback)\n\nAsynchronously parses a list of entries either in a single string (entries will\nbe split by newlines) or in an array of strings. Invokes the callback when all\nthe entries have been processed, and passes an array of JavaScript objects.\n\n### parseEntry(string)\n\nParses a single string such as:\n\n`drwxrwxrwx 2 1001 1001 4096 Sep 21 11:20 project`\n\nand returns the JavaScript object for it. There is no need to specify whether the\nentry is in UNIX or MS-DOS/Windows format. The parser will find out by itself.\n\n## Installation\n\nWith NPM:\n\n npm install parse-listing\n\nFrom GitHub:\n\n git clone https://github.com/sergi/parse-listing.git\n\n## License\n\nThe MIT License\n\nCopyright(c) 2013 Sergi Mansilla \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n\n\n", + "_id": "parse-listing@1.0.1", + "dist": { + "shasum": "3c24c2694ad07dd88959cc1d4d8c4b77699113c8" + }, + "_from": "parse-listing@~1.0.0" +} diff --git a/node_modules/jsftp/node_modules/parse-listing/src/.parser.js.un~ b/node_modules/jsftp/node_modules/parse-listing/src/.parser.js.un~ new file mode 100644 index 0000000000000000000000000000000000000000..23e16e3c0aeb7d6da7b86ec48c8c1dda699edb61 GIT binary patch literal 27249 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 + * @license https://github.com/sergi/parse-listing/blob/master/LICENSE MIT License + */ + +"use strict"; + +var async = require("async"); + +/** + * this is the regular expression used by Unix Parsers. + * + * Permissions: + * r the file is readable + * w the file is writable + * x the file is executable + * - the indicated permission is not granted + * L mandatory locking occurs during access (the set-group-ID bit is + * on and the group execution bit is off) + * s the set-user-ID or set-group-ID bit is on, and the corresponding + * user or group execution bit is also on + * S undefined bit-state (the set-user-ID bit is on and the user + * execution bit is off) + * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and + * execution is on + * T the 1000 bit is turned on, and execution is off (undefined bit- + * state) + */ + +var RE_UnixEntry = new RegExp( + "([bcdlfmpSs-])" + + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" + + "(\\d+)\\s+" + + "(\\S+)\\s+" + + "(?:(\\S+)\\s+)?" + + "(\\d+)\\s+" + + //numeric or standard format date + + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+" + + // year (for non-recent standard format) + // or time (for numeric or recent standard format) + + "(\\d+(?::\\d+)?)\\s*" + + //+ "(\\S*)(\\s*.*)" + + "(.*)" +); + +// MSDOS format +// 04-27-00 09:09PM

licensed +// 07-18-00 10:16AM pub +// 04-14-00 03:47PM 589 readme.htm +var RE_DOSEntry = new RegExp( + "(\\S+)\\s+(\\S+)\\s+" + + "()?\\s*" + + "([0-9]+)?\\s*" + + "(\\S.*)" +); + +// Not used for now +// var RE_VMSEntry = new RegExp( +// "(.*;[0-9]+)\\s*" +// + "(\\d+)/\\d+\\s*" +// + "(\\S+)\\s+(\\S+)\\s+" +// + "\\[(([0-9$A-Za-z_]+)|([0-9$A-Za-z_]+),([0-9$a-zA-Z_]+))\\]?\\s*" +// + "\\([a-zA-Z]*,[a-zA-Z]*,[a-zA-Z]*,[a-zA-Z]*\\)" +// ); + +exports.nodeTypes = { + FILE_TYPE: 0, + DIRECTORY_TYPE: 1, + SYMBOLIC_LINK_TYPE: 2, + UNKNOWN_TYPE: 3 +}; + +exports.permissions = { + READ_PERMISSION: 0, + WRITE_PERMISSION: 1, + EXECUTE_PERMISSION: 2 +}; + +exports.access = { + USER_ACCESS: 0, + GROUP_ACCESS: 1, + WORLD_ACCESS: 2 +}; + +/** + * Selects which parser to use depending on the first character of the line to + * parse. + * + * @param entries {Array.|string} FTP file entry line. + * @param callback {Function} Callback function with error or result. + */ +exports.parseEntries = function(entries, callback) { + if (typeof entries === "string") { + entries = entries.split(/(\r\n|\n)/); + } + async.map(entries, parseEntry, callback); +}; + +/** + * Selects which parser to use depending on the first character of the line to + * parse. + * + * @param entry {string} FTP file entry line + * @returns {Object|null} Parsed object with the file entry properties + */ +var parseEntry = exports.parseEntry = function(entry) { + var c = entry.charAt(0); + + if ('bcdlps-'.indexOf(c) > -1) { + return parsers.unix(entry); + } + else if ('0123456789'.indexOf(c) > -1) { + return parsers.msdos(entry); + } + else { + return null; + } +}; + +var parsers = { + unix: function(entry) { + var target, writePerm, readPerm, execPerm; + var group = entry.match(RE_UnixEntry); + + if (group) { + var type = group[1]; + //var hardLinks = group[15]; + var usr = group[16]; + var grp = group[17]; + var size = group[18]; + var name = group[21]; + + var date; + // Check whether we are given the time (recent file) or the year + // (older file) in the file listing. + if (group[20].indexOf(":") === -1) { + date = +new Date(group[19] + " " + group[20]).getTime(); + } + else { + var currentMonth = new Date().getMonth(); + var month = new Date(group[19]).getMonth(); + var year = new Date().getFullYear() - (currentMonth < month ? 1 : 0); + + date = +new Date(group[19] + " " + group[20] + " " + year); + } + + // Ignoring '.' and '..' entries for now + if (name === "." || name === "..") + return; + + //var endtoken = group[22]; + + switch (type[0]) { + case 'd': + type = exports.nodeTypes.DIRECTORY_TYPE; + break; + case 'l': + type = exports.nodeTypes.SYMBOLIC_LINK_TYPE; + var isLink = /(.*)\s->\s(.*)/.exec(name); + if (isLink) { + name = isLink[1]; + target = isLink[2]; + } + break; + case 'b': + case 'c': + // break; - fall through + case 'f': + case '-': + type = exports.nodeTypes.FILE_TYPE; + break; + default: + type = exports.nodeTypes.UNKNOWN_TYPE; + } + + var file = { + name: name, + type: type, + time: date, + size: size, + owner: usr, + group: grp + }; + + if (target) file.target = target; + + var g = 4; + ["user", "group", "other"].forEach(function(access) { + // Use != '-' to avoid having to check for suid and sticky bits + readPerm = group[g] !== "-"; + writePerm = group[g + 1] !== "-"; + + var execPermStr = group[g + 2]; + + file[access + "Permissions"] = { + read: readPerm, + write: writePerm, + exec: (execPermStr !== "-") && !(/[A-Z]/.test(execPermStr[0])) + }; + + g += 4; + }); + + return file; + } + }, + + msdos: function(entry) { + var group = entry.match(RE_DOSEntry); + var type; + + if (!group) + return null; + + var replacer = function replacer(str, hour, min, ampm, offset, s) { + return hour + ":" + min + " " + ampm; + }; + + var time = group[2].replace(/(\d{2}):(\d{2})([AP]M)/, replacer); + var date = new Date(group[1] + " " + time).getTime(); + var dirString = group[3]; + var size = group[4]; + var name = group[5]; + + if (null == name || name === "." || name === "..") + return null; + + if (dirString === "") { + type = exports.nodeTypes.DIRECTORY_TYPE; + size = 0; + } + else { + type = exports.nodeTypes.FILE_TYPE; + } + + return { + name: name, + type: type, + time: date, + size: size + }; + } +}; + +/* + * MLSx commands are not being used for now. + * + * http://rfc-ref.org/RFC-TEXTS/3659/chapter7.html + * http://www.rhinosoft.com/newsletter/NewsL2005-07-06.asp?prod=rs + * + var reKV = /(.+?)=(.+?);/; + exports.parseMList = function(line) { + var ret; + var result = line.trim().split(reKV); + + if (result && result.length > 0) { + ret = {}; + if (result.length === 1) { + ret.name = result[0].trim(); + } + else { + var i, k, v, len = result.length; + for (i = 1; i < len; i += 3) { + k = result[i]; + v = result[i+1]; + ret[k] = v; + } + ret.name = result[result.length-1].trim(); + } + } else + ret = line; + + return ret; + } + */ + + diff --git a/node_modules/jsftp/node_modules/parse-listing/test/.parser.test.js.un~ b/node_modules/jsftp/node_modules/parse-listing/test/.parser.test.js.un~ new file mode 100644 index 0000000000000000000000000000000000000000..98e2321240f547acab8e559ec80c0b2aaa8c6927 GIT binary patch literal 599 zcmWH`%$*;a=aT=Ff$5Q3H}lgEHxJgD@6S#yJU#2fJg+OO4#x6ZXFgB5zdk>nfq~hK zfq_A-EU`!-AhD=8wMfBMp(wSmG_xpGLrG6hKPNLuzW^+uq-hOQ$qmGkK+FimKnl(V zi7^Cj7G-3C$i`m)37`Rx6wDBY|3CoNj;3goORW$9Mh62lI=)H(4dnu29%!tCqJqen JH*I{r3IIu5J5K-r literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/test/.parser_test.js.un~ b/node_modules/jsftp/node_modules/parse-listing/test/.parser_test.js.un~ new file mode 100644 index 0000000000000000000000000000000000000000..a798f2d129735686e512f153ad8efd36f2c77709 GIT binary patch literal 28357 zcmeI5U2Ggz701_WCv6kkO=346ErlBwu$_4A-StQ8bz4XihhotrrmdmWQP|pRchP#+ z?3YUmLLj~>;DHwq1&Ie9D5V4>KOn>#5)Zr~@z@8XLJ9N%fuJ-L5S;&=duGqtv+nhb z_w8PFa_@|H?#!Iu{h#|e)5ZMy_Z}}^EWH+scmDJFlOu2b{KD%mzBT=m^nss!|HiHP zS6}&M&u3q{y!gXkocw$&);Sf6#RlrxvbvfrS8`?bfGX!cS<9DmLj$9uV;lP5!0;^n z>M(7mXzQRYLmL8O-eL<|$NtuZzxKzj{|0}h5ceYeFaCr~bod_yofRK&@jhE%Ve9ZW zHI7*9jgyr3A==pdKyd-o3;Cxuvel;v`$oC6oU2qubH)0TgNv6Qy?*JjtJg1FSsc8k z-lqnsEYMFA%cc6D%JA;s?BwS(X=K+)8wXIwrS8H3M;caWXCTtw-cGukh&(_S1)4;l zz8U>lH8{3f-SG53ILu7JuuI%E_&TP-Ani>O(-$<;j)rMOxIj#$f z?^^rPsc|(|Tv4S}RjFpHwaNfHP>|xhksaiR({*s<*b*UQ8*&dza%hXtNtzt$wz-w^ z<}(Q$6os@(C6g(n8VT%7Bxk17mF22R&8XB=CUp)CrBCFRtLdfkQgMkgiu=St_$Y1U zr*!~*5Wk2$M;um&5Maqgb3W3SOuBAj>VI^9Jc#)H^aC`59^Mz?%Et}7$N1tD13hlc z;TA>-orEP9&Dlr?;eKj2+#lEv+zr~G+h^_v5y;<3n?B~m4UPlIymI{T^W+kJYwrN^{V86I$kOWb!X-8C8CQQ}F1c|AKjdL=0-sK$*o{*!YpltK6PbzWW*-jL7}(~3a^R%Q)`%M0u&)KN$9xcc z_yU96?!%*U2WLz~aX=aPG4Hr`8(Sh|Y{UIo$^B)z&<%3o%$qk_fscoH7G$Xv0R~Xq`UG?NlZ0BnQrOwfiS(0`} z>v?kCD)FFBjd{B}hsHqq9`Rf#1t9+jjydwMLWE#L{?kF^XXv8v)mQhGd}GiAnBO5D z3`GFgKZIirJFE~P*kkYVx>+oE#(rgAvHJs~pnR`*E-C@>FY&&k&Xx!n+fe_85PSTK z!u$~X$+sC7gJ9n&ZjMmKzefAI7w_;1%Y;eV;QN-u_f<0g*Qu7CrmLZr-Z8G7@Rr&Dbd9`!4Z#R32Oz5cY_cv?&AL>W>53 zamTp~oTKYP!XMi{x8bAr6INwFwJZQ|0E#vaR2VuZX$K|H)!b~Au(3@6_ouuO<#!;;3TXG|t_iglVMoqwxA-R3Fv zPxQmrjkktUk3feqU@c~xe+8T|15#0P0Q)8o)W+oPfUh#BP9CQ)D;H49{Fie;8l-;B!nM(aWkMu( zIOrgSzAP(fAcBq%(4V@GmC3PGXih7VUygwsM`+_l08x$qk@j^9+>sT4Ogg{X0F;6Z!b_qFFt!5^^

y)JXHv|L-K=2~U++5ColX$Df)Rk(Q| zg74%W25!(fR>1$FfgkaJ^Y{o)XPjy&cQeBXv8KhfiKV`(>plsigiTuUyCvNw7^DalA}l;;cD6ZhNx+Uh##mRIQYIbA||Zh|5w95>N)1^@TaZdx5f)yW9=0O zLf!EX+Udsu*Slh~go|zP_Vx>Hcl@2q|Ax>ut*`WgdC@8Dy?fsD(&Fak=2(s1rnsD4 z&y7`cm1<_mRLamTt?cpxfky6LaB$fY;o^oR$*TXNJmMywb*z8C0%vAW#As5 zADzeBr>484-&UkA#V|;f#kg2;*`x0*xTUuhsUe*l&rD33$z{sqp&gk_+GpZTQBy8+ z$c5VUi86n+Ce>7jdy5vYi3 z0D8w2Yi)+m|5z34du6e{C5koLnnzl^p$AWd{sad?*g@xd!C_-dgo~}PDT(b(Pc^z3 z3D@VmmI)i-(_Orj1nEALQJvHDn$8CWN&W4w6GRdd)Tn89yj1CbOw3Q9 zm#6!9t-l89zX1)py|i%vdH=o~Z8l5D*oO8MNgF6gbUy8%Ps^F%7$(KHhEs7p_dzMP zNR-%y++*9x9Tnu-_hC3GS3Dwa6`OV?PO>mbgkS^y@$KLt*;%?M9CXmvq?%SSg#8j^ z7iE6XJlP?NG3e*G8-uhzqA%tDJBT~~zbkfe)gX17xI<6`Ip?Z{QQDUza&pR3w|C&G znOyr^H9+4k9t?%RNmdx7y-5P^O?R6AvC#I`Roh%OINu?j4Q1di+FTVajMBa&^`W|? ztH$)R>_a`V{i7s)*U3*<^q~SiNy0A;NX_FhQ5L}8^`T*nwg=*WQ1g#_!&=~fTr|=4 KuMd6mrT+uCGsRK> literal 0 HcmV?d00001 diff --git a/node_modules/jsftp/node_modules/parse-listing/test/parser.test.js b/node_modules/jsftp/node_modules/parse-listing/test/parser.test.js new file mode 100644 index 0000000..017bfb5 --- /dev/null +++ b/node_modules/jsftp/node_modules/parse-listing/test/parser.test.js @@ -0,0 +1,804 @@ +/* + * @package jsftp + * @copyright Copyright(c) 2011 Ajax.org B.V. + * @author Sergi Mansilla + * @license https://github.com/sergi/jsFTP/blob/master/LICENSE MIT License + */ + +"use strict"; + +var assert = require("assert"); +var Parser = require("../src/parser"); + +describe("jsftp file listing parser", function() { + it("test ftp unix STAT responses", function() { + var str = "drwxr-xr-x 5 1001 1001 4096 Jan 09 11:52 .\r\n\ +drwxr-xr-x 4 0 0 4096 Sep 19 13:50 ..\r\n\ +-rw------- 1 1001 1001 1118 Jan 09 12:09 .bash_history\r\n\ +-rw------- 1 1001 1001 943 Jan 09 11:52 .viminfo\r\n\ +drwxrwxr-x 5 1001 1001 4096 Jan 09 11:52 inaccessible\r\n\ +drwxrwxrwx 2 1001 1001 4096 Sep 21 11:20 project1\r\n\ +drwx------ 2 1001 1001 4096 Oct 19 16:17 project2\r\n"; + + var unixEntries = [ + { + type: 0, + size: 1118, + name: ".bash_history", + time: +new Date("Jan 9 12:09 " + new Date().getFullYear()), + owner: "1001", + group: "1001", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: false, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 0, + size: 943, + name: ".viminfo", + time: +new Date("Jan 9 11:52 " + new Date().getFullYear()), + owner: "1001", + group: "1001", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: false, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 1, + size: 4096, + name: "inaccessible", + time: +new Date("Jan 9 11:52 " + new Date().getFullYear()), + owner: "1001", + group: "1001", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: true, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + name: "project1", + time: +new Date("Sep 21 11:20 " + new Date().getFullYear()), + owner: "1001", + group: "1001", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: true, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: true, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + name: "project2", + time: +new Date("Oct 19 16:17 " + new Date().getFullYear()), + owner: "1001", + group: "1001", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: false, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + } + ]; + + Parser.parseEntries(str, function(err, entryArray) { + entryArray.forEach(function(entry, i) { + assert.equal(unixEntries[i].type, entry.type); + assert.equal(unixEntries[i].size, entry.size); + assert.equal(unixEntries[i].name, entry.name); + //assert.equal(unixEntries[i].time, entry.time); + assert.equal(unixEntries[i].owner, entry.owner); + assert.equal(unixEntries[i].group, entry.group); + + assert.equal(unixEntries[i].userReadPerm, entry.userPermissions.read); + assert.equal(unixEntries[i].userWritePerm, entry.userPermissions.write); + assert.equal(unixEntries[i].userExecPerm, entry.userPermissions.exec); + + assert.equal(unixEntries[i].groupReadPerm, entry.groupPermissions.read); + assert.equal(unixEntries[i].groupWritePerm, entry.groupPermissions.write); + assert.equal(unixEntries[i].groupExecPerm, entry.groupPermissions.exec); + + assert.equal(unixEntries[i].otherReadPerm, entry.otherPermissions.read); + assert.equal(unixEntries[i].otherWritePerm, entry.otherPermissions.write); + assert.equal(unixEntries[i].otherExecPerm, entry.otherPermissions.exec); + }); + }) + }); + + it("test ftp unix LIST responses", function() { + var str = "drwx--x--- 10 mrclash adm 4096 Aug 9 14:48 .\r\n\ + drwx--x--- 10 mrclash adm 4096 Aug 9 14:48 ..\r\n\ + -rw-r--r-- 1 mrclash pg223090 260 Mar 25 2008 .alias\r\n\ + -rw------- 1 mrclash pg223090 2219 Sep 5 2010 .bash_history\r\n\ + -rw-r--r-- 1 mrclash pg223090 55 Mar 25 2008 .bashrc\r\n\ + drwx------ 2 mrclash pg223090 4096 Aug 9 14:39 .ssh\r\n\ + -rw-r--r-- 1 mrclash pg223090 18 Aug 8 13:06 Cloud9 FTP connection test.\r\n\ + -rwxr-xr-x 1 mrclash pg223090 68491314 Jan 22 2009 Documents.zip\r\n\ + -rwxr-xr-x 1 mrclash pg223090 141 Nov 1 2008 EcPxMptYISIdOSjS.XFV.Q--.html\r\n\ + dr-xr-x--- 7 mrclash dhapache 4096 May 29 07:47 logs\r\n\ + drwxr-xr-x 7 mrclash pg223090 4096 Aug 9 14:48 re-alpine.git\r\n\ + -rwxr-xr-x 1 mrclash pg223090 312115 Jan 22 2009 restaurants.csv\r\n\ + drwxr-xr-x 12 mrclash pg223090 4096 Jul 24 02:42 sergimansilla.com\r\n\ + drwxr-xr-x 10 mrclash pg223090 4096 Aug 3 2009 svn\r\n\ + -rwxr-xr-x 1 mrclash pg223090 76 Aug 9 14:47 sync-alpine.sh\r\n\ + drwxr-xr-x 2 mrclash pg223090 4096 Aug 4 10:00 test_c9\r\n\ + -rw-r--r-- 1 mrclash pg223090 4 Aug 4 09:11 testfile.txt\r\n\ + lrwxr-xr-x 1 sergi staff 11 Jul 7 2011 .vimrc -> .vim/.vimrc\r\n"; + + var unixEntries = [ + { + //line: "-rw-r--r-- 1 mrclash pg223090 260 Mar 25 2008 .alias", + type: 0, + size: 260, + name: ".alias", + time: +new Date("Mar 25 2008"), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: false + }, + { + //line: "-rw------- 1 mrclash pg223090 2219 Sep 5 2010 .bash_history", + type: 0, + size: 2219, + name: ".bash_history", + time: +new Date("Sep 5 2010"), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: false, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 0, + size: 55, + name: ".bashrc", + time: +new Date("Mar 25 2008"), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 1, + size: 4096, + name: ".ssh", + time: +new Date("Aug 9 14:39 " + new Date().getFullYear()), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: false, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 0, + size: 18, + name: "Cloud9 FTP connection test.", + time: +new Date("Aug 8 13:06 " + new Date().getFullYear()), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 0, + size: 68491314, + name: "Documents.zip", + time: +new Date("Jan 22 2009"), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 0, + size: 141, + name: "EcPxMptYISIdOSjS.XFV.Q--.html", + time: +new Date("Nov 1 2008"), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + name: "logs", + time: +new Date("May 29 07:47 " + new Date().getFullYear()), + owner: "mrclash", + group: "dhapache", + + userReadPerm: true, + userWritePerm: false, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 1, + size: 4096, + name: "re-alpine.git", + time: +new Date("Aug 9 14:48 " + new Date().getFullYear()), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 0, + size: 312115, + time: +new Date("Jan 22 2009"), + name: "restaurants.csv", + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + time: +new Date("Jul 24 02:42 " + new Date().getFullYear()), + name: "sergimansilla.com", + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + time: +new Date("Aug 3 2009"), + name: "svn", + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 0, + size: 76, + time: +new Date("Aug 9 14:47 " + new Date().getFullYear()), + name: "sync-alpine.sh", + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + time: +new Date("Aug 4 10:00 " + new Date().getFullYear()), + name: "test_c9", + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 0, + size: 4, + time: +new Date("Aug 4 09:11 " + new Date().getFullYear()), + name: "testfile.txt", + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: false + }, + { + type: 2, + size: 11, + time: +new Date("Jul 7 2011"), + name: ".vimrc", + target: ".vim/.vimrc", + owner: "sergi", + group: "staff", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + } + ]; + + var str2 = "\ +-drwxr-x--- 2 userName alternc 4096 Aug 22 03:45 .\r\n\ +-drwxr-x--- 5 userName alternc 4096 Aug 22 03:45 ..\r\n\ +--rw-r----- 1 userName alternc 460 Aug 22 03:45 test1\r\n\ +--rw-r----- 1 userName alternc 560 Aug 22 03:47 test2\r\n"; + + var unixEntries2 = [ + { + //line: "-rw-r--r-- 1 mrclash pg223090 260 Mar 25 2008 .alias", + type: 0, + size: 460, + name: "test1", + time: +new Date("Aug 22 03:45 " + new Date().getFullYear()), + owner: "userName", + group: "alternc", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + }, + { + //line: "-rw-r--r-- 1 mrclash pg223090 260 Mar 25 2008 .alias", + type: 0, + size: 560, + name: "test2", + time: +new Date("Aug 22 03:47 " + new Date().getFullYear()), + owner: "userName", + group: "alternc", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: false, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + } + ]; + + Parser.parseEntries(str, function(err, entryArray) { + entryArray.forEach(function(entry, i) { + assert.equal(unixEntries[i].type, entry.type); + assert.equal(unixEntries[i].size, entry.size); + assert.equal(unixEntries[i].name, entry.name); + //assert.equal(unixEntries[i].time, entry.time); + assert.equal(unixEntries[i].owner, entry.owner); + assert.equal(unixEntries[i].group, entry.group); + + assert.equal(unixEntries[i].userReadPerm, entry.userPermissions.read); + assert.equal(unixEntries[i].userWritePerm, entry.userPermissions.write); + assert.equal(unixEntries[i].userExecPerm, entry.userPermissions.exec); + + assert.equal(unixEntries[i].groupReadPerm, entry.groupPermissions.read); + assert.equal(unixEntries[i].groupWritePerm, entry.groupPermissions.write); + assert.equal(unixEntries[i].groupExecPerm, entry.groupPermissions.exec); + + assert.equal(unixEntries[i].otherReadPerm, entry.otherPermissions.read); + assert.equal(unixEntries[i].otherWritePerm, entry.otherPermissions.write); + assert.equal(unixEntries[i].otherExecPerm, entry.otherPermissions.exec); + }); + }); + + Parser.parseEntries(str2, function(err, entryArray) { + entryArray.forEach(function(entry, i) { + assert.equal(unixEntries2[i].type, entry.type); + assert.equal(unixEntries2[i].size, entry.size); + assert.equal(unixEntries2[i].name, entry.name); + //assert.equal(unixEntries2[i].time, entry.time); + assert.equal(unixEntries2[i].owner, entry.owner); + assert.equal(unixEntries2[i].group, entry.group); + + assert.equal(unixEntries2[i].userReadPerm, entry.userPermissions.read); + assert.equal(unixEntries2[i].userWritePerm, entry.userPermissions.write); + assert.equal(unixEntries2[i].userExecPerm, entry.userPermissions.exec); + + assert.equal(unixEntries2[i].groupReadPerm, entry.groupPermissions.read); + assert.equal(unixEntries2[i].groupWritePerm, entry.groupPermissions.write); + assert.equal(unixEntries2[i].groupExecPerm, entry.groupPermissions.exec); + + assert.equal(unixEntries2[i].otherReadPerm, entry.otherPermissions.read); + assert.equal(unixEntries2[i].otherWritePerm, entry.otherPermissions.write); + assert.equal(unixEntries2[i].otherExecPerm, entry.otherPermissions.exec); + }); + }) + }); + + + it("test ftp windows/DOS LIST responses", function() { + var dosEntries = [ + { + line: '04-27-00 09:09PM

licensed', + type: 1, + size: 0, + time: +(new Date("04-27-00 09:09 PM")), + name: 'licensed', + }, + { + line: '11-18-03 10:16AM pub', + type: 1, + size: 0, + time: +(new Date("11-18-03 10:16 AM")), + name: 'pub', + }, + { + line: '04-14-99 03:47PM 589 readme.htm', + type: 0, + size: 589, + time: +(new Date("04-14-99 03:47 PM")), + name: 'readme.htm' + } + ]; + + dosEntries.forEach(function(entry) { + var result = Parser.parseEntry(entry.line); + + assert.equal(result.type, entry.type); + assert.equal(result.size, entry.size); + assert.equal(result.name, entry.name); + assert.equal(result.time, entry.time); + }); + }); + + it("test truncated listing", function() { + var truncated = "\ +drwxr-xr-x 33 0 0 4096 Nov 28 01:19 .\r\n\ +drwxr-xr-x 33 0 0 4096 Nov 28 01:19 ..\r\n\ +-rwxr-xr-x 1 mrclash pg223090 68491314 Jan 22 2009 Documents.zip\r\n\ +drwxr-xr-x 3 0 0 4096 Apr 16 2011 bourd\n\ +arie\r\n\ +drwxr-xr-x 2 0 0 4096 Apr 16 2011 denton\r\n\ +drwx------ 2 0 0 4096 Apr 16 2011 lost+found\r\n" + + var unixEntries = [ + { + type: 0, + size: 68491314, + name: "Documents.zip", + time: +new Date("Jan 22 2009"), + owner: "mrclash", + group: "pg223090", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + name: "bourdarie", + time: +new Date("Apr 16 2011"), + owner: "0", + group: "0", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + name: "denton", + time: +new Date("Apr 16 2011"), + owner: "0", + group: "0", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: true, + groupWritePerm: false, + groupExecPerm: true, + + otherReadPerm: true, + otherWritePerm: false, + otherExecPerm: true + }, + { + type: 1, + size: 4096, + name: "lost+found", + time: +new Date("Apr 16 2011"), + owner: "0", + group: "0", + + userReadPerm: true, + userWritePerm: true, + userExecPerm: true, + + groupReadPerm: false, + groupWritePerm: false, + groupExecPerm: false, + + otherReadPerm: false, + otherWritePerm: false, + otherExecPerm: false + } + ]; + + Parser.parseEntries(truncated, function(err, entryArray) { + entryArray.forEach(function(entry, i) { + assert.equal(unixEntries[i].type, entry.type); + assert.equal(unixEntries[i].size, entry.size); + assert.equal(unixEntries[i].name, entry.name); + //assert.equal(unixEntries[i].time, entry.time); + assert.equal(unixEntries[i].owner, entry.owner); + assert.equal(unixEntries[i].group, entry.group); + + assert.equal(unixEntries[i].userReadPerm, entry.userPermissions.read); + assert.equal(unixEntries[i].userWritePerm, entry.userPermissions.write); + assert.equal(unixEntries[i].userExecPerm, entry.userPermissions.exec); + + assert.equal(unixEntries[i].groupReadPerm, entry.groupPermissions.read); + assert.equal(unixEntries[i].groupWritePerm, entry.groupPermissions.write); + assert.equal(unixEntries[i].groupExecPerm, entry.groupPermissions.exec); + + assert.equal(unixEntries[i].otherReadPerm, entry.otherPermissions.read); + assert.equal(unixEntries[i].otherWritePerm, entry.otherPermissions.write); + assert.equal(unixEntries[i].otherExecPerm, entry.otherPermissions.exec); + }); + }); + }); + + + /* + * We are not supporting MLSx commands yet + * + * http://rfc-ref.org/RFC-TEXTS/3659/chapter7.html + * http://www.rhinosoft.com/newsletter/NewsL2005-07-06.asp?prod=rs + * + "test parse MLSD command lines" : function(next) { + var lines = [ + { + line: "Type=file;Size=17709913;Modify=20050502182143; Choices.mp3", + Type: "file", + Size: "17709913", + Modify: "20050502182143", + name: "Choices.mp3" + }, + { + line: "Type=cdir;Perm=el;Unique=keVO1+ZF4; test", + type: "file", + perm: "el", + + }, + { + line: "Type=pdir;Perm=e;Unique=keVO1+d?3; .." + } + ]; + + + + + //"Type=cdir;Perm=el;Unique=keVO1+ZF4; test", + //"Type=pdir;Perm=e;Unique=keVO1+d?3; ..", + //"Type=OS.unix=slink:/foobar;Perm=;Unique=keVO1+4G4; foobar", + //"Type=OS.unix=chr-13/29;Perm=;Unique=keVO1+5G4; device", + //"Type=OS.unix=blk-11/108;Perm=;Unique=keVO1+6G4; block", + //"Type=file;Perm=awr;Unique=keVO1+8G4; writable", + //"Type=dir;Perm=cpmel;Unique=keVO1+7G4; promiscuous", + //"Type=dir;Perm=;Unique=keVO1+1t2; no-exec", + //"Type=file;Perm=r;Unique=keVO1+EG4; two words", + //"Type=file;Perm=r;Unique=keVO1+IH4; leading space", + //"Type=file;Perm=r;Unique=keVO1+1G4; file1", + //"Type=dir;Perm=cpmel;Unique=keVO1+7G4; incoming", + //"Type=file;Perm=r;Unique=keVO1+1G4; file2", + //"Type=file;Perm=r;Unique=keVO1+1G4; file3", + //"Type=file;Perm=r;Unique=keVO1+1G4; file4", + + var parsed = Parser.parseMList(line); + + assert.equal("file", parsed.Type); + assert.equal("17709913", parsed.Size); + assert.equal("20050502182143", parsed.Modify); + assert.equal("Choices.mp3", parsed.name); + next(); + } + */ +}); + + diff --git a/node_modules/jsftp/package.json b/node_modules/jsftp/package.json new file mode 100644 index 0000000..55fe5eb --- /dev/null +++ b/node_modules/jsftp/package.json @@ -0,0 +1,59 @@ +{ + "name": "jsftp", + "id": "jsftp", + "version": "1.1.1", + "description": "A sane FTP client implementation for NodeJS", + "keywords": [ + "ftp", + "protocol", + "files", + "server", + "client", + "async" + ], + "author": { + "name": "Sergi Mansilla", + "email": "sergi.mansilla@gmail.com", + "url": "http://sergimansilla.com" + }, + "homepage": "https://github.com/sergi/jsftp", + "repository": { + "type": "git", + "url": "https://github.com/sergi/jsftp.git" + }, + "bugs": { + "url": "https://github.com/sergi/jsftp/issues" + }, + "dependencies": { + "event-stream": "~3.0.14", + "parse-listing": "~1.0.0", + "async": "~0.2.9" + }, + "devDependencies": { + "mocha": "~1.10.0", + "istanbul": "~0.1.36", + "mocha-istanbul": "~0.2.0", + "sinon": "~1.7.2", + "ftp-test-server": "~0.0.1", + "rimraf": "~2.2.0" + }, + "main": "./jsftp.js", + "engines": { + "node": ">=0.6.21" + }, + "scripts": { + "test": "mocha -R spec -t 5000" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/sergi/jsftp/blob/master/LICENSE" + } + ], + "readme": "jsftp \"Flattr\n=====\n\nA client FTP library for NodeJS that focuses on correctness, clarity\nand conciseness. It doesn't get in the way and plays nice with streaming APIs.\n\n[![NPM](https://nodei.co/npm/jsftp.png)](https://nodei.co/npm/jsftp/)\n\n**Warning: The latest version (1.0.0) of jsftp breaks API compatibility with previous\nversions, it is NOT a drop-in replacement. Please be careful when upgrading. The\nAPI changes are not drastic at all and it is all documented below. If you do not\nwant to upgrade yet you should stay with version 0.6.0, the last one before the\nupgrade. The API docs below are updated for 1.0.**\n\nStarting it up\n--------------\n\n```javascript\nvar JSFtp = require(\"jsftp\");\n\nvar Ftp = new JSFtp({\n host: \"myserver.com\",\n port: 3331, // defaults to 21\n user: \"user\", // defaults to \"anonymous\"\n pass: \"1234\" // defaults to \"@anonymous\"\n};\n```\n\njsftp gives you access to all the raw commands of the FTP protocol in form of\nmethods in the `Ftp` object. It also provides several convenience methods for\nactions that require complex chains of commands (e.g. uploading and retrieving\nfiles, passive operations), as shown below.\n\nWhen raw commands succeed they always pass the response of the server to the\ncallback, in the form of an object that contains two properties: `code`, which\nis the response code of the FTP operation, and `text`, which is the complete\ntext of the response.\n\nRaw (or native) commands are accessible in the form `Ftp.raw[\"command\"](params, callback)`\n\nThus, a command like `QUIT` will be called like this:\n\n```javascript\nFtp.raw.quit(function(err, data) {\n if (err) return console.error(err);\n\n console.log(\"Bye!\");\n});\n```\n\nand a command like `MKD` (make directory), which accepts parameters, looks like this:\n\n```javascript\nFtp.raw.mkd(\"/new_dir\", function(err, data) {\n if (err) return console.error(err);\n\n console.log(data.text); // Show the FTP response text to the user\n console.log(data.code); // Show the FTP response code to the user\n});\n```\n\nAPI and examples\n----------------\n\n#### new Ftp(options)\n - `options` is an object with the following properties:\n\n ```javascript\n {\n host: 'localhost', // Host name for the current FTP server.\n port: 3333, // Port number for the current FTP server (defaults to 21).\n user: 'user', // Username\n pass: 'pass', // Password\n }\n ```\n\nCreates a new Ftp instance.\n\n\n#### Ftp.host\n\nHost name for the current FTP server.\n\n#### Ftp.port\n\nPort number for the current FTP server (defaults to 21).\n\n#### Ftp.socket\n\nNodeJS socket for the current FTP server.\n\n#### Ftp.features\n\nArray of feature names for the current FTP server. It is\ngenerated when the user authenticates with the `auth` method.\n\n#### Ftp.system\n\nContains the system identification string for the remote FTP server.\n\n\n### Methods\n\n#### Ftp.raw.FTP_COMMAND([params], callback)\nAll the standard FTP commands are available under the `raw` namespace. These\ncommands might accept parameters or not, but they always accept a callback\nwith the signature `err, data`, in which `err` is the error response coming\nfrom the server (usually a 4xx or 5xx error code) and the data is an object\nthat contains two properties: `code` and `text`. `code` is an integer indicating\nthe response code of the response and `text` is the response string itself.\n\n#### Ftp.auth(username, password, callback)\nAuthenticates the user with the given username and password. If null or empty\nvalues are passed for those, `auth` will use anonymous credentials. `callback`\nwill be called with the response text in case of successful login or with an\nerror as a first parameter, in normal Node fashion.\n\n#### Ftp.ls(filePath, callback)\nLists information about files or directories and yields an array of file objects\nwith parsed file properties to the `callback`. You should use this function\ninstead of `stat` or `list` in case you need to do something with the individual\nfile properties.\n\n```javascript\nftp.ls(\".\", function(err, res) {\n res.forEach(function(file) {\n console.log(file.name);\n });\n});\n```\n\n#### Ftp.list(filePath, callback)\nLists `filePath` contents using a passive connection. Calls callback with an\narray of strings with complete file information.\n\n```javascript\nftp.list(remoteCWD, function(err, res) {\n res.forEach(function(file) {\n console.log(file.name);\n });\n // Prints something like\n // -rw-r--r-- 1 sergi staff 4 Jun 03 09:32 testfile1.txt\n // -rw-r--r-- 1 sergi staff 4 Jun 03 09:31 testfile2.txt\n // -rw-r--r-- 1 sergi staff 0 May 29 13:05 testfile3.txt\n // ...\n});\n```\n\n#### Ftp.get(remotePath, callback)\nGives back a paused socket with the file contents ready to be streamed,\nor calls the callback with an error if not successful.\n\n```javascript\n var str = \"\"; // Will store the contents of the file\n ftp.get('remote/path/file.txt', function(err, socket) {\n if (err) return;\n\n socket.on(\"data\", function(d) { str += d.toString(); })\n socket.on(\"close\", function(hadErr) {\n if (hadErr)\n console.error('There was an error retrieving the file.');\n });\n socket.resume();\n });\n```\n\n#### Ftp.get(remotePath, localPath, callback)\nStores the remote file directly in the given local path.\n\n```javascript\n ftp.get('remote/file.txt, 'local/file.txt, function(hadErr) {\n if (hadErr)\n console.error('There was an error retrieving the file.');\n else\n console.log('File copied successfully!');\n });\n```\n\n#### Ftp.put(source, remotePath, callback)\nUploads a file to `filePath`. It accepts a string with the local path for the\nfile or a `Buffer` as a `source` parameter.\n\n```javascript\nftp.put(buffer, 'path/to/remote/file.txt', function(hadError) {\n if (!hadError)\n console.log(\"File transferred successfully!\");\n});\n```\n\n#### Ftp.rename(from, to, callback)\nRenames a file in the server. `from` and `to` are both filepaths.\n\n```javascript\nftp.rename(from, to, function(err, res) {\n if (!err)\n console.log(\"Renaming successful!\");\n});\n```\n\n#### Ftp.keepAlive()\nRefreshes the interval thats keep the server connection active.\n\nYou can find more usage examples in the [unit tests](https://github.com/sergi/jsftp/blob/master/test/jsftp_test.js). This documentation\nwill grow as jsftp evolves.\n\nInstallation\n------------\n\n npm install jsftp\n\nTest coverage\n-------------\n\nIn order to run coverage reports:\n\n npm install --dev\n make coverage\n\n Current overall coverage rate:\n lines......: 92.1% (316 of 343 lines)\n functions..: 91.0% (71 of 78 functions)\n\n\nTests\n-----\n\nTo run tests:\n\n npm install --dev\n make test\n\nLicense\n-------\n\nSee LICENSE.\n", + "_id": "jsftp@1.1.1", + "dist": { + "shasum": "7947db8de04c8ea1a035451c1a8b6bcaa5b0d2d5" + }, + "_from": "jsftp" +} diff --git a/node_modules/jsftp/test.js b/node_modules/jsftp/test.js new file mode 100644 index 0000000..f0c023f --- /dev/null +++ b/node_modules/jsftp/test.js @@ -0,0 +1,78 @@ +// Retrieve a file in the remote server. When the file has been retrieved, +// the callback will be called with `data` being the Buffer with the +// contents of the file. + +// Store the file in `remotePath` locally in `"/Users/sergi/file.txt"`. +var remotePath = "/folder/file.txt"; +var localPath = "/Users/sergi/file.txt"; +ftp.get(remotePath, localPath, function(err, data) { + if (err) + return console.error(err); + + console.log(remotePath + " stored successfully at " + localPath); +}); + + +// Store the file in `remotePath` locally in `"/Users/sergi/file.txt"`. +ftp.get(remotePath, function(err, socket) { + if (err) + console.error("Something went wrong."); + + var writeStream = fs.createWriteStream("/Users/sergi/file.txt"); + socket.pipe(writeStream); + socket.resume(); +}); + +// Create a directory +ftp.raw.mkd("/example_dir", function(err, data) { + if (err) return console.error(err); + + console.log(data.text); +}); + +// Delete a directory +ftp.raw.rmd("/example_dir", function(err, data) { + if (err) return console.error(err); + + console.log(data.text); +}); + +// Listing a directory +ftp.ls("/example_dir", function(err, files) { + if (err) return console.error(err); + console.log(files); // Contains an array of file objects +}); + +// Retrieving a file using streams +ftp.getGetSocket("/test_dir/testfile.txt", function(err, socket) { + if (err) return console.error(err); + + var pieces = []; + // `readable` is a stream, so we can attach events to it now + socket.on("data", function(p) { pieces.push(p); }); + socket.on("close", function(err) { + if (err) return console.error(new Error("readable connection error")); + + // `Ftp._concat` is an internal method used to concatenate buffers, it + // is used here only for illustration purposes. + console.log(Ftp._concat(pieces)); // print the contents of the file + }); + + // The readable stream is already paused, we have to resume it so it can + // start streaming. + readable.resume(); +}); + +// Storing a file in the FTP server, using streams +var originalData = Fs.createReadStream("sourceFile.txt") +) +; +originalData.pause(); + +ftp.getPutSocket("/remote_folder/sourceFileCopy.txt"), function(err, socket) { + if (err) return console.error(err); + originalData.pipe(socket); // Transfer from source to the remote file + originalData.resume(); +} +) +; diff --git a/node_modules/jsftp/test/jsftp_test.js b/node_modules/jsftp/test/jsftp_test.js new file mode 100755 index 0000000..cfb4555 --- /dev/null +++ b/node_modules/jsftp/test/jsftp_test.js @@ -0,0 +1,698 @@ +/* + * @package jsftp + * @copyright Copyright(c) 2011 Ajax.org B.V. + * @author Sergi Mansilla + * @license https://github.com/sergi/jsFTP/blob/master/LICENSE MIT License + */ +/*global it describe beforeEach afterEach */ + +"use strict"; + +var assert = require("assert"); +var Fs = require("fs"); +var exec = require('child_process').spawn; +var Ftp = require("../"); +var Path = require("path"); +var Utils = require("../lib/utils"); +var sinon = require("sinon"); +var EventEmitter = require("events").EventEmitter; +var ftpServer = require("ftp-test-server"); +var rimraf = require("rimraf"); + +var concat = function(bufs) { + var buffer, length = 0, + index = 0; + + if (!Array.isArray(bufs)) + bufs = Array.prototype.slice.call(arguments); + + for (var i = 0, l = bufs.length; i < l; i++) { + buffer = bufs[i]; + length += buffer.length; + } + + buffer = new Buffer(length); + + bufs.forEach(function(buf) { + buf.copy(buffer, index, 0, buf.length); + index += buf.length; + }); + + return buffer; +}; + +var concatStream = function(err, socket, callback) { + if (err) return callback(err); + + var pieces = []; + socket.on("data", function(p) { + pieces.push(p); + }); + socket.on("close", function(hadError) { + if (hadError) + return callback(new Error("Socket connection error")); + + callback(null, concat(pieces)); + }); + socket.resume(); +}; + +// Write down your system credentials. This test suite will use OSX internal +// FTP server. If you want to test against a remote server, simply change the +// `host` and `port` properties as well. +var FTPCredentials = { + host: "localhost", + user: "user", + port: 3334, + pass: "12345" +}; + +function getRemotePath(path) { + return Path.join('test', 'test_c9', path); +} + +function getLocalPath(path) { + return Path.join(process.cwd(), 'test', 'test_c9', path); +} +var CWD = process.cwd() + "/test"; +var remoteCWD = "test/test_c9"; +var daemon; +exec('mkdir', [__dirname + "/" + remoteCWD]); + +describe("jsftp test suite", function() { + var ftp, server; + beforeEach(function(next) { + rimraf(getLocalPath(''), function() { + Fs.mkdirSync(getLocalPath('')); + Fs.writeFileSync(getLocalPath('testfile.txt'), "test"); + + if (FTPCredentials.host === "localhost") { + server = new ftpServer(); + server.init(FTPCredentials); + } + + setTimeout(function() { + ftp = new Ftp(FTPCredentials); + next(); + }, 100); + }); + }); + + afterEach(function(next) { + if (daemon) + daemon.kill(); + + setTimeout(function() { + server.stop(); + if (ftp) { + ftp.destroy(); + ftp = null; + } + }, 50); + next(); + }); + + it("test initialize bad host", function(next) { + var ftp2 = new Ftp({ + host: "badhost", + user: "user", + port: 21, + pass: "12345" + }); + + ftp2.on("error", function(err) { + assert.equal(err.code, 'ENOTFOUND'); + next(); + }); + }); + + it("test initialize", function(next) { + assert.equal(ftp.host, FTPCredentials.host); + assert.equal(ftp.port, FTPCredentials.port); + assert.equal(ftp.user, FTPCredentials.user); + + assert.ok(ftp instanceof EventEmitter); + assert.equal(ftp.pending.length, 0); + assert.equal(ftp.cmdBuffer_.length, 0); + + next(); + }) + + it("test parseResponse with mark", function(next) { + var cb = sinon.spy(); + cb.expectsMark = { + marks: [150] + }; + var data = { + code: 150, + text: "150 File status okay; about to open data connection." + }; + + ftp.cmdBuffer_ = [ + ["retr fakefile.txt", cb] + ]; + ftp.parse = sinon.spy(); + + var firstCmd = ftp.cmdBuffer_[0]; + ftp.parseResponse(data); + assert(ftp.parse.calledWith(data, firstCmd)); + next(); + }); + + it("test parseResponse with no mark", function(next) { + var cb = sinon.spy(); + var data = { + code: 150, + text: "150 File status okay; about to open data connection." + }; + + ftp.cmdBuffer_ = [ + ["retr fakefile.txt", cb] + ]; + ftp.parse = sinon.spy(); + + ftp.parseResponse(data); + assert.equal(ftp.parse.callCount, 0); + next(); + }); + + it("test send function", function(next) { + ftp.pipeline = { + write: sinon.spy() + }; + ftp.send(); + ftp.send("list /"); + assert.equal(ftp.pipeline.write.callCount, 1); + assert(ftp.pipeline.write.calledWithExactly("list /\r\n")); + next(); + }); + + it("test parseResponse with ignore code", function(next) { + var cb = sinon.spy(); + cb.expectsMark = { + marks: [150], + ignore: 226 + }; + var data1 = { + code: 150, + text: "150 File status okay; about to open data connection." + }; + var data2 = { + code: 226, + text: "226 Transfer complete." + }; + + ftp.cmdBuffer_ = [ + ["retr fakefile.txt", cb], + ["list /", function() {}] + ]; + ftp.parse = sinon.spy(); + ftp.ignoreCmdCode = 150; + + ftp.parseResponse(data1); + assert.equal(ftp.ignoreCmdCode, 226); + ftp.parseResponse(data2); + assert.equal(ftp.ignoreCmdCode, null); + assert(ftp.parse.calledOnce); + next(); + }); + + it("test getFeatures", function(next) { + ftp.getFeatures(function(err, feats) { + assert.ok(Array.isArray(feats)); + assert.ok(Array.isArray(ftp.features)); + assert.ok(ftp.system.length > 0); + + var feat = ftp.features[0]; + assert.ok(ftp.hasFeat(feat)); + assert.equal(false, ftp.hasFeat("madeup-feat")); + next(); + }); + }); + + it("test getFeatures", function(next) { + ftp.getFeatures(function(err, feats) { + assert.ok(Array.isArray(feats)); + assert.ok(Array.isArray(ftp.features)); + assert.ok(ftp.system.length > 0); + + var feat = ftp.features[0]; + assert.ok(ftp.hasFeat(feat)); + assert.equal(false, ftp.hasFeat("madeup-feat")); + next(); + }); + }); + + it("test print working directory", function(next) { + ftp.raw.pwd(function(err, res) { + assert(!err, err); + + var code = parseInt(res.code, 10); + assert.ok(code === 257, "PWD command was not successful: " + res.text); + + next(); + }); + }); + + it("test switch CWD", function(next) { + ftp.raw.cwd(remoteCWD, function(err, res) { + assert.ok(!err, err); + + var code = parseInt(res.code, 10); + assert.ok(code === 200 || code === 250, "CWD command was not successful"); + + ftp.raw.pwd(function(err, res) { + assert.ok(!err, err); + + var code = parseInt(res.code, 10); + assert.ok(code === 257, "PWD command was not successful"); + assert.ok(res.text.indexOf(remoteCWD), "Unexpected CWD"); + next(); + }); + }); + }); + + it("test switch to unexistent CWD", function(next) { + ftp.raw.cwd("/unexistentDir/", function(err, res) { + var code = parseInt(res.code, 10); + assert.ok( !! err); + assert.equal(code, 550, "A (wrong) CWD command was successful. It should have failed"); + next(); + }); + }); + + it("test listing with bad line breaks", function(next) { + var badStr = "\ +213-Status follows:\r\n\ +-rw-r--r-- 1 0 0 105981956 Dec 20 18:07 GAT\r\n\ +SBY.MPG\r\n\ +-rw-r--r-- 1 0 0 74450948 Jan 17 18:16 GIJO.MPG\r\n\ +drwxr-xr-x 3 0 0 4096 Apr 16 2011 bourd\n\ +arie\r\n\ +drwxr-xr-x 2 0 0 4096 Apr 16 2011 denton\r\n\ +213 End of status"; + + var entries = Utils.parseEntry(badStr); + assert.equal("GATSBY.MPG", entries[0].name); + assert.equal("GIJO.MPG", entries[1].name); + assert.equal("bourdarie", entries[2].name); + assert.equal("denton", entries[3].name); + + next(); + }); + + it("test passive listing of current directory", function(next) { + ftp.list(remoteCWD, function(err, res) { + assert.ok(!err, err); + assert.ok(res.length > 0); + next(); + }); + }); + + it("test ftp node stat", function(next) { + ftp.raw.pwd(function(err, res) { + var parent = /.*"(.*)".*/.exec(res.text)[1]; + var path = Path.resolve(parent + "/" + remoteCWD); + ftp.raw.stat(path, function(err, res) { + assert.ok(!err, res); + assert.ok(res); + + assert.ok(res.code === 211 || res.code === 212 || res.code === 213); + next(); + }); + }); + }); + + it("test create and delete a directory", function(next) { + var newDir = remoteCWD + "/ftp_test_dir"; + ftp.raw.mkd(newDir, function(err, res) { + assert.ok(!err); + assert.equal(res.code, 257); + + ftp.raw.rmd(newDir, function(err, res) { + assert.ok(!err); + next(); + }); + }); + }); + + it("test create and delete a directory containing a space", function(next) { + var newDir = remoteCWD + "/ftp test dür"; + ftp.raw.mkd(newDir, function(err, res) { + assert.ok(!err); + assert.equal(res.code, 257); + + ftp.raw.rmd(newDir, function(err, res) { + assert.ok(!err); + next(); + }); + }); + }); + + it("test create and delete a file", function(next) { + var filePath = getRemotePath("file_ftp_test.txt"); + Fs.readFile(__filename, "binary", function(err, data) { + var buffer = new Buffer(data, "binary"); + ftp.put(buffer, filePath, function(hadError) { + assert.ok(!hadError); + + ftp.ls(filePath, function(err, res) { + assert.ok(!err); + assert.equal(buffer.length, Fs.statSync(CWD + "/jsftp_test.js").size); + + ftp.raw.dele(filePath, function(err, data) { + assert.ok(!err); + + next(); + }); + }); + }); + }); + }); + + it("test save a remote copy of a local file", function(next) { + var filePath = getRemotePath("file_ftp_test.txt"); + var onProgress = sinon.spy(); + ftp.on('progress', onProgress); + ftp.put(__filename, filePath, function(err, res) { + assert.ok(!err, err); + + ftp.ls(filePath, function(err, res) { + assert.ok(!err); + assert(onProgress.called); + var data = onProgress.args[0][0]; + assert.equal(data.filename, filePath); + assert.equal(data.action, 'put'); + assert.ok(typeof data.transferred, 'number'); + + ftp.raw.dele(filePath, function(err, data) { + assert.ok(!err); + next(); + }); + }); + }); + }); + + it("test rename a file", function(next) { + var from = getRemotePath("file_ftp_test.txt"); + var to = getRemotePath("file_ftp_test_renamed.txt"); + Fs.readFile(__filename, "binary", function(err, data) { + assert.ok(!err, err); + var buffer = new Buffer(data, "binary"); + ftp.put(buffer, from, function(err, res) { + assert.ok(!err, err); + + ftp.rename(from, to, function(err, res) { + ftp.ls(to, function(err, res) { + assert.ok(!err); + + assert.equal(buffer.length, Fs.statSync(__filename).size); + + ftp.raw.dele(to, function(err, data) { + assert.ok(!err); + next(); + }); + }); + }); + }); + }); + }); + + it("test get a file", function(next) { + var localPath = CWD + '/test_c9/testfile.txt'; + var remotePath = remoteCWD + "/testfile.txt"; + var realContents = Fs.readFileSync(localPath, "utf8"); + var str = ""; + ftp.get(remotePath, function(err, socket) { + assert.ok(!err, err); + assert.ok(arguments.length === 2); + socket.on("data", function(d) { + str += d; + }) + socket.on("close", function(hadErr) { + assert.equal(realContents, str); + next(); + }); + socket.resume(); + }); + }); + + it("test get a file and save it locally", function(next) { + var localPath = getLocalPath("testfile.txt"); + var remotePath = getRemotePath("testfile.txt"); + var destination = localPath + ".copy"; + var onProgress = sinon.spy(); + ftp.on('progress', onProgress); + + Fs.unlink(destination, function() { + Fs.readFile(localPath, "utf8", function(err, realContents) { + ftp.get(remotePath, destination, function(err) { + assert.ok(!err, err); + assert.ok(arguments.length < 2, arguments.length); + var data = onProgress.args[0][0]; + assert.equal(data.filename, remotePath); + assert.equal(data.action, 'get'); + assert.ok(typeof data.transferred, 'number'); + Fs.readFile(destination, "utf8", function(err, data) { + assert.strictEqual(data, realContents); + next(); + }); + }); + }); + }); + }); + + it("test get a big file stream", function(next) { + var remotePath = getRemotePath("bigfile.test"); + var localPath = getLocalPath("bigfile.test"); + var data = (new Array(1 * 1024 * 1024)).join("x"); + var buffer = new Buffer(data, "binary"); + + Fs.writeFileSync(localPath, buffer); + + ftp.getGetSocket(remotePath, function(err, socket) { + assert.ok(!err, err); + + socket.resume(); + + var counter = 0; + + socket.on('data', function(data) { + counter += data.length; + }); + + socket.on('close', function() { + assert.equal(buffer.length, counter); + + ftp.raw.dele(remotePath, function(err, data) { + assert.ok(!err); + next(); + }); + }); + }); + }); + + it("test put a big file stream", function(next) { + var remotePath = getRemotePath("bigfile.test"); + var data = (new Array(1 * 1024 * 1024)).join("x"); + + ftp.getPutSocket(remotePath, function(err, socket) { + assert.ok(!err, err); + + socket.write(data, function(err) { + assert.ok(!err, err); + socket.end(); + }); + }, function(err, res) { + assert.ok(!err, err); + + ftp.raw.dele(remotePath, function(err, data) { + assert.ok(!err); + next(); + }); + }); + }); + + it("test put a big file stream fail", function(next) { + var remotePath = getRemotePath("/nonexisting/path/to/file.txt"); + + ftp.getPutSocket(remotePath, function(err, socket, res) { + assert.ok( !! err, err); + assert.equal(err.code, 550, err); + }, function(err, res) { + assert.ok( !! err); + next(); + }); + }); + + it("test get fileList array", function(next) { + var file1 = "testfile.txt"; + + ftp.raw.cwd(getRemotePath(''), function() { + ftp.ls(".", function(err, res) { + assert.ok(!err, err); + assert.ok(Array.isArray(res)); + + res.forEach(assert.ok); + res = res.map(function(file) { + return file.name; + }); + + assert.ok(res.indexOf(file1) > -1); + + next(); + }); + }); + }); + + it("test reconnect", function(next) { + ftp.raw.pwd(function(err, res) { + if (err) throw err; + + var code = parseInt(res.code, 10); + assert.ok(code === 257, "PWD command was not successful"); + + ftp.socket.end(); + ftp.raw.quit(function(err, res) { + if (err) throw err; + next(); + }); + }); + }); + + it("test attach event handlers: connect", function(_next) { + var clientOnConnect = function() { + client.auth(FTPCredentials.user, FTPCredentials.pass, next); + }; + + var next = function(err) { + assert.ok(!err); + client.destroy(); + _next(); + }; + + var client = new Ftp({ + host: "localhost", + user: "user", + port: 3334, + pass: "12345" + }); + client.on("connect", clientOnConnect); + }); + + it("test PASV streaming: Copy file using piping", function(next) { + var filePath = getRemotePath("testfile.txt"); + var originalData = Fs.readFileSync(getLocalPath("testfile.txt")); + ftp.getGetSocket(filePath, function(err, readable) { + assert(!err, err); + assert.ok(readable); + + readable.on("error", error); + + function error(err) { + assert.ok(!err, err); + if (readable.destroy) readable.destroy(); + next(); + } + + var remoteCopy = filePath + ".bak"; + ftp.getPutSocket(remoteCopy, function(err, socket) { + assert.ok(!err, err); + readable.pipe(socket); + readable.resume(); + }, + + function(hadError) { + assert.ok(!hadError); + + var str = ""; + ftp.getGetSocket(remoteCopy, function(err, socket) { + assert.ok(!err, err); + socket.on("data", function(d) { + str += d; + }); + socket.on("close", function(hadErr) { + assert.equal(originalData.toString("utf8"), str); + next(); + }); + socket.resume(); + }); + }); + }); + }); + + it("Test that streaming GET (RETR) retrieves a file properly", function(next) { + var path = getLocalPath("testfile.txt"); + var originalData = Fs.readFileSync(path); + ftp.getGetSocket(getRemotePath("testfile.txt"), function(err, readable) { + assert.ok(!err); + concatStream(err, readable, function(err, buffer) { + assert.ok(!err); + assert.equal(buffer.toString(), originalData.toString()); + next(); + }); + }); + }); + + it("Test that streaming GET (RETR) fails when a file is not present", function(next) { + ftp.getGetSocket("unexisting/file/path", function(err, readable) { + assert.ok(err); + assert.equal(550, err.code); + next(); + }); + }); + + it("Test that streaming PUT (STOR) stores a file properly", function(next) { + var path = getLocalPath("testfile.txt"); + var originalData = Fs.createReadStream(getLocalPath("testfile.txt")); + originalData.pause(); + + ftp.getPutSocket(getRemotePath("testfile.txt.bak"), function(err, socket) { + assert.ok(!err); + originalData.pipe(socket); + originalData.resume(); + concatStream(err, originalData, function(err, buffer) { + assert.ok(!err); + Fs.readFile(path, "utf8", function(err, original) { + assert.equal(buffer.toString("utf8"), original); + next(); + }); + }); + }); + }); + + it("Test that streaming PUT (STOR) fails when a file is not present", function(next) { + ftp.getPutSocket("unexisting/file/path", function(err, socket) { + assert.ok(err); + next(); + }); + }); + + it("Test that onConnect is called", function(next) { + var ftp2 = new Ftp(FTPCredentials); + ftp2.on("connect", function() { + next(); + }); + }); + + it("Test for correct data on ls 1", function(next) { + var paths = ['test/test_c9/testfile.txt', 'work/test/main.css']; + ftp.auth(FTPCredentials.user, FTPCredentials.pass, function(err, data) { + if (err) return console.log(err); + + var processed = 0; + ftp.ls(paths[0], function showFile(err, res) { + assert.ok(!err); + assert.strictEqual(res[0].name, 'testfile.txt'); + processed += 1; + if (processed === 2) next(); + }); + + ftp.ls(paths[1], function showFile(err, res1) { + assert.ok(!!err); + processed += 1; + if (processed === 2) next(); + }); + }); + }); +}); diff --git a/node_modules/jsftp/test/keycert.pem b/node_modules/jsftp/test/keycert.pem new file mode 100644 index 0000000..bfa28de --- /dev/null +++ b/node_modules/jsftp/test/keycert.pem @@ -0,0 +1,32 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L +opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH +fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB +AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU +D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA +IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM +oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 +ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ +loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j +oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA +z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq +ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV +q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD +VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x +IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT +U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 +NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl +bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj +aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh +m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 +M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn +fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC +AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb +08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx +CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ +iHkC6gGdBJhogs4= +-----END CERTIFICATE----- diff --git a/node_modules/jsftp/test/mockup_test.js b/node_modules/jsftp/test/mockup_test.js new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/jsftp/test/test_c9/testfile.txt b/node_modules/jsftp/test/test_c9/testfile.txt new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/node_modules/jsftp/test/test_c9/testfile.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/node_modules/jsftp/test/tls_ftpd.py b/node_modules/jsftp/test/tls_ftpd.py new file mode 100644 index 0000000..d2c204e --- /dev/null +++ b/node_modules/jsftp/test/tls_ftpd.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +""" +An RFC-4217 asynchronous FTPS server supporting both SSL and TLS. +Requires PyOpenSSL module (http://pypi.python.org/pypi/pyOpenSSL). +""" + +from pyftpdlib.servers import FTPServer +from pyftpdlib.authorizers import DummyAuthorizer +from pyftpdlib.handlers import TLS_FTPHandler,FTPHandler + + +def main(): + authorizer = DummyAuthorizer() + authorizer.add_user('user', '12345', '.', perm='elradfmw') + authorizer.add_anonymous('.') + handler = TLS_FTPHandler + handler.certfile = 'keycert.pem' + handler.authorizer = authorizer + # requires SSL for both control and data channel + #handler.tls_control_required = True + #handler.tls_data_required = True + server = FTPServer(('', 21), handler) + server.serve_forever() + +if __name__ == '__main__': + main() diff --git a/node_modules/ws/.npmignore b/node_modules/ws/.npmignore new file mode 100644 index 0000000..8233793 --- /dev/null +++ b/node_modules/ws/.npmignore @@ -0,0 +1,6 @@ +npm-debug.log +node_modules +.*.swp +.lock-* +build + diff --git a/node_modules/ws/.travis.yml b/node_modules/ws/.travis.yml new file mode 100644 index 0000000..08e4dad --- /dev/null +++ b/node_modules/ws/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +npm_args: --ws:native +node_js: + - "0.6" + - "0.8" + - "0.10" diff --git a/node_modules/ws/History.md b/node_modules/ws/History.md new file mode 100644 index 0000000..63cf0ea --- /dev/null +++ b/node_modules/ws/History.md @@ -0,0 +1,312 @@ +v0.4.31 - September 23th, 2013 +===================== + +* Component support + +v0.4.30 - August 30th, 2013 +===================== + +* BufferedAmount could be undefined, default to 0 [TooTallNate] +* Support protocols as second argument and options as third [TooTallNate] +* Proper browserify shim [mcollina] +* Broadcasting example in README [stefanocudini] + +v0.4.29 - August 23th, 2013 +===================== +* Small clean up of the Node 0.11 support by using NAN from the NPM registry [kkoopa] +* Support for custom `Agent`'s through the options. [gramakri] & [TooTallNate] +* Support for custom headers through the options [3rd-Eden] +* Added a `gypfile` flag to the package.json for compiled module discovery [wolfeidau] + +v0.4.28 - August 16th, 2013 +===================== +* Node 0.11 support. [kkoopa] +* Authorization headers are sent when basic auth is used in the url [jcrugzz] +* Origin header will now include the port number [Jason Plum] +* Race condition fixed where data was received before the readyState was updated. [saschagehlich] + +v0.4.27 - June 27th, 2013 +===================== +* Frames are no longer masked in `wscat`. [slaskis] +* Don't retrain reference to large slab buffers. [jmatthewsr-msi] +* Don't use Buffer.byteLength for ArrayBuffer's. [Anthony Pesch] +* Fix browser field in package.json. [shtylman] +* Client-side certificate support & documentation improvements. [Lukas Berns] +* WebSocket readyState's is added to the prototype for spec compatiblity. [BallBearing] +* Use Object.defineProperty. [arlolra] +* Autodetect ArrayBuffers as binary when sending. [BallBearing] +* Check instanceof Buffer for binary data. [arlolra] +* Emit the close event before destroying the internal socket. [3rd-Eden] +* Don't setup multiply timeouts for one connection. [AndreasMadsen] +* Allow support for binding to ethereal port. [wpreul] +* Fix broken terminate reference. [3rd-Eden] +* Misc node 0.10 test fixes and documentation improvements. [3rd-Eden] +* Ensure ssl options are propagated to request. [einaros] +* Add 'Host' and 'Origin' to request header. [Lars-Magnus Skog] +* Subprotocol support. [kanaka] +* Honor ArrayBufferView's byteOffset when sending. [Anthony Pesch] +* Added target attribute for events. [arlolra] + +v0.4.26 - Skipped +===================== + +v0.4.25 - December 17th, 2012 +===================== +* Removed install.js. [shtylman] +* Added browser field to package.json. [shtylman] +* Support overwriting host header. [Raynos] +* Emit 'listening' also with custom http server. [sebiq] + +v0.4.24 - December 6th, 2012 +===================== +* Yet another intermediate release, to not delay minor features any longer. +* Native support installation issues further circumvented. [einaros] + +v0.4.23 - November 19th, 2012 +===================== +* Service release - last before major upgrade. +* Changes default host from 127.0.0.1 to 0.0.0.0. [einaros] + +v0.4.22 - October 3rd, 2012 +===================== +* clear failsafe cleanup timeout once cleanup is called [AndreasMadsen] +* added w3c compatible CloseEvent for onclose / addEventListener("close", ...). [einaros] +* fix the sub protocol header handler [sonnyp] +* fix unhandled exception if socket closes and 'error' is emitted [jmatthewsr-ms] + +v0.4.21 - July 14th, 2012 +===================== +* Emit error if server reponds with anything other than status code 101. [einaros] +* Added 'headers' event to server. [rauchg] +* path.exists moved to fs.exists. [blakmatrix] + +v0.4.20 - June 26th, 2012 +===================== +* node v0.8.0 compatibility release. + +v0.4.19 - June 19th, 2012 +===================== +* Change sender to merge buffers for relatively small payloads, may improve perf in some cases [einaros] +* Avoid EventEmitter for Receiver classes. As above this may improve perf. [einaros] +* Renamed fallback files from the somewhat misleading '*Windows'. [einaros] + +v0.4.18 - June 14th 2012 +===================== +* Fixed incorrect md5 digest encoding in Hixie handshake [nicokaiser] +* Added example of use with Express 3 [einaros] +* Change installation procedure to not require --ws:native to build native extensions. They will now build if a compiler is available. [einaros] + +v0.4.17 - June 13th 2012 +===================== +* Improve error handling during connection handshaking [einaros] +* Ensure that errors are caught also after connection teardown [nicokaiser] +* Update 'mocha' version to 1.1.0. [einaros] +* Stop showing 'undefined' for some error logs. [tricknotes] +* Update 'should' version to 0.6.3 [tricknotes] + +v0.4.16 - June 1st 2012 +===================== +* Build fix for Windows. [einaros] + +v0.4.15 - May 20th 2012 +===================== +* Enable fauxe streaming for hixie tansport. [einaros] +* Allow hixie sender to deal with buffers. [einaros/pigne] +* Allow error code 1011. [einaros] +* Fix framing for empty packets (empty pings and pongs might break). [einaros] +* Improve error and close handling, to avoid connections lingering in CLOSING state. [einaros] + +v0.4.14 - Apr 30th 2012 +===================== +* use node-gyp instead of node-waf [TooTallNate] +* remove old windows compatibility makefile, and silently fall back to native modules [einaros] +* ensure connection status [nicokaiser] +* websocket client updated to use port 443 by default for wss:// connections [einaros] +* support unix sockets [kschzt] + +v0.4.13 - Apr 12th 2012 +===================== + +* circumvent node 0.6+ related memory leak caused by Object.defineProperty [nicokaiser] +* improved error handling, improving stability in massive load use cases [nicokaiser] + +v0.4.12 - Mar 30th 2012 +===================== + +* various memory leak / possible memory leak cleanups [einaros] +* api documentation [nicokaiser] +* add option to disable client tracking [nicokaiser] + +v0.4.11 - Mar 24th 2012 +===================== + +* node v0.7 compatibillity release +* gyp support [TooTallNate] +* commander dependency update [jwueller] +* loadbalancer support [nicokaiser] + +v0.4.10 - Mar 22th 2012 +===================== + +* Final hixie close frame fixes. [nicokaiser] + +v0.4.9 - Mar 21st 2012 +===================== + +* Various hixie bugfixes (such as proper close frame handling). [einaros] + +v0.4.8 - Feb 29th 2012 +===================== + +* Allow verifyClient to run asynchronously [karlsequin] +* Various bugfixes and cleanups. [einaros] + +v0.4.7 - Feb 21st 2012 +===================== + +* Exposed bytesReceived from websocket client object, which makes it possible to implement bandwidth sampling. [einaros] +* Updated browser based file upload example to include and output per websocket channel bandwidth sampling. [einaros] +* Changed build scripts to check which architecture is currently in use. Required after the node.js changes to have prebuilt packages target ia32 by default. [einaros] + +v0.4.6 - Feb 9th 2012 +===================== + +* Added browser based file upload example. [einaros] +* Added server-to-browser status push example. [einaros] +* Exposed pause() and resume() on WebSocket object, to enable client stream shaping. [einaros] + +v0.4.5 - Feb 7th 2012 +===================== + +* Corrected regression bug in handling of connections with the initial frame delivered across both http upgrade head and a standalone packet. This would lead to a race condition, which in some cases could cause message corruption. [einaros] + +v0.4.4 - Feb 6th 2012 +===================== + +* Pass original request object to verifyClient, for cookie or authentication verifications. [einaros] +* Implemented addEventListener and slightly improved the emulation API by adding a MessageEvent with a readonly data attribute. [aslakhellesoy] +* Rewrite parts of hybi receiver to avoid stack overflows for large amounts of packets bundled in the same buffer / packet. [einaros] + +v0.4.3 - Feb 4th 2012 +===================== + +* Prioritized update: Corrected issue which would cause sockets to stay open longer than necessary, and resource leakage because of this. [einaros] + +v0.4.2 - Feb 4th 2012 +===================== + +* Breaking change: WebSocketServer's verifyOrigin option has been renamed to verifyClient. [einaros] +* verifyClient now receives { origin: 'origin header', secure: true/false }, where 'secure' will be true for ssl connections. [einaros] +* Split benchmark, in preparation for more thorough case. [einaros] +* Introduced hixie-76 draft support for server, since Safari (iPhone / iPad / OS X) and Opera still aren't updated to use Hybi. [einaros] +* Expose 'supports' object from WebSocket, to indicate e.g. the underlying transport's support for binary data. [einaros] +* Test and code cleanups. [einaros] + +v0.4.1 - Jan 25th 2012 +===================== + +* Use readline in wscat [tricknotes] +* Refactor _state away, in favor of the new _readyState [tricknotes] +* travis-ci integration [einaros] +* Fixed race condition in testsuite, causing a few tests to fail (without actually indicating errors) on travis [einaros] +* Expose pong event [paddybyers] +* Enabled running of WebSocketServer in noServer-mode, meaning that upgrades are passed in manually. [einaros] +* Reworked connection procedure for WebSocketServer, and cleaned up tests. [einaros] + +v0.4.0 - Jan 2nd 2012 +===================== + +* Windows compatibility [einaros] +* Windows compatible test script [einaros] + +v0.3.9 - Jan 1st 2012 +====================== + +* Improved protocol framing performance [einaros] +* WSS support [kazuyukitanimura] +* WSS tests [einaros] +* readyState exposed [justinlatimer, tricknotes] +* url property exposed [justinlatimer] +* Removed old 'state' property [einaros] +* Test cleanups [einaros] + +v0.3.8 - Dec 27th 2011 +====================== + +* Made it possible to listen on specific paths, which is especially good to have for precreated http servers [einaros] +* Extensive WebSocket / WebSocketServer cleanup, including changing all internal properties to unconfigurable, unenumerable properties [einaros] +* Receiver modifications to ensure even better performance with fragmented sends [einaros] +* Fixed issue in sender.js, which would cause SlowBuffer instances (such as returned from the crypto library's randomBytes) to be copied (and thus be dead slow) [einaros] +* Removed redundant buffer copy in sender.js, which should improve server performance [einaros] + +v0.3.7 - Dec 25nd 2011 +====================== + +* Added a browser based API which uses EventEmitters internally [3rd-Eden] +* Expose request information from upgrade event for websocket server clients [mmalecki] + +v0.3.6 - Dec 19th 2011 +====================== + +* Added option to let WebSocket.Server use an already existing http server [mmalecki] +* Migrating various option structures to use options.js module [einaros] +* Added a few more tests, options and handshake verifications to ensure that faulty connections are dealt with [einaros] +* Code cleanups in Sender and Receiver, to ensure even faster parsing [einaros] + +v0.3.5 - Dec 13th 2011 +====================== + +* Optimized Sender.js, Receiver.js and bufferutil.cc: + * Apply loop-unrolling-like small block copies rather than use node.js Buffer#copy() (which is slow). + * Mask blocks of data using combination of 32bit xor and loop-unrolling, instead of single bytes. + * Keep pre-made send buffer for small transfers. +* Leak fixes and code cleanups. + +v0.3.3 - Dec 12th 2011 +====================== + +* Compile fix for Linux. +* Rewrote parts of WebSocket.js, to avoid try/catch and thus avoid optimizer bailouts. + +v0.3.2 - Dec 11th 2011 +====================== + +* Further performance updates, including the additions of a native BufferUtil module, which deals with several of the cpu intensive WebSocket operations. + +v0.3.1 - Dec 8th 2011 +====================== + +* Service release, fixing broken tests. + +v0.3.0 - Dec 8th 2011 +====================== + +* Node.js v0.4.x compatibility. +* Code cleanups and efficiency improvements. +* WebSocket server added, although this will still mainly be a client library. +* WebSocket server certified to pass the Autobahn test suite. +* Protocol improvements and corrections - such as handling (redundant) masks for empty fragments. +* 'wscat' command line utility added, which can act as either client or server. + +v0.2.6 - Dec 3rd 2011 +====================== + +* Renamed to 'ws'. Big woop, right -- but easy-websocket really just doesn't cut it anymore! + +v0.2.5 - Dec 3rd 2011 +====================== + + * Rewrote much of the WebSocket parser, to ensure high speed for highly fragmented messages. + * Added a BufferPool, as a start to more efficiently deal with allocations for WebSocket connections. More work to come, in that area. + * Updated the Autobahn report, at http://einaros.github.com/easy-websocket, with comparisons against WebSocket-Node 1.0.2 and Chrome 16. + +v0.2.0 - Nov 25th 2011 +====================== + + * Major rework to make sure all the Autobahn test cases pass. Also updated the internal tests to cover more corner cases. + +v0.1.2 - Nov 14th 2011 +====================== + + * Back and forth, back and forth: now settled on keeping the api (event names, methods) closer to the websocket browser api. This will stick now. + * Started keeping this history record. Better late than never, right? diff --git a/node_modules/ws/Makefile b/node_modules/ws/Makefile new file mode 100644 index 0000000..151aa2b --- /dev/null +++ b/node_modules/ws/Makefile @@ -0,0 +1,40 @@ +ALL_TESTS = $(shell find test/ -name '*.test.js') +ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') + +all: + node-gyp configure build + +clean: + node-gyp clean + +run-tests: + @./node_modules/.bin/mocha \ + -t 2000 \ + -s 2400 \ + $(TESTFLAGS) \ + $(TESTS) + +run-integrationtests: + @./node_modules/.bin/mocha \ + -t 5000 \ + -s 6000 \ + $(TESTFLAGS) \ + $(TESTS) + +test: + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests + +integrationtest: + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_INTEGRATION)" run-integrationtests + +benchmark: + @node bench/sender.benchmark.js + @node bench/parser.benchmark.js + +autobahn: + @NODE_PATH=lib node test/autobahn.js + +autobahn-server: + @NODE_PATH=lib node test/autobahn-server.js + +.PHONY: test diff --git a/node_modules/ws/README.md b/node_modules/ws/README.md new file mode 100644 index 0000000..e6646e7 --- /dev/null +++ b/node_modules/ws/README.md @@ -0,0 +1,171 @@ +[![Build Status](https://secure.travis-ci.org/einaros/ws.png)](http://travis-ci.org/einaros/ws) + +# ws: a node.js websocket library # + +`ws` is a simple to use websocket implementation, up-to-date against RFC-6455, and [probably the fastest WebSocket library for node.js](http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs). + +Passes the quite extensive Autobahn test suite. See http://einaros.github.com/ws for the full reports. + +Comes with a command line utility, `wscat`, which can either act as a server (--listen), or client (--connect); Use it to debug simple websocket services. + +## Protocol support ## + +* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. Added to ws version 0.4.2, but server only. Can be disabled by setting the `disableHixie` option to true.) +* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`, or argument `-p 8` for wscat) +* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`, or argument `-p 13` for wscat) + +_See the echo.websocket.org example below for how to use the `protocolVersion` option._ + +## Usage ## + +### Installing ### + +`npm install ws` + +### Sending and receiving text data ### + +```js +var WebSocket = require('ws'); +var ws = new WebSocket('ws://www.host.com/path'); +ws.on('open', function() { + ws.send('something'); +}); +ws.on('message', function(data, flags) { + // flags.binary will be set if a binary data is received + // flags.masked will be set if the data was masked +}); +``` + +### Sending binary data ### + +```js +var WebSocket = require('ws'); +var ws = new WebSocket('ws://www.host.com/path'); +ws.on('open', function() { + var array = new Float32Array(5); + for (var i = 0; i < array.length; ++i) array[i] = i / 2; + ws.send(array, {binary: true, mask: true}); +}); +``` + +Setting `mask`, as done for the send options above, will cause the data to be masked according to the websocket protocol. The same option applies for text data. + +### Server example ### + +```js +var WebSocketServer = require('ws').Server + , wss = new WebSocketServer({port: 8080}); +wss.on('connection', function(ws) { + ws.on('message', function(message) { + console.log('received: %s', message); + }); + ws.send('something'); +}); +``` + +### Server sending broadcast data ### + +```js +var WebSocketServer = require('ws').Server + , wss = new WebSocketServer({port: 8080}); + +wss.broadcast = function(data) { + for(var i in this.clients) + this.clients[i].send(data); +}; +``` + +### Error handling best practices ### + +```js +// If the WebSocket is closed before the following send is attempted +ws.send('something'); + +// Errors (both immediate and async write errors) can be detected in an optional callback. +// The callback is also the only way of being notified that data has actually been sent. +ws.send('something', function(error) { + // if error is null, the send has been completed, + // otherwise the error object will indicate what failed. +}); + +// Immediate errors can also be handled with try/catch-blocks, but **note** +// that since sends are inherently asynchronous, socket write failures will *not* +// be captured when this technique is used. +try { + ws.send('something'); +} +catch (e) { + // handle error +} +``` + +### echo.websocket.org demo ### + +```js +var WebSocket = require('ws'); +var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 8, origin: 'http://websocket.org'}); +ws.on('open', function() { + console.log('connected'); + ws.send(Date.now().toString(), {mask: true}); +}); +ws.on('close', function() { + console.log('disconnected'); +}); +ws.on('message', function(data, flags) { + console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags); + setTimeout(function() { + ws.send(Date.now().toString(), {mask: true}); + }, 500); +}); +``` + +### wscat against echo.websocket.org ### + + $ npm install -g ws + $ wscat -c ws://echo.websocket.org -p 8 + connected (press CTRL+C to quit) + > hi there + < hi there + > are you a happy parrot? + < are you a happy parrot? + +### Other examples ### + +For a full example with a browser client communicating with a ws server, see the examples folder. + +Note that the usage together with Express 3.0 is quite different from Express 2.x. The difference is expressed in the two different serverstats-examples. + +Otherwise, see the test cases. + +### Running the tests ### + +`make test` + +## API Docs ## + +See the doc/ directory for Node.js-like docs for the ws classes. + +## License ## + +(The MIT License) + +Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/ws/bench/parser.benchmark.js b/node_modules/ws/bench/parser.benchmark.js new file mode 100644 index 0000000..ff5f737 --- /dev/null +++ b/node_modules/ws/bench/parser.benchmark.js @@ -0,0 +1,115 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +/** + * Benchmark dependencies. + */ + +var benchmark = require('benchmark') + , Receiver = require('../').Receiver + , suite = new benchmark.Suite('Receiver'); +require('tinycolor'); +require('./util'); + +/** + * Setup receiver. + */ + +suite.on('start', function () { + receiver = new Receiver(); +}); + +suite.on('cycle', function () { + receiver = new Receiver(); +}); + +/** + * Benchmarks. + */ + +var pingMessage = 'Hello' + , pingPacket1 = getBufferFromHexString('89 ' + (pack(2, 0x80 | pingMessage.length)) + + ' 34 83 a8 68 '+ getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'))); +suite.add('ping message', function () { + receiver.add(pingPacket1); +}); + +var pingPacket2 = getBufferFromHexString('89 00') +suite.add('ping with no data', function () { + receiver.add(pingPacket2); +}); + +var closePacket = getBufferFromHexString('88 00'); +suite.add('close message', function () { + receiver.add(closePacket); + receiver.endPacket(); +}); + +var maskedTextPacket = getBufferFromHexString('81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5'); +suite.add('masked text message', function () { + receiver.add(maskedTextPacket); +}); + +binaryDataPacket = (function() { + var length = 125 + , message = new Buffer(length) + for (var i = 0; i < length; ++i) message[i] = i % 10; + return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + + getHexStringFromBuffer(mask(message), '34 83 a8 68')); +})(); +suite.add('binary data (125 bytes)', function () { + try { + receiver.add(binaryDataPacket); + + } + catch(e) {console.log(e)} +}); + +binaryDataPacket2 = (function() { + var length = 65535 + , message = new Buffer(length) + for (var i = 0; i < length; ++i) message[i] = i % 10; + return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + + getHexStringFromBuffer(mask(message), '34 83 a8 68')); +})(); +suite.add('binary data (65535 bytes)', function () { + receiver.add(binaryDataPacket2); +}); + +binaryDataPacket3 = (function() { + var length = 200*1024 + , message = new Buffer(length) + for (var i = 0; i < length; ++i) message[i] = i % 10; + return getBufferFromHexString('82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + + getHexStringFromBuffer(mask(message), '34 83 a8 68')); +})(); +suite.add('binary data (200 kB)', function () { + receiver.add(binaryDataPacket3); +}); + +/** + * Output progress. + */ + +suite.on('cycle', function (bench, details) { + console.log('\n ' + suite.name.grey, details.name.white.bold); + console.log(' ' + [ + details.hz.toFixed(2).cyan + ' ops/sec'.grey + , details.count.toString().white + ' times executed'.grey + , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey + , + ].join(', '.grey)); +}); + +/** + * Run/export benchmarks. + */ + +if (!module.parent) { + suite.run(); +} else { + module.exports = suite; +} diff --git a/node_modules/ws/bench/sender.benchmark.js b/node_modules/ws/bench/sender.benchmark.js new file mode 100644 index 0000000..20c171a --- /dev/null +++ b/node_modules/ws/bench/sender.benchmark.js @@ -0,0 +1,66 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +/** + * Benchmark dependencies. + */ + +var benchmark = require('benchmark') + , Sender = require('../').Sender + , suite = new benchmark.Suite('Sender'); +require('tinycolor'); +require('./util'); + +/** + * Setup sender. + */ + +suite.on('start', function () { + sender = new Sender(); + sender._socket = { write: function() {} }; +}); + +suite.on('cycle', function () { + sender = new Sender(); + sender._socket = { write: function() {} }; +}); + +/** + * Benchmarks + */ + +framePacket = new Buffer(200*1024); +framePacket.fill(99); +suite.add('frameAndSend, unmasked (200 kB)', function () { + sender.frameAndSend(0x2, framePacket, true, false); +}); +suite.add('frameAndSend, masked (200 kB)', function () { + sender.frameAndSend(0x2, framePacket, true, true); +}); + +/** + * Output progress. + */ + +suite.on('cycle', function (bench, details) { + console.log('\n ' + suite.name.grey, details.name.white.bold); + console.log(' ' + [ + details.hz.toFixed(2).cyan + ' ops/sec'.grey + , details.count.toString().white + ' times executed'.grey + , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey + , + ].join(', '.grey)); +}); + +/** + * Run/export benchmarks. + */ + +if (!module.parent) { + suite.run(); +} else { + module.exports = suite; +} diff --git a/node_modules/ws/bench/speed.js b/node_modules/ws/bench/speed.js new file mode 100644 index 0000000..3ce6414 --- /dev/null +++ b/node_modules/ws/bench/speed.js @@ -0,0 +1,105 @@ +var cluster = require('cluster') + , WebSocket = require('../') + , WebSocketServer = WebSocket.Server + , crypto = require('crypto') + , util = require('util') + , ansi = require('ansi'); +require('tinycolor'); + +function roundPrec(num, prec) { + var mul = Math.pow(10, prec); + return Math.round(num * mul) / mul; +} + +function humanSize(bytes) { + if (bytes >= 1048576) return roundPrec(bytes / 1048576, 2) + ' MB'; + if (bytes >= 1024) return roundPrec(bytes / 1024, 2) + ' kB'; + return roundPrec(bytes, 2) + ' B'; +} + +function generateRandomData(size) { + var buffer = new Buffer(size); + for (var i = 0; i < size; ++i) { + buffer[i] = ~~(Math.random() * 127); + } + return buffer; +} + +if (cluster.isMaster) { + var wss = new WebSocketServer({port: 8181}, function() { + cluster.fork(); + }); + wss.on('connection', function(ws) { + ws.on('message', function(data, flags) { + ws.send(data, {binary: flags&&flags.binary}); + }); + ws.on('close', function() {}); + }); + cluster.on('death', function(worker) { + wss.close(); + }); +} +else { + var cursor = ansi(process.stdout); + + var configs = [ + [true, 10000, 64], + [true, 5000, 16*1024], + [true, 1000, 128*1024], + [true, 100, 1024*1024], + [true, 1, 500*1024*1024], + [false, 10000, 64], + [false, 5000, 16*1024], + [false, 1000, 128*1024], + [false, 100, 1024*1024], + ]; + + var largest = configs[0][1]; + for (var i = 0, l = configs.length; i < l; ++i) { + if (configs[i][2] > largest) largest = configs[i][2]; + } + + console.log('Generating %s of test data ...', humanSize(largest)); + var randomBytes = generateRandomData(largest); + + function roundtrip(useBinary, roundtrips, size, cb) { + var data = randomBytes.slice(0, size); + var prefix = util.format('Running %d roundtrips of %s %s data', roundtrips, humanSize(size), useBinary ? 'binary' : 'text'); + console.log(prefix); + var client = new WebSocket('ws://localhost:' + '8181'); + var dt; + var roundtrip = 0; + function send() { + client.send(data, {binary: useBinary}); + } + client.on('error', function(e) { + console.error(e); + process.exit(); + }); + client.on('open', function() { + dt = Date.now(); + send(); + }); + client.on('message', function(data, flags) { + if (++roundtrip == roundtrips) { + var elapsed = Date.now() - dt; + cursor.up(); + console.log('%s:\t%ss\t%s' + , useBinary ? prefix.green : prefix.cyan + , roundPrec(elapsed / 1000, 1).toString().green.bold + , (humanSize((size * roundtrips) / elapsed * 1000) + '/s').blue.bold); + client.close(); + cb(); + return; + } + process.nextTick(send); + }); + } + + (function run() { + if (configs.length == 0) process.exit(); + var config = configs.shift(); + config.push(run); + roundtrip.apply(null, config); + })(); +} \ No newline at end of file diff --git a/node_modules/ws/bench/util.js b/node_modules/ws/bench/util.js new file mode 100644 index 0000000..5f01281 --- /dev/null +++ b/node_modules/ws/bench/util.js @@ -0,0 +1,105 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +/** + * Returns a Buffer from a "ff 00 ff"-type hex string. + */ + +getBufferFromHexString = function(byteStr) { + var bytes = byteStr.split(' '); + var buf = new Buffer(bytes.length); + for (var i = 0; i < bytes.length; ++i) { + buf[i] = parseInt(bytes[i], 16); + } + return buf; +} + +/** + * Returns a hex string from a Buffer. + */ + +getHexStringFromBuffer = function(data) { + var s = ''; + for (var i = 0; i < data.length; ++i) { + s += padl(data[i].toString(16), 2, '0') + ' '; + } + return s.trim(); +} + +/** + * Splits a buffer in two parts. + */ + +splitBuffer = function(buffer) { + var b1 = new Buffer(Math.ceil(buffer.length / 2)); + buffer.copy(b1, 0, 0, b1.length); + var b2 = new Buffer(Math.floor(buffer.length / 2)); + buffer.copy(b2, 0, b1.length, b1.length + b2.length); + return [b1, b2]; +} + +/** + * Performs hybi07+ type masking on a hex string or buffer. + */ + +mask = function(buf, maskString) { + if (typeof buf == 'string') buf = new Buffer(buf); + var mask = getBufferFromHexString(maskString || '34 83 a8 68'); + for (var i = 0; i < buf.length; ++i) { + buf[i] ^= mask[i % 4]; + } + return buf; +} + +/** + * Returns a hex string representing the length of a message + */ + +getHybiLengthAsHexString = function(len, masked) { + if (len < 126) { + var buf = new Buffer(1); + buf[0] = (masked ? 0x80 : 0) | len; + } + else if (len < 65536) { + var buf = new Buffer(3); + buf[0] = (masked ? 0x80 : 0) | 126; + getBufferFromHexString(pack(4, len)).copy(buf, 1); + } + else { + var buf = new Buffer(9); + buf[0] = (masked ? 0x80 : 0) | 127; + getBufferFromHexString(pack(16, len)).copy(buf, 1); + } + return getHexStringFromBuffer(buf); +} + +/** + * Unpacks a Buffer into a number. + */ + +unpack = function(buffer) { + var n = 0; + for (var i = 0; i < buffer.length; ++i) { + n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; + } + return n; +} + +/** + * Returns a hex string, representing a specific byte count 'length', from a number. + */ + +pack = function(length, number) { + return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); +} + +/** + * Left pads the string 's' to a total length of 'n' with char 'c'. + */ + +padl = function(s, n, c) { + return new Array(1 + n - s.length).join(c) + s; +} diff --git a/node_modules/ws/bin/wscat b/node_modules/ws/bin/wscat new file mode 100755 index 0000000..0ec894d --- /dev/null +++ b/node_modules/ws/bin/wscat @@ -0,0 +1,190 @@ +#!/usr/bin/env node + +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var WebSocket = require('../') + , fs = require('fs') + , program = require('commander') + , util = require('util') + , events = require('events') + , readline = require('readline'); + +/** + * InputReader - processes console input + */ + +function Console() { + this.stdin = process.stdin; + this.stdout = process.stdout; + + this.readlineInterface = readline.createInterface(this.stdin, this.stdout); + + var self = this; + this.readlineInterface.on('line', function(data) { + self.emit('line', data); + }); + this.readlineInterface.on('close', function() { + self.emit('close'); + }); + + this._resetInput = function() { + self.clear(); + } +} +util.inherits(Console, events.EventEmitter); + +Console.Colors = { + Red: '\033[31m', + Green: '\033[32m', + Yellow: '\033[33m', + Blue: '\033[34m', + Default: '\033[39m' +}; + +Console.prototype.prompt = function() { + this.readlineInterface.prompt(); +} + +Console.prototype.print = function(msg, color) { + this.clear(); + color = color || Console.Colors.Default; + this.stdout.write(color + msg + Console.Colors.Default + '\n'); + this.prompt(); +} + +Console.prototype.clear = function() { + this.stdout.write('\033[2K\033[E'); +} + +Console.prototype.pause = function() { + this.stdin.on('keypress', this._resetInput); +} + +Console.prototype.resume = function() { + this.stdin.removeListener('keypress', this._resetInput); +} + +/** + * The actual application + */ + +var version = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')).version; +program + .version(version) + .usage('[options] ') + .option('-l, --listen ', 'listen on port') + .option('-c, --connect ', 'connect to a websocket server') + .option('-p, --protocol ', 'optional protocol version') + .option('-o, --origin ', 'optional origin') + .option('--host ', 'optional host') + .option('-s, --subprotocol ', 'optional subprotocol') + .parse(process.argv); + +if (program.listen && program.connect) { + console.error('\033[33merror: use either --listen or --connect\033[39m'); + process.exit(-1); +} +else if (program.listen) { + var wsConsole = new Console(); + wsConsole.pause(); + var options = {}; + if (program.protocol) options.protocolVersion = program.protocol; + if (program.origin) options.origin = program.origin; + if (program.subprotocol) options.protocol = program.subprotocol; + var ws = null; + var wss = new WebSocket.Server({port: program.listen}, function() { + wsConsole.print('listening on port ' + program.listen + ' (press CTRL+C to quit)', Console.Colors.Green); + wsConsole.clear(); + }); + wsConsole.on('close', function() { + if (ws) { + try { + ws.close(); + } + catch (e) {} + } + process.exit(0); + }); + wsConsole.on('line', function(data) { + if (ws) { + ws.send(data, {mask: false}); + wsConsole.prompt(); + } + }); + wss.on('connection', function(newClient) { + if (ws) { + // limit to one client + newClient.terminate(); + return; + }; + ws = newClient; + wsConsole.resume(); + wsConsole.prompt(); + wsConsole.print('client connected', Console.Colors.Green); + ws.on('close', function() { + wsConsole.print('disconnected', Console.Colors.Green); + wsConsole.clear(); + wsConsole.pause(); + ws = null; + }); + ws.on('error', function(code, description) { + wsConsole.print('error: ' + code + (description ? ' ' + description : ''), Console.Colors.Yellow); + }); + ws.on('message', function(data, flags) { + wsConsole.print('< ' + data, Console.Colors.Blue); + }); + }); + wss.on('error', function(error) { + wsConsole.print('error: ' + error.toString(), Console.Colors.Yellow); + process.exit(-1); + }); +} +else if (program.connect) { + var wsConsole = new Console(); + var options = {}; + if (program.protocol) options.protocolVersion = program.protocol; + if (program.origin) options.origin = program.origin; + if (program.subprotocol) options.protocol = program.subprotocol; + if (program.host) options.host = program.host; + var ws = new WebSocket(program.connect, options); + ws.on('open', function() { + wsConsole.print('connected (press CTRL+C to quit)', Console.Colors.Green); + wsConsole.on('line', function(data) { + ws.send(data, {mask: true}); + wsConsole.prompt(); + }); + }); + ws.on('close', function() { + wsConsole.print('disconnected', Console.Colors.Green); + wsConsole.clear(); + process.exit(); + }); + ws.on('error', function(code, description) { + wsConsole.print('error: ' + code + (description ? ' ' + description : ''), Console.Colors.Yellow); + process.exit(-1); + }); + ws.on('message', function(data, flags) { + wsConsole.print('< ' + data, Console.Colors.Blue); + }); + wsConsole.on('close', function() { + if (ws) { + try { + ws.close(); + } + catch(e) {} + process.exit(); + } + }); +} +else { + console.error('\033[33merror: use either --listen or --connect\033[39m'); + process.exit(-1); +} diff --git a/node_modules/ws/binding.gyp b/node_modules/ws/binding.gyp new file mode 100644 index 0000000..c9b4d1a --- /dev/null +++ b/node_modules/ws/binding.gyp @@ -0,0 +1,16 @@ +{ + 'targets': [ + { + 'target_name': 'validation', + 'include_dirs': ["> $(depfile) +# Add extra rules as in (2). +# We remove slashes and replace spaces with new lines; +# remove blank lines; +# delete the first line and append a colon to the remaining lines. +sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\ + grep -v '^$$' |\ + sed -e 1d -e 's|$$|:|' \ + >> $(depfile) +rm $(depfile).raw +endef + +# Command definitions: +# - cmd_foo is the actual command to run; +# - quiet_cmd_foo is the brief-output summary of the command. + +quiet_cmd_cc = CC($(TOOLSET)) $@ +cmd_cc = $(CC.$(TOOLSET)) $(GYP_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET)) -c -o $@ $< + +quiet_cmd_cxx = CXX($(TOOLSET)) $@ +cmd_cxx = $(CXX.$(TOOLSET)) $(GYP_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< + +quiet_cmd_objc = CXX($(TOOLSET)) $@ +cmd_objc = $(CC.$(TOOLSET)) $(GYP_OBJCFLAGS) $(DEPFLAGS) -c -o $@ $< + +quiet_cmd_objcxx = CXX($(TOOLSET)) $@ +cmd_objcxx = $(CXX.$(TOOLSET)) $(GYP_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $< + +# Commands for precompiled header files. +quiet_cmd_pch_c = CXX($(TOOLSET)) $@ +cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< +quiet_cmd_pch_cc = CXX($(TOOLSET)) $@ +cmd_pch_cc = $(CC.$(TOOLSET)) $(GYP_PCH_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< +quiet_cmd_pch_m = CXX($(TOOLSET)) $@ +cmd_pch_m = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCFLAGS) $(DEPFLAGS) -c -o $@ $< +quiet_cmd_pch_mm = CXX($(TOOLSET)) $@ +cmd_pch_mm = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $< + +# gyp-mac-tool is written next to the root Makefile by gyp. +# Use $(4) for the command, since $(2) and $(3) are used as flag by do_cmd +# already. +quiet_cmd_mac_tool = MACTOOL $(4) $< +cmd_mac_tool = ./gyp-mac-tool $(4) $< "$@" + +quiet_cmd_mac_package_framework = PACKAGE FRAMEWORK $@ +cmd_mac_package_framework = ./gyp-mac-tool package-framework "$@" $(4) + +quiet_cmd_infoplist = INFOPLIST $@ +cmd_infoplist = $(CC.$(TOOLSET)) -E -P -Wno-trigraphs -x c $(INFOPLIST_DEFINES) "$<" -o "$@" + +quiet_cmd_touch = TOUCH $@ +cmd_touch = touch $@ + +quiet_cmd_copy = COPY $@ +# send stderr to /dev/null to ignore messages when linking directories. +cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp -af "$<" "$@") + +quiet_cmd_alink = LIBTOOL-STATIC $@ +cmd_alink = rm -f $@ && ./gyp-mac-tool filter-libtool libtool -static -o $@ $(filter %.o,$^) + +quiet_cmd_link = LINK($(TOOLSET)) $@ +cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS) + +# TODO(thakis): Find out and document the difference between shared_library and +# loadable_module on mac. +quiet_cmd_solink = SOLINK($(TOOLSET)) $@ +cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS) + +# TODO(thakis): The solink_module rule is likely wrong. Xcode seems to pass +# -bundle -single_module here (for osmesa.so). +quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@ +cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS) + + +# Define an escape_quotes function to escape single quotes. +# This allows us to handle quotes properly as long as we always use +# use single quotes and escape_quotes. +escape_quotes = $(subst ','\'',$(1)) +# This comment is here just to include a ' to unconfuse syntax highlighting. +# Define an escape_vars function to escape '$' variable syntax. +# This allows us to read/write command lines with shell variables (e.g. +# $LD_LIBRARY_PATH), without triggering make substitution. +escape_vars = $(subst $$,$$$$,$(1)) +# Helper that expands to a shell command to echo a string exactly as it is in +# make. This uses printf instead of echo because printf's behaviour with respect +# to escape sequences is more portable than echo's across different shells +# (e.g., dash, bash). +exact_echo = printf '%s\n' '$(call escape_quotes,$(1))' + +# Helper to compare the command we're about to run against the command +# we logged the last time we ran the command. Produces an empty +# string (false) when the commands match. +# Tricky point: Make has no string-equality test function. +# The kernel uses the following, but it seems like it would have false +# positives, where one string reordered its arguments. +# arg_check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \ +# $(filter-out $(cmd_$@), $(cmd_$(1)))) +# We instead substitute each for the empty string into the other, and +# say they're equal if both substitutions produce the empty string. +# .d files contain ? instead of spaces, take that into account. +command_changed = $(or $(subst $(cmd_$(1)),,$(cmd_$(call replace_spaces,$@))),\ + $(subst $(cmd_$(call replace_spaces,$@)),,$(cmd_$(1)))) + +# Helper that is non-empty when a prerequisite changes. +# Normally make does this implicitly, but we force rules to always run +# so we can check their command lines. +# $? -- new prerequisites +# $| -- order-only dependencies +prereq_changed = $(filter-out FORCE_DO_CMD,$(filter-out $|,$?)) + +# Helper that executes all postbuilds, and deletes the output file when done +# if any of the postbuilds failed. +define do_postbuilds + @E=0;\ + for p in $(POSTBUILDS); do\ + eval $$p;\ + F=$$?;\ + if [ $$F -ne 0 ]; then\ + E=$$F;\ + fi;\ + done;\ + if [ $$E -ne 0 ]; then\ + rm -rf "$@";\ + exit $$E;\ + fi +endef + +# do_cmd: run a command via the above cmd_foo names, if necessary. +# Should always run for a given target to handle command-line changes. +# Second argument, if non-zero, makes it do asm/C/C++ dependency munging. +# Third argument, if non-zero, makes it do POSTBUILDS processing. +# Note: We intentionally do NOT call dirx for depfile, since it contains ? for +# spaces already and dirx strips the ? characters. +define do_cmd +$(if $(or $(command_changed),$(prereq_changed)), + @$(call exact_echo, $($(quiet)cmd_$(1))) + @mkdir -p "$(call dirx,$@)" "$(dir $(depfile))" + $(if $(findstring flock,$(word 2,$(cmd_$1))), + @$(cmd_$(1)) + @echo " $(quiet_cmd_$(1)): Finished", + @$(cmd_$(1)) + ) + @$(call exact_echo,$(call escape_vars,cmd_$(call replace_spaces,$@) := $(cmd_$(1)))) > $(depfile) + @$(if $(2),$(fixup_dep)) + $(if $(and $(3), $(POSTBUILDS)), + $(call do_postbuilds) + ) +) +endef + +# Declare the "all" target first so it is the default, +# even though we don't have the deps yet. +.PHONY: all +all: + +# make looks for ways to re-generate included makefiles, but in our case, we +# don't have a direct way. Explicitly telling make that it has nothing to do +# for them makes it go faster. +%.d: ; + +# Use FORCE_DO_CMD to force a target to run. Should be coupled with +# do_cmd. +.PHONY: FORCE_DO_CMD +FORCE_DO_CMD: + +TOOLSET := target +# Suffix rules, putting all outputs into $(obj). +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.c FORCE_DO_CMD + @$(call do_cmd,cc,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cpp FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cxx FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.m FORCE_DO_CMD + @$(call do_cmd,objc,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.mm FORCE_DO_CMD + @$(call do_cmd,objcxx,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.S FORCE_DO_CMD + @$(call do_cmd,cc,1) +$(obj).$(TOOLSET)/%.o: $(srcdir)/%.s FORCE_DO_CMD + @$(call do_cmd,cc,1) + +# Try building from generated source, too. +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD + @$(call do_cmd,cc,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cpp FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cxx FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.m FORCE_DO_CMD + @$(call do_cmd,objc,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.mm FORCE_DO_CMD + @$(call do_cmd,objcxx,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.S FORCE_DO_CMD + @$(call do_cmd,cc,1) +$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.s FORCE_DO_CMD + @$(call do_cmd,cc,1) + +$(obj).$(TOOLSET)/%.o: $(obj)/%.c FORCE_DO_CMD + @$(call do_cmd,cc,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.cc FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.cpp FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.cxx FORCE_DO_CMD + @$(call do_cmd,cxx,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.m FORCE_DO_CMD + @$(call do_cmd,objc,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.mm FORCE_DO_CMD + @$(call do_cmd,objcxx,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.S FORCE_DO_CMD + @$(call do_cmd,cc,1) +$(obj).$(TOOLSET)/%.o: $(obj)/%.s FORCE_DO_CMD + @$(call do_cmd,cc,1) + + +ifeq ($(strip $(foreach prefix,$(NO_LOAD),\ + $(findstring $(join ^,$(prefix)),\ + $(join ^,bufferutil.target.mk)))),) + include bufferutil.target.mk +endif +ifeq ($(strip $(foreach prefix,$(NO_LOAD),\ + $(findstring $(join ^,$(prefix)),\ + $(join ^,validation.target.mk)))),) + include validation.target.mk +endif + +quiet_cmd_regen_makefile = ACTION Regenerating $@ +cmd_regen_makefile = /Users/jefklak/.node-gyp/0.8.12/tools/gyp/gyp -fmake --ignore-environment "--toplevel-dir=." -I/Users/jefklak/development/privatews/webcam/node_modules/ws/build/config.gypi -I/usr/local/Cellar/node/0.8.12/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/Users/jefklak/.node-gyp/0.8.12/common.gypi "--depth=." "-Goutput_dir=." "--generator-output=build" "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/jefklak/.node-gyp/0.8.12" "-Dmodule_root_dir=/Users/jefklak/development/privatews/webcam/node_modules/ws" binding.gyp +Makefile: $(srcdir)/../../../../../.node-gyp/0.8.12/common.gypi $(srcdir)/../../../../../../../usr/local/Cellar/node/0.8.12/lib/node_modules/npm/node_modules/node-gyp/addon.gypi $(srcdir)/build/config.gypi $(srcdir)/binding.gyp + $(call do_cmd,regen_makefile) + +# "all" is a concatenation of the "all" targets from all the included +# sub-makefiles. This is just here to clarify. +all: + +# Add in dependency-tracking rules. $(all_deps) is the list of every single +# target in our tree. Only consider the ones with .d (dependency) info: +d_files := $(wildcard $(foreach f,$(all_deps),$(depsdir)/$(f).d)) +ifneq ($(d_files),) + include $(d_files) +endif diff --git a/node_modules/ws/build/Release/.deps/Release/bufferutil.node.d b/node_modules/ws/build/Release/.deps/Release/bufferutil.node.d new file mode 100644 index 0000000..45d22a2 --- /dev/null +++ b/node_modules/ws/build/Release/.deps/Release/bufferutil.node.d @@ -0,0 +1 @@ +cmd_Release/bufferutil.node := ./gyp-mac-tool flock ./Release/linker.lock c++ -shared -Wl,-search_paths_first -mmacosx-version-min=10.5 -arch x86_64 -L./Release -install_name /usr/local/lib/bufferutil.node -o Release/bufferutil.node Release/obj.target/bufferutil/src/bufferutil.o -undefined dynamic_lookup diff --git a/node_modules/ws/build/Release/.deps/Release/obj.target/bufferutil/src/bufferutil.o.d b/node_modules/ws/build/Release/.deps/Release/obj.target/bufferutil/src/bufferutil.o.d new file mode 100644 index 0000000..45d116f --- /dev/null +++ b/node_modules/ws/build/Release/.deps/Release/obj.target/bufferutil/src/bufferutil.o.d @@ -0,0 +1,33 @@ +cmd_Release/obj.target/bufferutil/src/bufferutil.o := c++ '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-D_DARWIN_USE_64_BIT_INODE=1' -I/Users/jefklak/.node-gyp/0.8.12/src -I/Users/jefklak/.node-gyp/0.8.12/deps/uv/include -I/Users/jefklak/.node-gyp/0.8.12/deps/v8/include -I/Users/jefklak/development/privatews/webcam/node_modules/ws/node_modules/nan -Os -gdwarf-2 -mmacosx-version-min=10.5 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -fno-rtti -fno-exceptions -fno-threadsafe-statics -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/bufferutil/src/bufferutil.o.d.raw -c -o Release/obj.target/bufferutil/src/bufferutil.o ../src/bufferutil.cc +Release/obj.target/bufferutil/src/bufferutil.o: ../src/bufferutil.cc \ + /Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8stdint.h \ + /Users/jefklak/.node-gyp/0.8.12/src/node.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares_version.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/uv-unix.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ngx-queue.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ev.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/eio.h \ + /Users/jefklak/.node-gyp/0.8.12/src/node_object_wrap.h \ + /Users/jefklak/.node-gyp/0.8.12/src/ev-emul.h \ + /Users/jefklak/.node-gyp/0.8.12/src/eio-emul.h \ + /Users/jefklak/.node-gyp/0.8.12/src/node_buffer.h \ + /Users/jefklak/development/privatews/webcam/node_modules/ws/node_modules/nan/nan.h +../src/bufferutil.cc: +/Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8.h: +/Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8stdint.h: +/Users/jefklak/.node-gyp/0.8.12/src/node.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares_version.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/uv-unix.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ngx-queue.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ev.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/eio.h: +/Users/jefklak/.node-gyp/0.8.12/src/node_object_wrap.h: +/Users/jefklak/.node-gyp/0.8.12/src/ev-emul.h: +/Users/jefklak/.node-gyp/0.8.12/src/eio-emul.h: +/Users/jefklak/.node-gyp/0.8.12/src/node_buffer.h: +/Users/jefklak/development/privatews/webcam/node_modules/ws/node_modules/nan/nan.h: diff --git a/node_modules/ws/build/Release/.deps/Release/obj.target/validation/src/validation.o.d b/node_modules/ws/build/Release/.deps/Release/obj.target/validation/src/validation.o.d new file mode 100644 index 0000000..a827fc8 --- /dev/null +++ b/node_modules/ws/build/Release/.deps/Release/obj.target/validation/src/validation.o.d @@ -0,0 +1,33 @@ +cmd_Release/obj.target/validation/src/validation.o := c++ '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-D_DARWIN_USE_64_BIT_INODE=1' -I/Users/jefklak/.node-gyp/0.8.12/src -I/Users/jefklak/.node-gyp/0.8.12/deps/uv/include -I/Users/jefklak/.node-gyp/0.8.12/deps/v8/include -I/Users/jefklak/development/privatews/webcam/node_modules/ws/node_modules/nan -Os -gdwarf-2 -mmacosx-version-min=10.5 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -fno-rtti -fno-exceptions -fno-threadsafe-statics -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/validation/src/validation.o.d.raw -c -o Release/obj.target/validation/src/validation.o ../src/validation.cc +Release/obj.target/validation/src/validation.o: ../src/validation.cc \ + /Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8stdint.h \ + /Users/jefklak/.node-gyp/0.8.12/src/node.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares_version.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/uv-unix.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ngx-queue.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ev.h \ + /Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/eio.h \ + /Users/jefklak/.node-gyp/0.8.12/src/node_object_wrap.h \ + /Users/jefklak/.node-gyp/0.8.12/src/ev-emul.h \ + /Users/jefklak/.node-gyp/0.8.12/src/eio-emul.h \ + /Users/jefklak/.node-gyp/0.8.12/src/node_buffer.h \ + /Users/jefklak/development/privatews/webcam/node_modules/ws/node_modules/nan/nan.h +../src/validation.cc: +/Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8.h: +/Users/jefklak/.node-gyp/0.8.12/deps/v8/include/v8stdint.h: +/Users/jefklak/.node-gyp/0.8.12/src/node.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/ares_version.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/uv-unix.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ngx-queue.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/ev.h: +/Users/jefklak/.node-gyp/0.8.12/deps/uv/include/uv-private/eio.h: +/Users/jefklak/.node-gyp/0.8.12/src/node_object_wrap.h: +/Users/jefklak/.node-gyp/0.8.12/src/ev-emul.h: +/Users/jefklak/.node-gyp/0.8.12/src/eio-emul.h: +/Users/jefklak/.node-gyp/0.8.12/src/node_buffer.h: +/Users/jefklak/development/privatews/webcam/node_modules/ws/node_modules/nan/nan.h: diff --git a/node_modules/ws/build/Release/.deps/Release/validation.node.d b/node_modules/ws/build/Release/.deps/Release/validation.node.d new file mode 100644 index 0000000..55bc3e7 --- /dev/null +++ b/node_modules/ws/build/Release/.deps/Release/validation.node.d @@ -0,0 +1 @@ +cmd_Release/validation.node := ./gyp-mac-tool flock ./Release/linker.lock c++ -shared -Wl,-search_paths_first -mmacosx-version-min=10.5 -arch x86_64 -L./Release -install_name /usr/local/lib/validation.node -o Release/validation.node Release/obj.target/validation/src/validation.o -undefined dynamic_lookup diff --git a/node_modules/ws/build/Release/bufferutil.node b/node_modules/ws/build/Release/bufferutil.node new file mode 100755 index 0000000000000000000000000000000000000000..9cb2b987083148bbeefc4094c5f0814d43aab964 GIT binary patch literal 18268 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%0A&(NBGZ1#j?B9I)!?!^t#$+>EGeFbFR$R zLhE&2hGt|mUn#Gn9n{KrMr$Oa?IWY@A*1akqwON2eLzO@^P|rXZ95sw&zoLH+d@X$ zL`K^{MthZv_6iwoEg5YM8Eq9AZ6z6PIT>vk8Eq*UZ7~^bAsKA}8EqaJZ7vzDfs8hX zj8;!Zn?**eBcshAqfI5F)soR_$Y|rqXw_u2ab&bHWVF#_v~n_985wN^8LgCzRzgM_ zL`Ewnqg_HqD^$WB5H+ zc>PC@+d&<)!s|cM8p&w;$Y^`WXnsx9+oSCwqkTX|+d)Pv*755UwMWx2GGw$OGMayn zTJM|6(3X+WmXgsHlhGEE(H4-==8@6nlF=H-XmiMD^<=bJWVAXm+6*$MjJy$8%;(lC!>{-(MFKbO37#?WVAtKv|=*aC1kWBGFl-SEuW0mjf~cn zjMkZqmP9U4n1Qy|9WnW9uMbV z&-=)Gm>$FV*Xz5f@1l<3{Ok1(sPCYT;r#3M?bNqX$8i4j`WEV&sAD+)dVK@+SE*w- z|9bru>T9WEIRAQm4fR#jF`R$BzLNTK>KM+yUSCFiDRm6zU#~BwzK}YG^RL$zP@hK~ z!}-_ibE!8_$8i4j`W))@)G?fYy*`V29d!)nU$4)gK9xF#^RL%ysn<})aQ^lBcKM+yULQ@poH~Z{uh+|{kD!j>{Ok2n>Lt|C{99%E9N6O5_&CnNDOibD zV0^vBU)SFLbZm+L)qMQ%_i+QR#i#Kx{1N^TZ^G;FDjbaQ^_I@$)_4N$*Llq6{}H~2 zo6$eNczp#vi4WjxoQ4x{EMATA^_N0&SB$T__;)6JJ_mH3bU((w;h%6lzKGA@qxc}+ zhSjLgilN{5y36_GcKGng`p}^d{`K$b0sTBizMs5=JeT}S@_h1d$WM^>lb<5{e}eP= z-zR^qWt8`lTa(+p7tQ~AawqbQ zqIQZe{{nIjxtirajeHw9;aMBEaeuTvxuSKex~!VFqWq~YUbJpkmsQjlpNOM z`PosuntFJjruP5Y7S$W6@1Wj6&u8zy^6jX8o-U$z|7|{ta=D&gUeBiedfKm|{Vgq` z`s(e`^t))kiuV1fFQ>kVdIR-BT`%$e8~+lu_upgq{Hv+oL;JUA?-zl*zJvNsmVXrO zhts~8`exea_=QOQoJ0HU&!haJZ%@s?l4geEA#Lep`I`bLz6z^8D!J^HbhK{MpCb|HaR4uj~2JhW`A0 zOj6mebsd(=Q}Q=7aq{#PTAt+Wn=v`hOUa*T{gdmpnqhMOgZ6K7J~?IjO}c)ST%VRQ z{gRaFFHf1iZ_51qyYoS*dYHyE9Y$jxrF58S z_)6OMipTVdhXD)I`v_xc@9gd4iybz+nC@^G-`Xm9gf5Lln`s6Gz2aM9$FuZNoS3>$ z!v*O-xyO|remC~!|DRp=D)9!%+b#2mnm*-MH`-e2a%CF1Gj+O*VD1M5hS(W5r zKnudX#~G5&S2@#)`9|)Od`}IZQPUkrbn}$xso88>9_ww5Sb=y?eCgXd(XOy;;$$E2 z^6`)`qa&yO~*P0I`>6)fLeaBOVXTeu7ifBB;wEXa@lQE;>waoCGizreS{5!W}~ lBXR%n1}pF_cEpz9u*__Hv-)O_d&wGHQgNdXgqfd}^?zuV5aR#< literal 0 HcmV?d00001 diff --git a/node_modules/ws/build/Release/obj.target/validation/src/validation.o b/node_modules/ws/build/Release/obj.target/validation/src/validation.o new file mode 100644 index 0000000000000000000000000000000000000000..f077de7d7b425643a7ab57822363ca49e7da1062 GIT binary patch literal 74376 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 + + + + + + + +

This example will upload an entire directory tree to the node.js server via a fast and persistent WebSocket connection.

+

Note that the example is Chrome only for now.

+

+ Upload status: +
Please select a directory to upload.
+ + diff --git a/node_modules/ws/examples/fileapi/public/uploader.js b/node_modules/ws/examples/fileapi/public/uploader.js new file mode 100644 index 0000000..0c34a7f --- /dev/null +++ b/node_modules/ws/examples/fileapi/public/uploader.js @@ -0,0 +1,55 @@ +function Uploader(url, cb) { + this.ws = new WebSocket(url); + if (cb) this.ws.onopen = cb; + this.sendQueue = []; + this.sending = null; + this.sendCallback = null; + this.ondone = null; + var self = this; + this.ws.onmessage = function(event) { + var data = JSON.parse(event.data); + if (data.event == 'complete') { + if (data.path != self.sending.path) { + self.sendQueue = []; + self.sending = null; + self.sendCallback = null; + throw new Error('Got message for wrong file!'); + } + self.sending = null; + var callback = self.sendCallback; + self.sendCallback = null; + if (callback) callback(); + if (self.sendQueue.length === 0 && self.ondone) self.ondone(null); + if (self.sendQueue.length > 0) { + var args = self.sendQueue.pop(); + setTimeout(function() { self.sendFile.apply(self, args); }, 0); + } + } + else if (data.event == 'error') { + self.sendQueue = []; + self.sending = null; + var callback = self.sendCallback; + self.sendCallback = null; + var error = new Error('Server reported send error for file ' + data.path); + if (callback) callback(error); + if (self.ondone) self.ondone(error); + } + } +} + +Uploader.prototype.sendFile = function(file, cb) { + if (this.ws.readyState != WebSocket.OPEN) throw new Error('Not connected'); + if (this.sending) { + this.sendQueue.push(arguments); + return; + } + var fileData = { name: file.name, path: file.webkitRelativePath }; + this.sending = fileData; + this.sendCallback = cb; + this.ws.send(JSON.stringify(fileData)); + this.ws.send(file); +} + +Uploader.prototype.close = function() { + this.ws.close(); +} diff --git a/node_modules/ws/examples/fileapi/server.js b/node_modules/ws/examples/fileapi/server.js new file mode 100644 index 0000000..badfeba --- /dev/null +++ b/node_modules/ws/examples/fileapi/server.js @@ -0,0 +1,103 @@ +var WebSocketServer = require('../../').Server + , express = require('express') + , fs = require('fs') + , http = require('http') + , util = require('util') + , path = require('path') + , app = express.createServer() + , events = require('events') + , ansi = require('ansi') + , cursor = ansi(process.stdout); + +function BandwidthSampler(ws, interval) { + interval = interval || 2000; + var previousByteCount = 0; + var self = this; + var intervalId = setInterval(function() { + var byteCount = ws.bytesReceived; + var bytesPerSec = (byteCount - previousByteCount) / (interval / 1000); + previousByteCount = byteCount; + self.emit('sample', bytesPerSec); + }, interval); + ws.on('close', function() { + clearInterval(intervalId); + }); +} +util.inherits(BandwidthSampler, events.EventEmitter); + +function makePathForFile(filePath, prefix, cb) { + if (typeof cb !== 'function') throw new Error('callback is required'); + filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, ''); + var pieces = filePath.split(/(\\|\/)/); + var incrementalPath = prefix; + function step(error) { + if (error) return cb(error); + if (pieces.length == 0) return cb(null, incrementalPath); + incrementalPath += '/' + pieces.shift(); + fs.exists(incrementalPath, function(exists) { + if (!exists) fs.mkdir(incrementalPath, step); + else process.nextTick(step); + }); + } + step(); +} + +cursor.eraseData(2).goto(1, 1); +app.use(express.static(__dirname + '/public')); + +var clientId = 0; +var wss = new WebSocketServer({server: app}); +wss.on('connection', function(ws) { + var thisId = ++clientId; + cursor.goto(1, 4 + thisId).eraseLine(); + console.log('Client #%d connected', thisId); + + var sampler = new BandwidthSampler(ws); + sampler.on('sample', function(bps) { + cursor.goto(1, 4 + thisId).eraseLine(); + console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024*1024))); + }); + + var filesReceived = 0; + var currentFile = null; + ws.on('message', function(data, flags) { + if (!flags.binary) { + currentFile = JSON.parse(data); + // note: a real-world app would want to sanity check the data + } + else { + if (currentFile == null) return; + makePathForFile(currentFile.path, __dirname + '/uploaded', function(error, path) { + if (error) { + console.log(error); + ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message})); + return; + } + fs.writeFile(path + '/' + currentFile.name, data, function(error) { + ++filesReceived; + // console.log('received %d bytes long file, %s', data.length, currentFile.path); + ws.send(JSON.stringify({event: 'complete', path: currentFile.path})); + currentFile = null; + }); + }); + } + }); + + ws.on('close', function() { + cursor.goto(1, 4 + thisId).eraseLine(); + console.log('Client #%d disconnected. %d files received.', thisId, filesReceived); + }); + + ws.on('error', function(e) { + cursor.goto(1, 4 + thisId).eraseLine(); + console.log('Client #%d error: %s', thisId, e.message); + }); +}); + +fs.mkdir(__dirname + '/uploaded', function(error) { + // ignore errors, most likely means directory exists + console.log('Uploaded files will be saved to %s/uploaded.', __dirname); + console.log('Remember to wipe this directory if you upload lots and lots.'); + app.listen(8080); + console.log('Listening on http://localhost:8080'); +}); diff --git a/node_modules/ws/examples/serverstats-express_3/package.json b/node_modules/ws/examples/serverstats-express_3/package.json new file mode 100644 index 0000000..99722c4 --- /dev/null +++ b/node_modules/ws/examples/serverstats-express_3/package.json @@ -0,0 +1,17 @@ +{ + "author": "", + "name": "serverstats", + "version": "0.0.0", + "repository": { + "type": "git", + "url": "git://github.com/einaros/ws.git" + }, + "engines": { + "node": ">0.4.0" + }, + "dependencies": { + "express": "~3.0.0" + }, + "devDependencies": {}, + "optionalDependencies": {} +} diff --git a/node_modules/ws/examples/serverstats-express_3/public/index.html b/node_modules/ws/examples/serverstats-express_3/public/index.html new file mode 100644 index 0000000..24d84e1 --- /dev/null +++ b/node_modules/ws/examples/serverstats-express_3/public/index.html @@ -0,0 +1,33 @@ + + + + + + + + Server Stats
+ RSS:

+ Heap total:

+ Heap used:

+ + diff --git a/node_modules/ws/examples/serverstats-express_3/server.js b/node_modules/ws/examples/serverstats-express_3/server.js new file mode 100644 index 0000000..88bbc9e --- /dev/null +++ b/node_modules/ws/examples/serverstats-express_3/server.js @@ -0,0 +1,21 @@ +var WebSocketServer = require('../../').Server + , http = require('http') + , express = require('express') + , app = express(); + +app.use(express.static(__dirname + '/public')); + +var server = http.createServer(app); +server.listen(8080); + +var wss = new WebSocketServer({server: server}); +wss.on('connection', function(ws) { + var id = setInterval(function() { + ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); + }, 100); + console.log('started client interval'); + ws.on('close', function() { + console.log('stopping client interval'); + clearInterval(id); + }); +}); diff --git a/node_modules/ws/examples/serverstats/package.json b/node_modules/ws/examples/serverstats/package.json new file mode 100644 index 0000000..65c900a --- /dev/null +++ b/node_modules/ws/examples/serverstats/package.json @@ -0,0 +1,17 @@ +{ + "author": "", + "name": "serverstats", + "version": "0.0.0", + "repository": { + "type": "git", + "url": "git://github.com/einaros/ws.git" + }, + "engines": { + "node": ">0.4.0" + }, + "dependencies": { + "express": "2.x" + }, + "devDependencies": {}, + "optionalDependencies": {} +} diff --git a/node_modules/ws/examples/serverstats/public/index.html b/node_modules/ws/examples/serverstats/public/index.html new file mode 100644 index 0000000..24d84e1 --- /dev/null +++ b/node_modules/ws/examples/serverstats/public/index.html @@ -0,0 +1,33 @@ + + + + + + + + Server Stats
+ RSS:

+ Heap total:

+ Heap used:

+ + diff --git a/node_modules/ws/examples/serverstats/server.js b/node_modules/ws/examples/serverstats/server.js new file mode 100644 index 0000000..0bbce36 --- /dev/null +++ b/node_modules/ws/examples/serverstats/server.js @@ -0,0 +1,19 @@ +var WebSocketServer = require('../../').Server + , http = require('http') + , express = require('express') + , app = express.createServer(); + +app.use(express.static(__dirname + '/public')); +app.listen(8080); + +var wss = new WebSocketServer({server: app}); +wss.on('connection', function(ws) { + var id = setInterval(function() { + ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ }); + }, 100); + console.log('started client interval'); + ws.on('close', function() { + console.log('stopping client interval'); + clearInterval(id); + }) +}); diff --git a/node_modules/ws/examples/ssl.js b/node_modules/ws/examples/ssl.js new file mode 100644 index 0000000..bf1bf53 --- /dev/null +++ b/node_modules/ws/examples/ssl.js @@ -0,0 +1,59 @@ + +(function(){ + + "use strict"; + + var fs = require('fs'); + + // you'll probably load configuration from config + var cfg = { + ssl: true, + port: 8080, + ssl_key: '/path/to/you/ssl.key', + ssl_cert: '/path/to/you/ssl.crt' + }; + + var httpServ = ( cfg.ssl ) ? require('https') : require('http'); + + var WebSocketServer = require('../').Server; + + var app = null; + + // dummy request processing + var processRequest = function( req, res ) { + + res.writeHead(200); + res.end("All glory to WebSockets!\n"); + }; + + if ( cfg.ssl ) { + + app = httpServ.createServer({ + + // providing server with SSL key/cert + key: fs.readFileSync( cfg.ssl_key ), + cert: fs.readFileSync( cfg.ssl_cert ) + + }, processRequest ).listen( cfg.port ); + + } else { + + app = httpServ.createServer( processRequest ).listen( cfg.port ); + } + + // passing or reference to web server so WS would knew port and SSL capabilities + var wss = new WebSocketServer( { server: app } ); + + + wss.on( 'connection', function ( wsConnect ) { + + wsConnect.on( 'message', function ( message ) { + + console.log( message ); + + }); + + }); + + +}()); \ No newline at end of file diff --git a/node_modules/ws/index.js b/node_modules/ws/index.js new file mode 100644 index 0000000..3423ff2 --- /dev/null +++ b/node_modules/ws/index.js @@ -0,0 +1,26 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports = require('./lib/WebSocket'); +module.exports.Server = require('./lib/WebSocketServer'); +module.exports.Sender = require('./lib/Sender'); +module.exports.Receiver = require('./lib/Receiver'); + +module.exports.createServer = function (options, connectionListener) { + var server = new module.exports.Server(options); + if (typeof connectionListener === 'function') { + server.on('connection', connectionListener); + } + return server; +}; + +module.exports.connect = module.exports.createConnection = function (address, openListener) { + var client = new module.exports(address); + if (typeof openListener === 'function') { + client.on('open', openListener); + } + return client; +}; diff --git a/node_modules/ws/lib/BufferPool.js b/node_modules/ws/lib/BufferPool.js new file mode 100644 index 0000000..faf8637 --- /dev/null +++ b/node_modules/ws/lib/BufferPool.js @@ -0,0 +1,59 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util'); + +function BufferPool(initialSize, growStrategy, shrinkStrategy) { + if (typeof initialSize === 'function') { + shrinkStrategy = growStrategy; + growStrategy = initialSize; + initialSize = 0; + } + else if (typeof initialSize === 'undefined') { + initialSize = 0; + } + this._growStrategy = (growStrategy || function(db, size) { + return db.used + size; + }).bind(null, this); + this._shrinkStrategy = (shrinkStrategy || function(db) { + return initialSize; + }).bind(null, this); + this._buffer = initialSize ? new Buffer(initialSize) : null; + this._offset = 0; + this._used = 0; + this._changeFactor = 0; + this.__defineGetter__('size', function(){ + return this._buffer == null ? 0 : this._buffer.length; + }); + this.__defineGetter__('used', function(){ + return this._used; + }); +} + +BufferPool.prototype.get = function(length) { + if (this._buffer == null || this._offset + length > this._buffer.length) { + var newBuf = new Buffer(this._growStrategy(length)); + this._buffer = newBuf; + this._offset = 0; + } + this._used += length; + var buf = this._buffer.slice(this._offset, this._offset + length); + this._offset += length; + return buf; +} + +BufferPool.prototype.reset = function(forceNewBuffer) { + var len = this._shrinkStrategy(); + if (len < this.size) this._changeFactor -= 1; + if (forceNewBuffer || this._changeFactor < -2) { + this._changeFactor = 0; + this._buffer = len ? new Buffer(len) : null; + } + this._offset = 0; + this._used = 0; +} + +module.exports = BufferPool; diff --git a/node_modules/ws/lib/BufferUtil.fallback.js b/node_modules/ws/lib/BufferUtil.fallback.js new file mode 100644 index 0000000..508542c --- /dev/null +++ b/node_modules/ws/lib/BufferUtil.fallback.js @@ -0,0 +1,47 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports.BufferUtil = { + merge: function(mergedBuffer, buffers) { + var offset = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + var buf = buffers[i]; + buf.copy(mergedBuffer, offset); + offset += buf.length; + } + }, + mask: function(source, mask, output, offset, length) { + var maskNum = mask.readUInt32LE(0, true); + var i = 0; + for (; i < length - 3; i += 4) { + var num = maskNum ^ source.readUInt32LE(i, true); + if (num < 0) num = 4294967296 + num; + output.writeUInt32LE(num, offset + i, true); + } + switch (length % 4) { + case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; + case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; + case 1: output[offset + i] = source[i] ^ mask[0]; + case 0:; + } + }, + unmask: function(data, mask) { + var maskNum = mask.readUInt32LE(0, true); + var length = data.length; + var i = 0; + for (; i < length - 3; i += 4) { + var num = maskNum ^ data.readUInt32LE(i, true); + if (num < 0) num = 4294967296 + num; + data.writeUInt32LE(num, i, true); + } + switch (length % 4) { + case 3: data[i + 2] = data[i + 2] ^ mask[2]; + case 2: data[i + 1] = data[i + 1] ^ mask[1]; + case 1: data[i] = data[i] ^ mask[0]; + case 0:; + } + } +} diff --git a/node_modules/ws/lib/BufferUtil.js b/node_modules/ws/lib/BufferUtil.js new file mode 100644 index 0000000..15d35b9 --- /dev/null +++ b/node_modules/ws/lib/BufferUtil.js @@ -0,0 +1,16 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +try { + module.exports = require('../build/Release/bufferutil'); +} catch (e) { try { + module.exports = require('../build/default/bufferutil'); +} catch (e) { try { + module.exports = require('./BufferUtil.fallback'); +} catch (e) { + console.error('bufferutil.node seems to not have been built. Run npm install.'); + throw e; +}}} diff --git a/node_modules/ws/lib/ErrorCodes.js b/node_modules/ws/lib/ErrorCodes.js new file mode 100644 index 0000000..55ebd52 --- /dev/null +++ b/node_modules/ws/lib/ErrorCodes.js @@ -0,0 +1,24 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports = { + isValidErrorCode: function(code) { + return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || + (code >= 3000 && code <= 4999); + }, + 1000: 'normal', + 1001: 'going away', + 1002: 'protocol error', + 1003: 'unsupported data', + 1004: 'reserved', + 1005: 'reserved for extensions', + 1006: 'reserved for extensions', + 1007: 'inconsistent or invalid data', + 1008: 'policy violation', + 1009: 'message too big', + 1010: 'extension handshake missing', + 1011: 'an unexpected condition prevented the request from being fulfilled', +}; \ No newline at end of file diff --git a/node_modules/ws/lib/Receiver.hixie.js b/node_modules/ws/lib/Receiver.hixie.js new file mode 100644 index 0000000..f54ad96 --- /dev/null +++ b/node_modules/ws/lib/Receiver.hixie.js @@ -0,0 +1,180 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util'); + +/** + * State constants + */ + +var EMPTY = 0 + , BODY = 1; +var BINARYLENGTH = 2 + , BINARYBODY = 3; + +/** + * Hixie Receiver implementation + */ + +function Receiver () { + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; + this.dead = false; + + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; +} + +module.exports = Receiver; + +/** + * Add new data to the parser. + * + * @api public + */ + +Receiver.prototype.add = function(data) { + var self = this; + function doAdd() { + if (self.state === EMPTY) { + if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { + self.reset(); + self.onclose(); + return; + } + if (data[0] === 0x80) { + self.messageEnd = 0; + self.state = BINARYLENGTH; + data = data.slice(1); + } else { + + if (data[0] !== 0x00) { + self.error('payload must start with 0x00 byte', true); + return; + } + data = data.slice(1); + self.state = BODY; + + } + } + if (self.state === BINARYLENGTH) { + var i = 0; + while ((i < data.length) && (data[i] & 0x80)) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + ++i; + } + if (i < data.length) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + self.state = BINARYBODY; + ++i; + } + if (i > 0) + data = data.slice(i); + } + if (self.state === BINARYBODY) { + var dataleft = self.messageEnd - self.spanLength; + if (data.length >= dataleft) { + // consume the whole buffer to finish the frame + self.buffers.push(data); + self.spanLength += dataleft; + self.messageEnd = dataleft; + return self.parse(); + } + // frame's not done even if we consume it all + self.buffers.push(data); + self.spanLength += data.length; + return; + } + self.buffers.push(data); + if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) { + self.spanLength += self.messageEnd; + return self.parse(); + } + else self.spanLength += data.length; + } + while(data) data = doAdd(); +} + +/** + * Releases all resources used by the receiver. + * + * @api public + */ + +Receiver.prototype.cleanup = function() { + this.dead = true; + this.state = EMPTY; + this.buffers = []; +} + +/** + * Process buffered data. + * + * @api public + */ + +Receiver.prototype.parse = function() { + var output = new Buffer(this.spanLength); + var outputIndex = 0; + for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { + var buffer = this.buffers[bi]; + buffer.copy(output, outputIndex); + outputIndex += buffer.length; + } + var lastBuffer = this.buffers[this.buffers.length - 1]; + if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); + if (this.state !== BODY) --this.messageEnd; + var tail = null; + if (this.messageEnd < lastBuffer.length - 1) { + tail = lastBuffer.slice(this.messageEnd + 1); + } + this.reset(); + this.ontext(output.toString('utf8')); + return tail; +} + +/** + * Handles an error + * + * @api private + */ + +Receiver.prototype.error = function (reason, terminate) { + this.reset(); + this.onerror(reason, terminate); + return this; +} + +/** + * Reset parser state + * + * @api private + */ + +Receiver.prototype.reset = function (reason) { + if (this.dead) return; + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; +} + +/** + * Internal api + */ + +function bufferIndex(buffer, byte) { + for (var i = 0, l = buffer.length; i < l; ++i) { + if (buffer[i] === byte) return i; + } + return -1; +} diff --git a/node_modules/ws/lib/Receiver.js b/node_modules/ws/lib/Receiver.js new file mode 100644 index 0000000..2752726 --- /dev/null +++ b/node_modules/ws/lib/Receiver.js @@ -0,0 +1,591 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util') + , Validation = require('./Validation').Validation + , ErrorCodes = require('./ErrorCodes') + , BufferPool = require('./BufferPool') + , bufferUtil = require('./BufferUtil').BufferUtil; + +/** + * Node version 0.4 and 0.6 compatibility + */ + +var isNodeV4 = /^v0\.4/.test(process.version); + +/** + * HyBi Receiver implementation + */ + +function Receiver () { + // memory pool for fragmented messages + var fragmentedPoolPrevUsed = -1; + this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? + (fragmentedPoolPrevUsed + db.used) / 2 : + db.used; + }); + + // memory pool for unfragmented messages + var unfragmentedPoolPrevUsed = -1; + this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? + (unfragmentedPoolPrevUsed + db.used) / 2 : + db.used; + }); + + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.overflow = []; + this.headerBuffer = new Buffer(10); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = []; + this.expectHeader(2, this.processPacket); + this.dead = false; + + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; +}; + +module.exports = Receiver; + +/** + * Add new data to the parser. + * + * @api public + */ + +Receiver.prototype.add = function(data) { + var dataLength = data.length; + if (dataLength == 0) return; + if (this.expectBuffer == null) { + this.overflow.push(data); + return; + } + var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); + fastCopy(toRead, data, this.expectBuffer, this.expectOffset); + this.expectOffset += toRead; + if (toRead < dataLength) { + this.overflow.push(data.slice(toRead)); + } + while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } +} + +/** + * Releases all resources used by the receiver. + * + * @api public + */ + +Receiver.prototype.cleanup = function() { + this.dead = true; + this.overflow = null; + this.headerBuffer = null; + this.expectBuffer = null; + this.expectHandler = null; + this.unfragmentedBufferPool = null; + this.fragmentedBufferPool = null; + this.state = null; + this.currentMessage = null; + this.onerror = null; + this.ontext = null; + this.onbinary = null; + this.onclose = null; + this.onping = null; + this.onpong = null; +} + +/** + * Waits for a certain amount of header bytes to be available, then fires a callback. + * + * @api private + */ + +Receiver.prototype.expectHeader = function(length, handler) { + if (length == 0) { + handler(null); + return; + } + this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; + } +} + +/** + * Waits for a certain amount of data bytes to be available, then fires a callback. + * + * @api private + */ + +Receiver.prototype.expectData = function(length, handler) { + if (length == 0) { + handler(null); + return; + } + this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; + } +} + +/** + * Allocates memory from the buffer pool. + * + * @api private + */ + +Receiver.prototype.allocateFromPool = !isNodeV4 + ? function(length, isFragmented) { return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); } + : function(length) { return new Buffer(length); }; + +/** + * Start processing a new packet. + * + * @api private + */ + +Receiver.prototype.processPacket = function (data) { + if ((data[0] & 0x70) != 0) { + this.error('reserved fields must be empty', 1002); + return; + } + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var opcode = data[0] & 0xf; + if (opcode === 0) { + // continuation frame + this.state.fragmentedOperation = true; + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode', 1002); + return; + } + } + else { + if (opcode < 3 && this.state.activeFragmentedOperation != null) { + this.error('data frames after the initial data frame must have opcode 0', 1002); + return; + } + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.fragmentedOperation = true; + this.state.activeFragmentedOperation = opcode; + } + else this.state.fragmentedOperation = false; + } + var handler = opcodes[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); + else { + handler.start.call(this, data); + } +} + +/** + * Endprocessing a packet. + * + * @api private + */ + +Receiver.prototype.endPacket = function() { + if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); + else if (this.state.lastFragment) this.fragmentedBufferPool.reset(false); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) { + // end current fragmented operation + this.state.activeFragmentedOperation = null; + } + this.state.lastFragment = false; + this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; + this.state.masked = false; + this.expectHeader(2, this.processPacket); +} + +/** + * Reset the parser state. + * + * @api private + */ + +Receiver.prototype.reset = function() { + if (this.dead) return; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.fragmentedBufferPool.reset(true); + this.unfragmentedBufferPool.reset(true); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = []; + this.currentMessage = []; +} + +/** + * Unmask received data. + * + * @api private + */ + +Receiver.prototype.unmask = function (mask, buf, binary) { + if (mask != null && buf != null) bufferUtil.unmask(buf, mask); + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; +} + +/** + * Concatenates a list of buffers. + * + * @api private + */ + +Receiver.prototype.concatBuffers = function(buffers) { + var length = 0; + for (var i = 0, l = buffers.length; i < l; ++i) length += buffers[i].length; + var mergedBuffer = new Buffer(length); + bufferUtil.merge(mergedBuffer, buffers); + return mergedBuffer; +} + +/** + * Handles an error + * + * @api private + */ + +Receiver.prototype.error = function (reason, protocolErrorCode) { + this.reset(); + this.onerror(reason, protocolErrorCode); + return this; +} + +/** + * Buffer utilities + */ + +function readUInt16BE(start) { + return (this[start]<<8) + + this[start+1]; +} + +function readUInt32BE(start) { + return (this[start]<<24) + + (this[start+1]<<16) + + (this[start+2]<<8) + + this[start+3]; +} + +function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { + switch (length) { + default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; + case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; + case 1: dstBuffer[dstOffset] = srcBuffer[0]; + } +} + +/** + * Opcode handlers + */ + +var opcodes = { + // text + '1': { + start: function(data) { + var self = this; + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['1'].getData.call(self, firstLength); + } + else if (firstLength == 126) { + self.expectHeader(2, function(data) { + opcodes['1'].getData.call(self, readUInt16BE.call(data, 0)); + }); + } + else if (firstLength == 127) { + self.expectHeader(8, function(data) { + if (readUInt32BE.call(data, 0) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + return; + } + opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); + }); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['1'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['1'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var packet = this.unmask(mask, data, true); + if (packet != null) this.currentMessage.push(packet); + if (this.state.lastFragment) { + var messageBuffer = this.concatBuffers(this.currentMessage); + if (!Validation.isValidUTF8(messageBuffer)) { + this.error('invalid utf8 sequence', 1007); + return; + } + this.ontext(messageBuffer.toString('utf8'), {masked: this.state.masked, buffer: messageBuffer}); + this.currentMessage = []; + } + this.endPacket(); + } + }, + // binary + '2': { + start: function(data) { + var self = this; + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['2'].getData.call(self, firstLength); + } + else if (firstLength == 126) { + self.expectHeader(2, function(data) { + opcodes['2'].getData.call(self, readUInt16BE.call(data, 0)); + }); + } + else if (firstLength == 127) { + self.expectHeader(8, function(data) { + if (readUInt32BE.call(data, 0) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + return; + } + opcodes['2'].getData.call(self, readUInt32BE.call(data, 4, true)); + }); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['2'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['2'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var packet = this.unmask(mask, data, true); + if (packet != null) this.currentMessage.push(packet); + if (this.state.lastFragment) { + var messageBuffer = this.concatBuffers(this.currentMessage); + this.onbinary(messageBuffer, {masked: this.state.masked, buffer: messageBuffer}); + this.currentMessage = []; + } + this.endPacket(); + } + }, + // close + '8': { + start: function(data) { + var self = this; + if (self.state.lastFragment == false) { + self.error('fragmented close is not supported', 1002); + return; + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['8'].getData.call(self, firstLength); + } + else { + self.error('control frames cannot have more than 125 bytes of data', 1002); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['8'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['8'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var self = this; + data = self.unmask(mask, data, true); + if (data && data.length == 1) { + self.error('close packets with data must be at least two bytes long', 1002); + return; + } + var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000; + if (!ErrorCodes.isValidErrorCode(code)) { + self.error('invalid error code', 1002); + return; + } + var message = ''; + if (data && data.length > 2) { + var messageBuffer = data.slice(2); + if (!Validation.isValidUTF8(messageBuffer)) { + self.error('invalid utf8 sequence', 1007); + return; + } + message = messageBuffer.toString('utf8'); + } + this.onclose(code, message, {masked: self.state.masked}); + this.reset(); + }, + }, + // ping + '9': { + start: function(data) { + var self = this; + if (self.state.lastFragment == false) { + self.error('fragmented ping is not supported', 1002); + return; + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['9'].getData.call(self, firstLength); + } + else { + self.error('control frames cannot have more than 125 bytes of data', 1002); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['9'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['9'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + this.onping(this.unmask(mask, data, true), {masked: this.state.masked, binary: true}); + this.endPacket(); + } + }, + // pong + '10': { + start: function(data) { + var self = this; + if (self.state.lastFragment == false) { + self.error('fragmented pong is not supported', 1002); + return; + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['10'].getData.call(self, firstLength); + } + else { + self.error('control frames cannot have more than 125 bytes of data', 1002); + } + }, + getData: function(length) { + var self = this; + if (this.state.masked) { + this.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['10'].finish.call(self, mask, data); + }); + }); + } + else { + this.expectData(length, function(data) { + opcodes['10'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + this.onpong(this.unmask(mask, data, true), {masked: this.state.masked, binary: true}); + this.endPacket(); + } + } +} diff --git a/node_modules/ws/lib/Sender.hixie.js b/node_modules/ws/lib/Sender.hixie.js new file mode 100644 index 0000000..1754afb --- /dev/null +++ b/node_modules/ws/lib/Sender.hixie.js @@ -0,0 +1,123 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var events = require('events') + , util = require('util') + , EventEmitter = events.EventEmitter; + +/** + * Hixie Sender implementation + */ + +function Sender(socket) { + this.socket = socket; + this.continuationFrame = false; + this.isClosed = false; +} + +module.exports = Sender; + +/** + * Inherits from EventEmitter. + */ + +util.inherits(Sender, events.EventEmitter); + +/** + * Frames and writes data. + * + * @api public + */ + +Sender.prototype.send = function(data, options, cb) { + if (this.isClosed) return; +/* + if (options && options.binary) { + this.error('hixie websockets do not support binary'); + return; + } +*/ + var isString = typeof data == 'string' + , length = isString ? Buffer.byteLength(data) : data.length + , lengthbytes = (length > 127) ? 2 : 1 // assume less than 2**14 bytes + , writeStartMarker = this.continuationFrame == false + , writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin) + , buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)) + , offset = writeStartMarker ? 1 : 0; + + if (writeStartMarker) { + if (options && options.binary) { + buffer.write('\x80', 'binary'); + // assume length less than 2**14 bytes + if (lengthbytes > 1) + buffer.write(String.fromCharCode(128+length/128), offset++, 'binary'); + buffer.write(String.fromCharCode(length&0x7f), offset++, 'binary'); + } else + buffer.write('\x00', 'binary'); + } + + if (isString) buffer.write(data, offset, 'utf8'); + else data.copy(buffer, offset, 0); + + if (writeEndMarker) { + if (options && options.binary) { + // sending binary, not writing end marker + } else + buffer.write('\xff', offset + length, 'binary'); + this.continuationFrame = false; + } + else this.continuationFrame = true; + + try { + this.socket.write(buffer, 'binary', cb); + } catch (e) { + this.error(e.toString()); + } +} + +/** + * Sends a close instruction to the remote party. + * + * @api public + */ + +Sender.prototype.close = function(code, data, mask, cb) { + if (this.isClosed) return; + this.isClosed = true; + try { + if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); + this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); + } catch (e) { + this.error(e.toString()); + } +} + +/** + * Sends a ping message to the remote party. Not available for hixie. + * + * @api public + */ + +Sender.prototype.ping = function(data, options) {} + +/** + * Sends a pong message to the remote party. Not available for hixie. + * + * @api public + */ + +Sender.prototype.pong = function(data, options) {} + +/** + * Handles an error + * + * @api private + */ + +Sender.prototype.error = function (reason) { + this.emit('error', reason); + return this; +} diff --git a/node_modules/ws/lib/Sender.js b/node_modules/ws/lib/Sender.js new file mode 100644 index 0000000..fc3b437 --- /dev/null +++ b/node_modules/ws/lib/Sender.js @@ -0,0 +1,227 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var events = require('events') + , util = require('util') + , EventEmitter = events.EventEmitter + , ErrorCodes = require('./ErrorCodes') + , bufferUtil = require('./BufferUtil').BufferUtil; + +/** + * HyBi Sender implementation + */ + +function Sender(socket) { + this._socket = socket; + this.firstFragment = true; +} + +/** + * Inherits from EventEmitter. + */ + +util.inherits(Sender, events.EventEmitter); + +/** + * Sends a close instruction to the remote party. + * + * @api public + */ + +Sender.prototype.close = function(code, data, mask) { + if (typeof code !== 'undefined') { + if (typeof code !== 'number' || + !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + } + code = code || 1000; + var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); + writeUInt16BE.call(dataBuffer, code, 0); + if (dataBuffer.length > 2) dataBuffer.write(data, 2); + this.frameAndSend(0x8, dataBuffer, true, mask); +} + +/** + * Sends a ping message to the remote party. + * + * @api public + */ + +Sender.prototype.ping = function(data, options) { + var mask = options && options.mask; + this.frameAndSend(0x9, data || '', true, mask); +} + +/** + * Sends a pong message to the remote party. + * + * @api public + */ + +Sender.prototype.pong = function(data, options) { + var mask = options && options.mask; + this.frameAndSend(0xa, data || '', true, mask); +} + +/** + * Sends text or binary data to the remote party. + * + * @api public + */ + +Sender.prototype.send = function(data, options, cb) { + var finalFragment = options && options.fin === false ? false : true; + var mask = options && options.mask; + var opcode = options && options.binary ? 2 : 1; + if (this.firstFragment === false) opcode = 0; + else this.firstFragment = false; + if (finalFragment) this.firstFragment = true + this.frameAndSend(opcode, data, finalFragment, mask, cb); +} + +/** + * Frames and sends a piece of data according to the HyBi WebSocket protocol. + * + * @api private + */ + +Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, cb) { + var canModifyData = false; + + if (!data) { + try { + this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + return; + } + + if (!Buffer.isBuffer(data)) { + canModifyData = true; + if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { + data = getArrayBuffer(data); + } else { + data = new Buffer(data); + } + } + + var dataLength = data.length + , dataOffset = maskData ? 6 : 2 + , secondByte = dataLength; + + if (dataLength >= 65536) { + dataOffset += 8; + secondByte = 127; + } + else if (dataLength > 125) { + dataOffset += 2; + secondByte = 126; + } + + var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); + var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; + var outputBuffer = new Buffer(totalLength); + outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; + + switch (secondByte) { + case 126: + writeUInt16BE.call(outputBuffer, dataLength, 2); + break; + case 127: + writeUInt32BE.call(outputBuffer, 0, 2); + writeUInt32BE.call(outputBuffer, dataLength, 6); + } + + if (maskData) { + outputBuffer[1] = secondByte | 0x80; + var mask = this._randomMask || (this._randomMask = getRandomMask()); + outputBuffer[dataOffset - 4] = mask[0]; + outputBuffer[dataOffset - 3] = mask[1]; + outputBuffer[dataOffset - 2] = mask[2]; + outputBuffer[dataOffset - 1] = mask[3]; + if (mergeBuffers) { + bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + else { + bufferUtil.mask(data, mask, data, 0, dataLength); + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + } + else { + outputBuffer[1] = secondByte; + if (mergeBuffers) { + data.copy(outputBuffer, dataOffset); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + else { + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + } +} + +module.exports = Sender; + +function writeUInt16BE(value, offset) { + this[offset] = (value & 0xff00)>>8; + this[offset+1] = value & 0xff; +} + +function writeUInt32BE(value, offset) { + this[offset] = (value & 0xff000000)>>24; + this[offset+1] = (value & 0xff0000)>>16; + this[offset+2] = (value & 0xff00)>>8; + this[offset+3] = value & 0xff; +} + +function getArrayBuffer(data) { + // data is either an ArrayBuffer or ArrayBufferView. + var array = new Uint8Array(data.buffer || data) + , l = data.byteLength || data.length + , o = data.byteOffset || 0 + , buffer = new Buffer(l); + for (var i = 0; i < l; ++i) { + buffer[i] = array[o+i]; + } + return buffer; +} + +function getRandomMask() { + return new Buffer([ + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255) + ]); +} diff --git a/node_modules/ws/lib/Validation.fallback.js b/node_modules/ws/lib/Validation.fallback.js new file mode 100644 index 0000000..2c7c4fd --- /dev/null +++ b/node_modules/ws/lib/Validation.fallback.js @@ -0,0 +1,12 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports.Validation = { + isValidUTF8: function(buffer) { + return true; + } +}; + diff --git a/node_modules/ws/lib/Validation.js b/node_modules/ws/lib/Validation.js new file mode 100644 index 0000000..0f3109a --- /dev/null +++ b/node_modules/ws/lib/Validation.js @@ -0,0 +1,16 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +try { + module.exports = require('../build/Release/validation'); +} catch (e) { try { + module.exports = require('../build/default/validation'); +} catch (e) { try { + module.exports = require('./Validation.fallback'); +} catch (e) { + console.error('validation.node seems to not have been built. Run npm install.'); + throw e; +}}} diff --git a/node_modules/ws/lib/WebSocket.js b/node_modules/ws/lib/WebSocket.js new file mode 100644 index 0000000..cce3cb4 --- /dev/null +++ b/node_modules/ws/lib/WebSocket.js @@ -0,0 +1,818 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util') + , events = require('events') + , http = require('http') + , https = require('https') + , crypto = require('crypto') + , url = require('url') + , fs = require('fs') + , Options = require('options') + , Sender = require('./Sender') + , Receiver = require('./Receiver') + , SenderHixie = require('./Sender.hixie') + , ReceiverHixie = require('./Receiver.hixie'); + +/** + * Constants + */ + +// Default protocol version + +var protocolVersion = 13; + +// Close timeout + +var closeTimeout = 30000; // Allow 5 seconds to terminate the connection cleanly + +/** + * Node version 0.4 and 0.6 compatibility + */ + +var isNodeV4 = /^v0\.4/.test(process.version); + +/** + * WebSocket implementation + */ + +function WebSocket(address, protocols, options) { + + if (protocols && !Array.isArray(protocols) && 'object' == typeof protocols) { + // accept the "options" Object as the 2nd argument + options = protocols; + protocols = null; + } + if ('string' == typeof protocols) { + protocols = [ protocols ]; + } + if (!Array.isArray(protocols)) { + protocols = []; + } + // TODO: actually handle the `Sub-Protocols` part of the WebSocket client + + this._socket = null; + this.bytesReceived = 0; + this.readyState = null; + this.supports = {}; + + if (Array.isArray(address)) { + initAsServerClient.apply(this, address.concat(options)); + } else { + initAsClient.apply(this, [address, protocols, options]); + } +} + +/** + * Inherits from EventEmitter. + */ + +util.inherits(WebSocket, events.EventEmitter); + +/** + * Ready States + */ + +["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function (state, index) { + WebSocket.prototype[state] = WebSocket[state] = index; +}); + +/** + * Gracefully closes the connection, after sending a description message to the server + * + * @param {Object} data to be sent to the server + * @api public + */ + +WebSocket.prototype.close = function(code, data) { + if (this.readyState == WebSocket.CLOSING || this.readyState == WebSocket.CLOSED) return; + if (this.readyState == WebSocket.CONNECTING) { + this.readyState = WebSocket.CLOSED; + return; + } + try { + this.readyState = WebSocket.CLOSING; + this._closeCode = code; + this._closeMessage = data; + var mask = !this._isServer; + this._sender.close(code, data, mask); + } + catch (e) { + this.emit('error', e); + } + finally { + this.terminate(); + } +} + +/** + * Pause the client stream + * + * @api public + */ + +WebSocket.prototype.pause = function() { + if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); + return this._socket.pause(); +} + +/** + * Sends a ping + * + * @param {Object} data to be sent to the server + * @param {Object} Members - mask: boolean, binary: boolean + * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open + * @api public + */ + +WebSocket.prototype.ping = function(data, options, dontFailWhenClosed) { + if (this.readyState != WebSocket.OPEN) { + if (dontFailWhenClosed === true) return; + throw new Error('not opened'); + } + options = options || {}; + if (typeof options.mask == 'undefined') options.mask = !this._isServer; + this._sender.ping(data, options); +} + +/** + * Sends a pong + * + * @param {Object} data to be sent to the server + * @param {Object} Members - mask: boolean, binary: boolean + * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open + * @api public + */ + +WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { + if (this.readyState != WebSocket.OPEN) { + if (dontFailWhenClosed === true) return; + throw new Error('not opened'); + } + options = options || {}; + if (typeof options.mask == 'undefined') options.mask = !this._isServer; + this._sender.pong(data, options); +} + +/** + * Resume the client stream + * + * @api public + */ + +WebSocket.prototype.resume = function() { + if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); + return this._socket.resume(); +} + +/** + * Sends a piece of data + * + * @param {Object} data to be sent to the server + * @param {Object} Members - mask: boolean, binary: boolean + * @param {function} Optional callback which is executed after the send completes + * @api public + */ + +WebSocket.prototype.send = function(data, options, cb) { + if (typeof options == 'function') { + cb = options; + options = {}; + } + if (this.readyState != WebSocket.OPEN) { + if (typeof cb == 'function') cb(new Error('not opened')); + else throw new Error('not opened'); + return; + } + if (!data) data = ''; + if (this._queue) { + var self = this; + this._queue.push(function() { self.send(data, options, cb); }); + return; + } + options = options || {}; + options.fin = true; + if (typeof options.binary == 'undefined') { + options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || + data instanceof Uint8Array || + data instanceof Uint16Array || + data instanceof Uint32Array || + data instanceof Int8Array || + data instanceof Int16Array || + data instanceof Int32Array || + data instanceof Float32Array || + data instanceof Float64Array); + } + if (typeof options.mask == 'undefined') options.mask = !this._isServer; + if (data instanceof fs.ReadStream) { + startQueue(this); + var self = this; + sendStream(this, data, options, function(error) { + process.nextTick(function() { executeQueueSends(self); }); + if (typeof cb == 'function') cb(error); + }); + } + else this._sender.send(data, options, cb); +} + +/** + * Streams data through calls to a user supplied function + * + * @param {Object} Members - mask: boolean, binary: boolean + * @param {function} 'function (error, send)' which is executed on successive ticks of which send is 'function (data, final)'. + * @api public + */ + +WebSocket.prototype.stream = function(options, cb) { + if (typeof options == 'function') { + cb = options; + options = {}; + } + var self = this; + if (typeof cb != 'function') throw new Error('callback must be provided'); + if (this.readyState != WebSocket.OPEN) { + if (typeof cb == 'function') cb(new Error('not opened')); + else throw new Error('not opened'); + return; + } + if (this._queue) { + this._queue.push(function() { self.stream(options, cb); }); + return; + } + options = options || {}; + if (typeof options.mask == 'undefined') options.mask = !this._isServer; + startQueue(this); + var send = function(data, final) { + try { + if (self.readyState != WebSocket.OPEN) throw new Error('not opened'); + options.fin = final === true; + self._sender.send(data, options); + if (!final) process.nextTick(cb.bind(null, null, send)); + else executeQueueSends(self); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else { + delete self._queue; + self.emit('error', e); + } + } + } + process.nextTick(cb.bind(null, null, send)); +} + +/** + * Immediately shuts down the connection + * + * @api public + */ + +WebSocket.prototype.terminate = function() { + if (this.readyState == WebSocket.CLOSED) return; + if (this._socket) { + try { + // End the connection + this._socket.end(); + } + catch (e) { + // Socket error during end() call, so just destroy it right now + cleanupWebsocketResources.call(this, true); + return; + } + + // Add a timeout to ensure that the connection is completely + // cleaned up within 30 seconds, even if the clean close procedure + // fails for whatever reason + this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); + } + else if (this.readyState == WebSocket.CONNECTING) { + cleanupWebsocketResources.call(this, true); + } +}; + +/** + * Expose bufferedAmount + * + * @api public + */ + +Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { + get: function get() { + var amount = 0; + if (this._socket) { + amount = this._socket.bufferSize || 0; + } + return amount; + } +}); + +/** + * Emulates the W3C Browser based WebSocket interface using function members. + * + * @see http://dev.w3.org/html5/websockets/#the-websocket-interface + * @api public + */ + +['open', 'error', 'close', 'message'].forEach(function(method) { + Object.defineProperty(WebSocket.prototype, 'on' + method, { + /** + * Returns the current listener + * + * @returns {Mixed} the set function or undefined + * @api public + */ + + get: function get() { + var listener = this.listeners(method)[0]; + return listener ? (listener._listener ? listener._listener : listener) : undefined; + }, + + /** + * Start listening for events + * + * @param {Function} listener the listener + * @returns {Mixed} the set function or undefined + * @api public + */ + + set: function set(listener) { + this.removeAllListeners(method); + this.addEventListener(method, listener); + } + }); +}); + +/** + * Emulates the W3C Browser based WebSocket interface using addEventListener. + * + * @see https://developer.mozilla.org/en/DOM/element.addEventListener + * @see http://dev.w3.org/html5/websockets/#the-websocket-interface + * @api public + */ +WebSocket.prototype.addEventListener = function(method, listener) { + var target = this; + if (typeof listener === 'function') { + if (method === 'message') { + function onMessage (data, flags) { + listener.call(this, new MessageEvent(data, flags.binary ? 'Binary' : 'Text', target)); + } + // store a reference so we can return the original function from the addEventListener hook + onMessage._listener = listener; + this.on(method, onMessage); + } else if (method === 'close') { + function onClose (code, message) { + listener.call(this, new CloseEvent(code, message, target)); + } + // store a reference so we can return the original function from the addEventListener hook + onClose._listener = listener; + this.on(method, onClose); + } else if (method === 'error') { + function onError (event) { + event.target = target; + listener.call(this, event); + } + // store a reference so we can return the original function from the addEventListener hook + onError._listener = listener; + this.on(method, onError); + } else if (method === 'open') { + function onOpen () { + listener.call(this, new OpenEvent(target)); + } + // store a reference so we can return the original function from the addEventListener hook + onOpen._listener = listener; + this.on(method, onOpen); + } else { + this.on(method, listener); + } + } +} + +module.exports = WebSocket; + +/** + * W3C MessageEvent + * + * @see http://www.w3.org/TR/html5/comms.html + * @api private + */ + +function MessageEvent(dataArg, typeArg, target) { + this.data = dataArg; + this.type = typeArg; + this.target = target; +} + +/** + * W3C CloseEvent + * + * @see http://www.w3.org/TR/html5/comms.html + * @api private + */ + +function CloseEvent(code, reason, target) { + this.wasClean = (typeof code == 'undefined' || code == 1000); + this.code = code; + this.reason = reason; + this.target = target; +} + +/** + * W3C OpenEvent + * + * @see http://www.w3.org/TR/html5/comms.html + * @api private + */ + +function OpenEvent(target) { + this.target = target; +} + +/** + * Entirely private apis, + * which may or may not be bound to a sepcific WebSocket instance. + */ + +function initAsServerClient(req, socket, upgradeHead, options) { + options = new Options({ + protocolVersion: protocolVersion, + protocol: null + }).merge(options); + + // expose state properties + this.protocol = options.value.protocol; + this.protocolVersion = options.value.protocolVersion; + this.supports.binary = (this.protocolVersion != 'hixie-76'); + this.upgradeReq = req; + this.readyState = WebSocket.CONNECTING; + this._isServer = true; + + // establish connection + if (options.value.protocolVersion == 'hixie-76') establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); + else establishConnection.call(this, Receiver, Sender, socket, upgradeHead); +} + +function initAsClient(address, protocols, options) { + options = new Options({ + origin: null, + protocolVersion: protocolVersion, + host: null, + headers: null, + protocol: null, + agent: null, + + // ssl-related options + pfx: null, + key: null, + passphrase: null, + cert: null, + ca: null, + ciphers: null, + rejectUnauthorized: null + }).merge(options); + if (options.value.protocolVersion != 8 && options.value.protocolVersion != 13) { + throw new Error('unsupported protocol version'); + } + + // verify url and establish http class + var serverUrl = url.parse(address); + var isUnixSocket = serverUrl.protocol === 'ws+unix:'; + if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url'); + var isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; + var httpObj = isSecure ? https : http; + var port = serverUrl.port || (isSecure ? 443 : 80); + var auth = serverUrl.auth; + + // expose state properties + this._isServer = false; + this.url = address; + this.protocolVersion = options.value.protocolVersion; + this.supports.binary = (this.protocolVersion != 'hixie-76'); + + // begin handshake + var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); + var shasum = crypto.createHash('sha1'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); + var expectedServerKey = shasum.digest('base64'); + + var agent = options.value.agent; + // node<=v0.4.x compatibility + if (!agent && isNodeV4) { + isNodeV4 = true; + agent = new httpObj.Agent({ + host: serverUrl.hostname, + port: port + }); + } + + var headerHost = serverUrl.hostname; + // Append port number to Host and Origin header, only if specified in the url and non-default + if(serverUrl.port) { + if((isSecure && (port != 443)) || (!isSecure && (port != 80))){ + headerHost = headerHost + ':' + port; + } + } + + var requestOptions = { + port: port, + host: serverUrl.hostname, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Host': headerHost, + 'Origin': headerHost, + 'Sec-WebSocket-Version': options.value.protocolVersion, + 'Sec-WebSocket-Key': key + } + }; + + // If we have basic auth. + if (auth) { + requestOptions.headers['Authorization'] = 'Basic ' + new Buffer(auth).toString('base64'); + } + + if (options.value.protocol) { + requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol; + } + + if (options.value.host) { + requestOptions.headers['Host'] = options.value.host; + } + + if (options.value.headers) { + for (var header in options.value.headers) { + if (options.value.headers.hasOwnProperty(header)) { + requestOptions.headers[header] = options.value.headers[header]; + } + } + } + + if (options.isDefinedAndNonNull('pfx') + || options.isDefinedAndNonNull('key') + || options.isDefinedAndNonNull('passphrase') + || options.isDefinedAndNonNull('cert') + || options.isDefinedAndNonNull('ca') + || options.isDefinedAndNonNull('ciphers') + || options.isDefinedAndNonNull('rejectUnauthorized')) { + + if (isNodeV4) { + throw new Error('Client side certificates are not supported on Node 0.4.x'); + } + + if (options.isDefinedAndNonNull('pfx')) requestOptions.pfx = options.value.pfx; + if (options.isDefinedAndNonNull('key')) requestOptions.key = options.value.key; + if (options.isDefinedAndNonNull('passphrase')) requestOptions.passphrase = options.value.passphrase; + if (options.isDefinedAndNonNull('cert')) requestOptions.cert = options.value.cert; + if (options.isDefinedAndNonNull('ca')) requestOptions.ca = options.value.ca; + if (options.isDefinedAndNonNull('ciphers')) requestOptions.ciphers = options.value.ciphers; + if (options.isDefinedAndNonNull('rejectUnauthorized')) requestOptions.rejectUnauthorized = options.value.rejectUnauthorized; + + if (!agent) { + // global agent ignores client side certificates + agent = new httpObj.Agent(requestOptions); + } + } + + if (isNodeV4) { + requestOptions.path = (serverUrl.pathname || '/') + (serverUrl.search || ''); + } + else requestOptions.path = serverUrl.path || '/'; + + if (agent) { + requestOptions.agent = agent; + } + + if (isUnixSocket) { + requestOptions.socketPath = serverUrl.pathname; + } + if (options.value.origin) { + if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; + else requestOptions.headers['Origin'] = options.value.origin; + } + + var self = this; + var req = httpObj.request(requestOptions); + + (isNodeV4 ? agent : req).on('error', function(error) { + self.emit('error', error); + cleanupWebsocketResources.call(this, error); + }); + (isNodeV4 ? agent : req).once('response', function(res) { + var error = new Error('unexpected server response (' + res.statusCode + ')'); + self.emit('error', error); + cleanupWebsocketResources.call(this, error); + }); + (isNodeV4 ? agent : req).once('upgrade', function(res, socket, upgradeHead) { + if (self.readyState == WebSocket.CLOSED) { + // client closed before server accepted connection + self.emit('close'); + removeAllListeners(self); + socket.end(); + return; + } + var serverKey = res.headers['sec-websocket-accept']; + if (typeof serverKey == 'undefined' || serverKey !== expectedServerKey) { + self.emit('error', 'invalid server key'); + removeAllListeners(self); + socket.end(); + return; + } + + var serverProt = res.headers['sec-websocket-protocol']; + var protList = (options.value.protocol || "").split(/, */); + var protError = null; + if (!options.value.protocol && serverProt) { + protError = 'server sent a subprotocol even though none requested'; + } else if (options.value.protocol && !serverProt) { + protError = 'server sent no subprotocol even though requested'; + } else if (serverProt && protList.indexOf(serverProt) === -1) { + protError = 'server responded with an invalid protocol'; + } + if (protError) { + self.emit('error', protError); + removeAllListeners(self); + socket.end(); + return; + } else if (serverProt) { + self.protocol = serverProt; + } + + establishConnection.call(self, Receiver, Sender, socket, upgradeHead); + + // perform cleanup on http resources + removeAllListeners(isNodeV4 ? agent : req); + req = null; + agent = null; + }); + + req.end(); + this.readyState = WebSocket.CONNECTING; +} + +function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { + this._socket = socket; + socket.setTimeout(0); + socket.setNoDelay(true); + var self = this; + this._receiver = new ReceiverClass(); + + // socket cleanup handlers + socket.on('end', cleanupWebsocketResources.bind(this)); + socket.on('close', cleanupWebsocketResources.bind(this)); + socket.on('error', cleanupWebsocketResources.bind(this)); + + // ensure that the upgradeHead is added to the receiver + function firstHandler(data) { + if (self.readyState != WebSocket.OPEN) return; + if (upgradeHead && upgradeHead.length > 0) { + self.bytesReceived += upgradeHead.length; + var head = upgradeHead; + upgradeHead = null; + self._receiver.add(head); + } + dataHandler = realHandler; + if (data) { + self.bytesReceived += data.length; + self._receiver.add(data); + } + } + // subsequent packets are pushed straight to the receiver + function realHandler(data) { + if (data) self.bytesReceived += data.length; + self._receiver.add(data); + } + var dataHandler = firstHandler; + // if data was passed along with the http upgrade, + // this will schedule a push of that on to the receiver. + // this has to be done on next tick, since the caller + // hasn't had a chance to set event handlers on this client + // object yet. + process.nextTick(firstHandler); + + // receiver event handlers + self._receiver.ontext = function (data, flags) { + flags = flags || {}; + self.emit('message', data, flags); + }; + self._receiver.onbinary = function (data, flags) { + flags = flags || {}; + flags.binary = true; + self.emit('message', data, flags); + }; + self._receiver.onping = function(data, flags) { + flags = flags || {}; + self.pong(data, {mask: !self._isServer, binary: flags.binary === true}, true); + self.emit('ping', data, flags); + }; + self._receiver.onpong = function(data, flags) { + self.emit('pong', data, flags); + }; + self._receiver.onclose = function(code, data, flags) { + flags = flags || {}; + self.close(code, data); + }; + self._receiver.onerror = function(reason, errorCode) { + // close the connection when the receiver reports a HyBi error code + self.close(typeof errorCode != 'undefined' ? errorCode : 1002, ''); + self.emit('error', reason, errorCode); + }; + + // finalize the client + this._sender = new SenderClass(socket); + this._sender.on('error', function(error) { + self.close(1002, ''); + self.emit('error', error); + }); + this.readyState = WebSocket.OPEN; + this.emit('open'); + + socket.on('data', dataHandler); +} + +function startQueue(instance) { + instance._queue = instance._queue || []; +} + +function executeQueueSends(instance) { + var queue = instance._queue; + if (typeof queue == 'undefined') return; + delete instance._queue; + for (var i = 0, l = queue.length; i < l; ++i) { + queue[i](); + } +} + +function sendStream(instance, stream, options, cb) { + stream.on('data', function(data) { + if (instance.readyState != WebSocket.OPEN) { + if (typeof cb == 'function') cb(new Error('not opened')); + else { + delete instance._queue; + instance.emit('error', new Error('not opened')); + } + return; + } + options.fin = false; + instance._sender.send(data, options); + }); + stream.on('end', function() { + if (instance.readyState != WebSocket.OPEN) { + if (typeof cb == 'function') cb(new Error('not opened')); + else { + delete instance._queue; + instance.emit('error', new Error('not opened')); + } + return; + } + options.fin = true; + instance._sender.send(null, options); + if (typeof cb == 'function') cb(null); + }); +} + +function cleanupWebsocketResources(error) { + if (this.readyState == WebSocket.CLOSED) return; + var emitClose = this.readyState != WebSocket.CONNECTING; + this.readyState = WebSocket.CLOSED; + + clearTimeout(this._closeTimer); + this._closeTimer = null; + if (emitClose) this.emit('close', this._closeCode || 1000, this._closeMessage || ''); + + if (this._socket) { + removeAllListeners(this._socket); + // catch all socket error after removing all standard handlers + var socket = this._socket; + this._socket.on('error', function() { + try { socket.destroy(); } catch (e) {} + }); + try { + if (!error) this._socket.end(); + else this._socket.destroy(); + } + catch (e) { /* Ignore termination errors */ } + this._socket = null; + } + if (this._sender) { + removeAllListeners(this._sender); + this._sender = null; + } + if (this._receiver) { + this._receiver.cleanup(); + this._receiver = null; + } + removeAllListeners(this); + this.on('error', function() {}); // catch all errors after this + delete this._queue; +} + +function removeAllListeners(instance) { + if (isNodeV4) { + // node v4 doesn't *actually* remove all listeners globally, + // so we do that instead + instance._events = {}; + } + else instance.removeAllListeners(); +} diff --git a/node_modules/ws/lib/WebSocketServer.js b/node_modules/ws/lib/WebSocketServer.js new file mode 100644 index 0000000..da759f8 --- /dev/null +++ b/node_modules/ws/lib/WebSocketServer.js @@ -0,0 +1,460 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util') + , events = require('events') + , http = require('http') + , crypto = require('crypto') + , url = require('url') + , Options = require('options') + , WebSocket = require('./WebSocket') + , tls = require('tls') + , url = require('url'); + +/** + * WebSocket Server implementation + */ + +function WebSocketServer(options, callback) { + options = new Options({ + host: '0.0.0.0', + port: null, + server: null, + verifyClient: null, + handleProtocols: null, + path: null, + noServer: false, + disableHixie: false, + clientTracking: true + }).merge(options); + + if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { + throw new TypeError('`port` or a `server` must be provided'); + } + + var self = this; + + if (options.isDefinedAndNonNull('port')) { + this._server = http.createServer(function (req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Not implemented'); + }); + this._server.listen(options.value.port, options.value.host, callback); + this._closeServer = function() { self._server.close(); }; + } + else if (options.value.server) { + this._server = options.value.server; + if (options.value.path) { + // take note of the path, to avoid collisions when multiple websocket servers are + // listening on the same http server + if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) { + throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); + } + if (typeof this._server._webSocketPaths !== 'object') { + this._server._webSocketPaths = {}; + } + this._server._webSocketPaths[options.value.path] = 1; + } + } + if (this._server) this._server.once('listening', function() { self.emit('listening'); }); + + if (typeof this._server != 'undefined') { + this._server.on('error', function(error) { + self.emit('error', error) + }); + this._server.on('upgrade', function(req, socket, upgradeHead) { + //copy upgradeHead to avoid retention of large slab buffers used in node core + var head = new Buffer(upgradeHead.length); + upgradeHead.copy(head); + + self.handleUpgrade(req, socket, head, function(client) { + self.emit('connection'+req.url, client); + self.emit('connection', client); + }); + }); + } + + this.options = options.value; + this.path = options.value.path; + this.clients = []; +} + +/** + * Inherits from EventEmitter. + */ + +util.inherits(WebSocketServer, events.EventEmitter); + +/** + * Immediately shuts down the connection. + * + * @api public + */ + +WebSocketServer.prototype.close = function() { + // terminate all associated clients + var error = null; + try { + for (var i = 0, l = this.clients.length; i < l; ++i) { + this.clients[i].terminate(); + } + } + catch (e) { + error = e; + } + + // remove path descriptor, if any + if (this.path && this._server._webSocketPaths) { + delete this._server._webSocketPaths[this.path]; + if (Object.keys(this._server._webSocketPaths).length == 0) { + delete this._server._webSocketPaths; + } + } + + // close the http server if it was internally created + try { + if (typeof this._closeServer !== 'undefined') { + this._closeServer(); + } + } + finally { + delete this._server; + } + if (error) throw error; +} + +/** + * Handle a HTTP Upgrade request. + * + * @api public + */ + +WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) { + // check for wrong path + if (this.options.path) { + var u = url.parse(req.url); + if (u && u.pathname !== this.options.path) return; + } + + if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); + else handleHybiUpgrade.apply(this, arguments); +} + +module.exports = WebSocketServer; + +/** + * Entirely private apis, + * which may or may not be bound to a sepcific WebSocket instance. + */ + +function handleHybiUpgrade(req, socket, upgradeHead, cb) { + // handle premature socket errors + var errorHandler = function() { + try { socket.destroy(); } catch (e) {} + } + socket.on('error', errorHandler); + + // verify key presence + if (!req.headers['sec-websocket-key']) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + // verify version + var version = parseInt(req.headers['sec-websocket-version']); + if ([8, 13].indexOf(version) === -1) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + // verify protocol + var protocols = req.headers['sec-websocket-protocol']; + + // verify client + var origin = version < 13 ? + req.headers['sec-websocket-origin'] : + req.headers['origin']; + + // handler to call when the connection sequence completes + var self = this; + var completeHybiUpgrade2 = function(protocol) { + + // calc key + var key = req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + if (typeof protocol != 'undefined') { + headers.push('Sec-WebSocket-Protocol: ' + protocol); + } + + // allows external modification/inspection of handshake headers + self.emit('headers', headers); + + socket.setTimeout(0); + socket.setNoDelay(true); + try { + socket.write(headers.concat('', '').join('\r\n')); + } + catch (e) { + // if the upgrade write fails, shut the connection down hard + try { socket.destroy(); } catch (e) {} + return; + } + + var client = new WebSocket([req, socket, upgradeHead], { + protocolVersion: version, + protocol: protocol + }); + + if (self.options.clientTracking) { + self.clients.push(client); + client.on('close', function() { + var index = self.clients.indexOf(client); + if (index != -1) { + self.clients.splice(index, 1); + } + }); + } + + // signal upgrade complete + socket.removeListener('error', errorHandler); + cb(client); + } + + // optionally call external protocol selection handler before + // calling completeHybiUpgrade2 + var completeHybiUpgrade1 = function() { + // choose from the sub-protocols + if (typeof self.options.handleProtocols == 'function') { + var protList = (protocols || "").split(/, */); + var callbackCalled = false; + var res = self.options.handleProtocols(protList, function(result, protocol) { + callbackCalled = true; + if (!result) abortConnection(socket, 404, 'Unauthorized') + else completeHybiUpgrade2(protocol); + }); + if (!callbackCalled) { + // the handleProtocols handler never called our callback + abortConnection(socket, 501, 'Could not process protocols'); + } + return; + } else { + if (typeof protocols !== 'undefined') { + completeHybiUpgrade2(protocols.split(/, */)[0]); + } + else { + completeHybiUpgrade2(); + } + } + } + + // optionally call external client verification handler + if (typeof this.options.verifyClient == 'function') { + var info = { + origin: origin, + secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', + req: req + }; + if (this.options.verifyClient.length == 2) { + this.options.verifyClient(info, function(result) { + if (!result) abortConnection(socket, 401, 'Unauthorized') + else completeHybiUpgrade1(); + }); + return; + } + else if (!this.options.verifyClient(info)) { + abortConnection(socket, 401, 'Unauthorized'); + return; + } + } + + completeHybiUpgrade1(); +} + +function handleHixieUpgrade(req, socket, upgradeHead, cb) { + // handle premature socket errors + var errorHandler = function() { + try { socket.destroy(); } catch (e) {} + } + socket.on('error', errorHandler); + + // bail if options prevent hixie + if (this.options.disableHixie) { + abortConnection(socket, 401, 'Hixie support disabled'); + return; + } + + // verify key presence + if (!req.headers['sec-websocket-key2']) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + var origin = req.headers['origin'] + , self = this; + + // setup handshake completion to run after client has been verified + var onClientVerified = function() { + var wshost; + if (!req.headers['x-forwarded-host']) + wshost = req.headers.host; + else + wshost = req.headers['x-forwarded-host']; + var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url + , protocol = req.headers['sec-websocket-protocol']; + + // handshake completion code to run once nonce has been successfully retrieved + var completeHandshake = function(nonce, rest) { + // calculate key + var k1 = req.headers['sec-websocket-key1'] + , k2 = req.headers['sec-websocket-key2'] + , md5 = crypto.createHash('md5'); + + [k1, k2].forEach(function (k) { + var n = parseInt(k.replace(/[^\d]/g, '')) + , spaces = k.replace(/[^ ]/g, '').length; + if (spaces === 0 || n % spaces !== 0){ + abortConnection(socket, 400, 'Bad Request'); + return; + } + n /= spaces; + md5.update(String.fromCharCode( + n >> 24 & 0xFF, + n >> 16 & 0xFF, + n >> 8 & 0xFF, + n & 0xFF)); + }); + md5.update(nonce.toString('binary')); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: WebSocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Location: ' + location + ]; + if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); + if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + + socket.setTimeout(0); + socket.setNoDelay(true); + try { + // merge header and hash buffer + var headerBuffer = new Buffer(headers.concat('', '').join('\r\n')); + var hashBuffer = new Buffer(md5.digest('binary'), 'binary'); + var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length); + headerBuffer.copy(handshakeBuffer, 0); + hashBuffer.copy(handshakeBuffer, headerBuffer.length); + + // do a single write, which - upon success - causes a new client websocket to be setup + socket.write(handshakeBuffer, 'binary', function(err) { + if (err) return; // do not create client if an error happens + var client = new WebSocket([req, socket, rest], { + protocolVersion: 'hixie-76', + protocol: protocol + }); + if (self.options.clientTracking) { + self.clients.push(client); + client.on('close', function() { + var index = self.clients.indexOf(client); + if (index != -1) { + self.clients.splice(index, 1); + } + }); + } + + // signal upgrade complete + socket.removeListener('error', errorHandler); + cb(client); + }); + } + catch (e) { + try { socket.destroy(); } catch (e) {} + return; + } + } + + // retrieve nonce + var nonceLength = 8; + if (upgradeHead && upgradeHead.length >= nonceLength) { + var nonce = upgradeHead.slice(0, nonceLength); + var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; + completeHandshake.call(self, nonce, rest); + } + else { + // nonce not present in upgradeHead, so we must wait for enough data + // data to arrive before continuing + var nonce = new Buffer(nonceLength); + upgradeHead.copy(nonce, 0); + var received = upgradeHead.length; + var rest = null; + var handler = function (data) { + var toRead = Math.min(data.length, nonceLength - received); + if (toRead === 0) return; + data.copy(nonce, received, 0, toRead); + received += toRead; + if (received == nonceLength) { + socket.removeListener('data', handler); + if (toRead < data.length) rest = data.slice(toRead); + completeHandshake.call(self, nonce, rest); + } + } + socket.on('data', handler); + } + } + + // verify client + if (typeof this.options.verifyClient == 'function') { + var info = { + origin: origin, + secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', + req: req + }; + if (this.options.verifyClient.length == 2) { + var self = this; + this.options.verifyClient(info, function(result) { + if (!result) abortConnection(socket, 401, 'Unauthorized') + else onClientVerified.apply(self); + }); + return; + } + else if (!this.options.verifyClient(info)) { + abortConnection(socket, 401, 'Unauthorized'); + return; + } + } + + // no client verification required + onClientVerified(); +} + +function abortConnection(socket, code, name) { + try { + var response = [ + 'HTTP/1.1 ' + code + ' ' + name, + 'Content-type: text/html' + ]; + socket.write(response.concat('', '').join('\r\n')); + } + catch (e) { /* ignore errors - we've aborted this connection */ } + finally { + // ensure that an early aborted connection is shut down completely + try { socket.destroy(); } catch (e) {} + } +} diff --git a/node_modules/ws/lib/browser.js b/node_modules/ws/lib/browser.js new file mode 100644 index 0000000..8d3a755 --- /dev/null +++ b/node_modules/ws/lib/browser.js @@ -0,0 +1,43 @@ + +/** + * Module dependencies. + */ + +var global = (function() { return this; })(); + +/** + * WebSocket constructor. + */ + +var WebSocket = global.WebSocket || global.MozWebSocket; + +/** + * Module exports. + */ + +module.exports = WebSocket ? ws : null; + +/** + * WebSocket constructor. + * + * The third `opts` options object gets ignored in web browsers, since it's + * non-standard, and throws a TypeError if passed to the constructor. + * See: https://github.com/einaros/ws/issues/227 + * + * @param {String} uri + * @param {Array} protocols (optional) + * @param {Object) opts (optional) + * @api public + */ + +function ws(uri, protocols, opts) { + var instance; + if (protocols) { + instance = new WebSocket(uri, protocols); + } else { + instance = new WebSocket(uri); + } + return instance; +} + +if (WebSocket) ws.prototype = WebSocket.prototype; diff --git a/node_modules/ws/node_modules/commander/.npmignore b/node_modules/ws/node_modules/commander/.npmignore new file mode 100644 index 0000000..f1250e5 --- /dev/null +++ b/node_modules/ws/node_modules/commander/.npmignore @@ -0,0 +1,4 @@ +support +test +examples +*.sock diff --git a/node_modules/ws/node_modules/commander/.travis.yml b/node_modules/ws/node_modules/commander/.travis.yml new file mode 100644 index 0000000..f1d0f13 --- /dev/null +++ b/node_modules/ws/node_modules/commander/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 diff --git a/node_modules/ws/node_modules/commander/History.md b/node_modules/ws/node_modules/commander/History.md new file mode 100644 index 0000000..4961d2e --- /dev/null +++ b/node_modules/ws/node_modules/commander/History.md @@ -0,0 +1,107 @@ + +0.6.1 / 2012-06-01 +================== + + * Added: append (yes or no) on confirmation + * Added: allow node.js v0.7.x + +0.6.0 / 2012-04-10 +================== + + * Added `.prompt(obj, callback)` support. Closes #49 + * Added default support to .choose(). Closes #41 + * Fixed the choice example + +0.5.1 / 2011-12-20 +================== + + * Fixed `password()` for recent nodes. Closes #36 + +0.5.0 / 2011-12-04 +================== + + * Added sub-command option support [itay] + +0.4.3 / 2011-12-04 +================== + + * Fixed custom help ordering. Closes #32 + +0.4.2 / 2011-11-24 +================== + + * Added travis support + * Fixed: line-buffered input automatically trimmed. Closes #31 + +0.4.1 / 2011-11-18 +================== + + * Removed listening for "close" on --help + +0.4.0 / 2011-11-15 +================== + + * Added support for `--`. Closes #24 + +0.3.3 / 2011-11-14 +================== + + * Fixed: wait for close event when writing help info [Jerry Hamlet] + +0.3.2 / 2011-11-01 +================== + + * Fixed long flag definitions with values [felixge] + +0.3.1 / 2011-10-31 +================== + + * Changed `--version` short flag to `-V` from `-v` + * Changed `.version()` so it's configurable [felixge] + +0.3.0 / 2011-10-31 +================== + + * Added support for long flags only. Closes #18 + +0.2.1 / 2011-10-24 +================== + + * "node": ">= 0.4.x < 0.7.0". Closes #20 + +0.2.0 / 2011-09-26 +================== + + * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs] + +0.1.0 / 2011-08-24 +================== + + * Added support for custom `--help` output + +0.0.5 / 2011-08-18 +================== + + * Changed: when the user enters nothing prompt for password again + * Fixed issue with passwords beginning with numbers [NuckChorris] + +0.0.4 / 2011-08-15 +================== + + * Fixed `Commander#args` + +0.0.3 / 2011-08-15 +================== + + * Added default option value support + +0.0.2 / 2011-08-15 +================== + + * Added mask support to `Command#password(str[, mask], fn)` + * Added `Command#password(str, fn)` + +0.0.1 / 2010-01-03 +================== + + * Initial release diff --git a/node_modules/ws/node_modules/commander/Makefile b/node_modules/ws/node_modules/commander/Makefile new file mode 100644 index 0000000..0074625 --- /dev/null +++ b/node_modules/ws/node_modules/commander/Makefile @@ -0,0 +1,7 @@ + +TESTS = $(shell find test/test.*.js) + +test: + @./test/run $(TESTS) + +.PHONY: test \ No newline at end of file diff --git a/node_modules/ws/node_modules/commander/Readme.md b/node_modules/ws/node_modules/commander/Readme.md new file mode 100644 index 0000000..b8328c3 --- /dev/null +++ b/node_modules/ws/node_modules/commander/Readme.md @@ -0,0 +1,262 @@ +# Commander.js + + The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander). + + [![Build Status](https://secure.travis-ci.org/visionmedia/commander.js.png)](http://travis-ci.org/visionmedia/commander.js) + +## Installation + + $ npm install commander + +## Option parsing + + Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options. + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('commander'); + +program + .version('0.0.1') + .option('-p, --peppers', 'Add peppers') + .option('-P, --pineapple', 'Add pineapple') + .option('-b, --bbq', 'Add bbq sauce') + .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble') + .parse(process.argv); + +console.log('you ordered a pizza with:'); +if (program.peppers) console.log(' - peppers'); +if (program.pineapple) console.log(' - pineappe'); +if (program.bbq) console.log(' - bbq'); +console.log(' - %s cheese', program.cheese); +``` + + Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. + +## Automated --help + + The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free: + +``` + $ ./examples/pizza --help + + Usage: pizza [options] + + Options: + + -V, --version output the version number + -p, --peppers Add peppers + -P, --pineapple Add pineappe + -b, --bbq Add bbq sauce + -c, --cheese Add the specified type of cheese [marble] + -h, --help output usage information + +``` + +## Coercion + +```js +function range(val) { + return val.split('..').map(Number); +} + +function list(val) { + return val.split(','); +} + +program + .version('0.0.1') + .usage('[options] ') + .option('-i, --integer ', 'An integer argument', parseInt) + .option('-f, --float ', 'A float argument', parseFloat) + .option('-r, --range ..', 'A range', range) + .option('-l, --list ', 'A list', list) + .option('-o, --optional [value]', 'An optional value') + .parse(process.argv); + +console.log(' int: %j', program.integer); +console.log(' float: %j', program.float); +console.log(' optional: %j', program.optional); +program.range = program.range || []; +console.log(' range: %j..%j', program.range[0], program.range[1]); +console.log(' list: %j', program.list); +console.log(' args: %j', program.args); +``` + +## Custom help + + You can display arbitrary `-h, --help` information + by listening for "--help". Commander will automatically + exit once you are done so that the remainder of your program + does not execute causing undesired behaviours, for example + in the following executable "stuff" will not output when + `--help` is used. + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +function list(val) { + return val.split(',').map(Number); +} + +program + .version('0.0.1') + .option('-f, --foo', 'enable some foo') + .option('-b, --bar', 'enable some bar') + .option('-B, --baz', 'enable some baz'); + +// must be before .parse() since +// node's emit() is immediate + +program.on('--help', function(){ + console.log(' Examples:'); + console.log(''); + console.log(' $ custom-help --help'); + console.log(' $ custom-help -h'); + console.log(''); +}); + +program.parse(process.argv); + +console.log('stuff'); +``` + +yielding the following help output: + +``` + +Usage: custom-help [options] + +Options: + + -h, --help output usage information + -V, --version output the version number + -f, --foo enable some foo + -b, --bar enable some bar + -B, --baz enable some baz + +Examples: + + $ custom-help --help + $ custom-help -h + +``` + +## .prompt(msg, fn) + + Single-line prompt: + +```js +program.prompt('name: ', function(name){ + console.log('hi %s', name); +}); +``` + + Multi-line prompt: + +```js +program.prompt('description:', function(name){ + console.log('hi %s', name); +}); +``` + + Coercion: + +```js +program.prompt('Age: ', Number, function(age){ + console.log('age: %j', age); +}); +``` + +```js +program.prompt('Birthdate: ', Date, function(date){ + console.log('date: %s', date); +}); +``` + +## .password(msg[, mask], fn) + +Prompt for password without echoing: + +```js +program.password('Password: ', function(pass){ + console.log('got "%s"', pass); + process.stdin.destroy(); +}); +``` + +Prompt for password with mask char "*": + +```js +program.password('Password: ', '*', function(pass){ + console.log('got "%s"', pass); + process.stdin.destroy(); +}); +``` + +## .confirm(msg, fn) + + Confirm with the given `msg`: + +```js +program.confirm('continue? ', function(ok){ + console.log(' got %j', ok); +}); +``` + +## .choose(list, fn) + + Let the user choose from a `list`: + +```js +var list = ['tobi', 'loki', 'jane', 'manny', 'luna']; + +console.log('Choose the coolest pet:'); +program.choose(list, function(i){ + console.log('you chose %d "%s"', i, list[i]); +}); +``` + +## Links + + - [API documentation](http://visionmedia.github.com/commander.js/) + - [ascii tables](https://github.com/LearnBoost/cli-table) + - [progress bars](https://github.com/visionmedia/node-progress) + - [more progress bars](https://github.com/substack/node-multimeter) + - [examples](https://github.com/visionmedia/commander.js/tree/master/examples) + +## License + +(The MIT License) + +Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/ws/node_modules/commander/index.js b/node_modules/ws/node_modules/commander/index.js new file mode 100644 index 0000000..06ec1e4 --- /dev/null +++ b/node_modules/ws/node_modules/commander/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/commander'); \ No newline at end of file diff --git a/node_modules/ws/node_modules/commander/lib/commander.js b/node_modules/ws/node_modules/commander/lib/commander.js new file mode 100644 index 0000000..5ba87eb --- /dev/null +++ b/node_modules/ws/node_modules/commander/lib/commander.js @@ -0,0 +1,1026 @@ + +/*! + * commander + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , path = require('path') + , tty = require('tty') + , basename = path.basename; + +/** + * Expose the root command. + */ + +exports = module.exports = new Command; + +/** + * Expose `Command`. + */ + +exports.Command = Command; + +/** + * Expose `Option`. + */ + +exports.Option = Option; + +/** + * Initialize a new `Option` with the given `flags` and `description`. + * + * @param {String} flags + * @param {String} description + * @api public + */ + +function Option(flags, description) { + this.flags = flags; + this.required = ~flags.indexOf('<'); + this.optional = ~flags.indexOf('['); + this.bool = !~flags.indexOf('-no-'); + flags = flags.split(/[ ,|]+/); + if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift(); + this.long = flags.shift(); + this.description = description; +} + +/** + * Return option name. + * + * @return {String} + * @api private + */ + +Option.prototype.name = function(){ + return this.long + .replace('--', '') + .replace('no-', ''); +}; + +/** + * Check if `arg` matches the short or long flag. + * + * @param {String} arg + * @return {Boolean} + * @api private + */ + +Option.prototype.is = function(arg){ + return arg == this.short + || arg == this.long; +}; + +/** + * Initialize a new `Command`. + * + * @param {String} name + * @api public + */ + +function Command(name) { + this.commands = []; + this.options = []; + this.args = []; + this.name = name; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Command.prototype.__proto__ = EventEmitter.prototype; + +/** + * Add command `name`. + * + * The `.action()` callback is invoked when the + * command `name` is specified via __ARGV__, + * and the remaining arguments are applied to the + * function for access. + * + * When the `name` is "*" an un-matched command + * will be passed as the first arg, followed by + * the rest of __ARGV__ remaining. + * + * Examples: + * + * program + * .version('0.0.1') + * .option('-C, --chdir ', 'change the working directory') + * .option('-c, --config ', 'set config path. defaults to ./deploy.conf') + * .option('-T, --no-tests', 'ignore test hook') + * + * program + * .command('setup') + * .description('run remote setup commands') + * .action(function(){ + * console.log('setup'); + * }); + * + * program + * .command('exec ') + * .description('run the given remote command') + * .action(function(cmd){ + * console.log('exec "%s"', cmd); + * }); + * + * program + * .command('*') + * .description('deploy the given env') + * .action(function(env){ + * console.log('deploying "%s"', env); + * }); + * + * program.parse(process.argv); + * + * @param {String} name + * @return {Command} the new command + * @api public + */ + +Command.prototype.command = function(name){ + var args = name.split(/ +/); + var cmd = new Command(args.shift()); + this.commands.push(cmd); + cmd.parseExpectedArgs(args); + cmd.parent = this; + return cmd; +}; + +/** + * Parse expected `args`. + * + * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`. + * + * @param {Array} args + * @return {Command} for chaining + * @api public + */ + +Command.prototype.parseExpectedArgs = function(args){ + if (!args.length) return; + var self = this; + args.forEach(function(arg){ + switch (arg[0]) { + case '<': + self.args.push({ required: true, name: arg.slice(1, -1) }); + break; + case '[': + self.args.push({ required: false, name: arg.slice(1, -1) }); + break; + } + }); + return this; +}; + +/** + * Register callback `fn` for the command. + * + * Examples: + * + * program + * .command('help') + * .description('display verbose help') + * .action(function(){ + * // output help here + * }); + * + * @param {Function} fn + * @return {Command} for chaining + * @api public + */ + +Command.prototype.action = function(fn){ + var self = this; + this.parent.on(this.name, function(args, unknown){ + // Parse any so-far unknown options + unknown = unknown || []; + var parsed = self.parseOptions(unknown); + + // Output help if necessary + outputHelpIfNecessary(self, parsed.unknown); + + // If there are still any unknown options, then we simply + // die, unless someone asked for help, in which case we give it + // to them, and then we die. + if (parsed.unknown.length > 0) { + self.unknownOption(parsed.unknown[0]); + } + + self.args.forEach(function(arg, i){ + if (arg.required && null == args[i]) { + self.missingArgument(arg.name); + } + }); + + // Always append ourselves to the end of the arguments, + // to make sure we match the number of arguments the user + // expects + if (self.args.length) { + args[self.args.length] = self; + } else { + args.push(self); + } + + fn.apply(this, args); + }); + return this; +}; + +/** + * Define option with `flags`, `description` and optional + * coercion `fn`. + * + * The `flags` string should contain both the short and long flags, + * separated by comma, a pipe or space. The following are all valid + * all will output this way when `--help` is used. + * + * "-p, --pepper" + * "-p|--pepper" + * "-p --pepper" + * + * Examples: + * + * // simple boolean defaulting to false + * program.option('-p, --pepper', 'add pepper'); + * + * --pepper + * program.pepper + * // => Boolean + * + * // simple boolean defaulting to false + * program.option('-C, --no-cheese', 'remove cheese'); + * + * program.cheese + * // => true + * + * --no-cheese + * program.cheese + * // => true + * + * // required argument + * program.option('-C, --chdir ', 'change the working directory'); + * + * --chdir /tmp + * program.chdir + * // => "/tmp" + * + * // optional argument + * program.option('-c, --cheese [type]', 'add cheese [marble]'); + * + * @param {String} flags + * @param {String} description + * @param {Function|Mixed} fn or default + * @param {Mixed} defaultValue + * @return {Command} for chaining + * @api public + */ + +Command.prototype.option = function(flags, description, fn, defaultValue){ + var self = this + , option = new Option(flags, description) + , oname = option.name() + , name = camelcase(oname); + + // default as 3rd arg + if ('function' != typeof fn) defaultValue = fn, fn = null; + + // preassign default value only for --no-*, [optional], or + if (false == option.bool || option.optional || option.required) { + // when --no-* we make sure default is true + if (false == option.bool) defaultValue = true; + // preassign only if we have a default + if (undefined !== defaultValue) self[name] = defaultValue; + } + + // register the option + this.options.push(option); + + // when it's passed assign the value + // and conditionally invoke the callback + this.on(oname, function(val){ + // coercion + if (null != val && fn) val = fn(val); + + // unassigned or bool + if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) { + // if no value, bool true, and we have a default, then use it! + if (null == val) { + self[name] = option.bool + ? defaultValue || true + : false; + } else { + self[name] = val; + } + } else if (null !== val) { + // reassign + self[name] = val; + } + }); + + return this; +}; + +/** + * Parse `argv`, settings options and invoking commands when defined. + * + * @param {Array} argv + * @return {Command} for chaining + * @api public + */ + +Command.prototype.parse = function(argv){ + // store raw args + this.rawArgs = argv; + + // guess name + if (!this.name) this.name = basename(argv[1]); + + // process argv + var parsed = this.parseOptions(this.normalize(argv.slice(2))); + this.args = parsed.args; + return this.parseArgs(this.args, parsed.unknown); +}; + +/** + * Normalize `args`, splitting joined short flags. For example + * the arg "-abc" is equivalent to "-a -b -c". + * + * @param {Array} args + * @return {Array} + * @api private + */ + +Command.prototype.normalize = function(args){ + var ret = [] + , arg; + + for (var i = 0, len = args.length; i < len; ++i) { + arg = args[i]; + if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) { + arg.slice(1).split('').forEach(function(c){ + ret.push('-' + c); + }); + } else { + ret.push(arg); + } + } + + return ret; +}; + +/** + * Parse command `args`. + * + * When listener(s) are available those + * callbacks are invoked, otherwise the "*" + * event is emitted and those actions are invoked. + * + * @param {Array} args + * @return {Command} for chaining + * @api private + */ + +Command.prototype.parseArgs = function(args, unknown){ + var cmds = this.commands + , len = cmds.length + , name; + + if (args.length) { + name = args[0]; + if (this.listeners(name).length) { + this.emit(args.shift(), args, unknown); + } else { + this.emit('*', args); + } + } else { + outputHelpIfNecessary(this, unknown); + + // If there were no args and we have unknown options, + // then they are extraneous and we need to error. + if (unknown.length > 0) { + this.unknownOption(unknown[0]); + } + } + + return this; +}; + +/** + * Return an option matching `arg` if any. + * + * @param {String} arg + * @return {Option} + * @api private + */ + +Command.prototype.optionFor = function(arg){ + for (var i = 0, len = this.options.length; i < len; ++i) { + if (this.options[i].is(arg)) { + return this.options[i]; + } + } +}; + +/** + * Parse options from `argv` returning `argv` + * void of these options. + * + * @param {Array} argv + * @return {Array} + * @api public + */ + +Command.prototype.parseOptions = function(argv){ + var args = [] + , len = argv.length + , literal + , option + , arg; + + var unknownOptions = []; + + // parse options + for (var i = 0; i < len; ++i) { + arg = argv[i]; + + // literal args after -- + if ('--' == arg) { + literal = true; + continue; + } + + if (literal) { + args.push(arg); + continue; + } + + // find matching Option + option = this.optionFor(arg); + + // option is defined + if (option) { + // requires arg + if (option.required) { + arg = argv[++i]; + if (null == arg) return this.optionMissingArgument(option); + if ('-' == arg[0]) return this.optionMissingArgument(option, arg); + this.emit(option.name(), arg); + // optional arg + } else if (option.optional) { + arg = argv[i+1]; + if (null == arg || '-' == arg[0]) { + arg = null; + } else { + ++i; + } + this.emit(option.name(), arg); + // bool + } else { + this.emit(option.name()); + } + continue; + } + + // looks like an option + if (arg.length > 1 && '-' == arg[0]) { + unknownOptions.push(arg); + + // If the next argument looks like it might be + // an argument for this option, we pass it on. + // If it isn't, then it'll simply be ignored + if (argv[i+1] && '-' != argv[i+1][0]) { + unknownOptions.push(argv[++i]); + } + continue; + } + + // arg + args.push(arg); + } + + return { args: args, unknown: unknownOptions }; +}; + +/** + * Argument `name` is missing. + * + * @param {String} name + * @api private + */ + +Command.prototype.missingArgument = function(name){ + console.error(); + console.error(" error: missing required argument `%s'", name); + console.error(); + process.exit(1); +}; + +/** + * `Option` is missing an argument, but received `flag` or nothing. + * + * @param {String} option + * @param {String} flag + * @api private + */ + +Command.prototype.optionMissingArgument = function(option, flag){ + console.error(); + if (flag) { + console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag); + } else { + console.error(" error: option `%s' argument missing", option.flags); + } + console.error(); + process.exit(1); +}; + +/** + * Unknown option `flag`. + * + * @param {String} flag + * @api private + */ + +Command.prototype.unknownOption = function(flag){ + console.error(); + console.error(" error: unknown option `%s'", flag); + console.error(); + process.exit(1); +}; + +/** + * Set the program version to `str`. + * + * This method auto-registers the "-V, --version" flag + * which will print the version number when passed. + * + * @param {String} str + * @param {String} flags + * @return {Command} for chaining + * @api public + */ + +Command.prototype.version = function(str, flags){ + if (0 == arguments.length) return this._version; + this._version = str; + flags = flags || '-V, --version'; + this.option(flags, 'output the version number'); + this.on('version', function(){ + console.log(str); + process.exit(0); + }); + return this; +}; + +/** + * Set the description `str`. + * + * @param {String} str + * @return {String|Command} + * @api public + */ + +Command.prototype.description = function(str){ + if (0 == arguments.length) return this._description; + this._description = str; + return this; +}; + +/** + * Set / get the command usage `str`. + * + * @param {String} str + * @return {String|Command} + * @api public + */ + +Command.prototype.usage = function(str){ + var args = this.args.map(function(arg){ + return arg.required + ? '<' + arg.name + '>' + : '[' + arg.name + ']'; + }); + + var usage = '[options' + + (this.commands.length ? '] [command' : '') + + ']' + + (this.args.length ? ' ' + args : ''); + if (0 == arguments.length) return this._usage || usage; + this._usage = str; + + return this; +}; + +/** + * Return the largest option length. + * + * @return {Number} + * @api private + */ + +Command.prototype.largestOptionLength = function(){ + return this.options.reduce(function(max, option){ + return Math.max(max, option.flags.length); + }, 0); +}; + +/** + * Return help for options. + * + * @return {String} + * @api private + */ + +Command.prototype.optionHelp = function(){ + var width = this.largestOptionLength(); + + // Prepend the help information + return [pad('-h, --help', width) + ' ' + 'output usage information'] + .concat(this.options.map(function(option){ + return pad(option.flags, width) + + ' ' + option.description; + })) + .join('\n'); +}; + +/** + * Return command help documentation. + * + * @return {String} + * @api private + */ + +Command.prototype.commandHelp = function(){ + if (!this.commands.length) return ''; + return [ + '' + , ' Commands:' + , '' + , this.commands.map(function(cmd){ + var args = cmd.args.map(function(arg){ + return arg.required + ? '<' + arg.name + '>' + : '[' + arg.name + ']'; + }).join(' '); + + return cmd.name + + (cmd.options.length + ? ' [options]' + : '') + ' ' + args + + (cmd.description() + ? '\n' + cmd.description() + : ''); + }).join('\n\n').replace(/^/gm, ' ') + , '' + ].join('\n'); +}; + +/** + * Return program help documentation. + * + * @return {String} + * @api private + */ + +Command.prototype.helpInformation = function(){ + return [ + '' + , ' Usage: ' + this.name + ' ' + this.usage() + , '' + this.commandHelp() + , ' Options:' + , '' + , '' + this.optionHelp().replace(/^/gm, ' ') + , '' + , '' + ].join('\n'); +}; + +/** + * Prompt for a `Number`. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptForNumber = function(str, fn){ + var self = this; + this.promptSingleLine(str, function parseNumber(val){ + val = Number(val); + if (isNaN(val)) return self.promptSingleLine(str + '(must be a number) ', parseNumber); + fn(val); + }); +}; + +/** + * Prompt for a `Date`. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptForDate = function(str, fn){ + var self = this; + this.promptSingleLine(str, function parseDate(val){ + val = new Date(val); + if (isNaN(val.getTime())) return self.promptSingleLine(str + '(must be a date) ', parseDate); + fn(val); + }); +}; + +/** + * Single-line prompt. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptSingleLine = function(str, fn){ + if ('function' == typeof arguments[2]) { + return this['promptFor' + (fn.name || fn)](str, arguments[2]); + } + + process.stdout.write(str); + process.stdin.setEncoding('utf8'); + process.stdin.once('data', function(val){ + fn(val.trim()); + }).resume(); +}; + +/** + * Multi-line prompt. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptMultiLine = function(str, fn){ + var buf = []; + console.log(str); + process.stdin.setEncoding('utf8'); + process.stdin.on('data', function(val){ + if ('\n' == val || '\r\n' == val) { + process.stdin.removeAllListeners('data'); + fn(buf.join('\n')); + } else { + buf.push(val.trimRight()); + } + }).resume(); +}; + +/** + * Prompt `str` and callback `fn(val)` + * + * Commander supports single-line and multi-line prompts. + * To issue a single-line prompt simply add white-space + * to the end of `str`, something like "name: ", whereas + * for a multi-line prompt omit this "description:". + * + * + * Examples: + * + * program.prompt('Username: ', function(name){ + * console.log('hi %s', name); + * }); + * + * program.prompt('Description:', function(desc){ + * console.log('description was "%s"', desc.trim()); + * }); + * + * @param {String|Object} str + * @param {Function} fn + * @api public + */ + +Command.prototype.prompt = function(str, fn){ + var self = this; + + if ('string' == typeof str) { + if (/ $/.test(str)) return this.promptSingleLine.apply(this, arguments); + this.promptMultiLine(str, fn); + } else { + var keys = Object.keys(str) + , obj = {}; + + function next() { + var key = keys.shift() + , label = str[key]; + + if (!key) return fn(obj); + self.prompt(label, function(val){ + obj[key] = val; + next(); + }); + } + + next(); + } +}; + +/** + * Prompt for password with `str`, `mask` char and callback `fn(val)`. + * + * The mask string defaults to '', aka no output is + * written while typing, you may want to use "*" etc. + * + * Examples: + * + * program.password('Password: ', function(pass){ + * console.log('got "%s"', pass); + * process.stdin.destroy(); + * }); + * + * program.password('Password: ', '*', function(pass){ + * console.log('got "%s"', pass); + * process.stdin.destroy(); + * }); + * + * @param {String} str + * @param {String} mask + * @param {Function} fn + * @api public + */ + +Command.prototype.password = function(str, mask, fn){ + var self = this + , buf = ''; + + // default mask + if ('function' == typeof mask) { + fn = mask; + mask = ''; + } + + process.stdin.resume(); + tty.setRawMode(true); + process.stdout.write(str); + + // keypress + process.stdin.on('keypress', function(c, key){ + if (key && 'enter' == key.name) { + console.log(); + process.stdin.removeAllListeners('keypress'); + tty.setRawMode(false); + if (!buf.trim().length) return self.password(str, mask, fn); + fn(buf); + return; + } + + if (key && key.ctrl && 'c' == key.name) { + console.log('%s', buf); + process.exit(); + } + + process.stdout.write(mask); + buf += c; + }).resume(); +}; + +/** + * Confirmation prompt with `str` and callback `fn(bool)` + * + * Examples: + * + * program.confirm('continue? ', function(ok){ + * console.log(' got %j', ok); + * process.stdin.destroy(); + * }); + * + * @param {String} str + * @param {Function} fn + * @api public + */ + + +Command.prototype.confirm = function(str, fn, verbose){ + var self = this; + this.prompt(str, function(ok){ + if (!ok.trim()) { + if (!verbose) str += '(yes or no) '; + return self.confirm(str, fn, true); + } + fn(parseBool(ok)); + }); +}; + +/** + * Choice prompt with `list` of items and callback `fn(index, item)` + * + * Examples: + * + * var list = ['tobi', 'loki', 'jane', 'manny', 'luna']; + * + * console.log('Choose the coolest pet:'); + * program.choose(list, function(i){ + * console.log('you chose %d "%s"', i, list[i]); + * process.stdin.destroy(); + * }); + * + * @param {Array} list + * @param {Number|Function} index or fn + * @param {Function} fn + * @api public + */ + +Command.prototype.choose = function(list, index, fn){ + var self = this + , hasDefault = 'number' == typeof index; + + if (!hasDefault) { + fn = index; + index = null; + } + + list.forEach(function(item, i){ + if (hasDefault && i == index) { + console.log('* %d) %s', i + 1, item); + } else { + console.log(' %d) %s', i + 1, item); + } + }); + + function again() { + self.prompt(' : ', function(val){ + val = parseInt(val, 10) - 1; + if (hasDefault && isNaN(val)) val = index; + + if (null == list[val]) { + again(); + } else { + fn(val, list[val]); + } + }); + } + + again(); +}; + +/** + * Camel-case the given `flag` + * + * @param {String} flag + * @return {String} + * @api private + */ + +function camelcase(flag) { + return flag.split('-').reduce(function(str, word){ + return str + word[0].toUpperCase() + word.slice(1); + }); +} + +/** + * Parse a boolean `str`. + * + * @param {String} str + * @return {Boolean} + * @api private + */ + +function parseBool(str) { + return /^y|yes|ok|true$/i.test(str); +} + +/** + * Pad `str` to `width`. + * + * @param {String} str + * @param {Number} width + * @return {String} + * @api private + */ + +function pad(str, width) { + var len = Math.max(0, width - str.length); + return str + Array(len + 1).join(' '); +} + +/** + * Output help information if necessary + * + * @param {Command} command to output help for + * @param {Array} array of options to search for -h or --help + * @api private + */ + +function outputHelpIfNecessary(cmd, options) { + options = options || []; + for (var i = 0; i < options.length; i++) { + if (options[i] == '--help' || options[i] == '-h') { + process.stdout.write(cmd.helpInformation()); + cmd.emit('--help'); + process.exit(0); + } + } +} diff --git a/node_modules/ws/node_modules/commander/package.json b/node_modules/ws/node_modules/commander/package.json new file mode 100644 index 0000000..3200d50 --- /dev/null +++ b/node_modules/ws/node_modules/commander/package.json @@ -0,0 +1,37 @@ +{ + "name": "commander", + "version": "0.6.1", + "description": "the complete solution for node.js command-line programs", + "keywords": [ + "command", + "option", + "parser", + "prompt", + "stdin" + ], + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "repository": { + "type": "git", + "url": "https://github.com/visionmedia/commander.js.git" + }, + "dependencies": {}, + "devDependencies": { + "should": ">= 0.0.1" + }, + "scripts": { + "test": "make test" + }, + "main": "index", + "engines": { + "node": ">= 0.4.x" + }, + "readme": "# Commander.js\n\n The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander).\n\n [![Build Status](https://secure.travis-ci.org/visionmedia/commander.js.png)](http://travis-ci.org/visionmedia/commander.js)\n\n## Installation\n\n $ npm install commander\n\n## Option parsing\n\n Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.\n\n```js\n#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar program = require('commander');\n\nprogram\n .version('0.0.1')\n .option('-p, --peppers', 'Add peppers')\n .option('-P, --pineapple', 'Add pineapple')\n .option('-b, --bbq', 'Add bbq sauce')\n .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')\n .parse(process.argv);\n\nconsole.log('you ordered a pizza with:');\nif (program.peppers) console.log(' - peppers');\nif (program.pineapple) console.log(' - pineappe');\nif (program.bbq) console.log(' - bbq');\nconsole.log(' - %s cheese', program.cheese);\n```\n\n Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as \"--template-engine\" are camel-cased, becoming `program.templateEngine` etc.\n\n## Automated --help\n\n The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:\n\n``` \n $ ./examples/pizza --help\n\n Usage: pizza [options]\n\n Options:\n\n -V, --version output the version number\n -p, --peppers Add peppers\n -P, --pineapple Add pineappe\n -b, --bbq Add bbq sauce\n -c, --cheese Add the specified type of cheese [marble]\n -h, --help output usage information\n\n```\n\n## Coercion\n\n```js\nfunction range(val) {\n return val.split('..').map(Number);\n}\n\nfunction list(val) {\n return val.split(',');\n}\n\nprogram\n .version('0.0.1')\n .usage('[options] ')\n .option('-i, --integer ', 'An integer argument', parseInt)\n .option('-f, --float ', 'A float argument', parseFloat)\n .option('-r, --range ..', 'A range', range)\n .option('-l, --list ', 'A list', list)\n .option('-o, --optional [value]', 'An optional value')\n .parse(process.argv);\n\nconsole.log(' int: %j', program.integer);\nconsole.log(' float: %j', program.float);\nconsole.log(' optional: %j', program.optional);\nprogram.range = program.range || [];\nconsole.log(' range: %j..%j', program.range[0], program.range[1]);\nconsole.log(' list: %j', program.list);\nconsole.log(' args: %j', program.args);\n```\n\n## Custom help\n\n You can display arbitrary `-h, --help` information\n by listening for \"--help\". Commander will automatically\n exit once you are done so that the remainder of your program\n does not execute causing undesired behaviours, for example\n in the following executable \"stuff\" will not output when\n `--help` is used.\n\n```js\n#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar program = require('../');\n\nfunction list(val) {\n return val.split(',').map(Number);\n}\n\nprogram\n .version('0.0.1')\n .option('-f, --foo', 'enable some foo')\n .option('-b, --bar', 'enable some bar')\n .option('-B, --baz', 'enable some baz');\n\n// must be before .parse() since\n// node's emit() is immediate\n\nprogram.on('--help', function(){\n console.log(' Examples:');\n console.log('');\n console.log(' $ custom-help --help');\n console.log(' $ custom-help -h');\n console.log('');\n});\n\nprogram.parse(process.argv);\n\nconsole.log('stuff');\n```\n\nyielding the following help output:\n\n```\n\nUsage: custom-help [options]\n\nOptions:\n\n -h, --help output usage information\n -V, --version output the version number\n -f, --foo enable some foo\n -b, --bar enable some bar\n -B, --baz enable some baz\n\nExamples:\n\n $ custom-help --help\n $ custom-help -h\n\n```\n\n## .prompt(msg, fn)\n\n Single-line prompt:\n\n```js\nprogram.prompt('name: ', function(name){\n console.log('hi %s', name);\n});\n```\n\n Multi-line prompt:\n\n```js\nprogram.prompt('description:', function(name){\n console.log('hi %s', name);\n});\n```\n\n Coercion:\n\n```js\nprogram.prompt('Age: ', Number, function(age){\n console.log('age: %j', age);\n});\n```\n\n```js\nprogram.prompt('Birthdate: ', Date, function(date){\n console.log('date: %s', date);\n});\n```\n\n## .password(msg[, mask], fn)\n\nPrompt for password without echoing:\n\n```js\nprogram.password('Password: ', function(pass){\n console.log('got \"%s\"', pass);\n process.stdin.destroy();\n});\n```\n\nPrompt for password with mask char \"*\":\n\n```js\nprogram.password('Password: ', '*', function(pass){\n console.log('got \"%s\"', pass);\n process.stdin.destroy();\n});\n```\n\n## .confirm(msg, fn)\n\n Confirm with the given `msg`:\n\n```js\nprogram.confirm('continue? ', function(ok){\n console.log(' got %j', ok);\n});\n```\n\n## .choose(list, fn)\n\n Let the user choose from a `list`:\n\n```js\nvar list = ['tobi', 'loki', 'jane', 'manny', 'luna'];\n\nconsole.log('Choose the coolest pet:');\nprogram.choose(list, function(i){\n console.log('you chose %d \"%s\"', i, list[i]);\n});\n```\n\n## Links\n\n - [API documentation](http://visionmedia.github.com/commander.js/)\n - [ascii tables](https://github.com/LearnBoost/cli-table)\n - [progress bars](https://github.com/visionmedia/node-progress)\n - [more progress bars](https://github.com/substack/node-multimeter)\n - [examples](https://github.com/visionmedia/commander.js/tree/master/examples)\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.", + "_id": "commander@0.6.1", + "dist": { + "shasum": "ba9086c935fe769898d0417f9a8a0a19f37e85a2" + }, + "_from": "commander@~0.6.1" +} diff --git a/node_modules/ws/node_modules/nan/.index.js b/node_modules/ws/node_modules/nan/.index.js new file mode 100644 index 0000000..68da1f3 --- /dev/null +++ b/node_modules/ws/node_modules/nan/.index.js @@ -0,0 +1 @@ +//noop \ No newline at end of file diff --git a/node_modules/ws/node_modules/nan/LICENSE b/node_modules/ws/node_modules/nan/LICENSE new file mode 100644 index 0000000..352c287 --- /dev/null +++ b/node_modules/ws/node_modules/nan/LICENSE @@ -0,0 +1,43 @@ +Copyright 2013, NAN contributors: + - Rod Vagg + - Benjamin Byholm + - Trevor Norris +(the "Original Author") +All rights reserved. + +MIT +no-false-attribs License + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +Distributions of all or part of the Software intended to be used +by the recipients as they would use the unmodified Software, +containing modifications that substantially alter, remove, or +disable functionality of the Software, outside of the documented +configuration mechanisms provided by the Software, shall be +modified such that the Original Author's bug reporting email +addresses and urls are either replaced with the contact information +of the parties responsible for the changes, or removed entirely. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + + +Except where noted, this license applies to any and all software +programs and associated documentation files created by the +Original Author, when distributed with the Software. diff --git a/node_modules/ws/node_modules/nan/README.md b/node_modules/ws/node_modules/nan/README.md new file mode 100644 index 0000000..6ba57f7 --- /dev/null +++ b/node_modules/ws/node_modules/nan/README.md @@ -0,0 +1,705 @@ +Native Abstractions for Node.js +=============================== + +**A header file filled with macro and utility goodness for making addon development for Node.js easier across versions 0.8, 0.10 and 0.11, and eventually 0.12.** + +***Current version: 0.3.2*** *(See [nan.h](https://github.com/rvagg/nan/blob/master/nan.h) for changelog)* + +[![NPM](https://nodei.co/npm/nan.png?downloads=true&stars=true)](https://nodei.co/npm/nan/) [![NPM](https://nodei.co/npm-dl/nan.png?months=6)](https://nodei.co/npm/nan/) + +Thanks to the crazy changes in V8 (and some in Node core), keeping native addons compiling happily across versions, particularly 0.10 to 0.11/0.12, is a minor nightmare. The goal of this project is to store all logic necessary to develop native Node.js addons without having to inspect `NODE_MODULE_VERSION` and get yourself into a macro-tangle. + +This project also contains some helper utilities that make addon development a bit more pleasant. + + * **[Usage](#usage)** + * **[Example](#example)** + * **[API](#api)** + + +## Usage + +Simply add **NAN** as a dependency in the *package.json* of your Node addon: + +```js +"dependencies": { + ... + "nan" : "~0.3.1" + ... +} +``` + +Pull in the path to **NAN** in your *binding.gyp* so that you can use `#include "nan.h"` in your *.cpp*: + +```js +"include_dirs" : [ + ... + "` when compiling your addon. + + +## Example + +See **[LevelDOWN](https://github.com/rvagg/node-leveldown/pull/48)** for a full example of **NAN** in use. + +For a simpler example, see the **[async pi estimation example](https://github.com/rvagg/nan/tree/master/examples/async_pi_estimate)** in the examples directory for full code and an explanation of what this Monte Carlo Pi estimation example does. Below are just some parts of the full example that illustrate the use of **NAN**. + +Compare to the current 0.10 version of this example, found in the [node-addon-examples](https://github.com/rvagg/node-addon-examples/tree/master/9_async_work) repository and also a 0.11 version of the same found [here](https://github.com/kkoopa/node-addon-examples/tree/5c01f58fc993377a567812597e54a83af69686d7/9_async_work). + +Note that there is no embedded version sniffing going on here and also the async work is made much simpler, see below for details on the `NanAsyncWorker` class. + +```c++ +// addon.cc +#include +#include "nan.h" +// ... + +using namespace v8; + +void InitAll(Handle exports) { + exports->Set(NanSymbol("calculateSync"), + FunctionTemplate::New(CalculateSync)->GetFunction()); + + exports->Set(NanSymbol("calculateAsync"), + FunctionTemplate::New(CalculateAsync)->GetFunction()); +} + +NODE_MODULE(addon, InitAll) +``` + +```c++ +// sync.h +#include +#include "nan.h" + +NAN_METHOD(CalculateSync); +``` + +```c++ +// sync.cc +#include +#include "nan.h" +#include "sync.h" +// ... + +using namespace v8; + +// Simple synchronous access to the `Estimate()` function +NAN_METHOD(CalculateSync) { + NanScope(); + + // expect a number as the first argument + int points = args[0]->Uint32Value(); + double est = Estimate(points); + + NanReturnValue(Number::New(est)); +} +``` + +```c++ +// async.cc +#include +#include "nan.h" +#include "async.h" + +// ... + +using namespace v8; + +class PiWorker : public NanAsyncWorker { + public: + PiWorker(NanCallback *callback, int points) + : NanAsyncWorker(callback), points(points) {} + ~PiWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access V8, or V8 data structures + // here, so everything we need for input and output + // should go on `this`. + void Execute () { + estimate = Estimate(points); + } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use V8 again + void HandleOKCallback () { + NanScope(); + + Local argv[] = { + Local::New(Null()) + , Number::New(estimate) + }; + + callback->Call(2, argv); + }; + + private: + int points; + double estimate; +}; + +// Asynchronous access to the `Estimate()` function +NAN_METHOD(CalculateAsync) { + NanScope(); + + int points = args[0]->Uint32Value(); + NanCallback *callback = new NanCallback(args[1].As()); + + NanAsyncQueueWorker(new PiWorker(callback, points)); + NanReturnUndefined(); +} +``` + + +## API + + * NAN_METHOD + * NAN_GETTER + * NAN_SETTER + * NAN_PROPERTY_GETTER + * NAN_PROPERTY_SETTER + * NAN_PROPERTY_ENUMERATOR + * NAN_PROPERTY_DELETER + * NAN_PROPERTY_QUERY + * NAN_WEAK_CALLBACK + * NanReturnValue + * NanReturnUndefined + * NanReturnNull + * NanReturnEmptyString + * NanScope + * NanLocker + * NanUnlocker + * NanGetInternalFieldPointer + * NanSetInternalFieldPointer + * NanObjectWrapHandle + * NanMakeWeak + * NanSymbol + * NanGetPointerSafe + * NanSetPointerSafe + * NanFromV8String + * NanBooleanOptionValue + * NanUInt32OptionValue + * NanThrowError, NanThrowTypeError, NanThrowRangeError, NanThrowError(Handle), NanThrowError(Handle, int) + * NanNewBufferHandle(char *, size_t, FreeCallback, void *), NanNewBufferHandle(char *, uint32_t), NanNewBufferHandle(uint32_t) + * NanBufferUse(char *, uint32_t) + * NanNewContextHandle + * NanHasInstance + * NanPersistentToLocal + * NanDispose + * NanAssignPersistent + * NanInitPersistent + * NanCallback + * NanAsyncWorker + * NanAsyncQueueWorker + + +### NAN_METHOD(methodname) + +Use `NAN_METHOD` to define your V8 accessible methods: + +```c++ +// .h: +class Foo : public node::ObjectWrap { + ... + + static NAN_METHOD(Bar); + static NAN_METHOD(Baz); +} + + +// .cc: +NAN_METHOD(Foo::Bar) { + ... +} + +NAN_METHOD(Foo::Baz) { + ... +} +``` + +The reason for this macro is because of the method signature change in 0.11: + +```c++ +// 0.10 and below: +Handle name(const Arguments& args) + +// 0.11 and above +void name(const FunctionCallbackInfo& args) +``` + +The introduction of `FunctionCallbackInfo` brings additional complications: + + +### NAN_GETTER(methodname) + +Use `NAN_GETTER` to declare your V8 accessible getters. You get a `Local` `property` and an appropriately typed `args` object that can act like the `args` argument to a `NAN_METHOD` call. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_GETTER`. + + +### NAN_SETTER(methodname) + +Use `NAN_SETTER` to declare your V8 accessible setters. Same as `NAN_GETTER` but you also get a `Local` `value` object to work with. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_SETTER`. + + +### NAN_PROPERTY_GETTER(cbname) +Use `NAN_PROPERTY_GETTER` to declare your V8 accessible property getters. You get a `Local` `property` and an appropriately typed `args` object that can act similar to the `args` argument to a `NAN_METHOD` call. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_GETTER`. + + +### NAN_PROPERTY_SETTER(cbname) +Use `NAN_PROPERTY_SETTER` to declare your V8 accessible property setters. Same as `NAN_PROPERTY_GETTER` but you also get a `Local` `value` object to work with. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_SETTER`. + + +### NAN_PROPERTY_ENUMERATOR(cbname) +Use `NAN_PROPERTY_ENUMERATOR` to declare your V8 accessible property enumerators. You get an appropriately typed `args` object like the `args` argument to a `NAN_PROPERTY_GETTER` call. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_ENUMERATOR`. + + +### NAN_PROPERTY_DELETER(cbname) +Use `NAN_PROPERTY_DELETER` to declare your V8 accessible property deleters. Same as `NAN_PROPERTY_GETTER`. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_DELETER`. + + +### NAN_PROPERTY_QUERY(cbname) +Use `NAN_PROPERTY_QUERY` to declare your V8 accessible property queries. Same as `NAN_PROPERTY_GETTER`. + +You can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_QUERY`. + + +### NAN_WEAK_CALLBACK(type, cbname) + +Use `NAN_WEAK_CALLBACK` to declare your V8 WeakReference callbacks. There is an object argument accessible through `NAN_WEAK_CALLBACK_OBJECT`. The `type` argument gives the type of the `data` argument, accessible through `NAN_WEAK_CALLBACK_DATA(type)`. + +```c++ +static NAN_WEAK_CALLBACK(BufferReference*, WeakCheck) { + if (NAN_WEAK_CALLBACK_DATA(BufferReference*)->noLongerNeeded_) { + delete NAN_WEAK_CALLBACK_DATA(BufferReference*); + } else { + // Still in use, revive, prevent GC + NanMakeWeak(NAN_WEAK_CALLBACK_OBJECT, NAN_WEAK_CALLBACK_DATA(BufferReference*), &WeakCheck); + } +} + +``` + +### NanReturnValue(Handle<Value>) + +Use `NanReturnValue` when you want to return a value from your V8 accessible method: + +```c++ +NAN_METHOD(Foo::Bar) { + ... + + NanReturnValue(String::New("FooBar!")); +} +``` + +No `return` statement required. + + +### NanReturnUndefined() + +Use `NanReturnUndefined` when you don't want to return anything from your V8 accessible method: + +```c++ +NAN_METHOD(Foo::Baz) { + ... + + NanReturnUndefined(); +} +``` + + +### NanReturnNull() + +Use `NanReturnNull` when you want to return `Null` from your V8 accessible method: + +```c++ +NAN_METHOD(Foo::Baz) { + ... + + NanReturnNull(); +} +``` + + +### NanReturnEmptyString() + +Use `NanReturnEmptyString` when you want to return an empty `String` from your V8 accessible method: + +```c++ +NAN_METHOD(Foo::Baz) { + ... + + NanReturnEmptyString(); +} +``` + + +### NanScope() + +The introduction of `isolate` references for many V8 calls in Node 0.11 makes `NanScope()` necessary, use it in place of `HandleScope scope`: + +```c++ +NAN_METHOD(Foo::Bar) { + NanScope(); + + NanReturnValue(String::New("FooBar!")); +} +``` + + +### NanLocker() + +The introduction of `isolate` references for many V8 calls in Node 0.11 makes `NanLocker()` necessary, use it in place of `Locker locker`: + +```c++ +NAN_METHOD(Foo::Bar) { + NanLocker(); + ... + NanUnlocker(); +} +``` + + +### NanUnlocker() + +The introduction of `isolate` references for many V8 calls in Node 0.11 makes `NanUnlocker()` necessary, use it in place of `Unlocker unlocker`: + +```c++ +NAN_METHOD(Foo::Bar) { + NanLocker(); + ... + NanUnlocker(); +} +``` + + +### void * NanGetInternalFieldPointer(Handle<Object>, int) + +Gets a pointer to the internal field with at `index` from a V8 `Object` handle. + +```c++ +Local obj; +... +NanGetInternalFieldPointer(obj, 0); +``` + +### void NanSetInternalFieldPointer(Handle<Object>, int, void *) + +Sets the value of the internal field at `index` on a V8 `Object` handle. + +```c++ +static Persistent dataWrapperCtor; +... +Local wrapper = NanPersistentToLocal(dataWrapperCtor)->NewInstance(); +NanSetInternalFieldPointer(wrapper, 0, this); +``` + + +### Local<Object> NanObjectWrapHandle(Object) + +When you want to fetch the V8 object handle from a native object you've wrapped with Node's `ObjectWrap`, you should use `NanObjectWrapHandle`: + +```c++ +NanObjectWrapHandle(iterator)->Get(String::NewSymbol("end")) +``` + + +### NanMakeWeak(Persistent<T>, parameter, callback) + +Make a persistent reference weak. + + +### String NanSymbol(char *) + +This isn't strictly about compatibility, it's just an easier way to create string symbol objects (i.e. `String::NewSymbol(x)`), for getting and setting object properties, or names of objects. + +```c++ +bool foo = false; +if (obj->Has(NanSymbol("foo"))) + foo = optionsObj->Get(NanSymbol("foo"))->BooleanValue() +``` + + +### Type NanGetPointerSafe(Type *[, Type]) + +A helper for getting values from optional pointers. If the pointer is `NULL`, the function returns the optional default value, which defaults to `0`. Otherwise, the function returns the value the pointer points to. + +```c++ +char *plugh(uint32_t *optional) { + char res[] = "xyzzy"; + uint32_t param = NanGetPointerSafe(optional, 0x1337); + switch (param) { + ... + } + NanSetPointerSafe(optional, 0xDEADBEEF); +} +``` + + +### bool NanSetPointerSafe(Type *, Type) + +A helper for setting optional argument pointers. If the pointer is `NULL`, the function simply return `false`. Otherwise, the value is assigned to the variable the pointer points to. + +```c++ +const char *plugh(size_t *outputsize) { + char res[] = "xyzzy"; + if !(NanSetPointerSafe(outputsize, strlen(res) + 1)) { + ... + } + + ... +} +``` + + +### char* NanFromV8String(Handle<Value>[, enum Nan::Encoding, size_t *, char *, size_t, int]) + +When you want to convert a V8 `String` to a `char*` use `NanFromV8String`. It is possible to define an encoding that defaults to `Nan::UTF8` as well as a pointer to a variable that will be assigned the number of bytes in the returned string. It is also possible to supply a buffer and its length to the function in order not to have a new buffer allocated. The final argument allows optionally setting `String::WriteOptions`, which default to `String::HINT_MANY_WRITES_EXPECTED | String::NO_NULL_TERMINATION`. +Just remember that you'll end up with an object that you'll need to `delete[]` at some point unless you supply your own buffer: + +```c++ +size_t count; +char* name = NanFromV8String(args[0]); +char* decoded = NanFromV8String(args[1], Nan::BASE64, &count, NULL, 0, String::HINT_MANY_WRITES_EXPECTED); +char param_copy[count]; +memcpy(param_copy, decoded, count); +delete[] decoded; +``` + + +### bool NanBooleanOptionValue(Handle<Value>, Handle<String>[, bool]) + +When you have an "options" object that you need to fetch properties from, boolean options can be fetched with this pair. They check first if the object exists (`IsEmpty`), then if the object has the given property (`Has`) then they get and convert/coerce the property to a `bool`. + +The optional last parameter is the *default* value, which is `false` if left off: + +```c++ +// `foo` is false unless the user supplies a truthy value for it +bool foo = NanBooleanOptionValue(optionsObj, NanSymbol("foo")); +// `bar` is true unless the user supplies a falsy value for it +bool bar = NanBooleanOptionValueDefTrue(optionsObj, NanSymbol("bar"), true); +``` + + +### uint32_t NanUInt32OptionValue(Handle<Value>, Handle<String>, uint32_t) + +Similar to `NanBooleanOptionValue`, use `NanUInt32OptionValue` to fetch an integer option from your options object. Can be any kind of JavaScript `Number` and it will be coerced to an unsigned 32-bit integer. + +Requires all 3 arguments as a default is not optional: + +```c++ +uint32_t count = NanUInt32OptionValue(optionsObj, NanSymbol("count"), 1024); +``` + + +### NanThrowError(message), NanThrowTypeError(message), NanThrowRangeError(message), NanThrowError(Local<Value>), NanThrowError(Local<Value>, int) + +For throwing `Error`, `TypeError` and `RangeError` objects. You should `return` this call: + +```c++ +return NanThrowError("you must supply a callback argument"); +``` + +Can also handle any custom object you may want to throw. If used with the error code argument, it will add the supplied error code to the error object as a property called `code`. + + +### Local<Object> NanNewBufferHandle(char *, uint32_t), Local<Object> NanNewBufferHandle(uint32_t) + +The `Buffer` API has changed a little in Node 0.11, this helper provides consistent access to `Buffer` creation: + +```c++ +NanNewBufferHandle((char*)value.data(), value.size()); +``` + +Can also be used to initialize a `Buffer` with just a `size` argument. + +Can also be supplied with a `NAN_WEAK_CALLBACK` and a hint for the garbage collector, when dealing with weak references. + + +### Local<Object> NanBufferUse(char*, uint32_t) + +`Buffer::New(char*, uint32_t)` prior to 0.11 would make a copy of the data. +While it was possible to get around this, it required a shim by passing a +callback. So the new API `Buffer::Use(char*, uint32_t)` was introduced to remove +needing to use this shim. + +`NanBufferUse` uses the `char*` passed as the backing data, and will free the +memory automatically when the weak callback is called. Keep this in mind, as +careless use can lead to "double free or corruption" and other cryptic failures. + + +### bool NanHasInstance(Persistent<FunctionTemplate>&, Handle<Value>) + +Can be used to check the type of an object to determine it is of a particular class you have already defined and have a `Persistent` handle for. + + +### Local<Type> NanPersistentToLocal(Persistent<Type>&) + +Aside from `FunctionCallbackInfo`, the biggest and most painful change to V8 in Node 0.11 is the many restrictions now placed on `Persistent` handles. They are difficult to assign and difficult to fetch the original value out of. + +Use `NanPersistentToLocal` to convert a `Persistent` handle back to a `Local` handle. + +```c++ +Local handle = NanPersistentToLocal(persistentHandle); +``` + + +### Local<Context> NanNewContextHandle([ExtensionConfiguration*, Handle<ObjectTemplate>, Handle<Value>]) +Creates a new `Local` handle. + +```c++ +Local ftmpl = FunctionTemplate::New(); +Local otmpl = ftmpl->InstanceTemplate(); +Local ctx = NanNewContextHandle(NULL, otmpl); +``` + + +### void NanDispose(Persistent<T> &) + +Use `NanDispose` to dispose a `Persistent` handle. + +```c++ +NanDispose(persistentHandle); +``` + + +### NanAssignPersistent(type, handle, object) + +Use `NanAssignPersistent` to assign a non-`Persistent` handle to a `Persistent` one. You can no longer just declare a `Persistent` handle and assign directly to it later, you have to `Reset` it in Node 0.11, so this makes it easier. + +In general it is now better to place anything you want to protect from V8's garbage collector as properties of a generic `Object` and then assign that to a `Persistent`. This works in older versions of Node also if you use `NanAssignPersistent`: + +```c++ +Persistent persistentHandle; + +... + +Local obj = Object::New(); +obj->Set(NanSymbol("key"), keyHandle); // where keyHandle might be a Local +NanAssignPersistent(Object, persistentHandle, obj) +``` + + +### NanInitPersistent(type, name, object) + +User `NanInitPersistent` to declare and initialize a new `Persistent` with the supplied object. The assignment operator for `Persistent` is no longer public in Node 0.11, so this macro makes it easier to declare and initializing a new `Persistent`. See NanAssignPersistent for more information. + +```c++ +Local obj = Object::New(); +obj->Set(NanSymbol("key"), keyHandle); // where keyHandle might be a Local +NanInitPersistent(Object, persistentHandle, obj); +``` + + +### NanCallback + +Because of the difficulties imposed by the changes to `Persistent` handles in V8 in Node 0.11, creating `Persistent` versions of your `Local` handles is annoyingly tricky. `NanCallback` makes it easier by taking your `Local` handle, making it persistent until the `NanCallback` is deleted and even providing a handy `Call()` method to fetch and execute the callback `Function`. + +```c++ +Local callbackHandle = callback = args[0].As(); +NanCallback *callback = new NanCallback(callbackHandle); +// pass `callback` around and it's safe from GC until you: +delete callback; +``` + +You can execute the callback like so: + +```c++ +// no arguments: +callback->Call(0, NULL); + +// an error argument: +Local argv[] = { + Exception::Error(String::New("fail!")) +}; +callback->Call(1, argv); + +// a success argument: +Local argv[] = { + Local::New(Null()), + String::New("w00t!") +}; +callback->Call(2, argv); +``` + +`NanCallback` also has a `Local GetCallback()` method that you can use to fetch a local handle to the underlying callback function if you need it. + + +### NanAsyncWorker + +`NanAsyncWorker` is an abstract class that you can subclass to have much of the annoying async queuing and handling taken care of for you. It can even store arbitrary V8 objects for you and have them persist while the async work is in progress. + +See a rough outline of the implementation: + +```c++ +class NanAsyncWorker { +public: + NanAsyncWorker (NanCallback *callback); + + // Clean up persistent handles and delete the *callback + virtual ~NanAsyncWorker (); + + // Check the `char *errmsg` property and call HandleOKCallback() + // or HandleErrorCallback depending on whether it has been set or not + virtual void WorkComplete (); + + // You must implement this to do some async work. If there is an + // error then allocate `errmsg` to to a message and the callback will + // be passed that string in an Error object + virtual void Execute (); + +protected: + // Set this if there is an error, otherwise it's NULL + const char *errmsg; + + // Save a V8 object in a Persistent handle to protect it from GC + void SavePersistent(const char *key, Local &obj); + + // Fetch a stored V8 object (don't call from within `Execute()`) + Local GetFromPersistent(const char *key); + + // Default implementation calls the callback function with no arguments. + // Override this to return meaningful data + virtual void HandleOKCallback (); + + // Default implementation calls the callback function with an Error object + // wrapping the `errmsg` string + virtual void HandleErrorCallback (); +}; +``` + + +### NanAsyncQueueWorker(NanAsyncWorker *) + +`NanAsyncQueueWorker` will run a `NanAsyncWorker` asynchronously via libuv. Both the *execute* and *after_work* steps are taken care of for you—most of the logic for this is embedded in `NanAsyncWorker`. + +### Contributors + +NAN is only possible due to the excellent work of the following contributors: + + + + + +
Rod VaggGitHub/rvaggTwitter/@rvagg
Benjamin ByholmGitHub/kkoopa
Trevor NorrisGitHub/trevnorrisTwitter/@trevnorris
+ +Licence & copyright +----------------------- + +Copyright (c) 2013 Rod Vagg & NAN contributors (listed above). + +Native Abstractions for Node.js is licensed under an MIT +no-false-attribs license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. diff --git a/node_modules/ws/node_modules/nan/nan.h b/node_modules/ws/node_modules/nan/nan.h new file mode 100644 index 0000000..b3eb02d --- /dev/null +++ b/node_modules/ws/node_modules/nan/nan.h @@ -0,0 +1,884 @@ +/********************************************************************************** + * NAN - Native Abstractions for Node.js + * + * Copyright (c) 2013 NAN contributors: + * - Rod Vagg + * - Benjamin Byholm + * - Trevor Norris + * + * MIT +no-false-attribs License + * + * Version 0.3.2 (current Node unstable: 0.11.6, Node stable: 0.10.17) + * + * ChangeLog: + * * 0.3.2 Aug 30 2013 + * - Fix missing scope declaration in GetFromPersistent() and SaveToPersistent + * in NanAsyncWorker + * + * * 0.3.1 Aug 20 2013 + * - fix "not all control paths return a value" compile warning on some platforms + * + * * 0.3.0 Aug 19 2013 + * - Made NAN work with NPM + * - Lots of fixes to NanFromV8String, pulling in features from new Node core + * - Changed node::encoding to Nan::Encoding in NanFromV8String to unify the API + * - Added optional error number argument for NanThrowError() + * - Added NanInitPersistent() + * - Added NanReturnNull() and NanReturnEmptyString() + * - Added NanLocker and NanUnlocker + * - Added missing scopes + * - Made sure to clear disposed Persistent handles + * - Changed NanAsyncWorker to allocate error messages on the heap + * - Changed NanThrowError(Local) to NanThrowError(Handle) + * - Fixed leak in NanAsyncWorker when errmsg is used + * + * * 0.2.2 Aug 5 2013 + * - Fixed usage of undefined variable with node::BASE64 in NanFromV8String() + * + * * 0.2.1 Aug 5 2013 + * - Fixed 0.8 breakage, node::BUFFER encoding type not available in 0.8 for + * NanFromV8String() + * + * * 0.2.0 Aug 5 2013 + * - Added NAN_PROPERTY_GETTER, NAN_PROPERTY_SETTER, NAN_PROPERTY_ENUMERATOR, + * NAN_PROPERTY_DELETER, NAN_PROPERTY_QUERY + * - Extracted _NAN_METHOD_ARGS, _NAN_GETTER_ARGS, _NAN_SETTER_ARGS, + * _NAN_PROPERTY_GETTER_ARGS, _NAN_PROPERTY_SETTER_ARGS, + * _NAN_PROPERTY_ENUMERATOR_ARGS, _NAN_PROPERTY_DELETER_ARGS, + * _NAN_PROPERTY_QUERY_ARGS + * - Added NanGetInternalFieldPointer, NanSetInternalFieldPointer + * - Added NAN_WEAK_CALLBACK, NAN_WEAK_CALLBACK_OBJECT, + * NAN_WEAK_CALLBACK_DATA, NanMakeWeak + * - Renamed THROW_ERROR to _NAN_THROW_ERROR + * - Added NanNewBufferHandle(char*, size_t, node::smalloc::FreeCallback, void*) + * - Added NanBufferUse(char*, uint32_t) + * - Added NanNewContextHandle(v8::ExtensionConfiguration*, + * v8::Handle, v8::Handle) + * - Fixed broken NanCallback#GetFunction() + * - Added optional encoding and size arguments to NanFromV8String() + * - Added NanGetPointerSafe() and NanSetPointerSafe() + * - Added initial test suite (to be expanded) + * - Allow NanUInt32OptionValue to convert any Number object + * + * * 0.1.0 Jul 21 2013 + * - Added `NAN_GETTER`, `NAN_SETTER` + * - Added `NanThrowError` with single Local argument + * - Added `NanNewBufferHandle` with single uint32_t argument + * - Added `NanHasInstance(Persistent&, Handle)` + * - Added `Local NanCallback#GetFunction()` + * - Added `NanCallback#Call(int, Local[])` + * - Deprecated `NanCallback#Run(int, Local[])` in favour of Call + * + * See https://github.com/rvagg/nan for the latest update to this file + **********************************************************************************/ + +#ifndef NAN_H +#define NAN_H + +#include +#include +#include + +// some generic helpers + +template static inline bool NanSetPointerSafe(T *var, T val) { + if (var) { + *var = val; + return true; + } else { + return false; + } +} + +template static inline T NanGetPointerSafe( + T *var, + T fallback = reinterpret_cast(0)) { + if (var) { + return *var; + } else { + return fallback; + } +} + +#define NanSymbol(value) v8::String::NewSymbol(value) + +static inline bool NanBooleanOptionValue( + v8::Local optionsObj + , v8::Handle opt, bool def) { + + if (def) { + return optionsObj.IsEmpty() + || !optionsObj->Has(opt) + || optionsObj->Get(opt)->BooleanValue(); + } else { + return !optionsObj.IsEmpty() + && optionsObj->Has(opt) + && optionsObj->Get(opt)->BooleanValue(); + } +} + +static inline bool NanBooleanOptionValue( + v8::Local optionsObj + , v8::Handle opt) { + return NanBooleanOptionValue(optionsObj, opt, false); +} + +static inline uint32_t NanUInt32OptionValue( + v8::Local optionsObj + , v8::Handle opt + , uint32_t def) { + + return !optionsObj.IsEmpty() + && optionsObj->Has(opt) + && optionsObj->Get(opt)->IsNumber() + ? optionsObj->Get(opt)->Uint32Value() + : def; +} + +#if (NODE_MODULE_VERSION > 0x000B) +// Node 0.11+ (0.11.3 and below won't compile with these) + +static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); + +# define _NAN_METHOD_ARGS const v8::FunctionCallbackInfo& args +# define NAN_METHOD(name) void name(_NAN_METHOD_ARGS) +# define _NAN_GETTER_ARGS const v8::PropertyCallbackInfo& args +# define NAN_GETTER(name) \ + void name(v8::Local property, _NAN_GETTER_ARGS) +# define _NAN_SETTER_ARGS const v8::PropertyCallbackInfo& args +# define NAN_SETTER(name) \ + void name( \ + v8::Local property \ + , v8::Local value \ + , _NAN_SETTER_ARGS) +# define _NAN_PROPERTY_GETTER_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_GETTER(name) \ + void name(v8::Local property \ + , _NAN_PROPERTY_GETTER_ARGS) +# define _NAN_PROPERTY_SETTER_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_SETTER(name) \ + void name(v8::Local property \ + , v8::Local value \ + , _NAN_PROPERTY_SETTER_ARGS) +# define _NAN_PROPERTY_ENUMERATOR_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_ENUMERATOR(name) \ + void name(_NAN_PROPERTY_ENUMERATOR_ARGS) +# define _NAN_PROPERTY_DELETER_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_DELETER(name) \ + void name( \ + v8::Local property \ + , _NAN_PROPERTY_DELETER_ARGS) +# define _NAN_PROPERTY_QUERY_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_QUERY(name) \ + void name(v8::Local property, _NAN_PROPERTY_QUERY_ARGS) +# define NanGetInternalFieldPointer(object, index) \ + object->GetAlignedPointerFromInternalField(index) +# define NanSetInternalFieldPointer(object, index, value) \ + object->SetAlignedPointerInInternalField(index, value) + +# define NAN_WEAK_CALLBACK(type, name) \ + void name( \ + v8::Isolate* isolate, \ + v8::Persistent* object, \ + type data) +# define NAN_WEAK_CALLBACK_OBJECT (*object) +# define NAN_WEAK_CALLBACK_DATA(type) ((type) data) + +# define NanScope() v8::HandleScope scope(nan_isolate) +# define NanLocker() v8::Locker locker(nan_isolate) +# define NanUnlocker() v8::Unlocker unlocker(nan_isolate) +# define NanReturnValue(value) return args.GetReturnValue().Set(value) +# define NanReturnUndefined() return +# define NanReturnNull() return args.GetReturnValue().SetNull() +# define NanReturnEmptyString() return args.GetReturnValue().SetEmptyString() +# define NanAssignPersistent(type, handle, obj) handle.Reset(nan_isolate, obj) +# define NanInitPersistent(type, name, obj) \ + v8::Persistent name(nan_isolate, obj) +# define NanObjectWrapHandle(obj) obj->handle() +# define NanMakeWeak(handle, parameter, callback) \ + handle.MakeWeak(nan_isolate, parameter, callback) + +# define _NAN_THROW_ERROR(fun, errmsg) \ + do { \ + NanScope(); \ + v8::ThrowException(fun(v8::String::New(errmsg))); \ + } while (0); + + inline static void NanThrowError(const char* errmsg) { + _NAN_THROW_ERROR(v8::Exception::Error, errmsg); + } + + inline static void NanThrowError(v8::Handle error) { + NanScope(); + v8::ThrowException(error); + } + + inline static void NanThrowError(const char *msg, const int errorNumber) { + v8::Local err = v8::Exception::Error(v8::String::New(msg)); + v8::Local obj = err.As(); + obj->Set(v8::String::New("code"), v8::Int32::New(errorNumber)); + NanThrowError(err); + } + + inline static void NanThrowTypeError(const char* errmsg) { + _NAN_THROW_ERROR(v8::Exception::TypeError, errmsg); + } + + inline static void NanThrowRangeError(const char* errmsg) { + _NAN_THROW_ERROR(v8::Exception::RangeError, errmsg); + } + + template static inline void NanDispose(v8::Persistent &handle) { + handle.Dispose(nan_isolate); + handle.Clear(); + } + + static inline v8::Local NanNewBufferHandle ( + char *data, + size_t length, + node::smalloc::FreeCallback callback, + void *hint) { + return node::Buffer::New(data, length, callback, hint); + } + + static inline v8::Local NanNewBufferHandle ( + char *data, uint32_t size) { + return node::Buffer::New(data, size); + } + + static inline v8::Local NanNewBufferHandle (uint32_t size) { + return node::Buffer::New(size); + } + + static inline v8::Local NanBufferUse(char* data, uint32_t size) { + return node::Buffer::Use(data, size); + } + + template + inline v8::Local NanPersistentToLocal( + const v8::Persistent& persistent) { + if (persistent.IsWeak()) { + return v8::Local::New(nan_isolate, persistent); + } else { + return *reinterpret_cast*>( + const_cast*>(&persistent)); + } + } + + inline bool NanHasInstance( + v8::Persistent& function_template + , v8::Handle value) { + return NanPersistentToLocal(function_template)->HasInstance(value); + } + + static inline v8::Local NanNewContextHandle( + v8::ExtensionConfiguration* extensions = NULL, + v8::Handle tmpl = v8::Handle(), + v8::Handle obj = v8::Handle()) { + return v8::Local::New(nan_isolate, v8::Context::New( + nan_isolate, extensions, tmpl, obj)); + } + +#else +// Node 0.8 and 0.10 + +# define _NAN_METHOD_ARGS const v8::Arguments& args +# define NAN_METHOD(name) v8::Handle name(_NAN_METHOD_ARGS) +# define _NAN_GETTER_ARGS const v8::AccessorInfo &args +# define NAN_GETTER(name) \ + v8::Handle name(v8::Local property, _NAN_GETTER_ARGS) +# define _NAN_SETTER_ARGS const v8::AccessorInfo &args +# define NAN_SETTER(name) \ + void name( \ + v8::Local property \ + , v8::Local value \ + , _NAN_SETTER_ARGS) +# define _NAN_PROPERTY_GETTER_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_GETTER(name) \ + v8::Handle name(v8::Local property \ + , _NAN_PROPERTY_GETTER_ARGS) +# define _NAN_PROPERTY_SETTER_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_SETTER(name) \ + v8::Handle name(v8::Local property \ + , v8::Local value \ + , _NAN_PROPERTY_SETTER_ARGS) +# define _NAN_PROPERTY_ENUMERATOR_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_ENUMERATOR(name) \ + v8::Handle name(_NAN_PROPERTY_ENUMERATOR_ARGS) +# define _NAN_PROPERTY_DELETER_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_DELETER(name) \ + v8::Handle name( \ + v8::Local property \ + , _NAN_PROPERTY_DELETER_ARGS) +# define _NAN_PROPERTY_QUERY_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_QUERY(name) \ + v8::Handle name( \ + v8::Local property \ + , _NAN_PROPERTY_QUERY_ARGS) + +# define NanGetInternalFieldPointer(object, index) \ + object->GetPointerFromInternalField(index) +# define NanSetInternalFieldPointer(object, index, value) \ + object->SetPointerInInternalField(index, value) +# define NAN_WEAK_CALLBACK(type, name) void name( \ + v8::Persistent object, \ + void *data) +# define NAN_WEAK_CALLBACK_OBJECT object +# define NAN_WEAK_CALLBACK_DATA(type) ((type) data) + +# define NanScope() v8::HandleScope scope +# define NanLocker() v8::Locker locker +# define NanUnlocker() v8::Unlocker unlocker +# define NanReturnValue(value) return scope.Close(value) +# define NanReturnUndefined() return v8::Undefined() +# define NanReturnNull() return v8::Null() +# define NanReturnEmptyString() return v8::String::Empty() +# define NanInitPersistent(type, name, obj) \ + v8::Persistent name = v8::Persistent::New(obj) +# define NanAssignPersistent(type, handle, obj) \ + handle = v8::Persistent::New(obj) +# define NanObjectWrapHandle(obj) obj->handle_ +# define NanMakeWeak(handle, parameters, callback) \ + handle.MakeWeak(parameters, callback) + +# define _NAN_THROW_ERROR(fun, errmsg) \ + do { \ + NanScope(); \ + return v8::ThrowException(fun(v8::String::New(errmsg))); \ + } while (0); + + inline static v8::Handle NanThrowError(const char* errmsg) { + _NAN_THROW_ERROR(v8::Exception::Error, errmsg); + } + + inline static v8::Handle NanThrowError( + v8::Handle error) { + NanScope(); + return v8::ThrowException(error); + } + + inline static v8::Handle NanThrowError( + const char *msg, + const int errorNumber) { + v8::Local err = v8::Exception::Error(v8::String::New(msg)); + v8::Local obj = err.As(); + obj->Set(v8::String::New("code"), v8::Int32::New(errorNumber)); + return NanThrowError(err); + } + + inline static v8::Handle NanThrowTypeError(const char* errmsg) { + _NAN_THROW_ERROR(v8::Exception::TypeError, errmsg); + } + + inline static v8::Handle NanThrowRangeError(const char* errmsg) { + _NAN_THROW_ERROR(v8::Exception::RangeError, errmsg); + } + + template static inline void NanDispose(v8::Persistent &handle) { + handle.Dispose(); + handle.Clear(); + } + + static inline v8::Local NanNewBufferHandle ( + char *data, + size_t length, + node::Buffer::free_callback callback, + void *hint) { + return v8::Local::New( + node::Buffer::New(data, length, callback, hint)->handle_); + } + + static inline v8::Local NanNewBufferHandle ( + char *data, uint32_t size) { + return v8::Local::New(node::Buffer::New(data, size)->handle_); + } + + static inline v8::Local NanNewBufferHandle (uint32_t size) { + return v8::Local::New(node::Buffer::New(size)->handle_); + } + + static inline void FreeData(char *data, void *hint) { + delete[] data; + } + + static inline v8::Local NanBufferUse(char* data, uint32_t size) { + return v8::Local::New( + node::Buffer::New(data, size, FreeData, NULL)->handle_); + } + + template + inline v8::Local NanPersistentToLocal( + const v8::Persistent& persistent) { + if (persistent.IsWeak()) { + return v8::Local::New(persistent); + } else { + return *reinterpret_cast*>( + const_cast*>(&persistent)); + } + } + + inline bool NanHasInstance( + v8::Persistent& function_template + , v8::Handle value) { + return function_template->HasInstance(value); + } + + static inline v8::Local NanNewContextHandle( + v8::ExtensionConfiguration* extensions = NULL + , v8::Handle tmpl = + v8::Handle() + , v8::Handle obj = v8::Handle() + ) { + v8::Persistent ctx = + v8::Context::New(extensions, tmpl, obj); + v8::Local lctx = v8::Local::New(ctx); + ctx.Dispose(); + return lctx; + } + +#endif // node version + +class NanCallback { + public: + NanCallback(const v8::Local &fn) { + NanScope(); + v8::Local obj = v8::Object::New(); + obj->Set(NanSymbol("callback"), fn); + NanAssignPersistent(v8::Object, handle, obj); + } + + ~NanCallback() { + if (handle.IsEmpty()) return; + handle.Dispose(); + handle.Clear(); + } + + inline v8::Local GetFunction () { + return NanPersistentToLocal(handle)->Get(NanSymbol("callback")) + .As(); + } + + // deprecated + void Run(int argc, v8::Local argv[]) { + Call(argc, argv); + } + + void Call(int argc, v8::Local argv[]) { + NanScope(); + + v8::Local callback = NanPersistentToLocal(handle)-> + Get(NanSymbol("callback")).As(); + v8::TryCatch try_catch; + callback->Call(v8::Context::GetCurrent()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } + + private: + v8::Persistent handle; +}; + +/* abstract */ class NanAsyncWorker { +public: + NanAsyncWorker (NanCallback *callback) : callback(callback) { + request.data = this; + errmsg = NULL; + } + + virtual ~NanAsyncWorker () { + NanScope(); + + if (!persistentHandle.IsEmpty()) + NanDispose(persistentHandle); + if (callback) + delete callback; + if (errmsg) + delete errmsg; + } + + virtual void WorkComplete () { + NanScope(); + + if (errmsg == NULL) + HandleOKCallback(); + else + HandleErrorCallback(); + delete callback; + callback = NULL; + } + + virtual void Execute () =0; + + uv_work_t request; + +protected: + v8::Persistent persistentHandle; + NanCallback *callback; + const char *errmsg; + + void SavePersistent(const char *key, v8::Local &obj) { + NanScope(); + + v8::Local handle = NanPersistentToLocal(persistentHandle); + handle->Set(NanSymbol(key), obj); + } + + v8::Local GetFromPersistent(const char *key) { + NanScope(); + + v8::Local handle = NanPersistentToLocal(persistentHandle); + return handle->Get(NanSymbol(key)).As(); + } + + virtual void HandleOKCallback () { + NanScope(); + + callback->Call(0, NULL); + }; + + virtual void HandleErrorCallback () { + NanScope(); + + v8::Local argv[] = { + v8::Exception::Error(v8::String::New(errmsg)) + }; + callback->Call(1, argv); + } +}; + +inline void NanAsyncExecute (uv_work_t* req) { + NanAsyncWorker *worker = static_cast(req->data); + worker->Execute(); +} + +inline void NanAsyncExecuteComplete (uv_work_t* req) { + NanAsyncWorker* worker = static_cast(req->data); + worker->WorkComplete(); + delete worker; +} + +inline void NanAsyncQueueWorker (NanAsyncWorker* worker) { + uv_queue_work( + uv_default_loop() + , &worker->request + , NanAsyncExecute + , (uv_after_work_cb)NanAsyncExecuteComplete + ); +} + +//// Base 64 //// + +#define _nan_base64_encoded_size(size) ((size + 2 - ((size + 2) % 3)) / 3 * 4) + + +// Doesn't check for padding at the end. Can be 1-2 bytes over. +static inline size_t _nan_base64_decoded_size_fast(size_t size) { + size_t remainder = size % 4; + + size = (size / 4) * 3; + if (remainder) { + if (size == 0 && remainder == 1) { + // special case: 1-byte input cannot be decoded + size = 0; + } else { + // non-padded input, add 1 or 2 extra bytes + size += 1 + (remainder == 3); + } + } + + return size; +} + +template +static size_t _nan_base64_decoded_size(const TypeName* src, size_t size) { + if (size == 0) + return 0; + + if (src[size - 1] == '=') + size--; + if (size > 0 && src[size - 1] == '=') + size--; + + return _nan_base64_decoded_size_fast(size); +} + + +// supports regular and URL-safe base64 +static const int _nan_unbase64_table[] = + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + +#define _nan_unbase64(x) _nan_unbase64_table[(uint8_t)(x)] + + +template +static size_t _nan_base64_decode(char* buf, + size_t len, + const TypeName* src, + const size_t srcLen) { + char a, b, c, d; + char* dst = buf; + char* dstEnd = buf + len; + const TypeName* srcEnd = src + srcLen; + + while (src < srcEnd && dst < dstEnd) { + int remaining = srcEnd - src; + + while (_nan_unbase64(*src) < 0 && src < srcEnd) src++, remaining--; + if (remaining == 0 || *src == '=') break; + a = _nan_unbase64(*src++); + + while (_nan_unbase64(*src) < 0 && src < srcEnd) src++, remaining--; + if (remaining <= 1 || *src == '=') break; + b = _nan_unbase64(*src++); + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (dst == dstEnd) break; + + while (_nan_unbase64(*src) < 0 && src < srcEnd) src++, remaining--; + if (remaining <= 2 || *src == '=') break; + c = _nan_unbase64(*src++); + + *dst++ = ((b & 0x0F) << 4) | ((c & 0x3C) >> 2); + if (dst == dstEnd) break; + + while (_nan_unbase64(*src) < 0 && src < srcEnd) src++, remaining--; + if (remaining <= 3 || *src == '=') break; + d = _nan_unbase64(*src++); + + *dst++ = ((c & 0x03) << 6) | (d & 0x3F); + } + + return dst - buf; +} + +//// HEX //// + +template +unsigned _nan_hex2bin(TypeName c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + return static_cast(-1); +} + + +template +static size_t _nan_hex_decode(char* buf, + size_t len, + const TypeName* src, + const size_t srcLen) { + size_t i; + for (i = 0; i < len && i * 2 + 1 < srcLen; ++i) { + unsigned a = _nan_hex2bin(src[i * 2 + 0]); + unsigned b = _nan_hex2bin(src[i * 2 + 1]); + if (!~a || !~b) return i; + buf[i] = a * 16 + b; + } + + return i; +} + +static bool _NanGetExternalParts( + v8::Handle val + , const char** data + , size_t* len) { + + if (node::Buffer::HasInstance(val)) { + *data = node::Buffer::Data(val.As()); + *len = node::Buffer::Length(val.As()); + return true; + + } + + assert(val->IsString()); + v8::Local str = v8::Local::New(val.As()); + + if (str->IsExternalAscii()) { + const v8::String::ExternalAsciiStringResource* ext; + ext = str->GetExternalAsciiStringResource(); + *data = ext->data(); + *len = ext->length(); + return true; + + } else if (str->IsExternal()) { + const v8::String::ExternalStringResource* ext; + ext = str->GetExternalStringResource(); + *data = reinterpret_cast(ext->data()); + *len = ext->length(); + return true; + } + + return false; +} + +namespace Nan { + enum Encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER}; +} + +static inline char* NanFromV8String( + v8::Handle from + , enum Nan::Encoding encoding = Nan::UTF8 + , size_t *datalen = NULL + , char *buf = NULL + , size_t buflen = 0 + , int flags = v8::String::NO_NULL_TERMINATION + | v8::String::HINT_MANY_WRITES_EXPECTED) { + + NanScope(); + + size_t sz_; + size_t term_len = !(flags & v8::String::NO_NULL_TERMINATION); + char *data = NULL; + size_t len; + bool is_extern = _NanGetExternalParts( + from + , const_cast(&data) + , &len); + + if (is_extern && !term_len) { + NanSetPointerSafe(datalen, len); + return data; + } + + v8::Local toStr = from->ToString(); + + char *to = buf; + + v8::String::AsciiValue value(toStr); + switch(encoding) { + case Nan::ASCII: +#if NODE_MODULE_VERSION < 0x0C + sz_ = toStr->Length(); + if (to == NULL) { + to = new char[sz_ + term_len]; + } else { + assert(buflen >= sz_ + term_len && "too small buffer"); + } + NanSetPointerSafe( + datalen + , toStr->WriteAscii(to, 0, sz_ + term_len, flags)); + return to; +#endif + case Nan::BINARY: + case Nan::BUFFER: + sz_ = toStr->Length(); + if (to == NULL) { + to = new char[sz_ + term_len]; + } else { + assert(buflen >= sz_ + term_len && "too small buffer"); + } +#if NODE_MODULE_VERSION < 0x0C + // TODO(isaacs): THIS IS AWFUL!!! + // AGREE(kkoopa) + { + uint16_t* twobytebuf = new uint16_t[sz_ + term_len]; + + size_t len = toStr->Write(twobytebuf, 0, sz_ + term_len, flags); + + for (size_t i = 0; i < sz_ + term_len && i < len + term_len; i++) { + unsigned char *b = reinterpret_cast(&twobytebuf[i]); + to[i] = *b; + } + + NanSetPointerSafe(datalen, len); + + delete[] twobytebuf; + return to; + } +#else + NanSetPointerSafe( + datalen, + toStr->WriteOneByte( + reinterpret_cast(to) + , 0 + , sz_ + term_len + , flags)); + return to; +#endif + case Nan::UTF8: + sz_ = toStr->Utf8Length(); + if (to == NULL) { + to = new char[sz_ + term_len]; + } else { + assert(buflen >= sz_ + term_len && "too small buffer"); + } + NanSetPointerSafe( + datalen + , toStr->WriteUtf8(to, sz_ + term_len, NULL, flags) - term_len); + return to; + case Nan::BASE64: + sz_ = _nan_base64_decoded_size(*value, toStr->Length()); + if (to == NULL) { + to = new char[sz_ + term_len]; + } else { + assert(buflen >= sz_ + term_len); + } + NanSetPointerSafe( + datalen + , _nan_base64_decode(to, sz_, *value, value.length())); + if (term_len) { + to[sz_] = '\0'; + } + return to; + case Nan::UCS2: + { + sz_ = toStr->Length(); + if (to == NULL) { + to = new char[(sz_ + term_len) * 2]; + } else { + assert(buflen >= (sz_ + term_len) * 2 && "too small buffer"); + } + + int bc = 2 * toStr->Write( + reinterpret_cast(to) + , 0 + , sz_ + term_len + , flags); + NanSetPointerSafe(datalen, bc); + return to; + } + case Nan::HEX: + sz_ = toStr->Length(); + assert(!(sz_ & 1) && "bad hex data"); + if (to == NULL) { + to = new char[sz_ / 2 + term_len]; + } else { + assert(buflen >= sz_ / 2 + term_len && "too small buffer"); + } + + NanSetPointerSafe( + datalen + , _nan_hex_decode(to, sz_ / 2, *value, value.length())); + if (term_len) { + to[sz_ / 2] = '\0'; + } + return to; + default: + assert(0 && "unknown encoding"); + } + return to; +} + +#endif diff --git a/node_modules/ws/node_modules/nan/package.json b/node_modules/ws/node_modules/nan/package.json new file mode 100644 index 0000000..af7bf04 --- /dev/null +++ b/node_modules/ws/node_modules/nan/package.json @@ -0,0 +1,34 @@ +{ + "name": "nan", + "version": "0.3.2", + "description": "Native Abstractions for Node.js: C++ header for Node 0.8->0.12 compatibility", + "main": ".index.js", + "repository": { + "type": "git", + "url": "git://github.com/rvagg/nan.git" + }, + "contributors": [ + { + "name": "Rod Vagg", + "email": "r@va.gg", + "url": "https://github.com/rvagg" + }, + { + "name": "Benjamin Byholm", + "email": "bbyholm@abo.fi", + "url": "https://github.com/kkoopa/" + }, + { + "name": "Trevor Norris", + "email": "trev.norris@gmail.com", + "url": "https://github.com/trevnorris" + } + ], + "license": "MIT", + "readme": "Native Abstractions for Node.js\n===============================\n\n**A header file filled with macro and utility goodness for making addon development for Node.js easier across versions 0.8, 0.10 and 0.11, and eventually 0.12.**\n\n***Current version: 0.3.2*** *(See [nan.h](https://github.com/rvagg/nan/blob/master/nan.h) for changelog)*\n\n[![NPM](https://nodei.co/npm/nan.png?downloads=true&stars=true)](https://nodei.co/npm/nan/) [![NPM](https://nodei.co/npm-dl/nan.png?months=6)](https://nodei.co/npm/nan/)\n\nThanks to the crazy changes in V8 (and some in Node core), keeping native addons compiling happily across versions, particularly 0.10 to 0.11/0.12, is a minor nightmare. The goal of this project is to store all logic necessary to develop native Node.js addons without having to inspect `NODE_MODULE_VERSION` and get yourself into a macro-tangle.\n\nThis project also contains some helper utilities that make addon development a bit more pleasant.\n\n * **[Usage](#usage)**\n * **[Example](#example)**\n * **[API](#api)**\n\n\n## Usage\n\nSimply add **NAN** as a dependency in the *package.json* of your Node addon:\n\n```js\n\"dependencies\": {\n ...\n \"nan\" : \"~0.3.1\"\n ...\n}\n```\n\nPull in the path to **NAN** in your *binding.gyp* so that you can use `#include \"nan.h\"` in your *.cpp*:\n\n```js\n\"include_dirs\" : [\n ...\n \"` when compiling your addon.\n\n\n## Example\n\nSee **[LevelDOWN](https://github.com/rvagg/node-leveldown/pull/48)** for a full example of **NAN** in use.\n\nFor a simpler example, see the **[async pi estimation example](https://github.com/rvagg/nan/tree/master/examples/async_pi_estimate)** in the examples directory for full code and an explanation of what this Monte Carlo Pi estimation example does. Below are just some parts of the full example that illustrate the use of **NAN**.\n\nCompare to the current 0.10 version of this example, found in the [node-addon-examples](https://github.com/rvagg/node-addon-examples/tree/master/9_async_work) repository and also a 0.11 version of the same found [here](https://github.com/kkoopa/node-addon-examples/tree/5c01f58fc993377a567812597e54a83af69686d7/9_async_work).\n\nNote that there is no embedded version sniffing going on here and also the async work is made much simpler, see below for details on the `NanAsyncWorker` class.\n\n```c++\n// addon.cc\n#include \n#include \"nan.h\"\n// ...\n\nusing namespace v8;\n\nvoid InitAll(Handle exports) {\n exports->Set(NanSymbol(\"calculateSync\"),\n FunctionTemplate::New(CalculateSync)->GetFunction());\n\n exports->Set(NanSymbol(\"calculateAsync\"),\n FunctionTemplate::New(CalculateAsync)->GetFunction());\n}\n\nNODE_MODULE(addon, InitAll)\n```\n\n```c++\n// sync.h\n#include \n#include \"nan.h\"\n\nNAN_METHOD(CalculateSync);\n```\n\n```c++\n// sync.cc\n#include \n#include \"nan.h\"\n#include \"sync.h\"\n// ...\n\nusing namespace v8;\n\n// Simple synchronous access to the `Estimate()` function\nNAN_METHOD(CalculateSync) {\n NanScope();\n\n // expect a number as the first argument\n int points = args[0]->Uint32Value();\n double est = Estimate(points);\n\n NanReturnValue(Number::New(est));\n}\n```\n\n```c++\n// async.cc\n#include \n#include \"nan.h\"\n#include \"async.h\"\n\n// ...\n\nusing namespace v8;\n\nclass PiWorker : public NanAsyncWorker {\n public:\n PiWorker(NanCallback *callback, int points)\n : NanAsyncWorker(callback), points(points) {}\n ~PiWorker() {}\n\n // Executed inside the worker-thread.\n // It is not safe to access V8, or V8 data structures\n // here, so everything we need for input and output\n // should go on `this`.\n void Execute () {\n estimate = Estimate(points);\n }\n\n // Executed when the async work is complete\n // this function will be run inside the main event loop\n // so it is safe to use V8 again\n void HandleOKCallback () {\n NanScope();\n\n Local argv[] = {\n Local::New(Null())\n , Number::New(estimate)\n };\n\n callback->Call(2, argv);\n };\n\n private:\n int points;\n double estimate;\n};\n\n// Asynchronous access to the `Estimate()` function\nNAN_METHOD(CalculateAsync) {\n NanScope();\n\n int points = args[0]->Uint32Value();\n NanCallback *callback = new NanCallback(args[1].As());\n\n NanAsyncQueueWorker(new PiWorker(callback, points));\n NanReturnUndefined();\n}\n```\n\n\n## API\n\n * NAN_METHOD\n * NAN_GETTER\n * NAN_SETTER\n * NAN_PROPERTY_GETTER\n * NAN_PROPERTY_SETTER\n * NAN_PROPERTY_ENUMERATOR\n * NAN_PROPERTY_DELETER\n * NAN_PROPERTY_QUERY\n * NAN_WEAK_CALLBACK\n * NanReturnValue\n * NanReturnUndefined\n * NanReturnNull\n * NanReturnEmptyString\n * NanScope\n * NanLocker\n * NanUnlocker\n * NanGetInternalFieldPointer\n * NanSetInternalFieldPointer\n * NanObjectWrapHandle\n * NanMakeWeak\n * NanSymbol\n * NanGetPointerSafe\n * NanSetPointerSafe\n * NanFromV8String\n * NanBooleanOptionValue\n * NanUInt32OptionValue\n * NanThrowError, NanThrowTypeError, NanThrowRangeError, NanThrowError(Handle), NanThrowError(Handle, int)\n * NanNewBufferHandle(char *, size_t, FreeCallback, void *), NanNewBufferHandle(char *, uint32_t), NanNewBufferHandle(uint32_t)\n * NanBufferUse(char *, uint32_t)\n * NanNewContextHandle\n * NanHasInstance\n * NanPersistentToLocal\n * NanDispose\n * NanAssignPersistent\n * NanInitPersistent\n * NanCallback\n * NanAsyncWorker\n * NanAsyncQueueWorker\n\n\n### NAN_METHOD(methodname)\n\nUse `NAN_METHOD` to define your V8 accessible methods:\n\n```c++\n// .h:\nclass Foo : public node::ObjectWrap {\n ...\n\n static NAN_METHOD(Bar);\n static NAN_METHOD(Baz);\n}\n\n\n// .cc:\nNAN_METHOD(Foo::Bar) {\n ...\n}\n\nNAN_METHOD(Foo::Baz) {\n ...\n}\n```\n\nThe reason for this macro is because of the method signature change in 0.11:\n\n```c++\n// 0.10 and below:\nHandle name(const Arguments& args)\n\n// 0.11 and above\nvoid name(const FunctionCallbackInfo& args)\n```\n\nThe introduction of `FunctionCallbackInfo` brings additional complications:\n\n\n### NAN_GETTER(methodname)\n\nUse `NAN_GETTER` to declare your V8 accessible getters. You get a `Local` `property` and an appropriately typed `args` object that can act like the `args` argument to a `NAN_METHOD` call.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_GETTER`.\n\n\n### NAN_SETTER(methodname)\n\nUse `NAN_SETTER` to declare your V8 accessible setters. Same as `NAN_GETTER` but you also get a `Local` `value` object to work with.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_SETTER`.\n\n\n### NAN_PROPERTY_GETTER(cbname)\nUse `NAN_PROPERTY_GETTER` to declare your V8 accessible property getters. You get a `Local` `property` and an appropriately typed `args` object that can act similar to the `args` argument to a `NAN_METHOD` call.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_GETTER`.\n\n\n### NAN_PROPERTY_SETTER(cbname)\nUse `NAN_PROPERTY_SETTER` to declare your V8 accessible property setters. Same as `NAN_PROPERTY_GETTER` but you also get a `Local` `value` object to work with.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_SETTER`.\n\n\n### NAN_PROPERTY_ENUMERATOR(cbname)\nUse `NAN_PROPERTY_ENUMERATOR` to declare your V8 accessible property enumerators. You get an appropriately typed `args` object like the `args` argument to a `NAN_PROPERTY_GETTER` call.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_ENUMERATOR`.\n\n\n### NAN_PROPERTY_DELETER(cbname)\nUse `NAN_PROPERTY_DELETER` to declare your V8 accessible property deleters. Same as `NAN_PROPERTY_GETTER`.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_DELETER`.\n\n\n### NAN_PROPERTY_QUERY(cbname)\nUse `NAN_PROPERTY_QUERY` to declare your V8 accessible property queries. Same as `NAN_PROPERTY_GETTER`.\n\nYou can use `NanReturnNull()`, `NanReturnEmptyString()`, `NanReturnUndefined()` and `NanReturnValue()` in a `NAN_PROPERTY_QUERY`.\n\n\n### NAN_WEAK_CALLBACK(type, cbname)\n\nUse `NAN_WEAK_CALLBACK` to declare your V8 WeakReference callbacks. There is an object argument accessible through `NAN_WEAK_CALLBACK_OBJECT`. The `type` argument gives the type of the `data` argument, accessible through `NAN_WEAK_CALLBACK_DATA(type)`.\n\n```c++\nstatic NAN_WEAK_CALLBACK(BufferReference*, WeakCheck) {\n if (NAN_WEAK_CALLBACK_DATA(BufferReference*)->noLongerNeeded_) {\n delete NAN_WEAK_CALLBACK_DATA(BufferReference*);\n } else {\n // Still in use, revive, prevent GC\n NanMakeWeak(NAN_WEAK_CALLBACK_OBJECT, NAN_WEAK_CALLBACK_DATA(BufferReference*), &WeakCheck);\n }\n}\n\n```\n\n### NanReturnValue(Handle<Value>)\n\nUse `NanReturnValue` when you want to return a value from your V8 accessible method:\n\n```c++\nNAN_METHOD(Foo::Bar) {\n ...\n\n NanReturnValue(String::New(\"FooBar!\"));\n}\n```\n\nNo `return` statement required.\n\n\n### NanReturnUndefined()\n\nUse `NanReturnUndefined` when you don't want to return anything from your V8 accessible method:\n\n```c++\nNAN_METHOD(Foo::Baz) {\n ...\n\n NanReturnUndefined();\n}\n```\n\n\n### NanReturnNull()\n\nUse `NanReturnNull` when you want to return `Null` from your V8 accessible method:\n\n```c++\nNAN_METHOD(Foo::Baz) {\n ...\n\n NanReturnNull();\n}\n```\n\n\n### NanReturnEmptyString()\n\nUse `NanReturnEmptyString` when you want to return an empty `String` from your V8 accessible method:\n\n```c++\nNAN_METHOD(Foo::Baz) {\n ...\n\n NanReturnEmptyString();\n}\n```\n\n\n### NanScope()\n\nThe introduction of `isolate` references for many V8 calls in Node 0.11 makes `NanScope()` necessary, use it in place of `HandleScope scope`:\n\n```c++\nNAN_METHOD(Foo::Bar) {\n NanScope();\n\n NanReturnValue(String::New(\"FooBar!\"));\n}\n```\n\n\n### NanLocker()\n\nThe introduction of `isolate` references for many V8 calls in Node 0.11 makes `NanLocker()` necessary, use it in place of `Locker locker`:\n\n```c++\nNAN_METHOD(Foo::Bar) {\n NanLocker();\n ...\n NanUnlocker();\n}\n```\n\n\n### NanUnlocker()\n\nThe introduction of `isolate` references for many V8 calls in Node 0.11 makes `NanUnlocker()` necessary, use it in place of `Unlocker unlocker`:\n\n```c++\nNAN_METHOD(Foo::Bar) {\n NanLocker();\n ...\n NanUnlocker();\n}\n```\n\n\n### void * NanGetInternalFieldPointer(Handle<Object>, int)\n\nGets a pointer to the internal field with at `index` from a V8 `Object` handle.\n\n```c++\nLocal obj;\n...\nNanGetInternalFieldPointer(obj, 0);\n```\n\n### void NanSetInternalFieldPointer(Handle<Object>, int, void *)\n\nSets the value of the internal field at `index` on a V8 `Object` handle.\n\n```c++\nstatic Persistent dataWrapperCtor;\n...\nLocal wrapper = NanPersistentToLocal(dataWrapperCtor)->NewInstance();\nNanSetInternalFieldPointer(wrapper, 0, this);\n```\n\n\n### Local<Object> NanObjectWrapHandle(Object)\n\nWhen you want to fetch the V8 object handle from a native object you've wrapped with Node's `ObjectWrap`, you should use `NanObjectWrapHandle`:\n\n```c++\nNanObjectWrapHandle(iterator)->Get(String::NewSymbol(\"end\"))\n```\n\n\n### NanMakeWeak(Persistent<T>, parameter, callback)\n\nMake a persistent reference weak.\n\n\n### String NanSymbol(char *)\n\nThis isn't strictly about compatibility, it's just an easier way to create string symbol objects (i.e. `String::NewSymbol(x)`), for getting and setting object properties, or names of objects.\n\n```c++\nbool foo = false;\nif (obj->Has(NanSymbol(\"foo\")))\n foo = optionsObj->Get(NanSymbol(\"foo\"))->BooleanValue()\n```\n\n\n### Type NanGetPointerSafe(Type *[, Type])\n\nA helper for getting values from optional pointers. If the pointer is `NULL`, the function returns the optional default value, which defaults to `0`. Otherwise, the function returns the value the pointer points to.\n\n```c++\nchar *plugh(uint32_t *optional) {\n char res[] = \"xyzzy\";\n uint32_t param = NanGetPointerSafe(optional, 0x1337);\n switch (param) {\n ...\n }\n NanSetPointerSafe(optional, 0xDEADBEEF);\n} \n```\n\n\n### bool NanSetPointerSafe(Type *, Type)\n\nA helper for setting optional argument pointers. If the pointer is `NULL`, the function simply return `false`. Otherwise, the value is assigned to the variable the pointer points to.\n\n```c++\nconst char *plugh(size_t *outputsize) {\n char res[] = \"xyzzy\";\n if !(NanSetPointerSafe(outputsize, strlen(res) + 1)) {\n ...\n }\n\n ...\n}\n```\n\n\n### char* NanFromV8String(Handle<Value>[, enum Nan::Encoding, size_t *, char *, size_t, int])\n\nWhen you want to convert a V8 `String` to a `char*` use `NanFromV8String`. It is possible to define an encoding that defaults to `Nan::UTF8` as well as a pointer to a variable that will be assigned the number of bytes in the returned string. It is also possible to supply a buffer and its length to the function in order not to have a new buffer allocated. The final argument allows optionally setting `String::WriteOptions`, which default to `String::HINT_MANY_WRITES_EXPECTED | String::NO_NULL_TERMINATION`.\nJust remember that you'll end up with an object that you'll need to `delete[]` at some point unless you supply your own buffer:\n\n```c++\nsize_t count;\nchar* name = NanFromV8String(args[0]);\nchar* decoded = NanFromV8String(args[1], Nan::BASE64, &count, NULL, 0, String::HINT_MANY_WRITES_EXPECTED);\nchar param_copy[count];\nmemcpy(param_copy, decoded, count);\ndelete[] decoded;\n```\n\n\n### bool NanBooleanOptionValue(Handle<Value>, Handle<String>[, bool])\n\nWhen you have an \"options\" object that you need to fetch properties from, boolean options can be fetched with this pair. They check first if the object exists (`IsEmpty`), then if the object has the given property (`Has`) then they get and convert/coerce the property to a `bool`.\n\nThe optional last parameter is the *default* value, which is `false` if left off:\n\n```c++\n// `foo` is false unless the user supplies a truthy value for it\nbool foo = NanBooleanOptionValue(optionsObj, NanSymbol(\"foo\"));\n// `bar` is true unless the user supplies a falsy value for it\nbool bar = NanBooleanOptionValueDefTrue(optionsObj, NanSymbol(\"bar\"), true);\n```\n\n\n### uint32_t NanUInt32OptionValue(Handle<Value>, Handle<String>, uint32_t)\n\nSimilar to `NanBooleanOptionValue`, use `NanUInt32OptionValue` to fetch an integer option from your options object. Can be any kind of JavaScript `Number` and it will be coerced to an unsigned 32-bit integer.\n\nRequires all 3 arguments as a default is not optional:\n\n```c++\nuint32_t count = NanUInt32OptionValue(optionsObj, NanSymbol(\"count\"), 1024);\n```\n\n\n### NanThrowError(message), NanThrowTypeError(message), NanThrowRangeError(message), NanThrowError(Local<Value>), NanThrowError(Local<Value>, int)\n\nFor throwing `Error`, `TypeError` and `RangeError` objects. You should `return` this call:\n\n```c++\nreturn NanThrowError(\"you must supply a callback argument\");\n```\n\nCan also handle any custom object you may want to throw. If used with the error code argument, it will add the supplied error code to the error object as a property called `code`.\n\n\n### Local<Object> NanNewBufferHandle(char *, uint32_t), Local<Object> NanNewBufferHandle(uint32_t)\n\nThe `Buffer` API has changed a little in Node 0.11, this helper provides consistent access to `Buffer` creation:\n\n```c++\nNanNewBufferHandle((char*)value.data(), value.size());\n```\n\nCan also be used to initialize a `Buffer` with just a `size` argument.\n\nCan also be supplied with a `NAN_WEAK_CALLBACK` and a hint for the garbage collector, when dealing with weak references.\n\n\n### Local<Object> NanBufferUse(char*, uint32_t)\n\n`Buffer::New(char*, uint32_t)` prior to 0.11 would make a copy of the data.\nWhile it was possible to get around this, it required a shim by passing a\ncallback. So the new API `Buffer::Use(char*, uint32_t)` was introduced to remove\nneeding to use this shim.\n\n`NanBufferUse` uses the `char*` passed as the backing data, and will free the\nmemory automatically when the weak callback is called. Keep this in mind, as\ncareless use can lead to \"double free or corruption\" and other cryptic failures.\n\n\n### bool NanHasInstance(Persistent<FunctionTemplate>&, Handle<Value>)\n\nCan be used to check the type of an object to determine it is of a particular class you have already defined and have a `Persistent` handle for.\n\n\n### Local<Type> NanPersistentToLocal(Persistent<Type>&)\n\nAside from `FunctionCallbackInfo`, the biggest and most painful change to V8 in Node 0.11 is the many restrictions now placed on `Persistent` handles. They are difficult to assign and difficult to fetch the original value out of.\n\nUse `NanPersistentToLocal` to convert a `Persistent` handle back to a `Local` handle.\n\n```c++\nLocal handle = NanPersistentToLocal(persistentHandle);\n```\n\n\n### Local<Context> NanNewContextHandle([ExtensionConfiguration*, Handle<ObjectTemplate>, Handle<Value>])\nCreates a new `Local` handle.\n\n```c++\nLocal ftmpl = FunctionTemplate::New();\nLocal otmpl = ftmpl->InstanceTemplate();\nLocal ctx = NanNewContextHandle(NULL, otmpl);\n```\n\n\n### void NanDispose(Persistent<T> &)\n\nUse `NanDispose` to dispose a `Persistent` handle.\n\n```c++\nNanDispose(persistentHandle);\n```\n\n\n### NanAssignPersistent(type, handle, object)\n\nUse `NanAssignPersistent` to assign a non-`Persistent` handle to a `Persistent` one. You can no longer just declare a `Persistent` handle and assign directly to it later, you have to `Reset` it in Node 0.11, so this makes it easier.\n\nIn general it is now better to place anything you want to protect from V8's garbage collector as properties of a generic `Object` and then assign that to a `Persistent`. This works in older versions of Node also if you use `NanAssignPersistent`:\n\n```c++\nPersistent persistentHandle;\n\n...\n\nLocal obj = Object::New();\nobj->Set(NanSymbol(\"key\"), keyHandle); // where keyHandle might be a Local\nNanAssignPersistent(Object, persistentHandle, obj)\n```\n\n\n### NanInitPersistent(type, name, object)\n\nUser `NanInitPersistent` to declare and initialize a new `Persistent` with the supplied object. The assignment operator for `Persistent` is no longer public in Node 0.11, so this macro makes it easier to declare and initializing a new `Persistent`. See NanAssignPersistent for more information.\n\n```c++\nLocal obj = Object::New();\nobj->Set(NanSymbol(\"key\"), keyHandle); // where keyHandle might be a Local\nNanInitPersistent(Object, persistentHandle, obj);\n```\n\n\n### NanCallback\n\nBecause of the difficulties imposed by the changes to `Persistent` handles in V8 in Node 0.11, creating `Persistent` versions of your `Local` handles is annoyingly tricky. `NanCallback` makes it easier by taking your `Local` handle, making it persistent until the `NanCallback` is deleted and even providing a handy `Call()` method to fetch and execute the callback `Function`.\n\n```c++\nLocal callbackHandle = callback = args[0].As();\nNanCallback *callback = new NanCallback(callbackHandle);\n// pass `callback` around and it's safe from GC until you:\ndelete callback;\n```\n\nYou can execute the callback like so:\n\n```c++\n// no arguments:\ncallback->Call(0, NULL);\n\n// an error argument:\nLocal argv[] = {\n Exception::Error(String::New(\"fail!\"))\n};\ncallback->Call(1, argv);\n\n// a success argument:\nLocal argv[] = {\n Local::New(Null()),\n String::New(\"w00t!\")\n};\ncallback->Call(2, argv);\n```\n\n`NanCallback` also has a `Local GetCallback()` method that you can use to fetch a local handle to the underlying callback function if you need it.\n\n\n### NanAsyncWorker\n\n`NanAsyncWorker` is an abstract class that you can subclass to have much of the annoying async queuing and handling taken care of for you. It can even store arbitrary V8 objects for you and have them persist while the async work is in progress.\n\nSee a rough outline of the implementation:\n\n```c++\nclass NanAsyncWorker {\npublic:\n NanAsyncWorker (NanCallback *callback);\n\n // Clean up persistent handles and delete the *callback\n virtual ~NanAsyncWorker ();\n\n // Check the `char *errmsg` property and call HandleOKCallback()\n // or HandleErrorCallback depending on whether it has been set or not\n virtual void WorkComplete ();\n\n // You must implement this to do some async work. If there is an\n // error then allocate `errmsg` to to a message and the callback will\n // be passed that string in an Error object\n virtual void Execute ();\n\nprotected:\n // Set this if there is an error, otherwise it's NULL\n const char *errmsg;\n\n // Save a V8 object in a Persistent handle to protect it from GC\n void SavePersistent(const char *key, Local &obj);\n\n // Fetch a stored V8 object (don't call from within `Execute()`)\n Local GetFromPersistent(const char *key);\n\n // Default implementation calls the callback function with no arguments.\n // Override this to return meaningful data\n virtual void HandleOKCallback ();\n\n // Default implementation calls the callback function with an Error object\n // wrapping the `errmsg` string\n virtual void HandleErrorCallback ();\n};\n```\n\n\n### NanAsyncQueueWorker(NanAsyncWorker *)\n\n`NanAsyncQueueWorker` will run a `NanAsyncWorker` asynchronously via libuv. Both the *execute* and *after_work* steps are taken care of for you—most of the logic for this is embedded in `NanAsyncWorker`.\n\n### Contributors\n\nNAN is only possible due to the excellent work of the following contributors:\n\n\n\n\n\n
Rod VaggGitHub/rvaggTwitter/@rvagg
Benjamin ByholmGitHub/kkoopa
Trevor NorrisGitHub/trevnorrisTwitter/@trevnorris
\n\nLicence & copyright\n-----------------------\n\nCopyright (c) 2013 Rod Vagg & NAN contributors (listed above).\n\nNative Abstractions for Node.js is licensed under an MIT +no-false-attribs license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.\n", + "_id": "nan@0.3.2", + "dist": { + "shasum": "10b41e29145e722aad573c9c5b493d9b1c0887c0" + }, + "_from": "nan@~0.3.0" +} diff --git a/node_modules/ws/node_modules/options/.npmignore b/node_modules/ws/node_modules/options/.npmignore new file mode 100644 index 0000000..6bfffbb --- /dev/null +++ b/node_modules/ws/node_modules/options/.npmignore @@ -0,0 +1,5 @@ +npm-debug.log +node_modules +.*.swp +.lock-* +build/ diff --git a/node_modules/ws/node_modules/options/Makefile b/node_modules/ws/node_modules/options/Makefile new file mode 100644 index 0000000..7496b6f --- /dev/null +++ b/node_modules/ws/node_modules/options/Makefile @@ -0,0 +1,12 @@ +ALL_TESTS = $(shell find test/ -name '*.test.js') + +run-tests: + @./node_modules/.bin/mocha \ + -t 2000 \ + $(TESTFLAGS) \ + $(TESTS) + +test: + @$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests + +.PHONY: test diff --git a/node_modules/ws/node_modules/options/README.md b/node_modules/ws/node_modules/options/README.md new file mode 100644 index 0000000..4b39a2a --- /dev/null +++ b/node_modules/ws/node_modules/options/README.md @@ -0,0 +1,28 @@ +# options.js # + +A very light-weight in-code option parsers for node.js. + +## License ## + +(The MIT License) + +Copyright (c) 2012 Einar Otto Stangvik <einaros@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/ws/node_modules/options/lib/options.js b/node_modules/ws/node_modules/options/lib/options.js new file mode 100644 index 0000000..4fc45e9 --- /dev/null +++ b/node_modules/ws/node_modules/options/lib/options.js @@ -0,0 +1,86 @@ +/*! + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var fs = require('fs'); + +function Options(defaults) { + var internalValues = {}; + var values = this.value = {}; + Object.keys(defaults).forEach(function(key) { + internalValues[key] = defaults[key]; + Object.defineProperty(values, key, { + get: function() { return internalValues[key]; }, + configurable: false, + enumerable: true + }); + }); + this.reset = function() { + Object.keys(defaults).forEach(function(key) { + internalValues[key] = defaults[key]; + }); + return this; + }; + this.merge = function(options, required) { + options = options || {}; + if (Object.prototype.toString.call(required) === '[object Array]') { + var missing = []; + for (var i = 0, l = required.length; i < l; ++i) { + var key = required[i]; + if (!(key in options)) { + missing.push(key); + } + } + if (missing.length > 0) { + if (missing.length > 1) { + throw new Error('options ' + + missing.slice(0, missing.length - 1).join(', ') + ' and ' + + missing[missing.length - 1] + ' must be defined'); + } + else throw new Error('option ' + missing[0] + ' must be defined'); + } + } + Object.keys(options).forEach(function(key) { + if (key in internalValues) { + internalValues[key] = options[key]; + } + }); + return this; + }; + this.copy = function(keys) { + var obj = {}; + Object.keys(defaults).forEach(function(key) { + if (keys.indexOf(key) !== -1) { + obj[key] = values[key]; + } + }); + return obj; + }; + this.read = function(filename, cb) { + if (typeof cb == 'function') { + var self = this; + fs.readFile(filename, function(error, data) { + if (error) return cb(error); + var conf = JSON.parse(data); + self.merge(conf); + cb(); + }); + } + else { + var conf = JSON.parse(fs.readFileSync(filename)); + this.merge(conf); + } + return this; + }; + this.isDefined = function(key) { + return typeof values[key] != 'undefined'; + }; + this.isDefinedAndNonNull = function(key) { + return typeof values[key] != 'undefined' && values[key] !== null; + }; + Object.freeze(values); + Object.freeze(this); +} + +module.exports = Options; diff --git a/node_modules/ws/node_modules/options/package.json b/node_modules/ws/node_modules/options/package.json new file mode 100644 index 0000000..cf42c90 --- /dev/null +++ b/node_modules/ws/node_modules/options/package.json @@ -0,0 +1,31 @@ +{ + "author": { + "name": "Einar Otto Stangvik", + "email": "einaros@gmail.com", + "url": "http://2x.io" + }, + "name": "options", + "description": "A very light-weight in-code option parsers for node.js.", + "version": "0.0.5", + "repository": { + "type": "git", + "url": "git://github.com/einaros/options.js.git" + }, + "main": "lib/options", + "scripts": { + "test": "make test" + }, + "engines": { + "node": ">=0.4.0" + }, + "dependencies": {}, + "devDependencies": { + "mocha": "latest" + }, + "readme": "# options.js #\n\nA very light-weight in-code option parsers for node.js.\n\n## License ##\n\n(The MIT License)\n\nCopyright (c) 2012 Einar Otto Stangvik <einaros@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", + "_id": "options@0.0.5", + "dist": { + "shasum": "b0976718113cc4ca5fc50fe967034c339207f0ed" + }, + "_from": "options@>=0.0.5" +} diff --git a/node_modules/ws/node_modules/options/test/fixtures/test.conf b/node_modules/ws/node_modules/options/test/fixtures/test.conf new file mode 100644 index 0000000..6e62444 --- /dev/null +++ b/node_modules/ws/node_modules/options/test/fixtures/test.conf @@ -0,0 +1,4 @@ +{ + "a": "foobar", + "b": false +} \ No newline at end of file diff --git a/node_modules/ws/node_modules/options/test/options.test.js b/node_modules/ws/node_modules/options/test/options.test.js new file mode 100644 index 0000000..6a1d9f5 --- /dev/null +++ b/node_modules/ws/node_modules/options/test/options.test.js @@ -0,0 +1,140 @@ +var Options = require('options') + , assert = require('assert'); + +describe('Options', function() { + describe('#ctor', function() { + it('initializes options', function() { + var option = new Options({a: true, b: false}); + assert.strictEqual(true, option.value.a); + assert.strictEqual(false, option.value.b); + }); + }); + + describe('#merge', function() { + it('merges options from another object', function() { + var option = new Options({a: true, b: false}); + option.merge({b: true}); + assert.strictEqual(true, option.value.a); + assert.strictEqual(true, option.value.b); + }); + it('does nothing when arguments are undefined', function() { + var option = new Options({a: true, b: false}); + option.merge(undefined); + assert.strictEqual(true, option.value.a); + assert.strictEqual(false, option.value.b); + }); + it('cannot set values that werent already there', function() { + var option = new Options({a: true, b: false}); + option.merge({c: true}); + assert.strictEqual('undefined', typeof option.value.c); + }); + it('can require certain options to be defined', function() { + var option = new Options({a: true, b: false, c: 3}); + var caughtException = false; + try { + option.merge({}, ['a', 'b', 'c']); + } + catch (e) { + caughtException = e.toString() == 'Error: options a, b and c must be defined'; + } + assert.strictEqual(true, caughtException); + }); + it('can require certain options to be defined, when options are undefined', function() { + var option = new Options({a: true, b: false, c: 3}); + var caughtException = false; + try { + option.merge(undefined, ['a', 'b', 'c']); + } + catch (e) { + caughtException = e.toString() == 'Error: options a, b and c must be defined'; + } + assert.strictEqual(true, caughtException); + }); + it('returns "this"', function() { + var option = new Options({a: true, b: false, c: 3}); + assert.strictEqual(option, option.merge()); + }); + }); + + describe('#copy', function() { + it('returns a new object with the indicated options', function() { + var option = new Options({a: true, b: false, c: 3}); + option.merge({c: 4}); + var obj = option.copy(['a', 'c']); + assert.strictEqual(true, obj.a); + assert.strictEqual(4, obj.c); + assert.strictEqual('undefined', typeof obj.b); + }); + }); + + describe('#value', function() { + it('can be enumerated', function() { + var option = new Options({a: true, b: false}); + assert.strictEqual(2, Object.keys(option.value).length); + }); + it('can not be used to set values', function() { + var option = new Options({a: true, b: false}); + option.value.b = true; + assert.strictEqual(false, option.value.b); + }); + it('can not be used to add values', function() { + var option = new Options({a: true, b: false}); + option.value.c = 3; + assert.strictEqual('undefined', typeof option.value.c); + }); + }); + + describe('#isDefined', function() { + it('returns true if the named value is defined', function() { + var option = new Options({a: undefined}); + assert.strictEqual(false, option.isDefined('a')); + option.merge({a: false}); + assert.strictEqual(true, option.isDefined('a')); + }); + }); + + describe('#isDefinedAndNonNull', function() { + it('returns true if the named value is defined and non-null', function() { + var option = new Options({a: undefined}); + assert.strictEqual(false, option.isDefinedAndNonNull('a')); + option.merge({a: null}); + assert.strictEqual(false, option.isDefinedAndNonNull('a')); + option.merge({a: 2}); + assert.strictEqual(true, option.isDefinedAndNonNull('a')); + }); + }); + + describe('#read', function() { + it('reads and merges config from a file', function() { + var option = new Options({a: true, b: true}); + option.read(__dirname + '/fixtures/test.conf'); + assert.strictEqual('foobar', option.value.a); + assert.strictEqual(false, option.value.b); + }); + + it('asynchronously reads and merges config from a file when a callback is passed', function(done) { + var option = new Options({a: true, b: true}); + option.read(__dirname + '/fixtures/test.conf', function(error) { + assert.strictEqual('foobar', option.value.a); + assert.strictEqual(false, option.value.b); + done(); + }); + }); + }); + + describe('#reset', function() { + it('resets options to defaults', function() { + var option = new Options({a: true, b: false}); + option.merge({b: true}); + assert.strictEqual(true, option.value.b); + option.reset(); + assert.strictEqual(false, option.value.b); + }); + }); + + it('is immutable', function() { + var option = new Options({a: true, b: false}); + option.foo = 2; + assert.strictEqual('undefined', typeof option.foo); + }); +}); diff --git a/node_modules/ws/node_modules/tinycolor/.npmignore b/node_modules/ws/node_modules/tinycolor/.npmignore new file mode 100644 index 0000000..6bfffbb --- /dev/null +++ b/node_modules/ws/node_modules/tinycolor/.npmignore @@ -0,0 +1,5 @@ +npm-debug.log +node_modules +.*.swp +.lock-* +build/ diff --git a/node_modules/ws/node_modules/tinycolor/README.md b/node_modules/ws/node_modules/tinycolor/README.md new file mode 100644 index 0000000..55eb3c1 --- /dev/null +++ b/node_modules/ws/node_modules/tinycolor/README.md @@ -0,0 +1,3 @@ +# tinycolor # + +This is a no-fuzz, barebone, zero muppetry color module for node.js. \ No newline at end of file diff --git a/node_modules/ws/node_modules/tinycolor/example.js b/node_modules/ws/node_modules/tinycolor/example.js new file mode 100644 index 0000000..f754046 --- /dev/null +++ b/node_modules/ws/node_modules/tinycolor/example.js @@ -0,0 +1,3 @@ +require('./tinycolor'); +console.log('this should be red and have an underline!'.grey.underline); +console.log('this should have a blue background!'.bgBlue); \ No newline at end of file diff --git a/node_modules/ws/node_modules/tinycolor/package.json b/node_modules/ws/node_modules/tinycolor/package.json new file mode 100644 index 0000000..d0faef4 --- /dev/null +++ b/node_modules/ws/node_modules/tinycolor/package.json @@ -0,0 +1,26 @@ +{ + "author": { + "name": "Einar Otto Stangvik", + "email": "einaros@gmail.com", + "url": "http://2x.io" + }, + "name": "tinycolor", + "description": "a to-the-point color module for node", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "git://github.com/einaros/tinycolor.git" + }, + "engines": { + "node": ">=0.4.0" + }, + "dependencies": {}, + "devDependencies": {}, + "main": "tinycolor", + "readme": "# tinycolor #\n\nThis is a no-fuzz, barebone, zero muppetry color module for node.js.", + "_id": "tinycolor@0.0.1", + "dist": { + "shasum": "e1ee86d35d022d6d5fccba53141d5c50727ff76c" + }, + "_from": "tinycolor@0.x" +} diff --git a/node_modules/ws/node_modules/tinycolor/tinycolor.js b/node_modules/ws/node_modules/tinycolor/tinycolor.js new file mode 100644 index 0000000..36e552c --- /dev/null +++ b/node_modules/ws/node_modules/tinycolor/tinycolor.js @@ -0,0 +1,31 @@ +var styles = { + 'bold': ['\033[1m', '\033[22m'], + 'italic': ['\033[3m', '\033[23m'], + 'underline': ['\033[4m', '\033[24m'], + 'inverse': ['\033[7m', '\033[27m'], + 'black': ['\033[30m', '\033[39m'], + 'red': ['\033[31m', '\033[39m'], + 'green': ['\033[32m', '\033[39m'], + 'yellow': ['\033[33m', '\033[39m'], + 'blue': ['\033[34m', '\033[39m'], + 'magenta': ['\033[35m', '\033[39m'], + 'cyan': ['\033[36m', '\033[39m'], + 'white': ['\033[37m', '\033[39m'], + 'default': ['\033[39m', '\033[39m'], + 'grey': ['\033[90m', '\033[39m'], + 'bgBlack': ['\033[40m', '\033[49m'], + 'bgRed': ['\033[41m', '\033[49m'], + 'bgGreen': ['\033[42m', '\033[49m'], + 'bgYellow': ['\033[43m', '\033[49m'], + 'bgBlue': ['\033[44m', '\033[49m'], + 'bgMagenta': ['\033[45m', '\033[49m'], + 'bgCyan': ['\033[46m', '\033[49m'], + 'bgWhite': ['\033[47m', '\033[49m'], + 'bgDefault': ['\033[49m', '\033[49m'] +} +Object.keys(styles).forEach(function(style) { + Object.defineProperty(String.prototype, style, { + get: function() { return styles[style][0] + this + styles[style][1]; }, + enumerable: false + }); +}); diff --git a/node_modules/ws/package.json b/node_modules/ws/package.json new file mode 100644 index 0000000..ea5e990 --- /dev/null +++ b/node_modules/ws/package.json @@ -0,0 +1,59 @@ +{ + "author": { + "name": "Einar Otto Stangvik", + "email": "einaros@gmail.com", + "url": "http://2x.io" + }, + "name": "ws", + "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", + "version": "0.4.31", + "keywords": [ + "Hixie", + "HyBi", + "Push", + "RFC-6455", + "WebSocket", + "WebSockets", + "real-time" + ], + "repository": { + "type": "git", + "url": "git://github.com/einaros/ws.git" + }, + "bin": { + "wscat": "./bin/wscat" + }, + "scripts": { + "test": "make test", + "install": "(node-gyp rebuild 2> builderror.log) || (exit 0)" + }, + "engines": { + "node": ">=0.4.0" + }, + "dependencies": { + "commander": "~0.6.1", + "nan": "~0.3.0", + "tinycolor": "0.x", + "options": ">=0.0.5" + }, + "devDependencies": { + "mocha": "1.12.0", + "should": "1.2.x", + "expect.js": "0.2.x", + "benchmark": "0.3.x", + "ansi": "latest" + }, + "browser": "./lib/browser.js", + "component": { + "scripts": { + "ws/index.js": "./lib/browser.js" + } + }, + "gypfile": true, + "readme": "[![Build Status](https://secure.travis-ci.org/einaros/ws.png)](http://travis-ci.org/einaros/ws)\n\n# ws: a node.js websocket library #\n\n`ws` is a simple to use websocket implementation, up-to-date against RFC-6455, and [probably the fastest WebSocket library for node.js](http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs).\n\nPasses the quite extensive Autobahn test suite. See http://einaros.github.com/ws for the full reports.\n\nComes with a command line utility, `wscat`, which can either act as a server (--listen), or client (--connect); Use it to debug simple websocket services.\n\n## Protocol support ##\n\n* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. Added to ws version 0.4.2, but server only. Can be disabled by setting the `disableHixie` option to true.)\n* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`, or argument `-p 8` for wscat)\n* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`, or argument `-p 13` for wscat)\n\n_See the echo.websocket.org example below for how to use the `protocolVersion` option._\n\n## Usage ##\n\n### Installing ###\n\n`npm install ws`\n\n### Sending and receiving text data ###\n\n```js\nvar WebSocket = require('ws');\nvar ws = new WebSocket('ws://www.host.com/path');\nws.on('open', function() {\n ws.send('something');\n});\nws.on('message', function(data, flags) {\n // flags.binary will be set if a binary data is received\n // flags.masked will be set if the data was masked\n});\n```\n\n### Sending binary data ###\n\n```js\nvar WebSocket = require('ws');\nvar ws = new WebSocket('ws://www.host.com/path');\nws.on('open', function() {\n var array = new Float32Array(5);\n for (var i = 0; i < array.length; ++i) array[i] = i / 2;\n ws.send(array, {binary: true, mask: true});\n});\n```\n\nSetting `mask`, as done for the send options above, will cause the data to be masked according to the websocket protocol. The same option applies for text data.\n\n### Server example ###\n\n```js\nvar WebSocketServer = require('ws').Server\n , wss = new WebSocketServer({port: 8080});\nwss.on('connection', function(ws) {\n ws.on('message', function(message) {\n console.log('received: %s', message);\n });\n ws.send('something');\n});\n```\n\n### Server sending broadcast data ###\n\n```js\nvar WebSocketServer = require('ws').Server\n , wss = new WebSocketServer({port: 8080});\n \nwss.broadcast = function(data) {\n\tfor(var i in this.clients)\n\t\tthis.clients[i].send(data);\n};\n```\n\n### Error handling best practices ###\n\n```js\n// If the WebSocket is closed before the following send is attempted\nws.send('something');\n\n// Errors (both immediate and async write errors) can be detected in an optional callback.\n// The callback is also the only way of being notified that data has actually been sent.\nws.send('something', function(error) {\n // if error is null, the send has been completed,\n // otherwise the error object will indicate what failed.\n});\n\n// Immediate errors can also be handled with try/catch-blocks, but **note**\n// that since sends are inherently asynchronous, socket write failures will *not*\n// be captured when this technique is used.\ntry {\n ws.send('something');\n}\ncatch (e) {\n // handle error\n}\n```\n\n### echo.websocket.org demo ###\n\n```js\nvar WebSocket = require('ws');\nvar ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 8, origin: 'http://websocket.org'});\nws.on('open', function() {\n console.log('connected');\n ws.send(Date.now().toString(), {mask: true});\n});\nws.on('close', function() {\n console.log('disconnected');\n});\nws.on('message', function(data, flags) {\n console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags);\n setTimeout(function() {\n ws.send(Date.now().toString(), {mask: true});\n }, 500);\n});\n```\n\n### wscat against echo.websocket.org ###\n\n $ npm install -g ws\n $ wscat -c ws://echo.websocket.org -p 8\n connected (press CTRL+C to quit)\n > hi there\n < hi there\n > are you a happy parrot?\n < are you a happy parrot?\n\n### Other examples ###\n\nFor a full example with a browser client communicating with a ws server, see the examples folder.\n\nNote that the usage together with Express 3.0 is quite different from Express 2.x. The difference is expressed in the two different serverstats-examples.\n\nOtherwise, see the test cases.\n\n### Running the tests ###\n\n`make test`\n\n## API Docs ##\n\nSee the doc/ directory for Node.js-like docs for the ws classes.\n\n## License ##\n\n(The MIT License)\n\nCopyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", + "_id": "ws@0.4.31", + "dist": { + "shasum": "ee52dad87e6af1da37ab99ccad4e68ad4f11b3ee" + }, + "_from": "ws" +} diff --git a/node_modules/ws/src/bufferutil.cc b/node_modules/ws/src/bufferutil.cc new file mode 100644 index 0000000..f06777f --- /dev/null +++ b/node_modules/ws/src/bufferutil.cc @@ -0,0 +1,117 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "nan.h" + +using namespace v8; +using namespace node; + +class BufferUtil : public ObjectWrap +{ +public: + + static void Initialize(v8::Handle target) + { + NanScope(); + Local t = FunctionTemplate::New(New); + t->InstanceTemplate()->SetInternalFieldCount(1); + NODE_SET_METHOD(t, "unmask", BufferUtil::Unmask); + NODE_SET_METHOD(t, "mask", BufferUtil::Mask); + NODE_SET_METHOD(t, "merge", BufferUtil::Merge); + target->Set(String::NewSymbol("BufferUtil"), t->GetFunction()); + } + +protected: + + static NAN_METHOD(New) + { + NanScope(); + BufferUtil* bufferUtil = new BufferUtil(); + bufferUtil->Wrap(args.This()); + NanReturnValue(args.This()); + } + + static NAN_METHOD(Merge) + { + NanScope(); + Local bufferObj = args[0]->ToObject(); + char* buffer = Buffer::Data(bufferObj); + Local array = Local::Cast(args[1]); + unsigned int arrayLength = array->Length(); + size_t offset = 0; + unsigned int i; + for (i = 0; i < arrayLength; ++i) { + Local src = array->Get(i)->ToObject(); + size_t length = Buffer::Length(src); + memcpy(buffer + offset, Buffer::Data(src), length); + offset += length; + } + NanReturnValue(True()); + } + + static NAN_METHOD(Unmask) + { + NanScope(); + Local buffer_obj = args[0]->ToObject(); + size_t length = Buffer::Length(buffer_obj); + Local mask_obj = args[1]->ToObject(); + unsigned int *mask = (unsigned int*)Buffer::Data(mask_obj); + unsigned int* from = (unsigned int*)Buffer::Data(buffer_obj); + size_t len32 = length / 4; + unsigned int i; + for (i = 0; i < len32; ++i) *(from + i) ^= *mask; + from += i; + switch (length % 4) { + case 3: *((unsigned char*)from+2) = *((unsigned char*)from+2) ^ ((unsigned char*)mask)[2]; + case 2: *((unsigned char*)from+1) = *((unsigned char*)from+1) ^ ((unsigned char*)mask)[1]; + case 1: *((unsigned char*)from ) = *((unsigned char*)from ) ^ ((unsigned char*)mask)[0]; + case 0:; + } + NanReturnValue(True()); + } + + static NAN_METHOD(Mask) + { + NanScope(); + Local buffer_obj = args[0]->ToObject(); + Local mask_obj = args[1]->ToObject(); + unsigned int *mask = (unsigned int*)Buffer::Data(mask_obj); + Local output_obj = args[2]->ToObject(); + unsigned int dataOffset = args[3]->Int32Value(); + unsigned int length = args[4]->Int32Value(); + unsigned int* to = (unsigned int*)(Buffer::Data(output_obj) + dataOffset); + unsigned int* from = (unsigned int*)Buffer::Data(buffer_obj); + unsigned int len32 = length / 4; + unsigned int i; + for (i = 0; i < len32; ++i) *(to + i) = *(from + i) ^ *mask; + to += i; + from += i; + switch (length % 4) { + case 3: *((unsigned char*)to+2) = *((unsigned char*)from+2) ^ *((unsigned char*)mask+2); + case 2: *((unsigned char*)to+1) = *((unsigned char*)from+1) ^ *((unsigned char*)mask+1); + case 1: *((unsigned char*)to ) = *((unsigned char*)from ) ^ *((unsigned char*)mask); + case 0:; + } + NanReturnValue(True()); + } +}; + +extern "C" void init (Handle target) +{ + NanScope(); + BufferUtil::Initialize(target); +} + +NODE_MODULE(bufferutil, init) + diff --git a/node_modules/ws/src/validation.cc b/node_modules/ws/src/validation.cc new file mode 100644 index 0000000..528eda1 --- /dev/null +++ b/node_modules/ws/src/validation.cc @@ -0,0 +1,145 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +#include +#include +#include +#include +#include +#include +#include +#include "nan.h" + +using namespace v8; +using namespace node; + +#define UNI_SUR_HIGH_START (uint32_t) 0xD800 +#define UNI_SUR_LOW_END (uint32_t) 0xDFFF +#define UNI_REPLACEMENT_CHAR (uint32_t) 0x0000FFFD +#define UNI_MAX_LEGAL_UTF32 (uint32_t) 0x0010FFFF + +static const uint8_t trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000, 0x00003080, 0x000E2080, + 0x03C82080, 0xFA082080, 0x82082080 +}; + +static int isLegalUTF8(const uint8_t *source, const int length) +{ + uint8_t a; + const uint8_t *srcptr = source+length; + switch (length) { + default: return 0; + /* Everything else falls through when "true"... */ + /* RFC3629 makes 5 & 6 bytes UTF-8 illegal + case 6: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0; + case 5: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0; */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0; + case 2: if ((a = (*--srcptr)) > 0xBF) return 0; + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return 0; break; + case 0xED: if (a > 0x9F) return 0; break; + case 0xF0: if (a < 0x90) return 0; break; + case 0xF4: if (a > 0x8F) return 0; break; + default: if (a < 0x80) return 0; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return 0; + } + if (*source > 0xF4) return 0; + return 1; +} + +int is_valid_utf8 (size_t len, char *value) +{ + /* is the string valid UTF-8? */ + for (unsigned int i = 0; i < len; i++) { + uint32_t ch = 0; + uint8_t extrabytes = trailingBytesForUTF8[(uint8_t) value[i]]; + + if (extrabytes + i >= len) + return 0; + + if (isLegalUTF8 ((uint8_t *) (value + i), extrabytes + 1) == 0) return 0; + + switch (extrabytes) { + case 5 : ch += (uint8_t) value[i++]; ch <<= 6; + case 4 : ch += (uint8_t) value[i++]; ch <<= 6; + case 3 : ch += (uint8_t) value[i++]; ch <<= 6; + case 2 : ch += (uint8_t) value[i++]; ch <<= 6; + case 1 : ch += (uint8_t) value[i++]; ch <<= 6; + case 0 : ch += (uint8_t) value[i]; + } + + ch -= offsetsFromUTF8[extrabytes]; + + if (ch <= UNI_MAX_LEGAL_UTF32) { + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) + return 0; + } else { + return 0; + } + } + + return 1; +} + +class Validation : public ObjectWrap +{ +public: + + static void Initialize(v8::Handle target) + { + HandleScope scope; + Local t = FunctionTemplate::New(New); + t->InstanceTemplate()->SetInternalFieldCount(1); + NODE_SET_METHOD(t, "isValidUTF8", Validation::IsValidUTF8); + target->Set(String::NewSymbol("Validation"), t->GetFunction()); + } + +protected: + + static NAN_METHOD(New) + { + NanScope(); + Validation* validation = new Validation(); + validation->Wrap(args.This()); + NanReturnValue(args.This()); + } + + static NAN_METHOD(IsValidUTF8) + { + NanScope(); + if (!Buffer::HasInstance(args[0])) { + return NanThrowTypeError("First argument needs to be a buffer"); + } + Local buffer_obj = args[0]->ToObject(); + char *buffer_data = Buffer::Data(buffer_obj); + size_t buffer_length = Buffer::Length(buffer_obj); + NanReturnValue(is_valid_utf8(buffer_length, buffer_data) == 1 ? True() : False()); + } +}; + +extern "C" void init (Handle target) +{ + NanScope(); + Validation::Initialize(target); +} + +NODE_MODULE(validation, init) + diff --git a/node_modules/ws/test/BufferPool.test.js b/node_modules/ws/test/BufferPool.test.js new file mode 100644 index 0000000..1ee7ff0 --- /dev/null +++ b/node_modules/ws/test/BufferPool.test.js @@ -0,0 +1,63 @@ +var BufferPool = require('../lib/BufferPool'); +require('should'); + +describe('BufferPool', function() { + describe('#ctor', function() { + it('allocates pool', function() { + var db = new BufferPool(1000); + db.size.should.eql(1000); + }); + }); + describe('#get', function() { + it('grows the pool if necessary', function() { + var db = new BufferPool(1000); + var buf = db.get(2000); + db.size.should.be.above(1000); + db.used.should.eql(2000); + buf.length.should.eql(2000); + }); + it('grows the pool after the first call, if necessary', function() { + var db = new BufferPool(1000); + var buf = db.get(1000); + db.used.should.eql(1000); + db.size.should.eql(1000); + buf.length.should.eql(1000); + var buf2 = db.get(1000); + db.used.should.eql(2000); + db.size.should.be.above(1000); + buf2.length.should.eql(1000); + }); + it('grows the pool according to the growStrategy if necessary', function() { + var db = new BufferPool(1000, function(db, length) { + return db.size + 2345; + }); + var buf = db.get(2000); + db.size.should.eql(3345); + buf.length.should.eql(2000); + }); + it('doesnt grow the pool if theres enough room available', function() { + var db = new BufferPool(1000); + var buf = db.get(1000); + db.size.should.eql(1000); + buf.length.should.eql(1000); + }); + }); + describe('#reset', function() { + it('shinks the pool', function() { + var db = new BufferPool(1000); + var buf = db.get(2000); + db.reset(true); + db.size.should.eql(1000); + }); + it('shrinks the pool according to the shrinkStrategy', function() { + var db = new BufferPool(1000, function(db, length) { + return db.used + length; + }, function(db) { + return 0; + }); + var buf = db.get(2000); + db.reset(true); + db.size.should.eql(0); + }); + }); +}); diff --git a/node_modules/ws/test/Receiver.hixie.test.js b/node_modules/ws/test/Receiver.hixie.test.js new file mode 100644 index 0000000..043d3bc --- /dev/null +++ b/node_modules/ws/test/Receiver.hixie.test.js @@ -0,0 +1,158 @@ +var assert = require('assert') + , expect = require('expect.js') + , Receiver = require('../lib/Receiver.hixie'); +require('./hybi-common'); + +describe('Receiver', function() { + it('can parse text message', function() { + var p = new Receiver(); + var packet = '00 48 65 6c 6c 6f ff'; + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal('Hello', data); + }; + + p.add(getBufferFromHexString(packet)); + expect(gotData).to.equal(true); + }); + + it('can parse multiple text messages', function() { + var p = new Receiver(); + var packet = '00 48 65 6c 6c 6f ff 00 48 65 6c 6c 6f ff'; + + var gotData = false; + var messages = []; + p.ontext = function(data) { + gotData = true; + messages.push(data); + }; + + p.add(getBufferFromHexString(packet)); + expect(gotData).to.equal(true); + for (var i = 0; i < 2; ++i) { + expect(messages[i]).to.equal('Hello'); + } + }); + + it('can parse empty message', function() { + var p = new Receiver(); + var packet = '00 ff'; + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal('', data); + }; + + p.add(getBufferFromHexString(packet)); + expect(gotData).to.equal(true); + }); + + it('can parse text messages delivered over multiple frames', function() { + var p = new Receiver(); + var packets = [ + '00 48', + '65 6c 6c', + '6f ff 00 48', + '65', + '6c 6c 6f', + 'ff' + ]; + + var gotData = false; + var messages = []; + p.ontext = function(data) { + gotData = true; + messages.push(data); + }; + + for (var i = 0; i < packets.length; ++i) { + p.add(getBufferFromHexString(packets[i])); + } + expect(gotData).to.equal(true); + for (var i = 0; i < 2; ++i) { + expect(messages[i]).to.equal('Hello'); + } + }); + + it('emits an error if a payload doesnt start with 0x00', function() { + var p = new Receiver(); + var packets = [ + '00 6c ff', + '00 6c ff ff', + 'ff 00 6c ff 00 6c ff', + '00', + '6c 6c 6f', + 'ff' + ]; + + var gotData = false; + var gotError = false; + var messages = []; + p.ontext = function(data) { + gotData = true; + messages.push(data); + }; + p.onerror = function(reason, code) { + gotError = code == true; + }; + + for (var i = 0; i < packets.length && !gotError; ++i) { + p.add(getBufferFromHexString(packets[i])); + } + expect(gotError).to.equal(true); + expect(messages[0]).to.equal('l'); + expect(messages[1]).to.equal('l'); + expect(messages.length).to.equal(2); + }); + + it('can parse close messages', function() { + var p = new Receiver(); + var packets = [ + 'ff 00' + ]; + + var gotClose = false; + var gotError = false; + p.onclose = function() { + gotClose = true; + }; + p.onerror = function(reason, code) { + gotError = code == true; + }; + + for (var i = 0; i < packets.length && !gotError; ++i) { + p.add(getBufferFromHexString(packets[i])); + } + expect(gotClose).to.equal(true); + expect(gotError).to.equal(false); + }); + + it('can parse binary messages delivered over multiple frames', function() { + var p = new Receiver(); + var packets = [ + '80 05 48', + '65 6c 6c', + '6f 80 80 05 48', + '65', + '6c 6c 6f' + ]; + + var gotData = false; + var messages = []; + p.ontext = function(data) { + gotData = true; + messages.push(data); + }; + + for (var i = 0; i < packets.length; ++i) { + p.add(getBufferFromHexString(packets[i])); + } + expect(gotData).to.equal(true); + for (var i = 0; i < 2; ++i) { + expect(messages[i]).to.equal('Hello'); + } + }); +}); diff --git a/node_modules/ws/test/Receiver.test.js b/node_modules/ws/test/Receiver.test.js new file mode 100644 index 0000000..b0b5c0a --- /dev/null +++ b/node_modules/ws/test/Receiver.test.js @@ -0,0 +1,255 @@ +var assert = require('assert') + , Receiver = require('../lib/Receiver'); +require('should'); +require('./hybi-common'); + +describe('Receiver', function() { + it('can parse unmasked text message', function() { + var p = new Receiver(); + var packet = '81 05 48 65 6c 6c 6f'; + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal('Hello', data); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse close message', function() { + var p = new Receiver(); + var packet = '88 00'; + + var gotClose = false; + p.onclose = function(data) { + gotClose = true; + }; + + p.add(getBufferFromHexString(packet)); + gotClose.should.be.ok; + }); + it('can parse masked text message', function() { + var p = new Receiver(); + var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5'; + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal('5:::{"name":"echo"}', data); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse a masked text message longer than 125 bytes', function() { + var p = new Receiver(); + var message = 'A'; + for (var i = 0; i < 300; ++i) message += (i % 5).toString(); + var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal(message, data); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse a really long masked text message', function() { + var p = new Receiver(); + var message = 'A'; + for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString(); + var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal(message, data); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse a fragmented masked text message of 300 bytes', function() { + var p = new Receiver(); + var message = 'A'; + for (var i = 0; i < 300; ++i) message += (i % 5).toString(); + var msgpiece1 = message.substr(0, 150); + var msgpiece2 = message.substr(150); + var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal(message, data); + }; + + p.add(getBufferFromHexString(packet1)); + p.add(getBufferFromHexString(packet2)); + gotData.should.be.ok; + }); + it('can parse a ping message', function() { + var p = new Receiver(); + var message = 'Hello'; + var packet = '89 ' + getHybiLengthAsHexString(message.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotPing = false; + p.onping = function(data) { + gotPing = true; + assert.equal(message, data); + }; + + p.add(getBufferFromHexString(packet)); + gotPing.should.be.ok; + }); + it('can parse a ping with no data', function() { + var p = new Receiver(); + var packet = '89 00'; + + var gotPing = false; + p.onping = function(data) { + gotPing = true; + }; + + p.add(getBufferFromHexString(packet)); + gotPing.should.be.ok; + }); + it('can parse a fragmented masked text message of 300 bytes with a ping in the middle', function() { + var p = new Receiver(); + var message = 'A'; + for (var i = 0; i < 300; ++i) message += (i % 5).toString(); + + var msgpiece1 = message.substr(0, 150); + var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + + var pingMessage = 'Hello'; + var pingPacket = '89 ' + getHybiLengthAsHexString(pingMessage.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68')); + + var msgpiece2 = message.substr(150); + var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal(message, data); + }; + var gotPing = false; + p.onping = function(data) { + gotPing = true; + assert.equal(pingMessage, data); + }; + + p.add(getBufferFromHexString(packet1)); + p.add(getBufferFromHexString(pingPacket)); + p.add(getBufferFromHexString(packet2)); + gotData.should.be.ok; + gotPing.should.be.ok; + }); + it('can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets', function() { + var p = new Receiver(); + var message = 'A'; + for (var i = 0; i < 300; ++i) message += (i % 5).toString(); + + var msgpiece1 = message.substr(0, 150); + var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68')); + + var pingMessage = 'Hello'; + var pingPacket = '89 ' + getHybiLengthAsHexString(pingMessage.length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68')); + + var msgpiece2 = message.substr(150); + var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68')); + + var gotData = false; + p.ontext = function(data) { + gotData = true; + assert.equal(message, data); + }; + var gotPing = false; + p.onping = function(data) { + gotPing = true; + assert.equal(pingMessage, data); + }; + + var buffers = []; + buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1))); + buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket))); + buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2))); + for (var i = 0; i < buffers.length; ++i) { + p.add(buffers[i]); + } + gotData.should.be.ok; + gotPing.should.be.ok; + }); + it('can parse a 100 byte long masked binary message', function() { + var p = new Receiver(); + var length = 100; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotData = false; + p.onbinary = function(data) { + gotData = true; + assert.equal(originalMessage, getHexStringFromBuffer(data)); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse a 256 byte long masked binary message', function() { + var p = new Receiver(); + var length = 256; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotData = false; + p.onbinary = function(data) { + gotData = true; + assert.equal(originalMessage, getHexStringFromBuffer(data)); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse a 200kb long masked binary message', function() { + var p = new Receiver(); + var length = 200 * 1024; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68')); + + var gotData = false; + p.onbinary = function(data) { + gotData = true; + assert.equal(originalMessage, getHexStringFromBuffer(data)); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); + it('can parse a 200kb long unmasked binary message', function() { + var p = new Receiver(); + var length = 200 * 1024; + var message = new Buffer(length); + for (var i = 0; i < length; ++i) message[i] = i % 256; + var originalMessage = getHexStringFromBuffer(message); + var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message); + + var gotData = false; + p.onbinary = function(data) { + gotData = true; + assert.equal(originalMessage, getHexStringFromBuffer(data)); + }; + + p.add(getBufferFromHexString(packet)); + gotData.should.be.ok; + }); +}); + diff --git a/node_modules/ws/test/Sender.hixie.test.js b/node_modules/ws/test/Sender.hixie.test.js new file mode 100644 index 0000000..783f892 --- /dev/null +++ b/node_modules/ws/test/Sender.hixie.test.js @@ -0,0 +1,134 @@ +var assert = require('assert') + , Sender = require('../lib/Sender.hixie'); +require('should'); +require('./hybi-common'); + +describe('Sender', function() { + describe('#send', function() { + it('frames and sends a text message', function(done) { + var message = 'Hello world'; + var received; + var socket = { + write: function(data, encoding, cb) { + received = data; + process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.send(message, {}, function() { + received.toString('utf8').should.eql('\u0000' + message + '\ufffd'); + done(); + }); + }); + + it('frames and sends an empty message', function(done) { + var socket = { + write: function(data, encoding, cb) { + done(); + } + }; + var sender = new Sender(socket, {}); + sender.send('', {}, function() {}); + }); + + it('frames and sends a buffer', function(done) { + var received; + var socket = { + write: function(data, encoding, cb) { + received = data; + process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.send(new Buffer('foobar'), {}, function() { + received.toString('utf8').should.eql('\u0000foobar\ufffd'); + done(); + }); + }); + + it('frames and sends a binary message', function(done) { + var message = 'Hello world'; + var received; + var socket = { + write: function(data, encoding, cb) { + received = data; + process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.send(message, {binary: true}, function() { + received.toString('hex').should.eql( + // 0x80 0x0b H e l l o w o r l d + '800b48656c6c6f20776f726c64'); + done(); + }); + }); +/* + it('throws an exception for binary data', function(done) { + var socket = { + write: function(data, encoding, cb) { + process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.on('error', function() { + done(); + }); + sender.send(new Buffer(100), {binary: true}, function() {}); + }); +*/ + it('can fauxe stream data', function(done) { + var received = []; + var socket = { + write: function(data, encoding, cb) { + received.push(data); + process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.send(new Buffer('foobar'), { fin: false }, function() {}); + sender.send('bazbar', { fin: false }, function() {}); + sender.send(new Buffer('end'), { fin: true }, function() { + received[0].toString('utf8').should.eql('\u0000foobar'); + received[1].toString('utf8').should.eql('bazbar'); + received[2].toString('utf8').should.eql('end\ufffd'); + done(); + }); + }); + }); + + describe('#close', function() { + it('sends a hixie close frame', function(done) { + var received; + var socket = { + write: function(data, encoding, cb) { + received = data; + process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.close(null, null, null, function() { + received.toString('utf8').should.eql('\ufffd\u0000'); + done(); + }); + }); + + it('sends a message end marker if fauxe streaming has started, before hixie close frame', function(done) { + var received = []; + var socket = { + write: function(data, encoding, cb) { + received.push(data); + if (cb) process.nextTick(cb); + } + }; + var sender = new Sender(socket, {}); + sender.send(new Buffer('foobar'), { fin: false }, function() {}); + sender.close(null, null, null, function() { + received[0].toString('utf8').should.eql('\u0000foobar'); + received[1].toString('utf8').should.eql('\ufffd'); + received[2].toString('utf8').should.eql('\ufffd\u0000'); + done(); + }); + }); + }); +}); diff --git a/node_modules/ws/test/Sender.test.js b/node_modules/ws/test/Sender.test.js new file mode 100644 index 0000000..43b4864 --- /dev/null +++ b/node_modules/ws/test/Sender.test.js @@ -0,0 +1,24 @@ +var Sender = require('../lib/Sender'); +require('should'); + +describe('Sender', function() { + describe('#frameAndSend', function() { + it('does not modify a masked binary buffer', function() { + var sender = new Sender({ write: function() {} }); + var buf = new Buffer([1, 2, 3, 4, 5]); + sender.frameAndSend(2, buf, true, true); + buf[0].should.eql(1); + buf[1].should.eql(2); + buf[2].should.eql(3); + buf[3].should.eql(4); + buf[4].should.eql(5); + }); + + it('does not modify a masked text buffer', function() { + var sender = new Sender({ write: function() {} }); + var text = 'hi there'; + sender.frameAndSend(1, text, true, true); + text.should.eql('hi there'); + }); + }); +}); diff --git a/node_modules/ws/test/Validation.test.js b/node_modules/ws/test/Validation.test.js new file mode 100644 index 0000000..37c3399 --- /dev/null +++ b/node_modules/ws/test/Validation.test.js @@ -0,0 +1,23 @@ +var Validation = require('../lib/Validation').Validation; +require('should'); + +describe('Validation', function() { + describe('isValidUTF8', function() { + it('should return true for a valid utf8 string', function() { + var validBuffer = new Buffer('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque gravida mattis rhoncus. Donec iaculis, metus quis varius accumsan, erat mauris condimentum diam, et egestas erat enim ut ligula. Praesent sollicitudin tellus eget dolor euismod euismod. Nullam ac augue nec neque varius luctus. Curabitur elit mi, consequat ultricies adipiscing mollis, scelerisque in erat. Phasellus facilisis fermentum ullamcorper. Nulla et sem eu arcu pharetra pellentesque. Praesent consectetur tempor justo, vel iaculis dui ullamcorper sit amet. Integer tristique viverra ullamcorper. Vivamus laoreet, nulla eget suscipit eleifend, lacus lectus feugiat libero, non fermentum erat nisi at risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pulvinar dignissim tellus, eu dignissim lorem vulputate quis. Morbi ut pulvinar augue.'); + Validation.isValidUTF8(validBuffer).should.be.ok; + }); + it('should return false for an erroneous string', function() { + var invalidBuffer = new Buffer([0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xb5, 0xed, 0xa0, 0x80, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64]); + Validation.isValidUTF8(invalidBuffer).should.not.be.ok; + }); + it('should return true for valid cases from the autobahn test suite', function() { + Validation.isValidUTF8(new Buffer('\xf0\x90\x80\x80')).should.be.ok; + Validation.isValidUTF8(new Buffer([0xf0, 0x90, 0x80, 0x80])).should.be.ok; + }); + it('should return false for erroneous autobahn strings', function() { + Validation.isValidUTF8(new Buffer([0xce, 0xba, 0xe1, 0xbd])).should.not.be.ok; + }); + }); +}); + diff --git a/node_modules/ws/test/WebSocket.integration.js b/node_modules/ws/test/WebSocket.integration.js new file mode 100644 index 0000000..5d4f426 --- /dev/null +++ b/node_modules/ws/test/WebSocket.integration.js @@ -0,0 +1,44 @@ +var assert = require('assert') + , WebSocket = require('../') + , server = require('./testserver'); + +var port = 20000; + +function getArrayBuffer(buf) { + var l = buf.length; + var arrayBuf = new ArrayBuffer(l); + var uint8View = new Uint8Array(arrayBuf); + + for (var i = 0; i < l; i++) { + uint8View[i] = buf[i]; + } + return uint8View.buffer; +} + +function areArraysEqual(x, y) { + if (x.length != y.length) return false; + for (var i = 0, l = x.length; i < l; ++i) { + if (x[i] !== y[i]) return false; + } + return true; +} + +describe('WebSocket', function() { + it('communicates successfully with echo service', function(done) { + var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 13, origin: 'http://websocket.org'}); + var str = Date.now().toString(); + var dataReceived = false; + ws.on('open', function() { + ws.send(str, {mask: true}); + }); + ws.on('close', function() { + assert.equal(true, dataReceived); + done(); + }); + ws.on('message', function(data, flags) { + assert.equal(str, data); + ws.terminate(); + dataReceived = true; + }); + }); +}); diff --git a/node_modules/ws/test/WebSocket.test.js b/node_modules/ws/test/WebSocket.test.js new file mode 100644 index 0000000..91336b9 --- /dev/null +++ b/node_modules/ws/test/WebSocket.test.js @@ -0,0 +1,1724 @@ +var assert = require('assert') + , https = require('https') + , http = require('http') + , should = require('should') + , WebSocket = require('../') + , WebSocketServer = require('../').Server + , fs = require('fs') + , server = require('./testserver') + , crypto = require('crypto'); + +var port = 20000; + +function getArrayBuffer(buf) { + var l = buf.length; + var arrayBuf = new ArrayBuffer(l); + var uint8View = new Uint8Array(arrayBuf); + for (var i = 0; i < l; i++) { + uint8View[i] = buf[i]; + } + return uint8View.buffer; +} + + +function areArraysEqual(x, y) { + if (x.length != y.length) return false; + for (var i = 0, l = x.length; i < l; ++i) { + if (x[i] !== y[i]) return false; + } + return true; +} + +describe('WebSocket', function() { + describe('#ctor', function() { + it('throws exception for invalid url', function(done) { + try { + var ws = new WebSocket('echo.websocket.org'); + } + catch (e) { + done(); + } + }); + }); + + describe('options', function() { + it('should accept an `agent` option', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var agent = { + addRequest: function() { + wss.close(); + done(); + } + }; + var ws = new WebSocket('ws://localhost:' + port, { agent: agent }); + }); + }); + // GH-227 + it('should accept the `options` object as the 3rd argument', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var agent = { + addRequest: function() { + wss.close(); + done(); + } + }; + var ws = new WebSocket('ws://localhost:' + port, [], { agent: agent }); + }); + }); + }); + + describe('properties', function() { + it('#bytesReceived exposes number of bytes received', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('message', function() { + ws.bytesReceived.should.eql(8); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + ws.send('foobar'); + }); + }); + + it('#url exposes the server url', function(done) { + server.createServer(++port, function(srv) { + var url = 'ws://localhost:' + port; + var ws = new WebSocket(url); + assert.equal(url, ws.url); + ws.terminate(); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + it('#protocolVersion exposes the protocol version', function(done) { + server.createServer(++port, function(srv) { + var url = 'ws://localhost:' + port; + var ws = new WebSocket(url); + assert.equal(13, ws.protocolVersion); + ws.terminate(); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + describe('#bufferedAmount', function() { + it('defaults to zero', function(done) { + server.createServer(++port, function(srv) { + var url = 'ws://localhost:' + port; + var ws = new WebSocket(url); + assert.equal(0, ws.bufferedAmount); + ws.terminate(); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + it('defaults to zero upon "open"', function(done) { + server.createServer(++port, function(srv) { + var url = 'ws://localhost:' + port; + var ws = new WebSocket(url); + ws.onopen = function() { + assert.equal(0, ws.bufferedAmount); + ws.terminate(); + ws.on('close', function() { + srv.close(); + done(); + }); + }; + }); + }); + + it('stress kernel write buffer', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(ws) { + while (true) { + if (ws.bufferedAmount > 0) break; + ws.send((new Array(10000)).join('hello')); + } + ws.terminate(); + ws.on('close', function() { + wss.close(); + done(); + }); + }); + }); + }); + + describe('Custom headers', function() { + it('request has an authorization header', function (done) { + var auth = 'test:testpass'; + var srv = http.createServer(function (req, res) {}); + var wss = new WebSocketServer({server: srv}); + srv.listen(++port); + var ws = new WebSocket('ws://' + auth + '@localhost:' + port); + srv.on('upgrade', function (req, socket, head) { + assert(req.headers.authorization, 'auth header exists'); + assert.equal(req.headers.authorization, 'Basic ' + new Buffer(auth).toString('base64')); + ws.terminate(); + ws.on('close', function () { + srv.close(); + wss.close(); + done(); + }); + }); + }); + + it('accepts custom headers', function (done) { + var srv = http.createServer(function (req, res) {}); + var wss = new WebSocketServer({server: srv}); + srv.listen(++port); + + var ws = new WebSocket('ws://localhost:' + port, { + headers: { + 'Cookie': 'foo=bar' + } + }); + + srv.on('upgrade', function (req, socket, head) { + assert(req.headers.cookie, 'auth header exists'); + assert.equal(req.headers.cookie, 'foo=bar'); + + ws.terminate(); + ws.on('close', function () { + srv.close(); + wss.close(); + done(); + }); + }); + }); + }); + + describe('#readyState', function() { + it('defaults to connecting', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + assert.equal(WebSocket.CONNECTING, ws.readyState); + ws.terminate(); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + it('set to open once connection is established', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + assert.equal(WebSocket.OPEN, ws.readyState); + srv.close(); + done(); + }); + }); + }); + + it('set to closed once connection is closed', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.close(1001); + ws.on('close', function() { + assert.equal(WebSocket.CLOSED, ws.readyState); + srv.close(); + done(); + }); + }); + }); + + it('set to closed once connection is terminated', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.terminate(); + ws.on('close', function() { + assert.equal(WebSocket.CLOSED, ws.readyState); + srv.close(); + done(); + }); + }); + }); + }); + + /* + * Ready state constants + */ + + var readyStates = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 + }; + + /* + * Ready state constant tests + */ + + Object.keys(readyStates).forEach(function(state) { + describe('.' + state, function() { + it('is enumerable property of class', function() { + var propertyDescripter = Object.getOwnPropertyDescriptor(WebSocket, state) + assert.equal(readyStates[state], propertyDescripter.value); + assert.equal(true, propertyDescripter.enumerable); + }); + }); + }); + + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + Object.keys(readyStates).forEach(function(state) { + describe('.' + state, function() { + it('is property of instance', function() { + assert.equal(readyStates[state], ws[state]); + }); + }); + }); + }); + }); + + describe('events', function() { + it('emits a ping event', function(done) { + var wss = new WebSocketServer({port: ++port}); + wss.on('connection', function(client) { + client.ping(); + }); + var ws = new WebSocket('ws://localhost:' + port); + ws.on('ping', function() { + ws.terminate(); + wss.close(); + done(); + }); + }); + + it('emits a pong event', function(done) { + var wss = new WebSocketServer({port: ++port}); + wss.on('connection', function(client) { + client.pong(); + }); + var ws = new WebSocket('ws://localhost:' + port); + ws.on('pong', function() { + ws.terminate(); + wss.close(); + done(); + }); + }); + }); + + describe('connection establishing', function() { + it('can disconnect before connection is established', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.terminate(); + ws.on('open', function() { + assert.fail('connect shouldnt be raised here'); + }); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + it('can close before connection is established', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.close(1001); + ws.on('open', function() { + assert.fail('connect shouldnt be raised here'); + }); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + it('invalid server key is denied', function(done) { + server.createServer(++port, server.handlers.invalidKey, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() { + srv.close(); + done(); + }); + }); + }); + + it('close event is raised when server closes connection', function(done) { + server.createServer(++port, server.handlers.closeAfterConnect, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('close', function() { + srv.close(); + done(); + }); + }); + }); + + it('error is emitted if server aborts connection', function(done) { + server.createServer(++port, server.handlers.return401, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + assert.fail('connect shouldnt be raised here'); + }); + ws.on('error', function() { + srv.close(); + done(); + }); + }); + }); + }); + + describe('#pause and #resume', function() { + it('pauses the underlying stream', function(done) { + // this test is sort-of racecondition'y, since an unlikely slow connection + // to localhost can cause the test to succeed even when the stream pausing + // isn't working as intended. that is an extremely unlikely scenario, though + // and an acceptable risk for the test. + var client; + var serverClient; + var openCount = 0; + function onOpen() { + if (++openCount == 2) { + var paused = true; + serverClient.on('message', function() { + paused.should.not.be.ok; + wss.close(); + done(); + }); + serverClient.pause(); + setTimeout(function() { + paused = false; + serverClient.resume(); + }, 200); + client.send('foo'); + } + } + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + serverClient = ws; + serverClient.on('open', onOpen); + }); + wss.on('connection', function(ws) { + client = ws; + onOpen(); + }); + }); + }); + + describe('#ping', function() { + it('before connect should fail', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + try { + ws.ping(); + } + catch (e) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + + it('before connect can silently fail', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + ws.ping('', {}, true); + srv.close(); + ws.terminate(); + done(); + }); + }); + + it('without message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.ping(); + }); + srv.on('ping', function(message) { + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.ping('hi'); + }); + srv.on('ping', function(message) { + assert.equal('hi', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with encoded message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.ping('hi', {mask: true}); + }); + srv.on('ping', function(message, flags) { + assert.ok(flags.masked); + assert.equal('hi', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + }); + + describe('#pong', function() { + it('before connect should fail', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + try { + ws.pong(); + } + catch (e) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + + it('before connect can silently fail', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + ws.pong('', {}, true); + srv.close(); + ws.terminate(); + done(); + }); + }); + + it('without message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.pong(); + }); + srv.on('pong', function(message) { + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.pong('hi'); + }); + srv.on('pong', function(message) { + assert.equal('hi', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with encoded message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.pong('hi', {mask: true}); + }); + srv.on('pong', function(message, flags) { + assert.ok(flags.masked); + assert.equal('hi', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + }); + + describe('#send', function() { + it('very long binary data can be sent and received (with echoing server)', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var array = new Float32Array(5 * 1024 * 1024); + for (var i = 0; i < array.length; ++i) array[i] = i / 5; + ws.on('open', function() { + ws.send(array, {binary: true}); + }); + ws.on('message', function(message, flags) { + assert.ok(flags.binary); + assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('can send and receive text data', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.send('hi'); + }); + ws.on('message', function(message, flags) { + assert.equal('hi', message); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('send and receive binary data as an array', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var array = new Float32Array(6); + for (var i = 0; i < array.length; ++i) array[i] = i / 2; + var partial = array.subarray(2, 5); + ws.on('open', function() { + ws.send(partial, {binary: true}); + }); + ws.on('message', function(message, flags) { + assert.ok(flags.binary); + assert.ok(areArraysEqual(partial, new Float32Array(getArrayBuffer(message)))); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('binary data can be sent and received as buffer', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var buf = new Buffer('foobar'); + ws.on('open', function() { + ws.send(buf, {binary: true}); + }); + ws.on('message', function(message, flags) { + assert.ok(flags.binary); + assert.ok(areArraysEqual(buf, message)); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('ArrayBuffer is auto-detected without binary flag', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var array = new Float32Array(5); + for (var i = 0; i < array.length; ++i) array[i] = i / 2; + ws.on('open', function() { + ws.send(array.buffer); + }); + ws.onmessage = function (event) { + assert.ok(event.type = 'Binary'); + assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(event.data)))); + ws.terminate(); + srv.close(); + done(); + }; + }); + }); + + it('Buffer is auto-detected without binary flag', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var buf = new Buffer('foobar'); + ws.on('open', function() { + ws.send(buf); + }); + ws.onmessage = function (event) { + assert.ok(event.type = 'Binary'); + assert.ok(areArraysEqual(event.data, buf)); + ws.terminate(); + srv.close(); + done(); + }; + }); + }); + + it('before connect should fail', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + try { + ws.send('hi'); + } + catch (e) { + ws.terminate(); + srv.close(); + done(); + } + }); + }); + + it('before connect should pass error through callback, if present', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + ws.send('hi', function(error) { + assert.ok(error instanceof Error); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('without data should be successful', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.send(); + }); + srv.on('message', function(message, flags) { + assert.equal('', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('calls optional callback when flushed', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.send('hi', function() { + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + }); + + it('with unencoded message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.send('hi'); + }); + srv.on('message', function(message, flags) { + assert.equal('hi', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with encoded message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.send('hi', {mask: true}); + }); + srv.on('message', function(message, flags) { + assert.ok(flags.masked); + assert.equal('hi', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with unencoded binary message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var array = new Float32Array(5); + for (var i = 0; i < array.length; ++i) array[i] = i / 2; + ws.on('open', function() { + ws.send(array, {binary: true}); + }); + srv.on('message', function(message, flags) { + assert.ok(flags.binary); + assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with encoded binary message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var array = new Float32Array(5); + for (var i = 0; i < array.length; ++i) array[i] = i / 2; + ws.on('open', function() { + ws.send(array, {mask: true, binary: true}); + }); + srv.on('message', function(message, flags) { + assert.ok(flags.binary); + assert.ok(flags.masked); + assert.ok(areArraysEqual(array, new Float32Array(getArrayBuffer(message)))); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with binary stream will send fragmented data', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var callbackFired = false; + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.bufferSize = 100; + ws.send(fileStream, {binary: true}, function(error) { + assert.equal(null, error); + callbackFired = true; + }); + }); + srv.on('message', function(data, flags) { + assert.ok(flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile'), data)); + ws.terminate(); + }); + ws.on('close', function() { + assert.ok(callbackFired); + srv.close(); + done(); + }); + }); + }); + + it('with text stream will send fragmented data', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var callbackFired = false; + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream, {binary: false}, function(error) { + assert.equal(null, error); + callbackFired = true; + }); + }); + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + ws.terminate(); + }); + ws.on('close', function() { + assert.ok(callbackFired); + srv.close(); + done(); + }); + }); + }); + + it('will cause intermittent send to be delayed in order', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream); + ws.send('foobar'); + ws.send('baz'); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + ++receivedIndex; + if (receivedIndex == 1) { + assert.ok(!flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + } + else if (receivedIndex == 2) { + assert.ok(!flags.binary); + assert.equal('foobar', data); + } + else { + assert.ok(!flags.binary); + assert.equal('baz', data); + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent stream to be delayed in order', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream); + var i = 0; + ws.stream(function(error, send) { + assert.ok(!error); + if (++i == 1) send('foo'); + else send('bar', true); + }); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + ++receivedIndex; + if (receivedIndex == 1) { + assert.ok(!flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + } + else if (receivedIndex == 2) { + assert.ok(!flags.binary); + assert.equal('foobar', data); + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent ping to be delivered', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream); + ws.ping('foobar'); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + srv.on('ping', function(data) { + assert.equal('foobar', data); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent pong to be delivered', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream); + ws.pong('foobar'); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + srv.on('pong', function(data) { + assert.equal('foobar', data); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent close to be delivered', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream); + ws.close(1000, 'foobar'); + }); + ws.on('close', function() { + srv.close(); + ws.terminate(); + done(); + }); + ws.on('error', function() { /* That's quite alright -- a send was attempted after close */ }); + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.ok(areArraysEqual(fs.readFileSync('test/fixtures/textfile', 'utf8'), data)); + }); + srv.on('close', function(code, data) { + assert.equal(1000, code); + assert.equal('foobar', data); + }); + }); + }); + }); + + describe('#stream', function() { + it('very long binary data can be streamed', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var buffer = new Buffer(10 * 1024); + for (var i = 0; i < buffer.length; ++i) buffer[i] = i % 0xff; + ws.on('open', function() { + var i = 0; + var blockSize = 800; + var bufLen = buffer.length; + ws.stream({binary: true}, function(error, send) { + assert.ok(!error); + var start = i * blockSize; + var toSend = Math.min(blockSize, bufLen - (i * blockSize)); + var end = start + toSend; + var isFinal = toSend < blockSize; + send(buffer.slice(start, end), isFinal); + i += 1; + }); + }); + srv.on('message', function(data, flags) { + assert.ok(flags.binary); + assert.ok(areArraysEqual(buffer, data)); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('before connect should pass error through callback', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('error', function() {}); + ws.stream(function(error) { + assert.ok(error instanceof Error); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('without callback should fail', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var payload = 'HelloWorld'; + ws.on('open', function() { + try { + ws.stream(); + } + catch (e) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent send to be delayed in order', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var payload = 'HelloWorld'; + ws.on('open', function() { + var i = 0; + ws.stream(function(error, send) { + assert.ok(!error); + if (++i == 1) { + send(payload.substr(0, 5)); + ws.send('foobar'); + ws.send('baz'); + } + else { + send(payload.substr(5, 5), true); + } + }); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + ++receivedIndex; + if (receivedIndex == 1) { + assert.ok(!flags.binary); + assert.equal(payload, data); + } + else if (receivedIndex == 2) { + assert.ok(!flags.binary); + assert.equal('foobar', data); + } + else { + assert.ok(!flags.binary); + assert.equal('baz', data); + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent stream to be delayed in order', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var payload = 'HelloWorld'; + ws.on('open', function() { + var i = 0; + ws.stream(function(error, send) { + assert.ok(!error); + if (++i == 1) { + send(payload.substr(0, 5)); + var i2 = 0; + ws.stream(function(error, send) { + assert.ok(!error); + if (++i2 == 1) send('foo'); + else send('bar', true); + }); + ws.send('baz'); + } + else send(payload.substr(5, 5), true); + }); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + ++receivedIndex; + if (receivedIndex == 1) { + assert.ok(!flags.binary); + assert.equal(payload, data); + } + else if (receivedIndex == 2) { + assert.ok(!flags.binary); + assert.equal('foobar', data); + } + else if (receivedIndex == 3){ + assert.ok(!flags.binary); + assert.equal('baz', data); + setTimeout(function() { + srv.close(); + ws.terminate(); + done(); + }, 1000); + } + else throw new Error('more messages than we actually sent just arrived'); + }); + }); + }); + + it('will cause intermittent ping to be delivered', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var payload = 'HelloWorld'; + ws.on('open', function() { + var i = 0; + ws.stream(function(error, send) { + assert.ok(!error); + if (++i == 1) { + send(payload.substr(0, 5)); + ws.ping('foobar'); + } + else { + send(payload.substr(5, 5), true); + } + }); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.equal(payload, data); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + srv.on('ping', function(data) { + assert.equal('foobar', data); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent pong to be delivered', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var payload = 'HelloWorld'; + ws.on('open', function() { + var i = 0; + ws.stream(function(error, send) { + assert.ok(!error); + if (++i == 1) { + send(payload.substr(0, 5)); + ws.pong('foobar'); + } + else { + send(payload.substr(5, 5), true); + } + }); + }); + var receivedIndex = 0; + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.equal(payload, data); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + srv.on('pong', function(data) { + assert.equal('foobar', data); + if (++receivedIndex == 2) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('will cause intermittent close to be delivered', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var payload = 'HelloWorld'; + var errorGiven = false; + ws.on('open', function() { + var i = 0; + ws.stream(function(error, send) { + if (++i == 1) { + send(payload.substr(0, 5)); + ws.close(1000, 'foobar'); + } + else if(i == 2) { + send(payload.substr(5, 5), true); + } + else if (i == 3) { + assert.ok(error); + errorGiven = true; + } + }); + }); + ws.on('close', function() { + assert.ok(errorGiven); + srv.close(); + ws.terminate(); + done(); + }); + srv.on('message', function(data, flags) { + assert.ok(!flags.binary); + assert.equal(payload, data); + }); + srv.on('close', function(code, data) { + assert.equal(1000, code); + assert.equal('foobar', data); + }); + }); + }); + }); + + describe('#close', function() { + it('will raise error callback, if any, if called during send stream', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var errorGiven = false; + ws.on('open', function() { + var fileStream = fs.createReadStream('test/fixtures/textfile'); + fileStream.setEncoding('utf8'); + fileStream.bufferSize = 100; + ws.send(fileStream, function(error) { + errorGiven = error != null; + }); + ws.close(1000, 'foobar'); + }); + ws.on('close', function() { + setTimeout(function() { + assert.ok(errorGiven); + srv.close(); + ws.terminate(); + done(); + }, 1000); + }); + }); + }); + + it('without invalid first argument throws exception', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + try { + ws.close('error'); + } + catch (e) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('without reserved error code 1004 throws exception', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + try { + ws.close(1004); + } + catch (e) { + srv.close(); + ws.terminate(); + done(); + } + }); + }); + }); + + it('without message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.close(1000); + }); + srv.on('close', function(code, message, flags) { + assert.equal('', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.close(1000, 'some reason'); + }); + srv.on('close', function(code, message, flags) { + assert.ok(flags.masked); + assert.equal('some reason', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('with encoded message is successfully transmitted to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('open', function() { + ws.close(1000, 'some reason', {mask: true}); + }); + srv.on('close', function(code, message, flags) { + assert.ok(flags.masked); + assert.equal('some reason', message); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + + it('ends connection to the server', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var connectedOnce = false; + ws.on('open', function() { + connectedOnce = true; + ws.close(1000, 'some reason', {mask: true}); + }); + ws.on('close', function() { + assert.ok(connectedOnce); + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + }); + + describe('W3C API emulation', function() { + it('should not throw errors when getting and setting', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var listener = function () {}; + + ws.onmessage = listener; + ws.onerror = listener; + ws.onclose = listener; + ws.onopen = listener; + + assert.ok(ws.onopen === listener); + assert.ok(ws.onmessage === listener); + assert.ok(ws.onclose === listener); + assert.ok(ws.onerror === listener); + + srv.close(); + ws.terminate(); + done(); + }); + }); + + it('should work the same as the EventEmitter api', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + var listener = function() {}; + var message = 0; + var close = 0; + var open = 0; + + ws.onmessage = function(messageEvent) { + assert.ok(!!messageEvent.data); + ++message; + ws.close(); + }; + + ws.onopen = function() { + ++open; + } + + ws.onclose = function() { + ++close; + } + + ws.on('open', function() { + ws.send('foo'); + }); + + ws.on('close', function() { + process.nextTick(function() { + assert.ok(message === 1); + assert.ok(open === 1); + assert.ok(close === 1); + + srv.close(); + ws.terminate(); + done(); + }); + }); + }); + }); + + it('should receive text data wrapped in a MessageEvent when using addEventListener', function(done) { + server.createServer(++port, function(srv) { + var ws = new WebSocket('ws://localhost:' + port); + ws.addEventListener('open', function() { + ws.send('hi'); + }); + ws.addEventListener('message', function(messageEvent) { + assert.equal('hi', messageEvent.data); + ws.terminate(); + srv.close(); + done(); + }); + }); + }); + + it('should receive valid CloseEvent when server closes with code 1000', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + ws.addEventListener('close', function(closeEvent) { + assert.equal(true, closeEvent.wasClean); + assert.equal(1000, closeEvent.code); + ws.terminate(); + wss.close(); + done(); + }); + }); + wss.on('connection', function(client) { + client.close(1000); + }); + }); + + it('should receive valid CloseEvent when server closes with code 1001', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + ws.addEventListener('close', function(closeEvent) { + assert.equal(false, closeEvent.wasClean); + assert.equal(1001, closeEvent.code); + assert.equal('some daft reason', closeEvent.reason); + ws.terminate(); + wss.close(); + done(); + }); + }); + wss.on('connection', function(client) { + client.close(1001, 'some daft reason'); + }); + }); + + it('should have target set on Events', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + ws.addEventListener('open', function(openEvent) { + assert.equal(ws, openEvent.target); + }); + ws.addEventListener('message', function(messageEvent) { + assert.equal(ws, messageEvent.target); + wss.close(); + }); + ws.addEventListener('close', function(closeEvent) { + assert.equal(ws, closeEvent.target); + ws.emit('error', new Error('forced')); + }); + ws.addEventListener('error', function(errorEvent) { + assert.equal(errorEvent.message, 'forced'); + assert.equal(ws, errorEvent.target); + ws.terminate(); + done(); + }); + }); + wss.on('connection', function(client) { + client.send('hi') + }); + }); + }); + + describe('ssl', function() { + it('can connect to secure websocket server', function(done) { + var options = { + key: fs.readFileSync('test/fixtures/key.pem'), + cert: fs.readFileSync('test/fixtures/certificate.pem') + }; + var app = https.createServer(options, function (req, res) { + res.writeHead(200); + res.end(); + }); + var wss = new WebSocketServer({server: app}); + app.listen(++port, function() { + var ws = new WebSocket('wss://localhost:' + port); + }); + wss.on('connection', function(ws) { + app.close(); + ws.terminate(); + wss.close(); + done(); + }); + }); + + it('can connect to secure websocket server with client side certificate', function(done) { + var options = { + key: fs.readFileSync('test/fixtures/key.pem'), + cert: fs.readFileSync('test/fixtures/certificate.pem'), + ca: [fs.readFileSync('test/fixtures/ca1-cert.pem')], + requestCert: true + }; + var clientOptions = { + key: fs.readFileSync('test/fixtures/agent1-key.pem'), + cert: fs.readFileSync('test/fixtures/agent1-cert.pem') + }; + var app = https.createServer(options, function (req, res) { + res.writeHead(200); + res.end(); + }); + var success = false; + var wss = new WebSocketServer({ + server: app, + verifyClient: function(info) { + success = !!info.req.client.authorized; + return true; + } + }); + app.listen(++port, function() { + var ws = new WebSocket('wss://localhost:' + port, clientOptions); + }); + wss.on('connection', function(ws) { + app.close(); + ws.terminate(); + wss.close(); + success.should.be.ok; + done(); + }); + }); + + it('cannot connect to secure websocket server via ws://', function(done) { + var options = { + key: fs.readFileSync('test/fixtures/key.pem'), + cert: fs.readFileSync('test/fixtures/certificate.pem') + }; + var app = https.createServer(options, function (req, res) { + res.writeHead(200); + res.end(); + }); + var wss = new WebSocketServer({server: app}); + app.listen(++port, function() { + var ws = new WebSocket('ws://localhost:' + port, { rejectUnauthorized :false }); + ws.on('error', function() { + app.close(); + ws.terminate(); + wss.close(); + done(); + }); + }); + }); + + it('can send and receive text data', function(done) { + var options = { + key: fs.readFileSync('test/fixtures/key.pem'), + cert: fs.readFileSync('test/fixtures/certificate.pem') + }; + var app = https.createServer(options, function (req, res) { + res.writeHead(200); + res.end(); + }); + var wss = new WebSocketServer({server: app}); + app.listen(++port, function() { + var ws = new WebSocket('wss://localhost:' + port); + ws.on('open', function() { + ws.send('foobar'); + }); + }); + wss.on('connection', function(ws) { + ws.on('message', function(message, flags) { + message.should.eql('foobar'); + app.close(); + ws.terminate(); + wss.close(); + done(); + }); + }); + }); + + it('can send and receive very long binary data', function(done) { + var options = { + key: fs.readFileSync('test/fixtures/key.pem'), + cert: fs.readFileSync('test/fixtures/certificate.pem') + } + var app = https.createServer(options, function (req, res) { + res.writeHead(200); + res.end(); + }); + crypto.randomBytes(5 * 1024 * 1024, function(ex, buf) { + if (ex) throw ex; + var wss = new WebSocketServer({server: app}); + app.listen(++port, function() { + var ws = new WebSocket('wss://localhost:' + port); + ws.on('open', function() { + ws.send(buf, {binary: true}); + }); + ws.on('message', function(message, flags) { + flags.binary.should.be.ok; + areArraysEqual(buf, message).should.be.ok; + app.close(); + ws.terminate(); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + ws.on('message', function(message, flags) { + ws.send(message, {binary: true}); + }); + }); + }); + }); + }); + + describe('protocol support discovery', function() { + describe('#supports', function() { + describe('#binary', function() { + it('returns true for hybi transport', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + assert.equal(true, client.supports.binary); + wss.close(); + done(); + }); + }); + + it('returns false for hixie transport', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + }); + wss.on('connection', function(client) { + assert.equal(false, client.supports.binary); + wss.close(); + done(); + }); + }); + }); + }); + }); + + describe('host and origin headers', function() { + it('includes the host header with port number', function(done) { + var srv = http.createServer(); + srv.listen(++port, function(){ + srv.on('upgrade', function(req, socket, upgradeHeade) { + assert.equal('localhost:' + port, req.headers['host']); + srv.close(); + done(); + }); + var ws = new WebSocket('ws://localhost:' + port); + }); + }); + + it('includes the origin header with port number', function(done) { + var srv = http.createServer(); + srv.listen(++port, function() { + srv.on('upgrade', function(req, socket, upgradeHeade) { + assert.equal('localhost:' + port, req.headers['origin']); + srv.close(); + done(); + }); + var ws = new WebSocket('ws://localhost:' + port); + }); + }); + }); + +}); diff --git a/node_modules/ws/test/WebSocketServer.test.js b/node_modules/ws/test/WebSocketServer.test.js new file mode 100644 index 0000000..c21fd97 --- /dev/null +++ b/node_modules/ws/test/WebSocketServer.test.js @@ -0,0 +1,1103 @@ +var http = require('http') + , https = require('https') + , WebSocket = require('../') + , WebSocketServer = WebSocket.Server + , fs = require('fs') + , should = require('should'); + +var port = 8000; + +function getArrayBuffer(buf) { + var l = buf.length; + var arrayBuf = new ArrayBuffer(l); + for (var i = 0; i < l; ++i) { + arrayBuf[i] = buf[i]; + } + return arrayBuf; +} + +function areArraysEqual(x, y) { + if (x.length != y.length) return false; + for (var i = 0, l = x.length; i < l; ++i) { + if (x[i] !== y[i]) return false; + } + return true; +} + +describe('WebSocketServer', function() { + describe('#ctor', function() { + it('throws an error if no option object is passed', function() { + var gotException = false; + try { + var wss = new WebSocketServer(); + } + catch (e) { + gotException = true; + } + gotException.should.be.ok; + }); + + it('throws an error if no port or server is specified', function() { + var gotException = false; + try { + var wss = new WebSocketServer({}); + } + catch (e) { + gotException = true; + } + gotException.should.be.ok; + }); + + it('does not throw an error if no port or server is specified, when the noServer option is true', function() { + var gotException = false; + try { + var wss = new WebSocketServer({noServer: true}); + } + catch (e) { + gotException = true; + } + gotException.should.eql(false); + }); + + it('emits an error if http server bind fails', function(done) { + var wss = new WebSocketServer({port: 1}); + wss.on('error', function() { done(); }); + }); + + it('starts a server on a given port', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.close(); + done(); + }); + }); + + it('uses a precreated http server', function (done) { + var srv = http.createServer(); + srv.listen(++port, function () { + var wss = new WebSocketServer({server: srv}); + var ws = new WebSocket('ws://localhost:' + port); + + wss.on('connection', function(client) { + wss.close(); + srv.close(); + done(); + }); + }); + }); + + it('uses a precreated http server listening on unix socket', function (done) { + var srv = http.createServer(); + var sockPath = '/tmp/ws_socket_'+new Date().getTime()+'.'+Math.floor(Math.random() * 1000); + srv.listen(sockPath, function () { + var wss = new WebSocketServer({server: srv}); + var ws = new WebSocket('ws+unix://'+sockPath); + + wss.on('connection', function(client) { + wss.close(); + srv.close(); + done(); + }); + }); + }); + + it('emits path specific connection event', function (done) { + var srv = http.createServer(); + srv.listen(++port, function () { + var wss = new WebSocketServer({server: srv}); + var ws = new WebSocket('ws://localhost:' + port+'/endpointName'); + + wss.on('connection/endpointName', function(client) { + wss.close(); + srv.close(); + done(); + }); + }); + }); + + it('can have two different instances listening on the same http server with two different paths', function(done) { + var srv = http.createServer(); + srv.listen(++port, function () { + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}) + , wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + var doneCount = 0; + wss1.on('connection', function(client) { + wss1.close(); + if (++doneCount == 2) { + srv.close(); + done(); + } + }); + wss2.on('connection', function(client) { + wss2.close(); + if (++doneCount == 2) { + srv.close(); + done(); + } + }); + var ws1 = new WebSocket('ws://localhost:' + port + '/wss1'); + var ws2 = new WebSocket('ws://localhost:' + port + '/wss2?foo=1'); + }); + }); + + it('cannot have two different instances listening on the same http server with the same path', function(done) { + var srv = http.createServer(); + srv.listen(++port, function () { + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}); + try { + var wss2 = new WebSocketServer({server: srv, path: '/wss1'}); + } + catch (e) { + wss1.close(); + srv.close(); + done(); + } + }); + }); + }); + + describe('#close', function() { + it('will close all clients', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('close', function() { + if (++closes == 2) done(); + }); + }); + var closes = 0; + wss.on('connection', function(client) { + client.on('close', function() { + if (++closes == 2) done(); + }); + wss.close(); + }); + }); + + it('does not close a precreated server', function(done) { + var srv = http.createServer(); + var realClose = srv.close; + srv.close = function() { + should.fail('must not close pre-created server'); + } + srv.listen(++port, function () { + var wss = new WebSocketServer({server: srv}); + var ws = new WebSocket('ws://localhost:' + port); + wss.on('connection', function(client) { + wss.close(); + srv.close = realClose; + srv.close(); + done(); + }); + }); + }); + + it('cleans up websocket data on a precreated server', function(done) { + var srv = http.createServer(); + srv.listen(++port, function () { + var wss1 = new WebSocketServer({server: srv, path: '/wss1'}) + , wss2 = new WebSocketServer({server: srv, path: '/wss2'}); + (typeof srv._webSocketPaths).should.eql('object'); + Object.keys(srv._webSocketPaths).length.should.eql(2); + wss1.close(); + Object.keys(srv._webSocketPaths).length.should.eql(1); + wss2.close(); + (typeof srv._webSocketPaths).should.eql('undefined'); + srv.close(); + done(); + }); + }); + }); + + describe('#clients', function() { + it('returns a list of connected clients', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(1); + wss.close(); + done(); + }); + }); + + it('can be disabled', function(done) { + var wss = new WebSocketServer({port: ++port, clientTracking: false}, function() { + wss.clients.length.should.eql(0); + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + wss.clients.length.should.eql(0); + wss.close(); + done(); + }); + }); + + it('is updated when client terminates the connection', function(done) { + var ws; + var wss = new WebSocketServer({port: ++port}, function() { + ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + client.on('close', function() { + wss.clients.length.should.eql(0); + wss.close(); + done(); + }); + ws.terminate(); + }); + }); + + it('is updated when client closes the connection', function(done) { + var ws; + var wss = new WebSocketServer({port: ++port}, function() { + ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(client) { + client.on('close', function() { + wss.clients.length.should.eql(0); + wss.close(); + done(); + }); + ws.close(); + }); + }); + }); + + describe('#options', function() { + it('exposes options passed to constructor', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + wss.options.port.should.eql(port); + wss.close(); + done(); + }); + }); + }); + + describe('#handleUpgrade', function() { + it('can be used for a pre-existing server', function (done) { + var srv = http.createServer(); + srv.listen(++port, function () { + var wss = new WebSocketServer({noServer: true}); + srv.on('upgrade', function(req, socket, upgradeHead) { + wss.handleUpgrade(req, socket, upgradeHead, function(client) { + client.send('hello'); + }); + }); + var ws = new WebSocket('ws://localhost:' + port); + ws.on('message', function(message) { + message.should.eql('hello'); + wss.close(); + srv.close(); + done(); + }); + }); + }); + }); + + describe('hybi mode', function() { + describe('connection establishing', function() { + it('does not accept connections with no sec-websocket-key', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('does not accept connections with no sec-websocket-version', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('does not accept connections with invalid sec-websocket-version', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 12 + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('client can be denied', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + return false; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(401); + process.nextTick(function() { + wss.close(); + done(); + }); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('client can be accepted', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + return true; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + } + }; + var req = http.request(options); + req.end(); + }); + wss.on('connection', function(ws) { + ws.terminate(); + wss.close(); + done(); + }); + wss.on('error', function() {}); + }); + + it('verifyClient gets client origin', function(done) { + var verifyClientCalled = false; + var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + info.origin.should.eql('http://foobarbaz.com'); + verifyClientCalled = true; + return false; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobarbaz.com' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + verifyClientCalled.should.be.ok; + wss.close(); + done(); + }); + }); + wss.on('error', function() {}); + }); + + it('verifyClient gets original request', function(done) { + var verifyClientCalled = false; + var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + info.req.headers['sec-websocket-key'].should.eql('dGhlIHNhbXBsZSBub25jZQ=='); + verifyClientCalled = true; + return false; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobarbaz.com' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + verifyClientCalled.should.be.ok; + wss.close(); + done(); + }); + }); + wss.on('error', function() {}); + }); + + it('verifyClient has secure:true for ssl connections', function(done) { + var options = { + key: fs.readFileSync('test/fixtures/key.pem'), + cert: fs.readFileSync('test/fixtures/certificate.pem') + }; + var app = https.createServer(options, function (req, res) { + res.writeHead(200); + res.end(); + }); + var success = false; + var wss = new WebSocketServer({ + server: app, + verifyClient: function(info) { + success = info.secure === true; + return true; + } + }); + app.listen(++port, function() { + var ws = new WebSocket('wss://localhost:' + port); + }); + wss.on('connection', function(ws) { + app.close(); + ws.terminate(); + wss.close(); + success.should.be.ok; + done(); + }); + }); + + it('verifyClient has secure:false for non-ssl connections', function(done) { + var app = http.createServer(function (req, res) { + res.writeHead(200); + res.end(); + }); + var success = false; + var wss = new WebSocketServer({ + server: app, + verifyClient: function(info) { + success = info.secure === false; + return true; + } + }); + app.listen(++port, function() { + var ws = new WebSocket('ws://localhost:' + port); + }); + wss.on('connection', function(ws) { + app.close(); + ws.terminate(); + wss.close(); + success.should.be.ok; + done(); + }); + }); + + it('client can be denied asynchronously', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { + process.nextTick(function() { + cb(false); + }); + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 8, + 'Sec-WebSocket-Origin': 'http://foobar.com' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(401); + process.nextTick(function() { + wss.close(); + done(); + }); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('client can be accepted asynchronously', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { + process.nextTick(function() { + cb(true); + }); + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + } + }; + var req = http.request(options); + req.end(); + }); + wss.on('connection', function(ws) { + ws.terminate(); + wss.close(); + done(); + }); + wss.on('error', function() {}); + }); + + it('handles messages passed along with the upgrade request (upgrade head)', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + return true; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Origin': 'http://foobar.com' + } + }; + var req = http.request(options); + req.write(new Buffer([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f], 'binary')); + req.end(); + }); + wss.on('connection', function(ws) { + ws.on('message', function(data) { + data.should.eql('Hello'); + ws.terminate(); + wss.close(); + done(); + }); + }); + wss.on('error', function() {}); + }); + + it('selects the first protocol by default', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocol: 'prot1, prot2'}); + ws.on('open', function(client) { + ws.protocol.should.eql('prot1'); + wss.close(); + done(); + }); + }); + }); + + it('selects the last protocol via protocol handler', function(done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + cb(true, ps[ps.length-1]); }}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocol: 'prot1, prot2'}); + ws.on('open', function(client) { + ws.protocol.should.eql('prot2'); + wss.close(); + done(); + }); + }); + }); + + it('client detects invalid server protocol', function(done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + cb(true, 'prot3'); }}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocol: 'prot1, prot2'}); + ws.on('open', function(client) { + done(new Error('connection must not be established')); + }); + ws.on('error', function() { + done(); + }); + }); + }); + + it('client detects no server protocol', function(done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + cb(true); }}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocol: 'prot1, prot2'}); + ws.on('open', function(client) { + done(new Error('connection must not be established')); + }); + ws.on('error', function() { + done(); + }); + }); + }); + + it('client refuses server protocols', function(done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + cb(false); }}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocol: 'prot1, prot2'}); + ws.on('open', function(client) { + done(new Error('connection must not be established')); + }); + ws.on('error', function() { + done(); + }); + }); + }); + + it('server detects invalid protocol handler', function(done) { + var wss = new WebSocketServer({port: ++port, handleProtocols: function(ps, cb) { + // not calling callback is an error and shouldn't timeout + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Origin': 'http://foobar.com' + } + }; + options.port = port; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(501); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + }); + + describe('messaging', function() { + it('can send and receive data', function(done) { + var data = new Array(65*1024); + for (var i = 0; i < data.length; ++i) { + data[i] = String.fromCharCode(65 + ~~(25 * Math.random())); + } + data = data.join(''); + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port); + ws.on('message', function(message, flags) { + ws.send(message); + }); + }); + wss.on('connection', function(client) { + client.on('message', function(message) { + message.should.eql(data); + wss.close(); + done(); + }); + client.send(data); + }); + }); + }); + }); + + describe('hixie mode', function() { + it('can be disabled', function(done) { + var wss = new WebSocketServer({port: ++port, disableHixie: true}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(401); + process.nextTick(function() { + wss.close(); + done(); + }); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + describe('connection establishing', function() { + it('does not accept connections with no sec-websocket-key1', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('does not accept connections with no sec-websocket-key2', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('accepts connections with valid handshake', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + }); + wss.on('connection', function(ws) { + ws.terminate(); + wss.close(); + done(); + }); + wss.on('error', function() {}); + }); + + it('client can be denied', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + return false; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(401); + process.nextTick(function() { + wss.close(); + done(); + }); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('client can be accepted', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + return true; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + }); + wss.on('connection', function(ws) { + ws.terminate(); + wss.close(); + done(); + }); + wss.on('error', function() {}); + }); + + it('verifyClient gets client origin', function(done) { + var verifyClientCalled = false; + var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + info.origin.should.eql('http://foobarbaz.com'); + verifyClientCalled = true; + return false; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Origin': 'http://foobarbaz.com', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + req.on('response', function(res) { + verifyClientCalled.should.be.ok; + wss.close(); + done(); + }); + }); + wss.on('error', function() {}); + }); + + it('verifyClient gets original request', function(done) { + var verifyClientCalled = false; + var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { + info.req.headers['sec-websocket-key1'].should.eql('3e6b263 4 17 80'); + verifyClientCalled = true; + return false; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Origin': 'http://foobarbaz.com', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + req.on('response', function(res) { + verifyClientCalled.should.be.ok; + wss.close(); + done(); + }); + }); + wss.on('error', function() {}); + }); + + it('client can be denied asynchronously', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { + cb(false); + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Origin': 'http://foobarbaz.com', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + req.on('response', function(res) { + res.statusCode.should.eql(401); + process.nextTick(function() { + wss.close(); + done(); + }); + }); + }); + wss.on('connection', function(ws) { + done(new Error('connection must not be established')); + }); + wss.on('error', function() {}); + }); + + it('client can be accepted asynchronously', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { + cb(true); + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Origin': 'http://foobarbaz.com', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.end(); + }); + wss.on('connection', function(ws) { + wss.close(); + done(); + }); + wss.on('error', function() {}); + }); + + it('handles messages passed along with the upgrade request (upgrade head)', function(done) { + var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { + return true; + }}, function() { + var options = { + port: port, + host: '127.0.0.1', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '3e6b263 4 17 80', + 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90', + 'Origin': 'http://foobar.com' + } + }; + var req = http.request(options); + req.write('WjN}|M(6'); + req.write(new Buffer([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff], 'binary')); + req.end(); + }); + wss.on('connection', function(ws) { + ws.on('message', function(data) { + data.should.eql('Hello'); + ws.terminate(); + wss.close(); + done(); + }); + }); + wss.on('error', function() {}); + }); + }); + }); + + describe('client properties', function() { + it('protocol is exposed', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocol: 'hi'}); + }); + wss.on('connection', function(client) { + client.protocol.should.eql('hi'); + wss.close(); + done(); + }); + }); + + it('protocolVersion is exposed', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); + }); + wss.on('connection', function(client) { + client.protocolVersion.should.eql(8); + wss.close(); + done(); + }); + }); + + it('upgradeReq is the original request object', function(done) { + var wss = new WebSocketServer({port: ++port}, function() { + var ws = new WebSocket('ws://localhost:' + port, {protocolVersion: 8}); + }); + wss.on('connection', function(client) { + client.upgradeReq.httpVersion.should.eql('1.1'); + wss.close(); + done(); + }); + }); + }); + +}); diff --git a/node_modules/ws/test/autobahn-server.js b/node_modules/ws/test/autobahn-server.js new file mode 100644 index 0000000..36fe0c2 --- /dev/null +++ b/node_modules/ws/test/autobahn-server.js @@ -0,0 +1,29 @@ +var WebSocketServer = require('../').Server; + +process.on('uncaughtException', function(err) { + console.log('Caught exception: ', err, err.stack); +}); + +process.on('SIGINT', function () { + try { + console.log('Updating reports and shutting down'); + var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); + ws.on('close', function() { + process.exit(); + }); + } + catch(e) { + process.exit(); + } +}); + +var wss = new WebSocketServer({port: 8181}); +wss.on('connection', function(ws) { + console.log('new connection'); + ws.on('message', function(data, flags) { + ws.send(flags.buffer, {binary: flags.binary === true}); + }); + ws.on('error', function() { + console.log('error', arguments); + }); +}); diff --git a/node_modules/ws/test/autobahn.js b/node_modules/ws/test/autobahn.js new file mode 100644 index 0000000..048cc90 --- /dev/null +++ b/node_modules/ws/test/autobahn.js @@ -0,0 +1,52 @@ +var WebSocket = require('../'); +var currentTest = 1; +var lastTest = -1; +var testCount = null; + +process.on('uncaughtException', function(err) { + console.log('Caught exception: ', err, err.stack); +}); + +process.on('SIGINT', function () { + try { + console.log('Updating reports and shutting down'); + var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); + ws.on('close', function() { + process.exit(); + }); + } + catch(e) { + process.exit(); + } +}); + +function nextTest() { + if (currentTest > testCount || (lastTest != -1 && currentTest > lastTest)) { + console.log('Updating reports and shutting down'); + var ws = new WebSocket('ws://localhost:9001/updateReports?agent=ws'); + ws.on('close', function() { + process.exit(); + }); + return; + }; + console.log('Running test case ' + currentTest + '/' + testCount); + var ws = new WebSocket('ws://localhost:9001/runCase?case=' + currentTest + '&agent=ws'); + ws.on('message', function(data, flags) { + ws.send(flags.buffer, {binary: flags.binary === true, mask: true}); + }); + ws.on('close', function(data) { + currentTest += 1; + process.nextTick(nextTest); + }); + ws.on('error', function(e) {}); +} + +var ws = new WebSocket('ws://localhost:9001/getCaseCount'); +ws.on('message', function(data, flags) { + testCount = parseInt(data); +}); +ws.on('close', function() { + if (testCount > 0) { + nextTest(); + } +}); \ No newline at end of file diff --git a/node_modules/ws/test/fixtures/agent1-cert.pem b/node_modules/ws/test/fixtures/agent1-cert.pem new file mode 100644 index 0000000..cccb9fb --- /dev/null +++ b/node_modules/ws/test/fixtures/agent1-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICbjCCAdcCCQCVvok5oeLpqzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO +BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA +dGlueWNsb3Vkcy5vcmcwHhcNMTMwMzA4MDAzMDIyWhcNNDAwNzIzMDAzMDIyWjB9 +MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK +EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MTEgMB4G +CSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAL6GwKosYb0Yc3Qo0OtQVlCJ4208Idw11ij+t2W5sfYbCil5tyQo +jnhGM1CJhEXynQpXXwjKJuIeTQCkeUibTyFKa0bs8+li2FiGoKYbb4G81ovnqkmE +2iDVb8Gw3rrM4zeZ0ZdFnjMsAZac8h6+C4sB/pS9BiMOo6qTl15RQlcJAgMBAAEw +DQYJKoZIhvcNAQEFBQADgYEAOtmLo8DwTPnI4wfQbQ3hWlTS/9itww6IsxH2ODt9 +ggB7wi7N3uAdIWRZ54ke0NEAO5CW1xNTwsWcxQbiHrDOqX1vfVCjIenI76jVEEap +/Ay53ydHNBKdsKkib61Me14Mu0bA3lUul57VXwmH4NUEFB3w973Q60PschUhOEXj +7DY= +-----END CERTIFICATE----- diff --git a/node_modules/ws/test/fixtures/agent1-key.pem b/node_modules/ws/test/fixtures/agent1-key.pem new file mode 100644 index 0000000..cbd5f0c --- /dev/null +++ b/node_modules/ws/test/fixtures/agent1-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC+hsCqLGG9GHN0KNDrUFZQieNtPCHcNdYo/rdlubH2Gwopebck +KI54RjNQiYRF8p0KV18IyibiHk0ApHlIm08hSmtG7PPpYthYhqCmG2+BvNaL56pJ +hNog1W/BsN66zOM3mdGXRZ4zLAGWnPIevguLAf6UvQYjDqOqk5deUUJXCQIDAQAB +AoGANu/CBA+SCyVOvRK70u4yRTzNMAUjukxnuSBhH1rg/pajYnwvG6T6F6IeT72n +P0gKkh3JUE6B0bds+p9yPUZTFUXghxjcF33wlIY44H6gFE4K5WutsFJ9c450wtuu +8rXZTsIg7lAXWjTFVmdtOEPetcGlO2Hpi1O7ZzkzHgB2w9ECQQDksCCYx78or1zY +ZSokm8jmpIjG3VLKdvI9HAoJRN40ldnwFoigrFa1AHwsFtWNe8bKyVRPDoLDUjpB +dkPWgweVAkEA1UfgqguQ2KIkbtp9nDBionu3QaajksrRHwIa8vdfRfLxszfHk2fh +NGY3dkRZF8HUAbzYLrd9poVhCBAEjWekpQJASOM6AHfpnXYHCZF01SYx6hEW5wsz +kARJQODm8f1ZNTlttO/5q/xBxn7ZFNRSTD3fJlL05B2j380ddC/Vf1FT4QJAP1BC +GliqnBSuGhZUWYxni3KMeTm9rzL0F29pjpzutHYlWB2D6ndY/FQnvL0XcZ0Bka58 +womIDGnl3x3aLBwLXQJBAJv6h5CHbXHx7VyDJAcNfppAqZGcEaiVg8yf2F33iWy2 +FLthhJucx7df7SO2aw5h06bRDRAhb9br0R9/3mLr7RE= +-----END RSA PRIVATE KEY----- diff --git a/node_modules/ws/test/fixtures/ca1-cert.pem b/node_modules/ws/test/fixtures/ca1-cert.pem new file mode 100644 index 0000000..1d0c0d6 --- /dev/null +++ b/node_modules/ws/test/fixtures/ca1-cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICazCCAdQCCQC9/g69HtxXRzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO +BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA +dGlueWNsb3Vkcy5vcmcwHhcNMTMwMzA4MDAzMDIyWhcNNDAwNzIzMDAzMDIyWjB6 +MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK +EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqG +SIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBAKxr1mARUcv7zaqx5y4AxJPK6c1jdbSg7StcL4vg8klaPAlfNO6o+/Cl +w5CdQD3ukaVUwUOJ4T/+b3Xf7785XcWBC33GdjVQkfbHATJYcka7j7JDw3qev5Jk +1rAbRw48hF6rYlSGcx1mccAjoLoa3I8jgxCNAYHIjUQXgdmU893rAgMBAAEwDQYJ +KoZIhvcNAQEFBQADgYEAis05yxjCtJRuv8uX/DK6TX/j9C9Lzp1rKDNFTaTZ0iRw +KCw1EcNx4OXSj9gNblW4PWxpDvygrt1AmH9h2cb8K859NSHa9JOBFw6MA5C2A4Sj +NQfNATqUl4T6cdORlcDEZwHtT8b6D4A6Er31G/eJF4Sen0TUFpjdjd+l9RBjHlo= +-----END CERTIFICATE----- diff --git a/node_modules/ws/test/fixtures/ca1-key.pem b/node_modules/ws/test/fixtures/ca1-key.pem new file mode 100644 index 0000000..df14950 --- /dev/null +++ b/node_modules/ws/test/fixtures/ca1-key.pem @@ -0,0 +1,17 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIFeWxJE1BrRECAggA +MBQGCCqGSIb3DQMHBAgu9PlMSQ+BOASCAoDEZN2tX0xWo/N+Jg+PrvCrFDk3P+3x +5xG/PEDjtMCAWPBEwbnaYHDzYmhNcAmxzGqEHGMDiWYs46LbO560VS3uMvFbEWPo +KYYVb13vkxl2poXdonCb5cHZA5GUYzTIVVJFptl4LHwBczHoMHtA4FqAhKlYvlWw +EOrdLB8XcwMmGPFabbbGxno0+EWWM27uNjlogfoxj35mQqSW4rOlhZ460XjOB1Zx +LjXMuZeONojkGYQRG5EUMchBoctQpCOM6cAi9r1B9BvtFCBpDV1c1zEZBzTEUd8o +kLn6tjLmY+QpTdylFjEWc7U3ppLY/pkoTBv4r85a2sEMWqkhSJboLaTboWzDJcU3 +Ke61pMpovt/3yCUd3TKgwduVwwQtDVTlBe0p66aN9QVj3CrFy/bKAGO3vxlli24H +aIjZf+OVoBY21ESlW3jLvNlBf7Ezf///2E7j4SCDLyZSFMTpFoAG/jDRyvi+wTKX +Kh485Bptnip6DCSuoH4u2SkOqwz3gJS/6s02YKe4m311QT4Pzne5/FwOFaS/HhQg +Xvyh2/d00OgJ0Y0PYQsHILPRgTUCKUXvj1O58opn3fxSacsPxIXwj6Z4FYAjUTaV +2B85k1lpant/JJEilDqMjqzx4pHZ/Z3Uto1lSM1JZs9SNL/0UR+6F0TXZTULVU9V +w8jYzz4sPr7LEyrrTbzmjQgnQFVbhAN/eKgRZK/SpLjxpmBV5MfpbPKsPUZqT4UC +4nXa8a/NYUQ9e+QKK8enq9E599c2W442W7Z1uFRZTWReMx/lF8wwA6G8zOPG0bdj +d+T5Gegzd5mvRiXMBklCo8RLxOOvgxun1n3PY4a63aH6mqBhdfhiLp5j +-----END ENCRYPTED PRIVATE KEY----- diff --git a/node_modules/ws/test/fixtures/certificate.pem b/node_modules/ws/test/fixtures/certificate.pem new file mode 100644 index 0000000..0efc2ef --- /dev/null +++ b/node_modules/ws/test/fixtures/certificate.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICATCCAWoCCQDPufXH86n2QzANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJu +bzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTEyMDEwMTE0NDQwMFoXDTIwMDMxOTE0NDQwMFowRTELMAkG +A1UEBhMCbm8xEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtrQ7 ++r//2iV/B6F+4boH0XqFn7alcV9lpjvAmwRXNKnxAoa0f97AjYPGNLKrjpkNXXhB +JROIdbRbZnCNeC5fzX1a+JCo7KStzBXuGSZr27TtFmcV4H+9gIRIcNHtZmJLnxbJ +sIhkGR8yVYdmJZe4eT5ldk1zoB1adgPF1hZhCBMCAwEAATANBgkqhkiG9w0BAQUF +AAOBgQCeWBEHYJ4mCB5McwSSUox0T+/mJ4W48L/ZUE4LtRhHasU9hiW92xZkTa7E +QLcoJKQiWfiLX2ysAro0NX4+V8iqLziMqvswnPzz5nezaOLE/9U/QvH3l8qqNkXu +rNbsW1h/IO6FV8avWFYVFoutUwOaZ809k7iMh2F2JMgXQ5EymQ== +-----END CERTIFICATE----- diff --git a/node_modules/ws/test/fixtures/key.pem b/node_modules/ws/test/fixtures/key.pem new file mode 100644 index 0000000..176fe32 --- /dev/null +++ b/node_modules/ws/test/fixtures/key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC2tDv6v//aJX8HoX7hugfReoWftqVxX2WmO8CbBFc0qfEChrR/ +3sCNg8Y0squOmQ1deEElE4h1tFtmcI14Ll/NfVr4kKjspK3MFe4ZJmvbtO0WZxXg +f72AhEhw0e1mYkufFsmwiGQZHzJVh2Yll7h5PmV2TXOgHVp2A8XWFmEIEwIDAQAB +AoGAAlVY8sHi/aE+9xT77twWX3mGHV0SzdjfDnly40fx6S1Gc7bOtVdd9DC7pk6l +3ENeJVR02IlgU8iC5lMHq4JEHPE272jtPrLlrpWLTGmHEqoVFv9AITPqUDLhB9Kk +Hjl7h8NYBKbr2JHKICr3DIPKOT+RnXVb1PD4EORbJ3ooYmkCQQDfknUnVxPgxUGs +ouABw1WJIOVgcCY/IFt4Ihf6VWTsxBgzTJKxn3HtgvE0oqTH7V480XoH0QxHhjLq +DrgobWU9AkEA0TRJ8/ouXGnFEPAXjWr9GdPQRZ1Use2MrFjneH2+Sxc0CmYtwwqL +Kr5kS6mqJrxprJeluSjBd+3/ElxURrEXjwJAUvmlN1OPEhXDmRHd92mKnlkyKEeX +OkiFCiIFKih1S5Y/sRJTQ0781nyJjtJqO7UyC3pnQu1oFEePL+UEniRztQJAMfav +AtnpYKDSM+1jcp7uu9BemYGtzKDTTAYfoiNF42EzSJiGrWJDQn4eLgPjY0T0aAf/ +yGz3Z9ErbhMm/Ysl+QJBAL4kBxRT8gM4ByJw4sdOvSeCCANFq8fhbgm8pGWlCPb5 +JGmX3/GHFM8x2tbWMGpyZP1DLtiNEFz7eCGktWK5rqE= +-----END RSA PRIVATE KEY----- diff --git a/node_modules/ws/test/fixtures/request.pem b/node_modules/ws/test/fixtures/request.pem new file mode 100644 index 0000000..51bc7f6 --- /dev/null +++ b/node_modules/ws/test/fixtures/request.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBhDCB7gIBADBFMQswCQYDVQQGEwJubzETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQC2tDv6v//aJX8HoX7hugfReoWftqVxX2WmO8CbBFc0qfEC +hrR/3sCNg8Y0squOmQ1deEElE4h1tFtmcI14Ll/NfVr4kKjspK3MFe4ZJmvbtO0W +ZxXgf72AhEhw0e1mYkufFsmwiGQZHzJVh2Yll7h5PmV2TXOgHVp2A8XWFmEIEwID +AQABoAAwDQYJKoZIhvcNAQEFBQADgYEAjsUXEARgfxZNkMjuUcudgU2w4JXS0gGI +JQ0U1LmU0vMDSKwqndMlvCbKzEgPbJnGJDI8D4MeINCJHa5Ceyb8c+jaJYUcCabl +lQW5Psn3+eWp8ncKlIycDRj1Qk615XuXtV0fhkrgQM2ZCm9LaQ1O1Gd/CzLihLjF +W0MmgMKMMRk= +-----END CERTIFICATE REQUEST----- diff --git a/node_modules/ws/test/fixtures/textfile b/node_modules/ws/test/fixtures/textfile new file mode 100644 index 0000000..a10483b --- /dev/null +++ b/node_modules/ws/test/fixtures/textfile @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam egestas, massa at aliquam luctus, sapien erat viverra elit, nec pulvinar turpis eros sagittis urna. Pellentesque imperdiet tempor varius. Pellentesque blandit, ipsum in imperdiet venenatis, mi elit faucibus odio, id condimentum ante enim sed lectus. Aliquam et odio non odio pellentesque pulvinar. Vestibulum a erat dolor. Integer pretium risus sit amet nisl volutpat nec venenatis magna egestas. Ut bibendum felis eu tellus laoreet eleifend. Nam pulvinar auctor tortor, eu iaculis leo vestibulum quis. In euismod risus ac purus vehicula et fermentum ligula consectetur. Vivamus condimentum tempus lacinia. + +Curabitur sodales condimentum urna id dictum. Sed quis justo sit amet quam ultrices tincidunt vel laoreet nulla. Nullam quis ipsum sed nisi mollis bibendum at sit amet nisi. Donec laoreet consequat velit sit amet mollis. Nam sed sapien a massa iaculis dapibus. Sed dui nunc, ultricies et pellentesque ullamcorper, aliquet vitae ligula. Integer eu velit in neque iaculis venenatis. Ut rhoncus cursus est, ac dignissim leo vehicula a. Nulla ullamcorper vulputate mauris id blandit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eleifend, nisi a tempor sollicitudin, odio massa pretium urna, quis congue sapien elit at tortor. Curabitur ipsum orci, vehicula non commodo molestie, laoreet id enim. Pellentesque convallis ultrices congue. Pellentesque nec iaculis lorem. In sagittis pharetra ipsum eget sodales. + +Fusce id nulla odio. Nunc nibh justo, placerat vel tincidunt sed, ornare et enim. Nulla vel urna vel ante commodo bibendum in vitae metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis erat nunc, semper eget sagittis sit amet, ullamcorper eget lacus. Donec hendrerit ipsum vitae eros vestibulum eu gravida neque tincidunt. Ut molestie lacinia nulla. Donec mattis odio at magna egestas at pellentesque eros accumsan. Praesent interdum sem sit amet nibh commodo dignissim. Duis laoreet, enim ultricies fringilla suscipit, enim libero cursus nulla, sollicitudin adipiscing erat velit ut dui. Nulla eleifend mauris at velit fringilla a molestie lorem venenatis. + +Donec sit amet scelerisque metus. Cras ac felis a nulla venenatis vulputate. Duis porttitor eros ac neque rhoncus eget aliquet neque egestas. Quisque sed nunc est, vitae dapibus quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In vehicula, est vitae posuere ultricies, diam purus pretium sapien, nec rhoncus dolor nisl eget arcu. Aliquam et nisi vitae risus tincidunt auctor. In vehicula, erat a cursus adipiscing, lorem orci congue est, nec ultricies elit dui in nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Duis congue tempus elit sit amet auctor. Duis dignissim, risus ut sollicitudin ultricies, dolor ligula gravida odio, nec congue orci purus ut ligula. Fusce pretium dictum lectus at volutpat. Sed non auctor mauris. Etiam placerat vestibulum massa id blandit. Quisque consequat lacus ut nulla euismod facilisis. Sed aliquet ipsum nec mi imperdiet viverra. Pellentesque ullamcorper, lectus nec varius gravida, odio justo cursus risus, eu sagittis metus arcu quis felis. Phasellus consectetur vehicula libero, at condimentum orci euismod vel. Nunc purus tortor, suscipit nec fringilla nec, vulputate et nibh. Nam porta vehicula neque. Praesent porttitor, sapien eu auctor euismod, arcu quam elementum urna, sed hendrerit magna augue sed quam. \ No newline at end of file diff --git a/node_modules/ws/test/hybi-common.js b/node_modules/ws/test/hybi-common.js new file mode 100644 index 0000000..006f9c6 --- /dev/null +++ b/node_modules/ws/test/hybi-common.js @@ -0,0 +1,99 @@ +/** + * Returns a Buffer from a "ff 00 ff"-type hex string. + */ + +getBufferFromHexString = function(byteStr) { + var bytes = byteStr.split(' '); + var buf = new Buffer(bytes.length); + for (var i = 0; i < bytes.length; ++i) { + buf[i] = parseInt(bytes[i], 16); + } + return buf; +} + +/** + * Returns a hex string from a Buffer. + */ + +getHexStringFromBuffer = function(data) { + var s = ''; + for (var i = 0; i < data.length; ++i) { + s += padl(data[i].toString(16), 2, '0') + ' '; + } + return s.trim(); +} + +/** + * Splits a buffer in two parts. + */ + +splitBuffer = function(buffer) { + var b1 = new Buffer(Math.ceil(buffer.length / 2)); + buffer.copy(b1, 0, 0, b1.length); + var b2 = new Buffer(Math.floor(buffer.length / 2)); + buffer.copy(b2, 0, b1.length, b1.length + b2.length); + return [b1, b2]; +} + +/** + * Performs hybi07+ type masking on a hex string or buffer. + */ + +mask = function(buf, maskString) { + if (typeof buf == 'string') buf = new Buffer(buf); + var mask = getBufferFromHexString(maskString || '34 83 a8 68'); + for (var i = 0; i < buf.length; ++i) { + buf[i] ^= mask[i % 4]; + } + return buf; +} + +/** + * Returns a hex string representing the length of a message + */ + +getHybiLengthAsHexString = function(len, masked) { + if (len < 126) { + var buf = new Buffer(1); + buf[0] = (masked ? 0x80 : 0) | len; + } + else if (len < 65536) { + var buf = new Buffer(3); + buf[0] = (masked ? 0x80 : 0) | 126; + getBufferFromHexString(pack(4, len)).copy(buf, 1); + } + else { + var buf = new Buffer(9); + buf[0] = (masked ? 0x80 : 0) | 127; + getBufferFromHexString(pack(16, len)).copy(buf, 1); + } + return getHexStringFromBuffer(buf); +} + +/** + * Unpacks a Buffer into a number. + */ + +unpack = function(buffer) { + var n = 0; + for (var i = 0; i < buffer.length; ++i) { + n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; + } + return n; +} + +/** + * Returns a hex string, representing a specific byte count 'length', from a number. + */ + +pack = function(length, number) { + return padl(number.toString(16), length, '0').replace(/([0-9a-f][0-9a-f])/gi, '$1 ').trim(); +} + +/** + * Left pads the string 's' to a total length of 'n' with char 'c'. + */ + +padl = function(s, n, c) { + return new Array(1 + n - s.length).join(c) + s; +} diff --git a/node_modules/ws/test/testserver.js b/node_modules/ws/test/testserver.js new file mode 100644 index 0000000..3e7a966 --- /dev/null +++ b/node_modules/ws/test/testserver.js @@ -0,0 +1,180 @@ +var http = require('http') + , util = require('util') + , crypto = require('crypto') + , events = require('events') + , Sender = require('../lib/Sender') + , Receiver = require('../lib/Receiver'); + +module.exports = { + handlers: { + valid: validServer, + invalidKey: invalidRequestHandler, + closeAfterConnect: closeAfterConnectHandler, + return401: return401 + }, + createServer: function(port, handler, cb) { + if (handler && !cb) { + cb = handler; + handler = null; + } + var webServer = http.createServer(function (req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('okay'); + }); + var srv = new Server(webServer); + webServer.on('upgrade', function(req, socket) { + webServer._socket = socket; + (handler || validServer)(srv, req, socket); + }); + webServer.listen(port, '127.0.0.1', function() { cb(srv); }); + } +}; + +/** + * Test strategies + */ + +function validServer(server, req, socket) { + if (typeof req.headers.upgrade === 'undefined' || + req.headers.upgrade.toLowerCase() !== 'websocket') { + throw new Error('invalid headers'); + return; + } + + if (!req.headers['sec-websocket-key']) { + socket.end(); + throw new Error('websocket key is missing'); + } + + // calc key + var key = req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + socket.write(headers.concat('', '').join('\r\n')); + socket.setTimeout(0); + socket.setNoDelay(true); + + var sender = new Sender(socket); + var receiver = new Receiver(); + receiver.ontext = function (message, flags) { + server.emit('message', message, flags); + sender.send(message); + }; + receiver.onbinary = function (message, flags) { + flags = flags || {}; + flags.binary = true; + server.emit('message', message, flags); + sender.send(message, {binary: true}); + }; + receiver.onping = function (message, flags) { + flags = flags || {}; + server.emit('ping', message, flags); + }; + receiver.onpong = function (message, flags) { + flags = flags || {}; + server.emit('pong', message, flags); + }; + receiver.onclose = function (code, message, flags) { + flags = flags || {}; + server.emit('close', code, message, flags); + }; + socket.on('data', function (data) { + receiver.add(data); + }); + socket.on('end', function() { + socket.end(); + }); +} + +function invalidRequestHandler(server, req, socket) { + if (typeof req.headers.upgrade === 'undefined' || + req.headers.upgrade.toLowerCase() !== 'websocket') { + throw new Error('invalid headers'); + return; + } + + if (!req.headers['sec-websocket-key']) { + socket.end(); + throw new Error('websocket key is missing'); + } + + // calc key + var key = req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "bogus"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + socket.write(headers.concat('', '').join('\r\n')); + socket.end(); +} + +function closeAfterConnectHandler(server, req, socket) { + if (typeof req.headers.upgrade === 'undefined' || + req.headers.upgrade.toLowerCase() !== 'websocket') { + throw new Error('invalid headers'); + return; + } + + if (!req.headers['sec-websocket-key']) { + socket.end(); + throw new Error('websocket key is missing'); + } + + // calc key + var key = req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + socket.write(headers.concat('', '').join('\r\n')); + socket.end(); +} + + +function return401(server, req, socket) { + var headers = [ + 'HTTP/1.1 401 Unauthorized' + , 'Content-type: text/html' + ]; + + socket.write(headers.concat('', '').join('\r\n')); + socket.end(); +} + +/** + * Server object, which will do the actual emitting + */ + +function Server(webServer) { + this.webServer = webServer; +} + +util.inherits(Server, events.EventEmitter); + +Server.prototype.close = function() { + this.webServer.close(); + if (this._socket) this._socket.end(); +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..badbf2b --- /dev/null +++ b/server.js @@ -0,0 +1,102 @@ +// npm install ws en jsftp +var sys = require("sys"), + fs = require("fs"), + Ftp = require("jsftp"), + WebSocketServer = require('ws').Server; + +function hour() { return new Date().getHours(); }; + +var wss = new WebSocketServer({port: 8080}); +var clients = []; +var snapshotHour = -1; // start auto-capping the history feed. +var imageName = "image.jpg"; + +function convertDataURIToBinary(dataURI) { + var BASE64_MARKER = ';base64,'; + var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length; + var base64 = dataURI.substring(base64Index); + return new Buffer(base64, 'base64'); // atob(base64) in window scope +} + +function reuploadImage() { + var user = "FILL ME IN"; + var pass = "FILL ME IN"; + var rootPath = "/domains/brainbaking.com/public_html/cam/"; + + var ftp = new Ftp({ + host: "ftp.brainbaking.com", + port: 21 + }); + + ftp.auth(user, pass, function(err, res) { + function uploadImageTo(path, callback) { + ftp.put(imageName, path, function(err, data) { + if(err) { + sys.debug("ERROR while uploading"); + sys.debug(err); + } else { + sys.debug("file uploaded to " + path + " - callback firing..."); + callback(data); + } + }); + } + + function uploadSnapshotIndexFile() { + var images = { files: [] }; + ftp.ls(rootPath, function(err, list) { + + images.files = list.filter(function(file) { + return file.name.indexOf('.jpg') > 0 && file.name.indexOf('_') > 0; + }); + + fs.writeFile("index.json", JSON.stringify(images), function(e) { + if(e) throw e; + + ftp.put("index.json", rootPath + "index.json", function(e) { + if(e) throw e; + sys.debug("written index JSON file."); + }); + }); + + }); + } + + function createHistorySnapshotIfNeeded() { + if(hour() !== snapshotHour) { + sys.debug("Hourly snapshot needs refreshment, creating..."); + snapshotHour = hour(); + uploadImageTo(rootPath + new Date().getTime() + "_" + imageName, uploadSnapshotIndexFile); + } + + } + + uploadImageTo(rootPath + imageName, createHistorySnapshotIfNeeded); + }); +} + +wss.on('connection', function(ws) { + sys.debug("connected client"); + clients.push(ws); + ws.on('message', function(message) { + sys.debug('received data - broadcasting back to client...'); + fs.writeFile(imageName, convertDataURIToBinary(message), function(e) { + if(e) { + sys.debug(e); + } else { + sys.debug("file written, starting upload..."); + reuploadImage(); + } + }); + + clients.forEach(function(client) { + try { + client.send(message); + } catch(notOpenEx) { + // should I remove the client? Can I do a check instead of try/catch? + sys.debug("client not connected anymore, ignoring exception"); + } + }); + }); +}); + +sys.debug("Listening on port 8080"); diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..d3683a7 --- /dev/null +++ b/start.sh @@ -0,0 +1,2 @@ +python -m SimpleHTTPServer 8000 +