dart-backend / angel

A polished, production-ready backend framework in Dart for the VM, AOT, and Flutter.
https://github.com/dukefirehawk/angel
BSD 3-Clause "New" or "Revised" License
173 stars 21 forks source link

How to remove "x-frame-options" header from files returned by VirtualDirectory Middleware from angel3_static #72

Closed insinfo closed 2 months ago

insinfo commented 2 years ago

How to remove "x-frame-options" header from files returned by VirtualDirectory Middleware from angel3_static package: ^4.1.0

image

dukefirehawk commented 2 years ago

Based on initial scanning through the code, "x-frame-options" is not explicitly set anywhere. Will have to run through debug and analysis session to see where this comes from.

thosakwe commented 2 years ago

It comes from HttpServer.defaultResponseHeaders. It's not set directly by Angel: https://api.dart.dev/stable/2.17.7/dart-io/HttpServer/defaultResponseHeaders.html

IIRC you can do something like HttpServer.defaultResponseHeaders.remove("x-frame-options");.

insinfo commented 12 months ago

@thosakwe Can I do this in the angel instance without modifying the angel package code?

thosakwe commented 11 months ago

@thosakwe Can I do this in the angel instance without modifying the angel package code?

Hi Isaque, Http.defaultResponseHeaders is a global variable, if I remember correctly. It's not dependent on Angel, so you should be able to remove x-frame-options without modifying any Angel package code.

It's been a long time since I've touched Dart/Angel code, but I think you can just do something like this in the entry point of your program:

main() async {
  // Change global settings on startup
  HttpServer.defaultResponseHeaders.remove("x-frame-options");

  var app = Angel();
  // ... Initialize app logic...
}

Best of luck, and thanks everyone for keeping the spirit of Angel alive. It means a lot to me.

insinfo commented 11 months ago

@thosakwe Thank you very much for the feedback, I saw that get defaultResponseHeaders is an instance property, it's not static, so I had to modify the angel3_hot.dart and angel3_production.dart files

angel3_hot.dart

// TODO add this
  /// Boots a shared server instance. Use this if launching multiple isolates.
  Future<HttpServer> Function(dynamic, int) _startSharedHttpServer() {    
    return (address, int port) async {
      final server =
          await HttpServer.bind(address ?? '127.0.0.1', port, shared: true);
      server.defaultResponseHeaders.remove('X-Frame-Options', 'SAMEORIGIN');    
      return Future.value(server);
    };
  }

Future<AngelHttp> _generateServer() async {
    var app = await generator();
    await Future.forEach(app.startupHooks, app.configure);
    app.optimizeForProduction();
// TODO change to _startSharedHttpServer
    final angelServer = await AngelHttp.custom(app, _startSharedHttpServer());
    return Future.value(angelServer);
  }

 /// Starts listening to requests and filesystem events.
  Future<HttpServer> startServer([address, int? port]) async {
    var isHot = true;
    _server = await _generateServer();

    if (_paths.isNotEmpty != true) {
      _logWarning(
          'You have instantiated a HotReloader without providing any filesystem paths to watch.');
    }

    bool sw(String s) {
      return Platform.executableArguments.any((ss) => ss.startsWith(s));
    }

    if (!sw('--observe') && !sw('--enable-vm-service')) {
      _logWarning(
          'You have instantiated a HotReloader without passing `--enable-vm-service` or `--observe` to the Dart VM. Hot reloading will be disabled.');
      isHot = false;
    } else {
      var info = await dev.Service.getInfo();
      var uri = info.serverUri!;
      uri = uri.replace(path: p.join(uri.path, 'ws'));
      if (uri.scheme == 'https') {
        uri = uri.replace(scheme: 'wss');
      } else {
        uri = uri.replace(scheme: 'ws');
      }
      _client = await vm.vmServiceConnectUri(uri.toString());
      _vmachine ??= await _client.getVM();
      _mainIsolate ??= _vmachine?.isolates?.first;

      if (_vmachine != null) {
        for (var isolate in _vmachine!.isolates ?? <vm.IsolateRef>[]) {
          if (isolate.id != null) {
            await _client.setIsolatePauseMode(isolate.id!,
                exceptionPauseMode: 'None');
          }
        }
      }

      await _listenToFilesystem();
    }

    _onChange.stream
        //.transform( _Debounce( Duration(seconds: 1)))
        .listen(_handleWatchEvent);

    while (_requestQueue.isNotEmpty) {
      await _handle(_requestQueue.removeFirst());
    }
    var server = _io = await HttpServer.bind(address ?? '127.0.0.1', port ?? 0);

    // TODO add this
    server.defaultResponseHeaders.remove('X-Frame-Options', 'SAMEORIGIN');   

    server.listen(handleRequest);

    // Print a Flutter-like prompt...
    if (enableHotkeys) {
      var serverUri =
          Uri(scheme: 'http', host: server.address.address, port: server.port);

      Uri? observatoryUri;
      if (isHot) {
        observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri!);
      }

      print(styleBold.wrap(
          '\n🔥  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".'));
      stdout.write('Your server is listening at: ');
      print(wrapWith('$serverUri', [cyan, styleUnderlined]));

      if (isHot) {
        stdout.write(
            'An Observatory debugger and profiler on ${Platform.operatingSystem} is available at: ');

        print(wrapWith('$observatoryUri', [cyan, styleUnderlined]));
      } else {
        stdout.write(
            'The observatory debugger and profiler are not available.\n');
      }
      print(
          'For a more detailed help message, press "h". To quit, press "q".\n');

      if (_paths.isNotEmpty) {
        print(darkGray.wrap(
            'Changes to the following path(s) will also trigger a hot reload:'));

        for (var p in _paths) {
          print(darkGray.wrap('  * $p'));
        }

        stdout.writeln();
      }

      // Listen for hotkeys
      try {
        stdin.lineMode = stdin.echoMode = false;
      } catch (_) {}

      late StreamSubscription<int> sub;

      try {
        sub = stdin.expand((l) => l).listen((ch) async {
          if (ch == $r) {
            _handleWatchEvent(
                WatchEvent(ChangeType.MODIFY, '[manual-reload]'), isHot);
          }
          if (ch == $R) {
            _logInfo('Manually restarting server...\n');
            await _killServer();
            await _server!.close();
            var addr = _io.address.address;
            var port = _io.port;
            await _io.close(force: true);
            await startServer(addr, port);
          } else if (ch == $q) {
            stdin.echoMode = stdin.lineMode = true;
            await close();
            await sub.cancel();
            exit(0);
          } else if (ch == $h) {
            print(
                'Press "r" to hot reload the Dart VM, and restart the active server.');
            print(
                'Press "R" to restart the server, WITHOUT a hot reload of the VM.');
            print('Press "q" to quit the server.');
            print('Press "h" to display this help information.');
            stdout.writeln();
          }
        });
      } catch (_) {}
    }

    return server;
  }

angel3_production.dart

// TODO add this
  /// Boots a shared server instance. Use this if launching multiple isolates.
  static Future<HttpServer> Function(dynamic, int) startSharedHttpServer() {
    return (address, int port) async {
      final server =
          await HttpServer.bind(address ?? '127.0.0.1', port, shared: true);
      server.defaultResponseHeaders.remove('X-Frame-Options', 'SAMEORIGIN');
      return Future.value(server);
    };
  }

  static Future<HttpServer> Function(dynamic, int) startSharedSecureHttpServer(
      SecurityContext securityContext) {
    return (address, int port) async {
      final server = await HttpServer.bindSecure(
          address ?? '127.0.0.1', port, securityContext,
          shared: true);
      server.defaultResponseHeaders.remove('X-Frame-Options', 'SAMEORIGIN');
      return Future.value(server);
    };
  }

 static void isolateMain(RunnerArgsWithId argsWithId) {
    var args = argsWithId.args;
    hierarchicalLoggingEnabled = false;

    var zone = Zone.current.fork(specification: ZoneSpecification(
      print: (self, parent, zone, msg) {
        args.loggingSendPort.send(LogRecord(Level.INFO, msg, args.loggerName));
      },
    ));

    zone.run(() async {
      var client =
          pub_sub.IsolateClient('client${argsWithId.id}', args.pubSubSendPort);

      var app = Angel(reflector: args.reflector)
        ..container.registerSingleton<pub_sub.Client>(client)
        ..container.registerSingleton(InstanceInfo(id: argsWithId.id));

      app.shutdownHooks.add((_) => client.close());

      await app.configure(args.configureServer);

      app.logger = Logger(args.loggerName)
        ..onRecord.listen((rec) => Runner.handleLogRecord(rec, args.options));

      AngelHttp http;
      late SecurityContext securityContext;
      Uri serverUrl;

      if (args.options.ssl || args.options.http2) {
        securityContext = SecurityContext();
        if (args.options.certificateFile != null) {
          securityContext.useCertificateChain(args.options.certificateFile!,
              password: args.options.certificatePassword);
        }

        if (args.options.keyFile != null) {
          securityContext.usePrivateKey(args.options.keyFile!,
              password: args.options.keyPassword);
        }
      }

      if (args.options.ssl) {
        // TODO change to startSharedSecureHttpServer
        http = AngelHttp.custom(
            app, startSharedSecureHttpServer(securityContext),
            useZone: args.options.useZone);
      } else {
        // TODO change to startSharedHttpServer
        http = AngelHttp.custom(app, startSharedHttpServer(),
            useZone: args.options.useZone);
      }

      Driver driver;

      if (args.options.http2) {
        securityContext.setAlpnProtocols(['h2'], true);
        var http2 = AngelHttp2.custom(app, securityContext, startSharedHttp2,
            useZone: args.options.useZone);
        http2.onHttp1.listen(http.handleRequest);
        driver = http2;
      } else {
        driver = http;
      }

      await driver.startServer(args.options.hostname, args.options.port);
      serverUrl = driver.uri;
      if (args.options.ssl || args.options.http2) {
        serverUrl = serverUrl.replace(scheme: 'https');
      }
      print('Instance #${argsWithId.id} listening at $serverUrl');
    });
  }
dukefirehawk commented 10 months ago

angel3_production:8.1.0 now can support passing addition parameters to add or remove headers. See example in the updated README.md. For angel3_hot, defaultResponseHeaders method is already accessible. See example in the updated README.md.

insinfo commented 10 months ago

@duquefirehawk Thank you very much for your commitment and dedication to this project