Sanitize host before calling pm (#49591)

diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index 644bbb0..4e8e6f1 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -27,6 +27,7 @@
 import 'fuchsia_build.dart';
 import 'fuchsia_pm.dart';
 import 'fuchsia_sdk.dart';
+import 'fuchsia_utils.dart';
 import 'fuchsia_workflow.dart';
 import 'tiles_ctl.dart';
 
@@ -513,12 +514,7 @@
   void clearLogs() {}
 
   bool get ipv6 {
-    try {
-      Uri.parseIPv6Address(id);
-      return true;
-    } on FormatException {
-      return false;
-    }
+    return isIPv6Address(id);
   }
 
   /// List the ports currently running a dart observatory.
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
index 9400470..d8ca646 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
@@ -10,6 +10,7 @@
 import '../globals.dart' as globals;
 
 import 'fuchsia_sdk.dart';
+import 'fuchsia_utils.dart';
 
 /// This is a basic wrapper class for the Fuchsia SDK's `pm` tool.
 class FuchsiaPM {
@@ -109,6 +110,9 @@
     if (fuchsiaArtifacts.pm == null) {
       throwToolExit('Fuchsia pm tool not found');
     }
+    if (isIPv6Address(host.split('%').first)) {
+      host = '[${host.replaceAll('%', '%25')}]';
+    }
     final List<String> command = <String>[
       fuchsiaArtifacts.pm.path,
       'serve',
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_utils.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_utils.dart
new file mode 100644
index 0000000..af25998
--- /dev/null
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_utils.dart
@@ -0,0 +1,13 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Returns [true] if [address] is an IPv6 address.
+bool isIPv6Address(String address) {
+  try {
+    Uri.parseIPv6Address(address);
+    return true;
+  } on FormatException {
+    return false;
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_pm_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_pm_test.dart
new file mode 100644
index 0000000..a4d3d8d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_pm_test.dart
@@ -0,0 +1,80 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/process.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('FuchsiaPM', () {
+    MockFile pm;
+    MockProcessUtils mockProcessUtils;
+    MockFuchsiaArtifacts mockFuchsiaArtifacts;
+
+    setUp(() {
+      pm = MockFile();
+      when(pm.path).thenReturn('pm');
+
+      mockFuchsiaArtifacts = MockFuchsiaArtifacts();
+      when(mockFuchsiaArtifacts.pm).thenReturn(pm);
+
+      mockProcessUtils = MockProcessUtils();
+    });
+
+    testUsingContext('serve - IPv4 address', () async {
+      when(mockProcessUtils.start(any)).thenAnswer((_) {
+        return Future<Process>.value(createMockProcess());
+      });
+
+      await FuchsiaPM().serve('<repo>', '127.0.0.1', 43819);
+
+      verify(mockProcessUtils.start(<String>[
+        'pm',
+        'serve',
+        '-repo',
+        '<repo>',
+        '-l',
+        '127.0.0.1:43819',
+      ])).called(1);
+    }, overrides: <Type, Generator>{
+      FuchsiaArtifacts: () => mockFuchsiaArtifacts,
+      ProcessUtils: () => mockProcessUtils,
+    });
+
+    testUsingContext('serve - IPv6 address', () async {
+      when(mockProcessUtils.start(any)).thenAnswer((_) {
+        return Future<Process>.value(createMockProcess());
+      });
+
+      await FuchsiaPM().serve('<repo>', 'fe80::ec4:7aff:fecc:ea8f%eno2', 43819);
+
+      verify(mockProcessUtils.start(<String>[
+        'pm',
+        'serve',
+        '-repo',
+        '<repo>',
+        '-l',
+        '[fe80::ec4:7aff:fecc:ea8f%25eno2]:43819',
+      ])).called(1);
+    }, overrides: <Type, Generator>{
+      FuchsiaArtifacts: () => mockFuchsiaArtifacts,
+      ProcessUtils: () => mockProcessUtils,
+    });
+  });
+}
+
+class MockFuchsiaArtifacts extends Mock implements FuchsiaArtifacts {}
+class MockProcessUtils extends Mock implements ProcessUtils {}
+class MockFile extends Mock implements File {}
+class MockProcess extends Mock implements Process {}