├── .gitignore ├── AUTHORS ├── CONTRIBUTING ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── app ├── .bowerrc ├── assets │ └── Roboto-Medium.woff2 ├── bower.json ├── cred_store.js ├── icon128.png ├── icon_domain.png ├── ip.js ├── ip_cache.js ├── license_dialog.html ├── lmhosts.js ├── local_components │ ├── display-input │ │ ├── display-input.css │ │ └── display-input.html │ └── domain-input │ │ ├── domain-input.css │ │ └── domain-input.html ├── log.js ├── manifest.json ├── message_router.js ├── metadata_cache.js ├── mount_dialog.css ├── nacl.js ├── nbt.js ├── net.js ├── plugin.html ├── plugin.js ├── popup.js ├── samba.js ├── schema.json ├── stream.js ├── url.js ├── utils.js ├── window.html └── window_launchers.js ├── nacl ├── BaseNaclFsp.cc ├── BaseNaclFsp.h ├── INaclFsp.h ├── Logger.cc ├── Logger.h ├── Makefile ├── Options.cc ├── Options.h ├── SambaFsp.cc ├── SambaFsp.h ├── build.sh ├── clean.sh ├── nacl_fsp.cc ├── notes.txt └── util.h ├── package-lock.json ├── package.json ├── test └── app │ └── metadata_cache_tests.js ├── third_party ├── bower_components │ └── .gitignore └── nacl_sdk │ └── common.js └── tools ├── format.sh └── to_temp.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /app/glibc/ 2 | /app/bower_components/ 3 | /nacl/glibc/ 4 | /nacl/pnacl/ 5 | /app/mount_dialog.html 6 | /app/mount_dialog.js 7 | /app/common.js 8 | /tools/to_cros.sh 9 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of ChromeOS Network File Share authors for copyright 2 | # purposes. 3 | # This file is distinct from the CONTRIBUTORS files. 4 | # See the latter for an explanation. 5 | 6 | # Names should be added to this file as: 7 | # Name or Organization 8 | # The email address is not required for organizations. 9 | 10 | Google Inc. 11 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 6 | (CLA), which you can do online. The CLA is necessary mainly because you own the 7 | copyright to your changes, even after your contribution becomes part of our 8 | codebase, so we need your permission to use and distribute your code. We also 9 | need to be sure of various other things—for instance that you'll tell us if you 10 | know that your code infringes on other people's patents. You don't have to sign 11 | the CLA until after you've submitted your code for review and a member has 12 | approved it, but you must do it before we can put your code into our codebase. 13 | Before you start working on a larger contribution, you should get in touch with 14 | us first through the issue tracker with your idea so that we can help out and 15 | possibly guide you. Coordinating up front makes it much easier to avoid 16 | frustration later on. 17 | 18 | ### Code reviews 19 | All submissions, including submissions by project members, require review. We 20 | use Github pull requests for this purpose. 21 | 22 | ### The small print 23 | Contributions made by corporations are covered by a different agreement than 24 | the one above, the 25 | [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). 26 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Zentaro Kavanagh 13 | Bradley Nelson 14 | Sam Clegg 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | #### *** THIS EXTENSION IS DEPRECATED. SINCE CHROME 71 THIS FUNCTIONALITY IS BUILT IN TO CHROME OS *** 3 | --- 4 | 5 | On Chrome 71 or higher you can: 6 | 7 | * Open the files app 8 | * Click the "three dot menu" in the top right 9 | * Choose "Add New Service" 10 | * Choose "SMB file share" 11 | 12 | A dialog in settings will open (in 72 and later it's a standalone dialog). Enter your share information as before. 13 | 14 | You can uninstall the chrome app as it will be deprecated in the near future. 15 | 16 | **_If you have bugs with the new implementation you can open them at crbug.com under component "Platform>Apps>FileManager" with SMB in the title._** 17 | 18 | There will be no more work on this implementation. 19 | 20 | --- 21 | 22 | ## Overview 23 | 24 | This is a Chrome App that extends the built in File Manager to be able to 25 | support connecting to SMB file shares. 26 | 27 | The NaCl port is currently patched from Samba 4.1.22. 28 | 29 | ## Setup 30 | 31 | 1) Download the NaCl SDK and unzip it as directed. 32 | https://developer.chrome.com/native-client/sdk/download 33 | 34 | 2) Update the SDK and get the pepper_50 version. 35 | 36 | ``` 37 | cd nacl_sdk 38 | ./naclsdk update 39 | ./naclsdk update pepper_50 40 | export NACL_SDK_ROOT=/path/to/nacl_sdk/pepper_50 41 | ``` 42 | 43 | 3) Get depot_tools and gclient. 44 | ``` 45 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 46 | ``` 47 | 48 | 4) Put depot_tools on the path (or put it in your .bashrc) 49 | https://www.chromium.org/developers/how-tos/install-depot-tools 50 | 51 | 5) Get WebPorts (aka NaCl Ports). 52 | ``` 53 | mkdir webports 54 | cd webports 55 | gclient config --name=src https://chromium.googlesource.com/webports.git 56 | gclient sync 57 | ``` 58 | 59 | 6) Install a missing dependency from the NaCl SDK. 60 | ``` 61 | sudo apt-get install libglib2.0-0:i386 62 | ``` 63 | 64 | 7) Build Samba. 65 | ``` 66 | cd webports/src 67 | ./make_all.sh samba F=1 V=1 68 | ``` 69 | 8) Get the App code. 70 | ``` 71 | git clone https://github.com/GoogleChrome/chromeos_network_file_share 72 | ``` 73 | 9) Install bower. 74 | https://syntaxsugar.github.io/posts/2014/05/how-to-install-bower-on-ubuntu-1404-lts/ 75 | 76 | *Note: 77 | If you are a Google employee, please follow `sites/nodejs/installing-node` for node installation 78 | ``` 79 | sudo apt-get install nodejs 80 | sudo ln -s /usr/bin/nodejs /usr/bin/node 81 | sudo apt-get install npm 82 | sudo npm install -g bower 83 | ``` 84 | 10) Get the bower dependencies. 85 | ``` 86 | cd chromeos_network_file_share/app 87 | bower install 88 | ``` 89 | 11) Install Vulcanize and Crisper. 90 | ``` 91 | sudo npm install -g vulcanize 92 | sudo npm install -g crisper 93 | ``` 94 | 12) Setup build environment 95 | 96 | Set NACL_SDK_ROOT if not done above 97 | 98 | 13) Finally you can build! 99 | ``` 100 | cd chromeos_network_file_share 101 | nacl/build.sh 102 | ``` 103 | 104 | ## Tests 105 | 106 | We use [Mocha](http://mochajs.org) and [Chai](http://chaijs.com/) as our test framework. 107 | 108 | To download the testing dependencies: 109 | ``` 110 | npm install --only=dev 111 | ``` 112 | To run the tests: 113 | ``` 114 | npm test 115 | ``` 116 | 117 | ### Arm Nacl_IO Bug 118 | 119 | There is currently a bug that causes a crash on ARM Release builds. Until the 120 | bug is fixed the workaround is to build libnacl_io.so with optimizations turned 121 | off. If you don't do this extra step prior to building the app the ARM build 122 | will crash. 123 | 124 | Before building the app edit $NACL_SDK_ROOT/src/nacl_io/Makefile and add 125 | the following after the line 'CFLAGS += -DNACL_IO_LOGGING=0' 126 | ``` 127 | ifeq ($(NACL_ARCH), arm) 128 | CFLAGS += -O1 129 | endif 130 | 131 | // then rebuild nacl_io 132 | cd $NACL_SDK_ROOT/src/nacl_io/ 133 | make V=1 CONFIG=Release TOOLCHAIN=glibc NACL_ARCH=arm 134 | ``` 135 | That will rebuild libnacl_io.so with optimization disabled. Then rebuild the 136 | app. 137 | 138 | ### Nacl SDK 139 | Update SDK (currently using pepper_canary). 140 | ``` 141 | ./naclsdk update pepper_canary --force 142 | ``` 143 | ### Building The App/Extension 144 | 145 | Setup environment 146 | ``` 147 | export NACL_SDK_ROOT=/path/to/your/nacl/sdk 148 | ``` 149 | Build 150 | ``` 151 | nacl/build.sh 152 | ``` 153 | Package 154 | ``` 155 | tools/to_temp.sh 156 | ``` 157 | ### Troubleshooting 158 | ``` 159 | In file included from nacl_fsp.cc:24: 160 | ./SambaFsp.h:20:10: fatal error: 'samba/libsmbclient.h' file not found 161 | #include "samba/libsmbclient.h" 162 | ``` 163 | If you see this error follow the steps above for 'Setup environment' 164 | -------------------------------------------------------------------------------- /app/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "../third_party/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/chromeos_network_file_share/d3c4812b4252c486a797a6028824ceca7f627a52/app/assets/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /app/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Network File Share", 3 | "version": "0.0.0", 4 | "authors": [ 5 | "Zentaro Kavanagh " 6 | ], 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "test", 13 | "tests" 14 | ], 15 | "dependencies": { 16 | "polymer": "Polymer/polymer#^1.0.0", 17 | "paper-input": "PolymerElements/paper-input#~1.0.6", 18 | "paper-header-panel": "PolymerElements/paper-header-panel#~1.0.2", 19 | "paper-toolbar": "PolymerElements/paper-toolbar#~1.0.4", 20 | "paper-button": "PolymerElements/paper-button#~1.0.3", 21 | "paper-icon-button": "PolymerElements/paper-icon-button#~1.0.3", 22 | "iron-icons": "PolymerElements/iron-icons#~1.0.3", 23 | "paper-dialog": "PolymerElements/paper-dialog#~1.0.1", 24 | "paper-spinner": "PolymerElements/paper-spinner#~1.0.2", 25 | "paper-toast": "PolymerElements/paper-toast#~1.0.0", 26 | "iron-collapse": "PolymerElements/iron-collapse#~1.0.3", 27 | "paper-checkbox": "PolymerElements/paper-checkbox#~1.0.6", 28 | "paper-tooltip": "PolymerElements/paper-tooltip#~1.1.1", 29 | "paper-item": "PolymerElements/paper-item#~1.2.1", 30 | "paper-dropdown-menu": "PolymerElements/paper-dropdown-menu#~1.2.1", 31 | "paper-menu": "PolymerElements/paper-menu#~1.2.1", 32 | "paper-listbox": "PolymerElements/paper-listbox#~1.1.3", 33 | "vaadin-combo-box": "^1.3.3" 34 | }, 35 | "resolutions": { 36 | "paper-dialog": "~1.0.1", 37 | "paper-input": "^1.1.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/cred_store.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Simple credential store in local storage. Note each share can only be mounted 20 | * with a single credential. 21 | */ 22 | CredStore = function() { 23 | this.creds = {}; 24 | }; 25 | 26 | CredStore.LOCAL_STORAGE_KEY_NAME = 'creds'; 27 | 28 | 29 | /** 30 | * Returns a an object with a username, domain and password field if there is a 31 | * credential, otherwise null. 32 | */ 33 | CredStore.prototype.get = function(fileSystemId) { 34 | return this.creds[fileSystemId] || null; 35 | }; 36 | 37 | 38 | /** 39 | * Returns true if credentials exist for this fileSystemId. 40 | */ 41 | CredStore.prototype.exists = function(fileSystemId) { 42 | return this.get(fileSystemId) != null; 43 | }; 44 | 45 | 46 | /** 47 | * Adds a credential to the store. If a previous credential exists it will be 48 | * overwritten. 49 | */ 50 | CredStore.prototype.add = function(fileSystemId, domain, username, password) { 51 | this.creds[fileSystemId] = { 52 | domain: domain || '', 53 | username: username || '', 54 | password: password || '' 55 | }; 56 | }; 57 | 58 | 59 | /** 60 | * Removes any credential that may have been set for this file system. 61 | */ 62 | CredStore.prototype.clear = function(fileSystemId) { 63 | log.debug('Clearing credentials for ' + fileSystemId); 64 | delete this.creds[fileSystemId]; 65 | }; 66 | 67 | 68 | /** 69 | * Removes all saved credentials. 70 | */ 71 | CredStore.prototype.clearAll = function() { 72 | this.creds = {}; 73 | }; 74 | 75 | 76 | /** 77 | * Stores the current map of credentials to local storage. Returns 78 | * a promise that will resolve when storing is completed. 79 | */ 80 | CredStore.prototype.save = function() { 81 | var resolver = getPromiseResolver(); 82 | 83 | var data = {}; 84 | data[CredStore.LOCAL_STORAGE_KEY_NAME] = this.creds; 85 | chrome.storage.local.set(data, function() { 86 | if (chrome.runtime.lastError) { 87 | log.error('Failed saving creds ' + chrome.runtime.lastError.message); 88 | resolver.reject(chrome.runtime.lastError.message); 89 | return; 90 | } 91 | 92 | log.debug('Saved ' + keyCount(this.creds) + ' credentials'); 93 | resolver.resolve(); 94 | }.bind(this)); 95 | 96 | return resolver.promise; 97 | }; 98 | 99 | 100 | /** 101 | * Loads a map of credentials from local storage. 102 | */ 103 | CredStore.prototype.load = function() { 104 | var resolver = getPromiseResolver(); 105 | 106 | chrome.storage.local.get(CredStore.LOCAL_STORAGE_KEY_NAME, function(items) { 107 | if (chrome.runtime.lastError) { 108 | log.error('Failed loading creds ' + chrome.runtime.lastError.message); 109 | resolver.reject(chrome.runtime.lastError.message); 110 | return; 111 | } 112 | 113 | this.creds = items[CredStore.LOCAL_STORAGE_KEY_NAME] || {}; 114 | 115 | log.debug('Loaded ' + keyCount(this.creds) + ' credentials'); 116 | resolver.resolve(); 117 | }.bind(this)); 118 | 119 | return resolver.promise; 120 | }; 121 | -------------------------------------------------------------------------------- /app/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/chromeos_network_file_share/d3c4812b4252c486a797a6028824ceca7f627a52/app/icon128.png -------------------------------------------------------------------------------- /app/icon_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/chromeos_network_file_share/d3c4812b4252c486a797a6028824ceca7f627a52/app/icon_domain.png -------------------------------------------------------------------------------- /app/ip.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Takes an IPv4 address string in dotted format eg. 192.168.2.66 and converts 20 | * it to an unsigned 32-bit int. If it is invalid null is returned. 21 | */ 22 | function ipv4ToUint32(ipv4) { 23 | var parts = ipv4.split('.'); 24 | if (parts.length != 4) { 25 | return null; 26 | } 27 | 28 | var valid = true; 29 | parts.forEach(function(part) { 30 | var value = parseInt(part, 10); 31 | if (value < 0 || value > 255) { 32 | valid = false; 33 | } 34 | }); 35 | 36 | if (!valid) { 37 | return null; 38 | } 39 | 40 | return lshift(parseInt(parts[0], 10), 24) + 41 | lshift(parseInt(parts[1], 10), 16) + lshift(parseInt(parts[2], 10), 8) + 42 | parseInt(parts[3], 10); 43 | } 44 | 45 | function isValidIpv4(ipv4) { 46 | return ipv4ToUint32(ipv4) != null; 47 | } 48 | 49 | /** 50 | * Takes a Uint32 and converts to a dot-notation IPv4 string. 51 | */ 52 | function uint32ToIpv4(ipIntValue) { 53 | return ((ipIntValue >>> 24) & 0xff).toString() + '.' + 54 | ((ipIntValue >>> 16) & 0xff).toString() + '.' + 55 | ((ipIntValue >>> 8) & 0xff).toString() + '.' + 56 | (ipIntValue & 0xff).toString(); 57 | } 58 | 59 | /** 60 | * Takes an IPv4 address string and the number of bits are the subnet prefix and 61 | * returns the broadcast address. Essentially all the non-prefix bits set to 1. 62 | */ 63 | function makeBroadcastAddress(ipv4, prefixLength) { 64 | var ipValue = ipv4ToUint32(ipv4); 65 | if (ipValue == null) { 66 | return null; 67 | } 68 | 69 | // The mask that will be OR'd with the address to get the broadcast 70 | // address. NOTE that this is the inverted subnet mask. Unsigned 71 | // shift >>> is necessary here to ensure 0's are added to the left. 72 | var broadcastMask = 0xffffffff >>> prefixLength; 73 | var broadcastAddressValue = ipValue | broadcastMask; 74 | 75 | return uint32ToIpv4(broadcastAddressValue); 76 | } 77 | -------------------------------------------------------------------------------- /app/ip_cache.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Simple lookup of names to IP Addresses. 20 | */ 21 | IPCache = function() { 22 | this.hosts = {}; 23 | }; 24 | 25 | 26 | /** 27 | * Resolves the hostname to an ip address or returns null if unknown. 28 | */ 29 | IPCache.prototype.resolve = function(hostName) { 30 | return this.hosts[hostName.toUpperCase()] || null; 31 | }; 32 | 33 | 34 | /** 35 | * Adds a hostname and ip address pair to the list of hosts. 36 | */ 37 | IPCache.prototype.add = function(hostName, ipAddress) { 38 | // TODO(zentaro): Handle having multiple IP Addresses. 39 | this.hosts[hostName.toUpperCase()] = ipAddress; 40 | }; 41 | -------------------------------------------------------------------------------- /app/lmhosts.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Lookup service to emulate Windows LMHosts file. This currently uses local 20 | * storage. 21 | * TODO(zentaro): Support getting data from enterprise policy 22 | * eg .chrome.storage.managed 23 | */ 24 | LMHosts.prototype = new IPCache(); 25 | LMHosts.prototype.constructor = LMHosts; 26 | function LMHosts() {} 27 | 28 | LMHosts.LOCAL_STORAGE_KEY_NAME = 'lmhosts'; 29 | 30 | 31 | /** 32 | * Stores the current map of host to ip addresses to local storage. Returns 33 | * a promise that will resolve when storing is completed. 34 | */ 35 | LMHosts.prototype.save = function() { 36 | var resolver = getPromiseResolver(); 37 | 38 | var data = {}; 39 | data[LMHosts.LOCAL_STORAGE_KEY_NAME] = this.hosts; 40 | chrome.storage.local.set(data, function() { 41 | if (chrome.runtime.lastError) { 42 | log.error('Failed saving lmhosts ' + chrome.runtime.lastError.message); 43 | resolver.reject(chrome.runtime.lastError.message); 44 | return; 45 | } 46 | 47 | resolver.resolve(); 48 | }); 49 | 50 | return resolver.promise; 51 | }; 52 | 53 | 54 | /** 55 | * Loads a map of hostnames to IP addresses from local storage. 56 | */ 57 | LMHosts.prototype.load = function() { 58 | var resolver = getPromiseResolver(); 59 | 60 | chrome.storage.local.get(LMHosts.LOCAL_STORAGE_KEY_NAME, function(items) { 61 | if (chrome.runtime.lastError) { 62 | log.error('Failed loading lmhosts ' + chrome.runtime.lastError.message); 63 | resolver.reject(chrome.runtime.lastError.message); 64 | return; 65 | } 66 | 67 | this.hosts = items[LMHosts.LOCAL_STORAGE_KEY_NAME] || {}; 68 | 69 | resolver.resolve(); 70 | }.bind(this)); 71 | 72 | return resolver.promise; 73 | }; 74 | 75 | 76 | /** 77 | * Global object that can be used by the plugin to perform name resolution 78 | * before trying to use NetBIOS over TCP. 79 | */ 80 | var lmHosts = new LMHosts(); 81 | -------------------------------------------------------------------------------- /app/local_components/display-input/display-input.css: -------------------------------------------------------------------------------- 1 | .arrange-horizontally > * { 2 | display: inline-block; 3 | } 4 | 5 | .text-elem { 6 | position: absolute; 7 | top: 50%; 8 | transform: translateY(-50%); 9 | left: 45px; 10 | } -------------------------------------------------------------------------------- /app/local_components/display-input/display-input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | 28 | 122 | -------------------------------------------------------------------------------- /app/local_components/domain-input/domain-input.css: -------------------------------------------------------------------------------- 1 | #domain_label { 2 | font-size: small; 3 | font-weight: bold; 4 | } 5 | -------------------------------------------------------------------------------- /app/local_components/domain-input/domain-input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 74 | 75 | -------------------------------------------------------------------------------- /app/log.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Simple logger that handles log messages send back from NaCl. 20 | */ 21 | NaclLogger = function() {}; 22 | 23 | NaclLogger.prototype.handleMessage = function(message) { 24 | console.log(message.data); 25 | }; 26 | 27 | 28 | /** 29 | * Simple logger for javascript. 30 | */ 31 | JsLogger = function() {}; 32 | 33 | var JSLOG_DEBUG = 0; 34 | var JSLOG_INFO = 1; 35 | var JSLOG_WARNING = 2; 36 | var JSLOG_ERROR = 3; 37 | 38 | var jsLogLevel = JSLOG_WARNING; 39 | var traceEnabled = true; 40 | 41 | JsLogger.prototype.trace = function(operation, start, end) { 42 | if (traceEnabled) { 43 | var elapsedMs = Math.round(end - start); 44 | this.write_( 45 | JSLOG_DEBUG, 'TRACE: ', operation + ':' + Math.round(start) + '-' + 46 | Math.round(end) + ' = ' + elapsedMs); 47 | } 48 | }; 49 | 50 | JsLogger.prototype.debug = function(message) { 51 | this.write_(JSLOG_DEBUG, 'DEBUG: ', message); 52 | }; 53 | 54 | JsLogger.prototype.info = function(message) { 55 | this.write_(JSLOG_INFO, 'INFO: ', message); 56 | }; 57 | 58 | JsLogger.prototype.warning = function(message) { 59 | this.write_(JSLOG_WARNING, 'WARNING: ', message); 60 | }; 61 | 62 | JsLogger.prototype.error = function(message) { 63 | this.write_(JSLOG_ERROR, 'ERROR: ', message); 64 | }; 65 | 66 | JsLogger.prototype.write_ = function(level, prefix, message) { 67 | if (level >= jsLogLevel) { 68 | console.log(prefix + message); 69 | } 70 | }; 71 | 72 | // Global logger. 73 | var log = new JsLogger(); 74 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "Network File Share for Chrome OS", 5 | "short_name": "Network File Share", 6 | "description": "Access your network file shares directly from the Chrome OS files app.", 7 | "version": "0.81.18120", 8 | "author": "Zentaro Kavanagh", 9 | "icons": { 10 | "128": "icon128.png" 11 | }, 12 | "app": { 13 | "background": { 14 | "page": "plugin.html" 15 | } 16 | }, 17 | "storage": { 18 | "managed_schema": "schema.json" 19 | }, 20 | "permissions": [ 21 | "fileSystemProvider", 22 | "system.network", 23 | "storage", 24 | "mdns", 25 | { 26 | "socket": [ 27 | "tcp-listen:*:*", 28 | "tcp-connect", 29 | "resolve-host", 30 | "udp-bind:*:*", 31 | "udp-send-to:*:*", 32 | "udp-multicast-membership", 33 | "resolve-host" 34 | ] 35 | } 36 | ], 37 | "sockets": { 38 | "tcp": { 39 | "connect": "*" 40 | }, 41 | "udp": { 42 | "send": "*", 43 | "bind": "*", 44 | "multicastMembership": "" 45 | } 46 | }, 47 | "file_system_provider_capabilities": { 48 | "configurable": false, 49 | "multiple_mounts": true, 50 | "watchable": false, 51 | "source": "network" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/message_router.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | var MessageRouter = function() { 19 | log.debug('Initializing message router'); 20 | 21 | this.messages = {}; 22 | this.sendMessageFn = null; 23 | this.isInitialized = false; 24 | this.initializeResolver = getPromiseResolver(); 25 | }; 26 | 27 | MessageRouter.prototype.initialize = function(sendMessageFn) { 28 | log.debug('Setting sendMessageFn in message router'); 29 | this.sendMessageFn = sendMessageFn; 30 | this.isInitialized = true; 31 | 32 | // Resolve once initialize has been called so that send message can wait 33 | // if necessary. 34 | this.initializeResolver.resolve(); 35 | }; 36 | 37 | MessageRouter.prototype.sendMessageWithRetry = function( 38 | message, opt_processDataFn) { 39 | var operation = message.functionName + '[' + message.messageId + ']'; 40 | 41 | // TODO(zentaro): Should retry be configurable? 42 | return getRetryingPromise(function() { 43 | return this.sendMessage(message, opt_processDataFn); 44 | }.bind(this), operation, 3); 45 | }; 46 | 47 | MessageRouter.prototype.sendMessage = function(message, opt_processDataFn) { 48 | var messageId = message.messageId; 49 | 50 | if (messageId in this.messages) { 51 | throw 'Cannot send duplicate message id'; 52 | } 53 | 54 | // this.messages[messageId] = getTimedPromiseResolver(operation); 55 | this.messages[messageId] = { 56 | resolver: getPromiseResolver(), 57 | processDataFn: opt_processDataFn 58 | }; 59 | 60 | // Always make sure initialization is complete before sending messages. 61 | this.initializeResolver.promise.then(function() { 62 | this.sendMessageFn(message); 63 | }.bind(this)); 64 | 65 | return this.messages[messageId].resolver.promise; 66 | }; 67 | 68 | MessageRouter.prototype.handleMessage = function(message) { 69 | var messageId = message.data.messageId; 70 | 71 | if (!(messageId in this.messages)) { 72 | log.warning('Ignoring message with unknown id ' + messageId); 73 | return; 74 | } 75 | 76 | var error = message.data.result.error; 77 | var failed = false; 78 | if (error) { 79 | failed = true; 80 | log.error('rejecting message ' + messageId + ' ' + error); 81 | this.messages[messageId].resolver.reject(error); 82 | } else { 83 | var processDataFn = this.messages[messageId].processDataFn; 84 | 85 | if (isDef(processDataFn)) { 86 | log.debug('streaming data for ' + messageId); 87 | this.messages[messageId].processDataFn(message.data); 88 | 89 | if (!message.data.hasMore) { 90 | // TODO(zentaro): Accumulate results here so that the final resolution 91 | // also gets the full data set. 92 | this.messages[messageId].resolver.resolve(null); 93 | } 94 | } else { 95 | if (message.data.hasMore) { 96 | failed = true; 97 | var errorMessage = 98 | 'No processing function supplied for streamed message ' + messageId; 99 | log.error(errorMessage); 100 | this.messages[messageId].resolver.reject(errorMessage); 101 | } else { 102 | this.messages[messageId].resolver.resolve(message.data); 103 | } 104 | } 105 | } 106 | 107 | if (failed || !message.data.hasMore) { 108 | log.debug('Deleting state for message ' + messageId); 109 | delete this.messages[messageId]; 110 | } 111 | }; 112 | -------------------------------------------------------------------------------- /app/metadata_cache.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Class to cache EntryMetadata. Currently only relevant for caching 20 | * individual getMetadata requests. readDirectory calls currently don't use the 21 | * cache (but could with some modification). 22 | */ 23 | MetadataCache = function() { 24 | this.cache = {}; 25 | }; 26 | 27 | 28 | MetadataCache.prototype.cacheDirectoryContents = function( 29 | fileSystemId, directoryPath, entries, currentTime) { 30 | if (!(fileSystemId in this.cache)) { 31 | log.debug('Starting cache for ' + fileSystemId); 32 | this.cache[fileSystemId] = {}; 33 | } 34 | 35 | // Just overwrite if anything was there before. 36 | log.debug('Updating cache for ' + fileSystemId + '|' + directoryPath); 37 | this.cache[fileSystemId][directoryPath] = { 38 | 'timeCached': currentTime, 39 | 'entries': {}, 40 | 'incomplete_entries': {} 41 | }; 42 | 43 | entries.forEach(function(entry) { 44 | // Make sure the full entry path is stored in the entry for convenience. 45 | entry['entryPath'] = this.joinEntryPath_(directoryPath, entry.name); 46 | this.cache[fileSystemId][directoryPath]['entries'][entry.name] = entry; 47 | if (entry.size == -1) { 48 | log.debug('Adding incomplete entry for ' + entry.name); 49 | this.cache[fileSystemId][directoryPath]['incomplete_entries'] 50 | [entry.name] = true; 51 | } 52 | }.bind(this)); 53 | }; 54 | 55 | MetadataCache.prototype.lookupMetadata = function( 56 | fileSystemId, entryPath, currentTime) { 57 | var pathParts = this.splitEntryPath_(entryPath); 58 | var dirCache = this.getDirectoryCache_(fileSystemId, pathParts); 59 | 60 | if (!dirCache) { 61 | return null; 62 | } 63 | 64 | var entryExpiresAt = 65 | dirCache['timeCached'] + this.getCacheTimeMs_(pathParts['path']); 66 | if (currentTime >= entryExpiresAt) { 67 | // Invalidates the metadata for the entire directory. 68 | log.debug('Invalidating dir cache for ' + pathParts['path']); 69 | var fsCache = this.cache[fileSystemId]; 70 | delete fsCache[pathParts['path']]; 71 | return null; 72 | } 73 | 74 | return dirCache['entries'][pathParts['name']] || null; 75 | }; 76 | 77 | MetadataCache.prototype.getBatchToUpdate = function( 78 | fileSystemId, entryPath, batchSize) { 79 | var pathParts = this.splitEntryPath_(entryPath); 80 | var dirCache = this.getDirectoryCache_(fileSystemId, pathParts); 81 | 82 | if (!dirCache) { 83 | return []; 84 | } 85 | 86 | var upto = 1; 87 | var batch = [entryPath]; 88 | var toRemove = []; 89 | 90 | // TODO(zentaro): Might also have to put a promise on the entry 91 | // while a batch is in flight to prevent a race condition. 92 | for (var name in dirCache['incomplete_entries']) { 93 | if (name != pathParts['name']) { 94 | var fullPath = this.joinEntryPath_(pathParts['path'], name); 95 | batch.push(fullPath); 96 | } 97 | 98 | // Put a resolver in the cache entry that subsequent 99 | // calls can wait on. 100 | dirCache['entries'][name]['stat_resolver'] = getPromiseResolver(); 101 | toRemove.push(name); 102 | 103 | if (upto++ >= batchSize) { 104 | break; 105 | } 106 | } 107 | 108 | toRemove.forEach(function(name) { 109 | delete dirCache['incomplete_entries'][name]; 110 | }); 111 | 112 | return batch; 113 | }; 114 | 115 | MetadataCache.prototype.updateMetadata = function( 116 | fileSystemId, requestEntryPath, entry) { 117 | var pathParts = this.splitEntryPath_(requestEntryPath); 118 | var dirCache = this.getDirectoryCache_(fileSystemId, pathParts); 119 | 120 | // requestEntryPath could be a sibling in the case of batch updates so 121 | // always build the actual entry path explicitly. 122 | entry['entryPath'] = this.joinEntryPath_(pathParts['path'], entry.name); 123 | // TODO(zentaro): Is is actually necessary to store it in the entry? 124 | log.debug('Updating metadata for ' + entry['entryPath']); 125 | 126 | // TODO(zentaro): Consider having separate expirations on metadata entries. 127 | if (dirCache) { 128 | // Grab the resolver if it is there. 129 | var oldEntry = dirCache['entries'][entry['name']]; 130 | if (oldEntry) { 131 | var statResolver = oldEntry['stat_resolver']; 132 | if (statResolver) { 133 | log.debug('Firing pending stat_resolver for ' + entry['entryPath']); 134 | statResolver.resolve(entry); 135 | } 136 | } 137 | 138 | // Assumption is that updateMetadata is only called with complete entries. 139 | dirCache['entries'][entry['name']] = entry; 140 | 141 | // Remove from the incomplete_entries set. 142 | log.debug('Removing incomplete ' + entry['entryPath']); 143 | delete dirCache['incomplete_entries'][entry['name']]; 144 | } 145 | }; 146 | 147 | MetadataCache.prototype.invalidateEntry = function(fileSystemId, entryPath) { 148 | var pathParts = this.splitEntryPath_(entryPath); 149 | var dirCache = this.getDirectoryCache_(fileSystemId, pathParts); 150 | 151 | if (!dirCache) { 152 | return null; 153 | } 154 | 155 | log.debug('Invalidating metadata entry for ' + entryPath); 156 | 157 | // NOTE: Currently invalidation just deletes the entry in the dirCache which 158 | // is fine since we only use the cache for individual metadata requests. When 159 | // the cache misses it will do a real lookup. 160 | // 161 | // TODO(zentaro): If this class supports using the dirCache to service readDir 162 | // requests then this would also invalidate the whole dir. Currently a readDir 163 | // would refresh the cache in all cases. 164 | delete dirCache['entries'][pathParts['name']]; 165 | delete dirCache['incomplete_entries'][pathParts['name']]; 166 | }; 167 | 168 | // Returns how long in ms a directories cache entries are valid. 169 | // Currently just the same value. In theory later policy could mark certain 170 | // directories for longer cache time. 171 | MetadataCache.prototype.getCacheTimeMs_ = function(directoryPath) { 172 | // Allow a 5 min cache. This should be safe because any subsequent 173 | // readDirectory will refresh. This prevents the case where for a large 174 | // directory, part of set of stat()s hit the cache but then it degrades 175 | // from batching due to a total cache miss. 176 | return 5 * 60 * 1000; 177 | }; 178 | 179 | MetadataCache.prototype.getDirectoryCache_ = function(fileSystemId, pathParts) { 180 | if (!pathParts) { 181 | return null; 182 | } 183 | 184 | var fsCache = this.cache[fileSystemId]; 185 | 186 | if (!fsCache) { 187 | return null; 188 | } 189 | 190 | return fsCache[pathParts['path']] || null; 191 | }; 192 | 193 | MetadataCache.prototype.splitEntryPath_ = function(entryPath) { 194 | // TODO(zentaro): Can simplify. 195 | if (entryPath == '/') { 196 | return {'path': '/', 'name': ''}; 197 | } 198 | 199 | var slashAt = entryPath.lastIndexOf('/'); 200 | if (slashAt == -1) { 201 | return null; 202 | } else if (slashAt == 0) { 203 | return {'path': '/', 'name': entryPath.substring(1)}; 204 | } else { 205 | return { 206 | 'path': entryPath.substring(0, slashAt), 207 | 'name': entryPath.substring(slashAt + 1) 208 | }; 209 | } 210 | }; 211 | 212 | MetadataCache.prototype.joinEntryPath_ = function(path, name) { 213 | if (path == '/') { 214 | return path + name; 215 | } else { 216 | return path + '/' + name; 217 | } 218 | }; 219 | -------------------------------------------------------------------------------- /app/mount_dialog.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Roboto"; 3 | src: url("assets/Roboto-Medium.woff2") format("woff2"); 4 | font-weight: 500; 5 | } 6 | 7 | body { 8 | font-family: Roboto; 9 | font-weight: 500; 10 | } 11 | 12 | core-header-panel { 13 | background: white; 14 | } 15 | #header { 16 | background-color: #2196F3; 17 | } 18 | #mountButton { 19 | background-color: #2196F3; 20 | color: white; 21 | width: 90px; 22 | } 23 | #cancelButton { 24 | color: #cccccc; 25 | width: 90px; 26 | font-size: 13px; 27 | } 28 | paper-toolbar { 29 | font-size: 13px; 30 | } 31 | #header_text { 32 | font-family: Roboto; 33 | font-weight: 500; 34 | font-size: 15px; 35 | } 36 | #navheader { 37 | background-color: #56BA89; 38 | } 39 | .content { 40 | padding: 20px; 41 | } 42 | 43 | #body_content { 44 | margin: 20px; 45 | } 46 | 47 | #buttonDiv { 48 | margin-top: 15px; 49 | margin-bottom: 10px; 50 | position: absolute; 51 | bottom: 0px; 52 | right: 0px; 53 | } 54 | 55 | #licenseLink { 56 | margin-top: 15px; 57 | margin-bottom: 10px; 58 | position: absolute; 59 | bottom: 0px; 60 | left: 20px; 61 | color: blue; 62 | text-decoration: underline; 63 | cursor: pointer; 64 | } 65 | 66 | #spinner { 67 | margin: auto; 68 | } 69 | -------------------------------------------------------------------------------- /app/nacl.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | // TODO(zentaro): Is this needed? 19 | var logger = new NaclLogger(); 20 | 21 | // This function is called by common.js when the NaCl module is 22 | // loaded. 23 | function moduleDidLoad() { 24 | common.hideModule(); 25 | } 26 | -------------------------------------------------------------------------------- /app/nbt.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | /** 18 | * Global IP lookup cache. 19 | */ 20 | var ipCache = new IPCache(); 21 | 22 | 23 | /** 24 | * Send NetBIOS name request on all network interfaces and returns 25 | * an dictionary of hosts and their IP addresses that contain file shares. 26 | */ 27 | function getAllShareRoots() { 28 | var resolver = getPromiseResolver(); 29 | 30 | // TODO(zentaro): This should probably merge the lists. 31 | getNetworkInterfaces().then(function(interfaces) { 32 | var promiseList = []; 33 | interfaces.forEach(function(iface) { 34 | promiseList.push(getFileSharesOnInterface(iface.broadcastAddress)); 35 | }); 36 | 37 | joinAllIgnoringRejects(promiseList).then(function(hostsOnAllInterfaces) { 38 | var hosts = {}; 39 | hostsOnAllInterfaces.forEach(function(hostsOnInterface) { 40 | hosts = mergeInterfaceHosts(hosts, hostsOnInterface); 41 | }); 42 | 43 | // Update the cache. 44 | for (var hostName in hosts) { 45 | ipCache.add(hostName, hosts[hostName].ipAddress) 46 | } 47 | 48 | resolver.resolve(hosts); 49 | }); 50 | }); 51 | 52 | return resolver.promise; 53 | } 54 | 55 | function mergeInterfaceHosts(result, newHosts) { 56 | // TODO(zentaro): In future merge IP addresses to a list. 57 | for (var host in newHosts) { 58 | result[host] = newHosts[host]; 59 | } 60 | 61 | return result; 62 | } 63 | 64 | 65 | /** 66 | * Resolves a NetBIOS host name by sending a name request 67 | * UDP broadcast on every network interface. 68 | */ 69 | function resolveFileShareHostName(hostName) { 70 | var resolver = getPromiseResolver(); 71 | var ipAddress; 72 | 73 | if (isValidIpv4(hostName)) { 74 | // If the host name is an IP address then no need to do anything. 75 | ipAddress = hostName; 76 | } else { 77 | if (ipAddress = lmHosts.resolve(hostName)) { 78 | log.info('LMHosts resolved ' + hostName + ' to ' + ipAddress); 79 | } else if (ipAddress = ipCache.resolve(hostName)) { 80 | log.info('IPCache resolved ' + hostName + ' to ' + ipAddress); 81 | } else { 82 | getAllShareRoots().then(function() { 83 | ipAddress = ipCache.resolve(hostName); 84 | if (ipAddress) { 85 | resolver.resolve([ipAddress]); 86 | } else { 87 | log.error('Host ' + hostName + ' cannot be resolved'); 88 | resolver.resolve([]); 89 | } 90 | }, resolver.reject); 91 | 92 | return resolver.promise; 93 | } 94 | } 95 | 96 | resolver.resolve([ipAddress]); 97 | return resolver.promise; 98 | } 99 | 100 | function parseNameResponsePacket(arrayBuffer) { 101 | var reader = new ArrayBufferReader(arrayBuffer); 102 | var transId = reader.readUint16(); 103 | var flags = reader.readUint16(); 104 | 105 | reader.skip16(); // No questions 106 | var answerCount = reader.readUint16(); 107 | reader.skip16(); // No authority resources 108 | reader.skip16(); // No additional resources 109 | 110 | var nameLength = reader.readUint8(); 111 | // TOOD(zentaro): Get the name data?? 112 | reader.skipNBytes(nameLength); 113 | 114 | reader.skip8(); // Length of next segment (should be 0x00) 115 | reader.skip16(); // Question type/node status (Should be 0x0021 or 0x20) 116 | reader.skip16(); // Question class (Should be 0x0001) 117 | 118 | // ----------------------------------------------------------- 119 | // That's the end of the part that is essentially repeating back the question 120 | // (though the flags/questions/answers fields are different). 121 | 122 | // TODO (zentaro): Handle the difference between positive and negative 123 | // response. This is assuming a positive response. 124 | 125 | reader.skip32(); // Ignore TTL 126 | var addressListByteCount = reader.readUint16(); 127 | 128 | log.debug( 129 | 'packet size = ' + arrayBuffer.byteLength + ' bytes more=' + 130 | addressListByteCount); 131 | 132 | // TODO(zentaro): Check how many bytes are left in the buffer vs value above. 133 | var addressListEntryCount = reader.readUint8(); 134 | log.debug('Address list entries = ' + addressListEntryCount); 135 | log.debug('address left = ' + (addressListEntryCount * 18)); 136 | 137 | var addressList = []; 138 | var i = 0; 139 | for (i = 0; i < addressListEntryCount; i++) { 140 | // Each address entry is 18 bytes. 141 | // NAME (space padded) - 15 bytes 142 | // TYPE (file share, printer etc) - 1 byte 143 | // FLAGS - 2 bytes 144 | var c; 145 | var name = ''; 146 | for (c = 0; c < 15; c++) { 147 | name += String.fromCharCode(reader.readUint8()); 148 | } 149 | 150 | name = name.trim().toUpperCase(); 151 | var type = reader.readUint8(); 152 | var flags = reader.readUint16(); 153 | 154 | addressList.push({name: name, type: type, flags: flags}); 155 | 156 | log.info('Address ' + name + '[' + type + '][' + flags + ']'); 157 | } 158 | 159 | return addressList; 160 | } 161 | 162 | function getNameTypesFromResponse(arrayBuffer, desiredTypes, opt_name) { 163 | return parseNameResponsePacket(arrayBuffer).filter(function(nameInfo) { 164 | var typeMatches = 165 | ((desiredTypes.length == 0) || 166 | (desiredTypes.indexOf(nameInfo.type) >= 0)); 167 | var nameMatches = 168 | ((opt_name == undefined) || (opt_name.toUpperCase() == nameInfo.name)); 169 | return typeMatches && nameMatches; 170 | }); 171 | } 172 | 173 | function createNameQueryPacket() { 174 | var transId = 5566; // TODO(zentaro): generate! 175 | var flags = 0x0010; // Only broadcast flag is set 176 | var questionCount = 1; 177 | var answerResourceCount = 0; 178 | var authorityResourceCount = 0; 179 | var additionalResourceCount = 0; 180 | 181 | var bufferWriter = new ArrayBufferWriter(50); 182 | bufferWriter.writeUint16(transId); 183 | bufferWriter.writeUint16(flags); 184 | bufferWriter.writeUint16(questionCount); 185 | bufferWriter.writeUint16(answerResourceCount); 186 | bufferWriter.writeUint16(authorityResourceCount); 187 | bufferWriter.writeUint16(additionalResourceCount); 188 | 189 | // Length of name. 16 bytes of name encoded to 32 bytes. 190 | bufferWriter.writeUint8(0x20); 191 | 192 | // '*' character encodes to 2 bytes. 193 | bufferWriter.writeUint8(0x43); 194 | bufferWriter.writeUint8(0x4b); 195 | 196 | // Write the remaining 15 nulls which encode to 30* 0x41 197 | var i = 0; 198 | for (i = 0; i < 30; i++) { 199 | bufferWriter.writeUint8(0x41); 200 | } 201 | 202 | // 203 | // That's 45 bytes so far. 5 more to go in the coda. 204 | // 205 | 206 | // Length of next segment. 207 | bufferWriter.writeUint8(0); 208 | 209 | // Question type: Node status 210 | bufferWriter.writeUint16(0x21); 211 | 212 | // Question class: Internet 213 | bufferWriter.writeUint16(0x01); 214 | 215 | // Assert that the entire buffer was filled and return it. 216 | console.assert(bufferWriter.isFull()); 217 | return bufferWriter.getArrayBuffer(); 218 | } 219 | 220 | function getFileSharesOnInterface(broadcastAddress, opt_name) { 221 | // https://tools.ietf.org/html/rfc1002 222 | // Query is section 4.2.12 223 | // 224 | log.info( 225 | 'Looking for name [' + opt_name + '] at broadcast ' + broadcastAddress); 226 | var resolver = getPromiseResolver(); 227 | 228 | var buf = createNameQueryPacket(); 229 | log.debug('Sending name request query'); 230 | printPacket(buf); 231 | 232 | var nameLookup = {}; 233 | var sockProps = {}; 234 | var responseTimeout = 5000; 235 | var socketId; 236 | 237 | var timeoutId = window.setTimeout(function() { 238 | // If the timeout fires just resolve with the results we already have. 239 | resolver.resolve(nameLookup); 240 | closeUdpSocket(socketId); 241 | }, responseTimeout); 242 | 243 | // TODO(zentaro): Unbind/close the socket. 244 | chrome.sockets.udp.onReceive.addListener(function(info) { 245 | log.debug('UDP Received: ' + JSON.stringify(info)); 246 | log.debug('----------------------'); 247 | 248 | printPacket(info.data); 249 | 250 | getNameTypesFromResponse(info.data, [0x20], opt_name) 251 | .forEach(function(nameInfo) { 252 | log.info( 253 | 'FOUND FILE SHARE: ' + nameInfo.name + '[' + info.remoteAddress + 254 | ']'); 255 | nameInfo['ipAddress'] = info.remoteAddress; 256 | nameLookup[nameInfo.name] = nameInfo; 257 | 258 | if (opt_name && (opt_name.toUpperCase() == nameInfo.name)) { 259 | resolver.resolve(nameLookup); 260 | 261 | // Cancel the timer since the promise already resolved. 262 | window.clearTimeout(timeoutId); 263 | closeUdpSocket(socketId); 264 | } 265 | }); 266 | }); 267 | 268 | var socket = chrome.sockets.udp.create(sockProps, function(createInfo) { 269 | socketId = createInfo.socketId; 270 | log.debug('Socket id is ' + socketId); 271 | 272 | chrome.sockets.udp.bind(socketId, '0.0.0.0', 0, function(result) { 273 | log.debug('bind result ' + result); 274 | chrome.sockets.udp.setBroadcast( 275 | socketId, true, function(broadcastResult) { 276 | log.debug('setBroadcast result = ' + broadcastResult); 277 | 278 | chrome.sockets.udp.send( 279 | socketId, buf, broadcastAddress, 137, function(sendInfo) { 280 | log.debug('send result ' + sendInfo.resultCode); 281 | log.debug('bytesSent ' + sendInfo.bytesSent); 282 | }); 283 | }); 284 | }); 285 | }); 286 | 287 | 288 | return resolver.promise; 289 | } 290 | -------------------------------------------------------------------------------- /app/net.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Gets the list of network interfaces via the chrome.system.network API. 20 | * Returns a promise that resolves with a list of structs with fields 21 | * name, broadcastAddress. 22 | */ 23 | function getNetworkInterfaces() { 24 | var resolver = getPromiseResolver(); 25 | 26 | chrome.system.network.getNetworkInterfaces(function(interfaces) { 27 | log.debug('got interfaces: ' + JSON.stringify(interfaces)); 28 | var seenInterfaces = {}; 29 | var interfaceList = []; 30 | interfaces.forEach(function(iface) { 31 | log.info(iface.name + ' ' + iface.address + '/' + iface.prefixLength); 32 | if (!seenInterfaces[iface.name]) { 33 | // TODO(zentaro): Support IPv6? 34 | broadcastAddress = 35 | makeBroadcastAddress(iface.address, iface.prefixLength); 36 | 37 | if (broadcastAddress != null) { 38 | interfaceList.push( 39 | {name: iface.name, broadcastAddress: broadcastAddress}); 40 | 41 | seenInterfaces[iface.name] = true; 42 | } 43 | } 44 | }); 45 | 46 | resolver.resolve(interfaceList); 47 | }); 48 | 49 | return resolver.promise; 50 | } 51 | 52 | function closeUdpSocket(socketId) { 53 | if (socketId != undefined) { 54 | chrome.sockets.udp.close(socketId, function() { 55 | if (chrome.runtime.lastError) { 56 | log.error( 57 | 'Error closing socket ' + socketId + ': ' + 58 | chrome.runtime.lastError.message); 59 | } else { 60 | log.debug('Socket ' + socketId + ' closed'); 61 | } 62 | }); 63 | } 64 | } 65 | 66 | // TODO(zentaro): Could wrap the udp functionality here. 67 | // function broadcastAndListen(broadcastAddress, receiveFn, timeoutMs); 68 | -------------------------------------------------------------------------------- /app/plugin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /app/plugin.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | // Init client. 19 | var smbfs = new SambaClient(); 20 | 21 | function unmountRequestedHandler(options, successFn, failureFn) { 22 | log.debug('Unmount requested - calling onUnmount on ' + options.fileSystemId); 23 | 24 | smbfs.unmount(options, successFn, failureFn) 25 | .then( 26 | function() { 27 | log.debug('unmount resolved in unmountRequestedHandler'); 28 | }, 29 | function() { 30 | log.error('unmount rejected in unmountRequestedHandler'); 31 | }); 32 | } 33 | 34 | function mountRequestedHandler(options, successFn, failureFn) { 35 | log.debug('Mount requested - popping dialog'); 36 | loadForegroundPage(); 37 | } 38 | 39 | function naclLoaded() { 40 | log.debug('nacl module loaded. Setting function pointer'); 41 | smbfs.initialize(common.naclModule.postMessage); 42 | } 43 | 44 | // This function is called by common.js when a message is received from the 45 | // NaCl module. 46 | function handleMessage(message) { 47 | if (typeof message.data == 'string') { 48 | logger.handleMessage(message); 49 | } else { 50 | smbfs.router.handleMessage(message); 51 | } 52 | } 53 | 54 | function listenForFileSystemEvents() { 55 | log.debug('************* Setting up event listeners ********'); 56 | 57 | // Setup the event listeners that the Files App will use to get data from 58 | // the extension. 59 | chrome.fileSystemProvider.onUnmountRequested.addListener( 60 | unmountRequestedHandler); 61 | 62 | chrome.fileSystemProvider.onGetMetadataRequested.addListener( 63 | smbfs.getMetadataHandler.bind(smbfs)); 64 | 65 | chrome.fileSystemProvider.onReadDirectoryRequested.addListener( 66 | smbfs.readDirectoryHandler.bind(smbfs)); 67 | 68 | chrome.fileSystemProvider.onOpenFileRequested.addListener( 69 | smbfs.openFileHandler.bind(smbfs)); 70 | 71 | chrome.fileSystemProvider.onCloseFileRequested.addListener( 72 | smbfs.closeFileHandler.bind(smbfs)); 73 | 74 | chrome.fileSystemProvider.onReadFileRequested.addListener( 75 | smbfs.readFileHandler.bind(smbfs)); 76 | 77 | chrome.fileSystemProvider.onCreateFileRequested.addListener( 78 | smbfs.createFileHandler.bind(smbfs)); 79 | 80 | chrome.fileSystemProvider.onCreateDirectoryRequested.addListener( 81 | smbfs.createDirectoryHandler.bind(smbfs)); 82 | 83 | chrome.fileSystemProvider.onTruncateRequested.addListener( 84 | smbfs.truncateHandler.bind(smbfs)); 85 | 86 | chrome.fileSystemProvider.onWriteFileRequested.addListener( 87 | smbfs.writeFileHandler.bind(smbfs)); 88 | 89 | chrome.fileSystemProvider.onMoveEntryRequested.addListener( 90 | smbfs.moveEntryHandler.bind(smbfs)); 91 | 92 | // TODO(zentaro): Not implemented yet. They will fail in the NaCl module. 93 | chrome.fileSystemProvider.onDeleteEntryRequested.addListener( 94 | smbfs.deleteEntryHandler.bind(smbfs)); 95 | 96 | chrome.fileSystemProvider.onCopyEntryRequested.addListener( 97 | smbfs.copyEntryHandler.bind(smbfs)); 98 | 99 | // onMountRequested is only supported in Chrome 44 forward. 100 | // TODO(zentaro): Implement. 101 | if (chrome.fileSystemProvider.onMountRequested) { 102 | log.debug('onMountRequested is supported in this version of chrome.'); 103 | chrome.fileSystemProvider.onMountRequested.addListener( 104 | mountRequestedHandler); 105 | } 106 | 107 | // This listener handles messages coming from the UI/popup that have 108 | // been send to the background. This should only be mount/unmount 109 | // 110 | // Most other functionality of FSP is handled through event listeners setup 111 | // directly from the background page. 112 | chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { 113 | if (message.functionName == 'mount') { 114 | log.debug('Calling SambaClient.mount from the background'); 115 | smbfs.mount(message.mountInfo) 116 | .then( 117 | function() { 118 | log.debug('Sending mount success response to popup'); 119 | sendResponse({result: true}); 120 | }, 121 | function(err) { 122 | sendResponse({result: false, error: err}); 123 | log.error('Sending err response to popup'); 124 | }); 125 | } else if (message.functionName == 'unmount') { 126 | // TODO(zentaro): This code path maybe doesn't make sense with 127 | // multiple mounts. 128 | log.debug('Calling SambaClient.unmount from the background via message'); 129 | smbfs.unmount(); 130 | } else if (message.functionName == 'enumerateFileShares') { 131 | // TODO(zentaro): This code path maybe doesn't make sense with 132 | // multiple mounts. 133 | log.debug( 134 | 'Calling SambaClient.enumerateFileShares from the background via message'); 135 | smbfs.enumerateFileShares(message.hostMap).then(function(response) { 136 | log.debug('Send enumerateFileShares response to popup'); 137 | sendResponse(response); 138 | }); 139 | } else { 140 | log.error('ERROR: Unknown message passed.'); 141 | log.error(message); 142 | } 143 | 144 | // Keeps the sendResponse function alive to get an async message. 145 | return true; 146 | }); 147 | } 148 | 149 | document.addEventListener('DOMContentLoaded', function() { 150 | // Listen for when the module loads so a pointer to post message can be setup. 151 | var listenerDiv = document.getElementById('listener'); 152 | listenerDiv.addEventListener('load', naclLoaded, true); 153 | }); 154 | 155 | listenForFileSystemEvents(); 156 | chrome.app.runtime.onLaunched.addListener(loadForegroundPage); 157 | -------------------------------------------------------------------------------- /app/popup.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | function mapErrorCodeToText(errorCode) { 19 | switch (errorCode) { 20 | case 'ACCESS_DENIED': 21 | return 'Access Denied. Check user name and password are correct.'; 22 | case 'EXISTS': 23 | return 'This file share is already mounted.'; 24 | case 'NOT_FOUND': 25 | return 'File share can\'t be found. Check the share path is correct.'; 26 | case 'FAILED': 27 | default: 28 | return 'Share could not be mounted.'; 29 | } 30 | } 31 | 32 | function onMountClicked() { 33 | log.debug('Mount clicked'); 34 | 35 | var sharePath = document.getElementById('shareDropdown').sharePath; 36 | var displayName = document.getElementById('shareDropdown').displayName; 37 | 38 | var domain = ''; 39 | var user = ''; 40 | var password = ''; 41 | // Defaults to true because when no password is entered it is safe to save. 42 | var saveCredentials = true; 43 | var passwordCheckbox = document.getElementById('passwordCheck'); 44 | if (passwordCheckbox.checked) { 45 | log.debug('Using credential fields.'); 46 | domain = document.getElementById('user_domain_input').domain; 47 | user = document.getElementById('user_domain_input').user; 48 | password = document.getElementById('password').value; 49 | var savePasswordCheckbox = document.getElementById('savePassword'); 50 | saveCredentials = savePasswordCheckbox.checked; 51 | log.debug('domain=' + domain + ' user=' + user); 52 | } 53 | log.info("Saving current share path: " + sharePath); 54 | var savedUser = user; 55 | if (domain) { 56 | savedUser = domain + '\\' + user; 57 | } 58 | 59 | var mountData = { 60 | "lastSharePath" : sharePath, 61 | "lastShareUser" : savedUser 62 | }; 63 | 64 | chrome.storage.local.set({"mountData": mountData}); 65 | 66 | var toast = document.getElementById('errorToast'); 67 | 68 | if (!document.getElementById('user_domain_input').isValid) { 69 | toast.text = 'User and domain is incorrectly formatted.'; 70 | } 71 | 72 | var overlay = document.getElementById('spinner_overlay'); 73 | overlay.toggle(); 74 | 75 | log.debug('Entered share path: ' + sharePath); 76 | canonicalizeSambaUrl(sharePath, resolveFileShareHostName) 77 | .then( 78 | function(result) { 79 | log.info( 80 | 'Got server=' + result.server + ' share=' + result.share + 81 | ' path=' + result.path); 82 | log.info('Canonical=' + result.canonical); 83 | 84 | var message = { 85 | functionName: 'mount', 86 | mountInfo: { 87 | sharePath: result.canonical, 88 | displayName: displayName, 89 | domain: domain, 90 | user: user, 91 | password: password, 92 | server: result.server, 93 | path: result.path, 94 | share: result.share, 95 | serverIP: result.serverIP, 96 | saveCredentials: saveCredentials 97 | } 98 | }; 99 | 100 | log.debug('Sending mount to background'); 101 | chrome.runtime.sendMessage(message, function(response) { 102 | if (response.result) { 103 | log.info('Mount succeeded'); 104 | window.close(); 105 | } else { 106 | log.error('Mount failed with ' + response.error); 107 | overlay.toggle(); 108 | 109 | toast.text = mapErrorCodeToText(response.error); 110 | toast.show(); 111 | } 112 | }); 113 | }, 114 | function(err) { 115 | log.error('Canonicalize url failed with ' + err); 116 | overlay.toggle(); 117 | toast.text = 'Share path is incorrectly formatted.'; 118 | toast.show(); 119 | }); 120 | } 121 | 122 | function onCancel() { 123 | log.info('Cancel clicked'); 124 | // getNetworkInterfaces().then(function(interfaces) { 125 | // interfaces.forEach(function(iface) { 126 | // log.info('Interface ' + iface.name + ' BC=' + iface.broadcastAddress); 127 | // }); 128 | // }); 129 | 130 | // TODO(zentaro): Left here for now for easy testing. 131 | // 132 | // console.log('canonicalizing...'); 133 | // canonicalizeSambaUrl('\\\\Walrus\\fooshare', 134 | // resolveFileShareHostName).then(function(result) { 135 | // console.log('success'); 136 | // console.log(result); 137 | // }, function(err) { 138 | // console.log('errr' + err); 139 | // }); 140 | 141 | window.close(); 142 | } 143 | 144 | function onPasswordChecked(changeEvent) { 145 | var isChecked = changeEvent.target.checked; 146 | var collapser = document.getElementById('collapsedContent'); 147 | 148 | if (isChecked) { 149 | collapser.show(); 150 | } else { 151 | collapser.hide(); 152 | } 153 | } 154 | 155 | function onLicenseLinkClicked() { 156 | loadLicensePage(); 157 | } 158 | 159 | function enumerateFileShares() { 160 | getAllShareRoots().then(function(hostInfoMap) { 161 | var hostIPMap = {}; 162 | for (var hostName in hostInfoMap) { 163 | hostIPMap[hostName] = hostInfoMap[hostName].ipAddress; 164 | } 165 | 166 | var message = {functionName: 'enumerateFileShares', hostMap: hostIPMap}; 167 | 168 | log.debug('enumerateFileShares sending message to background'); 169 | chrome.runtime.sendMessage(message, function(response) { 170 | if (response.result) { 171 | log.info('enumerateFileShares succeeded'); 172 | addFoundShares(response.result.value); 173 | } else { 174 | log.error('enumerateFileShares failed with ' + response.error); 175 | var sharesDropdown = document.getElementById("shareDropdown"); 176 | sharesDropdown.setLoading(false); 177 | } 178 | }); 179 | }); 180 | } 181 | 182 | function addFoundShares(shares) { 183 | var sharesDropdown = document.getElementById("shareDropdown"); 184 | if (shares) { 185 | shares.forEach(function (share) { 186 | sharesDropdown.addShare(share.fullPath); 187 | }); 188 | } 189 | sharesDropdown.setLoading(false); 190 | } 191 | 192 | function onDefaultPopupLoaded() { 193 | // Do something 194 | log.info('Popup loaded'); 195 | var mountButton = document.getElementById('mountButton'); 196 | var cancelButton = document.getElementById('cancelButton'); 197 | var passwordCheck = document.getElementById('passwordCheck'); 198 | var licenseLink = document.getElementById('licenseLink'); 199 | 200 | mountButton.addEventListener('click', onMountClicked); 201 | cancelButton.addEventListener('click', onCancel); 202 | passwordCheck.addEventListener('change', onPasswordChecked); 203 | licenseLink.addEventListener('click', onLicenseLinkClicked); 204 | 205 | enumerateFileShares(); 206 | log.debug('Loading lmHosts'); 207 | lmHosts.load().then(function() { log.debug('lmHosts loaded.'); }); 208 | } 209 | 210 | function loadPreviousShareInformation() { 211 | var sharePath = document.getElementById('shareDropdown'); 212 | var checkBox = document.getElementById('passwordCheck'); 213 | var credentialCollapse = document.getElementById('collapsedContent'); 214 | var userInput = document.getElementById('user_domain_input'); 215 | 216 | chrome.storage.local.get("mountData", function(result) { 217 | if (!isEmpty(result) && result.hasOwnProperty('mountData')) { 218 | var mountData = result.mountData; 219 | sharePath.setValue(mountData.lastSharePath); 220 | if (mountData.hasOwnProperty('lastShareUser') && !isEmpty( 221 | mountData.lastShareUser)) { 222 | checkBox.checked = true; 223 | credentialCollapse.show(); 224 | userInput.setValue(mountData.lastShareUser); 225 | } 226 | } 227 | }); 228 | } 229 | 230 | function getManagedShares() { 231 | chrome.storage.managed.get("ManagedShares", function(data) { 232 | var shareField = document.getElementById('shareDropdown'); 233 | if (!isEmpty(data) && data.hasOwnProperty("ManagedShares")) { 234 | log.info("Found managed shares: " + JSON.stringify(data)); 235 | var shares = data.ManagedShares; 236 | for (var i = 0; i < shares.length; i++) { 237 | var share = shares[i]; 238 | //We are using the first item as default share 239 | shareField.setManagedShare(share, i === 0); 240 | } 241 | } 242 | }); 243 | } 244 | 245 | document.addEventListener('DOMContentLoaded', onDefaultPopupLoaded); 246 | window.addEventListener('WebComponentsReady', loadPreviousShareInformation); 247 | window.addEventListener('WebComponentsReady', getManagedShares); -------------------------------------------------------------------------------- /app/samba.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | // This is a bogus data URI. The Files.app will attempt to download whole image 19 | // files to create a thumbnail the first time you visit any folder. A bug 20 | // https://code.google.com/p/chromium/issues/detail?id=548050 tracks not 21 | // doing that for NETWORK providers. 22 | // This work around is to supply an icon but make it bogus so it falls back to 23 | // the generic icon. 24 | var UNKNOWN_IMAGE_DATA_URI = 'data:image/png;base64,X'; 25 | 26 | // The names of the fields in an EntryMetadata. 27 | var METADATA_FIELDS = [ 28 | 'name', 'size', 'modificationTime', 'thumbnail', 'mimeType', 'isDirectory' 29 | ]; 30 | 31 | // Mapping of field name to bits to use in a mask. 32 | // Must stay in sync with mapping on native side. 33 | var METADATA_FIELD_BITS = { 34 | 'name': 1, 35 | 'isDirectory': 2, 36 | 'size': 4, 37 | 'modificationTime': 8, 38 | 'thumbnail': 16, 39 | 'mimeType': 32 40 | }; 41 | 42 | var SambaClient = function() { 43 | log.info('Initializing samba client'); 44 | this.messageId_ = 0; 45 | this.router = new MessageRouter(); 46 | this.metadataCache = new MetadataCache(); 47 | this.fsp = chrome.fileSystemProvider; 48 | this.mounts = {}; 49 | this.credentials = new CredStore(); 50 | this.populateResolver = getPromiseResolver(); 51 | this.credentials.load().then(this.populateMounts_.bind(this)); 52 | }; 53 | 54 | SambaClient.prototype.initialize = function(sendMessageFn) { 55 | log.debug('Setting sendMessageFn in SambaClient with ' + sendMessageFn); 56 | this.router.initialize(sendMessageFn); 57 | }; 58 | 59 | SambaClient.prototype.regenerateMountInfo_ = function(sharePath) { 60 | var resolver = getPromiseResolver(); 61 | log.debug('Regenerating mountInfo for ' + sharePath); 62 | // TODO(zentaro): No name resolution function is passed here because it should 63 | // have already been resolved when it was mounted previously. This does mean 64 | // that if the IP changed this mount might be stale. Alternately more complex 65 | // logic would be required since this gets called as soon as the machine boots 66 | // and network connectivity has not even been established in some cases. 67 | canonicalizeSambaUrl(sharePath).then(function(result) { 68 | log.debug('Looking for credentials for ' + result.canonical); 69 | var creds = this.credentials.get(result.canonical); 70 | 71 | var domain = ''; 72 | var username = ''; 73 | var password = ''; 74 | if (creds) { 75 | domain = creds.domain; 76 | username = creds.username; 77 | password = creds.password; 78 | } 79 | 80 | var mountInfo = { 81 | sharePath: result.canonical, 82 | domain: domain, 83 | user: username, 84 | password: password, 85 | server: result.server, 86 | path: result.path, 87 | share: result.share, 88 | serverIP: result.serverIP 89 | }; 90 | 91 | log.debug('MountInfo regenerated for ' + sharePath); 92 | resolver.resolve(mountInfo); 93 | }.bind(this)); 94 | 95 | return resolver.promise; 96 | }; 97 | 98 | SambaClient.prototype.remountShare_ = function(fileSystem) { 99 | var resolver = getPromiseResolver(); 100 | 101 | this.mounts[fileSystem.fileSystemId]['mountPromise'] = resolver.promise; 102 | 103 | this.regenerateMountInfo_(fileSystem.fileSystemId).then(function(mountInfo) { 104 | this.mount_(mountInfo, false) 105 | .then( 106 | function() { 107 | log.debug('Remounted ' + fileSystem.fileSystemId); 108 | resolver.resolve(); 109 | }.bind(this), 110 | function(err) { 111 | log.error('Remounting ' + fileSystem.fileSystemId + ' failed'); 112 | this.mounts[fileSystem.fileSystemId]['mountPromise'] = null; 113 | resolver.reject(err); 114 | }.bind(this)); 115 | }.bind(this)); 116 | 117 | return resolver.promise; 118 | }; 119 | 120 | SambaClient.prototype.populateMounts_ = function() { 121 | log.debug('Looking for existing mounts'); 122 | this.fsp.getAll(function(fileSystems) { 123 | log.debug('Found ' + fileSystems.length + ' file systems mounted.'); 124 | var unmountPromises = []; 125 | fileSystems.forEach(function(fileSystem) { 126 | // If there aren't saved credentials for this share then unmount it. 127 | // Shares that don't need a password still have a credential entry 128 | // with empty strings. 129 | if (this.credentials.exists(fileSystem.fileSystemId)) { 130 | fileSystem['mountPromise'] = null; 131 | this.mounts[fileSystem.fileSystemId] = fileSystem; 132 | } else { 133 | log.debug( 134 | 'No credentials for ' + fileSystem.fileSystemId + '. Unmounting.'); 135 | // Unmount the fileSystemId with the FSP. 136 | var unmountOptions = {fileSystemId: fileSystem.fileSystemId}; 137 | var unmountResolver = getPromiseResolver(); 138 | unmountPromises.push(unmountResolver.promise); 139 | this.fsp.unmount(unmountOptions, function() { 140 | if (chrome.runtime.lastError) { 141 | log.error( 142 | 'Unmounting ' + fileSystem.fileSystemId + ' failed: ' + 143 | chrome.runtime.lastError.message); 144 | unmountResolver.reject(chrome.runtime.lastError.message); 145 | } else { 146 | log.info('Unmounting ' + fileSystem.fileSystemId + ' succeeded'); 147 | unmountResolver.resolve(); 148 | } 149 | }); 150 | } 151 | }.bind(this)); 152 | 153 | 154 | // When all the unmount promises from above resolve then it will resolve the 155 | // populate resolver. 156 | attachResolver( 157 | joinAllIgnoringRejects(unmountPromises), this.populateResolver); 158 | 159 | }.bind(this)); 160 | }; 161 | 162 | SambaClient.prototype.sendMessage_ = function(fnName, args, opt_processDataFn) { 163 | var messageId = this.getNextMessageId(); 164 | var message = {functionName: fnName, messageId: messageId, args: args}; 165 | 166 | var cleansedArgs = args; 167 | if (fnName == 'mount') { 168 | // On the mount message scrub the password before logging. 169 | cleansedArgs = JSON.parse(JSON.stringify(args)); 170 | cleansedArgs[1]['password'] = '***********'; 171 | } 172 | 173 | log.debug( 174 | 'Sending to NaCl fn=' + fnName + ' id=' + messageId + ' args=' + 175 | JSON.stringify(cleansedArgs)); 176 | if (fnName == 'mount' || fnName == 'unmount' || 177 | fnName == 'custom_enumerateFileShares') { 178 | log.debug('Passing through mount/unmount messages'); 179 | // These messages pass straight through. 180 | return this.router.sendMessageWithRetry(message); 181 | } 182 | 183 | var resolver = getPromiseResolver(); 184 | log.debug('Waiting for populate resolver'); 185 | this.populateResolver.promise.then(function() { 186 | var fileSystemId = args[0].fileSystemId; 187 | log.debug('Sending message for fsid ' + fileSystemId); 188 | var fileSystem = this.mounts[fileSystemId]; 189 | 190 | // It's possible that a message gets sent very early after boot before all 191 | // the persisted mounts have been remounted or unmounted if there are no 192 | // saved credentials. If it was unmounted while waiting for the populate 193 | // resolver then we just fail here. 194 | if (!fileSystem) { 195 | resolver.reject('NOT_FOUND'); 196 | return; 197 | } 198 | 199 | if (!fileSystem['mountPromise']) { 200 | log.debug('There was no mount promise. Trying to remount.'); 201 | // This will have set the mountPromise by the time it returns. 202 | this.remountShare_(this.mounts[fileSystemId]); 203 | } 204 | 205 | this.mounts[fileSystemId]['mountPromise'].then( 206 | function() { 207 | log.debug('mount promise resolved. sending message to router'); 208 | this.router.sendMessageWithRetry(message, opt_processDataFn) 209 | .then(resolver.resolve, resolver.reject); 210 | }.bind(this), 211 | function(err) { 212 | log.error('Trying to send message to failed mount'); 213 | resolver.reject('NOT_FOUND'); 214 | }.bind(this)); 215 | }.bind(this)); 216 | 217 | return resolver.promise; 218 | }; 219 | 220 | SambaClient.prototype.getNextMessageId = function() { 221 | return this.messageId_++; 222 | }; 223 | 224 | SambaClient.prototype.mount = function(shareInfo) { 225 | log.debug('Explicitly mounting'); 226 | return this.mount_(shareInfo, true); 227 | }; 228 | 229 | SambaClient.prototype.mount_ = function(shareInfo, isNewMount) { 230 | log.info('Mounting ShareInfo.sharePath=' + shareInfo.sharePath); 231 | 232 | var resolver = getPromiseResolver(); 233 | var options = { 234 | fileSystemId: shareInfo.sharePath, 235 | displayName: shareInfo.displayName, 236 | writable: true 237 | }; 238 | 239 | log.debug('Calling into Nacl to mount'); 240 | this.sendMessage_('mount', [options, shareInfo]) 241 | .then( 242 | function(result) { 243 | log.debug('Mount with Samba succeeded - calling fsp'); 244 | 245 | // A new mount is initiated by the user and after mounting with 246 | // samba it also needs to register with the files.app. On reboot 247 | // the mounts are maintained by the files.app and only the samba 248 | // side needs to be re-activated. 249 | if (isNewMount) { 250 | this.fsp.mount(options, function() { 251 | if (chrome.runtime.lastError) { 252 | log.error( 253 | 'Mount failed: ' + chrome.runtime.lastError.message); 254 | resolver.reject(chrome.runtime.lastError.message); 255 | } else { 256 | log.info('Mount succeeded'); 257 | var fileSystemId = options.fileSystemId; 258 | this.fsp.get(fileSystemId, function(fileSystem) { 259 | if (chrome.runtime.lastError) { 260 | log.error( 261 | 'Get filesystem failed: ' + 262 | chrome.runtime.lastError.message); 263 | resolver.reject(chrome.runtime.lastError.message); 264 | } else { 265 | log.debug('get filesystem succeeded'); 266 | fileSystem['mountPromise'] = resolver.promise; 267 | this.mounts[fileSystemId] = fileSystem; 268 | 269 | if (shareInfo.saveCredentials) { 270 | this.credentials.add( 271 | shareInfo.sharePath, shareInfo.domain, 272 | shareInfo.user, shareInfo.password); 273 | log.debug('saving credentials'); 274 | this.credentials.save().then( 275 | function() { 276 | log.debug('Saving credentials succeeded'); 277 | resolver.resolve(); 278 | }.bind(this), 279 | function(err) { resolver.reject(err); }); 280 | } else { 281 | log.debug('Not saving credentials for this mount'); 282 | resolver.resolve(); 283 | } 284 | } 285 | }.bind(this)); 286 | } 287 | }.bind(this)); 288 | } else { 289 | log.info('Remount succeeded'); 290 | resolver.resolve(); 291 | } 292 | }.bind(this), 293 | function(err) { 294 | log.error('Mount with samba failed with ' + err); 295 | resolver.reject(err); 296 | }); 297 | 298 | return resolver.promise; 299 | }; 300 | 301 | SambaClient.prototype.noParamsHandler_ = function( 302 | functionName, options, successFn, errorFn) { 303 | log.debug('Calling ' + functionName); 304 | 305 | this.sendMessage_(functionName, [options]) 306 | .then( 307 | function(response) { 308 | log.debug( 309 | functionName + ' promise resolved. Calling success callback.'); 310 | successFn(); 311 | }, 312 | function(err) { 313 | log.error(functionName + ' rejected promise'); 314 | 315 | errorFn(err); 316 | }); 317 | }; 318 | 319 | SambaClient.prototype.enumerateFileShares = function(hostMap) { 320 | var resolver = getPromiseResolver(); 321 | 322 | this.sendMessage_('custom_enumerateFileShares', [hostMap]) 323 | .then(function(response) { resolver.resolve(response); }); 324 | 325 | return resolver.promise; 326 | }; 327 | 328 | SambaClient.prototype.unmount = function(options, successFn, errorFn) { 329 | log.info('Unmounting'); 330 | var resolver = getPromiseResolver(); 331 | 332 | this.sendMessage_('unmount', [options]) 333 | .then( 334 | function(result) { 335 | log.debug('Unmount in samba succeeded. Calling fsp to unmount.'); 336 | // Create a new options without the requestId. 337 | var unmountOptions = {fileSystemId: options.fileSystemId}; 338 | log.debug('Unmount id ' + unmountOptions.fileSystemId); 339 | // TODO(zentaro): Chrome complains that lastError doesn't get called 340 | // because this happens async. Maybe have to just do this regardless 341 | // and ignore failure on samba side. It should never fail anyway. 342 | this.fsp.unmount(unmountOptions, function() { 343 | if (chrome.runtime.lastError) { 344 | log.error( 345 | 'Unmount failed: ' + chrome.runtime.lastError.message); 346 | errorFn(); 347 | } else { 348 | log.info('Unmount succeeded'); 349 | successFn(); 350 | } 351 | 352 | log.debug('Clearing credentials for unmounted file system'); 353 | this.credentials.clear(unmountOptions.fileSystemId); 354 | this.credentials.save().then( 355 | resolver.resolve.bind(resolver), 356 | resolver.reject.bind(resolver)); 357 | }.bind(this)); 358 | }.bind(this), 359 | function(err) { log.error('unmount rejected promise'); }); 360 | 361 | return resolver.promise; 362 | }; 363 | 364 | SambaClient.prototype.filterRequestedData_ = function(options, entry) { 365 | var result = {}; 366 | 367 | var addFieldIfRequested = function(fieldName) { 368 | if (getDefault(options, fieldName, true)) { 369 | result[fieldName] = entry[fieldName]; 370 | } 371 | }; 372 | 373 | METADATA_FIELDS.forEach(addFieldIfRequested); 374 | 375 | // Workaround to prevent Files.app downloading the entire file 376 | // to generate a thumb. 377 | // The original entry won't have the thumbnail so inject a broken 378 | // URI. 379 | if (getDefault(options, 'thumbnail', true)) { 380 | result['thumbnail'] = UNKNOWN_IMAGE_DATA_URI; 381 | } 382 | 383 | return result; 384 | }; 385 | 386 | SambaClient.prototype.isEmptyRequest_ = function(options) { 387 | // TODO(zentaro): Maybe clear the mimeType flag since this 388 | // extension never delivers that data. 389 | return options['fieldMask'] == 0; 390 | }; 391 | 392 | SambaClient.prototype.isThumbOnlyRequest_ = function(options) { 393 | // Identify when only a thumbnail is being requested. 394 | return options['fieldMask'] == METADATA_FIELD_BITS['thumbnail']; 395 | }; 396 | 397 | SambaClient.prototype.isMimeTypeOnlyRequest_ = function(options) { 398 | // Identify when only a mimeType is being requested. 399 | return options['fieldMask'] == METADATA_FIELD_BITS['mimeType']; 400 | }; 401 | 402 | SambaClient.prototype.requestNeedsStat_ = function(options) { 403 | // Identify when this request needs data from stat() (ie. size or 404 | // modificationTime). 405 | return ((options['fieldMask'] & METADATA_FIELD_BITS['size']) != 0) || 406 | ((options['fieldMask'] & METADATA_FIELD_BITS['modificationTime']) != 0); 407 | }; 408 | 409 | SambaClient.prototype.getMetadataHandler = function( 410 | options, successFn, errorFn) { 411 | // TODO(zentaro): Potentially could remove the raw fields so 412 | // they don't have to get marshalled. 413 | options['fieldMask'] = this.createFieldMask_(options); 414 | // log.debug('GetMetadata ' + options.entryPath + ' Fields=' + 415 | // options['fieldMask']); 416 | 417 | if (this.isEmptyRequest_(options)) { 418 | // See crbug.com/587231 419 | log.debug('Files app sent empty request'); 420 | successFn({}); 421 | return; 422 | } 423 | 424 | // Just return default information for the root, it never 425 | // gets displayed anywhere anyway. 426 | if (options.entryPath == '/') { 427 | var ignoredDate = new Date(0); 428 | var entry = { 429 | 'name': '', 430 | 'size': 0, 431 | 'isDirectory': true, 432 | 'modificationTime': ignoredDate 433 | }; 434 | var result = this.filterRequestedData_(options, entry); 435 | log.debug('Send default root metadata'); 436 | successFn(result); 437 | return; 438 | } 439 | 440 | var updateCache = false; 441 | var cachedEntry = this.metadataCache.lookupMetadata( 442 | options.fileSystemId, options.entryPath, window.performance.now()); 443 | 444 | if (cachedEntry) { 445 | var cacheHasStat = (cachedEntry.size != -1); 446 | if (cacheHasStat || !this.requestNeedsStat_(options)) { 447 | // Either the cache already has stat() info or the request 448 | // doesn't need it. 449 | log.info('getMetadata[cache hit] ' + options.entryPath); 450 | var result = this.filterRequestedData_(options, cachedEntry); 451 | successFn(result); 452 | 453 | return; 454 | } else { 455 | var stat_resolver = cachedEntry['stat_resolver']; 456 | if (stat_resolver) { 457 | // Another batch already grabbed this entry so just wait for 458 | // the promise to resolve. 459 | stat_resolver.promise.then( 460 | function(entry) { 461 | log.info('getMetadata[stat_resolver] ' + entry['entryPath']); 462 | var result = this.filterRequestedData_(options, entry); 463 | successFn(result); 464 | // TODO(zentaro): Is the delete redundant? 465 | delete cachedEntry['stat_resolver']; 466 | }.bind(this), 467 | function(err) { 468 | log.error('batchGetMetadata[stat_resolver] failed with ' + err); 469 | errorFn(err); 470 | delete cachedEntry['stat_resolver']; 471 | }); 472 | 473 | return; 474 | } 475 | 476 | // When the result comes back update the cache with the 477 | // stat() info. 478 | updateCache = true; 479 | 480 | // Since there is going to be a round trip anyway 481 | // ask the cache for a batch of entries that could be updated. 482 | var batch = this.metadataCache.getBatchToUpdate( 483 | options.fileSystemId, options.entryPath, 64); 484 | if (batch.length > 0) { 485 | var batchOptions = cloneObject(options); 486 | batchOptions['entries'] = batch; 487 | delete batchOptions['entryPath']; 488 | 489 | this.sendMessage_('batchGetMetadata', [batchOptions]) 490 | .then( 491 | function(response) { 492 | log.debug('batchGetMetadata succeeded'); 493 | var sentRequestedResult = false; 494 | response.result.value.forEach(function(entry) { 495 | var result = this.handleStatEntry_( 496 | options, options.entryPath, entry); 497 | // The first item in the batch is the cache miss that 498 | // triggered the batch so fire that off. 499 | if (!sentRequestedResult) { 500 | log.info('getMetadata[cache miss] ' + options.entryPath); 501 | successFn(result); 502 | sentRequestedResult = true; 503 | } 504 | }.bind(this)); 505 | }.bind(this), 506 | function(err) { 507 | log.error('batchGetMetadata failed with ' + err); 508 | errorFn(err); 509 | }); 510 | 511 | return; 512 | } 513 | } 514 | } 515 | 516 | if (this.isThumbOnlyRequest_(options)) { 517 | // Assumption is that the files app would never do this 518 | // on a non-existant file. Because if it did then 519 | // the error callback should be called instead. 520 | log.debug('Thumb only request.'); 521 | successFn({'thumbnail': UNKNOWN_IMAGE_DATA_URI}); 522 | return; 523 | } 524 | 525 | if (this.isMimeTypeOnlyRequest_(options)) { 526 | // This extension doesn't know mime types so always return 527 | // nothing. 528 | log.debug('Mime type only request'); 529 | successFn({}); 530 | return; 531 | } 532 | 533 | this.sendMessage_('getMetadata', [options]) 534 | .then( 535 | function(response) { 536 | log.info('getMetadata succeeded'); 537 | 538 | var result = this.handleStatEntry_( 539 | options, options.entryPath, response.result.value); 540 | successFn(result); 541 | }.bind(this), 542 | function(err) { 543 | log.error('getMetadata failed with ' + err); 544 | errorFn(err); 545 | }); 546 | }; 547 | 548 | SambaClient.prototype.handleStatEntry_ = function(options, entryPath, entry) { 549 | // TODO(zentaro): updateCache may become redundant. 550 | 551 | // Convert the date types to be dates from string 552 | entry.modificationTime = new Date(entry.modificationTime * 1000); 553 | 554 | // Workaround to prevent Files.app downloading the entire file 555 | // to generate a thumb. 556 | // TODO(zentaro): Turns out the app will ask for the thumb for all 557 | // files but ignore it for everything except images and vids. So 558 | // don't bother sending it for other cases. 559 | if (options.thumbnail) { 560 | entry.thumbnail = UNKNOWN_IMAGE_DATA_URI; 561 | } 562 | 563 | var result = this.filterRequestedData_(options, entry); 564 | this.metadataCache.updateMetadata(options.fileSystemId, entryPath, entry); 565 | 566 | return result; 567 | }; 568 | 569 | SambaClient.prototype.createFieldMask_ = function(options) { 570 | var mask = 0; 571 | METADATA_FIELDS.forEach(function(fieldName) { 572 | if (getDefault(options, fieldName, true)) { 573 | mask |= METADATA_FIELD_BITS[fieldName]; 574 | } 575 | }); 576 | 577 | return mask; 578 | }; 579 | 580 | SambaClient.prototype.readDirectoryHandler = function( 581 | options, successFn, errorFn) { 582 | log.debug('readDirectoryHandler called'); 583 | 584 | var entries = []; 585 | var startTime = window.performance.now(); 586 | var processDataFn = function(response) { 587 | // Convert the date types to be dates from string 588 | response.result.value = response.result.value.map(function(elem) { 589 | elem.modificationTime = new Date(elem.modificationTime * 1000); 590 | 591 | return elem; 592 | }); 593 | 594 | // Accumulate the entries so they can be set in the cache at the end. 595 | var elapsed = window.performance.now() - startTime; 596 | log.info( 597 | 'readDirectory:batch[' + entries.length + '-' + 598 | (entries.length + response.result.value.length) + '][' + elapsed + 599 | 'ms] ' + options.directoryPath); 600 | entries = extendArray(entries, response.result.value); 601 | 602 | var desiredBatchSize = 64; 603 | var batch = response.result.value; 604 | var filteredBatch = batch.map(function (entry) { 605 | return this.filterRequestedData_(options, entry); 606 | }.bind(this)); 607 | var currentBatchSize = filteredBatch.length; 608 | var upto = 0; 609 | if (currentBatchSize <= desiredBatchSize) { 610 | // Just send it without doing anything for small batches. 611 | successFn(filteredBatch, response.hasMore); 612 | } else { 613 | while (upto < currentBatchSize) { 614 | // Take a copy of a slice of the array. 615 | var newBatch = sliceArray(filteredBatch, upto, desiredBatchSize); 616 | successFn(newBatch, true); 617 | upto += desiredBatchSize; 618 | } 619 | 620 | // For simplicity just send an empty batch with hasMore=false. 621 | if (!response.hasMore) { 622 | successFn([], false); 623 | } 624 | } 625 | }.bind(this); 626 | 627 | // TODO(zentaro): Potentially could remove the raw fields so 628 | // they don't have to get marshalled. 629 | options['fieldMask'] = this.createFieldMask_(options); 630 | // log.debug('ReadDirectory Fields=' + options['fieldMask']); 631 | 632 | this.sendMessage_('readDirectory', [options], processDataFn) 633 | .then( 634 | function() { 635 | log.info( 636 | 'readDirectory[' + entries.length + ' results] ' + 637 | options.directoryPath); 638 | this.metadataCache.cacheDirectoryContents( 639 | options.fileSystemId, options.directoryPath, 640 | entries, window.performance.now()); 641 | }.bind(this), 642 | function(err) { 643 | log.error('readDirectory failed with ' + err); 644 | 645 | // TODO: More specific?? 646 | errorFn('FAILED'); 647 | }); 648 | }; 649 | 650 | SambaClient.prototype.openFileHandler = function(options, successFn, errorFn) { 651 | // TODO(zentaro): Could be smarter and only do this when opened for write. 652 | this.metadataCache.invalidateEntry(options.fileSystemId, options.filePath); 653 | this.noParamsHandler_('openFile', options, successFn, errorFn); 654 | }; 655 | 656 | SambaClient.prototype.closeFileHandler = function(options, successFn, errorFn) { 657 | this.noParamsHandler_('closeFile', options, successFn, errorFn); 658 | }; 659 | 660 | SambaClient.prototype.readFileHandler = function(options, successFn, errorFn) { 661 | log.debug('readFileHandler called'); 662 | 663 | var processDataFn = function(response) { 664 | log.debug('sending readFile batch'); 665 | successFn(response.result.value, response.hasMore); 666 | }; 667 | 668 | this.sendMessage_('readFile', [options], processDataFn) 669 | .then( 670 | function(response) { log.info('readFile succeeded'); }, 671 | function(err) { 672 | log.error('readFile failed with ' + err); 673 | 674 | // TODO: More specific?? 675 | errorFn('FAILED'); 676 | }); 677 | }; 678 | 679 | SambaClient.prototype.createDirectoryHandler = function( 680 | options, successFn, errorFn) { 681 | this.noParamsHandler_('createDirectory', options, successFn, errorFn); 682 | }; 683 | 684 | SambaClient.prototype.deleteEntryHandler = function( 685 | options, successFn, errorFn) { 686 | this.metadataCache.invalidateEntry(options.fileSystemId, options.entryPath); 687 | this.noParamsHandler_('deleteEntry', options, successFn, errorFn); 688 | }; 689 | 690 | SambaClient.prototype.createFileHandler = function( 691 | options, successFn, errorFn) { 692 | this.noParamsHandler_('createFile', options, successFn, errorFn); 693 | }; 694 | 695 | SambaClient.prototype.copyEntryHandler = function(options, successFn, errorFn) { 696 | this.metadataCache.invalidateEntry(options.fileSystemId, options.targetPath); 697 | this.noParamsHandler_('copyEntry', options, successFn, errorFn); 698 | }; 699 | 700 | SambaClient.prototype.moveEntryHandler = function(options, successFn, errorFn) { 701 | this.metadataCache.invalidateEntry(options.fileSystemId, options.targetPath); 702 | this.metadataCache.invalidateEntry(options.fileSystemId, options.sourcePath); 703 | this.noParamsHandler_('moveEntry', options, successFn, errorFn); 704 | }; 705 | 706 | SambaClient.prototype.truncateHandler = function(options, successFn, errorFn) { 707 | this.metadataCache.invalidateEntry(options.fileSystemId, options.filePath); 708 | this.noParamsHandler_('truncate', options, successFn, errorFn); 709 | }; 710 | 711 | SambaClient.prototype.writeFileHandler = function(options, successFn, errorFn) { 712 | this.noParamsHandler_('writeFile', options, successFn, errorFn); 713 | }; 714 | 715 | // TODO(zentaro): Implement abort? Is it even possible? 716 | -------------------------------------------------------------------------------- /app/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type":"object", 3 | "properties": { 4 | "ManagedShares": { 5 | "title": "Managed Shares", 6 | "description": "The Managed Shares that will be populated for managed users", 7 | "type": "array", 8 | "items" : { 9 | "type": "string" 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /app/stream.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | /** 19 | * Helper class for writing protocol data to an array buffer. Takes a size in 20 | * the constructor and allows a single sequential write pass as is usual when 21 | * construction a network packet. Data is written big endian. 22 | * TODO(zentaro): Add other write* functions if needed. 23 | * TODO(zentaro): Add optional endian param to write* methods if needed. 24 | */ 25 | ArrayBufferWriter = function(size) { 26 | this.size_ = size; 27 | this.upto_ = 0; 28 | this.arrayBuffer_ = new ArrayBuffer(size); 29 | this.dataView_ = new DataView(this.arrayBuffer_); 30 | }; 31 | 32 | ArrayBufferWriter.prototype.writeUint8 = function(value) { 33 | this.assertSize_(1); 34 | this.dataView_.setUint8(this.upto_, value); 35 | this.upto_++; 36 | }; 37 | 38 | ArrayBufferWriter.prototype.writeUint16 = function(value) { 39 | this.assertSize_(2); 40 | this.dataView_.setUint16(this.upto_, value); 41 | this.upto_ += 2; 42 | }; 43 | 44 | ArrayBufferWriter.prototype.isFull = function() { 45 | return this.upto_ == this.size_; 46 | }; 47 | 48 | ArrayBufferWriter.prototype.getArrayBuffer = function() { 49 | return this.arrayBuffer_; 50 | }; 51 | 52 | ArrayBufferWriter.prototype.assertSize_ = function(bytesToWrite) { 53 | console.assert((this.upto_ + bytesToWrite) <= this.size_); 54 | }; 55 | 56 | 57 | 58 | /** 59 | * Helper class for reading sequential protocol data from an array buffer. 60 | * Takes the array buffer in the constructor. Data is read big endian. 61 | * TODO(zentaro): Add other read* functions if needed. 62 | * TODO(zentaro): Add optional endian param to read* methods if needed. 63 | */ 64 | ArrayBufferReader = function(arrayBuffer) { 65 | this.upto_ = 0; 66 | this.arrayBuffer_ = arrayBuffer; 67 | this.dataView_ = new DataView(this.arrayBuffer_); 68 | }; 69 | 70 | ArrayBufferReader.prototype.readUint8 = function() { 71 | this.assertSize_(1); 72 | var result = this.dataView_.getUint8(this.upto_); 73 | this.upto_++; 74 | return result; 75 | }; 76 | 77 | ArrayBufferReader.prototype.readUint16 = function() { 78 | this.assertSize_(2); 79 | var result = this.dataView_.getUint16(this.upto_); 80 | this.upto_ += 2; 81 | return result; 82 | }; 83 | 84 | ArrayBufferReader.prototype.skip8 = function() { 85 | this.assertSize_(1); 86 | this.upto_ += 1; 87 | }; 88 | 89 | ArrayBufferReader.prototype.skip16 = function() { 90 | this.assertSize_(2); 91 | this.upto_ += 2; 92 | }; 93 | 94 | ArrayBufferReader.prototype.skip32 = function() { 95 | this.assertSize_(4); 96 | this.upto_ += 4; 97 | }; 98 | 99 | ArrayBufferReader.prototype.skipNBytes = function(numberOfBytes) { 100 | this.assertSize_(numberOfBytes); 101 | this.upto_ += numberOfBytes; 102 | }; 103 | 104 | 105 | ArrayBufferReader.prototype.isEndOfBuffer = function() { 106 | return this.upto_ == this.arrayBuffer_.byteLength; 107 | }; 108 | 109 | ArrayBufferReader.prototype.assertSize_ = function(bytesToRead) { 110 | console.assert((this.upto_ + bytesToRead) <= this.arrayBuffer_.byteLength); 111 | }; 112 | -------------------------------------------------------------------------------- /app/url.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | // Converts any backslashes (\) to forward slashes (/). 19 | function flipBackSlashes(original) { 20 | // This is effectively a replace all. 21 | // See http://jsperf.com/replace-all-vs-split-join 22 | return original.split('\\').join('/'); 23 | } 24 | 25 | // Converts any forward slashes (\) to backslashes (/). 26 | function flipForwardSlashes(original) { 27 | // This is effectively a replace all. 28 | // See http://jsperf.com/replace-all-vs-split-join 29 | return original.split('/').join('\\'); 30 | } 31 | 32 | 33 | // Canonicalizes and validates a url. If the url is invalid it returns null. 34 | // Attempt to accept either windows form \\server\share\path\to\file or 35 | // canonical smb://server/share/path/to/file and return the canonical form. 36 | function canonicalizeSambaUrl(original, opt_nameLookupFn) { 37 | var resolver = getPromiseResolver(); 38 | // TODO(zentaro): Regex is probably shorter. 39 | original = original.trim(); 40 | var flipped = flipBackSlashes(original); 41 | var windowsForm; 42 | var canonical; 43 | 44 | if (flipped.startsWith('smb://')) { 45 | canonical = flipped; 46 | flipped = flipped.substring(4); 47 | // Strip smb: from the front and make them back slashes. 48 | windowsForm = flipForwardSlashes(original.substring(4)); 49 | } else if (flipped.startsWith('//')) { 50 | canonical = 'smb:' + flipped; 51 | windowsForm = original; 52 | } else { 53 | // Not one of the 2 valid forms. 54 | resolver.reject('Invalid start of url format'); 55 | return resolver.promise; 56 | } 57 | 58 | // Take the // from the front. 59 | var working = flipped.substring(2); 60 | var nextSlash = working.indexOf('/'); 61 | if (nextSlash == -1) { 62 | resolver.reject('Invalid url format'); 63 | return resolver.promise; 64 | } 65 | 66 | // Extract the server part. 67 | var server = working.substring(0, nextSlash); 68 | working = working.substring(nextSlash + 1); 69 | 70 | // The share is either the rest of the string or upto the next slash. 71 | var share; 72 | nextSlash = working.indexOf('/'); 73 | if (nextSlash == -1) { 74 | share = working; 75 | working = ''; 76 | } else { 77 | share = working.substring(0, nextSlash); 78 | working = working.substring(nextSlash + 1); 79 | } 80 | 81 | // TODO(zentaro): In future potentially support enumerating the server 82 | // (share.length == 0) or the entire network when supported 83 | // (server.length == 0) 84 | if (server.length == 0 || share.length == 0) { 85 | resolver.reject('No server or share'); 86 | return resolver.promise; 87 | } 88 | 89 | // TODO(zentaro): Investigate what should happen if there is subsequent path? 90 | // Should the mount start deeper in the directory structure? 91 | var result = { 92 | original: original, 93 | canonical: canonical, 94 | windowsForm: windowsForm, 95 | server: server, 96 | share: share, 97 | path: working 98 | }; 99 | 100 | log.debug('Canonicalize result: ' + JSON.stringify(result)); 101 | 102 | if (opt_nameLookupFn == undefined) { 103 | // No IP lookup. 104 | resolver.resolve(result); 105 | } else { 106 | opt_nameLookupFn(server).then( 107 | function(ipAddresses) { 108 | if (ipAddresses.length == 0) { 109 | log.warning('Name could not be resolved'); 110 | result['serverIP'] = ''; 111 | resolver.resolve(result); 112 | } else { 113 | if (ipAddresses.length > 1) { 114 | log.warning( 115 | 'name resolved to multiple IP addresses. using first'); 116 | } 117 | 118 | result['serverIP'] = ipAddresses[0]; 119 | // Make sure it's in the right place before replacing. 120 | if (canonical.toUpperCase().indexOf(server.toUpperCase()) == 121 | 'smb://'.length) { 122 | // Replace the first instance of server with the ip address. 123 | var regex = new RegExp('(' + regexEscape(server) + ')', 'i'); 124 | result['canonical'] = 125 | canonical.replace(regex, result['serverIP']); 126 | log.debug('resolved canonical ' + result['canonical']); 127 | } 128 | 129 | resolver.resolve(result); 130 | } 131 | }, 132 | function(err) { 133 | log.error('name lookup failed ' + err); 134 | resolver.reject(err); 135 | }); 136 | } 137 | 138 | return resolver.promise; 139 | } 140 | -------------------------------------------------------------------------------- /app/utils.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | var isDef = function(value) { 18 | return value !== void 0; 19 | }; 20 | 21 | var thenAlways = function(promise, fn) { 22 | promise.then(fn, fn); 23 | return promise; 24 | }; 25 | 26 | var getPromiseResolver = function() { 27 | var resolveFn, rejectFn; 28 | // TODO(zentaro): Add timeout support. 29 | var promise = new Promise(function(resolve, reject) { 30 | resolveFn = resolve; 31 | rejectFn = reject; 32 | }); 33 | 34 | return {promise: promise, resolve: resolveFn, reject: rejectFn}; 35 | }; 36 | 37 | var getTimeoutResolver = function(timeoutMs) { 38 | var resolver = getPromiseResolver(); 39 | 40 | var timeout = setTimeout(function() { resolver.reject(); }, timeoutMs); 41 | 42 | thenAlways(resolver.promise, function() { clearTimeout(timeout); }); 43 | 44 | return resolver; 45 | }; 46 | 47 | // TODO(zentaro): This isn't as generic as it could be since it 48 | // special cases certain types of errors. Since some 'errors' are good errors. 49 | // For example when you try to save a new file ChromeOS will probe to try and 50 | // open a file with the new name. It is expecting to get a NOT_FOUND error in 51 | // response (which is expected and should not be retryied). 52 | // 53 | // The current way these situations are being held is that a certain set of 54 | // internal errors (like timeout, connection reset/abort) get mapped to a 55 | // fake error called SHOULD_RETRY. That error can never be bubbled up to CrOS 56 | // or it would cause a failure. The logic here will only retry when that is 57 | // the error and in the terminating case will replace that error with a generic 58 | // 'FAILED'. 59 | var getRetryingPromise = function(promiseFn, operationName, attemptCount) { 60 | if (attemptCount <= 0) { 61 | log.error('No attempt made for ' + operationName); 62 | return Promise.reject('FAILED'); 63 | } 64 | 65 | var attempts = 0; 66 | var resolver = getPromiseResolver(); 67 | 68 | // Declare outside since it is called recursively within the function. 69 | var retryFn; 70 | retryFn = function(err) { 71 | attempts++; 72 | if (attempts >= attemptCount) { 73 | log.error( 74 | operationName + ' failed on attempt ' + attempts + ' of ' + 75 | attemptCount + '. Retrying...'); 76 | if (err == 'SHOULD_RETRY') { 77 | err = 'FAILED'; 78 | } 79 | 80 | resolver.reject(err); 81 | } else { 82 | // There are still more attempts so try again if it is retryable. 83 | if (err == 'SHOULD_RETRY') { 84 | log.warning( 85 | operationName + ' failed on attempt ' + attempts + ' of ' + 86 | attemptCount + '. Retrying...'); 87 | 88 | promiseFn().then(resolver.resolve, retryFn); 89 | } else { 90 | // Non retry errors should just be passed through. 91 | resolver.reject(err); 92 | } 93 | } 94 | }; 95 | 96 | // Make the first attempt. 97 | promiseFn().then(resolver.resolve, retryFn); 98 | 99 | return resolver.promise; 100 | }; 101 | 102 | var getTimedPromiseResolver = function(operation) { 103 | var start = window.performance.now(); 104 | var end; 105 | var resolveFn, rejectFn; 106 | 107 | var traceFn = function() { 108 | end = window.performance.now(); 109 | log.trace(operation, start, end); 110 | }; 111 | 112 | // TODO(zentaro): Add timeout support. 113 | var promise = new Promise(function(resolve, reject) { 114 | resolveFn = function(result) { 115 | resolve(result); 116 | traceFn(); 117 | }; 118 | 119 | rejectFn = function(err) { 120 | reject(err); 121 | traceFn(); 122 | }; 123 | }); 124 | 125 | return {promise: promise, resolve: resolveFn, reject: rejectFn}; 126 | }; 127 | 128 | function cloneObject(obj) { 129 | var newObj = {}; 130 | for (var prop in obj) { 131 | newObj[prop] = obj[prop]; 132 | } 133 | 134 | return newObj; 135 | } 136 | 137 | function getDefault(obj, fieldName, defaultValue) { 138 | var value = obj[fieldName]; 139 | if (!isDef(value)) { 140 | value = defaultValue; 141 | } 142 | 143 | return value; 144 | } 145 | 146 | function regexEscape(value) { 147 | return value.replace(/[-\/\\^$*+?.()|[/]{}]/g, '\\$&'); 148 | } 149 | 150 | // Workaround javascripts unintuitive shift operators which wrap around like 151 | // signed 32 bit ints. However multiplication works. 152 | function lshift(num, bits) { 153 | return num * Math.pow(2, bits); 154 | } 155 | 156 | var hexChar = [ 157 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 158 | ]; 159 | 160 | function byteToHex(b) { 161 | return hexChar[(b >> 4) & 0x0f] + hexChar[b & 0x0f]; 162 | } 163 | 164 | function printPacket(arrayBuffer) { 165 | var packetLength = arrayBuffer.byteLength; 166 | var view = new Uint8Array(arrayBuffer); 167 | 168 | var s = ''; 169 | for (i = 0; i < packetLength; i++) { 170 | s = s + byteToHex(view[i]) + ' '; 171 | } 172 | 173 | log.debug('PACKET: ' + s); 174 | } 175 | 176 | 177 | /** 178 | * Returns the number of keys in the supplied object. 179 | */ 180 | function keyCount(obj) { 181 | return Object.keys(obj).length; 182 | } 183 | 184 | /** 185 | * This function behaves like goog.Promise.all() except that it ignores 186 | * promises in the list that reject rather than rejecting the entire list. 187 | * Only the results from the promises that resolve are put into the output list. 188 | */ 189 | function joinAllIgnoringRejects(promiseList) { 190 | var resolver = getPromiseResolver(); 191 | 192 | var resultList = []; 193 | var promisesRemaining = promiseList.length; 194 | 195 | // If the there are no promises, resolve with an empty list. 196 | if (promisesRemaining == 0) { 197 | resolver.resolve(resultList); 198 | } 199 | 200 | // Helper function to keep track of how many of the promises resolved and 201 | // rejected then resolve the outer promise when they are all completed. 202 | var onFulfilled = function() { 203 | promisesRemaining--; 204 | if (promisesRemaining == 0) { 205 | resolver.resolve(resultList); 206 | } 207 | }; 208 | 209 | // Iterate over each promise. For the ones that resolve, put the value into 210 | // resultList. Each resolve or reject decrements a counter, then resolves the 211 | // outer promise when all promises have been fulfilled. 212 | promiseList.forEach(function(promise) { 213 | promise = promise.then( 214 | function(result) { resultList.push(result); }, function() {}); 215 | thenAlways(promise, onFulfilled); 216 | }); 217 | 218 | return resolver.promise; 219 | } 220 | 221 | 222 | /** 223 | * Attaches the resolver's resolve and reject functions to a different promise. 224 | * This is useful for cases where an inner promise should resolve an outer 225 | * promise and the resolve value and reject values just pass through. 226 | */ 227 | function attachResolver(promise, resolver) { 228 | promise.then(resolver.resolve, resolver.reject); 229 | } 230 | 231 | 232 | /** 233 | * Appends tail to arr and returns arr. 234 | */ 235 | function extendArray(arr, tail) { 236 | var originalLength = arr.length; 237 | arr.length += tail.length; 238 | 239 | for (var i = originalLength, tailLen = tail.length, j = 0; j < tailLen; 240 | ++i, ++j) { 241 | arr[i] = tail[j]; 242 | } 243 | 244 | return arr; 245 | } 246 | 247 | function sliceArray(arr, begin, length) { 248 | var usableLength = clamp(length, 0, arr.length - begin); 249 | var result = new Array(usableLength); 250 | 251 | for (var i = 0, j = begin; i < usableLength; ++i, ++j) { 252 | result[i] = arr[j]; 253 | } 254 | 255 | return result; 256 | } 257 | 258 | function clamp(value, min, max) { 259 | return Math.max(min, Math.min(value, max)); 260 | } 261 | 262 | function isEmpty(obj) { 263 | for(var key in obj) { 264 | if(obj.hasOwnProperty(key)) 265 | return false; 266 | } 267 | return true; 268 | } -------------------------------------------------------------------------------- /app/window.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Mount A New File Share 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 |

Mounting network share...

52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | Mount File Share 60 | 61 |
62 | 63 | My file share needs a password 64 | 65 |
66 |
67 | 68 | If your user is part of a domain, enter it with the format DOMAIN\username 69 |
70 | 71 |
72 | Store credentials 73 | Shares that do not store credentials are unmounted on reboot. 74 |
75 |
76 |
77 |
78 | Mount 79 | Cancel 80 |
81 | 82 |
83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /app/window_launchers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | 17 | 18 | function loadForegroundPage() { 19 | chrome.app.window.create('mount_dialog.html', { 20 | id: 'main_window', 21 | innerBounds: {width: 640, height: 480, left: 100, top: 100}, 22 | hidden: false, 23 | minWidth: 640, 24 | minHeight: 480, 25 | frame: {type: 'chrome', color: '#137CD0'} 26 | }); 27 | } 28 | 29 | function loadLicensePage() { 30 | chrome.app.window.create('license_dialog.html', { 31 | id: 'license_window', 32 | innerBounds: {width: 640, height: 390, left: 100, top: 100}, 33 | hidden: false, 34 | minWidth: 640, 35 | minHeight: 390, 36 | frame: {type: 'chrome', color: '#137CD0'} 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /nacl/BaseNaclFsp.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include "BaseNaclFsp.h" 17 | #include "ppapi/cpp/var.h" 18 | #include "ppapi/cpp/var_array_buffer.h" 19 | #include "ppapi/cpp/var_dictionary.h" 20 | #include "util.h" 21 | 22 | #include "ppapi_simple/ps.h" 23 | #include "ppapi_simple/ps_interface.h" 24 | 25 | namespace NaclFsp { 26 | 27 | BaseNaclFsp::BaseNaclFsp() { this->logger.Info("BaseNaclFsp constructor"); } 28 | 29 | void BaseNaclFsp::HandleMount(const pp::VarArray& args, 30 | pp::VarDictionary* result) { 31 | MountOptions options; 32 | pp::VarDictionary optionsDict(args.Get(0)); 33 | this->logger.Info("Setting mount options"); 34 | options.Set(optionsDict); 35 | this->logger.Info("Done setting mount options"); 36 | 37 | this->logger.Info("Calling into samba to mount"); 38 | // The second arg is arbitrary extra data that can be passed to mount 39 | // and handled by the specific provider. 40 | pp::VarDictionary mountInfo(args.Get(1)); 41 | this->mount(options, mountInfo, result); 42 | } 43 | 44 | void BaseNaclFsp::HandleUnmount(const pp::VarDictionary& optionsDict, 45 | pp::VarDictionary* result) { 46 | UnmountOptions options; 47 | options.Set(optionsDict); 48 | this->unmount(options, result); 49 | } 50 | 51 | void BaseNaclFsp::HandleMessage(pp::Var var_message) { 52 | if (var_message.is_string()) { 53 | std::string message = var_message.AsString(); 54 | this->logger.Info("You sent me string '" + message + "'"); 55 | } else if (var_message.is_dictionary()) { 56 | pp::VarDictionary message(var_message); 57 | std::string functionName = message.Get("functionName").AsString(); 58 | int messageId = message.Get("messageId").AsInt(); 59 | pp::VarArray args(message.Get("args")); 60 | pp::VarDictionary optionsDict(args.Get(0)); 61 | pp::VarDictionary result; 62 | bool resultsAlreadySent = false; 63 | 64 | // TODO(zentaro): Turn this into a map to function pointers. At the 65 | // least reorder by most used. 66 | if (functionName == "mount") { 67 | // NOTE: HandleMount takes args not optionsDict because it handles 68 | // additional data in the second arg. 69 | HandleMount(args, &result); 70 | } else if (functionName == "unmount") { 71 | HandleUnmount(optionsDict, &result); 72 | } else if (functionName == "getMetadata") { 73 | GetMetadataOptions options; 74 | options.Set(optionsDict); 75 | this->getMetadata(options, &result); 76 | } else if (functionName == "batchGetMetadata") { 77 | BatchGetMetadataOptions options; 78 | options.Set(optionsDict); 79 | this->batchGetMetadata(options, &result); 80 | } else if (functionName == "readDirectory") { 81 | ReadDirectoryOptions options; 82 | options.Set(optionsDict); 83 | resultsAlreadySent = this->readDirectory(options, messageId, &result); 84 | } else if (functionName == "openFile") { 85 | OpenFileOptions options; 86 | options.Set(optionsDict); 87 | this->openFile(options, &result); 88 | } else if (functionName == "readFile") { 89 | ReadFileOptions options; 90 | options.Set(optionsDict); 91 | resultsAlreadySent = this->readFile(options, messageId, &result); 92 | } else if (functionName == "writeFile") { 93 | WriteFileOptions options; 94 | options.Set(optionsDict); 95 | this->writeFile(options, &result); 96 | } else if (functionName == "closeFile") { 97 | CloseFileOptions options; 98 | options.Set(optionsDict); 99 | this->closeFile(options, &result); 100 | } else if (functionName == "createFile") { 101 | CreateFileOptions options; 102 | options.Set(optionsDict); 103 | this->createFile(options, &result); 104 | } else if (functionName == "createDirectory") { 105 | CreateDirectoryOptions options; 106 | options.Set(optionsDict); 107 | this->createDirectory(options, &result); 108 | } else if (functionName == "deleteEntry") { 109 | DeleteEntryOptions options; 110 | options.Set(optionsDict); 111 | this->deleteEntry(options, &result); 112 | } else if (functionName == "truncate") { 113 | TruncateOptions options; 114 | options.Set(optionsDict); 115 | this->truncate(options, &result); 116 | } else if (functionName == "moveEntry") { 117 | MoveEntryOptions options; 118 | options.Set(optionsDict); 119 | this->moveEntry(options, &result); 120 | } else if (functionName == "copyEntry") { 121 | CopyEntryOptions options; 122 | options.Set(optionsDict); 123 | this->copyEntry(options, &result); 124 | } else if (Util::stringStartsWith(functionName, "custom_")) { 125 | // Custom message just pass it on. 126 | this->handleCustomMessage(functionName, args, &result); 127 | } else { 128 | this->logger.Info("Unknown function - " + functionName); 129 | return; 130 | } 131 | 132 | // Successfully streamed messages have already sent all 133 | // needed messages. 134 | if (!resultsAlreadySent) { 135 | this->sendMessage(functionName, messageId, result, false); 136 | } 137 | } 138 | } 139 | 140 | void BaseNaclFsp::sendMessage(const std::string& functionName, int messageId, 141 | const pp::VarDictionary& result, bool hasMore) { 142 | pp::VarDictionary response; 143 | response.Set(pp::Var("functionName"), functionName); 144 | response.Set(pp::Var("messageId"), messageId); 145 | response.Set(pp::Var("result"), result); 146 | response.Set(pp::Var("hasMore"), hasMore); 147 | 148 | PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), response.pp_var()); 149 | } 150 | 151 | void BaseNaclFsp::setEntryMetadata(const EntryMetadata& entry, 152 | pp::VarDictionary* value) { 153 | value->Set(pp::Var("isDirectory"), pp::Var(entry.isDirectory)); 154 | value->Set(pp::Var("name"), pp::Var(entry.name)); 155 | value->Set(pp::Var("fullPath"), pp::Var(entry.fullPath)); 156 | value->Set(pp::Var("size"), pp::Var(entry.size)); 157 | value->Set(pp::Var("modificationTime"), pp::Var(entry.modificationTime)); 158 | } 159 | 160 | void BaseNaclFsp::setResultFromEntryMetadata(const EntryMetadata& entry, 161 | pp::VarDictionary* result) { 162 | pp::VarDictionary entryDict; 163 | 164 | this->setEntryMetadata(entry, &entryDict); 165 | 166 | result->Set(pp::Var("value"), entryDict); 167 | } 168 | 169 | void BaseNaclFsp::setResultFromEntryMetadataVector( 170 | const std::vector::iterator& rangeStart, 171 | const std::vector::iterator& rangeEnd, 172 | pp::VarDictionary* result) { 173 | // TODO(zentaro): Is there an initializer to preset the array size? 174 | pp::VarArray entriesArray; 175 | size_t index = 0; 176 | 177 | for (std::vector::iterator it = rangeStart; it != rangeEnd; 178 | ++it) { 179 | pp::VarDictionary entryDict; 180 | this->setEntryMetadata(*it, &entryDict); 181 | entriesArray.Set(index, entryDict); 182 | index++; 183 | } 184 | 185 | result->Set(pp::Var("value"), entriesArray); 186 | } 187 | 188 | void BaseNaclFsp::setResultFromArrayBuffer(const pp::VarArrayBuffer& buffer, 189 | pp::VarDictionary* result) { 190 | result->Set(pp::Var("value"), buffer); 191 | } 192 | 193 | void BaseNaclFsp::setErrorResult(const std::string& error, 194 | pp::VarDictionary* result) { 195 | result->Set(pp::Var("error"), error); 196 | } 197 | 198 | std::string BaseNaclFsp::stringify(const EntryMetadata& entry) { 199 | std::ostringstream ss; 200 | ss << "Name=" << entry.name << ", " 201 | << "IsDir=" << entry.isDirectory << ", " 202 | << "Size=" << entry.size << ", " 203 | << "Time=" << entry.modificationTime; 204 | 205 | return ss.str(); 206 | } 207 | 208 | } // namespace NaclFsp 209 | -------------------------------------------------------------------------------- /nacl/BaseNaclFsp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include 18 | 19 | #include "INaclFsp.h" 20 | #include "Logger.h" 21 | 22 | namespace NaclFsp { 23 | 24 | class BaseNaclFsp : public INaclFsp { 25 | public: 26 | explicit BaseNaclFsp(); 27 | 28 | // TODO(zentaro): Maybe this shouldn't be virtual?? 29 | virtual void HandleMessage(pp::Var var_message); 30 | 31 | protected: 32 | Logger logger; 33 | 34 | void setErrorResult(const std::string& error, pp::VarDictionary* result); 35 | 36 | void setEntryMetadata(const EntryMetadata& entry, pp::VarDictionary* value); 37 | 38 | void setResultFromEntryMetadata(const EntryMetadata& entry, 39 | pp::VarDictionary* result); 40 | 41 | void setResultFromEntryMetadataVector( 42 | const std::vector::iterator& rangeStart, 43 | const std::vector::iterator& rangeEnd, 44 | pp::VarDictionary* result); 45 | 46 | void setResultFromArrayBuffer(const pp::VarArrayBuffer& buffer, 47 | pp::VarDictionary* result); 48 | 49 | void sendMessage(const std::string& functionName, int messageId, 50 | const pp::VarDictionary& result, bool hasMore); 51 | 52 | std::string stringify(const EntryMetadata& entry); 53 | 54 | private: 55 | // API Handler Methods 56 | void HandleMount(const pp::VarArray& args, pp::VarDictionary* result); 57 | void HandleUnmount(const pp::VarDictionary& optionsDict, 58 | pp::VarDictionary* result); 59 | }; 60 | 61 | } // namespace NaclFsp 62 | -------------------------------------------------------------------------------- /nacl/INaclFsp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | 18 | #include "Options.h" 19 | #include "ppapi/cpp/instance.h" 20 | 21 | namespace pp { 22 | class VarDictionary; 23 | class VarArray; 24 | class VarArrayBuffer; 25 | } 26 | 27 | namespace NaclFsp { 28 | 29 | class EntryMetadata { 30 | public: 31 | EntryMetadata() : size(-1.0), modificationTime(-1) {} 32 | 33 | bool isDirectory; 34 | std::string name; 35 | std::string fullPath; 36 | double size; 37 | 38 | int modificationTime; 39 | std::string mimeType; 40 | std::string thumbnail; 41 | 42 | /** 43 | * When stat info is populated size will be >=0. When this 44 | * returns true only name and isDirectory are populated. 45 | */ 46 | bool hasStatInfo() { return this->size >= 0; } 47 | }; 48 | 49 | class INaclFsp { 50 | public: 51 | virtual void HandleMessage(pp::Var var_message) = 0; 52 | 53 | protected: 54 | virtual void handleCustomMessage(const std::string& functionName, 55 | const pp::VarArray& args, 56 | pp::VarDictionary* result) = 0; 57 | 58 | // API Methods 59 | virtual void mount(const MountOptions& options, 60 | const pp::VarDictionary& mountInfo, 61 | pp::VarDictionary* result) = 0; 62 | virtual void unmount(const UnmountOptions& options, 63 | pp::VarDictionary* result) = 0; 64 | virtual void getMetadata(const GetMetadataOptions& options, 65 | pp::VarDictionary* result) = 0; 66 | virtual void batchGetMetadata(const BatchGetMetadataOptions& options, 67 | pp::VarDictionary* result) = 0; 68 | virtual bool readDirectory(const ReadDirectoryOptions& options, int messageId, 69 | pp::VarDictionary* result) = 0; 70 | virtual void createDirectory(const CreateDirectoryOptions& options, 71 | pp::VarDictionary* result) = 0; 72 | virtual void deleteEntry(const DeleteEntryOptions& options, 73 | pp::VarDictionary* result) = 0; 74 | virtual void moveEntry(const MoveEntryOptions& options, 75 | pp::VarDictionary* result) = 0; 76 | virtual void copyEntry(const CopyEntryOptions& options, 77 | pp::VarDictionary* result) = 0; 78 | virtual void truncate(const TruncateOptions& options, 79 | pp::VarDictionary* result) = 0; 80 | virtual void writeFile(const WriteFileOptions& options, 81 | pp::VarDictionary* result) = 0; 82 | virtual void createFile(const CreateFileOptions& options, 83 | pp::VarDictionary* result) = 0; 84 | virtual void openFile(const OpenFileOptions& options, 85 | pp::VarDictionary* result) = 0; 86 | virtual bool readFile(const ReadFileOptions& options, int messageId, 87 | pp::VarDictionary* result) = 0; 88 | virtual void closeFile(const CloseFileOptions& options, 89 | pp::VarDictionary* result) = 0; 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /nacl/Logger.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include "Logger.h" 17 | #include 18 | #include "ppapi/cpp/var.h" 19 | 20 | #include "ppapi_simple/ps.h" 21 | #include "ppapi_simple/ps_interface.h" 22 | 23 | namespace NaclFsp { 24 | 25 | Logger::Logger() { 26 | JavaScriptLogLevel = Logger::WARNING; 27 | // JavaScriptLogLevel = Logger::DEBUG; 28 | 29 | // TODO(zentaro): Probably make INFO by release time. 30 | PrintfLogLevel = Logger::WARNING; 31 | } 32 | 33 | void Logger::Debug(std::string message) { 34 | if (JavaScriptLogLevel <= Logger::DEBUG) { 35 | pp::Var var_message("NACL DEBUG: " + message); 36 | PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), 37 | var_message.pp_var()); 38 | } 39 | 40 | if (PrintfLogLevel <= Logger::DEBUG) { 41 | printf("%s\n", message.c_str()); 42 | } 43 | } 44 | 45 | void Logger::Info(std::string message) { 46 | if (JavaScriptLogLevel <= Logger::INFO) { 47 | pp::Var var_message("NACL INFO: " + message); 48 | PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), 49 | var_message.pp_var()); 50 | } 51 | 52 | if (PrintfLogLevel <= Logger::INFO) { 53 | printf("%s\n", message.c_str()); 54 | } 55 | } 56 | 57 | void Logger::Warning(std::string message) { 58 | if (JavaScriptLogLevel <= Logger::WARNING) { 59 | pp::Var var_message("NACL WARNING: " + message); 60 | PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), 61 | var_message.pp_var()); 62 | } 63 | 64 | if (PrintfLogLevel <= Logger::WARNING) { 65 | printf("%s\n", message.c_str()); 66 | } 67 | } 68 | 69 | void Logger::Error(std::string message) { 70 | if (JavaScriptLogLevel <= Logger::ERROR) { 71 | pp::Var var_message("NACL ERROR: " + message); 72 | PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), 73 | var_message.pp_var()); 74 | } 75 | 76 | if (PrintfLogLevel <= Logger::ERROR) { 77 | printf("%s\n", message.c_str()); 78 | } 79 | } 80 | 81 | } // namespace NaclFsp 82 | -------------------------------------------------------------------------------- /nacl/Logger.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | 18 | namespace NaclFsp { 19 | 20 | class Logger { 21 | public: 22 | enum LOG_LEVEL { DEBUG = 0, INFO = 1, WARNING = 2, ERROR = 3 }; 23 | 24 | Logger(); 25 | 26 | LOG_LEVEL JavaScriptLogLevel; 27 | LOG_LEVEL PrintfLogLevel; 28 | 29 | void Debug(std::string message); 30 | 31 | void Info(std::string message); 32 | 33 | void Warning(std::string message); 34 | 35 | void Error(std::string message); 36 | }; 37 | 38 | } // namespace NaclFsp 39 | -------------------------------------------------------------------------------- /nacl/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | # GNU Makefile based on shared rules provided by the Native Client SDK. 6 | # See README.Makefiles for more details. 7 | 8 | # TODO(XXXXX): What to do with this? It's based on a sample copied from NaCl SDK. 9 | 10 | TARGET = nacl_fsp 11 | 12 | include $(NACL_SDK_ROOT)/tools/common.mk 13 | 14 | EXTRA_INC_PATHS = ${NACL_SDK_ROOT}/ports/include 15 | EXTRA_LIB_PATHS = ${NACL_SDK_ROOT}/ports/lib 16 | 17 | #VALID_TOOLCHAINS := pnacl newlib glibc clang-newlib linux 18 | VALID_TOOLCHAINS := glibc 19 | 20 | DEPS = ppapi_simple_cpp nacl_io 21 | LIBS = ppapi_simple_cpp nacl_io ppapi ppapi_cpp pthread smbclient 22 | 23 | CFLAGS = -Wall 24 | SOURCES = Logger.cc Options.cc nacl_fsp.cc SambaFsp.cc BaseNaclFsp.cc 25 | 26 | # Build rules generated by macros from common.mk: 27 | 28 | $(foreach dep,$(DEPS),$(eval $(call DEPEND_RULE,$(dep)))) 29 | $(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS)))) 30 | 31 | # The PNaCl workflow uses both an unstripped and finalized/stripped binary. 32 | # On NaCl, only produce a stripped binary for Release configs (not Debug). 33 | ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG)))) 34 | $(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS))) 35 | $(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped)) 36 | else 37 | $(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS))) 38 | endif 39 | 40 | $(eval $(call NMF_RULE,$(TARGET),)) 41 | -------------------------------------------------------------------------------- /nacl/Options.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include "Options.h" 17 | 18 | #include "ppapi/cpp/var.h" 19 | #include "ppapi/cpp/var_array.h" 20 | #include "ppapi/cpp/var_array_buffer.h" 21 | #include "ppapi/cpp/var_dictionary.h" 22 | 23 | namespace NaclFsp { 24 | 25 | void BaseOptions::Set(const pp::VarDictionary& optionsDict) { 26 | fileSystemId = optionsDict.Get("fileSystemId").AsString(); 27 | } 28 | 29 | void TrackedOperationOptions::Set(const pp::VarDictionary& optionsDict) { 30 | BaseOptions::Set(optionsDict); 31 | requestId = optionsDict.Get("requestId").AsInt(); 32 | } 33 | 34 | void DirectoryOperationOptions::Set(const pp::VarDictionary& optionsDict) { 35 | TrackedOperationOptions::Set(optionsDict); 36 | directoryPath = optionsDict.Get("directoryPath").AsString(); 37 | } 38 | 39 | void OpenFileOperationOptions::Set(const pp::VarDictionary& optionsDict) { 40 | TrackedOperationOptions::Set(optionsDict); 41 | openRequestId = optionsDict.Get("openRequestId").AsInt(); 42 | } 43 | 44 | void FileIOOperationOptions::Set(const pp::VarDictionary& optionsDict) { 45 | OpenFileOperationOptions::Set(optionsDict); 46 | offset = optionsDict.Get("offset").AsDouble(); 47 | 48 | // NOTE: The base class holds this member but the derived class sets it 49 | // because in the write case it comes from the size of the ArrayBuffer. 50 | length = -1; 51 | } 52 | 53 | void MountOptions::Set(const pp::VarDictionary& optionsDict) { 54 | BaseOptions::Set(optionsDict); 55 | displayName = optionsDict.Get("displayName").AsString(); 56 | writable = optionsDict.Get("writable").AsBool(); 57 | pp::Var openLimitVar(optionsDict.Get("openedFilesLimit")); 58 | 59 | if (openLimitVar.is_int()) { 60 | openedFilesLimit = optionsDict.Get("openedFilesLimit").AsInt(); 61 | } else { 62 | openedFilesLimit = 0; 63 | } 64 | } 65 | 66 | void UnmountOptions::Set(const pp::VarDictionary& optionsDict) { 67 | BaseOptions::Set(optionsDict); 68 | } 69 | 70 | void GetMetadataOptions::Set(const pp::VarDictionary& optionsDict) { 71 | TrackedOperationOptions::Set(optionsDict); 72 | FieldMaskMixin::Set(optionsDict); 73 | entryPath = optionsDict.Get("entryPath").AsString(); 74 | } 75 | 76 | void BatchGetMetadataOptions::Set(const pp::VarDictionary& optionsDict) { 77 | TrackedOperationOptions::Set(optionsDict); 78 | FieldMaskMixin::Set(optionsDict); 79 | pp::VarArray entriesArray(optionsDict.Get("entries")); 80 | 81 | for (size_t i = 0; i < entriesArray.GetLength(); i++) { 82 | entries.push_back(entriesArray.Get(i).AsString()); 83 | } 84 | } 85 | 86 | void FieldMaskMixin::Set(const pp::VarDictionary& optionsDict) { 87 | fieldMask = optionsDict.Get("fieldMask").AsInt(); 88 | } 89 | 90 | void ReadDirectoryOptions::Set(const pp::VarDictionary& optionsDict) { 91 | DirectoryOperationOptions::Set(optionsDict); 92 | FieldMaskMixin::Set(optionsDict); 93 | } 94 | 95 | void CreateDirectoryOptions::Set(const pp::VarDictionary& optionsDict) { 96 | DirectoryOperationOptions::Set(optionsDict); 97 | recursive = optionsDict.Get("recursive").AsBool(); 98 | } 99 | 100 | void OpenFileOptions::Set(const pp::VarDictionary& optionsDict) { 101 | TrackedOperationOptions::Set(optionsDict); 102 | filePath = optionsDict.Get("filePath").AsString(); 103 | std::string fileMode = optionsDict.Get("mode").AsString(); 104 | if (fileMode == "READ") { 105 | mode = FILE_MODE_READ; 106 | } else { 107 | // TODO(zentaro): Assert it is write? 108 | mode = FILE_MODE_WRITE; 109 | } 110 | } 111 | 112 | void CreateFileOptions::Set(const pp::VarDictionary& optionsDict) { 113 | TrackedOperationOptions::Set(optionsDict); 114 | // TODO(zentaro): Can maybe consolidate filePath with OpenFileOptions. 115 | filePath = optionsDict.Get("filePath").AsString(); 116 | } 117 | 118 | void CloseFileOptions::Set(const pp::VarDictionary& optionsDict) { 119 | OpenFileOperationOptions::Set(optionsDict); 120 | } 121 | 122 | void ReadFileOptions::Set(const pp::VarDictionary& optionsDict) { 123 | FileIOOperationOptions::Set(optionsDict); 124 | 125 | // NOTE: Even though this is in the base class it is set here because the 126 | // derived write class gets this field from the length of the passed in 127 | // ArrayBuffer. 128 | length = optionsDict.Get("length").AsDouble(); 129 | } 130 | 131 | void WriteFileOptions::Set(const pp::VarDictionary& optionsDict) { 132 | FileIOOperationOptions::Set(optionsDict); 133 | 134 | // TODO(zentaro): Investigate if there might be an extra copy happening here. 135 | pp::VarArrayBuffer buffer(optionsDict.Get("data")); 136 | data = buffer.Map(); 137 | length = buffer.ByteLength(); 138 | } 139 | 140 | void DeleteEntryOptions::Set(const pp::VarDictionary& optionsDict) { 141 | TrackedOperationOptions::Set(optionsDict); 142 | entryPath = optionsDict.Get("entryPath").AsString(); 143 | recursive = optionsDict.Get("recursive").AsBool(); 144 | } 145 | 146 | void CopyEntryOptions::Set(const pp::VarDictionary& optionsDict) { 147 | TrackedOperationOptions::Set(optionsDict); 148 | sourcePath = optionsDict.Get("sourcePath").AsString(); 149 | targetPath = optionsDict.Get("targetPath").AsString(); 150 | } 151 | 152 | void MoveEntryOptions::Set(const pp::VarDictionary& optionsDict) { 153 | TrackedOperationOptions::Set(optionsDict); 154 | sourcePath = optionsDict.Get("sourcePath").AsString(); 155 | targetPath = optionsDict.Get("targetPath").AsString(); 156 | } 157 | 158 | void TruncateOptions::Set(const pp::VarDictionary& optionsDict) { 159 | TrackedOperationOptions::Set(optionsDict); 160 | filePath = optionsDict.Get("filePath").AsString(); 161 | length = optionsDict.Get("length").AsDouble(); 162 | } 163 | 164 | } // namespace NaclFsp 165 | -------------------------------------------------------------------------------- /nacl/Options.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace pp { 21 | class VarDictionary; 22 | } 23 | 24 | namespace NaclFsp { 25 | 26 | class BaseOptions { 27 | protected: 28 | BaseOptions() {} 29 | virtual void Set(const pp::VarDictionary& optionsDict); 30 | 31 | public: 32 | std::string fileSystemId; 33 | 34 | private: 35 | // Prevent copy and assignment. 36 | BaseOptions(const BaseOptions&); 37 | BaseOptions& operator=(const BaseOptions&); 38 | }; 39 | 40 | class TrackedOperationOptions : public BaseOptions { 41 | protected: 42 | TrackedOperationOptions() : requestId(-1) {} 43 | virtual void Set(const pp::VarDictionary& optionsDict); 44 | 45 | public: 46 | int requestId; 47 | }; 48 | 49 | class DirectoryOperationOptions : public TrackedOperationOptions { 50 | protected: 51 | DirectoryOperationOptions() {} 52 | virtual void Set(const pp::VarDictionary& optionsDict); 53 | 54 | public: 55 | std::string directoryPath; 56 | }; 57 | 58 | class OpenFileOperationOptions : public TrackedOperationOptions { 59 | protected: 60 | OpenFileOperationOptions() {} 61 | virtual void Set(const pp::VarDictionary& optionsDict); 62 | 63 | public: 64 | int openRequestId; 65 | }; 66 | 67 | class FileIOOperationOptions : public OpenFileOperationOptions { 68 | protected: 69 | FileIOOperationOptions() {} 70 | virtual void Set(const pp::VarDictionary& optionsDict); 71 | 72 | public: 73 | double offset; 74 | double length; 75 | }; 76 | 77 | class MountOptions : public BaseOptions { 78 | public: 79 | MountOptions() : writable(false), openedFilesLimit(0) {} 80 | virtual void Set(const pp::VarDictionary& optionsDict); 81 | 82 | std::string displayName; 83 | bool writable; 84 | int openedFilesLimit; 85 | }; 86 | 87 | class UnmountOptions : public BaseOptions { 88 | public: 89 | UnmountOptions() {} 90 | virtual void Set(const pp::VarDictionary& optionsDict); 91 | }; 92 | 93 | class FieldMaskMixin { 94 | protected: 95 | FieldMaskMixin() : fieldMask(0) {} 96 | 97 | public: 98 | uint32_t fieldMask; 99 | 100 | virtual void Set(const pp::VarDictionary& optionsDict); 101 | 102 | // If either size or modification time is required then 103 | // a call to stat() is required. 104 | bool needsStat() const { 105 | return ((this->fieldMask & FIELD_SIZE) != 0) && 106 | ((this->fieldMask & FIELD_MODIFICATION_TIME) != 0); 107 | } 108 | 109 | enum MetadataFields { 110 | FIELD_NAME = 1, 111 | FIELD_IS_DIRECTORY = 2, 112 | FIELD_SIZE = 4, 113 | FIELD_MODIFICATION_TIME = 8, 114 | FIELD_THUMBNAIL = 16, 115 | FIELD_MIME_TYPE = 32 116 | }; 117 | }; 118 | 119 | class GetMetadataOptions : public TrackedOperationOptions, 120 | public FieldMaskMixin { 121 | public: 122 | GetMetadataOptions() {} 123 | virtual void Set(const pp::VarDictionary& optionsDict); 124 | std::string entryPath; 125 | }; 126 | 127 | class BatchGetMetadataOptions : public TrackedOperationOptions, 128 | public FieldMaskMixin { 129 | public: 130 | BatchGetMetadataOptions() {} 131 | virtual void Set(const pp::VarDictionary& optionsDict); 132 | std::vector entries; 133 | }; 134 | 135 | class ReadDirectoryOptions : public DirectoryOperationOptions, 136 | public FieldMaskMixin { 137 | public: 138 | ReadDirectoryOptions() {} 139 | virtual void Set(const pp::VarDictionary& optionsDict); 140 | }; 141 | 142 | class CreateDirectoryOptions : public DirectoryOperationOptions { 143 | public: 144 | CreateDirectoryOptions() {} 145 | virtual void Set(const pp::VarDictionary& optionsDict); 146 | bool recursive; 147 | }; 148 | 149 | enum OpenFileMode { FILE_MODE_READ = 0, FILE_MODE_WRITE = 1 }; 150 | 151 | class OpenFileOptions : public TrackedOperationOptions { 152 | public: 153 | OpenFileOptions() {} 154 | virtual void Set(const pp::VarDictionary& optionsDict); 155 | std::string filePath; 156 | OpenFileMode mode; 157 | }; 158 | 159 | class CreateFileOptions : public TrackedOperationOptions { 160 | public: 161 | CreateFileOptions() {} 162 | virtual void Set(const pp::VarDictionary& optionsDict); 163 | std::string filePath; 164 | }; 165 | 166 | class CloseFileOptions : public OpenFileOperationOptions { 167 | public: 168 | CloseFileOptions() {} 169 | virtual void Set(const pp::VarDictionary& optionsDict); 170 | }; 171 | 172 | class ReadFileOptions : public FileIOOperationOptions { 173 | public: 174 | ReadFileOptions() {} 175 | virtual void Set(const pp::VarDictionary& optionsDict); 176 | }; 177 | 178 | class WriteFileOptions : public FileIOOperationOptions { 179 | public: 180 | WriteFileOptions() {} 181 | virtual void Set(const pp::VarDictionary& optionsDict); 182 | const void* data; 183 | }; 184 | 185 | class DeleteEntryOptions : public TrackedOperationOptions { 186 | public: 187 | DeleteEntryOptions() {} 188 | virtual void Set(const pp::VarDictionary& optionsDict); 189 | std::string entryPath; 190 | bool recursive; 191 | }; 192 | 193 | class CopyEntryOptions : public TrackedOperationOptions { 194 | public: 195 | CopyEntryOptions() {} 196 | virtual void Set(const pp::VarDictionary& optionsDict); 197 | std::string sourcePath; 198 | std::string targetPath; 199 | }; 200 | 201 | class MoveEntryOptions : public TrackedOperationOptions { 202 | public: 203 | MoveEntryOptions() {} 204 | virtual void Set(const pp::VarDictionary& optionsDict); 205 | std::string sourcePath; 206 | std::string targetPath; 207 | }; 208 | 209 | class TruncateOptions : public TrackedOperationOptions { 210 | public: 211 | TruncateOptions() {} 212 | virtual void Set(const pp::VarDictionary& optionsDict); 213 | std::string filePath; 214 | double length; 215 | }; 216 | 217 | } // namespace NaclFsp 218 | -------------------------------------------------------------------------------- /nacl/SambaFsp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include "BaseNaclFsp.h" 18 | #include "ppapi/cpp/var_dictionary.h" 19 | #include "samba/libsmbclient.h" 20 | 21 | namespace NaclFsp { 22 | 23 | class ShareData { 24 | public: 25 | std::string shareRoot; 26 | int smbShareId; 27 | }; 28 | 29 | class SambaMountConfig { 30 | public: 31 | // TODO(zentaro): Rename to uri? 32 | std::string sharePath; 33 | std::string domain; 34 | std::string user; 35 | std::string password; 36 | std::string server; 37 | std::string serverIP; 38 | std::string path; 39 | std::string share; 40 | }; 41 | 42 | class SambaCredTuple { 43 | public: 44 | std::string domain; 45 | std::string user; 46 | std::string password; 47 | }; 48 | 49 | class OpenFileInfo { 50 | public: 51 | int sambaFileId; 52 | size_t lengthAtOpen; 53 | off_t offset; 54 | OpenFileMode mode; 55 | }; 56 | 57 | class SambaFsp : public BaseNaclFsp { 58 | public: 59 | explicit SambaFsp(); 60 | 61 | protected: 62 | static void auth_fn(const char* srv, const char* shr, char* wg, int wglen, 63 | char* un, int unlen, char* pw, int pwlen); 64 | 65 | virtual void handleCustomMessage(const std::string& functionName, 66 | const pp::VarArray& args, 67 | pp::VarDictionary* result); 68 | virtual void mount(const MountOptions& options, 69 | const pp::VarDictionary& mountInfo, 70 | pp::VarDictionary* result); 71 | virtual void unmount(const UnmountOptions& options, 72 | pp::VarDictionary* result); 73 | virtual void getMetadata(const GetMetadataOptions& options, 74 | pp::VarDictionary* result); 75 | virtual void batchGetMetadata(const BatchGetMetadataOptions& options, 76 | pp::VarDictionary* result); 77 | virtual bool readDirectory(const ReadDirectoryOptions& options, int messageId, 78 | pp::VarDictionary* result); 79 | virtual void createDirectory(const CreateDirectoryOptions& options, 80 | pp::VarDictionary* result); 81 | virtual void deleteEntry(const DeleteEntryOptions& options, 82 | pp::VarDictionary* result); 83 | virtual void moveEntry(const MoveEntryOptions& options, 84 | pp::VarDictionary* result); 85 | virtual void copyEntry(const CopyEntryOptions& options, 86 | pp::VarDictionary* result); 87 | virtual void truncate(const TruncateOptions& options, 88 | pp::VarDictionary* result); 89 | virtual void writeFile(const WriteFileOptions& options, 90 | pp::VarDictionary* result); 91 | virtual void createFile(const CreateFileOptions& options, 92 | pp::VarDictionary* result); 93 | virtual void openFile(const OpenFileOptions& options, 94 | pp::VarDictionary* result); 95 | virtual bool readFile(const ReadFileOptions& options, int messageId, 96 | pp::VarDictionary* result); 97 | virtual void closeFile(const CloseFileOptions& options, 98 | pp::VarDictionary* result); 99 | 100 | private: 101 | typedef std::map MountMap; 102 | MountMap mounts; 103 | std::map openFiles; 104 | 105 | // TODO(zentaro): Use a dedicated class for credentials. 106 | typedef std::map CredentialStore; 107 | static CredentialStore Credentials; 108 | void saveCredentials(const SambaMountConfig& mountConfig); 109 | void removeCredentials(const SambaMountConfig& mountConfig); 110 | std::string createCredentialLookupKey(const SambaMountConfig& mountConfig); 111 | std::string mapDirectoryTypeToString(unsigned int dirType); 112 | std::string getNameFromPath(std::string path); 113 | std::string getFullPathFromRelativePath(const std::string& fileSystemId, 114 | const std::string& relativePath); 115 | void deleteEntry(const std::string& fullPath, bool recursive, 116 | pp::VarDictionary* result); 117 | bool deleteFile(const std::string& fullPath, pp::VarDictionary* result); 118 | bool deleteDirectoryContentsRecursive(const std::string& fullPath, 119 | pp::VarDictionary* result); 120 | bool deleteEmptyDirectory(const std::string& fullPath, 121 | pp::VarDictionary* result); 122 | bool readDirectoryEntries(const std::string& dirFullPath, bool readShares, 123 | std::vector* entries, 124 | pp::VarDictionary* result); 125 | bool readDirectoryEntries(const std::string& dirFullPath, 126 | std::vector* entries, 127 | pp::VarDictionary* result); 128 | bool readFileShares(const std::string& dirFullPath, 129 | std::vector* entries, 130 | pp::VarDictionary* result); 131 | bool getMetadataEntry(const std::string& fullPath, EntryMetadata* entry, 132 | pp::VarDictionary* result); 133 | void statAndStreamEntryMetadata(int messageId, 134 | std::vector* entries); 135 | void populateStatInfoVector( 136 | const std::vector::iterator& rangeStart, 137 | const std::vector::iterator& rangeEnd); 138 | void populateEntryMetadataWithStatInfo(EntryMetadata& entry); 139 | 140 | // TODO(zentaro): I don't think this is used any more. 141 | std::string flipSlashes(std::string path); 142 | void createMountConfig(const pp::VarDictionary& mountInfo, 143 | SambaMountConfig* mountConfig); 144 | void LogErrorAndSetErrorResult(std::string operationName, 145 | pp::VarDictionary* result); 146 | }; 147 | 148 | } // namespace NaclFsp 149 | -------------------------------------------------------------------------------- /nacl/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 20 | 21 | pushd $THIS_DIR 22 | make V=1 TOOLCHAIN=glibc VALID_ARCHES='x86_64 arm' 23 | 24 | 25 | if [ $? -eq 0 ]; then 26 | echo Build successful. 27 | 28 | echo Removing existing built code in app 29 | rm -rf ../app/glibc 30 | 31 | echo Copying built code back to app 32 | cp -r glibc ../app 33 | 34 | echo Removing old copy of bower_components 35 | rm -rf ../app/bower_components 36 | 37 | echo Copying bower_components into the app 38 | cp -r ../third_party/bower_components ../app 39 | 40 | echo Copying common.js from third_party into the app 41 | cp ../third_party/nacl_sdk/common.js ../app 42 | 43 | echo Vulcanizing mount dialog 44 | pushd ../app 45 | vulcanize window.html --inline-script | crisper --html mount_dialog.html --js mount_dialog.js 46 | popd 47 | fi 48 | popd 49 | -------------------------------------------------------------------------------- /nacl/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 19 | 20 | pushd $THIS_DIR 21 | rm -rf glibc 22 | rm -rf pnacl 23 | rm -rf ../app/glibc 24 | rm -rf ../app/bower_components 25 | pushd ../app 26 | rm -f mount_dialog.html 27 | rm -f mount_dialog.js 28 | rm -f common.js 29 | popd 30 | popd 31 | -------------------------------------------------------------------------------- /nacl/nacl_fsp.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | #include 16 | #include "ppapi/cpp/var.h" 17 | 18 | #include "ppapi_simple/ps.h" 19 | #include "ppapi_simple/ps_event.h" 20 | #include "ppapi_simple/ps_interface.h" 21 | #include "ppapi_simple/ps_main.h" 22 | 23 | #include "SambaFsp.h" 24 | 25 | int plugin_main(int argc, char* argv[]) { 26 | printf("plugin main: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx"); 27 | NaclFsp::SambaFsp fsp; 28 | PSEvent* ps_event = NULL; 29 | PSEventSetFilter(PSE_INSTANCE_HANDLEMESSAGE); 30 | 31 | while ((ps_event = PSEventWaitAcquire()) != NULL) { 32 | pp::Var var(ps_event->as_var); 33 | fsp.HandleMessage(var); 34 | PSEventRelease(ps_event); 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | // Register the function to call once the Instance Object is initialized. 41 | // see: pappi_simple/ps_main.h 42 | PPAPI_SIMPLE_REGISTER_MAIN(plugin_main); 43 | -------------------------------------------------------------------------------- /nacl/notes.txt: -------------------------------------------------------------------------------- 1 | ${NACL_ROOT}nacl_sdk/pepper_${TOOL_VERSION}/toolchain/linux_x86_glibc/bin/x86_64-nacl-gcc \ 2 | nacl_fsp.c -I${NACL_ROOT}nacl_sdk/pepper_${TOOL_VERSION}/include \ 3 | -L${NACL_ROOT}nacl_sdk/pepper_${TOOL_VERSION}/lib/newlib/Release -o nacl_fsp_x86_64.nexe \ 4 | -m32 -g -O2 -lppapi 5 | -------------------------------------------------------------------------------- /nacl/util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include 18 | 19 | namespace Util { 20 | 21 | template 22 | inline std::string ToString(T value) { 23 | std::ostringstream ss; 24 | ss << value; 25 | return ss.str(); 26 | } 27 | 28 | inline bool stringEndsWith(const std::string& s, const std::string& suffix) { 29 | if (s.length() < suffix.length()) { 30 | return false; 31 | } 32 | 33 | return s.compare(s.length() - suffix.length(), suffix.length(), suffix) == 0; 34 | } 35 | 36 | inline bool stringStartsWith(const std::string& s, const std::string& prefix) { 37 | if (s.length() < prefix.length()) { 38 | return false; 39 | } 40 | 41 | return s.compare(0, prefix.length(), prefix) == 0; 42 | } 43 | 44 | } // namespace Util 45 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "dependencies": { 4 | "assertion-error": { 5 | "version": "1.0.2", 6 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", 7 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", 8 | "dev": true 9 | }, 10 | "balanced-match": { 11 | "version": "1.0.0", 12 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 13 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 14 | "dev": true 15 | }, 16 | "brace-expansion": { 17 | "version": "1.1.8", 18 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 19 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 20 | "dev": true 21 | }, 22 | "browser-stdout": { 23 | "version": "1.3.0", 24 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 25 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 26 | "dev": true 27 | }, 28 | "chai": { 29 | "version": "4.0.2", 30 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.0.2.tgz", 31 | "integrity": "sha1-L3MnxN5vOF3XeHmZ4qsCaXoyuDs=", 32 | "dev": true 33 | }, 34 | "check-error": { 35 | "version": "1.0.2", 36 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 37 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 38 | "dev": true 39 | }, 40 | "commander": { 41 | "version": "2.9.0", 42 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 43 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 44 | "dev": true 45 | }, 46 | "concat-map": { 47 | "version": "0.0.1", 48 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 49 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 50 | "dev": true 51 | }, 52 | "debug": { 53 | "version": "2.6.0", 54 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", 55 | "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", 56 | "dev": true 57 | }, 58 | "deep-eql": { 59 | "version": "2.0.2", 60 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz", 61 | "integrity": "sha1-sbrAblbwp2d3aG1Qyf63XC7XZ5o=", 62 | "dev": true, 63 | "dependencies": { 64 | "type-detect": { 65 | "version": "3.0.0", 66 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-3.0.0.tgz", 67 | "integrity": "sha1-RtDMhVOrt7E6NSsNbeov1Y8tm1U=", 68 | "dev": true 69 | } 70 | } 71 | }, 72 | "diff": { 73 | "version": "3.2.0", 74 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 75 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 76 | "dev": true 77 | }, 78 | "escape-string-regexp": { 79 | "version": "1.0.5", 80 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 81 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 82 | "dev": true 83 | }, 84 | "fs.realpath": { 85 | "version": "1.0.0", 86 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 87 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 88 | "dev": true 89 | }, 90 | "get-func-name": { 91 | "version": "2.0.0", 92 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 93 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 94 | "dev": true 95 | }, 96 | "glob": { 97 | "version": "7.1.1", 98 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 99 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 100 | "dev": true 101 | }, 102 | "graceful-readlink": { 103 | "version": "1.0.1", 104 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 105 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 106 | "dev": true 107 | }, 108 | "growl": { 109 | "version": "1.9.2", 110 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 111 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 112 | "dev": true 113 | }, 114 | "has-flag": { 115 | "version": "1.0.0", 116 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 117 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 118 | "dev": true 119 | }, 120 | "inflight": { 121 | "version": "1.0.6", 122 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 123 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 124 | "dev": true 125 | }, 126 | "inherits": { 127 | "version": "2.0.3", 128 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 129 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 130 | "dev": true 131 | }, 132 | "json3": { 133 | "version": "3.3.2", 134 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 135 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 136 | "dev": true 137 | }, 138 | "lodash._baseassign": { 139 | "version": "3.2.0", 140 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 141 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 142 | "dev": true 143 | }, 144 | "lodash._basecopy": { 145 | "version": "3.0.1", 146 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 147 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 148 | "dev": true 149 | }, 150 | "lodash._basecreate": { 151 | "version": "3.0.3", 152 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 153 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 154 | "dev": true 155 | }, 156 | "lodash._getnative": { 157 | "version": "3.9.1", 158 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 159 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 160 | "dev": true 161 | }, 162 | "lodash._isiterateecall": { 163 | "version": "3.0.9", 164 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 165 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 166 | "dev": true 167 | }, 168 | "lodash.create": { 169 | "version": "3.1.1", 170 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 171 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 172 | "dev": true 173 | }, 174 | "lodash.isarguments": { 175 | "version": "3.1.0", 176 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 177 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 178 | "dev": true 179 | }, 180 | "lodash.isarray": { 181 | "version": "3.0.4", 182 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 183 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 184 | "dev": true 185 | }, 186 | "lodash.keys": { 187 | "version": "3.1.2", 188 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 189 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 190 | "dev": true 191 | }, 192 | "minimatch": { 193 | "version": "3.0.4", 194 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 195 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 196 | "dev": true 197 | }, 198 | "minimist": { 199 | "version": "0.0.8", 200 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 201 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 202 | "dev": true 203 | }, 204 | "mkdirp": { 205 | "version": "0.5.1", 206 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 207 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 208 | "dev": true 209 | }, 210 | "mocha": { 211 | "version": "3.4.2", 212 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", 213 | "integrity": "sha1-0O9NMyEm2/GNDWQMmzgt1IvpdZQ=", 214 | "dev": true 215 | }, 216 | "ms": { 217 | "version": "0.7.2", 218 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 219 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 220 | "dev": true 221 | }, 222 | "once": { 223 | "version": "1.4.0", 224 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 225 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 226 | "dev": true 227 | }, 228 | "path-is-absolute": { 229 | "version": "1.0.1", 230 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 231 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 232 | "dev": true 233 | }, 234 | "pathval": { 235 | "version": "1.1.0", 236 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 237 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 238 | "dev": true 239 | }, 240 | "supports-color": { 241 | "version": "3.1.2", 242 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 243 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 244 | "dev": true 245 | }, 246 | "type-detect": { 247 | "version": "4.0.3", 248 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", 249 | "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", 250 | "dev": true 251 | }, 252 | "wrappy": { 253 | "version": "1.0.2", 254 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 255 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 256 | "dev": true 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "mocha test/*" 4 | }, 5 | "devDependencies": { 6 | "chai": "*", 7 | "mocha": "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/app/metadata_cache_tests.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | require('../../app/metadata_cache'); 17 | require('../../app/log'); 18 | var chai = require('chai'); 19 | var assert = chai.assert; 20 | 21 | // Declare global `log` because of implicit dependency in MetadataCache 22 | global.log = new JsLogger(); 23 | 24 | describe('MetadataCache', function() { 25 | 26 | describe("Inserting and deleting value", function() { 27 | var cache = new MetadataCache(); 28 | const fileSystemId = "smb://127.0.0.1/testshare"; 29 | const directoryPath = "/tmp"; 30 | const entryName = "test.jpg"; 31 | var entry = { 32 | fullPath : "", 33 | name : entryName, 34 | isDirectory : false 35 | }; 36 | var date = new Date(); 37 | const requestEntryPath = directoryPath + "/" + entry.name; 38 | 39 | it("should be empty", function() { 40 | var emptyItem = cache.lookupMetadata(fileSystemId, requestEntryPath, 41 | date.getTime()); 42 | assert.isNull(emptyItem); 43 | }); 44 | 45 | it("should insert a value", function() { 46 | cache.cacheDirectoryContents(fileSystemId, directoryPath, [], 47 | date.getTime()); 48 | cache.updateMetadata(fileSystemId, requestEntryPath, entry); 49 | var item = cache.lookupMetadata(fileSystemId, requestEntryPath); 50 | assert(item); 51 | assert.equal(item.name, entryName); 52 | }); 53 | 54 | it("should delete value", function() { 55 | cache.invalidateEntry(fileSystemId, requestEntryPath); 56 | var deletedItem = cache.lookupMetadata(fileSystemId, requestEntryPath); 57 | assert.isNull(deletedItem); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /third_party/bower_components/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /third_party/nacl_sdk/common.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | // TODO(XXX): This came from NaCl SDK. Should this be moved to third_party? 5 | 6 | 7 | // Set to true when the Document is loaded IFF "test=true" is in the query 8 | // string. 9 | var isTest = false; 10 | 11 | // Set to true when loading a "Release" NaCl module, false when loading a 12 | // "Debug" NaCl module. 13 | var isRelease = true; 14 | 15 | // Javascript module pattern: 16 | // see http://en.wikipedia.org/wiki/Unobtrusive_JavaScript#Namespaces 17 | // In essence, we define an anonymous function which is immediately called and 18 | // returns a new object. The new object contains only the exported definitions; 19 | // all other definitions in the anonymous function are inaccessible to external 20 | // code. 21 | var common = (function() { 22 | 23 | function isHostToolchain(tool) { 24 | return tool == 'win' || tool == 'linux' || tool == 'mac'; 25 | } 26 | 27 | /** 28 | * Return the mime type for NaCl plugin. 29 | * 30 | * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. 31 | * @return {string} The mime-type for the kind of NaCl plugin matching 32 | * the given toolchain. 33 | */ 34 | function mimeTypeForTool(tool) { 35 | // For NaCl modules use application/x-nacl. 36 | var mimetype = 'application/x-nacl'; 37 | if (isHostToolchain(tool)) { 38 | // For non-NaCl PPAPI plugins use the x-ppapi-debug/release 39 | // mime type. 40 | if (isRelease) 41 | mimetype = 'application/x-ppapi-release'; 42 | else 43 | mimetype = 'application/x-ppapi-debug'; 44 | } else if (tool == 'pnacl') { 45 | mimetype = 'application/x-pnacl'; 46 | } 47 | return mimetype; 48 | } 49 | 50 | /** 51 | * Check if the browser supports NaCl plugins. 52 | * 53 | * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. 54 | * @return {bool} True if the browser supports the type of NaCl plugin 55 | * produced by the given toolchain. 56 | */ 57 | function browserSupportsNaCl(tool) { 58 | // Assume host toolchains always work with the given browser. 59 | // The below mime-type checking might not work with 60 | // --register-pepper-plugins. 61 | if (isHostToolchain(tool)) { 62 | return true; 63 | } 64 | var mimetype = mimeTypeForTool(tool); 65 | return navigator.mimeTypes[mimetype] !== undefined; 66 | } 67 | 68 | /** 69 | * Inject a script into the DOM, and call a callback when it is loaded. 70 | * 71 | * @param {string} url The url of the script to load. 72 | * @param {Function} onload The callback to call when the script is loaded. 73 | * @param {Function} onerror The callback to call if the script fails to load. 74 | */ 75 | function injectScript(url, onload, onerror) { 76 | var scriptEl = document.createElement('script'); 77 | scriptEl.type = 'text/javascript'; 78 | scriptEl.src = url; 79 | scriptEl.onload = onload; 80 | if (onerror) { 81 | scriptEl.addEventListener('error', onerror, false); 82 | } 83 | document.head.appendChild(scriptEl); 84 | } 85 | 86 | /** 87 | * Run all tests for this example. 88 | * 89 | * @param {Object} moduleEl The module DOM element. 90 | */ 91 | function runTests(moduleEl) { 92 | console.log('runTests()'); 93 | common.tester = new Tester(); 94 | 95 | // All NaCl SDK examples are OK if the example exits cleanly; (i.e. the 96 | // NaCl module returns 0 or calls exit(0)). 97 | // 98 | // Without this exception, the browser_tester thinks that the module 99 | // has crashed. 100 | common.tester.exitCleanlyIsOK(); 101 | 102 | common.tester.addAsyncTest('loaded', function(test) { test.pass(); }); 103 | 104 | if (typeof window.addTests !== 'undefined') { 105 | window.addTests(); 106 | } 107 | 108 | common.tester.waitFor(moduleEl); 109 | common.tester.run(); 110 | } 111 | 112 | /** 113 | * Create the Native Client element as a child of the DOM element 114 | * named "listener". 115 | * 116 | * @param {string} name The name of the example. 117 | * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. 118 | * @param {string} path Directory name where .nmf file can be found. 119 | * @param {number} width The width to create the plugin. 120 | * @param {number} height The height to create the plugin. 121 | * @param {Object} attrs Dictionary of attributes to set on the module. 122 | */ 123 | function createNaClModule(name, tool, path, width, height, attrs) { 124 | var moduleEl = document.createElement('embed'); 125 | moduleEl.setAttribute('name', 'nacl_module'); 126 | moduleEl.setAttribute('id', 'nacl_module'); 127 | moduleEl.setAttribute('width', width); 128 | moduleEl.setAttribute('height', height); 129 | moduleEl.setAttribute('path', path); 130 | moduleEl.setAttribute('PS_VERBOSITY', '0'); 131 | moduleEl.setAttribute('src', path + '/' + name + '.nmf'); 132 | 133 | // Make NaCl printf got to the console 134 | //moduleEl.setAttribute('PS_STDOUT', '/dev/console1'); 135 | 136 | // Add any optional arguments 137 | if (attrs) { 138 | for (var key in attrs) { 139 | moduleEl.setAttribute(key, attrs[key]); 140 | } 141 | } 142 | 143 | var mimetype = mimeTypeForTool(tool); 144 | moduleEl.setAttribute('type', mimetype); 145 | 146 | // The element is wrapped inside a
, which has both a 'load' 147 | // and a 'message' event listener attached. This wrapping method is used 148 | // instead of attaching the event listeners directly to the element 149 | // to ensure that the listeners are active before the NaCl module 'load' 150 | // event fires. 151 | var listenerDiv = document.getElementById('listener'); 152 | listenerDiv.appendChild(moduleEl); 153 | 154 | // Request the offsetTop property to force a relayout. As of Apr 10, 2014 155 | // this is needed if the module is being loaded on a Chrome App's 156 | // background page (see crbug.com/350445). 157 | moduleEl.offsetTop; 158 | 159 | // Host plugins don't send a moduleDidLoad message. We'll fake it here. 160 | var isHost = isHostToolchain(tool); 161 | if (isHost) { 162 | window.setTimeout(function() { 163 | moduleEl.readyState = 1; 164 | moduleEl.dispatchEvent(new CustomEvent('loadstart')); 165 | moduleEl.readyState = 4; 166 | moduleEl.dispatchEvent(new CustomEvent('load')); 167 | moduleEl.dispatchEvent(new CustomEvent('loadend')); 168 | }, 100); // 100 ms 169 | } 170 | 171 | // This is code that is only used to test the SDK. 172 | if (isTest) { 173 | var loadNaClTest = function() { 174 | injectScript('nacltest.js', function() { runTests(moduleEl); }); 175 | }; 176 | 177 | // Try to load test.js for the example. Whether or not it exists, load 178 | // nacltest.js. 179 | injectScript('test.js', loadNaClTest, loadNaClTest); 180 | } 181 | } 182 | 183 | /** 184 | * Add the default "load" and "message" event listeners to the element with 185 | * id "listener". 186 | * 187 | * The "load" event is sent when the module is successfully loaded. The 188 | * "message" event is sent when the naclModule posts a message using 189 | * PPB_Messaging.PostMessage() (in C) or pp::Instance().PostMessage() (in 190 | * C++). 191 | */ 192 | function attachDefaultListeners() { 193 | var listenerDiv = document.getElementById('listener'); 194 | listenerDiv.addEventListener('load', moduleDidLoad, true); 195 | listenerDiv.addEventListener('message', handleMessage, true); 196 | listenerDiv.addEventListener('error', handleError, true); 197 | listenerDiv.addEventListener('crash', handleCrash, true); 198 | if (typeof window.attachListeners !== 'undefined') { 199 | window.attachListeners(); 200 | } 201 | } 202 | 203 | /** 204 | * Called when the NaCl module fails to load. 205 | * 206 | * This event listener is registered in createNaClModule above. 207 | */ 208 | function handleError(event) { 209 | // We can't use common.naclModule yet because the module has not been 210 | // loaded. 211 | var moduleEl = document.getElementById('nacl_module'); 212 | updateStatus('ERROR [' + moduleEl.lastError + ']'); 213 | } 214 | 215 | /** 216 | * Called when the Browser can not communicate with the Module 217 | * 218 | * This event listener is registered in attachDefaultListeners above. 219 | */ 220 | function handleCrash(event) { 221 | if (common.naclModule.exitStatus == -1) { 222 | updateStatus('CRASHED'); 223 | } else { 224 | updateStatus('EXITED [' + common.naclModule.exitStatus + ']'); 225 | } 226 | if (typeof window.handleCrash !== 'undefined') { 227 | window.handleCrash(common.naclModule.lastError); 228 | } 229 | } 230 | 231 | /** 232 | * Called when the NaCl module is loaded. 233 | * 234 | * This event listener is registered in attachDefaultListeners above. 235 | */ 236 | function moduleDidLoad() { 237 | common.naclModule = document.getElementById('nacl_module'); 238 | updateStatus('RUNNING'); 239 | 240 | if (typeof window.moduleDidLoad !== 'undefined') { 241 | window.moduleDidLoad(); 242 | } 243 | } 244 | 245 | /** 246 | * Hide the NaCl module's embed element. 247 | * 248 | * We don't want to hide by default; if we do, it is harder to determine that 249 | * a plugin failed to load. Instead, call this function inside the example's 250 | * "moduleDidLoad" function. 251 | * 252 | */ 253 | function hideModule() { 254 | // Setting common.naclModule.style.display = "None" doesn't work; the 255 | // module will no longer be able to receive postMessages. 256 | common.naclModule.style.height = '0'; 257 | } 258 | 259 | /** 260 | * Remove the NaCl module from the page. 261 | */ 262 | function removeModule() { 263 | common.naclModule.parentNode.removeChild(common.naclModule); 264 | common.naclModule = null; 265 | } 266 | 267 | /** 268 | * Return true when |s| starts with the string |prefix|. 269 | * 270 | * @param {string} s The string to search. 271 | * @param {string} prefix The prefix to search for in |s|. 272 | */ 273 | function startsWith(s, prefix) { 274 | // indexOf would search the entire string, lastIndexOf(p, 0) only checks at 275 | // the first index. See: http://stackoverflow.com/a/4579228 276 | return s.lastIndexOf(prefix, 0) === 0; 277 | } 278 | 279 | /** Maximum length of logMessageArray. */ 280 | var kMaxLogMessageLength = 20; 281 | 282 | /** An array of messages to display in the element with id "log". */ 283 | var logMessageArray = []; 284 | 285 | /** 286 | * Add a message to an element with id "log". 287 | * 288 | * This function is used by the default "log:" message handler. 289 | * 290 | * @param {string} message The message to log. 291 | */ 292 | function logMessage(message) { 293 | logMessageArray.push(message); 294 | if (logMessageArray.length > kMaxLogMessageLength) logMessageArray.shift(); 295 | 296 | document.getElementById('log').textContent = logMessageArray.join('\n'); 297 | console.log(message); 298 | } 299 | 300 | /** 301 | */ 302 | var defaultMessageTypes = {'alert': alert, 'log': logMessage}; 303 | 304 | /** 305 | * Called when the NaCl module sends a message to JavaScript (via 306 | * PPB_Messaging.PostMessage()) 307 | * 308 | * This event listener is registered in createNaClModule above. 309 | * 310 | * @param {Event} message_event A message event. message_event.data contains 311 | * the data sent from the NaCl module. 312 | */ 313 | function handleMessage(message_event) { 314 | if (typeof message_event.data === 'string') { 315 | for (var type in defaultMessageTypes) { 316 | if (defaultMessageTypes.hasOwnProperty(type)) { 317 | if (startsWith(message_event.data, type + ':')) { 318 | func = defaultMessageTypes[type]; 319 | func(message_event.data.slice(type.length + 1)); 320 | return; 321 | } 322 | } 323 | } 324 | } 325 | 326 | if (typeof window.handleMessage !== 'undefined') { 327 | window.handleMessage(message_event); 328 | return; 329 | } 330 | 331 | logMessage('Unhandled message: ' + message_event.data); 332 | } 333 | 334 | /** 335 | * Called when the DOM content has loaded; i.e. the page's document is fully 336 | * parsed. At this point, we can safely query any elements in the document via 337 | * document.querySelector, document.getElementById, etc. 338 | * 339 | * @param {string} name The name of the example. 340 | * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. 341 | * @param {string} path Directory name where .nmf file can be found. 342 | * @param {number} width The width to create the plugin. 343 | * @param {number} height The height to create the plugin. 344 | * @param {Object} attrs Optional dictionary of additional attributes. 345 | */ 346 | function domContentLoaded(name, tool, path, width, height, attrs) { 347 | // If the page loads before the Native Client module loads, then set the 348 | // status message indicating that the module is still loading. Otherwise, 349 | // do not change the status message. 350 | updateStatus('Page loaded.'); 351 | if (!browserSupportsNaCl(tool)) { 352 | updateStatus('Browser does not support NaCl (' + tool + 353 | '), or NaCl is disabled'); 354 | } else if (common.naclModule == null) { 355 | updateStatus('Creating embed: ' + tool); 356 | 357 | // We use a non-zero sized embed to give Chrome space to place the bad 358 | // plug-in graphic, if there is a problem. 359 | width = typeof width !== 'undefined' ? width : 200; 360 | height = typeof height !== 'undefined' ? height : 200; 361 | attachDefaultListeners(); 362 | createNaClModule(name, tool, path, width, height, attrs); 363 | } else { 364 | // It's possible that the Native Client module onload event fired 365 | // before the page's onload event. In this case, the status message 366 | // will reflect 'SUCCESS', but won't be displayed. This call will 367 | // display the current message. 368 | updateStatus('Waiting.'); 369 | } 370 | } 371 | 372 | /** Saved text to display in the element with id 'statusField'. */ 373 | var statusText = 'NO-STATUSES'; 374 | 375 | /** 376 | * Set the global status message. If the element with id 'statusField' 377 | * exists, then set its HTML to the status message as well. 378 | * 379 | * @param {string} opt_message The message to set. If null or undefined, then 380 | * set element 'statusField' to the message from the last call to 381 | * updateStatus. 382 | */ 383 | function updateStatus(opt_message) { 384 | if (opt_message) { 385 | statusText = opt_message; 386 | } 387 | var statusField = document.getElementById('statusField'); 388 | if (statusField) { 389 | statusField.innerHTML = statusText; 390 | } 391 | } 392 | 393 | // The symbols to export. 394 | return { 395 | /** A reference to the NaCl module, once it is loaded. */ 396 | naclModule: null, 397 | 398 | attachDefaultListeners: attachDefaultListeners, 399 | domContentLoaded: domContentLoaded, 400 | createNaClModule: createNaClModule, 401 | hideModule: hideModule, 402 | removeModule: removeModule, 403 | logMessage: logMessage, 404 | updateStatus: updateStatus 405 | }; 406 | 407 | }()); 408 | 409 | // Listen for the DOM content to be loaded. This event is fired when parsing of 410 | // the page's document has finished. 411 | document.addEventListener('DOMContentLoaded', function() { 412 | console.log('In common.js - DOMContentLoaded'); 413 | var body = document.body; 414 | 415 | // The data-* attributes on the body can be referenced via body.dataset. 416 | if (body.dataset) { 417 | var loadFunction; 418 | if (!body.dataset.customLoad) { 419 | loadFunction = common.domContentLoaded; 420 | } else if (typeof window.domContentLoaded !== 'undefined') { 421 | loadFunction = window.domContentLoaded; 422 | } 423 | 424 | // From https://developer.mozilla.org/en-US/docs/DOM/window.location 425 | var searchVars = {}; 426 | if (window.location.search.length > 1) { 427 | var pairs = window.location.search.substr(1).split('&'); 428 | for (var key_ix = 0; key_ix < pairs.length; key_ix++) { 429 | var keyValue = pairs[key_ix].split('='); 430 | searchVars[unescape(keyValue[0])] = 431 | keyValue.length > 1 ? unescape(keyValue[1]) : ''; 432 | } 433 | } 434 | 435 | if (loadFunction) { 436 | var toolchains = body.dataset.tools.split(' '); 437 | var configs = body.dataset.configs.split(' '); 438 | 439 | var attrs = {}; 440 | if (body.dataset.attrs) { 441 | var attr_list = body.dataset.attrs.split(' '); 442 | for (var key in attr_list) { 443 | var attr = attr_list[key].split('='); 444 | var key = attr[0]; 445 | var value = attr[1]; 446 | attrs[key] = value; 447 | } 448 | } 449 | 450 | var tc = toolchains.indexOf(searchVars.tc) !== -1 ? searchVars.tc : 451 | toolchains[0]; 452 | 453 | // If the config value is included in the search vars, use that. 454 | // Otherwise default to Release if it is valid, or the first value if 455 | // Release is not valid. 456 | if (configs.indexOf(searchVars.config) !== -1) 457 | var config = searchVars.config; 458 | else if (configs.indexOf('Release') !== -1) 459 | var config = 'Release'; 460 | else 461 | var config = configs[0]; 462 | 463 | var pathFormat = body.dataset.path; 464 | var path = pathFormat.replace('{tc}', tc).replace('{config}', config); 465 | 466 | isTest = searchVars.test === 'true'; 467 | isRelease = path.toLowerCase().indexOf('release') != -1; 468 | 469 | loadFunction(body.dataset.name, tc, path, body.dataset.width, 470 | body.dataset.height, attrs); 471 | } 472 | } 473 | }); 474 | -------------------------------------------------------------------------------- /tools/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | # Format all the files. 19 | clang-format -i nacl/*.cc 20 | clang-format -i nacl/*.h 21 | clang-format -i app/*.js 22 | -------------------------------------------------------------------------------- /tools/to_temp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 19 | SOURCE_PATH="${THIS_DIR}/../app" 20 | TEMP_DIR=$(mktemp -d) 21 | PACKAGE_NAME="samba_fsp_app" 22 | PACKAGE_FILE="${PACKAGE_NAME}.zip" 23 | LOCAL_OUTPUT_PATH="${TEMP_DIR}/${PACKAGE_FILE}" 24 | 25 | echo Zipping up the extension: 26 | echo From: $SOURCE_PATH 27 | echo To : $LOCAL_OUTPUT_PATH 28 | 29 | pushd $SOURCE_PATH 30 | echo Zippppping 31 | zip -r $LOCAL_OUTPUT_PATH . 32 | popd 33 | 34 | echo Zip Location: $LOCAL_OUTPUT_PATH 35 | 36 | echo Done. 37 | --------------------------------------------------------------------------------