├── templatefs ├── .gitignore ├── extension │ ├── icon │ │ └── .gitkeep │ ├── _locales │ │ └── en │ │ │ └── messages.json │ └── manifest.json ├── ui │ ├── style.css │ ├── auth.html │ └── auth.js ├── package.json ├── js │ ├── events │ │ ├── onOpenFileRequested.js │ │ ├── onCloseFileRequested.js │ │ ├── onReadFileRequested.js │ │ ├── onReadDirectoryRequested.js │ │ └── onGetMetadataRequested.js │ └── main.js ├── readme.md ├── test │ └── spec │ │ └── events.js └── Gruntfile.js ├── testserver ├── .gitignore ├── package.json ├── config.js └── server.js ├── .bowerrc ├── s3fs ├── .gitignore ├── psd │ ├── s3-128.psd │ ├── s3-16.psd │ ├── s3-32.psd │ └── s3-48.psd ├── extension │ ├── icon │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 32.png │ │ └── 48.png │ ├── manifest.json │ └── _locales │ │ └── en │ │ └── messages.json ├── ui │ ├── style.css │ ├── auth.html │ ├── auth.js │ └── awsvalidator.js ├── package.json ├── test │ └── spec │ │ ├── helper.js │ │ ├── s3fs.js │ │ ├── util.js │ │ ├── events.js │ │ ├── awsvalidator.js │ │ └── s3mock.js ├── readme.md ├── js │ ├── s3fs.js │ ├── errors.js │ ├── main.js │ └── events.js └── Gruntfile.js ├── webdavfs ├── .gitignore ├── psd │ ├── webdav-128.psd │ ├── webdav-16.psd │ ├── webdav-32.psd │ └── webdav-48.psd ├── extension │ ├── icon │ │ ├── 16.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 128.png │ ├── manifest.json │ └── _locales │ │ └── en │ │ └── messages.json ├── ui │ ├── style.css │ ├── auth.html │ └── auth.js ├── package.json ├── js │ ├── errors.js │ ├── main.js │ ├── wdfs.js │ ├── client.js │ └── events.js ├── test │ └── spec │ │ ├── wdfs.js │ │ ├── helper.js │ │ ├── events.js │ │ └── client.js ├── readme.md └── Gruntfile.js ├── .gitignore ├── tools ├── package.json └── scaffolder.js ├── .travis.yml ├── bower.json ├── shared_tests ├── chromemock.js ├── onCreateFileRequested.js ├── onCloseFileRequested.js ├── onGetMetadataRequested.js ├── onOpenFileRequested.js ├── onReadDirectoryRequested.js ├── onTruncateRequested.js ├── onWriteFileRequested.js ├── onCopyEntryRequested.js ├── onReadFileRequested.js ├── onDeleteEntryRequested.js └── onMoveEntryRequested.js ├── shared ├── i18n.js └── util.js ├── karma.conf.js ├── LICENSE ├── CONTRIBUTING.md └── README.md /templatefs/.gitignore: -------------------------------------------------------------------------------- 1 | ui/build.js 2 | -------------------------------------------------------------------------------- /templatefs/extension/icon/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testserver/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "third_party" 3 | } 4 | -------------------------------------------------------------------------------- /s3fs/.gitignore: -------------------------------------------------------------------------------- 1 | extension/aws-sdk.js 2 | ui/build.js 3 | docs 4 | -------------------------------------------------------------------------------- /webdavfs/.gitignore: -------------------------------------------------------------------------------- 1 | extension/build.* 2 | ui/build.js 3 | docs 4 | -------------------------------------------------------------------------------- /s3fs/psd/s3-128.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/psd/s3-128.psd -------------------------------------------------------------------------------- /s3fs/psd/s3-16.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/psd/s3-16.psd -------------------------------------------------------------------------------- /s3fs/psd/s3-32.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/psd/s3-32.psd -------------------------------------------------------------------------------- /s3fs/psd/s3-48.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/psd/s3-48.psd -------------------------------------------------------------------------------- /s3fs/extension/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/extension/icon/128.png -------------------------------------------------------------------------------- /s3fs/extension/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/extension/icon/16.png -------------------------------------------------------------------------------- /s3fs/extension/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/extension/icon/32.png -------------------------------------------------------------------------------- /s3fs/extension/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/s3fs/extension/icon/48.png -------------------------------------------------------------------------------- /webdavfs/psd/webdav-128.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/psd/webdav-128.psd -------------------------------------------------------------------------------- /webdavfs/psd/webdav-16.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/psd/webdav-16.psd -------------------------------------------------------------------------------- /webdavfs/psd/webdav-32.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/psd/webdav-32.psd -------------------------------------------------------------------------------- /webdavfs/psd/webdav-48.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/psd/webdav-48.psd -------------------------------------------------------------------------------- /webdavfs/extension/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/extension/icon/16.png -------------------------------------------------------------------------------- /webdavfs/extension/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/extension/icon/32.png -------------------------------------------------------------------------------- /webdavfs/extension/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/extension/icon/48.png -------------------------------------------------------------------------------- /webdavfs/extension/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/chromeos-filesystems/HEAD/webdavfs/extension/icon/128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File types 2 | *.crx 3 | *.old 4 | *.zip 5 | *.DS_Store 6 | log.* 7 | 8 | # Library directories 9 | */node_modules 10 | third_party 11 | 12 | # Generated files 13 | */extension/build.* 14 | */extension/background.js 15 | */test/build 16 | 17 | # Example files 18 | example 19 | -------------------------------------------------------------------------------- /templatefs/extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "{{ displayName }}", 4 | "description": "Extension name." 5 | }, 6 | "extDescription": { 7 | "message": "{{ description }}", 8 | "description": "Extension description." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tools", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "scaffolder.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Giles Lavelle ", 10 | "license": "BSD", 11 | "dependencies": { 12 | "colors": "^0.6.2", 13 | "prompt": "^0.2.13", 14 | "shelljs": "^0.3.0", 15 | "underscore": "^1.7.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /testserver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testserver", 3 | "version": "0.0.1", 4 | "description": "Simple WebDAV server for testing", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "Giles Lavelle ", 11 | "license": "BSD", 12 | "dependencies": { 13 | "jsDAV": "^0.3.2", 14 | "mock-fs": "^2.3.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webdavfs/ui/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: Helvetica, Arial, sans-serif; 7 | margin: 0px; 8 | padding: 0px; 9 | } 10 | 11 | .container { 12 | margin: 0 auto; 13 | width: 500px; 14 | } 15 | 16 | paper-input { 17 | display: block; 18 | font-size: 20px; 19 | padding: 15px; 20 | width: 500px; 21 | } 22 | 23 | paper-button { 24 | display: block; 25 | margin: 0 auto; 26 | width: 100px; 27 | } 28 | -------------------------------------------------------------------------------- /templatefs/ui/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: Helvetica, Arial, sans-serif; 7 | margin: 0px; 8 | padding: 0px; 9 | } 10 | 11 | .container { 12 | margin: 0 auto; 13 | width: 500px; 14 | } 15 | 16 | paper-input { 17 | display: block; 18 | font-size: 20px; 19 | padding: 15px; 20 | width: 500px; 21 | } 22 | 23 | paper-button { 24 | display: block; 25 | margin: 0 auto; 26 | width: 100px; 27 | } 28 | -------------------------------------------------------------------------------- /s3fs/ui/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: Helvetica, Arial, sans-serif; 7 | margin: 0px; 8 | padding: 0px; 9 | } 10 | 11 | .container { 12 | margin: 0 auto; 13 | width: 500px; 14 | } 15 | 16 | paper-input, paper-dropdown { 17 | display: block; 18 | font-size: 20px; 19 | padding: 15px; 20 | width: 500px; 21 | } 22 | 23 | paper-button { 24 | display: block; 25 | margin: 0 auto; 26 | width: 100px; 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | env: 5 | - DIR=s3fs 6 | - DIR=webdavfs 7 | before_install: 8 | - npm install -g grunt-cli bower 9 | - bower install 10 | - cd testserver 11 | - npm install 12 | - node server.js & 13 | - cd .. 14 | - sleep 5 15 | install: 16 | - cd $DIR 17 | - npm install 18 | - cd .. 19 | before_script: 20 | - export DISPLAY=:99.0 21 | - sh -e /etc/init.d/xvfb start 22 | script: 23 | - cd $DIR 24 | - grunt lint 25 | - grunt test 26 | -------------------------------------------------------------------------------- /templatefs/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extName__", 3 | "description": "__MSG_extName__", 4 | "version": "0.0.1", 5 | "manifest_version": 2, 6 | "default_locale": "en", 7 | "app": { 8 | "background": { 9 | "scripts": [ 10 | "background.js" 11 | ] 12 | } 13 | }, 14 | "icons": { 15 | "16": "icon/16.png", 16 | "32": "icon/32.png", 17 | "48": "icon/48.png", 18 | "128": "icon/128.png" 19 | }, 20 | "permissions": [ 21 | "fileSystemProvider" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /webdavfs/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extName__", 3 | "description": "__MSG_extName__", 4 | "version": "0.0.1", 5 | "manifest_version": 2, 6 | "default_locale": "en", 7 | "app": { 8 | "background": { 9 | "scripts": [ 10 | "background.js" 11 | ] 12 | } 13 | }, 14 | "icons": { 15 | "16": "icon/16.png", 16 | "32": "icon/32.png", 17 | "48": "icon/48.png", 18 | "128": "icon/128.png" 19 | }, 20 | "permissions": [ 21 | "fileSystemProvider", 22 | "storage" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /templatefs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">= 0.10.0" 4 | }, 5 | "name": "{{ name }}", 6 | "version": "0.0.1", 7 | "main": "js/main.js", 8 | "devDependencies": { 9 | "chai": "^1.9.1", 10 | "grunt": "^0.4.5", 11 | "grunt-browserify": "^2.1.3", 12 | "grunt-contrib-jshint": "^0.10.0", 13 | "grunt-mocha": "^0.4.11", 14 | "load-grunt-tasks": "^0.6.0", 15 | "grunt-vulcanize": "^0.4.1" 16 | }, 17 | "scripts": { 18 | "test": "grunt test" 19 | }, 20 | "author": "{{ author }}", 21 | "license": "BSD" 22 | } 23 | -------------------------------------------------------------------------------- /s3fs/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extName__", 3 | "description": "__MSG_extName__", 4 | "version": "0.1", 5 | "manifest_version": 2, 6 | "default_locale": "en", 7 | "app": { 8 | "background": { 9 | "scripts": [ 10 | "aws-sdk.js", 11 | "background.js" 12 | ] 13 | } 14 | }, 15 | "icons": { 16 | "16": "icon/16.png", 17 | "32": "icon/32.png", 18 | "48": "icon/48.png", 19 | "128": "icon/128.png" 20 | }, 21 | "permissions": [ 22 | "fileSystemProvider", 23 | "storage" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chromeos-filesystems", 3 | "version": "0.0.1", 4 | "authors": [ 5 | "Giles Lavelle ", 6 | "Jun Mukai " 7 | ], 8 | "license": "BSD", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "third_party", 14 | "*/test", 15 | "*/tests" 16 | ], 17 | "dependencies": { 18 | "aws-sdk": "~2.0.9", 19 | "paper-toast": "Polymer/paper-toast#~0.5.1", 20 | "paper-input": "Polymer/paper-input#~0.5.1", 21 | "paper-button": "Polymer/paper-button#~0.5.1", 22 | "paper-dropdown": "Polymer/paper-dropdown#~0.5.1", 23 | "paper-item": "Polymer/paper-item#~0.5.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templatefs/js/events/onOpenFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Responds to a request to open a file. 11 | * @param {Object} options Input options. 12 | * @param {function} onSuccess Function to be called if the file was opened 13 | * successfully. 14 | * @param {function} onError Function to be called if an error occured while 15 | * attempting to open the file. 16 | */ 17 | module.exports = function(options, onSuccess, onError) { 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /shared_tests/chromemock.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function() { 10 | // Mocks the parts of the Chrome extension API needed to test the providers 11 | // in a regular webpage. 12 | window.chrome = { 13 | fileSystemProvider: { 14 | unmount: function(options, onSuccess) { 15 | onSuccess(options); 16 | } 17 | }, 18 | i18n: { 19 | getMessage: function(name) { 20 | return name; 21 | } 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /templatefs/js/events/onCloseFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Responds to a request to close a file. 11 | * @param {Object} options Input options. 12 | * @param {function} onSuccess Function to be called if the file was opened 13 | * successfully. 14 | * @param {function} onError Function to be called if an error occured while 15 | * attempting to close the file. 16 | */ 17 | module.exports = function(options, onSuccess, onError) { 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /templatefs/js/events/onReadFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Responds to a request for the contents of a file. 11 | * @param {Object} options Input options. 12 | * @param {function} onSuccess Function to be called if the file was 13 | * read successfully. 14 | * @param {function} onError Function to be called if an error occured while 15 | * attempting to read the file. 16 | */ 17 | module.exports = function(options, onSuccess, onError) { 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /templatefs/js/events/onReadDirectoryRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Responds to a request for the contents of a directory. 11 | * @param {Object} options Input options. 12 | * @param {function} onSuccess Function to be called if the directory was 13 | * read successfully. 14 | * @param {function} onError Function to be called if an error occured while 15 | * attempting to read the directory. 16 | */ 17 | module.exports = function(options, onSuccess, onError) { 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /templatefs/js/events/onGetMetadataRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Fetches the metadata associated with the file at the given path. 11 | * @param {Object} options Input options. 12 | * @param {function} onSuccess Function to be called if the metadata was 13 | * fetched successfully. 14 | * @param {function} onError Function to be called if an error occured while 15 | * attempting to fetch the metadata. 16 | */ 17 | module.exports = function(options, onSuccess, onError) { 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /templatefs/readme.md: -------------------------------------------------------------------------------- 1 | # {{ name }} 2 | 3 | ## Overview 4 | 5 | Describe your provider here 6 | 7 | ## Building 8 | 9 | First clone and configure this repository as described in the main readme. Then run the following commands. 10 | 11 | ```bash 12 | $ cd {{ name }} 13 | $ npm install 14 | $ grunt 15 | ``` 16 | 17 | This will install all the dependencies and build the project. You can then compress the `extension` directory to a ZIP archive, or install the extension for testing as an unpacked extension by selecting the `extension` directory from Chrome's 'Load unpacked extension' dialog. 18 | 19 | ## Testing 20 | 21 | To run the unit test suite, run `grunt test` from the top-level directory. Make sure you've followed all the setup instructions in the top-level readme first. 22 | 23 | -------------------------------------------------------------------------------- /webdavfs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">= 0.10.0" 4 | }, 5 | "name": "webdavfs", 6 | "version": "0.0.1", 7 | "main": "js/main.js", 8 | "scripts": { 9 | "test": "grunt test" 10 | }, 11 | "author": "Giles Lavelle ", 12 | "license": "BSD", 13 | "devDependencies": { 14 | "grunt": "^0.4.5", 15 | "grunt-browserify": "^2.1.3", 16 | "grunt-contrib-connect": "^0.8.0", 17 | "grunt-contrib-jshint": "^0.10.0", 18 | "grunt-jsdoc": "^0.5.7", 19 | "grunt-jsonlint": "^1.0.4", 20 | "grunt-karma": "^0.8.3", 21 | "grunt-vulcanize": "^0.4.1", 22 | "karma-chai": "^0.1.0", 23 | "karma-chrome-launcher": "^0.1.4", 24 | "karma-firefox-launcher": "^0.1.3", 25 | "karma-mocha": "^0.1.9", 26 | "load-grunt-tasks": "^0.6.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /s3fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">= 0.10.0" 4 | }, 5 | "name": "s3fs", 6 | "version": "0.0.1", 7 | "main": "js/main.js", 8 | "devDependencies": { 9 | "grunt": "^0.4.5", 10 | "grunt-browserify": "^2.1.3", 11 | "grunt-contrib-connect": "^0.8.0", 12 | "grunt-contrib-copy": "^0.5.0", 13 | "grunt-contrib-jshint": "^0.10.0", 14 | "grunt-jsdoc": "^0.5.7", 15 | "grunt-karma": "^0.8.3", 16 | "grunt-vulcanize": "^0.4.1", 17 | "karma": "^0.12.23", 18 | "karma-chai": "^0.1.0", 19 | "karma-firefox-launcher": "^0.1.3", 20 | "karma-mocha": "^0.1.9", 21 | "load-grunt-tasks": "^0.6.0", 22 | "mocha": "^1.21.4", 23 | "grunt-jsonlint": "^1.0.4" 24 | }, 25 | "scripts": { 26 | "test": "grunt test" 27 | }, 28 | "author": "Giles Lavelle ", 29 | "license": "BSD" 30 | } 31 | -------------------------------------------------------------------------------- /webdavfs/js/errors.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * WebDAV error code map. 11 | * All taken from http://msdn.microsoft.com/en-us/library/aa142917(v=exchg.65).aspx 12 | * @const 13 | * @type {Object} 14 | */ 15 | var ERROR_MAP = { 16 | 201: 'OK', 17 | 204: 'OK', 18 | 403: 'INVALID_OPERATION', 19 | 404: 'NOT_FOUND', 20 | 409: 'FAILED', 21 | 412: 'EXISTS', 22 | 423: 'IN_USE', 23 | 502: 'ACCESS_DENIED', 24 | 507: 'NO_SPACE' 25 | }; 26 | 27 | /** 28 | * Converts a HTTP error code into its equivalent code for the FSP API. 29 | * @param {number} code The HTTP error code. 30 | * @return {string} The FSP error code. 31 | */ 32 | var getError = function(code) { 33 | return ERROR_MAP[code] || 'FAILED'; 34 | }; 35 | 36 | module.exports = getError; 37 | -------------------------------------------------------------------------------- /webdavfs/test/spec/wdfs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /* jshint -W027 */ 10 | var config = require('../../../testserver/config'); 11 | var WebDAVFS = require('../../js/wdfs'); 12 | 13 | describe('WebDAV Filesystem', function() { 14 | it('should throw an error with an invalid URL', function() { 15 | var construct = function() { new WebDAVFS(''); }; 16 | construct.should.throw('invalidURL'); 17 | }); 18 | 19 | it('should have the correct ID', function() { 20 | webDAVFS.options.fileSystemId.should.equal('webdavfs'); 21 | }); 22 | 23 | it('should have the correct display name', function() { 24 | webDAVFS.options.displayName.should.equal('WebDAV'); 25 | }); 26 | 27 | it('should store the url passed to it', function() { 28 | webDAVFS.url.should.equal(config.URL); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /templatefs/ui/auth.html: -------------------------------------------------------------------------------- 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 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /s3fs/test/spec/helper.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W027 */ 8 | 9 | 'use strict'; 10 | 11 | var S3FS = require('../../js/s3fs'); 12 | // Mock the S3 API. 13 | window.AWS = require('./s3mock'); 14 | 15 | var chromemock = require('../../../shared_tests/chromemock'); 16 | chromemock(); 17 | 18 | // Convenience method to convert ArrayBuffer responses to strings for more 19 | // readable assertions. 20 | window.arrayBufferToString = require('../../../shared/util').arrayBufferToString; 21 | 22 | var access = 'fake-key'; 23 | var secret = 'fake-secret'; 24 | var region = 'us-west-2'; 25 | var bucket = 'chromeostest'; 26 | 27 | var makeClient = function() { 28 | window.s3fs = new S3FS(bucket, region, access, secret); 29 | }; 30 | 31 | makeClient(); 32 | 33 | before(function(done){ 34 | s3fs.s3.wdfs.checkConnection(done); 35 | }); 36 | 37 | beforeEach(function(done) { 38 | makeClient(); 39 | s3fs.s3.wdfs.reset(done); 40 | }); 41 | -------------------------------------------------------------------------------- /shared/i18n.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function() { 10 | var selector = 'data-message'; 11 | var elements = document.querySelectorAll('[' + selector + ']'); 12 | 13 | for (var i = 0; i < elements.length; i++) { 14 | var element = elements[i]; 15 | 16 | var messageID = element.getAttribute(selector); 17 | var messageText = chrome.i18n.getMessage(messageID); 18 | 19 | switch(element.tagName.toLowerCase()) { 20 | case 'paper-input': 21 | case 'paper-button': 22 | case 'paper-dropdown': 23 | element.setAttribute('label', messageText); 24 | break; 25 | case 'paper-toast': 26 | element.setAttribute('text', messageText); 27 | break; 28 | case 'h1': 29 | case 'title': 30 | var textNode = document.createTextNode(messageText); 31 | element.appendChild(textNode); 32 | break; 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /webdavfs/test/spec/helper.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var WebDAVFS = require('../../js/wdfs'); 10 | var config = require('../../../testserver/config'); 11 | 12 | var chromemock = require('../../../shared_tests/chromemock'); 13 | chromemock(); 14 | 15 | var makeClient = function() { 16 | // No need to try/catch here: If the URL is invalid the tests will abort and 17 | // the error message will be displayed in the console, which is desired 18 | // behaviour. 19 | window.webDAVFS = new WebDAVFS(config.URL); 20 | }; 21 | 22 | makeClient(); 23 | 24 | // Convenience method to convert ArrayBuffer responses to strings for more 25 | // readable assertions. 26 | window.arrayBufferToString = require('../../../shared/util').arrayBufferToString; 27 | 28 | // Run initialisation code to prepare the environment for testing. 29 | before(function(done){ 30 | webDAVFS.checkConnection(done); 31 | }); 32 | 33 | beforeEach(function(done) { 34 | makeClient(); 35 | webDAVFS.reset(done); 36 | }); 37 | -------------------------------------------------------------------------------- /webdavfs/ui/auth.html: -------------------------------------------------------------------------------- 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 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /testserver/config.js: -------------------------------------------------------------------------------- 1 | /** Configuration for the local WebDAV testing server. */ 2 | 3 | /** 4 | * @description The scheme of the protocol over which the connection is made. 5 | * Modify this if using an alternative such as HTTPS. 6 | * @type {string} 7 | * @constant 8 | */ 9 | var SCHEME = 'http'; 10 | 11 | /** 12 | * @description The hostname of the machine on which the server is running, 13 | * used by the test suite when connecting. Modify this if running the server 14 | * externally. 15 | * @type {string} 16 | * @constant 17 | */ 18 | var HOST = 'localhost'; 19 | 20 | /** 21 | * @description The port on which the server communicates. 22 | * @type {number} 23 | * @constant 24 | */ 25 | var PORT = 8000; 26 | 27 | /** 28 | * @description The full URL of the server, formed of scheme, host and port. 29 | * @type {string} 30 | * @constant 31 | */ 32 | var URL = SCHEME + '://' + HOST + ':' + PORT + '/'; 33 | 34 | /** 35 | * @description The relative path of the directory that stores assets for 36 | * testing. 37 | * @type {string} 38 | * @constant 39 | */ 40 | var ASSETS_DIRECTORY = 'assets'; 41 | 42 | module.exports = { 43 | SCHEME: SCHEME, 44 | HOST: HOST, 45 | PORT: PORT, 46 | URL: URL, 47 | ASSETS_DIRECTORY: ASSETS_DIRECTORY 48 | }; 49 | -------------------------------------------------------------------------------- /shared_tests/onCreateFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function(onCreateFileRequested, onGetMetadataRequested) { 10 | describe('onCreateFileRequested', function() { 11 | it('should create a new file', function(done) { 12 | var filename = 'new_create.txt'; 13 | 14 | var statOptions = { 15 | entryPath: '/' + filename 16 | }; 17 | 18 | var createOptions = { 19 | filePath: '/' + filename, 20 | }; 21 | 22 | var onError = function(error) { 23 | throw new Error(error); 24 | }; 25 | 26 | var postCreateSuccess = function(data) { 27 | data.name.should.equal(filename); 28 | done(); 29 | }; 30 | 31 | onGetMetadataRequested(statOptions, function() { 32 | throw new Error('File should not exist before creating.'); 33 | }, function() { 34 | onCreateFileRequested(createOptions, function() { 35 | onGetMetadataRequested(statOptions, postCreateSuccess, onError); 36 | }, onError); 37 | }); 38 | }); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /webdavfs/readme.md: -------------------------------------------------------------------------------- 1 | # WebDAVFS 2 | 3 | WebDAVFS is a Chrome extension for Chrome OS for accessing files via the WebDAV protocol directly through the Files app. 4 | 5 | ## Overview 6 | 7 | The project provides the glue between WebDAV and Chrome's `fileSystemProvider` API. Currently this API is read-only, and therefore creating or writing to files is not supported. This will be added when the Chrome API is updated. 8 | 9 | ## Building 10 | 11 | First clone and configure this repository as described in the main readme. Then run the following commands. 12 | 13 | ```bash 14 | $ cd webdavfs 15 | $ npm install 16 | $ grunt 17 | ``` 18 | 19 | This will install all the dependencies and build the project. You can then compress the `extension` directory to a ZIP archive, or install the extension for testing as an unpacked extension by selecting the `extension` directory from Chrome's 'Load unpacked extension' dialog. 20 | 21 | ## Testing 22 | 23 | To run the unit test suite, run `grunt test` from this directory. Make sure you've followed all the setup instructions in the top-level readme first. 24 | 25 | ## Documentation 26 | 27 | To generate HTML documentation for the provider from the JSDoc source code annotations, run `grunt docs`. The documentation will be generated to the `docs` directory. To view it, run `grunt connect:docs &` and navigate to `http://localhost:4000`. 28 | -------------------------------------------------------------------------------- /s3fs/readme.md: -------------------------------------------------------------------------------- 1 | # S3FS 2 | 3 | S3FS is a Chrome extension for Chrome OS for accessing files stored in an Amazon S3 bucket directly through the Files app. 4 | 5 | ## Overview 6 | 7 | The project provides the glue between the S3 API and Chrome's `fileSystemProvider` API. Currently this API is read-only, and therefore creating or writing to files in a bucket is not supported. This will be added when the Chrome API is updated. 8 | 9 | ## Building 10 | 11 | First clone and configure this repository as described in the main readme. Then run the following commands. 12 | 13 | ```bash 14 | $ cd s3fs 15 | $ npm install 16 | $ grunt 17 | $ grunt copy 18 | ``` 19 | 20 | This will install all the dependencies and build the project. You can then compress the `extension` directory to a ZIP archive, or install the extension for testing as an unpacked extension by selecting the `extension` directory from Chrome's 'Load unpacked extension' dialog. 21 | 22 | ## Testing 23 | 24 | To run the unit test suite, run `grunt test` from the this directory. Make sure you've followed all the setup instructions in the top-level readme first. 25 | 26 | ## Documentation 27 | 28 | To generate HTML documentation for the provider from the JSDoc source code annotations, run `grunt docs`. The documentation will be generated to the `docs` directory. To view it, run `grunt connect:docs &` and navigate to `http://localhost:4000`. 29 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration. 2 | module.exports = function(config) { 3 | config.set({ 4 | // Testing/assertion frameworks to use. 5 | frameworks: ['mocha', 'chai'], 6 | 7 | // List of files to exclude. 8 | exclude: [], 9 | 10 | // Preprocess matching files before serving them to the browser. 11 | preprocessors: {}, 12 | 13 | // Test results reporter to use. 14 | // Possible values: 'dots', 'progress'. 15 | reporters: ['progress'], 16 | 17 | // Web server port. 18 | port: 9876, 19 | 20 | // Enable/disable colors in the output (reporters and logs). 21 | colors: true, 22 | 23 | // Level of logging. 24 | // Possible values: LOG_DISABLE, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG. 25 | logLevel: config.LOG_INFO, 26 | 27 | // Enable/disable watching file and executing tests whenever any file 28 | // changes. 29 | autoWatch: true, 30 | 31 | // Start these browsers. 32 | // Available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 33 | // Only use Firefox because it's the only one that works on Travis CI. 34 | // TODO(lavelle): add separate build test targets so that Chrome and 35 | // Firefox are used locally, and only Firefox on Travis. 36 | browsers: ['Firefox'], 37 | 38 | // Continuous Integration mode. 39 | // If true, Karma captures browsers, runs the tests and exits. 40 | singleRun: true 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /s3fs/test/spec/s3fs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W027 */ 8 | 9 | 'use strict'; 10 | 11 | describe('S3FS', function() { 12 | it('should have an AWS client', function() { 13 | s3fs.s3.should.be.an.instanceof(AWS.S3); 14 | }); 15 | 16 | it('should have the correct display name', function() { 17 | s3fs.options.displayName.should.equal('Amazon S3 Bucket: chromeostest'); 18 | }); 19 | 20 | it('should have the correct ID', function() { 21 | s3fs.options.fileSystemId.should.equal('s3fs-chromeostest'); 22 | }); 23 | 24 | it('should store an index of currently open files', function() { 25 | s3fs.openedFiles.should.be.an('object'); 26 | }); 27 | 28 | it('should have a set of default parameters for calls to the API', 29 | function() { 30 | s3fs.defaultParameters.should.be.an('object'); 31 | }); 32 | 33 | it('should have the correct bucket name', function() { 34 | s3fs.defaultParameters.Bucket.should.equal('chromeostest'); 35 | }); 36 | 37 | describe('params', function() { 38 | it('should extend the default parameters with new ones', function() { 39 | s3fs.parameters({a: 1, b: 2}).should.deep.equal({ 40 | Bucket: 'chromeostest', 41 | a: 1, 42 | b: 2 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /templatefs/js/main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | // Import all the functions to handle the various file system events. 10 | var events = { 11 | onCloseFileRequested: require('./events/onCloseFileRequested'), 12 | onOpenFileRequested: require('./events/onOpenFileRequested'), 13 | onReadFileRequested: require('./events/onReadFileRequested'), 14 | onGetMetadataRequested: require('./events/onGetMetadataRequested'), 15 | onReadDirectoryRequested: require('./events/onReadDirectoryRequested') 16 | }; 17 | 18 | 19 | // Main function to handle requests from the settings UI. 20 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 21 | switch (request.type) { 22 | case 'mount': 23 | // Mount your instance here using the data from the UI. 24 | 25 | break; 26 | default: 27 | var message; 28 | if (request.type) { 29 | message = 'Invalid request type: ' + request.type + '.'; 30 | } else { 31 | message = 'No request type provided.'; 32 | } 33 | 34 | sendResponse({ 35 | type: 'error', 36 | success: false, 37 | message: message 38 | }); 39 | break; 40 | } 41 | 42 | // Return true from the event listener to indicate that we will be sending 43 | // the response asynchronously, so that the sendResponse function is still 44 | // valid at the time it's used. 45 | return true; 46 | }); 47 | -------------------------------------------------------------------------------- /s3fs/test/spec/util.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var assert = require('assert'); 10 | var util = require('../../../shared/util'); 11 | 12 | describe('extend', function() { 13 | var result; 14 | 15 | it('can extend an object with the attributes of another', function() { 16 | util.extend({}, {a: 'b'}).a.should.equal('b'); 17 | }); 18 | 19 | it('properties in source override destination', function() { 20 | util.extend({a: 'x'}, {a: 'b'}).a.should.equal('b'); 21 | }); 22 | 23 | it("properties not in source don't get overriden", function() { 24 | util.extend({x: 'x'}, {a: 'b'}).x.should.equal('x'); 25 | }); 26 | 27 | it("can extend from multiple source objects", function() { 28 | util.extend({x: 'x'}, {a: 'a'}, {b: 'b'}).should.deep.equal({x: 'x', a: 'a', b: 'b'}); 29 | }); 30 | 31 | it("gives priority to last property when extending from multiple source objects", function() { 32 | util.extend({x: 'x'}, {a: 'a', x: 2}, {a: 'b'}).should.deep.equal({x: 2, a: 'b'}); 33 | }); 34 | 35 | it("should copy undefined values", function() { 36 | var result = util.extend({}, {a: void 0, b: null}); 37 | should.equal(result.a, undefined); 38 | should.equal(result.b, null); 39 | }); 40 | 41 | it("should not error on `null` or `undefined` sources", function() { 42 | try { 43 | result = {}; 44 | util.extend(result, null, undefined, {a: 1}); 45 | } catch(ex) {} 46 | 47 | result.a.should.equal(1); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /webdavfs/extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "WebDAV File System", 4 | "description": "Extension name." 5 | }, 6 | "extDescription": { 7 | "message": "Access a WebDAV server directly from the Files app.", 8 | "description": "Extension description." 9 | }, 10 | "mountAttempt": { 11 | "message": "Attempting to mount instance with given credentials...", 12 | "description": "Popup notifcation that informs the user that the system is attempting to mount a new instance with the credentials they provided." 13 | }, 14 | "mountSuccess": { 15 | "message": "Mounted successfully.", 16 | "description": "Popup notifcation that informs the user that the system successfully mounted a new instance with the credentials they provided." 17 | }, 18 | "mountFail": { 19 | "message": "Failed to mount with given credentials.", 20 | "description": "Popup notifcation that informs the user that the system failed to mount a new instance with the credentials they provided." 21 | }, 22 | "url": { 23 | "message": "Server URL", 24 | "description": "Placeholder for a text field that informs the user that they should enter the URL of the WebDAV server." 25 | }, 26 | "mountHeader": { 27 | "message": "Mount a new WevDAV server", 28 | "description": "Header describing the server mount form." 29 | }, 30 | "mount": { 31 | "message": "Mount", 32 | "description": "Label for a button that triggers the mount action." 33 | }, 34 | "invalidURL": { 35 | "message": "The URL you entered for the WebDAV server is invalid.", 36 | "description": "Message body of a notification to be displayed if the user enters an invalid server URL." 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /shared_tests/onCloseFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W027 */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(fs, onCloseFileRequested, onOpenFileRequested) { 12 | describe('onCloseFileRequested', function() { 13 | it('should reject attempts to close unopened files', function(done) { 14 | var options = { 15 | openRequestId: 123 16 | }; 17 | 18 | var onSuccess = function() { 19 | throw new Error('Should have rejected attempt to close unopened file.'); 20 | done(); 21 | }; 22 | 23 | var onError = function(error) { 24 | error.should.be.a('string'); 25 | error.should.equal('INVALID_OPERATION'); 26 | done(); 27 | }; 28 | 29 | onCloseFileRequested(options, onSuccess, onError); 30 | }); 31 | 32 | it('should close previously opened files', function(done) { 33 | var openOptions = { 34 | filePath: '/new.txt', 35 | mode: 'READ', 36 | create: false, 37 | requestId: 1 38 | }; 39 | 40 | var closeOptions = { 41 | openRequestId: 1 42 | }; 43 | 44 | var onSuccess = function() { 45 | window[fs].openedFiles.should.not.have.property(closeOptions.openRequestId); 46 | done(); 47 | }; 48 | 49 | var onError = function(error) { 50 | throw new Error(error); 51 | done(); 52 | }; 53 | 54 | onOpenFileRequested(openOptions, function() { 55 | onCloseFileRequested(closeOptions, onSuccess, onError); 56 | }, onError); 57 | }); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /s3fs/ui/auth.html: -------------------------------------------------------------------------------- 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 |
26 |

27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /s3fs/js/s3fs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var util = require('../../shared/util'); 10 | 11 | /** 12 | * Class that encapsulates metadata for the S3 filesystem and the bucket to 13 | * which it connects. 14 | * @class 15 | * @param {string} bucket The name of the S3 bucket to connect to. 16 | * @param {string} region The AWS region of the bucket eg. 'us-west-2'. 17 | * @param {string} access The AWS access key ID used to authenticate. 18 | * @param {string} secret The AWS secret access key used to authenticate. 19 | */ 20 | var S3FS = function(bucket, region, access, secret) { 21 | AWS.config.update({ 22 | accessKeyId: access, 23 | secretAccessKey: secret 24 | }); 25 | 26 | // AWS bucket region string. 27 | AWS.config.region = region; 28 | 29 | // AWS SDK client for communicating with the API. 30 | this.s3 = new AWS.S3(); 31 | 32 | // Options for the file system provider API. 33 | this.options = { 34 | fileSystemId: 's3fs-' + bucket, 35 | displayName: 'Amazon S3 Bucket: ' + bucket 36 | }; 37 | 38 | this.supportsRecursive = false; 39 | 40 | // Stores a record of all opened files. 41 | this.openedFiles = {}; 42 | 43 | // Default parameters for calls to the AWS API. 44 | this.defaultParameters = {Bucket: bucket}; 45 | }; 46 | 47 | /** 48 | * Extends the default parameters with any additional ones needed for a 49 | * particular API call. 50 | * @param {Object=} opt_extras The extra parameters. 51 | * @return {Object} A new object with both the default parameters and the new 52 | * ones. 53 | */ 54 | S3FS.prototype.parameters = function(opt_extras) { 55 | var extras = opt_extras || {}; 56 | return util.extend({}, this.defaultParameters, extras); 57 | }; 58 | 59 | module.exports = S3FS; 60 | -------------------------------------------------------------------------------- /shared_tests/onGetMetadataRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W027 */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(onGetMetadataRequested) { 12 | describe('onGetMetadataRequested', function() { 13 | it('should return the correct metadata object for files', function(done) { 14 | var options = { 15 | entryPath: '/1.txt' 16 | }; 17 | 18 | var onSuccess = function(metadata, hasMore) { 19 | metadata.should.have.property('isDirectory', false); 20 | metadata.should.have.property('name', '1.txt'); 21 | metadata.should.have.property('size', 1); 22 | metadata.should.have.property('mimeType'); 23 | metadata.mimeType.should.match(/^text\/plain/); 24 | metadata.should.have.property('modificationTime') 25 | .that.is.an.instanceof(Date); 26 | done(); 27 | }; 28 | 29 | var onError = function(error) { 30 | throw new Error(error); 31 | }; 32 | 33 | onGetMetadataRequested(options, onSuccess, onError); 34 | }); 35 | 36 | it('should return the correct metadata object for directories', 37 | function(done) { 38 | var options = { 39 | entryPath: '/' 40 | }; 41 | 42 | var onSuccess = function(metadata, hasMore) { 43 | metadata.should.have.property('isDirectory', true); 44 | metadata.should.have.property('name', '/'); 45 | metadata.should.have.property('size', 0); 46 | metadata.should.have.property('modificationTime') 47 | .that.is.an.instanceof(Date); 48 | done(); 49 | }; 50 | 51 | var onError = function(error) { 52 | throw new Error(error); 53 | }; 54 | 55 | onGetMetadataRequested(options, onSuccess, onError); 56 | }); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /shared_tests/onOpenFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W027 */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(fs, onOpenFileRequested) { 12 | describe('onOpenFileRequested', function() { 13 | it('should allow attempts to create files', function(done) { 14 | var options = { 15 | filePath: '/new.txt', 16 | mode: 'READ', 17 | create: true, 18 | requestId: 1 19 | }; 20 | 21 | var onSuccess = function() { 22 | done(); 23 | }; 24 | 25 | var onError = function(error) { 26 | throw new Error(error); 27 | done(); 28 | }; 29 | 30 | onOpenFileRequested(options, onSuccess, onError); 31 | }); 32 | 33 | it('should allow attempts to write files', function(done) { 34 | var options = { 35 | filePath: '/new.txt', 36 | mode: 'WRITE', 37 | create: false, 38 | requestId: 1 39 | }; 40 | 41 | var onSuccess = function() { 42 | done(); 43 | }; 44 | 45 | var onError = function(error) { 46 | throw new Error(error); 47 | done(); 48 | }; 49 | 50 | onOpenFileRequested(options, onSuccess, onError); 51 | }); 52 | 53 | it('should allow read-only opening of existing files', function(done) { 54 | var options = { 55 | filePath: '/1.txt', 56 | mode: 'READ', 57 | create: false, 58 | requestId: 1 59 | }; 60 | 61 | var onSuccess = function() { 62 | window[fs].openedFiles[options.requestId].should.equal(options.filePath); 63 | done(); 64 | }; 65 | 66 | var onError = function(error) { 67 | throw error; 68 | done(); 69 | }; 70 | 71 | onOpenFileRequested(options, onSuccess, onError); 72 | }); 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /shared_tests/onReadDirectoryRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function(onReadDirectoryRequested) { 10 | describe('onReadDirectoryRequested', function() { 11 | it('should return the correct contents for the given directory', 12 | function(done) { 13 | var options = { 14 | directoryPath: '/' 15 | }; 16 | 17 | var onSuccess = function(list, hasMore) { 18 | list.should.have.length(12); 19 | 20 | var directory = list.filter(function(entry) { 21 | return entry.name === 'dir1'; 22 | })[0]; 23 | directory.should.exist; 24 | directory.should.have.property('isDirectory', true); 25 | directory.should.have.property('name', 'dir1'); 26 | directory.should.have.property('size', 0); 27 | directory.should.have.property('modificationTime') 28 | .that.is.an.instanceof(Date); 29 | directory.should.not.have.property('mimeType'); 30 | 31 | var file = list.filter(function(entry) { 32 | return entry.name === '1.txt'; 33 | })[0]; 34 | file.should.have.property('isDirectory', false); 35 | file.should.have.property('name', '1.txt'); 36 | 37 | // MIME type is optional, but if present should still be valid. 38 | if (file.mimeType) { 39 | file.mimeType.should.match(/^text\/plain/); 40 | } 41 | 42 | file.should.have.property('size', 1); 43 | file.should.have.property('modificationTime') 44 | .that.is.an.instanceof(Date); 45 | 46 | done(); 47 | }; 48 | 49 | var onError = function(error) { 50 | throw new Error(error); 51 | }; 52 | 53 | onReadDirectoryRequested(options, onSuccess, onError); 54 | }); 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /webdavfs/ui/auth.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var internationalise = require('../../shared/i18n'); 10 | 11 | window.chrome = window.chrome || {}; 12 | 13 | // Create a function for the Chrome i18n API if it doesn't exist the allow the 14 | // page to be tested as a normal browser page. 15 | if (!chrome.i18n) { 16 | chrome.i18n = { 17 | getMessage: function(name) { 18 | return name; 19 | } 20 | }; 21 | } 22 | 23 | var keys = ['url']; 24 | 25 | var fields = {}; 26 | 27 | keys.forEach(function(name) { 28 | fields[name] = document.getElementById(name); 29 | }); 30 | 31 | var button = document.getElementById('mount'); 32 | 33 | var restoreCredentials = function() { 34 | if (!chrome.storage) { return; } 35 | chrome.storage.sync.get(keys, function(items) { 36 | for (var key in items) { 37 | var value = items[key]; 38 | 39 | if (value) { 40 | fields[key].value = value; 41 | } 42 | } 43 | }); 44 | }; 45 | 46 | restoreCredentials(); 47 | internationalise(); 48 | 49 | button.addEventListener('click', function(event) { 50 | event.preventDefault(); 51 | 52 | button.setAttribute('disabled', 'true'); 53 | 54 | document.getElementById('toast-mount-attempt').show(); 55 | 56 | var request = { 57 | type: 'mount' 58 | }; 59 | 60 | for (var key in fields) { 61 | request[key] = fields[key].value; 62 | } 63 | 64 | chrome.runtime.sendMessage(request, function(response) { 65 | if (response.success) { 66 | document.getElementById('toast-mount-success').show(); 67 | 68 | window.setTimeout(function() { 69 | window.close(); 70 | }, 2000); 71 | } else { 72 | var toast = document.getElementById('toast-mount-fail'); 73 | if (response.error) { 74 | toast.setAttribute('text', response.error); 75 | } 76 | toast.show(); 77 | button.removeAttribute('disabled'); 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /templatefs/ui/auth.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | // Create a function for the Chrome i18n API if it doesn't exist the allow the 10 | // page to be tested as a normal browser page. 11 | if (!chrome.i18n) { 12 | chrome.i18n = { 13 | getMessage: function(name) { 14 | return name; 15 | } 16 | }; 17 | } 18 | 19 | var button = document.getElementById('mount'); 20 | 21 | var internationalise = function() { 22 | var selector = 'data-message'; 23 | var elements = document.querySelectorAll('[' + selector + ']'); 24 | 25 | for (var i = 0; i < elements.length; i++) { 26 | var element = elements[i]; 27 | 28 | var messageID = element.getAttribute(selector); 29 | var messageText = chrome.i18n.getMessage(messageID); 30 | 31 | switch(element.tagName.toLowerCase()) { 32 | case 'paper-input': 33 | case 'paper-button': 34 | element.setAttribute('label', messageText); 35 | break; 36 | case 'paper-toast': 37 | element.setAttribute('text', messageText); 38 | break; 39 | case 'h1': 40 | case 'title': 41 | element.innerText = messageText; 42 | break; 43 | } 44 | } 45 | }; 46 | 47 | internationalise(); 48 | 49 | button.addEventListener('click', function(event) { 50 | event.preventDefault(); 51 | 52 | button.setAttribute('disabled', 'true'); 53 | 54 | document.getElementById('toast-mount-attempt').show(); 55 | 56 | var request = { 57 | type: 'mount' 58 | }; 59 | 60 | // Add values from your form fields to the request object here that the 61 | // background script can then use when mounting the new instance. 62 | 63 | chrome.runtime.sendMessage(request, function(response) { 64 | if (response.success) { 65 | document.getElementById('toast-mount-success').show(); 66 | 67 | window.setTimeout(function() { 68 | window.close(); 69 | }, 2000); 70 | } else { 71 | document.getElementById('toast-mount-fail').show(); 72 | button.removeAttribute('disabled'); 73 | } 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | a just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to any Google project must be accompanied by a Contributor 9 | License Agreement. This is not a copyright **assignment**, it simply gives 10 | Google permission to use and redistribute your contributions as part of the 11 | project. 12 | 13 | * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA][]. 14 | 15 | * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA][]. 16 | 17 | You generally only need to submit a CLA once, so if you've already submitted 18 | one (even if it was for a different project), you probably don't need to do it 19 | again. 20 | 21 | [individual CLA]: https://developers.google.com/open-source/cla/individual 22 | [corporate CLA]: https://developers.google.com/open-source/cla/corporate 23 | 24 | ## Submitting a patch 25 | 26 | 1. It's generally best to start by opening a new issue describing the bug or feature you're intending to fix. Even if you think it's relatively minor, it's helpful to know what people are working on. Mention in the initial issue that you are planning to work on that bug or feature so that it can be assigned to you. 27 | 28 | 2. Follow the normal process of [forking][] the project, and setup a new branch to work in. It's important that each group of changes be done in separate branches in order to ensure that a pull request only includes the commits related to that bug or feature. 29 | 30 | 3. Any significant changes should almost always be accompanied by tests. The project already has good test coverage, so look at some of the existing tests if you're unsure how to go about it. 31 | 32 | 4. All contributions must be licensed Apache 2.0 and all files must have a copy of the boilerplate licence comment (can be copied from an existing 33 | file). Files should be formatted according to Google's JavaScript [styleguide][]. 34 | 35 | 5. Finally, push the commits to your fork and submit a [pull request][]. 36 | 37 | [forking]: https://help.github.com/articles/fork-a-repo 38 | [styleguide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml 39 | [pull request]: https://help.github.com/articles/creating-a-pull-request 40 | 41 | 42 | -------------------------------------------------------------------------------- /testserver/server.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /** 8 | * WebDAV server used to provide files for unit tests and manual testing of 9 | * the extension. 10 | * The skeleton directory that is served is located in `test/assets`. 11 | */ 12 | 13 | 'use strict'; 14 | 15 | var jsdav = require('jsDAV/lib/jsdav'); 16 | var server = require("jsDAV/lib/DAV/server"); 17 | var util = require("jsDAV/lib/shared/util"); 18 | var plugin = require("jsDAV/lib/DAV/plugin"); 19 | var mockfs = require('mock-fs'); 20 | var config = require('./config'); 21 | 22 | var argv = process.argv.slice(2); 23 | 24 | var flags = ['--debug', '-d']; 25 | 26 | // Enable debug mode if the command-line flag is set. 27 | if (argv.length > 0 && flags.indexOf(argv[0]) !== -1) { 28 | jsdav.debugMode = true; 29 | } 30 | 31 | // Define an object representing the contents of the mock filesystem to be 32 | // used by the tests. 33 | var structure = {}; 34 | structure[config.ASSETS_DIRECTORY] = { 35 | dir1: { 36 | '1.txt': '1', 37 | }, 38 | dir11: { 39 | '11.txt': '11' 40 | }, 41 | dir2: { 42 | '2.txt': '2' 43 | }, 44 | nonemptydir: { 45 | '4.txt': '4' 46 | }, 47 | walk: { 48 | dir1: { 49 | '1.txt': '1' 50 | }, 51 | dir2: { 52 | '2.txt': '2' 53 | }, 54 | '1.txt': '1' 55 | }, 56 | empty: {}, 57 | '1.txt': '1', 58 | '11.txt': '11', 59 | '2.txt': '2', 60 | '3.txt': '3', 61 | 'big.txt': new Array(10001).join('1'), 62 | 'truncatable.txt': 'abcdefghijklmnopqrstuvwxyz' 63 | }; 64 | 65 | // Create a custom JSDAV plugin that mocks the filesystem with the contents 66 | // defined above before every request so that each test has access to the same 67 | // state. 68 | var rebuilder = plugin.extend({ 69 | name: 'rebuilder', 70 | initialize: function(handler) { 71 | handler.addEventListener('beforeMethod', function(event, method, file) { 72 | if (method === 'GET' && file === 'reset') { 73 | mockfs(structure); 74 | } 75 | event.next(); 76 | }); 77 | } 78 | }); 79 | 80 | // Start a new DAV server with the configuration defined above. 81 | jsdav.createServer({ 82 | node: __dirname + '/' + config.ASSETS_DIRECTORY, 83 | plugins: util.extend(server.DEFAULT_PLUGINS, {rebuilder: rebuilder}) 84 | }, config.PORT); 85 | 86 | mockfs(structure); 87 | -------------------------------------------------------------------------------- /s3fs/test/spec/events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var fs = 's3fs'; 10 | 11 | var events = require('../../js/events'); 12 | 13 | var onCopyEntryRequested = events.onCopyEntryRequested; 14 | var onCreateFileRequested = events.onCreateFileRequested; 15 | var onCloseFileRequested = events.onCloseFileRequested; 16 | var onDeleteEntryRequested = events.onDeleteEntryRequested; 17 | var onGetMetadataRequested = events.onGetMetadataRequested; 18 | var onMoveEntryRequested = events.onMoveEntryRequested; 19 | var onOpenFileRequested = events.onOpenFileRequested; 20 | var onReadFileRequested = events.onReadFileRequested; 21 | var onReadDirectoryRequested = events.onReadDirectoryRequested; 22 | var onTruncateRequested = events.onTruncateRequested; 23 | var onWriteFileRequested = events.onWriteFileRequested; 24 | 25 | var testSuite; 26 | 27 | testSuite = require('../../../shared_tests/onCopyEntryRequested'); 28 | testSuite(onCopyEntryRequested, onGetMetadataRequested); 29 | 30 | testSuite = require('../../../shared_tests/onCreateFileRequested'); 31 | testSuite(onCreateFileRequested, onGetMetadataRequested); 32 | 33 | testSuite = require('../../../shared_tests/onCloseFileRequested'); 34 | testSuite(fs, onCloseFileRequested, onOpenFileRequested); 35 | 36 | testSuite = require('../../../shared_tests/onDeleteEntryRequested'); 37 | testSuite(fs, onDeleteEntryRequested, onGetMetadataRequested); 38 | 39 | testSuite = require('../../../shared_tests/onGetMetadataRequested'); 40 | testSuite(onGetMetadataRequested); 41 | 42 | testSuite = require('../../../shared_tests/onMoveEntryRequested'); 43 | testSuite(onMoveEntryRequested, onGetMetadataRequested); 44 | 45 | testSuite = require('../../../shared_tests/onOpenFileRequested'); 46 | testSuite(fs, onOpenFileRequested); 47 | 48 | testSuite = require('../../../shared_tests/onReadFileRequested'); 49 | testSuite(onReadFileRequested, onOpenFileRequested); 50 | 51 | testSuite = require('../../../shared_tests/onReadDirectoryRequested'); 52 | testSuite(onReadDirectoryRequested); 53 | 54 | testSuite = require('../../../shared_tests/onTruncateRequested'); 55 | testSuite(onTruncateRequested, onReadFileRequested, onOpenFileRequested); 56 | 57 | testSuite = require('../../../shared_tests/onWriteFileRequested'); 58 | testSuite(onWriteFileRequested, onReadFileRequested, onOpenFileRequested); 59 | -------------------------------------------------------------------------------- /templatefs/test/spec/events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var fs = '{{ name }}'; 10 | 11 | var events = require('../../js/events'); 12 | 13 | var onCopyEntryRequested = events.onCopyEntryRequested; 14 | var onCreateFileRequested = events.onCreateFileRequested; 15 | var onCloseFileRequested = events.onCloseFileRequested; 16 | var onDeleteEntryRequested = events.onDeleteEntryRequested; 17 | var onGetMetadataRequested = events.onGetMetadataRequested; 18 | var onMoveEntryRequested = events.onMoveEntryRequested; 19 | var onOpenFileRequested = events.onOpenFileRequested; 20 | var onReadFileRequested = events.onReadFileRequested; 21 | var onReadDirectoryRequested = events.onReadDirectoryRequested; 22 | var onTruncateRequested = events.onTruncateRequested; 23 | var onWriteFileRequested = events.onWriteFileRequested; 24 | 25 | var testSuite; 26 | 27 | testSuite = require('../../../shared_tests/onCopyEntryRequested'); 28 | testSuite(onCopyEntryRequested, onGetMetadataRequested); 29 | 30 | testSuite = require('../../../shared_tests/onCreateFileRequested'); 31 | testSuite(onCreateFileRequested, onGetMetadataRequested); 32 | 33 | testSuite = require('../../../shared_tests/onCloseFileRequested'); 34 | testSuite(fs, onCloseFileRequested, onOpenFileRequested); 35 | 36 | testSuite = require('../../../shared_tests/onDeleteEntryRequested'); 37 | testSuite(fs, onDeleteEntryRequested, onGetMetadataRequested); 38 | 39 | testSuite = require('../../../shared_tests/onGetMetadataRequested'); 40 | testSuite(onGetMetadataRequested); 41 | 42 | testSuite = require('../../../shared_tests/onMoveEntryRequested'); 43 | testSuite(onMoveEntryRequested, onGetMetadataRequested); 44 | 45 | testSuite = require('../../../shared_tests/onOpenFileRequested'); 46 | testSuite(fs, onOpenFileRequested); 47 | 48 | testSuite = require('../../../shared_tests/onReadFileRequested'); 49 | testSuite(onReadFileRequested, onOpenFileRequested); 50 | 51 | testSuite = require('../../../shared_tests/onReadDirectoryRequested'); 52 | testSuite(onReadDirectoryRequested); 53 | 54 | testSuite = require('../../../shared_tests/onTruncateRequested'); 55 | testSuite(onTruncateRequested, onReadFileRequested, onOpenFileRequested); 56 | 57 | testSuite = require('../../../shared_tests/onWriteFileRequested'); 58 | testSuite(onWriteFileRequested, onReadFileRequested, onOpenFileRequested); 59 | -------------------------------------------------------------------------------- /webdavfs/test/spec/events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var fs = 'webDAVFS'; 10 | 11 | var events = require('../../js/events'); 12 | 13 | var onCopyEntryRequested = events.onCopyEntryRequested; 14 | var onCreateFileRequested = events.onCreateFileRequested; 15 | var onCloseFileRequested = events.onCloseFileRequested; 16 | var onDeleteEntryRequested = events.onDeleteEntryRequested; 17 | var onGetMetadataRequested = events.onGetMetadataRequested; 18 | var onMoveEntryRequested = events.onMoveEntryRequested; 19 | var onOpenFileRequested = events.onOpenFileRequested; 20 | var onReadFileRequested = events.onReadFileRequested; 21 | var onReadDirectoryRequested = events.onReadDirectoryRequested; 22 | var onTruncateRequested = events.onTruncateRequested; 23 | var onWriteFileRequested = events.onWriteFileRequested; 24 | 25 | var testSuite; 26 | 27 | testSuite = require('../../../shared_tests/onCopyEntryRequested'); 28 | testSuite(onCopyEntryRequested, onGetMetadataRequested); 29 | 30 | testSuite = require('../../../shared_tests/onCreateFileRequested'); 31 | testSuite(onCreateFileRequested, onGetMetadataRequested); 32 | 33 | testSuite = require('../../../shared_tests/onCloseFileRequested'); 34 | testSuite(fs, onCloseFileRequested, onOpenFileRequested); 35 | 36 | testSuite = require('../../../shared_tests/onDeleteEntryRequested'); 37 | testSuite(fs, onDeleteEntryRequested, onGetMetadataRequested); 38 | 39 | testSuite = require('../../../shared_tests/onGetMetadataRequested'); 40 | testSuite(onGetMetadataRequested); 41 | 42 | testSuite = require('../../../shared_tests/onMoveEntryRequested'); 43 | testSuite(onMoveEntryRequested, onGetMetadataRequested); 44 | 45 | testSuite = require('../../../shared_tests/onOpenFileRequested'); 46 | testSuite(fs, onOpenFileRequested); 47 | 48 | testSuite = require('../../../shared_tests/onReadFileRequested'); 49 | testSuite(onReadFileRequested, onOpenFileRequested); 50 | 51 | testSuite = require('../../../shared_tests/onReadDirectoryRequested'); 52 | testSuite(onReadDirectoryRequested); 53 | 54 | testSuite = require('../../../shared_tests/onTruncateRequested'); 55 | testSuite(onTruncateRequested, onReadFileRequested, onOpenFileRequested); 56 | 57 | testSuite = require('../../../shared_tests/onWriteFileRequested'); 58 | testSuite(onWriteFileRequested, onReadFileRequested, onOpenFileRequested); 59 | -------------------------------------------------------------------------------- /shared_tests/onTruncateRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var util = require('../shared/util'); 10 | 11 | var testFactory = function(open, read, truncate) { 12 | return function(file, id, length, expected) { 13 | return function(done) { 14 | var openOptions = { 15 | filePath: file, 16 | mode: 'WRITE', 17 | create: false, 18 | requestId: id 19 | }; 20 | 21 | var readOptions = { 22 | length: 512, 23 | offset: 0, 24 | openRequestId: id 25 | }; 26 | 27 | var truncateOptions = { 28 | filePath: file, 29 | length: length 30 | }; 31 | 32 | var onError = function(error) { 33 | throw new Error(error); 34 | }; 35 | 36 | open(openOptions, function() { 37 | read(readOptions, function(data) { 38 | var before = util.arrayBufferToString(data); 39 | before.should.have.length(expected.before.length); 40 | before.should.equal(expected.before); 41 | 42 | truncate(truncateOptions, function() { 43 | read(readOptions, function(data) { 44 | var after = util.arrayBufferToString(data); 45 | after.should.have.length(expected.after.length); 46 | after.should.equal(expected.after); 47 | 48 | done(); 49 | }, onError); 50 | }, onError); 51 | }, onError); 52 | }, onError); 53 | }; 54 | }; 55 | }; 56 | 57 | module.exports = function(onTruncateRequested, onReadFileRequested, 58 | onOpenFileRequested) { 59 | describe('onTruncateRequested', function() { 60 | var testTruncate = testFactory(onOpenFileRequested, 61 | onReadFileRequested, onTruncateRequested); 62 | 63 | it('should truncate the contents of a file to the correct length', 64 | testTruncate('/truncatable.txt', 999, 10, { 65 | before: 'abcdefghijklmnopqrstuvwxyz', 66 | after: 'abcdefghij' 67 | })); 68 | 69 | it('should truncate the contents of a file to length zero correctly', 70 | testTruncate('/2.txt', 998, 0, { 71 | before: '2', 72 | after: '' 73 | })); 74 | 75 | it('should pad a file with null bytes when truncating with a length ' + 76 | 'longer than that of the original file', 77 | testTruncate('/11.txt', 997, 10, { 78 | before: '11', 79 | after: '11\0\0\0\0\0\0\0\0' 80 | })); 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /tools/scaffolder.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var fs = require('fs'); 10 | var _ = require('underscore'); 11 | var prompt = require('prompt'); 12 | require('shelljs/global'); 13 | require('colors'); 14 | 15 | var textExtensions = ['txt', 'js', 'json', 'html', 'css', 'md']; 16 | 17 | // Mustache-style curly-brace template variables eg. Hello {{ name }}! 18 | _.templateSettings = { 19 | interpolate: /\{\{(.+?)\}\}/g 20 | }; 21 | 22 | // Convenience function to read the contents of a file. 23 | var read = function(path) { 24 | return fs.readFileSync(path, 'utf-8'); 25 | }; 26 | 27 | // Convenience function for displaying an error message and terminating the 28 | // program. 29 | var fatal = function(message) { 30 | console.error(message.red); 31 | process.exit(1); 32 | }; 33 | 34 | var schema = { 35 | properties: { 36 | name: { 37 | required: true, 38 | description: 'Name your provider', 39 | pattern: /[a-z0-9]+/ 40 | }, 41 | displayName: { 42 | required: true, 43 | description: 'Name to be shown to users' 44 | }, 45 | description: { 46 | required: true, 47 | description: 'Describe your provider' 48 | }, 49 | author: { 50 | required: true, 51 | description: 'Your name' 52 | } 53 | } 54 | }; 55 | 56 | prompt.get(schema, function(error, config) { 57 | if (error) { 58 | fatal('Scaffolding cancelled.'); 59 | } 60 | 61 | var src = '../templatefs/*'; 62 | var out = '../' + config.name + 'fs'; 63 | 64 | // Ensure we won't be overwriting any existing files. 65 | if (fs.existsSync(out)) { 66 | fatal('Error: directory ' + out + ' already exists.'); 67 | } 68 | 69 | // Copy the template filesystem to the new directory. 70 | cp('-r', src, out); 71 | 72 | // Run the contents of each text file through the templater to fill out values. 73 | ls('-R', out).forEach(function(file) { 74 | // Ignore directories. 75 | if (file.indexOf('.') === -1) { return; } 76 | // Ignore non-text files. 77 | var extension = file.split('.').pop(); 78 | if (textExtensions.indexOf(extension) === -1) { return; } 79 | 80 | file = out + '/' + file; 81 | 82 | // Template each file. 83 | var contents = read(file); 84 | var template = _.template(contents); 85 | var output = template(config); 86 | 87 | fs.writeFileSync(file, output); 88 | }); 89 | 90 | // Show a success message. 91 | var message = 'Your new provider ' + config.name + ' has been generated.'; 92 | console.log(message.green); 93 | }); 94 | -------------------------------------------------------------------------------- /shared_tests/onWriteFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var util = require('../shared/util'); 10 | 11 | module.exports = function(onWriteFileRequested, onReadFileRequested, 12 | onOpenFileRequested) { 13 | describe('onWriteFileRequested', function() { 14 | it('should write the correct data to a new file', function(done) { 15 | var id = 987; 16 | 17 | var openOptions = { 18 | filePath: '/new_write.txt', 19 | mode: 'WRITE', 20 | create: true, 21 | requestId: id 22 | }; 23 | 24 | var readOptions = { 25 | length: 512, 26 | offset: 0, 27 | openRequestId: id 28 | }; 29 | 30 | var testString = 'TEST'; 31 | 32 | var writeOptions = { 33 | openRequestId: id, 34 | data: util.stringToArrayBuffer(testString), 35 | offset: 0, 36 | length: 512 37 | }; 38 | 39 | var onError = function(error) { 40 | throw new Error(error); 41 | }; 42 | 43 | onOpenFileRequested(openOptions, function() { 44 | onWriteFileRequested(writeOptions, function() { 45 | onReadFileRequested(readOptions, function(data) { 46 | util.arrayBufferToString(data).should.equal(testString); 47 | done(); 48 | }, onError); 49 | }, onError); 50 | }, onError); 51 | }); 52 | }); 53 | 54 | it('should overwrite the correct data to an exisiting file', function(done) { 55 | var id = 1024; 56 | 57 | var openOptions = { 58 | filePath: '/3.txt', 59 | mode: 'WRITE', 60 | create: false, 61 | requestId: id 62 | }; 63 | 64 | var readOptions = { 65 | length: 512, 66 | offset: 0, 67 | openRequestId: id 68 | }; 69 | 70 | var testString = 'OVERWRITTEN'; 71 | 72 | var writeOptions = { 73 | openRequestId: id, 74 | data: util.stringToArrayBuffer(testString), 75 | offset: 0, 76 | length: 512 77 | }; 78 | 79 | var onError = function(error) { 80 | throw new Error(error); 81 | }; 82 | 83 | onOpenFileRequested(openOptions, function() { 84 | onWriteFileRequested(writeOptions, function() { 85 | onReadFileRequested(readOptions, function(data) { 86 | util.arrayBufferToString(data).should.equal(testString); 87 | done(); 88 | }, onError); 89 | }, onError); 90 | }, onError); 91 | }); 92 | }; 93 | -------------------------------------------------------------------------------- /s3fs/extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Amazon S3 File System", 4 | "description": "Extension name." 5 | }, 6 | "extDescription": { 7 | "message": "Access the contents of an S3 bucket directly from the Files app.", 8 | "description": "Extension description." 9 | }, 10 | "mountAttempt": { 11 | "message": "Attempting to mount bucket with given credentials...", 12 | "description": "Popup notifcation that informs the user that the system is attempting to mount a new instance with the credentials they provided." 13 | }, 14 | "mountSuccess": { 15 | "message": "Mounted successfully.", 16 | "description": "Popup notifcation that informs the user that the system successfully mounted a new instance with the credentials they provided." 17 | }, 18 | "mountFail": { 19 | "message": "Failed to mount with given credentials.", 20 | "description": "Popup notifcation that informs the user that the system failed to mount a new instance with the credentials they provided." 21 | }, 22 | "bucketName": { 23 | "message": "Bucket name", 24 | "description": "Placeholder for a text field that informs the user that they should enter the name of the bucket." 25 | }, 26 | "bucketRegion": { 27 | "message": "Bucket region", 28 | "description": "Placeholder for a text field that informs the user that they should enter the region of the bucket." 29 | }, 30 | "accessKey": { 31 | "message": "IAM user access key ID", 32 | "description": "Placeholder for a text field that informs the user that they should enter an Amazon IAM user access key ID." 33 | }, 34 | "secretKey": { 35 | "message": "IAM user secret access key", 36 | "description": "Placeholder for a text field that informs the user that they should enter an Amazon IAM user secret access key." 37 | }, 38 | "mountHeader": { 39 | "message": "Mount a new Amazon S3 bucket", 40 | "description": "Header describing the bucket mount form." 41 | }, 42 | "mount": { 43 | "message": "Mount", 44 | "description": "Label for a button that triggers the mount action." 45 | }, 46 | "invalidBucket": { 47 | "message": "Bucket name is invalid.", 48 | "description": "Error message shown when the user enters an invalid bucket name." 49 | }, 50 | "invalidRegion": { 51 | "message": "No region selected.", 52 | "description": "Error message shown when the user attempts to submit the form before selecting a region." 53 | }, 54 | "invalidAccess": { 55 | "message": "Warning: Access key ID is invalid. You may not be able to access your bucket.", 56 | "description": "Warning message shown when the user enters an invalid access key." 57 | }, 58 | "invalidSecret": { 59 | "message": "Warning: Secret access key is invalid. You may not be able to access your bucket.", 60 | "description": "Warning message shown when the user enters an invalid secret key." 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /webdavfs/test/spec/client.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /* jshint -W027 */ 10 | 11 | describe('WebDAV Client', function() { 12 | // Unit tests for methods that operate on directories only. 13 | describe('Directory methods', function() { 14 | describe('children', function() { 15 | it('should list the contents of the directory', function(done) { 16 | webDAVFS.readDirectory({ 17 | path: '/', 18 | onSuccess: function(list) { 19 | list.should.have.length.above(0); 20 | list[0].should.have.property('name').that.is.a('string'); 21 | list[0].should.have.property('modificationTime') 22 | .that.is.an.instanceof(Date); 23 | list[0].should.have.property('size').that.is.a('number'); 24 | list[0].should.have.property('isDirectory').that.is.a('boolean'); 25 | 26 | done(); 27 | }, 28 | onError: function(error) { 29 | throw new Error(error); 30 | done(); 31 | } 32 | }); 33 | }); 34 | }); 35 | }); 36 | 37 | // Unit tests for methods that operate on files and directories. 38 | describe('Entry methods', function() { 39 | describe('metadata', function() { 40 | it('should return a metadata object for the file in the correct format', 41 | function(done) { 42 | webDAVFS.getMetadata({ 43 | path: '/1.txt', 44 | onSuccess: function(metadata) { 45 | metadata.should.have.property('name').that.is.a('string'); 46 | metadata.should.have.property('modificationTime') 47 | .that.is.an.instanceof(Date); 48 | metadata.should.have.property('size').that.is.a('number'); 49 | metadata.should.have.property('isDirectory').that.is.a('boolean'); 50 | 51 | done(); 52 | }, 53 | onError: function(error) { 54 | throw new Error(error); 55 | done(); 56 | } 57 | }); 58 | }); 59 | }); 60 | }); 61 | 62 | // Unit tests for methods that operate on files only. 63 | describe('File methods', function(){ 64 | describe('read', function() { 65 | it('should return the contents of the file', function(done) { 66 | webDAVFS.readFile({ 67 | path: '/1.txt', 68 | range: { 69 | start: 0, 70 | end: 512 71 | }, 72 | onSuccess: function(contents) { 73 | arrayBufferToString(contents).should.equal('1'); 74 | done(); 75 | }, 76 | onError: function(error) { 77 | throw new Error(error); 78 | done(); 79 | } 80 | }); 81 | }); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /webdavfs/js/main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var WebDAVFS = require('./wdfs'); 10 | 11 | // Import all the functions to handle the various file system events. 12 | var events = require('./events'); 13 | 14 | /** 15 | * Mounts a new instance of the WebDAV provider that connects to the server at 16 | * the given URL. 17 | * @param {string} url The URL of the server to connect to. 18 | * @param {function=} callbacks.onSuccess Function to call if the server was 19 | * mounted successfully. 20 | * @param {function=} callbacks.onError Function to call if an error occured 21 | * while attempting to mount the server. 22 | */ 23 | var mount = function(url, callbacks) { 24 | var onSuccess = callbacks.onSuccess || function() { }; 25 | 26 | var onError = callbacks.onError || function() { 27 | console.error('Failed to mount.'); 28 | }; 29 | 30 | try { 31 | window.webDAVFS = new WebDAVFS(url); 32 | } catch(error) { 33 | onError(error.message); 34 | return; 35 | } 36 | 37 | // Register each of the event listeners to the file system provider. 38 | for (var name in events) { 39 | chrome.fileSystemProvider[name].addListener(events[name]); 40 | } 41 | 42 | // Mount the file system. 43 | chrome.fileSystemProvider.mount(webDAVFS.options, onSuccess, onError); 44 | }; 45 | 46 | chrome.app.runtime.onLaunched.addListener(function() { 47 | // Open the settings UI when the user clicks on the app icon in the Chrome 48 | // app launcher. 49 | 50 | var windowOptions = { 51 | outerBounds: { 52 | minWidth: 800, 53 | minHeight: 700 54 | } 55 | }; 56 | 57 | chrome.app.window.create('build.html', windowOptions); 58 | }); 59 | 60 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 61 | switch (request.type) { 62 | case 'mount': 63 | // Mount the new instance with the given request data. 64 | mount(request.url, { 65 | onSuccess: function() { 66 | sendResponse({ 67 | type: 'mount', 68 | success: true 69 | }); 70 | }, 71 | onError: function(error) { 72 | sendResponse({ 73 | type: 'mount', 74 | success: false, 75 | error: error 76 | }); 77 | } 78 | }); 79 | break; 80 | default: 81 | var message; 82 | if (request.type) { 83 | message = 'Invalid request type: ' + request.type + '.'; 84 | } else { 85 | message = 'No request type provided.'; 86 | } 87 | 88 | sendResponse({ 89 | type: 'error', 90 | success: false, 91 | message: message 92 | }); 93 | break; 94 | } 95 | 96 | // Return true from the event listener to indicate that we will be sending 97 | // the response asynchronously, so that the sendResponse function is still 98 | // valid at the time it's used. 99 | return true; 100 | }); 101 | -------------------------------------------------------------------------------- /shared_tests/onCopyEntryRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function(onCopyEntryRequested, onGetMetadataRequested) { 10 | describe('onCopyEntryRequested', function() { 11 | it('should be able to copy files to locations that do not yet exist', function(done) { 12 | var source = 'dir2/2.txt'; 13 | var target = '2_copied.txt'; 14 | 15 | var statOptions = { 16 | entryPath: '/' + target 17 | }; 18 | 19 | var copyOptions = { 20 | sourcePath: '/' + source, 21 | targetPath: '/' + target 22 | }; 23 | 24 | var onError = function(error) { 25 | throw new Error(error); 26 | }; 27 | 28 | var postCopySuccess = function(data) { 29 | data.name.should.equal(target); 30 | done(); 31 | }; 32 | 33 | onGetMetadataRequested(statOptions, function() { 34 | throw new Error('File should not exist before copying.'); 35 | }, function() { 36 | onCopyEntryRequested(copyOptions, function() { 37 | onGetMetadataRequested(statOptions, postCopySuccess, onError); 38 | }, onError); 39 | }); 40 | }); 41 | 42 | it('should be able to copy directories to locations that do not yet exist', function(done) { 43 | var source = 'dir2'; 44 | var target = 'dir2_copied'; 45 | 46 | var statOptions = { 47 | entryPath: '/' + target 48 | }; 49 | 50 | var copyOptions = { 51 | sourcePath: '/' + source, 52 | targetPath: '/' + target 53 | }; 54 | 55 | var onError = function(error) { 56 | throw new Error(error); 57 | }; 58 | 59 | var postCopySuccess = function(data) { 60 | data.name.should.equal(target); 61 | done(); 62 | }; 63 | 64 | onGetMetadataRequested(statOptions, function() { 65 | throw new Error('Directory should not exist before copying.'); 66 | }, function() { 67 | onCopyEntryRequested(copyOptions, function() { 68 | onGetMetadataRequested(statOptions, postCopySuccess, onError); 69 | }, onError); 70 | }); 71 | }); 72 | 73 | it('should be not overwrite existing files/directories', function(done) { 74 | var source = 'dir2'; 75 | var target = 'dir1'; 76 | 77 | var statOptions = { 78 | entryPath: '/' + target 79 | }; 80 | 81 | var copyOptions = { 82 | sourcePath: '/' + source, 83 | targetPath: '/' + target 84 | }; 85 | 86 | onGetMetadataRequested(statOptions, function() { 87 | onCopyEntryRequested(copyOptions, function() { 88 | throw new Error('Should have rejected copy to existing location'); 89 | }, function(error) { 90 | error.should.be.a('string'); 91 | error.should.equal('EXISTS'); 92 | done(); 93 | }); 94 | }, function() { 95 | throw new Error('Target should exist before copying.'); 96 | }); 97 | }); 98 | }); 99 | }; 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome OS Filesystem Providers 2 | 3 | [![Build Status][travis image]][travis] 4 | [![Code Climate][codeclimate image]][codeclimate] 5 | 6 | This repository contains various filesystem providers for Chrome OS. They offer a way to access files stored on remote servers through the Files app, from a variety of sources. There are two categories of provider: 7 | - **Protocol** providers. These connect to arbitrary servers over a particular protocol, like WebDAV or FTP. 8 | - **Cloud** providers. These connect to particular accounts on various proprietary cloud storage services like Dropbox and Amazon S3. 9 | 10 | Providers that mount archive files like ZIP and RAR as file systems are considered to be separate category again and are not included in this repository: they are part of core Chromium. 11 | 12 | ## Current 13 | 14 | Work has started on the following providers 15 | 16 | - WebDAV 17 | - Supports all read and write operations. 18 | - Authentication not supported at this time. 19 | - Amazon S3 20 | - Supports all read operations and all non-recursive write operations. 21 | - Authentication supported through access and secret key. 22 | - Dropbox 23 | 24 | They can be found in their respective directories with this repository. Please refer to each provider's own readme for installation and usage instructions. 25 | 26 | ## Planned 27 | 28 | There are many more providers we would like to implement, including 29 | 30 | - SFTP 31 | - Google Cloud Storage 32 | - Samba 33 | - Git 34 | - Box 35 | 36 | ## Setup 37 | 38 | To work on any of these providers, you will need Git, Node.js, Google Chrome and Make installed. You should also install Grunt and Bower globally: 39 | 40 | ```bash 41 | $ npm install -g grunt-cli bower 42 | ``` 43 | 44 | Then you can get the repository installed: 45 | 46 | ```bash 47 | $ git clone https://github.com/google/chromeos-filesystems 48 | $ cd chromeos-filesystems 49 | $ bower install 50 | ``` 51 | 52 | ## Testing 53 | 54 | All providers read the files needed by their unit tests from the server in the `testserver` directory. To start it: 55 | 56 | ```bash 57 | $ cd testserver 58 | $ npm install 59 | $ node server.js & 60 | ``` 61 | 62 | ## Icons 63 | 64 | The Photoshop project files for the various sizes of icon for each provider are contained in the `psd` directory in the provider's directory. You will need to install [Photoshop][] or a [PSD-compatible image editor][psdeditor] to edit them. Rendered icons are stored in `extension/icon`. 65 | 66 | It will then run indefinitely in the background on port 8000. This can be changed by modifying `config.js`. 67 | 68 | ## Creating new providers 69 | 70 | Please refer to the [wiki page][create-provider] for instructions on how to create your own provider. Remember to follow the [guidelines](CONTRIBUTING.md) for contributing to this repository. 71 | 72 | ## License 73 | 74 | All providers are licensed under the BSD license. See the LICENSE file for details. 75 | All original source code is Copyright 2014 The Chromium Authors. 76 | 77 | [travis image]: https://travis-ci.org/google/chromeos-filesystems.svg?branch=master 78 | [travis]: https://travis-ci.org/google/chromeos-filesystems 79 | [codeclimate image]: https://codeclimate.com/github/google/chromeos-filesystems/badges/gpa.svg 80 | [codeclimate]: https://codeclimate.com/github/google/chromeos-filesystems 81 | [photoshop]: http://www.photoshop.com/ 82 | [psdeditor]: http://www.makeuseof.com/tag/the-best-ways-to-open-a-psd-file-without-photoshop/ 83 | [create-provider]: https://github.com/google/chromeos-filesystems/wiki/Creating-a-new-provider 84 | -------------------------------------------------------------------------------- /shared_tests/onReadFileRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W027 */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(onReadFileRequested, onOpenFileRequested) { 12 | describe('onReadFileRequested', function() { 13 | it('should fail for files that have not been opened yet', function(done) { 14 | var options = { 15 | openRequestId: 1, 16 | filePath: '/1.txt' 17 | }; 18 | 19 | var onSuccess = function(contents, hasMore) { 20 | throw new Error('Should have rejected file read.'); 21 | done(); 22 | }; 23 | 24 | var onError = function(error) { 25 | error.should.be.a('string'); 26 | error.should.equal('INVALID_OPERATION'); 27 | done(); 28 | }; 29 | 30 | onReadFileRequested(options, onSuccess, onError); 31 | }); 32 | 33 | it('should return the correct contents for an opened file', function(done) { 34 | var options = { 35 | filePath: '/1.txt', 36 | mode: 'READ', 37 | create: false, 38 | requestId: 1 39 | }; 40 | 41 | var expected = '1'; 42 | 43 | var onOpenSuccess = function() { 44 | var options = { 45 | length: 512, 46 | offset: 0, 47 | openRequestId: 1 48 | }; 49 | 50 | var onReadSuccess = function(contents, hasMore) { 51 | contents.should.be.an.instanceof(ArrayBuffer); 52 | 53 | contents.byteLength.should.equal(1); 54 | 55 | var string = arrayBufferToString(contents); 56 | string.substring(0, 6).should.equal(expected); 57 | 58 | done(); 59 | }; 60 | 61 | var onReadError = function(error) { 62 | throw new Error(error); 63 | }; 64 | 65 | onReadFileRequested(options, onReadSuccess, onReadError); 66 | }; 67 | 68 | var onOpenError = function(error) { 69 | throw new Error(error); 70 | }; 71 | 72 | onOpenFileRequested(options, onOpenSuccess, onOpenError); 73 | }); 74 | 75 | it('should work for files larger than the chunk size of 512kb', 76 | function(done) { 77 | // Downloads a 4 megabyte text file filled with 1s. 78 | var options = { 79 | filePath: '/big.txt', 80 | mode: 'READ', 81 | create: false, 82 | requestId: 2 83 | }; 84 | 85 | var onOpenSuccess = function() { 86 | var options = { 87 | length: 512, 88 | offset: 0, 89 | openRequestId: 2 90 | }; 91 | 92 | var onReadSuccess = function(contents, hasMore) { 93 | contents.should.be.an.instanceof(ArrayBuffer); 94 | 95 | contents.byteLength.should.equal(512); 96 | 97 | var string = arrayBufferToString(contents); 98 | string.should.equal(new Array(513).join('1')); 99 | 100 | done(); 101 | }; 102 | 103 | var onReadError = function(error) { 104 | throw new Error(error); 105 | }; 106 | 107 | onReadFileRequested(options, onReadSuccess, onReadError); 108 | }; 109 | 110 | var onOpenError = function(error) { 111 | throw new Error(error); 112 | }; 113 | 114 | onOpenFileRequested(options, onOpenSuccess, onOpenError); 115 | }); 116 | }); 117 | }; 118 | -------------------------------------------------------------------------------- /s3fs/ui/auth.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var AWSValidator = require('./awsvalidator'); 10 | var validator = new AWSValidator(); 11 | var internationalise = require('../../shared/i18n'); 12 | 13 | // Create a function for the Chrome i18n API if it doesn't exist the allow the 14 | // page to be tested as a normal browser page. 15 | if (!chrome.i18n) { 16 | chrome.i18n = { 17 | getMessage: function(name) { 18 | return name; 19 | } 20 | }; 21 | } 22 | 23 | var keys = ['bucket', 'access', 'secret']; 24 | 25 | var fields = {}; 26 | 27 | keys.forEach(function(name) { 28 | fields[name] = document.getElementById(name); 29 | }); 30 | 31 | var button = document.getElementById('mount'); 32 | 33 | // Populate the regions datalist with the list from the validator. 34 | var regionList = document.getElementById('region'); 35 | 36 | for (var i = 0; i < validator.regions.length; i++) { 37 | var region = validator.regions[i]; 38 | 39 | var item = document.createElement('paper-item'); 40 | item.setAttribute('label', region); 41 | 42 | regionList.appendChild(item); 43 | } 44 | 45 | // Restores previously saved credentials and autofills the text fields. 46 | var restoreCredentials = function() { 47 | if (!chrome.storage) { return; } 48 | chrome.storage.sync.get(keys, function(items) { 49 | for (var key in items) { 50 | var value = items[key]; 51 | 52 | if (value) { 53 | fields[key].value = value; 54 | } 55 | } 56 | }); 57 | }; 58 | 59 | var validate = function() { 60 | // Use a custom function here because it's too complex to be expressed by a 61 | // single regular expression. 62 | if (!validator.bucket(fields.bucket.value)) { 63 | document.getElementById('toast-invalid-bucket').show(); 64 | return false; 65 | } 66 | 67 | // Only show a warning for these, instead of returning false. The format is 68 | // not as well defined, so the check is just a guideline, not a rule. 69 | if (!validator.access(fields.bucket.access)) { 70 | document.getElementById('toast-invalid-access').show(); 71 | } 72 | 73 | if (!validator.secret(fields.bucket.secret)) { 74 | document.getElementById('toast-invalid-secret').show(); 75 | } 76 | 77 | return true; 78 | }; 79 | 80 | restoreCredentials(); 81 | internationalise(); 82 | 83 | button.addEventListener('click', function(event) { 84 | event.preventDefault(); 85 | 86 | if (!validate()) { return; } 87 | 88 | button.setAttribute('disabled', 'true'); 89 | 90 | document.getElementById('toast-mount-attempt').show(); 91 | 92 | var request = { 93 | type: 'mount' 94 | }; 95 | 96 | for (var key in fields) { 97 | request[key] = fields[key].value; 98 | } 99 | 100 | var regionSelector = document.getElementById('region'); 101 | 102 | if (regionSelector.selectedItem) { 103 | request.region = regionSelector.selectedItem.label; 104 | } else { 105 | document.getElementById('toast-invalid-region').show(); 106 | return; 107 | } 108 | 109 | chrome.runtime.sendMessage(request, function(response) { 110 | if (response.success) { 111 | document.getElementById('toast-mount-success').show(); 112 | 113 | window.setTimeout(function() { 114 | window.close(); 115 | }, 2000); 116 | } else { 117 | document.getElementById('toast-mount-fail').show(); 118 | button.removeAttribute('disabled'); 119 | } 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /shared/util.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Callback that an ArrayBuffer read asynchronously from some source. 11 | * 12 | * @callback readCallback 13 | * @param {ArrayBuffer} buffer 14 | */ 15 | 16 | /** 17 | * Converts an ArrayBuffer of bytes representing characters in UTF-8 encoding 18 | * into the equivalent string. 19 | * 20 | * @param {ArrayBuffer} buffer The buffer to convert. 21 | * @return {string} The string representation. 22 | */ 23 | var arrayBufferToString = function(buffer) { 24 | return new TextDecoder('utf-8').decode(new DataView(buffer)); 25 | }; 26 | 27 | /** 28 | * Converts a string into an ArrayBuffer of bytes representing the characters 29 | * in UTF-8 encoding. 30 | * 31 | * @param {string} string The string to convert. 32 | * @return {ArrayBuffer} The ArrayBuffer representation of the string. 33 | */ 34 | var stringToArrayBuffer = function(string) { 35 | return new TextEncoder('utf-8').encode(string).buffer; 36 | }; 37 | 38 | /** 39 | * Asynchronously reads the contents of a Blob into an ArrayBuffer. 40 | * 41 | * @param {Blob} blob The Blob to convert. 42 | * @param {readCallback} callback The function to be called with the resulting 43 | * ArrayBuffer when the reading process has completed. 44 | */ 45 | var blobToArrayBuffer = function(blob, callback) { 46 | var reader = new FileReader(); 47 | reader.addEventListener('loadend', function() { 48 | callback(reader.result); 49 | }); 50 | reader.readAsArrayBuffer(blob); 51 | }; 52 | 53 | /** 54 | * Returns true if an options object for opening a file is valid. 55 | * See: https://developer.chrome.com/apps/fileSystemProvider#event-onOpenFileRequested 56 | * 57 | * @param {Object} options The options object to validate. 58 | * @return {Boolean} Whether or not the options object is valid. 59 | */ 60 | var isRequestValid = function(options) { 61 | var validModes = ['READ', 'WRITE']; 62 | 63 | if (validModes.indexOf(options.mode) === -1) { return false; } 64 | 65 | // TODO(lavelle): More validation here eg. 66 | // - Is it valid to open a file with mode = 'READ' and create = true? 67 | // - Are 'READ' and 'WRITE' the only two valid modes? 68 | 69 | return true; 70 | }; 71 | 72 | /** 73 | * Extends a destination object with the properties of one or more source 74 | * objects. 75 | * @param {Object} destination The destination object. 76 | * @param {...Object} var_sources One or more source objects. 77 | * @return {Object} The original destination object, with all the additional 78 | * properties from the source objects. 79 | */ 80 | var extend = function(destination, var_sources) { 81 | var sources = Array.prototype.slice.call(arguments, 1); 82 | 83 | sources.forEach(function(source) { 84 | if (!source) { return; } 85 | 86 | for (var property in source) { 87 | destination[property] = source[property]; 88 | } 89 | }); 90 | 91 | return destination; 92 | }; 93 | 94 | /** 95 | * Creates a metadata object representing a directory. 96 | * @param {string} name The name of the directory. 97 | * @return {Object} A plain object representing a directory with the given name 98 | * in the file system provider API. 99 | */ 100 | var makeDirectory = function(name) { 101 | return { 102 | isDirectory: true, 103 | name: name, 104 | size: 0, 105 | modificationTime: new Date(0), 106 | }; 107 | }; 108 | 109 | module.exports = { 110 | arrayBufferToString: arrayBufferToString, 111 | stringToArrayBuffer: stringToArrayBuffer, 112 | blobToArrayBuffer: blobToArrayBuffer, 113 | isRequestValid: isRequestValid, 114 | extend: extend, 115 | makeDirectory: makeDirectory 116 | }; 117 | -------------------------------------------------------------------------------- /s3fs/ui/awsvalidator.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use-strict'; 8 | 9 | var AWSValidator = function() { 10 | // List of valid AWS regions for S3. Taken from 11 | // http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region. 12 | this.regions = [ 13 | 'us-east-1', 'us-west-2', 'us-west-1', 'eu-west-1', 14 | 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1' 15 | ]; 16 | 17 | // Matches a single alphanumeric character. 18 | var singleAlnum = "[a-z0-9]{1}"; 19 | 20 | this.patterns = { 21 | access: "^[0-9A-Z]{20}$", 22 | secret: "^([a-zA-Z0-9]|\\/|\\+|\\=){40}$", 23 | bucket: '^' + singleAlnum + '([a-z0-9]|\\-|\\.){1,61}' + singleAlnum + '$', 24 | }; 25 | 26 | this.regexes = { 27 | consecutivePeriods: /\.\./g 28 | }; 29 | 30 | for(var key in this.patterns) { 31 | this.regexes[key] = new RegExp(this.patterns[key]); 32 | } 33 | }; 34 | 35 | /** 36 | * Returns whether or not a given string is a valid AWS access key ID. 37 | * Current access key format is exactly 20 uppercase alphanumeric characters. 38 | * See: http://blogs.aws.amazon.com/security/post/Tx1XG3FX6VMU6O5/A-safer-way-to-distribute-AWS-credentials-to-EC2 39 | * 40 | * @param {string} key The key to validate. 41 | * @return {boolean} Whether or not the key is valid. 42 | */ 43 | AWSValidator.prototype.access = function(key) { 44 | return this.regexes.access.test(key); 45 | }; 46 | 47 | /** 48 | * Returns whether or not a given string is a valid AWS secret key. 49 | * Current format is exactly 40 base-64 characters (upper and lower case 50 | * letters, digits, slashes, pluses and equals). 51 | * See: http://blogs.aws.amazon.com/security/post/Tx1XG3FX6VMU6O5/A-safer-way-to-distribute-AWS-credentials-to-EC2 52 | * 53 | * @param {string} key The key to validate. 54 | * @return {boolean} Whether or not the key is valid. 55 | */ 56 | AWSValidator.prototype.secret = function(key) { 57 | return this.regexes.secret.test(key); 58 | }; 59 | 60 | /** 61 | * Returns whether or not a given string is a valid AWS S3 region. 62 | * @param {string} region The region to validate. 63 | * @return {boolean} Whether or not the region is valid. 64 | */ 65 | AWSValidator.prototype.region = function(region) { 66 | return this.regions.indexOf(region) !== -1; 67 | }; 68 | 69 | /** 70 | * Returns whether or not a given string is a valid IPV4 address. 71 | * @param {string} region The IP address to validate. 72 | * @return {boolean} Whether or not the region is valid. 73 | */ 74 | AWSValidator.prototype.ip = function(ip) { 75 | if (typeof ip !== 'string') { return false; } 76 | 77 | var parts = ip.split('.'); 78 | if (parts.length !== 4) { return false; } 79 | 80 | for (var i = 0; i < 4; i++) { 81 | var part = parts[i]; 82 | if (!(/^\d+$/.test(part))) { return false; } 83 | 84 | var number = parseInt(part, 10); 85 | if (Number.isNan(number) || number < 0 || number > 255) { return false; } 86 | } 87 | 88 | return true; 89 | }; 90 | 91 | /** 92 | * Returns whether or not a given string is a valid AWS S3 bucket name. 93 | * 94 | * A bucket name is a string of 3-63 characters (inclusive), containing only 95 | * lowercase letters, digits, periods and hyphens. There must be no consecutive 96 | * periods, and the first character must be a lowercase letter or a digit. 97 | * See: http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html 98 | * 99 | * @param {string} bucket The bucket name to validate. 100 | * @return {boolean} Whether or not the bucket name is valid. 101 | */ 102 | AWSValidator.prototype.bucket = function(bucket) { 103 | // Disallow consecutive periods anywhere in the string. 104 | if (this.regexes.consecutivePeriods.test(bucket)) { return false; } 105 | 106 | // Disallow IP addresses. 107 | if (this.ip(bucket)) { return false; } 108 | 109 | // Check for everything else mentioned above. 110 | return this.regexes.bucket.test(bucket); 111 | }; 112 | 113 | module.exports = AWSValidator; 114 | -------------------------------------------------------------------------------- /webdavfs/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | // Performs static analysis on the code to check for common errors. 5 | jshint: { 6 | options: { 7 | curly: true, 8 | eqeqeq: true, 9 | immed: true, 10 | latedef: true, 11 | newcap: true, 12 | noarg: true, 13 | sub: true, 14 | undef: true, 15 | unused: true, 16 | boss: true, 17 | eqnull: true, 18 | expr: true, 19 | node: true, 20 | browser: true, 21 | globals: { 22 | chrome: true, 23 | webDAVFS: true 24 | } 25 | }, 26 | gruntfile: { 27 | src: 'Gruntfile.js' 28 | }, 29 | src: { 30 | src: ['js/**/*.js'] 31 | }, 32 | test: { 33 | src: ['test/spec/*.js'], 34 | options: { 35 | expr: true, 36 | globals: { 37 | it: true, 38 | describe: true, 39 | should: true, 40 | before: true, 41 | beforeEach: true, 42 | webDAVFS: true, 43 | arrayBufferToString: true, 44 | TextDecoder: true 45 | } 46 | } 47 | }, 48 | ui: { 49 | src: ['ui/auth.js'], 50 | options: { 51 | unused: false, 52 | // Polymer is capitalised and so treated as a constructor by jshint, 53 | // but should not be used with `new`. 54 | newcap: false, 55 | globals: { 56 | Polymer: true, 57 | chrome: true 58 | } 59 | } 60 | } 61 | }, 62 | 63 | // Automatically resolves dependencies and bundles modules into a single 64 | // JavaScript file for deployment. 65 | browserify: { 66 | src: { 67 | files: { 68 | 'extension/background.js': 'js/main.js' 69 | } 70 | }, 71 | test: { 72 | expand: true, 73 | cwd: 'test/spec', 74 | src: '**/*.js', 75 | dest: 'test/build', 76 | ext: '.js', 77 | options: {} 78 | }, 79 | ui: { 80 | files: { 81 | 'ui/build.js': 'ui/auth.js' 82 | } 83 | } 84 | }, 85 | 86 | karma: { 87 | options: { 88 | basePath: 'webdavfs/test/build', 89 | files: [ 90 | 'helper.js', 91 | 'client.js', 92 | 'wdfs.js', 93 | 'events.js' 94 | ], 95 | configFile: '../karma.conf.js' 96 | }, 97 | unit: {} 98 | }, 99 | 100 | // Combines Polymer web components into a single file. 101 | vulcanize: { 102 | main: { 103 | options: { 104 | csp: true, 105 | inline: true 106 | }, 107 | files: { 108 | 'extension/build.html': 'ui/auth.html' 109 | } 110 | } 111 | }, 112 | 113 | jsdoc : { 114 | dist : { 115 | src: ['js/**/*.js'], 116 | options: { 117 | destination: 'docs', 118 | recurse: true 119 | } 120 | } 121 | }, 122 | 123 | connect: { 124 | docs: { 125 | options: { 126 | base: 'docs', 127 | port: 4000, 128 | keepalive: true 129 | } 130 | } 131 | }, 132 | 133 | jsonlint: { 134 | all: { 135 | src: [ 136 | 'package.json', 137 | '../bower.json', 138 | 'extension/manifest.json', 139 | 'extension/_locales/**/*.json' 140 | ] 141 | } 142 | } 143 | }); 144 | 145 | // Load all grunt plugins needed to run the tasks. 146 | require('load-grunt-tasks')(grunt); 147 | 148 | // Register task aliases. 149 | grunt.registerTask('src', ['jshint:src', 'browserify:src']); 150 | grunt.registerTask('test', ['jshint:test', 'browserify:test', 'karma']); 151 | grunt.registerTask('ui', ['jshint:ui', 'browserify:ui', 'vulcanize']); 152 | grunt.registerTask('docs', ['jsdoc']); 153 | grunt.registerTask('lint', ['jshint:gruntfile', 'jshint:src', 'jshint:test', 154 | 'jshint:ui', 'jsonlint']); 155 | 156 | grunt.registerTask('default', ['src', 'ui']); 157 | }; 158 | -------------------------------------------------------------------------------- /shared_tests/onDeleteEntryRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function(fs, onDeleteEntryRequested, onGetMetadataRequested) { 10 | describe('onDeleteEntryRequested', function() { 11 | it('should remove an existing file', function(done) { 12 | var statOptions = { 13 | entryPath: '/1.txt' 14 | }; 15 | 16 | var deleteOptions = { 17 | entryPath: '/1.txt', 18 | recursive: false 19 | }; 20 | 21 | var onError = function(error) { 22 | throw new Error(error); 23 | }; 24 | 25 | var postDeleteSuccess = function() { 26 | throw new Error('Delete operation failed to remove file.'); 27 | }; 28 | 29 | var postDeleteError = function(error) { 30 | error.should.be.a('string'); 31 | error.should.equal('NOT_FOUND'); 32 | done(); 33 | }; 34 | 35 | onGetMetadataRequested(statOptions, function() { 36 | onDeleteEntryRequested(deleteOptions, function() { 37 | onGetMetadataRequested(statOptions, postDeleteSuccess, 38 | postDeleteError); 39 | }, onError); 40 | }, onError); 41 | }); 42 | 43 | if (!window[fs].supportsRecursive) { 44 | return; 45 | } 46 | 47 | it('should remove an empty directory without needing the recursive flag', 48 | function(done) { 49 | var statOptions = { 50 | entryPath: '/empty' 51 | }; 52 | 53 | var deleteOptions = { 54 | entryPath: '/empty', 55 | recursive: false 56 | }; 57 | 58 | var onError = function(error) { 59 | throw new Error(error); 60 | }; 61 | 62 | var postDeleteSuccess = function() { 63 | throw new Error('Delete operation failed to remove empty directory.'); 64 | }; 65 | 66 | var postDeleteError = function(error) { 67 | error.should.be.a('string'); 68 | error.should.equal('NOT_FOUND'); 69 | done(); 70 | }; 71 | 72 | onGetMetadataRequested(statOptions, function() { 73 | onDeleteEntryRequested(deleteOptions, function() { 74 | onGetMetadataRequested(statOptions, postDeleteSuccess, 75 | postDeleteError); 76 | }, onError); 77 | }, onError); 78 | }); 79 | 80 | it('should remove a non-empty directory with the recursive flag', 81 | function(done) { 82 | var statOptions = { 83 | entryPath: '/dir1' 84 | }; 85 | 86 | var deleteOptions = { 87 | entryPath: '/dir1', 88 | recursive: true 89 | }; 90 | 91 | var onError = function(error) { 92 | throw new Error(error); 93 | }; 94 | 95 | var postDeleteSuccess = function() { 96 | throw new Error('Delete operation failed to remove directory.'); 97 | }; 98 | 99 | var postDeleteError = function(error) { 100 | error.should.be.a('string'); 101 | error.should.equal('NOT_FOUND'); 102 | done(); 103 | }; 104 | 105 | onGetMetadataRequested(statOptions, function() { 106 | onDeleteEntryRequested(deleteOptions, function() { 107 | onGetMetadataRequested(statOptions, postDeleteSuccess, 108 | postDeleteError); 109 | }, onError); 110 | }, onError); 111 | }); 112 | 113 | it('should refuse to remove a non-empty directory without the recursive flag', 114 | function(done) { 115 | var deleteOptions = { 116 | entryPath: '/dir1', 117 | recursive: false 118 | }; 119 | 120 | onDeleteEntryRequested(deleteOptions, function() { 121 | throw new Error('Should have rejected an attempt to delete a ' + 122 | 'non-empty directory without the recursive flag set.'); 123 | }, function(error) { 124 | error.should.be.a('string'); 125 | error.should.equal('NOT_EMPTY'); 126 | done(); 127 | }); 128 | }); 129 | }); 130 | }; 131 | -------------------------------------------------------------------------------- /s3fs/js/errors.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * WebDAV error code map. 11 | * All taken from http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html 12 | * @const 13 | * @type {Object} 14 | */ 15 | var ERROR_MAP = { 16 | AccessDenied: 'ACCESS_DENIED', 17 | AccountProblem: 'FAILED', 18 | AmbiguousGrantByEmailAddress: 'INVALID_OPERATION', 19 | BadDigest: 'INVALID_OPERATION', 20 | BucketAlreadyExists: 'EXISTS', 21 | BucketAlreadyOwnedByYou: 'FAILED', 22 | BucketNotEmpty: 'NOT_EMPTY', 23 | CredentialsNotSupported: 'INVALID_OPERATION', 24 | CrossLocationLoggingProhibited: 'ACCESS_DENIED', 25 | EntityTooSmall: 'INVALID_OPERATION', 26 | EntityTooLarge: 'INVALID_OPERATION', 27 | ExpiredToken: 'ACCESS_DENIED', 28 | IllegalVersioningConfigurationException: 'ACCESS_DENIED', 29 | IncompleteBody: 'INVALID_OPERATION', 30 | IncorrectNumberOfFilesInPostRequest: 'INVALID_OPERATION', 31 | InlineDataTooLarge: 'INVALID_OPERATION', 32 | InternalError: 'FAILED', 33 | InvalidAccessKeyId: 'INVALID_OPERATION', 34 | InvalidAddressingHeader: 'INVALID_OPERATION', 35 | InvalidArgument: 'INVALID_OPERATION', 36 | InvalidBucketName: 'INVALID_OPERATION', 37 | InvalidBucketState: 'INVALID_OPERATION', 38 | InvalidDigest: 'INVALID_OPERATION', 39 | InvalidEncryptionAlgorithmError: 'INVALID_OPERATION', 40 | InvalidLocationConstraint: 'INVALID_OPERATION', 41 | InvalidObjectState: 'INVALID_OPERATION', 42 | InvalidPart: 'INVALID_OPERATION', 43 | InvalidPartOrder: 'INVALID_OPERATION', 44 | InvalidPayer: 'INVALID_OPERATION', 45 | InvalidPolicyDocument: 'INVALID_OPERATION', 46 | InvalidRange: 'INVALID_OPERATION', 47 | InvalidRequest: 'INVALID_OPERATION', 48 | InvalidSecurity: 'INVALID_OPERATION', 49 | InvalidSOAPRequest: 'INVALID_OPERATION', 50 | InvalidStorageClass: 'INVALID_OPERATION', 51 | InvalidTargetBucketForLogging: 'INVALID_OPERATION', 52 | InvalidToken: 'INVALID_OPERATION', 53 | InvalidURI: 'INVALID_OPERATION', 54 | KeyTooLong: 'INVALID_OPERATION', 55 | MalformedACLError: 'INVALID_OPERATION', 56 | MalformedPOSTRequest: 'INVALID_OPERATION', 57 | MalformedXML: 'INVALID_OPERATION', 58 | MaxMessageLengthExceeded: 'INVALID_OPERATION', 59 | MaxPostPreDataLengthExceededError: 'INVALID_OPERATION', 60 | MetadataTooLarge: 'INVALID_OPERATION', 61 | MethodNotAllowed: 'ACCESS_DENIED', 62 | MissingAttachment: 'INVALID_OPERATION', 63 | MissingContentLength: 'INVALID_OPERATION', 64 | MissingRequestBodyError: 'INVALID_OPERATION', 65 | MissingSecurityElement: 'INVALID_OPERATION', 66 | MissingSecurityHeader: 'INVALID_OPERATION', 67 | NoLoggingStatusForKey: 'INVALID_OPERATION', 68 | NoSuchBucket: 'NOT_FOUND', 69 | NoSuchKey: 'NOT_FOUND', 70 | NoSuchLifecycleConfiguration: 'NOT_FOUND', 71 | NoSuchUpload: 'NOT_FOUND', 72 | NoSuchVersion: 'NOT_FOUND', 73 | NotImplemented: 'INVALID_OPERATION', 74 | NotSignedUp: 'ACCESS_DENIED', 75 | NotSuchBucketPolicy: 'NOT_FOUND', 76 | OperationAborted: 'FAILED', 77 | PermanentRedirect: 'FAILED', 78 | PreconditionFailed: 'FAILED', 79 | Redirect: 'FAILED', 80 | RestoreAlreadyInProgress: 'INVALID_OPERATION', 81 | RequestIsNotMultiPartContent: 'INVALID_OPERATION', 82 | RequestTimeout: 'FAILED', 83 | RequestTimeTooSkewed: 'FAILED', 84 | RequestTorrentOfBucketError: 'FAILED', 85 | SignatureDoesNotMatch: 'ACCESS_DENIED', 86 | ServiceUnavailable: 'NOT_FOUND', 87 | SlowDown: 'INVALID_OPERATION', 88 | TemporaryRedirect: 'INVALID_OPERATION', 89 | TokenRefreshRequired: 'ACCESS_DENIED', 90 | TooManyBuckets: 'INVALID_OPERATION', 91 | UnexpectedContent: 'INVALID_OPERATION', 92 | UnresolvableGrantByEmailAddress: 'SECURITY', 93 | UserKeyMustBeSpecified: 'INVALID_OPERATION' 94 | }; 95 | 96 | /** 97 | * Converts an AWS SDK error code into its equivalent code for the FSP API. 98 | * @param {Error} code The AWS SDK error object. 99 | * @return {string} The FSP error code. 100 | */ 101 | var getError = function(error) { 102 | return ERROR_MAP[error.code] || 'FAILED'; 103 | }; 104 | 105 | module.exports = getError; 106 | -------------------------------------------------------------------------------- /templatefs/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | 5 | // Performs static analysis on the code to check for common errors. 6 | jshint: { 7 | options: { 8 | curly: true, 9 | eqeqeq: true, 10 | immed: true, 11 | latedef: true, 12 | newcap: true, 13 | noarg: true, 14 | sub: true, 15 | undef: true, 16 | unused: true, 17 | boss: true, 18 | eqnull: true, 19 | strict: false, 20 | // Browserify provides a Node-like environement, so this is true. 21 | node: true, 22 | // Chrome extensions have access to browser APIs, so this is true. 23 | browser: true, 24 | globals: { 25 | chrome: true, 26 | AWS: true, 27 | s3fs: true 28 | } 29 | }, 30 | // Lint this file. 31 | gruntfile: { 32 | src: 'Gruntfile.js' 33 | }, 34 | // Lint the main source files for the extension. 35 | src: { 36 | src: ['js/**/*.js'] 37 | }, 38 | // Lint the unit test specification files. 39 | test: { 40 | src: ['test/spec/*.js'], 41 | options: { 42 | unused: false, 43 | globals: { 44 | // Global functions exposed by the Mocha test framework. 45 | it: true, 46 | describe: true, 47 | // Global functions exposed by the Chai assertion library. 48 | should: true, 49 | // Application globals. 50 | s3fs: true, 51 | AWS: true 52 | } 53 | } 54 | }, 55 | ui: { 56 | src: ['ui/auth.js'], 57 | options: { 58 | unused: false, 59 | // Polymer is capitalised and so treated as a constructor by jshint, 60 | // but should not be used with `new`. 61 | newcap: false, 62 | globals: { 63 | Polymer: true, 64 | chrome: true 65 | } 66 | } 67 | } 68 | }, 69 | 70 | // Automatically resolves dependencies and bundles modules into a single 71 | // JavaScript file for distribution. 72 | browserify: { 73 | // Bundles the main extension source code into a single file for 74 | // distribution. 75 | src: { 76 | files: { 77 | 'extension/background.js': 'js/main.js' 78 | } 79 | }, 80 | // Bundles the test spec files and their dependencies. 81 | test: { 82 | expand: true, 83 | cwd: 'test/spec', 84 | src: '**/*.js', 85 | dest: 'test/build', 86 | ext: '.js', 87 | options: {} 88 | }, 89 | ui: { 90 | files: { 91 | 'ui/build.js': 'ui/auth.js' 92 | } 93 | } 94 | }, 95 | 96 | // Runs the unit test suite in a headless WebKit instance. 97 | mocha: { 98 | src: ['test/index.html'], 99 | options: { 100 | // Report test results in full detail, instead of the default minimal 101 | // view. 102 | reporter: 'Spec', 103 | // Enable console.log within tests for debugging. 104 | log: true, 105 | // Show full error stack traces for debugging. 106 | logErrors: true 107 | } 108 | }, 109 | 110 | // Combines Polymer web components into a single file. 111 | vulcanize: { 112 | main: { 113 | options: { 114 | csp: true, 115 | inline: true 116 | }, 117 | files: { 118 | 'extension/build.html': 'ui/auth.html' 119 | } 120 | } 121 | } 122 | }); 123 | 124 | // Load all the plugins needed to run the tasks. 125 | require('load-grunt-tasks')(grunt); 126 | 127 | // Register aliases for common groups of tasks. 128 | 129 | // Src task lints the source code and bundles it for distribution. 130 | grunt.registerTask('src', ['jshint:src', 'browserify:src']); 131 | 132 | // Test task lints the test specifications themselves, bundles them and runs 133 | // them. 134 | grunt.registerTask('test', ['jshint:test', 'browserify:test', 'mocha']); 135 | 136 | // Lints the scripts for the UI, bundles them and then combines all the web 137 | // component assets into a single file. 138 | grunt.registerTask('ui', ['jshint:ui', 'browserify:ui', 'vulcanize']); 139 | 140 | grunt.registerTask('lint', ['jshint:gruntfile', 'jshint:src', 'jshint:test', 'jshint:ui']); 141 | 142 | grunt.registerTask('default', ['src', 'ui']); 143 | }; 144 | -------------------------------------------------------------------------------- /shared_tests/onMoveEntryRequested.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | module.exports = function(onMoveEntryRequested, onGetMetadataRequested) { 10 | describe('onMoveEntryRequested', function() { 11 | it('should be able to move a file ', function(done) { 12 | var source = 'dir11/11.txt'; 13 | var target = '11_moved.txt'; 14 | 15 | var statSource = { 16 | entryPath: '/' + source 17 | }; 18 | 19 | var statTarget = { 20 | entryPath: '/' + target 21 | }; 22 | 23 | var copyOptions = { 24 | sourcePath: '/' + source, 25 | targetPath: '/' + target 26 | }; 27 | 28 | var onError = function(error) { 29 | throw new Error(error); 30 | }; 31 | 32 | var postMoveSuccess = function(data) { 33 | data.name.should.equal(target); 34 | done(); 35 | }; 36 | 37 | onGetMetadataRequested(statTarget, function() { 38 | throw new Error('File should not be at target location before moving.'); 39 | }, function() { 40 | onMoveEntryRequested(copyOptions, function() { 41 | onGetMetadataRequested(statTarget, function(data) { 42 | onGetMetadataRequested(statSource, function() { 43 | throw new Error('File should not be at source location after ' + 44 | 'moving.'); 45 | }, function() { 46 | postMoveSuccess(data); 47 | }) 48 | }, function() { 49 | throw new Error('File should be at target location after moving.'); 50 | }); 51 | }, onError); 52 | }); 53 | }); 54 | 55 | it('should be able to move a directory', function(done) { 56 | var source = 'dir11'; 57 | var target = '11_moved'; 58 | 59 | var statSource = { 60 | entryPath: '/' + source 61 | }; 62 | 63 | var statTarget = { 64 | entryPath: '/' + target 65 | }; 66 | 67 | var copyOptions = { 68 | sourcePath: '/' + source, 69 | targetPath: '/' + target 70 | }; 71 | 72 | var onError = function(error) { 73 | throw new Error(error); 74 | }; 75 | 76 | var postMoveSuccess = function(data) { 77 | data.name.should.equal(target); 78 | done(); 79 | }; 80 | 81 | onGetMetadataRequested(statTarget, function() { 82 | throw new Error('Directory should not be at target location before moving.'); 83 | }, function() { 84 | onMoveEntryRequested(copyOptions, function() { 85 | onGetMetadataRequested(statTarget, function(data) { 86 | onGetMetadataRequested(statSource, function() { 87 | throw new Error('Directory should not be at source location after ' + 88 | 'moving.'); 89 | }, function() { 90 | postMoveSuccess(data); 91 | }) 92 | }, function() { 93 | throw new Error('Directory should be at target location after moving.'); 94 | }); 95 | }, onError); 96 | }); 97 | }); 98 | 99 | it('should not overwrite existing files/directories', function(done) { 100 | var source = 'dir11'; 101 | var target = '11_moved'; 102 | 103 | var statSource = { 104 | entryPath: '/' + source 105 | }; 106 | 107 | var statTarget = { 108 | entryPath: '/' + target 109 | }; 110 | 111 | var copyOptions = { 112 | sourcePath: '/' + source, 113 | targetPath: '/' + target 114 | }; 115 | 116 | var onError = function(error) { 117 | throw new Error(error); 118 | }; 119 | 120 | var postMoveSuccess = function(data) { 121 | data.name.should.equal(target); 122 | done(); 123 | }; 124 | 125 | onGetMetadataRequested(statTarget, function() { 126 | throw new Error('Directory should not be at target location before moving.'); 127 | }, function() { 128 | onMoveEntryRequested(copyOptions, function() { 129 | onGetMetadataRequested(statTarget, function(data) { 130 | onGetMetadataRequested(statSource, function() { 131 | throw new Error('Directory should not be at source location after ' + 132 | 'moving.'); 133 | }, function() { 134 | postMoveSuccess(data); 135 | }) 136 | }, function() { 137 | throw new Error('Directory should be at target location after moving.'); 138 | }); 139 | }, onError); 140 | }); 141 | }); 142 | }); 143 | }; 144 | -------------------------------------------------------------------------------- /s3fs/test/spec/awsvalidator.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | /* jshint -W030 */ 8 | 9 | 'use strict'; 10 | 11 | var AWSValidator = require('../../ui/awsvalidator'); 12 | 13 | var validator = new AWSValidator(); 14 | 15 | describe('AWSValidator', function() { 16 | describe('access', function() { 17 | it('should fail for strings shorter than 20 characters', function() { 18 | validator.access('FOO').should.be.false; 19 | }); 20 | 21 | it('should fail for strings longer than 20 characters', function() { 22 | validator.access('ABCDEFGHIJKLMNOPQRSTUVWXYZ').should.be.false; 23 | }); 24 | 25 | it('should fail for strings of 20 characters with the wrong start', function() { 26 | validator.access('BAD-ABCDEFGHIJKLMNOP').should.be.false; 27 | }); 28 | 29 | it('should fail for strings of 20 characters with the right start but other invalid characters', function() { 30 | validator.access('AKIABCDEFGHIJK+M!O#').should.be.false; 31 | }); 32 | 33 | it('should succeed for strings of 20 characters with the right start and all other characters valid', function() { 34 | validator.access('AKIABCDEFGHIJKLM1234').should.be.true; 35 | }); 36 | }); 37 | 38 | describe('secret', function() { 39 | it('should fail for strings shorter than 40 characters', function() { 40 | validator.secret('FOO').should.be.false; 41 | }); 42 | 43 | it('should fail for strings longer than 40 characters', function() { 44 | var long52 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ'; 45 | validator.secret(long52).should.be.false; 46 | }); 47 | 48 | it('should fail for strings of 40 characters with invalid characters', function() { 49 | validator.secret('ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHI#').should.be.false; 50 | }); 51 | 52 | it('should succeed for strings of 40 characters with valid characters', function() { 53 | validator.secret('ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ').should.be.true; 54 | }); 55 | }); 56 | 57 | describe('region', function() { 58 | it('should reject invalid regions', function() { 59 | validator.region(undefined).should.be.false; 60 | validator.region(null).should.be.false; 61 | validator.region(1).should.be.false; 62 | validator.region('foo').should.be.false; 63 | validator.region('reallyreallyreallylong').should.be.false; 64 | validator.region('us-west-932').should.be.false; 65 | }); 66 | 67 | it('should accept valid regions', function() { 68 | validator.region('us-west-1').should.be.true; 69 | validator.region('ap-southeast-2').should.be.true; 70 | }); 71 | }); 72 | 73 | describe('bucket', function() { 74 | it('should reject bucket names shorter than 3 characters', function() { 75 | validator.bucket('hi').should.be.false; 76 | }); 77 | 78 | it('should reject bucket names longer than 63 characters', function() { 79 | validator.bucket('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa').should.be.false; 80 | }); 81 | 82 | it('should reject bucket names that do not start with a letter or digit', function() { 83 | validator.bucket('.hi').should.be.false; 84 | validator.bucket('-hi').should.be.false; 85 | }); 86 | 87 | it('should reject bucket names with two or more consecutive periods', function() { 88 | validator.bucket('foo..foo').should.be.false; 89 | }); 90 | 91 | it('should reject bucket names that end with a period', function() { 92 | validator.bucket('foo.').should.be.false; 93 | }); 94 | 95 | it('should accept valid bucket names', function() { 96 | validator.bucket('mybucket').should.be.true; 97 | validator.bucket('cool-bucket').should.be.true; 98 | validator.bucket('foo.bucket').should.be.true; 99 | validator.bucket('foo').should.be.true; 100 | validator.bucket('a-pretty-long-bucket-but-still-shorter-than-63-chars').should.be.true; 101 | }); 102 | }); 103 | 104 | describe('ip', function() { 105 | it('should correctly identify IPV4 addresses', function() { 106 | it('should reject IP addresses', function() { 107 | var ip = validator.regexes.ip; 108 | 109 | validator.ip('127.0.0.1').should.be.true; 110 | validator.ip('192.168.0.1').should.be.true; 111 | validator.ip('8.8.8.8').should.be.true; 112 | validator.ip('255.255.255.255').should.be.true; 113 | validator.ip('999.999.999.999').should.be.false; 114 | validator.ip('abc.def.hij.klm').should.be.false; 115 | validator.ip('abc.1.1.1').should.be.false; 116 | validator.ip('0abc.0abc.0abc.0abc').should.be.false; 117 | }); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /s3fs/js/main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var S3FS = require('./s3fs'); 10 | 11 | // Import all the functions to handle the various file system events. 12 | var events = require('./events'); 13 | 14 | var keys = ['bucket', 'region', 'access', 'secret']; 15 | 16 | /** 17 | * Mounts an S3 bucket with the given name and region as a file system in the 18 | * file browser. 19 | * 20 | * @param {string} bucket The name of the bucket. 21 | * @param {string} region The AWS region of the bucket. 22 | * @param {string} access The AWS access key ID for the user who is accessing 23 | * the bucket. 24 | * @param {string} secret The AWS secret key for the user who is accessing 25 | * the bucket. 26 | * @param {function=} callbacks.onSuccess The function to be called when the 27 | * bucket is mounted successfully. 28 | * @param {function=} callbacks.onError The function to be called if the bucket 29 | * fails to mount. 30 | */ 31 | var mount = function(bucket, region, access, secret, opt_callbacks) { 32 | window.s3fs = new S3FS(bucket, region, access, secret); 33 | 34 | var callbacks = opt_callbacks || {}; 35 | 36 | // Set a default error handler that logs the error for developer use. 37 | var onError = callbacks.onError || function(error) { 38 | console.error('Failed to mount the file system.'); 39 | console.error(error); 40 | }; 41 | 42 | var onSuccess = function() { 43 | // Register each of the event listeners to the FSP. 44 | for (var name in events) { 45 | chrome.fileSystemProvider[name].addListener(events[name]); 46 | } 47 | 48 | // Store the credentials so the bucket can be automatically remounted 49 | // after a Chrome relaunch. 50 | chrome.storage.sync.set({ 51 | access: access, 52 | secret: secret, 53 | bucket: bucket, 54 | region: region 55 | }); 56 | 57 | if (callbacks.onSuccess) { 58 | callbacks.onSuccess(); 59 | } 60 | }; 61 | 62 | // Mount the file system. 63 | chrome.fileSystemProvider.mount(s3fs.options, onSuccess, onError); 64 | }; 65 | 66 | window.onload = function() { 67 | // Remount the instance when Chrome is relaunched. If there are credentials 68 | // saved, use them straight away to mount an instance. 69 | 70 | chrome.storage.sync.get(keys, function(items) { 71 | // If any of the 4 required values are missing, abort. 72 | for (var i = 0; i < keys.length; i++) { 73 | if (!items[keys[i]]) { return; } 74 | } 75 | 76 | // Mount the instance using saved credentials. 77 | mount(items.bucket, items.region, items.access, items.secret); 78 | }); 79 | }; 80 | 81 | 82 | chrome.app.runtime.onLaunched.addListener(function() { 83 | // Open the settings UI when the user clicks on the app icon in the Chrome 84 | // app launcher. 85 | 86 | var windowOptions = { 87 | outerBounds: { 88 | minWidth: 800, 89 | minHeight: 700 90 | } 91 | }; 92 | 93 | chrome.app.window.create('build.html', windowOptions); 94 | }); 95 | 96 | // Main function to handle requests from the settings UI. 97 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 98 | switch (request.type) { 99 | case 'mount': 100 | // TODO(lavelle): at this point bucket and region are syntactically valid 101 | // strings for their repsective types, but may still cause errors. 102 | // Errors to test for this before mounting: 103 | // - Bucket does not exist. 104 | // - Wrong region for bucket. 105 | // - Invalid credentials for bucket/access denied. 106 | 107 | // Mount the bucket with the given request data. 108 | mount(request.bucket, request.region, request.access, request.secret, { 109 | onSuccess: function() { 110 | sendResponse({ 111 | type: 'mount', 112 | success: true 113 | }); 114 | }, 115 | onError: function(error) { 116 | sendResponse({ 117 | type: 'mount', 118 | success: false, 119 | error: error 120 | }); 121 | } 122 | }); 123 | break; 124 | default: 125 | var message; 126 | if (request.type) { 127 | message = 'Invalid request type: ' + request.type + '.'; 128 | } else { 129 | message = 'No request type provided.'; 130 | } 131 | 132 | sendResponse({ 133 | type: 'error', 134 | success: false, 135 | message: message 136 | }); 137 | break; 138 | } 139 | 140 | // Return true from the event listener to indicate that we will be sending 141 | // the response asynchronously, so that the sendResponse function is still 142 | // valid at the time it's used. 143 | return true; 144 | }); 145 | -------------------------------------------------------------------------------- /s3fs/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | 5 | // Performs static analysis on the code to check for common errors. 6 | jshint: { 7 | options: { 8 | curly: true, 9 | eqeqeq: true, 10 | immed: true, 11 | latedef: true, 12 | newcap: true, 13 | noarg: true, 14 | sub: true, 15 | undef: true, 16 | unused: true, 17 | boss: true, 18 | eqnull: true, 19 | strict: false, 20 | // Browserify provides a Node-like environement, so this is true. 21 | node: true, 22 | // Chrome extensions have access to browser APIs, so this is true. 23 | browser: true, 24 | globals: { 25 | chrome: true, 26 | AWS: true, 27 | s3fs: true 28 | } 29 | }, 30 | // Lint this file. 31 | gruntfile: { 32 | src: 'Gruntfile.js' 33 | }, 34 | // Lint the main source files for the extension. 35 | src: { 36 | src: ['js/**/*.js'] 37 | }, 38 | // Lint the unit test specification files. 39 | test: { 40 | src: ['test/spec/*.js'], 41 | options: { 42 | unused: false, 43 | globals: { 44 | // Global functions exposed by the Mocha test framework. 45 | it: true, 46 | describe: true, 47 | before: true, 48 | beforeEach: true, 49 | // Global functions exposed by the Chai assertion library. 50 | should: true, 51 | // Application globals. 52 | s3fs: true, 53 | AWS: true 54 | } 55 | } 56 | }, 57 | ui: { 58 | src: ['ui/auth.js'], 59 | options: { 60 | unused: false, 61 | // Polymer is capitalised and so treated as a constructor by jshint, 62 | // but should not be used with `new`. 63 | newcap: false, 64 | globals: { 65 | Polymer: true, 66 | chrome: true 67 | } 68 | } 69 | } 70 | }, 71 | 72 | // Automatically resolves dependencies and bundles modules into a single 73 | // JavaScript file for distribution. 74 | browserify: { 75 | // Bundles the main extension source code into a single file for 76 | // distribution. 77 | src: { 78 | files: { 79 | 'extension/background.js': 'js/main.js' 80 | } 81 | }, 82 | // Bundles the test spec files and their dependencies. 83 | test: { 84 | expand: true, 85 | cwd: 'test/spec', 86 | src: '**/*.js', 87 | dest: 'test/build', 88 | ext: '.js', 89 | options: {} 90 | }, 91 | ui: { 92 | files: { 93 | 'ui/build.js': 'ui/auth.js' 94 | } 95 | } 96 | }, 97 | 98 | // Combines Polymer web components into a single file. 99 | vulcanize: { 100 | main: { 101 | options: { 102 | csp: true, 103 | inline: true 104 | }, 105 | files: { 106 | 'extension/build.html': 'ui/auth.html' 107 | } 108 | } 109 | }, 110 | 111 | karma: { 112 | options: { 113 | basePath: 's3fs/test/build', 114 | files: [ 115 | 's3mock.js', 116 | 'helper.js', 117 | 's3fs.js', 118 | 'events.js', 119 | 'awsvalidator.js', 120 | 'util.js' 121 | ], 122 | configFile: '../karma.conf.js' 123 | }, 124 | unit: {} 125 | }, 126 | 127 | jsdoc : { 128 | dist : { 129 | src: ['js/**/*.js'], 130 | options: { 131 | destination: 'docs', 132 | recurse: true 133 | } 134 | } 135 | }, 136 | 137 | connect: { 138 | docs: { 139 | options: { 140 | base: 'docs', 141 | port: 4000, 142 | keepalive: true 143 | } 144 | } 145 | }, 146 | 147 | copy: { 148 | main: { 149 | files: { 150 | 'extension/aws-sdk.js': '../third_party/aws-sdk/dist/aws-sdk.min.js' 151 | } 152 | } 153 | }, 154 | 155 | jsonlint: { 156 | all: { 157 | src: [ 158 | 'package.json', 159 | '../bower.json', 160 | 'extension/manifest.json', 161 | 'extension/_locales/**/*.json' 162 | ] 163 | } 164 | } 165 | }); 166 | 167 | // Load all the plugins needed to run the tasks. 168 | require('load-grunt-tasks')(grunt); 169 | 170 | // Register aliases for common groups of tasks. 171 | 172 | // Src task lints the source code and bundles it for distribution. 173 | grunt.registerTask('src', ['jshint:src', 'browserify:src']); 174 | 175 | // Test task lints the test specifications themselves, bundles them and runs 176 | // them. 177 | grunt.registerTask('test', ['jshint:test', 'browserify:test', 'karma']); 178 | 179 | // Lints the scripts for the UI, bundles them and then combines all the web 180 | // component assets into a single file. 181 | grunt.registerTask('ui', ['jshint:ui', 'browserify:ui', 'vulcanize']); 182 | 183 | grunt.registerTask('docs', ['jsdoc']); 184 | 185 | grunt.registerTask('lint', ['jshint:gruntfile', 'jshint:src', 'jshint:test', 186 | 'jshint:ui', 'jsonlint']); 187 | 188 | grunt.registerTask('default', ['src', 'ui']); 189 | }; 190 | -------------------------------------------------------------------------------- /s3fs/test/spec/s3mock.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var WebDAVFS = require('../../../webdavfs/js/wdfs'); 10 | /** 11 | * Mocks the parts of the official AWS JavaScript SDK that are used by the S3 12 | * filesystem for testing without needing to connect to a real S3 bucket. 13 | * Connects to the test WebDAV server used by the WebDAV FS test suite and 14 | * converts the responses to the S3 API format. 15 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html 16 | */ 17 | var AWS = {}; 18 | 19 | AWS.config = { 20 | // Credentials aren't needed by the mock so this is a noop. 21 | update: function() {} 22 | }; 23 | 24 | /** 25 | * Dummy class to mock the interface of the S3 response body object. 26 | * @constructs 27 | * @param {ArrayBuffer} buffer An ArrayBuffer holding the contents of the file. 28 | */ 29 | var ResponseBody = function(buffer) { 30 | this.buffer = buffer; 31 | this.length = buffer.byteLength; 32 | }; 33 | 34 | /** 35 | * @return ArrayBuffer The stored buffer. 36 | */ 37 | ResponseBody.prototype.toArrayBuffer = function() { 38 | return this.buffer; 39 | }; 40 | 41 | /** 42 | * Class for S3-specific methods within the AWS namespace. 43 | */ 44 | var S3 = function() { 45 | this.wdfs = new WebDAVFS('http://localhost:8000'); 46 | }; 47 | 48 | /** 49 | * Implements the S3 listObjects method in terms of WebDAVFS's readDirectory. 50 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#listObjects-property 51 | * 52 | * @param {Object} parameters The S3 API parameters for this function. 53 | * @param {function} callback The callback to be executed when the operation 54 | * finishes. 55 | */ 56 | S3.prototype.listObjects = function(parameters, callback) { 57 | var path = '/' + parameters.Prefix; 58 | 59 | if (parameters.Delimiter !== '/') { 60 | var message = 'Missing or invalid delimiter for listObjects call. ' + 61 | 'Always use \'/\'.'; 62 | callback(message, null); 63 | return; 64 | } 65 | 66 | this.wdfs.readDirectory({ 67 | path: path, 68 | onSuccess: function(response) { 69 | var error = null; 70 | 71 | var CommonPrefixes = []; 72 | var Contents = []; 73 | 74 | response.map(function(item) { 75 | if (item.isDirectory) { 76 | CommonPrefixes.push({ 77 | Prefix: item.name + '/' 78 | }); 79 | } else { 80 | Contents.push({ 81 | Key: item.name, 82 | Size: item.size, 83 | LastModified: item.modificationTime 84 | }); 85 | } 86 | }); 87 | 88 | var data = { 89 | CommonPrefixes: CommonPrefixes, 90 | Contents: Contents 91 | }; 92 | callback(error, data); 93 | }, 94 | onError: function(error) { 95 | var data = null; 96 | callback(error, data); 97 | } 98 | }); 99 | }; 100 | 101 | /** 102 | * Implements the S3 getObject method in terms of WebDAVFS's readFile. 103 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property 104 | * 105 | * @param {Object} parameters The S3 API parameters for this function. 106 | * @param {function} callback The callback to be executed when the operation 107 | * finishes. 108 | */ 109 | S3.prototype.getObject = function(parameters, callback) { 110 | var path = '/' + parameters.Key; 111 | 112 | var args = { 113 | path: path, 114 | onSuccess: function(buffer) { 115 | var error = null; 116 | 117 | var data = { 118 | LastModified: new Date(), 119 | Body: new ResponseBody(buffer), 120 | ContentType: 'text/plain; charset=utf-8' 121 | }; 122 | 123 | callback(error, data); 124 | }, 125 | onError: function(error) { 126 | var data = null; 127 | callback(error, data); 128 | } 129 | }; 130 | 131 | if (parameters.Range) { 132 | args.range = {}; 133 | var parts = parameters.Range.replace('bytes=', '').split('-'); 134 | 135 | args.range.start = parseInt(parts[0], 10); 136 | 137 | var end = parts[1]; 138 | if (end.length > 0) { 139 | args.range.end = parseInt(end, 10) + 1; 140 | } 141 | } 142 | 143 | this.wdfs.readFile(args); 144 | }; 145 | 146 | /** 147 | * Implements the S3 headObject method in terms of WebDAVFS's getMetadata. 148 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#headObject-property 149 | * 150 | * @param {Object} parameters The S3 API parameters for this function. 151 | * @param {function} callback The callback to be executed when the operation 152 | * finishes. 153 | */ 154 | S3.prototype.headObject = function(parameters, callback) { 155 | var path = '/' + parameters.Key; 156 | 157 | this.wdfs.getMetadata({ 158 | path: path, 159 | onSuccess: function(response) { 160 | var error = null; 161 | 162 | var data = { 163 | ContentLength: response.size, 164 | ContentType: response.mimeType, 165 | LastModified: response.modificationTime.toString() 166 | }; 167 | 168 | callback(error, data); 169 | }, 170 | onError: function(error) { 171 | var data = null; 172 | callback(error, data); 173 | } 174 | }); 175 | }; 176 | 177 | /** 178 | * Implements the S3 putObject method in terms of WebDAVFS's writeFile. 179 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property 180 | * 181 | * @param {Object} parameters The S3 API parameters for this function. 182 | * @param {function} callback The callback to be executed when the operation 183 | * finishes. 184 | */ 185 | S3.prototype.putObject = function(parameters, callback) { 186 | this.wdfs.writeFile({ 187 | path: '/' + parameters.Key, 188 | data: parameters.Body, 189 | onSuccess: function() { 190 | callback(null, {}); 191 | }, 192 | onError: function(error) { 193 | callback(error, null); 194 | } 195 | }); 196 | }; 197 | 198 | /** 199 | * Implements the S3 deleteObject method in terms of WebDAVFS's deleteEntry. 200 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property 201 | * 202 | * @param {Object} parameters The S3 API parameters for this function. 203 | * @param {function} callback The callback to be executed when the operation 204 | * finishes. 205 | */ 206 | S3.prototype.deleteObject = function(parameters, callback) { 207 | this.wdfs.deleteEntry({ 208 | path: '/' + parameters.Key, 209 | onSuccess: function() { 210 | callback(null, {}); 211 | }, 212 | onError: function(error) { 213 | callback(error, null); 214 | } 215 | }); 216 | }; 217 | 218 | /** 219 | * Implements the S3 copyObject method in terms of WebDAVFS's copyEntry. 220 | * See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#copyObject-property 221 | * 222 | * @param {Object} parameters The S3 API parameters for this function. 223 | * @param {function} callback The callback to be executed when the operation 224 | * finishes. 225 | */ 226 | S3.prototype.copyObject = function(parameters, callback) { 227 | this.wdfs.copyEntry({ 228 | sourcePath: '/' + decodeURIComponent(parameters.CopySource).split('/').pop(), 229 | targetPath: '/' + parameters.Key, 230 | onSuccess: function() { 231 | callback(null, {}); 232 | }, 233 | onError: function(error) { 234 | callback(error, null); 235 | } 236 | }); 237 | }; 238 | 239 | AWS.S3 = S3; 240 | 241 | module.exports = AWS; 242 | -------------------------------------------------------------------------------- /webdavfs/js/wdfs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var WebDAVClient = require('./client'); 10 | 11 | var client = new WebDAVClient(); 12 | 13 | /** 14 | * Class to encapsulate data relating to the WebDAV file system. 15 | * @class 16 | * @param {string} url The URL of the WebDAV server to which the client will 17 | * connect. 18 | */ 19 | var WebDAVFS = function(url) { 20 | if (url === '') { 21 | throw new Error(chrome.i18n.getMessage('invalidURL')); 22 | } 23 | 24 | if (url.charAt(url.length - 1) !== '/') { 25 | url += '/'; 26 | } 27 | 28 | this.url = url; 29 | 30 | this.options = { 31 | fileSystemId: 'webdavfs', 32 | displayName: 'WebDAV' 33 | }; 34 | 35 | this.supportsRecursive = true; 36 | 37 | this.openedFiles = {}; 38 | }; 39 | 40 | /** 41 | * Returns the contents of the specified file as an ArrayBuffer. 42 | * @param {Object} options Input options. 43 | * @param {string} path Path to the file to be read, relative to the server 44 | * root. 45 | * @param {function} onSuccess Function to be called with the contents 46 | * of the file if the request succeeds. 47 | * @param {function} onError Function to be called with the error if 48 | * the request fails. 49 | */ 50 | WebDAVFS.prototype.readFile = function(options) { 51 | var url = this.url + options.path; 52 | var headers = null; 53 | 54 | if (options.range && (options.range.start || options.range.end)) { 55 | var rangeString = 'bytes='; 56 | var start = options.range.start; 57 | var end = options.range.end; 58 | 59 | rangeString += start ? start : ''; 60 | rangeString += '-'; 61 | rangeString += end ? end : ''; 62 | 63 | headers = { 64 | Range: rangeString 65 | }; 66 | } 67 | 68 | client.get(url, headers, options.onSuccess, options.onError); 69 | }; 70 | 71 | /** 72 | * Writes the given ArrayBuffer to the file at the given path. 73 | * @param {Object} options Input options. 74 | * @param {string} path Path to the file to write, relative to the server 75 | * root. 76 | * @param {ArrayBuffer} data The data to write. 77 | * @param {function} onSuccess Function to be called if the request succeeds. 78 | * @param {function} onError Function to be called with the error if 79 | * the request fails. 80 | */ 81 | WebDAVFS.prototype.writeFile = function(options) { 82 | var url = this.url + options.path; 83 | var headers = null; 84 | 85 | client.put(url, options.data, headers, options.onSuccess, options.onError); 86 | }; 87 | 88 | /** 89 | * Deletes the file or directory at the given path. 90 | * @param {Object} options Input options. 91 | * @param {string} path Path to the entry to delete, relative to the server 92 | * root. 93 | * @param {function} onSuccess Function to be called if the request succeeds. 94 | * @param {function} onError Function to be called with the error if 95 | * the request fails. 96 | */ 97 | WebDAVFS.prototype.deleteEntry = function(options) { 98 | var url = this.url + options.path; 99 | var headers = null; 100 | 101 | client.delete(url, headers, options.onSuccess, options.onError); 102 | }; 103 | 104 | 105 | /** 106 | * Returns the metadata of the specified file or directory. 107 | * @param {Object} options Input options. 108 | * @param {string} path Path to the file to retrieve the metadata for, 109 | * relative to the server root. 110 | * @param {function} onSuccess Function to be called with the metadata 111 | * if the request succeeds. 112 | * @param {function} onError Function to be called with the error if 113 | * the request fails. 114 | */ 115 | WebDAVFS.prototype.getMetadata = function(options) { 116 | // If it's a directory, we only want the directory metadata itself, not 117 | // that of its children. 118 | var depth = 0; 119 | 120 | var onSuccess = function(doc) { 121 | options.onSuccess(client.nodeToEntry(doc)); 122 | }; 123 | 124 | client.propertyFind(this.url + options.path, onSuccess, options.onError, depth); 125 | }; 126 | 127 | /** 128 | * Copies the file or directory at the source path to the target path. 129 | * @param {Object} options Input options. 130 | * @param {string} sourcePath Path to the file to copy. 131 | * @param {string} targetPath Path to the file new file to copy to. 132 | * @param {function} onSuccess Function to be called if the request 133 | * succeeds. 134 | * @param {function} onError Function to be called with the error if 135 | * the request fails. 136 | */ 137 | WebDAVFS.prototype.copyEntry = function(options) { 138 | client.copy(this.url + options.sourcePath, this.url + options.targetPath, 139 | options.onSuccess, options.onError); 140 | }; 141 | 142 | /** 143 | * Moves the file or directory at the source path to the target path. 144 | * @param {Object} options Input options. 145 | * @param {string} sourcePath Path to the entry to move. 146 | * @param {string} targetPath Path to the location to the move the entry to. 147 | * @param {function} onSuccess Function to be called if the request 148 | * succeeds. 149 | * @param {function} onError Function to be called with the error if 150 | * the request fails. 151 | */ 152 | WebDAVFS.prototype.moveEntry = function(options) { 153 | client.move(this.url + options.sourcePath, this.url + options.targetPath, 154 | options.onSuccess, options.onError); 155 | }; 156 | 157 | /** 158 | * Returns all files and folders that are immediate children of the specified 159 | * directory. 160 | * @param {Object} options Input options. 161 | * @param {string} path Path to the directory to list the contents of, 162 | * relative to the server root. 163 | * @param {function} onSuccess Function to be called with the array of 164 | * child entries if the request succeeds. 165 | * @param {function} onError Function to be called with the error if 166 | * the request fails. 167 | */ 168 | WebDAVFS.prototype.readDirectory = function(options) { 169 | // Converts the WebDAV XML response to the 170 | var convert = function(xmlDocument) { 171 | // If the there are no children at all, the directory must not 172 | // exist, since every directory contains at least a reference 173 | // to itself and its parent. 174 | if (xmlDocument.childNodes == null) { 175 | options.onError('NOT_FOUND'); 176 | return; 177 | } 178 | 179 | // Exclude the first element - it refers to the current 180 | // directory itself. 181 | var nodes = Array.prototype.slice.call(xmlDocument.childNodes, 1); 182 | 183 | // Convert all nodes to entries and return. 184 | return nodes.map(function(node) { 185 | return client.nodeToEntry(node); 186 | }); 187 | }; 188 | 189 | // Only show immediate children. 190 | var depth = 1; 191 | 192 | var onSuccess = function(xmlDocument) { 193 | options.onSuccess(convert(xmlDocument)); 194 | }; 195 | 196 | client.propertyFind(this.url + options.path, onSuccess, options.onError, depth); 197 | }; 198 | 199 | /** 200 | * Makes a request for a special file on the test WebDAV server that causes it 201 | * to reset its contents. Used by the test suite to ensure each test has a 202 | * consistent environment. 203 | * @private 204 | * @param {function} callback Mocha `done` callback. 205 | */ 206 | WebDAVFS.prototype.reset = function(callback) { 207 | var onResponse = function() { callback(); }; 208 | 209 | client.request('GET', this.url + 'reset', {}, null, 'document', onResponse, 210 | onResponse); 211 | }; 212 | 213 | /** 214 | * Test the connection to the server by attempting to read a known file and 215 | * show an error message prompting the developer to start the server if it 216 | * doesn't exist. 217 | * @private 218 | * @param {function} callback Mocha `done` callback. 219 | */ 220 | WebDAVFS.prototype.checkConnection = function(callback) { 221 | this.readFile({ 222 | path: '/1.txt', 223 | range: { 224 | start: 0, 225 | end: 512 226 | }, 227 | onSuccess: function() { 228 | callback(); 229 | }, 230 | onError: function() { 231 | var message = 'Could not connect to server.\nPlease start it by ' + 232 | 'typing `node server.js &` from the testserver directory.'; 233 | throw new Error(message); 234 | } 235 | }); 236 | }; 237 | 238 | module.exports = WebDAVFS; 239 | -------------------------------------------------------------------------------- /webdavfs/js/client.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var getError = require('./errors'); 10 | 11 | if (!Number.isNaN) { 12 | Number.isNaN = function(obj) { 13 | return typeof obj === 'number' && obj !== +obj; 14 | }; 15 | } 16 | 17 | /** 18 | * Class containing methods for communicating with a WebDAV server over XHR 19 | * and manipulating the responses. 20 | * @class 21 | */ 22 | var WebDAVClient = function() {}; 23 | 24 | /** 25 | * Convenience function for selecting the text content of nodes in an XML 26 | * document, using the DAV namespace. 27 | * @param {Node} element The XML Node to select from. 28 | * @param {string} selector The name of the tag to search select, excluding 29 | * the namespace. 30 | * @return {string} The text content of the selected node. 31 | */ 32 | WebDAVClient.prototype.select = function(element, selector) { 33 | // TODO(lavelle): is this consistent for the WebDAV protocol, or an 34 | // implementation detail of the server? 35 | var namespace = 'DAV:'; 36 | 37 | // Perform a query for the given selector, scoped by the DAV namespace. 38 | var elements = element.getElementsByTagNameNS(namespace, selector); 39 | 40 | // Return the text content of the first node in the returned collection, if 41 | // there are any. 42 | if (elements.length > 0) { 43 | return elements[0].textContent; 44 | } else { 45 | // Otherwise just return the empty string - no useful data here. 46 | return ''; 47 | } 48 | }; 49 | 50 | /** 51 | * Converts an XML Node in a WebDAV response object to an object representing 52 | * an entry in the ChromeOS File system. 53 | * @param {Node} node The XML Node to convert 54 | * @return {Object} A plain object representing the file or directory in the 55 | * file system provider API. 56 | */ 57 | WebDAVClient.prototype.nodeToEntry = function(node) { 58 | // Extract the name of the file/directory from the href attribute. 59 | var name = this.select(node, 'href'); 60 | 61 | if (name !== '/') { 62 | // Remove leading and trailing slashes from the name. 63 | name = name.replace(/^\/|\/$/g, '').split('/').pop(); 64 | } 65 | 66 | // Extract the MIME type of the file from the getcontenttype attribute. 67 | var contentType = this.select(node, 'getcontenttype'); 68 | 69 | // Determine whether an entry is a directory by the absence of a content type 70 | // attribute. 71 | var isDirectory = contentType === ''; 72 | 73 | // Extract the modification time of the file from the getlastmodified 74 | // attribute, and parse it into a Date object. 75 | var modificationTime = new Date(this.select(node, 'getlastmodified')); 76 | 77 | var size = parseInt(this.select(node, 'getcontentlength'), 10); 78 | 79 | if (Number.isNaN(size)) { 80 | size = 0; 81 | } 82 | 83 | // Construct the entry object to be returned to the file system provider. 84 | var entry = { 85 | name: name, 86 | modificationTime: modificationTime, 87 | size: size, 88 | isDirectory: isDirectory 89 | }; 90 | 91 | // Only give files MIME types, not directories. 92 | if (!isDirectory) { 93 | entry.mimeType = contentType; 94 | } 95 | 96 | return entry; 97 | }; 98 | 99 | /** 100 | * Make a HTTP GET request -- fetch an entry from the server. 101 | * @param {string} url The URL of the server. 102 | * @param {Object=} opt_headers Any HTTP headers to set on the request. 103 | * @param {Function} onSuccess Function to be called with the response data 104 | * from the request if it was successful. 105 | * @param {Function} onError Function to be called with an error message 106 | * if the request failed. 107 | */ 108 | WebDAVClient.prototype.get = function(url, opt_headers, onSuccess, onError) { 109 | var verb = 'GET'; 110 | var headers = opt_headers || {}; 111 | var data = null; 112 | var responseType = 'arraybuffer'; 113 | 114 | this.request(verb, url, headers, data, responseType, onSuccess, onError); 115 | }; 116 | 117 | /** 118 | * Make a HTTP PUT request -- write an entry to the server. 119 | * @param {string} url The URL of the server. 120 | * @param {Object} data The data to write. 121 | * @param {Object=} opt_headers Any HTTP headers to set on the request. 122 | * @param {Function} onSuccess Function to be called with the response data 123 | * from the request if it was successful. 124 | * @param {Function} onError Function to be called with an error message 125 | * if the request failed. 126 | */ 127 | WebDAVClient.prototype.put = function(url, data, opt_headers, onSuccess, onError) { 128 | var verb = 'PUT'; 129 | var headers = opt_headers || {}; 130 | var responseType = 'document'; 131 | 132 | this.request(verb, url, headers, data, responseType, onSuccess, onError); 133 | }; 134 | 135 | /** 136 | * Make a HTTP DELETE request -- remove an entry from the server. 137 | * @param {string} url The URL of the server. 138 | * @param {Object=} opt_headers Any HTTP headers to set on the request. 139 | * @param {Function} onSuccess Function to be called with the response data 140 | * from the request if it was successful. 141 | * @param {Function} onError Function to be called with an error message 142 | * if the request failed. 143 | */ 144 | WebDAVClient.prototype.delete = function(url, opt_headers, onSuccess, onError) { 145 | var verb = 'DELETE'; 146 | var headers = opt_headers || {}; 147 | var data = null; 148 | var responseType = 'document'; 149 | 150 | this.request(verb, url, headers, data, responseType, onSuccess, onError); 151 | }; 152 | 153 | /** 154 | * Make a HTTP COPY request -- copy an entry from one location to another. 155 | * @param {string} url The URL of the server. 156 | * @param {string} target The path to copy the file to. 157 | * @param {Function} onSuccess Function to be called with the response data 158 | * from the request if it was successful. 159 | * @param {Function} onError Function to be called with an error message 160 | * if the request failed. 161 | */ 162 | WebDAVClient.prototype.copy = function(source, target, onSuccess, onError) { 163 | var verb = 'COPY'; 164 | var headers = { 165 | Destination: target, 166 | Overwrite: 'F' 167 | }; 168 | var data = null; 169 | var responseType = 'document'; 170 | 171 | this.request(verb, source, headers, data, responseType, onSuccess, onError); 172 | }; 173 | 174 | /** 175 | * Make a HTTP MOVE request -- move an entry from one location to another. 176 | * @param {string} url The URL of the server. 177 | * @param {string} target The path to copy the file to. 178 | * @param {Function} onSuccess Function to be called with the response data 179 | * from the request if it was successful. 180 | * @param {Function} onError Function to be called with an error message 181 | * if the request failed. 182 | */ 183 | WebDAVClient.prototype.move = function(source, target, onSuccess, onError) { 184 | var verb = 'MOVE'; 185 | var headers = { 186 | Destination: target, 187 | Overwrite: 'F' 188 | }; 189 | var data = null; 190 | var responseType = 'document'; 191 | 192 | this.request(verb, source, headers, data, responseType, onSuccess, onError); 193 | }; 194 | 195 | /** 196 | * Make a HTTP PROPFIND request -- fetch the metadta for an entry from the 197 | * server. 198 | * @param {string} url The URL of the server. 199 | * @param {Function} onSuccess Function to be called with the response data 200 | * from the request if it was successful. 201 | * @param {Function} onError Function to be called with an error message 202 | * if the request failed. 203 | * @param {Number} depth Number of levels of the filesystem to hierarchy to 204 | * return information for. 205 | */ 206 | WebDAVClient.prototype.propertyFind = function(url, onSuccess, onError, depth) { 207 | var verb = 'PROPFIND'; 208 | var headers = {Depth: depth}; 209 | var data = null; 210 | var responseType = 'document'; 211 | 212 | this.request(verb, url, headers, data, responseType, onSuccess, onError); 213 | }; 214 | 215 | /** 216 | * Low-level wrapper function for making HTTP requests. 217 | * @param {string} verb HTTP verb to use for the request. 218 | * @param {string} url URL to send the request to. 219 | * @param {Object} headers Any HTTP headers to attach to the request. 220 | * @param {Object} data Data to be sent in the request. 221 | * @param {string} responseType The type of data returned by the request. 222 | * @param {Function} onSuccess Function to be called with the response data 223 | * from the request if it was successful. 224 | * @param {Function} onError Function to be called with an error message 225 | * if the request failed. 226 | * @return {Element} The XML document response from the server. 227 | */ 228 | WebDAVClient.prototype.request = function(verb, url, headers, data, responseType, 229 | onSuccess, onError) { 230 | var xhr = new XMLHttpRequest(); 231 | 232 | var processBody = function() { 233 | var body = xhr.response; 234 | 235 | // If we're making a PROPFIND, remove the top level of the XML document 236 | // as it contains no useful information. 237 | if (verb === 'PROPFIND') { 238 | body = body.firstChild.nextSibling ? body.firstChild.nextSibling : body.firstChild; 239 | } 240 | 241 | return body; 242 | }; 243 | 244 | // Register the callback to to be called when the request sucessfully 245 | // completes. 246 | xhr.onreadystatechange = function() { 247 | if(xhr.readyState !== 4) { return; } 248 | 249 | // Return an error for a non-2XX failure status code. 250 | if (xhr.status < 200 || xhr.status >= 300) { 251 | onError(getError(xhr.status)); 252 | return; 253 | } 254 | 255 | var body = processBody(); 256 | 257 | // Return an error if there was no response. 258 | if (!body) { 259 | onError('FAILED'); 260 | return; 261 | } 262 | 263 | onSuccess(body); 264 | }; 265 | 266 | // Always make asynchronous requests. 267 | var isAsynchronous = true; 268 | 269 | // Open the XHR connection. 270 | xhr.open(verb, url, isAsynchronous); 271 | 272 | // Set the expected data type of the server response. 273 | xhr.responseType = responseType; 274 | 275 | // If we're making a GET request, the request data is an XML document 276 | // so make sure to set this in the headers. 277 | if (verb === 'GET') { 278 | xhr.setRequestHeader('Content-Type', 'text/xml; charset=UTF-8'); 279 | } 280 | 281 | // Set any other headers provided by the caller. 282 | for (var header in headers) { 283 | xhr.setRequestHeader(header, headers[header]); 284 | } 285 | 286 | // Send the data over the connection. 287 | xhr.send(data); 288 | }; 289 | 290 | module.exports = WebDAVClient; 291 | -------------------------------------------------------------------------------- /webdavfs/js/events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var util = require('../../shared/util'); 10 | 11 | /** 12 | * Responds to a request to close a file. 13 | * @param {object} options Input options. 14 | * @param {function} onSuccess Function to be called if the file was closed 15 | * successfully. 16 | * @param {function} onError Function to be called if an error occured while 17 | * attempting to close the file. 18 | */ 19 | var onCloseFileRequested = function(options, onSuccess, onError) { 20 | if (!options.openRequestId) { 21 | onError('INVALID_OPERATION'); 22 | return; 23 | } 24 | 25 | if (!webDAVFS.openedFiles[options.openRequestId]) { 26 | onError('INVALID_OPERATION'); 27 | } else { 28 | delete webDAVFS.openedFiles[options.openRequestId]; 29 | onSuccess(); 30 | } 31 | }; 32 | 33 | /** 34 | * Fetches the metadata associated with the file at the given path from the 35 | * WebDAV server. 36 | * @param {Object} options Input options. 37 | * @param {Function} onSuccess Function to be called if the metadata was 38 | * fetched successfully. 39 | * @param {Function} onError Function to be called if an error occured while 40 | * attempting to fetch the metadata. 41 | */ 42 | var onGetMetadataRequested = function(options, onSuccess, onError) { 43 | if (!options.entryPath) { 44 | onError('INVALID_OPERATION'); 45 | return; 46 | } 47 | 48 | var path = options.entryPath; 49 | 50 | webDAVFS.getMetadata({ 51 | path: path, 52 | onSuccess: function(metadata) { 53 | onSuccess(metadata, false); 54 | }, 55 | onError: onError 56 | }); 57 | }; 58 | 59 | /** 60 | * Responds to a request to open a file. 61 | * @param {Object} options Input options. 62 | * @param {Function} onSuccess Function to be called if the file was opened 63 | * successfully. 64 | * @param {Function} onError Function to be called if an error occured while 65 | * attempting to open the file. 66 | */ 67 | var onOpenFileRequested = function(options, onSuccess, onError) { 68 | if (!util.isRequestValid(options)) { 69 | onError('INVALID_OPERATION'); 70 | } 71 | else { 72 | if (options.create) { 73 | // TODO(lavelle): create file here. 74 | } 75 | 76 | webDAVFS.openedFiles[options.requestId] = options.filePath; 77 | onSuccess(); 78 | } 79 | }; 80 | 81 | /** 82 | * Responds to a request for the contents of a directory. 83 | * @param {Object} options Input options. 84 | * @param {Function} onSuccess Function to be called if the directory was 85 | * read successfully. 86 | * @param {Function} onError Function to be called if an error occured while 87 | * attempting to read the directory. 88 | */ 89 | var onReadDirectoryRequested = function(options, onSuccess, onError) { 90 | if (!options.directoryPath) { 91 | onError('INVALID_OPERATION'); 92 | return; 93 | } 94 | 95 | webDAVFS.readDirectory({ 96 | path: options.directoryPath, 97 | onSuccess: function(list) { 98 | onSuccess(list, false); 99 | }, 100 | onError: onError 101 | }); 102 | }; 103 | 104 | /** 105 | * Responds to a request to read the contents of a file. 106 | * @param {Object} options Input options. 107 | * @param {Function} onSuccess Function to be called if the file was 108 | * read successfully. 109 | * @param {Function} onError Function to be called if an error occured while 110 | * attempting to read the file. 111 | */ 112 | var onReadFileRequested = function(options, onSuccess, onError) { 113 | if (!options.openRequestId) { 114 | onError('INVALID_OPERATION'); 115 | return; 116 | } 117 | 118 | var path = webDAVFS.openedFiles[options.openRequestId]; 119 | 120 | if (!path) { 121 | onError('INVALID_OPERATION'); 122 | return; 123 | } 124 | 125 | webDAVFS.readFile({ 126 | path: path, 127 | range: { 128 | start: options.offset, 129 | end: options.offset + options.length 130 | }, 131 | onSuccess: function(data) { 132 | onSuccess(data, false); 133 | }, 134 | onError: onError 135 | }); 136 | }; 137 | 138 | /** 139 | * Responds to a request to write some data to a file. 140 | * @param {Object} options Input options. 141 | * @param {Function} onSuccess Function to be called if the file was 142 | * written to successfully. 143 | * @param {Function} onError Function to be called if an error occured while 144 | * attempting to write to the file. 145 | */ 146 | var onWriteFileRequested = function(options, onSuccess, onError) { 147 | if (!options.openRequestId) { 148 | onError('INVALID_OPERATION'); 149 | return; 150 | } 151 | 152 | var path = webDAVFS.openedFiles[options.openRequestId]; 153 | 154 | if (!path) { 155 | onError('INVALID_OPERATION'); 156 | return; 157 | } 158 | 159 | var end = options.offset + options.length - 1; 160 | var body = options.data.slice(options.offset, end); 161 | 162 | webDAVFS.writeFile({ 163 | path: path, 164 | data: body, 165 | onSuccess: onSuccess, 166 | onError: onError 167 | }); 168 | }; 169 | 170 | /** 171 | * Responds to a request to truncate the contents of a file. 172 | * @param {Object} options Input options. 173 | * @param {Function} onSuccess Function to be called if the file was 174 | * truncated successfully. 175 | * @param {Function} onError Function to be called if an error occured while 176 | * attempting to truncate the file. 177 | */ 178 | 179 | var onTruncateRequested = function(options, onSuccess, onError) { 180 | if (!options.filePath) { 181 | onError('INVALID_OPERATION'); 182 | return; 183 | } 184 | 185 | var path = options.filePath.substring(1); 186 | 187 | var write = function(data) { 188 | webDAVFS.writeFile({ 189 | path: path, 190 | data: data, 191 | onSuccess: onSuccess, 192 | onError: onError 193 | }); 194 | }; 195 | 196 | webDAVFS.readFile({ 197 | path: path, 198 | onSuccess: function(data) { 199 | if (options.length < data.byteLength) { 200 | // Truncate. 201 | write(data.slice(0, options.length)); 202 | } else { 203 | // Pad with null bytes. 204 | var diff = options.length - data.byteLength; 205 | var blob = new Blob([data, new Array(diff + 1).join('\0')]); 206 | util.blobToArrayBuffer(blob, write); 207 | } 208 | }, 209 | onError: onError 210 | }); 211 | }; 212 | 213 | /** 214 | * Responds to a request to create a new file. 215 | * @param {Object} options Input options. 216 | * @param {Function} onSuccess Function to be called if the file was 217 | * created successfully. 218 | * @param {Function} onError Function to be called if an error occured while 219 | * attempting to create the file. 220 | */ 221 | var onCreateFileRequested = function(options, onSuccess, onError) { 222 | if (!options.filePath) { 223 | onError('INVALID_OPERATION'); 224 | return; 225 | } 226 | 227 | webDAVFS.writeFile({ 228 | path: options.filePath.substring(1), 229 | data: new ArrayBuffer(), 230 | onSuccess: onSuccess, 231 | onError: onError 232 | }); 233 | }; 234 | 235 | /** 236 | * Responds to a request to copy a file or directory to a new location. 237 | * @param {Object} options Input options. 238 | * @param {Function} onSuccess Function to be called if the entry was 239 | * copied successfully. 240 | * @param {Function} onError Function to be called if an error occured while 241 | * attempting to copy the entry. 242 | */ 243 | var onCopyEntryRequested = function(options, onSuccess, onError) { 244 | webDAVFS.copyEntry({ 245 | sourcePath: options.sourcePath.substring(1), 246 | targetPath: options.targetPath.substring(1), 247 | onSuccess: onSuccess, 248 | onError: onError 249 | }); 250 | }; 251 | 252 | /** 253 | * Responds to a request to move a file or directory to a new location. 254 | * @param {Object} options Input options. 255 | * @param {Function} onSuccess Function to be called if the entry was 256 | * copied successfully. 257 | * @param {Function} onError Function to be called if an error occured while 258 | * attempting to copy the entry. 259 | */ 260 | var onMoveEntryRequested = function(options, onSuccess, onError) { 261 | webDAVFS.moveEntry({ 262 | sourcePath: options.sourcePath.substring(1), 263 | targetPath: options.targetPath.substring(1), 264 | onSuccess: onSuccess, 265 | onError: onError 266 | }); 267 | }; 268 | 269 | /** 270 | * Responds to a request to delete a file or directory. 271 | * @param {Object} options Input options. 272 | * @param {Function} onSuccess Function to be called if the file was 273 | * deleted successfully. 274 | * @param {Function} onError Function to be called if an error occured while 275 | * attempting to delete the file. 276 | */ 277 | var onDeleteEntryRequested = function(options, onSuccess, onError) { 278 | if (!options.entryPath) { 279 | onError('INVALID_OPERATION'); 280 | return; 281 | } 282 | 283 | var path = options.entryPath.substring(1); 284 | 285 | var doDelete = function() { 286 | webDAVFS.deleteEntry({ 287 | path: path, 288 | onError: onError, 289 | onSuccess: onSuccess 290 | }); 291 | }; 292 | 293 | // Delete always works if recursive flag is passed. 294 | if (options.recursive) { 295 | doDelete(); 296 | return; 297 | } 298 | 299 | var onMetadataSuccess = function(metadata) { 300 | if (metadata.isDirectory) { 301 | webDAVFS.readDirectory({ 302 | path: path, 303 | onSuccess: function(list) { 304 | if (list.length === 0) { 305 | // Delete works without recursive flag for empty directories 306 | doDelete(); 307 | } else { 308 | // Delete fails without recursive flag for non-empty directories. 309 | onError('NOT_EMPTY'); 310 | } 311 | }, 312 | onError: onError 313 | }); 314 | } else { 315 | // Delete works without recursive flag for files. 316 | doDelete(); 317 | } 318 | }; 319 | 320 | webDAVFS.getMetadata({ 321 | path: path, 322 | onError: onError, 323 | onSuccess: onMetadataSuccess 324 | }); 325 | }; 326 | 327 | /** 328 | * Responds to a request to unmount the file system. 329 | * @param {Object} inputOptions Input options. 330 | * @param {Function} onSuccess Function to be called if the file system was 331 | * unmounted successfully. 332 | * @param {Function} onError Function to be called if an error occured while 333 | * attempting to umount the file system. 334 | */ 335 | var onUnmountRequested = function(inputOptions, onSuccess, onError) { 336 | var options = { 337 | fileSystemId: webDAVFS.options.fileSystemId 338 | }; 339 | 340 | chrome.fileSystemProvider.unmount(options, onSuccess, onError); 341 | }; 342 | 343 | module.exports = { 344 | onCloseFileRequested: onCloseFileRequested, 345 | onOpenFileRequested: onOpenFileRequested, 346 | onReadFileRequested: onReadFileRequested, 347 | onCreateFileRequested: onCreateFileRequested, 348 | onWriteFileRequested: onWriteFileRequested, 349 | onTruncateRequested: onTruncateRequested, 350 | onDeleteEntryRequested: onDeleteEntryRequested, 351 | onCopyEntryRequested: onCopyEntryRequested, 352 | onMoveEntryRequested: onMoveEntryRequested, 353 | onGetMetadataRequested: onGetMetadataRequested, 354 | onReadDirectoryRequested: onReadDirectoryRequested, 355 | onUnmountRequested: onUnmountRequested 356 | }; 357 | -------------------------------------------------------------------------------- /s3fs/js/events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | 'use strict'; 8 | 9 | var util = require('../../shared/util'); 10 | 11 | /** 12 | * Fetches the metadata associated with the file at the given path from the 13 | * WebDAV server. 14 | * @param {Object} options Input options. 15 | * @param {function} onSuccess Function to be called if the metadata was 16 | * fetched successfully. 17 | * @param {function} onError Function to be called if an error occured while 18 | * attempting to fetch the metadata. 19 | */ 20 | 21 | var onCloseFileRequested = function(options, onSuccess, onError) { 22 | if (!s3fs.openedFiles[options.openRequestId]) { 23 | onError('INVALID_OPERATION'); 24 | } else { 25 | delete s3fs.openedFiles[options.openRequestId]; 26 | onSuccess(); 27 | } 28 | }; 29 | 30 | /** 31 | * Responds to a request to copy an entry. 32 | * @param {Object} options Input options. 33 | * @param {function} onSuccess Function to be called if the entry was copied 34 | * successfully. 35 | * @param {function} onError Function to be called if an error occured while 36 | * attempting to copy the entry. 37 | */ 38 | var onCopyEntryRequested = function(options, onSuccess, onError) { 39 | // Strip the leading slash, since not used internally. 40 | var targetPath = options.targetPath.substring(1); 41 | 42 | var copySource = encodeURIComponent(s3fs.defaultParameters.Bucket + 43 | options.sourcePath); 44 | 45 | var parameters = s3fs.parameters({ 46 | Key: targetPath, 47 | CopySource: copySource 48 | }); 49 | 50 | // TODO(lavelle): handle the recursive/directory case. 51 | 52 | s3fs.s3.copyObject(parameters, function(error) { 53 | if (error) { 54 | onError(error); 55 | } else { 56 | onSuccess(); 57 | } 58 | }); 59 | }; 60 | 61 | /** 62 | * Responds to a request to create a new file. 63 | * @param {Object} options Input options. 64 | * @param {function} onSuccess Function to be called if the file was created 65 | * successfully. 66 | * @param {function} onError Function to be called if an error occured while 67 | * attempting to create the file. 68 | */ 69 | var onCreateFileRequested = function(options, onSuccess, onError) { 70 | // Strip the leading slash, since not used internally. 71 | var path = options.filePath.substring(1); 72 | 73 | var parameters = s3fs.parameters({ 74 | Key: path, 75 | Body: new ArrayBuffer(), 76 | ContentLength: 0 77 | }); 78 | 79 | // TODO(lavelle): handle the case where it already exists here. 80 | // Do nothing, or report an error? 81 | 82 | s3fs.s3.putObject(parameters, function(error) { 83 | if (error) { 84 | onError(error); 85 | } else { 86 | onSuccess(); 87 | } 88 | }); 89 | }; 90 | 91 | /** 92 | * Responds to a request to delete a file or directory. 93 | * @param {Object} options Input options. 94 | * @param {function} onSuccess Function to be called if the entry was deleted 95 | * successfully. 96 | * @param {function} onError Function to be called if an error occured while 97 | * attempting to delete the entry. 98 | */ 99 | var onDeleteEntryRequested = function(options, onSuccess, onError) { 100 | // Strip the leading slash, since not used internally. 101 | var path = options.entryPath.substring(1); 102 | 103 | var parameters = s3fs.parameters({ 104 | Key: path, 105 | }); 106 | 107 | // TODO(lavelle): handle the directory/recursive case here. 108 | 109 | s3fs.s3.deleteObject(parameters, function(error) { 110 | if (error) { 111 | onError(error); 112 | } else { 113 | onSuccess(); 114 | } 115 | }); 116 | }; 117 | 118 | /** 119 | * Fetches the metadata associated with the file at the given path from the 120 | * WebDAV server. 121 | * @param {Object} options Input options. 122 | * @param {function} onSuccess Function to be called if the metadata was 123 | * fetched successfully. 124 | * @param {function} onError Function to be called if an error occured while 125 | * attempting to fetch the metadata. 126 | */ 127 | var onGetMetadataRequested = function(options, onSuccess, onError) { 128 | // Remove the leading slash from the path -- this isn't used in S3 keys. 129 | var path = options.entryPath.substring(1); 130 | 131 | var metadata; 132 | // Handle the special case for the root directory. 133 | if (path === '') { 134 | metadata = util.makeDirectory('/'); 135 | onSuccess(metadata, false); 136 | return; 137 | } 138 | 139 | // First check if the path corresponds to a valid directory by seeing if 140 | // there are any objects in the bucket with the path as a prefix. 141 | var parameters = s3fs.parameters({ 142 | Prefix: path + '/', 143 | Delimiter: '/' 144 | }); 145 | 146 | s3fs.s3.listObjects(parameters, function(error, data) { 147 | if (data && data.Contents.length > 0) { 148 | metadata = util.makeDirectory(path); 149 | onSuccess(metadata, false); 150 | return; 151 | } 152 | 153 | // If it isn't a directory, it might still be a file, so send another 154 | // request to fetch the metadata for that. 155 | parameters = s3fs.parameters({Key: path}); 156 | 157 | s3fs.s3.headObject(parameters, function(error, data) { 158 | // If there's still an error, it's not a file or a directory, so call 159 | // the error callback. 160 | if (error) { 161 | onError(error); 162 | return; 163 | } 164 | 165 | // If there's no error, it's a file, so convert the metadata into the 166 | // right format and return it to the API. 167 | metadata = { 168 | isDirectory: false, 169 | name: path, 170 | size: data.ContentLength, 171 | modificationTime: new Date(data.LastModified), 172 | mimeType: data.ContentType 173 | }; 174 | 175 | onSuccess(metadata, false); 176 | }); 177 | }); 178 | }; 179 | 180 | /** 181 | * Responds to a request to open a file. 182 | * @param {Object} options Input options. 183 | * @param {function} onSuccess Function to be called if the file was opened 184 | * successfully. 185 | * @param {function} onError Function to be called if an error occured while 186 | * attempting to open the file. 187 | */ 188 | var onOpenFileRequested = function(options, onSuccess, onError) { 189 | if (!util.isRequestValid(options)) { 190 | onError('INVALID_OPERATION'); 191 | } else { 192 | if (options.create) { 193 | // TODO(lavelle): create file here. 194 | } 195 | 196 | s3fs.openedFiles[options.requestId] = options.filePath; 197 | onSuccess(); 198 | } 199 | }; 200 | 201 | /** 202 | * Responds to a request for the contents of a directory. 203 | * @param {Object} options Input options. 204 | * @param {function} onSuccess Function to be called if the directory was 205 | * read successfully. 206 | * @param {function} onError Function to be called if an error occured while 207 | * attempting to read the directory. 208 | */ 209 | var onReadDirectoryRequested = function(options, onSuccess, onError) { 210 | // Remove the leading slash from the file path - not used in S3 bucket keys. 211 | var path = options.directoryPath.substring(1); 212 | 213 | var parameters = s3fs.parameters({ 214 | Prefix: path, 215 | Delimiter: '/' 216 | }); 217 | 218 | s3fs.s3.listObjects(parameters, function(error, data) { 219 | if (error) { 220 | onError(error); 221 | return; 222 | } 223 | 224 | var files = data.Contents.map(function(item) { 225 | // Content type is omitted from the results as it is not present in the 226 | // S3 API response. 227 | return { 228 | isDirectory: false, 229 | name: item.Key, 230 | size: item.Size, 231 | modificationTime: item.LastModified, 232 | }; 233 | }); 234 | 235 | var directories = data.CommonPrefixes.map(function(item) { 236 | var name = item.Prefix.substring(0, item.Prefix.length - 1); 237 | 238 | return util.makeDirectory(name); 239 | }); 240 | 241 | var list = directories.concat(files); 242 | 243 | // Return it to the API. 244 | onSuccess(list, false); 245 | }); 246 | }; 247 | 248 | /** 249 | * Responds to a request for the contents of a file. 250 | * @param {Object} options Input options. 251 | * @param {function} onSuccess Function to be called if the file was 252 | * read successfully. 253 | * @param {function} onError Function to be called if an error occured while 254 | * attempting to read the file. 255 | */ 256 | var onReadFileRequested = function(options, onSuccess, onError) { 257 | if (!(options.openRequestId in s3fs.openedFiles)) { 258 | onError('INVALID_OPERATION'); 259 | return; 260 | } 261 | 262 | // Strip the leading slash, since not used internally. 263 | var path = s3fs.openedFiles[options.openRequestId].substring(1); 264 | 265 | // Calculate the index of the last byte to fetch. Subtract 1 because HTTP 266 | // byte ranges are inclusive. 267 | var end = options.offset + options.length - 1; 268 | 269 | var parameters = s3fs.parameters({ 270 | Key: path, 271 | Range: 'bytes=' + options.offset + '-' + end 272 | }); 273 | 274 | s3fs.s3.getObject(parameters, function(error, data) { 275 | if (error) { 276 | onError(error); 277 | } else { 278 | onSuccess(data.Body.toArrayBuffer(), false); 279 | } 280 | }); 281 | }; 282 | 283 | /** 284 | * Responds to a request to truncate the contents of a file. 285 | * @param {Object} options Input options. 286 | * @param {function} onSuccess Function to be called if the file was 287 | * read successfully. 288 | * @param {function} onError Function to be called if an error occured while 289 | * attempting to read the file. 290 | */ 291 | var onTruncateRequested = function(options, onSuccess, onError) { 292 | // Strip the leading slash, since not used internally. 293 | var path = options.filePath.substring(1); 294 | 295 | var readParameters = s3fs.parameters({ 296 | Key: path 297 | }); 298 | 299 | s3fs.s3.getObject(readParameters, function(error, data) { 300 | if (error) { 301 | onError(error); 302 | } else { 303 | var buffer = data.Body.toArrayBuffer(); 304 | 305 | var write = function(data) { 306 | var writeParameters = s3fs.parameters({ 307 | Key: path, 308 | Body: data, 309 | ContentLength: data.byteLength 310 | }); 311 | 312 | s3fs.s3.putObject(writeParameters, function(error) { 313 | if (error) { 314 | onError(error); 315 | } else { 316 | onSuccess(); 317 | } 318 | }); 319 | }; 320 | 321 | if (options.length < buffer.byteLength) { 322 | // Truncate. 323 | write(buffer.slice(0, options.length)); 324 | } else { 325 | // Pad with null bytes. 326 | var diff = options.length - buffer.byteLength; 327 | var blob = new Blob([buffer, new Array(diff + 1).join('\0')]); 328 | util.blobToArrayBuffer(blob, write); 329 | } 330 | } 331 | }); 332 | }; 333 | 334 | /** 335 | * Responds to a request to write some data to a file. 336 | * @param {Object} options Input options. 337 | * @param {function} onSuccess Function to be called if the file was 338 | * read successfully. 339 | * @param {function} onError Function to be called if an error occured while 340 | * attempting to read the file. 341 | */ 342 | var onWriteFileRequested = function(options, onSuccess, onError) { 343 | if (!(options.openRequestId in s3fs.openedFiles)) { 344 | onError('INVALID_OPERATION'); 345 | return; 346 | } 347 | 348 | // Strip the leading slash, since not used internally. 349 | var path = s3fs.openedFiles[options.openRequestId].substring(1); 350 | 351 | // Calculate the index of the last byte to fetch. Subtract 1 because HTTP 352 | // byte ranges are inclusive. 353 | var end = options.offset + options.length - 1; 354 | 355 | var body = options.data.slice(options.offset, end); 356 | 357 | var parameters = s3fs.parameters({ 358 | Key: path, 359 | Body: body, 360 | ContentLength: body.byteLength 361 | }); 362 | 363 | s3fs.s3.putObject(parameters, function(error) { 364 | if (error) { 365 | onError(error); 366 | } else { 367 | onSuccess(); 368 | } 369 | }); 370 | }; 371 | 372 | /** 373 | * Responds to a request to move an entry. 374 | * @param {Object} options Input options. 375 | * @param {function} onSuccess Function to be called if the entry was copied 376 | * successfully. 377 | * @param {function} onError Function to be called if an error occured while 378 | * attempting to copy the entry. 379 | */ 380 | var onMoveEntryRequested = function(options, onSuccess, onError) { 381 | // Strip the leading slash, since not used internally. 382 | var targetPath = options.targetPath.substring(1); 383 | 384 | var copySource = encodeURIComponent(s3fs.defaultParameters.Bucket + 385 | options.sourcePath); 386 | 387 | // The AWS SDK has no moveObject method, so we emulate it with a copy from A 388 | // to B followed by a delete of A. 389 | 390 | var copyParameters = s3fs.parameters({ 391 | Key: targetPath, 392 | CopySource: copySource 393 | }); 394 | 395 | var deleteParameters = s3fs.parameters({ 396 | Key: options.sourcePath.substring(1) 397 | }); 398 | 399 | // TODO(lavelle): handle the recursive/directory case. 400 | 401 | s3fs.s3.copyObject(copyParameters, function(error) { 402 | if (error) { 403 | onError(error); 404 | } else { 405 | s3fs.s3.deleteObject(deleteParameters, function(error) { 406 | if (error) { 407 | onError(error); 408 | } 409 | else { 410 | onSuccess(); 411 | } 412 | }); 413 | } 414 | }); 415 | }; 416 | 417 | module.exports = { 418 | onCloseFileRequested: onCloseFileRequested, 419 | onOpenFileRequested: onOpenFileRequested, 420 | onReadFileRequested: onReadFileRequested, 421 | onCreateFileRequested: onCreateFileRequested, 422 | onWriteFileRequested: onWriteFileRequested, 423 | onTruncateRequested: onTruncateRequested, 424 | onDeleteEntryRequested: onDeleteEntryRequested, 425 | onCopyEntryRequested: onCopyEntryRequested, 426 | onMoveEntryRequested: onMoveEntryRequested, 427 | onGetMetadataRequested: onGetMetadataRequested, 428 | onReadDirectoryRequested: onReadDirectoryRequested 429 | }; 430 | --------------------------------------------------------------------------------