Commit 7f6be612 authored by Grant's avatar Grant
Browse files

fully implement authenticated federated media

parent 79fe292e
Loading
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
import crypto from "node:crypto";
import type Express from "express";

type MultipartMixedPart = {
  headers: {
    [k: string]: string;
  };
  body: any;
};

export class MultipartMixed {
  readonly boundary = crypto.randomUUID();
  parts: MultipartMixedPart[] = [];

  add(part: MultipartMixedPart) {
    this.parts.push(part);
    return this;
  }

  send(res: Express.Response) {
    res.setHeader(
      "Content-Type",
      `multipart/mixed; boundary="${this.boundary}"`
    );
    res.send(this.toString());
  }

  toString() {
    const lines: string[] = [
      "sc07 matrix-discord-bridge multipart/mixed builder",
    ];

    for (const part of this.parts) {
      lines.push("--" + this.boundary);

      for (const [header, value] of Object.entries(part.headers)) {
        lines.push(header + ": " + value);
      }

      lines.push("");
      lines.push(part.body);
      lines.push("");
    }

    lines.push("--" + this.boundary + "--");

    return lines.join("\r\n");
  }
}
+61 −34
Original line number Diff line number Diff line
@@ -6,15 +6,20 @@ import { Signed } from "../lib/signed";
import { SignatureError } from "signed";
import { Matrix } from "../lib/matrix";
import { Router } from "./_router";
import { MultipartMixed } from "../lib/MultipartMixed";

export class MediaRoutes extends Router {
  override setup(router: e.Router) {
    router.get(
      "/_matrix/media/v3/download/:hostname/:name/:filename?",
      this.handleMedia.bind(this)
      this.handleLegacyMedia.bind(this)
    );
    router.get(
      "/_matrix/client/v1/media/download/:hostname/:name/:filename?",
      this.handleLegacyMedia.bind(this)
    );
    router.get(
      "/_matrix/federation/v1/media/download/:name",
      this.handleMedia.bind(this)
    );

@@ -50,21 +55,12 @@ export class MediaRoutes extends Router {
    );
  }

  private async handleMedia(
    req: e.Request<{ hostname: string; name: string }>,
    res: e.Response
  ) {
    if (req.params.hostname !== process.env.HOST) {
      res.status(400).json({
        errcode: "M_ERROR",
        error: "This service does not proxy external MXCs",
      });
      return;
    }

    const parts = Buffer.from(req.params.name, "base64url")
      .toString()
      .split(",");
  /**
   * Get target url from the media_id
   * @param media_id base64 encoded media_id
   */
  private async parseURL(media_id: string): Promise<string | null> {
    const parts = Buffer.from(media_id, "base64url").toString().split(",");

    const mode = parts.shift();
    let targetURL: string;
@@ -79,10 +75,7 @@ export class MediaRoutes extends Router {
          message_id,
          attachment_id
        );
        if (!attachment) {
          res.status(404).json({});
          return;
        }
        if (!attachment) return null;

        targetURL = attachment;
        break;
@@ -93,19 +86,13 @@ export class MediaRoutes extends Router {
        const user = await Discord.client.users
          .fetch(userid)
          .catch((e) => null);
        if (!user) {
          res.status(404).json({});
          return;
        }
        if (!user) return null;

        const avatarURL = user.avatarURL({
          extension: "png",
          forceStatic: true,
        });
        if (!avatarURL) {
          res.status(404).json({});
          return;
        }
        if (!avatarURL) return null;

        targetURL = avatarURL;
        break;
@@ -116,10 +103,7 @@ export class MediaRoutes extends Router {
        const sticker = await Discord.client
          .fetchSticker(stickerid)
          .catch((e) => null);
        if (!sticker) {
          res.status(404).json({});
          return;
        }
        if (!sticker) return null;

        targetURL = sticker.url;
        break;
@@ -133,7 +117,50 @@ export class MediaRoutes extends Router {
        break;
      }
      default:
        res.status(404).json({ errcode: "M_UNKNOWN", error: "Unknown media" });
        return null;
    }

    return targetURL;
  }

  private async handleMedia(req: e.Request<{ name: string }>, res: e.Response) {
    const targetURL = await this.parseURL(req.params.name);
    if (!targetURL) {
      res.status(404).json({});
      return;
    }

    new MultipartMixed()
      .add({
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({}),
      })
      .add({
        headers: {
          Location: targetURL,
        },
        body: "",
      })
      .send(res);
  }

  private async handleLegacyMedia(
    req: e.Request<{ hostname: string; name: string }>,
    res: e.Response
  ) {
    if (req.params.hostname !== process.env.HOST) {
      res.status(400).json({
        errcode: "M_ERROR",
        error: "This service does not proxy external MXCs",
      });
      return;
    }

    const targetURL = await this.parseURL(req.params.name);
    if (!targetURL) {
      res.status(404).json({});
      return;
    }