| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of dart._vmservice; |
| |
| String _encodeDevFSDisabledError(Message message) => |
| encodeRpcError(message, kFeatureDisabled, |
| details: 'DevFS is not supported by this Dart implementation'); |
| |
| String _encodeFileSystemAlreadyExistsError(Message message, String fsName) => |
| encodeRpcError(message, kFileSystemAlreadyExists, |
| details: "${message.method}: file system '${fsName}' already exists"); |
| |
| String _encodeFileSystemDoesNotExistError(Message message, String fsName) => |
| encodeRpcError(message, kFileSystemDoesNotExist, |
| details: "${message.method}: file system '${fsName}' does not exist"); |
| |
| class _FileSystem { |
| _FileSystem(this.name, this.uri); |
| |
| final String name; |
| final Uri uri; |
| |
| Uri? resolvePath(String path) { |
| if (path.startsWith('/')) { |
| path = path.substring(1); |
| } |
| if (path.isEmpty) { |
| return null; |
| } |
| Uri pathUri; |
| try { |
| pathUri = Uri.file(path); |
| // ignore: unused_catch_clause |
| } on FormatException catch (_) { |
| return null; |
| } |
| |
| return resolve(pathUri); |
| } |
| |
| Uri? resolve(Uri pathUri) { |
| try { |
| // Make sure that this pathUri can be converted to a file path. |
| pathUri.toFilePath(); |
| // ignore: unused_catch_clause |
| } on UnsupportedError catch (_) { |
| return null; |
| } |
| |
| final resolvedUri = uri.resolveUri(pathUri); |
| if (!resolvedUri.toString().startsWith(uri.toString())) { |
| // Resolved uri must be within the filesystem's base uri. |
| return null; |
| } |
| return resolvedUri; |
| } |
| |
| Map<String, String> toMap() => { |
| 'type': 'FileSystem', |
| 'name': name, |
| 'uri': uri.toString(), |
| }; |
| } |
| |
| class DevFS { |
| DevFS(); |
| |
| final _fsMap = <String, _FileSystem>{}; |
| |
| final _rpcNames = <String>{ |
| '_listDevFS', |
| '_createDevFS', |
| '_deleteDevFS', |
| '_readDevFSFile', |
| '_writeDevFSFile', |
| '_writeDevFSFiles', |
| '_listDevFSFiles', |
| }; |
| |
| void cleanup() { |
| final deleteDir = VMServiceEmbedderHooks.deleteDir; |
| if (deleteDir == null) { |
| return; |
| } |
| final deletions = <Future>[ |
| for (final fs in _fsMap.values) deleteDir(fs.uri), |
| ]; |
| Future.wait(deletions); |
| _fsMap.clear(); |
| } |
| |
| bool shouldHandleMessage(Message message) => |
| _rpcNames.contains(message.method); |
| |
| Future<String> handleMessage(Message message) async { |
| switch (message.method!) { |
| case '_listDevFS': |
| return _listDevFS(message); |
| case '_createDevFS': |
| return _createDevFS(message); |
| case '_deleteDevFS': |
| return _deleteDevFS(message); |
| case '_readDevFSFile': |
| return _readDevFSFile(message); |
| case '_writeDevFSFile': |
| return _writeDevFSFile(message); |
| case '_writeDevFSFiles': |
| return _writeDevFSFiles(message); |
| case '_listDevFSFiles': |
| return _listDevFSFiles(message); |
| default: |
| return encodeRpcError(message, kInternalError, |
| details: 'Unexpected rpc ${message.method}'); |
| } |
| } |
| |
| Future<String> handlePutStream( |
| Object? fsName, Object? path, Uri? fsUri, Stream<List<int>> bytes) async { |
| // A dummy Message for error message construction. |
| final message = Message.forMethod('_writeDevFSFile'); |
| final writeStreamFile = VMServiceEmbedderHooks.writeStreamFile; |
| if (writeStreamFile == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| var fs = _fsMap[fsName]; |
| if (fs == null) { |
| return _encodeFileSystemDoesNotExistError(message, fsName); |
| } |
| Uri? uri = fsUri; |
| if (uri == null) { |
| if (path == null) { |
| return encodeMissingParamError(message, 'path'); |
| } |
| if (path is! String) { |
| return encodeInvalidParamError(message, 'path'); |
| } |
| uri = fs.resolvePath(path); |
| if (uri == null) { |
| return encodeInvalidParamError(message, 'path'); |
| } |
| } else { |
| uri = fs.resolve(uri); |
| if (uri == null) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| } |
| await writeStreamFile(uri, bytes); |
| return encodeSuccess(message); |
| } |
| |
| Future<String> _listDevFS(Message message) async { |
| final result = <String, dynamic>{}; |
| result['type'] = 'FileSystemList'; |
| result['fsNames'] = _fsMap.keys.toList(); |
| return encodeResult(message, result); |
| } |
| |
| Future<String> _createDevFS(Message message) async { |
| final createTempDir = VMServiceEmbedderHooks.createTempDir; |
| if (createTempDir == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| final fsName = message.params['fsName']; |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| _FileSystem? fs = _fsMap[fsName]; |
| if (fs != null) { |
| return _encodeFileSystemAlreadyExistsError(message, fsName); |
| } |
| final tempDir = await createTempDir(fsName); |
| fs = _FileSystem(fsName, tempDir); |
| _fsMap[fsName] = fs; |
| return encodeResult(message, fs.toMap()); |
| } |
| |
| Future<String> _deleteDevFS(Message message) async { |
| final deleteDir = VMServiceEmbedderHooks.deleteDir; |
| if (deleteDir == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| final fsName = message.params['fsName']; |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| final fs = _fsMap.remove(fsName); |
| if (fs == null) { |
| return _encodeFileSystemDoesNotExistError(message, fsName); |
| } |
| await deleteDir(fs.uri); |
| return encodeSuccess(message); |
| } |
| |
| Future<String> _readDevFSFile(Message message) async { |
| final readFile = VMServiceEmbedderHooks.readFile; |
| if (readFile == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| final fsName = message.params['fsName']; |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| final fs = _fsMap[fsName]; |
| if (fs == null) { |
| return _encodeFileSystemDoesNotExistError(message, fsName); |
| } |
| Uri? uri; |
| if (message.params['uri'] != null) { |
| try { |
| final uriParam = message.params['uri']; |
| if (uriParam is! String) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| final parsedUri = Uri.parse(uriParam); |
| uri = fs.resolve(parsedUri); |
| if (uri == null) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| } catch (e) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| } else { |
| final path = message.params['path']; |
| if (path == null) { |
| return encodeMissingParamError(message, 'path'); |
| } |
| if (path is! String) { |
| return encodeInvalidParamError(message, 'path'); |
| } |
| uri = fs.resolvePath(path); |
| if (uri == null) { |
| return encodeInvalidParamError(message, 'path'); |
| } |
| } |
| try { |
| final bytes = await readFile(uri); |
| final result = {'type': 'FSFile', 'fileContents': base64.encode(bytes)}; |
| return encodeResult(message, result); |
| } catch (e) { |
| return encodeRpcError(message, kFileDoesNotExist, |
| details: '_readDevFSFile: $e'); |
| } |
| } |
| |
| Future<String> _writeDevFSFile(Message message) async { |
| final writeFile = VMServiceEmbedderHooks.writeFile; |
| if (writeFile == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| final fsName = message.params['fsName']; |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| final fs = _fsMap[fsName]; |
| if (fs == null) { |
| return _encodeFileSystemDoesNotExistError(message, fsName); |
| } |
| Uri? uri; |
| if (message.params['uri'] != null) { |
| try { |
| final uriParam = message.params['uri']; |
| if (uriParam is! String) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| final parsedUri = Uri.parse(uriParam); |
| uri = fs.resolve(parsedUri); |
| if (uri == null) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| } catch (e) { |
| return encodeInvalidParamError(message, 'uri'); |
| } |
| } else { |
| final path = message.params['path']; |
| if (path == null) { |
| return encodeMissingParamError(message, 'path'); |
| } |
| if (path is! String) { |
| return encodeInvalidParamError(message, 'path'); |
| } |
| uri = fs.resolvePath(path); |
| if (uri == null) { |
| return encodeInvalidParamError(message, 'path'); |
| } |
| } |
| final fileContents = message.params['fileContents']; |
| if (fileContents == null) { |
| return encodeMissingParamError(message, 'fileContents'); |
| } |
| if (fileContents is! String) { |
| return encodeInvalidParamError(message, 'fileContents'); |
| } |
| final decodedFileContents = base64.decode(fileContents); |
| |
| await writeFile(uri, decodedFileContents); |
| return encodeSuccess(message); |
| } |
| |
| Future<String> _writeDevFSFiles(Message message) async { |
| final writeFile = VMServiceEmbedderHooks.writeFile; |
| if (writeFile == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| final fsName = message.params['fsName']; |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| final fs = _fsMap[fsName]; |
| if (fs == null) { |
| return _encodeFileSystemDoesNotExistError(message, fsName); |
| } |
| final files = message.params['files']; |
| if (files == null) { |
| return encodeMissingParamError(message, 'files'); |
| } |
| if (files is! List) { |
| return encodeInvalidParamError(message, 'files'); |
| } |
| final uris = <Uri>[]; |
| for (int i = 0; i < files.length; i++) { |
| final fileInfo = files[i]; |
| if (fileInfo is! List || |
| fileInfo.length != 2 || |
| fileInfo[0] is! String || |
| fileInfo[1] is! String) { |
| return encodeRpcError(message, kInvalidParams, |
| details: "${message.method}: invalid 'files' parameter " |
| "at index ${i}: ${fileInfo}"); |
| } |
| final uri = fs.resolvePath(fileInfo[0]); |
| if (uri == null) { |
| return encodeRpcError(message, kInvalidParams, |
| details: "${message.method}: invalid 'files' parameter " |
| "at index ${i}: ${fileInfo}"); |
| } |
| uris.add(uri); |
| } |
| final pendingWrites = <Future>[]; |
| for (int i = 0; i < uris.length; i++) { |
| final decodedFileContents = base64.decode(files[i][1]); |
| pendingWrites.add(writeFile(uris[i], decodedFileContents)); |
| } |
| await Future.wait(pendingWrites); |
| return encodeSuccess(message); |
| } |
| |
| Future<String> _listDevFSFiles(Message message) async { |
| final listFiles = VMServiceEmbedderHooks.listFiles; |
| if (listFiles == null) { |
| return _encodeDevFSDisabledError(message); |
| } |
| final fsName = message.params['fsName']; |
| if (fsName == null) { |
| return encodeMissingParamError(message, 'fsName'); |
| } |
| if (fsName is! String) { |
| return encodeInvalidParamError(message, 'fsName'); |
| } |
| final fs = _fsMap[fsName]; |
| if (fs == null) { |
| return _encodeFileSystemDoesNotExistError(message, fsName); |
| } |
| final fileList = await listFiles(fs.uri); |
| // Remove any url-encoding in the filenames. |
| for (int i = 0; i < fileList.length; i++) { |
| fileList[i]['name'] = Uri.decodeFull(fileList[i]['name']); |
| } |
| final result = <String, dynamic>{'type': 'FSFileList', 'files': fileList}; |
| return encodeResult(message, result); |
| } |
| } |