2a62d9944542018a9e499e54ba253d0185afd591
[platform/core/api/webapi-plugins.git] / src / filesystem / js / common.js
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 var privUtils_ = xwalk.utils;
18 var privilege_ = privUtils_.privilege;
19 var type_ = privUtils_.type;
20 var converter_ = privUtils_.converter;
21 var validator_ = privUtils_.validator;
22 var types_ = validator_.Types;
23 var native_ = new xwalk.utils.NativeManager(extension);
24
25 /*
26  * Create new array-like object of numbers: UTF-16 char codes from string.
27  * As type pass Array, Uint8Array, etc.
28  * Useful for passing data through crosswalk.
29  */
30 function StringToArray(str, type) {
31     var len = str.length;
32     var output = new type(len);
33     for (var i = 0; i < len; i++) {
34         output[i] = str.charCodeAt(i);
35     }
36     return output;
37 }
38
39 /*
40  * Pass array-like object of numbers (Array, Uint8Array, etc.), returns string.
41  * Each char has codepoint equal to value from array cropped with & 0xFF
42  * Useful for passing data through crosswalk.
43  */
44 function ArrayToString(data) {
45     var output = '';
46     var len = data.length;
47     for (var i = 0; i < len; i++) {
48         output += String.fromCharCode(data[i] & 0xff); // conversion to octet
49     }
50     return output;
51 }
52
53 function SetReadOnlyProperty(obj, n, v) {
54     Object.defineProperty(obj, n, { value: v, writable: false });
55 }
56
57 var FileSystemStorageType = { INTERNAL: 'INTERNAL', EXTERNAL: 'EXTERNAL' };
58
59 var FileSystemStorageState = {
60     MOUNTED: 'MOUNTED',
61     REMOVED: 'REMOVED',
62     UNMOUNTABLE: 'UNMOUNTABLE'
63 };
64
65 var FileMode = { a: 'a', r: 'r', rw: 'rw', rwo: 'rwo', w: 'w' };
66
67 var BaseSeekPosition = { BEGIN: 'BEGIN', CURRENT: 'CURRENT', END: 'END' };
68
69 var tizen24home = '/opt/usr/media';
70
71 // this variable need to match same variable in
72 // common/filesystem/filesystem_provider_storage.cc
73 var kVirtualRootImages = 'images';
74
75 var commonFS_ = (function() {
76     var cacheReady = false;
77     var listenerRegistered = false;
78     var cacheVirtualToReal = {};
79     var cacheStorages = [];
80     var uriPrefix = 'file://';
81     // special condition for previous versions paths
82     // (global paths usage issue workaround)
83     var isAppForEarlierVersion = privUtils_.isAppVersionEarlierThan('3.0');
84     var homeDir = undefined;
85
86     function clearCache() {
87         cacheVirtualToReal = {};
88         cacheStorages = [];
89         cacheReady = false;
90     }
91
92     // initalize home directory for correct mapping global paths from tizen 2.4
93     // (global paths usage issue workaround)
94     function initHomeDir(aPath) {
95         if (homeDir || !isAppForEarlierVersion) {
96             return;
97         }
98         var imagesPath = cacheVirtualToReal[kVirtualRootImages].path;
99
100         if (imagesPath[imagesPath.length - 1] === '/') {
101             homeDir = imagesPath
102                 .split('/')
103                 .slice(0, -2)
104                 .join('/');
105         } else {
106             homeDir = imagesPath
107                 .split('/')
108                 .slice(0, -1)
109                 .join('/');
110         }
111     }
112
113     function initCache() {
114         if (cacheReady) {
115             return;
116         }
117         var result = native_.callSync('Filesystem_fetchAllStorages', {});
118         if (native_.isFailure(result)) {
119             throw native_.getErrorObject(result);
120         }
121         var virtualRoots = native_.getResultObject(result);
122
123         for (var i = 0; i < virtualRoots.length; ++i) {
124             cacheVirtualToReal[virtualRoots[i].name] = {
125                 path: virtualRoots[i].path,
126                 label: virtualRoots[i].name,
127                 type: virtualRoots[i].type,
128                 state: virtualRoots[i].state
129             };
130         }
131         // initalize home directory for correct mapping global paths from tizen 2.4
132         // (global paths usage issue workaround)
133         initHomeDir();
134
135         var result = native_.callSync('FileSystemManager_fetchStorages', {});
136         if (native_.isFailure(result)) {
137             throw native_.getErrorObject(result);
138         }
139
140         var storages = native_.getResultObject(result);
141         for (var i = 0; i < storages.length; ++i) {
142             cacheStorages.push({
143                 path: storages[i].path,
144                 label: storages[i].name,
145                 type: storages[i].type,
146                 state: storages[i].state,
147                 storage_id: storages[i].storage_id
148             });
149         }
150
151         if (!listenerRegistered) {
152             try {
153                 tizen.filesystem.addStorageStateChangeListener(function() {
154                     clearCache();
155                 });
156                 listenerRegistered = true;
157             } catch (e) {
158                 privUtils_.log(
159                     'Failed to register storage change listener, ' +
160                         'storage information may be corrupted: ' +
161                         e.message
162                 );
163             }
164         }
165
166         cacheReady = true;
167     }
168
169     function mergeMultipleSlashes(str) {
170         var retStr = str.replace(/(^(file\:\/\/\/)|^(file\:\/\/)|\/)\/{0,}/g, '$1');
171         return retStr;
172     }
173
174     function removeDotsFromPath(str) {
175         if (str === undefined) {
176             return str;
177         }
178
179         var _pathTokens = str.split('/');
180         var _correctDir = [];
181         var _fileRealPath = _pathTokens[0];
182         _correctDir.push(_pathTokens[0]);
183         for (var i = 1; i < _pathTokens.length; ++i) {
184             if (_pathTokens[i] == '..') {
185                 if (_fileRealPath == '') {
186                     _fileRealPath = undefined;
187                     break;
188                 }
189                 var _lastDir = _correctDir.pop();
190                 _fileRealPath = _fileRealPath.substring(
191                     0,
192                     _fileRealPath.length - _lastDir.length - 1
193                 );
194             } else if (_pathTokens[i] != '.') {
195                 _fileRealPath += '/' + _pathTokens[i];
196                 _correctDir.push(_pathTokens[i]);
197             }
198         }
199         return _fileRealPath;
200     }
201
202     function checkPathWithoutDots(aPath) {
203         if (-1 !== aPath.indexOf('/../')) {
204             return false;
205         }
206         if (-1 !== aPath.indexOf('/./')) {
207             return false;
208         }
209         // check if path ends with '/.' or '/..'
210         if (aPath.match(/\/\.\.?$/)) {
211             return false;
212         }
213         // check if path starts with './' or '../'
214         if (aPath.match(/^\.\.?\//)) {
215             return false;
216         }
217         return true;
218     }
219
220     function convertForEarlierVersionPath(aPath) {
221         if (isAppForEarlierVersion) {
222             if (aPath && aPath.indexOf(tizen24home) === 0) {
223                 privUtils_.log('Converting 2.4 style path to 3.0 pattern');
224                 aPath = homeDir + aPath.substr(tizen24home.length);
225             }
226         }
227         return aPath;
228     }
229
230     function toRealPath(aPath) {
231         var _fileRealPath = '';
232
233         aPath = mergeMultipleSlashes(aPath);
234
235         if (aPath.indexOf(uriPrefix) === 0) {
236             _fileRealPath = aPath.substr(uriPrefix.length);
237         } else if (aPath[0] !== '/') {
238             // virtual path
239             initCache();
240
241             var _pathTokens = aPath.split('/');
242
243             if (cacheVirtualToReal[_pathTokens[0]]) {
244                 _fileRealPath = cacheVirtualToReal[_pathTokens[0]].path;
245                 for (var i = 1; i < _pathTokens.length; ++i) {
246                     _fileRealPath += '/' + _pathTokens[i];
247                 }
248             } else {
249                 // If path token is not present in cache then it is invalid
250                 _fileRealPath = undefined;
251                 // check storages
252                 for (var j = 0; j < cacheStorages.length; ++j) {
253                     if (cacheStorages[j].label === _pathTokens[0]) {
254                         _fileRealPath = cacheStorages[j].path;
255                         for (var i = 1; i < _pathTokens.length; ++i) {
256                             _fileRealPath += '/' + _pathTokens[i];
257                         }
258                         break;
259                     }
260                 }
261             }
262         } else {
263             _fileRealPath = aPath;
264         }
265         // removeDotsFromPath execution here, results with '.' and '..' beeing
266         // supported in paths, next methods throw an error when getting argument
267         // with '.' or '..' in it
268         // (see commonFS_.checkPathWithoutDots() method)
269         _fileRealPath = removeDotsFromPath(_fileRealPath);
270         // convert path to be compatibile with previous version of Tizen
271         // (global paths usage issue workaround)
272         _fileRealPath = convertForEarlierVersionPath(_fileRealPath);
273         // if path is valid try to cut last '/' if it is present
274         if (_fileRealPath) {
275             _fileRealPath = mergeMultipleSlashes(_fileRealPath);
276         }
277         return _fileRealPath;
278     }
279
280     function toVirtualPath(aPath) {
281         aPath = mergeMultipleSlashes(aPath);
282         var _virtualPath = aPath;
283
284         if (_virtualPath.indexOf(uriPrefix) === 0) {
285             _virtualPath = _virtualPath.substr(uriPrefix.length);
286         }
287
288         initCache();
289         // find virtual root with longest path
290         var foundLength = 0;
291         var foundVirtualRoot;
292         var foundVirtualPath;
293         for (var virtual_root in cacheVirtualToReal) {
294             var real_root_path = cacheVirtualToReal[virtual_root].path;
295             if (_virtualPath.indexOf(real_root_path, 0) === 0) {
296                 var currentLength = real_root_path.length;
297                 if (currentLength > foundLength) {
298                     foundLength = currentLength;
299                     foundVirtualRoot = virtual_root;
300                     foundVirtualPath = real_root_path;
301                 }
302             }
303         }
304         if (foundLength != 0) {
305             return _virtualPath.replace(foundVirtualPath, foundVirtualRoot);
306         }
307         return _virtualPath;
308     }
309
310     function getFileInfo(aStatObj, secondIter, aMode) {
311         var _result = {},
312             _pathTokens,
313             _fileParentPath = '',
314             i;
315         var aPath = toVirtualPath(aStatObj.path);
316
317         _result.readOnly = aStatObj.readOnly;
318         _result.isFile = aStatObj.isFile;
319         _result.isDirectory = aStatObj.isDirectory;
320         _result.created = new Date(aStatObj.ctime * 1000);
321         _result.modified = new Date(aStatObj.mtime * 1000);
322         _result.fullPath = aPath;
323         _result.fileSize = aStatObj.size;
324         _result.mode = aMode;
325         if (_result.isDirectory) {
326             try {
327                 _result.length = aStatObj.nlink;
328             } catch (err) {
329                 _result.length = 0;
330             }
331         } else {
332             _result.length = undefined;
333         }
334
335         _pathTokens = aPath.split('/');
336         if (_pathTokens.length > 1) {
337             var last = _pathTokens.length - 1;
338             var lastToken = '';
339             if (_pathTokens[last] === '') {
340                 // 'abc/d/e/' case with trailing '/' sign
341                 last = _pathTokens.length - 2;
342                 lastToken = '/';
343             }
344             for (i = 0; i < last; ++i) {
345                 _fileParentPath += _pathTokens[i] + '/';
346             }
347             if (last > 0) {
348                 _result.path = _fileParentPath;
349                 _result.name = secondIter
350                     ? _pathTokens[last]
351                     : _pathTokens[last] + lastToken;
352                 _result.parent = secondIter ? null : _fileParentPath;
353             } else {
354                 // '/' dir case
355                 _result.path = _pathTokens[last] + lastToken;
356                 _result.name = '';
357                 _result.parent = secondIter ? null : _fileParentPath;
358             }
359         } else {
360             _result.parent = null;
361             _result.path = aPath;
362             _result.name = '';
363         }
364         return _result;
365     }
366
367     function isLocationAllowed(aPath) {
368         if (!aPath) {
369             return false;
370         }
371         initCache();
372         if (aPath.indexOf(cacheVirtualToReal.ringtones.path) === 0) {
373             return false;
374         }
375         if (aPath.indexOf(cacheVirtualToReal['wgt-package'].path) === 0) {
376             return false;
377         }
378
379         return true;
380     }
381
382     function toCanonicalPath(path) {
383         var result = native_.callSync('FileSystemManager_getCanonicalPath', {
384             path: path
385         });
386         if (native_.isFailure(result)) {
387             throw native_.getErrorObject(result);
388         }
389
390         return native_.getResultObject(result);
391     }
392
393     function f_isSubDir(fullPathToCheck, fullPath) {
394         var fullCanonicalPathToCheck = toCanonicalPath(toRealPath(fullPathToCheck));
395         var fullCanonicalPath = toCanonicalPath(toRealPath(fullPath));
396
397         if (fullCanonicalPathToCheck === fullCanonicalPath) {
398             return false;
399         }
400
401         return fullCanonicalPathToCheck.indexOf(fullCanonicalPath) === 0;
402     }
403
404     function f_isCorrectRelativePath(relativePath) {
405         return (
406             0 !== relativePath.indexOf('/') &&
407             0 !== relativePath.indexOf('\\') &&
408             -1 === relativePath.indexOf('?') &&
409             -1 === relativePath.indexOf('*') &&
410             -1 === relativePath.indexOf(':') &&
411             -1 === relativePath.indexOf('"') &&
412             -1 === relativePath.indexOf('<') &&
413             -1 === relativePath.indexOf('>')
414         );
415     }
416
417     function cloneStorage(storage) {
418         return { label: storage.label, type: storage.type, state: storage.state };
419     }
420
421     function getStorage(label) {
422         initCache();
423         for (var i = 0; i < cacheStorages.length; ++i) {
424             if (cacheStorages[i].label === label) {
425                 return cloneStorage(cacheStorages[i]);
426             }
427         }
428
429         for (var key in cacheVirtualToReal) {
430             if (cacheVirtualToReal.hasOwnProperty(key)) {
431                 if (cacheVirtualToReal[key].label === label) {
432                     return cloneStorage(cacheVirtualToReal[key]);
433                 }
434             }
435         }
436
437         return null;
438     }
439
440     function getAllStorages() {
441         var ret = [];
442         initCache();
443         for (var i = 0; i < cacheStorages.length; ++i) {
444             ret.push(cloneStorage(cacheStorages[i]));
445         }
446
447         for (var key in cacheVirtualToReal) {
448             if (cacheVirtualToReal.hasOwnProperty(key)) {
449                 ret.push(cloneStorage(cacheVirtualToReal[key]));
450             }
451         }
452
453         return ret;
454     }
455
456     return {
457         clearCache: clearCache,
458         checkPathWithoutDots: checkPathWithoutDots,
459         toRealPath: toRealPath,
460         toVirtualPath: toVirtualPath,
461         getFileInfo: getFileInfo,
462         isLocationAllowed: isLocationAllowed,
463         f_isSubDir: f_isSubDir,
464         f_isCorrectRelativePath: f_isCorrectRelativePath,
465         getStorage: getStorage,
466         getAllStorages: getAllStorages,
467         mergeMultipleSlashes: mergeMultipleSlashes
468     };
469 })();