From f9e878ec6065aaf8a660a5e49a1f3a980c00f140 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 24 Jul 2020 22:00:10 -0600
Subject: [PATCH] Support enough to get Complement to run the tests

---
 Complement.Dockerfile     |   3 +-
 cmd/complement_hs/main.go |  92 +++++++-
 docker/complement-run.sh  |   6 +-
 docker/complement.yaml    | 467 +-------------------------------------
 matrix/federation.go      |  68 ++++--
 5 files changed, 149 insertions(+), 487 deletions(-)

diff --git a/Complement.Dockerfile b/Complement.Dockerfile
index f9b316fb..e525d8c8 100644
--- a/Complement.Dockerfile
+++ b/Complement.Dockerfile
@@ -23,6 +23,7 @@ COPY ./docker/complement.yaml /data/media-repo.yaml
 ENV REPO_CONFIG=/data/media-repo.yaml
 ENV SERVER_NAME=localhost
 ENV PGDATA=/data/pgdata
+ENV MEDIA_REPO_UNSAFE_FEDERATION=true
 
 COPY ./docker/complement.sh ./docker/complement-run.sh /usr/local/bin/
 RUN dos2unix /usr/local/bin/complement.sh /usr/local/bin/complement-run.sh
@@ -35,8 +36,6 @@ RUN mkdir -p /run/postgresql
 RUN chown postgres:postgres /data/pgdata
 RUN chown postgres:postgres /run/postgresql
 RUN su postgres -c initdb
-RUN openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=${SERVER_NAME}" -keyout /data/server.key  -out /data/server.crt
-RUN sed -i "s/SERVER_NAME/${SERVER_NAME}/g" /data/media-repo.yaml
 RUN sh /usr/local/bin/complement.sh
 
 CMD /usr/local/bin/complement-run.sh
\ No newline at end of file
diff --git a/cmd/complement_hs/main.go b/cmd/complement_hs/main.go
index d98fe59c..ff723e72 100644
--- a/cmd/complement_hs/main.go
+++ b/cmd/complement_hs/main.go
@@ -1,23 +1,109 @@
 package main
 
 import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
 	"os/signal"
+	"strconv"
+	"strings"
 	"sync"
 
 	"github.com/gorilla/mux"
+	"github.com/turt2live/matrix-media-repo/util/cleanup"
 )
 
+type VersionsResponse struct {
+	CSAPIVersions []string `json:"versions,flow"`
+}
+
+type RegisterRequest struct {
+	DesiredUsername string `json:"username"`
+}
+
+type RegisterResponse struct {
+	UserID string `json:"user_id"`
+	AccessToken string `json:"access_token"`
+}
+
+type WhoamiResponse struct {
+	UserID string `json:"user_id"`
+}
+
+func requestJson(r *http.Request, i interface{}) error {
+	b, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(b, &i)
+}
+
+func respondJson(w http.ResponseWriter, i interface{}) error {
+	resp, err := json.Marshal(i)
+	if err != nil {
+		return err
+	}
+	w.Header().Set("Content-Length",strconv.Itoa(len(resp)))
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(200)
+	_, err = w.Write(resp)
+	return err
+}
+
 func main() {
 	// Prepare local server
 	log.Println("Preparing local server...")
 	rtr := mux.NewRouter()
 	rtr.HandleFunc("/_matrix/client/versions", func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "application/json")
-		w.WriteHeader(200)
-		_, err := w.Write([]byte("{\"versions\":[\"r0.6.0\"]}"))
+		defer cleanup.DumpAndCloseStream(r.Body)
+		err := respondJson(w, &VersionsResponse{CSAPIVersions: []string{"r0.6.0"}})
+		if err != nil {
+			log.Fatal(err)
+		}
+	})
+	rtr.HandleFunc("/_matrix/client/r0/register", func(w http.ResponseWriter, r *http.Request) {
+		rr := &RegisterRequest{}
+		err := requestJson(r, &rr)
+		if err != nil {
+			log.Fatal(err)
+		}
+		userId := fmt.Sprintf("@%s:%s", rr.DesiredUsername, os.Getenv("SERVER_NAME"))
+		err = respondJson(w, &RegisterResponse{
+			AccessToken: userId,
+			UserID: userId,
+		})
+		if err != nil {
+			log.Fatal(err)
+		}
+	})
+	rtr.HandleFunc("/_matrix/client/r0/account/whoami", func(w http.ResponseWriter, r *http.Request) {
+		defer cleanup.DumpAndCloseStream(r.Body)
+		userId := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") // including space after Bearer.
+		err := respondJson(w, &WhoamiResponse{UserID: userId})
+		if err != nil {
+			log.Fatal(err)
+		}
+	})
+	rtr.PathPrefix("/_matrix/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Proxy to the media repo running within the container
+		r2, err := http.NewRequest(r.Method, "http://127.0.0.1:8228" + r.RequestURI, r.Body)
+		if err != nil {
+			log.Fatal(err)
+		}
+		r2.Host = os.Getenv("SERVER_NAME")
+		resp, err := http.DefaultClient.Do(r2)
+		if err != nil {
+			log.Fatal(err)
+		}
+		err = resp.Header.Write(w)
+		if err != nil {
+			log.Fatal(err)
+		}
+		_, err = io.Copy(w, resp.Body)
 		if err != nil {
 			log.Fatal(err)
 		}
diff --git a/docker/complement-run.sh b/docker/complement-run.sh
index 4c99443d..9dbef054 100644
--- a/docker/complement-run.sh
+++ b/docker/complement-run.sh
@@ -1,5 +1,7 @@
 #!/usr/bin/env sh
+openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=${SERVER_NAME}" -keyout /data/server.key  -out /data/server.crt
+sed -i "s/SERVER_NAME/${SERVER_NAME}/g" /data/media-repo.yaml
 su postgres -c "postgres -h 0.0.0.0" &
-sleep 3
+sleep 12
 /usr/local/bin/media_repo &
-/usr/local/bin/complement_hs
\ No newline at end of file
+/usr/local/bin/complement_hs
diff --git a/docker/complement.yaml b/docker/complement.yaml
index 4d4e3c67..e73f26a3 100644
--- a/docker/complement.yaml
+++ b/docker/complement.yaml
@@ -1,476 +1,21 @@
-# General repo configuration
 repo:
   bindAddress: '127.0.0.1'
-  port: 8000
-
-  # Where to store the logs, relative to where the repo is started from. Logs will be automatically
-  # rotated every day and held for 14 days. To disable the repo logging to files, set this to
-  # "-" (including quotation marks).
-  #
-  # Note: to change the log directory you'll have to restart the repository. This setting cannot be
-  # live reloaded.
-  logDirectory: logs
-
-  # If true, the media repo will accept any X-Forwarded-For header without validation. In most cases
-  # this option should be left as "false". Note that the media repo already expects an X-Forwarded-For
-  # header, but validates it to ensure the IP being given makes sense.
-  trustAnyForwardedAddress: false
-
-  # If false, the media repo will not use the X-Forwarded-Host header commonly added by reverse proxies.
-  # Typically this should remain as true, though in some circumstances it may need to be disabled.
-  # See https://github.com/turt2live/matrix-media-repo/issues/202 for more information.
-  useForwardedHost: true
-
-# Options for dealing with federation
-federation:
-  # On a per-host basis, the number of consecutive failures in calling the host before the
-  # media repo will back off. This defaults to 20 if not given. Note that 404 errors from
-  # the remote server do not count towards this.
-  backoffAt: 20
-
-# The database configuration for the media repository
-# Do NOT put your homeserver's existing database credentials here. Create a new database and
-# user instead. Using the same server is fine, just not the same username and database.
+  port: 8228
 database:
-  # Currently only "postgres" is supported.
   postgres: "postgres://mediarepo:mediarepo@127.0.0.1/mediarepo?sslmode=disable"
-
-  # The database pooling options
-  pool:
-    # The maximum number of connects to hold open. More of these allow for more concurrent
-    # processes to happen.
-    maxConnections: 25
-
-    # The maximum number of connects to leave idle. More of these reduces the time it takes
-    # to serve requests in low-traffic scenarios.
-    maxIdleConnections: 5
-
-# The configuration for the homeservers this media repository is known to control. Servers
-# not listed here will not be able to upload media.
 homeservers:
-  - name: SERVER_NAME # This should match the server_name of your homeserver, and the Host header
-    # provided to the media repo.
-    csApi: "http://localhost:8008/" # The base URL to where the homeserver can actually be reached
-    backoffAt: 10 # The number of consecutive failures in calling this homeserver before the
-    # media repository will start backing off. This defaults to 10 if not given.
-    adminApiKind: "matrix" # The kind of admin API the homeserver supports. If set to "matrix",
-      # the media repo will use the Synapse-defined endpoints under the
-      # unstable client-server API. When this is "synapse", the new /_synapse
-      # endpoints will be used instead. Unknown values are treated as the
-      # default, "matrix".
-
-# Options for controlling how access tokens work with the media repo. It is recommended that if
-# you are going to use these options that the `/logout` and `/logout/all` client-server endpoints
-# be proxied through this process. They will also be called on the homeserver, and the response
-# sent straight through the client - they are simply used to invalidate the cache faster for
-# a particular user. Without these, the access tokens might still work for a short period of time
-# after the user has already invalidated them.
-#
-# This will also cache errors from the homeserver.
-#
-# Note that when this config block is used outside of a per-domain config, all hosts will be
-# subject to the same cache. This also means that application services on limited homeservers
-# could be authorized on the wrong domain.
-#
-# ***************************************************************************
-# *  IT IS HIGHLY RECOMMENDED TO USE PER-DOMAIN CONFIGS WITH THIS FEATURE.  *
-# ***************************************************************************
-accessTokens:
-  # The maximum time a cached access token will be considered valid. Set to zero (the default)
-  # to disable the cache and constantly hit the homeserver. This is recommended to be set to
-  # 43200 (12 hours) on servers with the logout endpoints proxied through the media repo, and
-  # zero for servers who do not proxy the endpoints through.
-  maxCacheTimeSeconds: 0
-
-  # Whether or not to use the `appservices` config option below. If disabled (the default),
-  # the regular access token cache will be used for each user, potentially leading to high
-  # memory usage.
-  useLocalAppserviceConfig: false
-
-  # The application services (and their namespaces) registered on the homeserver. Only used
-  # if `useLocalAppserviceConfig` is enabled (recommended).
-  #
-  # Usually the appservice will provide you with these config details - they'll just need
-  # translating from the appservice registration to here. Note that this does not require
-  # all options from the registration, and only requires the bare minimum required to run
-  # the media repo.
-  appservices:
-    - id: Name_of_appservice_for_your_reference
-      asToken: Secret_token_for_appservices_to_use
-      senderUserId: "@_example_bridge:yourdomain.com"
-      userNamespaces:
-        - regex: "@_example_bridge_.+:yourdomain.com"
-          # A note about regexes: it is best to suffix *all* namespaces with the homeserver
-          # domain users are valid for, as otherwise the appservice can use any user with
-          # any domain name it feels like, even if that domain is not configured with the
-          # media repo. This will lead to inaccurate reporting in the case of the media
-          # repo, and potentially leading to media being considered "remote".
-
-# These users have full access to the administrative functions of the media repository.
-# See docs/admin.md for information on what these people can do. They must belong to one of the
-# configured homeservers above.
-admins:
-  - "@your_username:example.org"
-
-# Shared secret auth is useful for applications building on top of the media repository, such
-# as a management interface. The `token` provided here is treated as a repository administrator
-# when shared secret auth is enabled: if the `token` is used in place of an access token, the'
-# request will be authorized. This is not limited to any particular domain, giving applications
-# the ability to use it on any configured hostname.
-sharedSecretAuth:
-  # Set this to true to enable shared secret auth.
-  enabled: false
-
-  # Use a secure value here to prevent unauthorized access to the media repository.
-  token: "PutSomeRandomSecureValueHere"
-
-# Datastores are places where media should be persisted. This isn't dedicated for just uploads:
-# thumbnails and other misc data is also stored in these places. When the media repo is looking
-# to store new media (such as user uploads, thumbnails, etc) it will look for a datastore which
-# is flagged as forUploads. It will try to use the smallest datastore first.
+  - name: SERVER_NAME
+    csApi: "http://127.0.0.1:8008/"
 datastores:
   - type: file
-    enabled: true # Enable this to set up data storage.
-    # Datastores can be split into many areas when handling uploads. Media is still de-duplicated
-    # across all datastores (local content which duplicates remote content will re-use the remote
-    # content's location). This option is useful if your datastore is becoming very large, or if
-    # you want faster storage for a particular kind of media.
-    #
-    # The kinds available are:
-    #   thumbnails    - Used to store thumbnails of media (local and remote).
-    #   remote_media  - Original copies of remote media (servers not configured by this repo).
-    #   local_media   - Original uploads for local media.
-    #   archives      - Archives of content (GDPR and similar requests).
+    enabled: true
     forKinds: ["all"]
     opts:
       path: /data/media
-
-# Options for controlling archives. Archives are exports of a particular user's content for
-# the purpose of GDPR or moving media to a different server.
-archiving:
-  # Whether archiving is enabled or not. Default enabled.
-  enabled: true
-  # If true, users can request a copy of their own data. By default, only repository administrators
-  # can request a copy.
-  # This includes the ability for homeserver admins to request a copy of their own server's
-  # data, as known to the repo.
-  selfService: false
-  # The number of bytes to target per archive before breaking up the files. This is independent
-  # of any file upload limits and will require a similar amount of memory when performing an export.
-  # The file size is also a target, not a guarantee - it is possible to have files that are smaller
-  # or larger than the target. This is recommended to be approximately double the size of your
-  # file upload limit, provided there is enough memory available for the demand of exporting.
-  targetBytesPerPart: 209715200 # 200mb default
-
-# The file upload settings for the media repository
-uploads:
-  maxBytes: 104857600 # 100MB default, 0 to disable
-
-  # The minimum number of bytes to let people upload
-  minBytes: 100 # 100 bytes by default
-
-  # The number of bytes to claim as the maximum size for uploads for the limits API. If this
-  # is not provided then the maxBytes setting will be used instead. This is useful to provide
-  # if the media repo's settings and the reverse proxy do not match for maximum request size.
-  # This is purely for informational reasons and does not actually limit any functionality.
-  # Set this to -1 to indicate that there is no limit. Zero will force the use of maxBytes.
-  #reportedMaxBytes: 104857600
-
-
-# Settings related to downloading files from the media repository
-downloads:
-  # The maximum number of bytes to download from other servers
-  maxBytes: 104857600 # 100MB default, 0 to disable
-
-  # The number of workers to use when downloading remote media. Raise this number if remote
-  # media is downloading slowly or timing out.
-  #
-  # Maximum memory usage = numWorkers multiplied by the maximum download size
-  # Average memory usage is dependent on how many concurrent downloads your users are doing.
-  numWorkers: 10
-
-  # How long, in minutes, to cache errors related to downloading remote media. Once this time
-  # has passed, the media is able to be re-requested.
-  failureCacheMinutes: 5
-
-  # The cache control settings for downloads. This can help speed up downloads for users by
-  # keeping popular media in the cache. This cache is also used for thumbnails.
-  cache:
-    enabled: true
-
-    # The maximum size of cache to have. Higher numbers are better.
-    maxSizeBytes: 1048576000 # 1GB default
-
-    # The maximum file size to cache. This should normally be the same size as your maximum
-    # upload size.
-    maxFileSizeBytes: 104857600 # 100MB default
-
-    # The number of minutes to track how many downloads a file gets
-    trackedMinutes: 30
-
-    # The number of downloads a file must receive in the window above (trackedMinutes) in
-    # order to be cached.
-    minDownloads: 5
-
-    # The minimum amount of time an item should remain in the cache. This prevents the cache
-    # from cycling out the file if it needs more room during this time. Note that the media
-    # repo regularly cleans out media which is past this point from the cache, so this number
-    # may need increasing depending on your use case. If the maxSizeBytes is reached for the
-    # media repo, and some cached items are still under this timer, new items will not be able
-    # to enter the cache. When this happens, consider raising maxSizeBytes or lowering this
-    # timer.
-    minCacheTimeSeconds: 300
-
-    # The minimum amount of time an item should remain outside the cache once it is removed.
-    minEvictedTimeSeconds: 60
-
-  # How many days after a piece of remote content is downloaded before it expires. It can be
-  # re-downloaded on demand, this just helps free up space in your datastore. Set to zero or
-  # negative to disable. Defaults to disabled.
-  expireAfterDays: 0
-
-# URL Preview settings
 urlPreviews:
-  enabled: true # If enabled, the preview_url routes will be accessible
-  maxPageSizeBytes: 10485760 # 10MB default, 0 to disable
-
-  # If true, the media repository will try to provide previews for URLs with invalid or unsafe
-  # certificates. If false (the default), the media repo will fail requests to said URLs.
-  previewUnsafeCertificates: false
-
-  # Note: URL previews are limited to a given number of words, which are then limited to a number
-  # of characters, taking off the last word if it needs to. This also applies for the title.
-
-  numWords: 50 # The number of words to include in a preview (maximum)
-  maxLength: 200 # The maximum number of characters for a description
-
-  numTitleWords: 30 # The maximum number of words to include in a preview's title
-  maxTitleLength: 150 # The maximum number of characters for a title
-
-  # The mime types to preview when OpenGraph previews cannot be rendered. OpenGraph previews are
-  # calculated on anything matching "text/*". To have a thumbnail in the preview the URL must be
-  # an image and the image's type must be allowed by the thumbnailer.
-  filePreviewTypes:
-    - "image/*"
-
-  # The number of workers to use when generating url previews. Raise this number if url
-  # previews are slow or timing out.
-  #
-  # Maximum memory usage = numWorkers multiplied by the maximum page size
-  # Average memory usage is dependent on how many concurrent urls your users are previewing.
-  numWorkers: 10
-
-  # Either allowedNetworks or disallowedNetworks must be provided. If both are provided, they
-  # will be merged. URL previews will be disabled if neither is supplied. Each entry must be
-  # a CIDR range.
   disallowedNetworks:
-    - "127.0.0.1/8"
-    - "10.0.0.0/8"
-    - "172.16.0.0/12"
-    - "192.168.0.0/16"
-    - "100.64.0.0/10"
-    - "169.254.0.0/16"
-    - '::1/128'
-    - 'fe80::/64'
-    - 'fc00::/7'
+    - "192.168.0.0/16" # Don't limit localhost
   allowedNetworks:
-    - "0.0.0.0/0" # "Everything". The blacklist will help limit this.
-    # This is the default value for this field.
-
-  # How many days after a preview is generated before it expires and is deleted. The preview
-  # can be regenerated safely - this just helps free up some space in your database. Set to
-  # zero or negative to disable. Defaults to disabled.
-  expireAfterDays: 0
-
-  # The default Accept-Language header to supply when generating URL previews when one isn't
-  # supplied by the client.
-  # Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language
-  defaultLanguage: "en-US,en"
-
-# The thumbnail configuration for the media repository.
-thumbnails:
-  # The maximum number of bytes an image can be before the thumbnailer refuses.
-  maxSourceBytes: 10485760 # 10MB default, 0 to disable
-
-  # The number of workers to use when generating thumbnails. Raise this number if thumbnails
-  # are slow to generate or timing out.
-  #
-  # Maximum memory usage = numWorkers multiplied by the maximum image source size
-  # Average memory usage is dependent on how many thumbnails are being generated by your users
-  numWorkers: 100
-
-  # All thumbnails are generated into one of the sizes listed here. The first size is used as
-  # the default for when no width or height is requested. The media repository will return
-  # either an exact match or the next largest size of thumbnail.
-  sizes:
-    - width: 32
-      height: 32
-    - width: 96
-      height: 96
-    - width: 320
-      height: 240
-    - width: 640
-      height: 480
-    - width: 800
-      height: 600
-
-  # The content types to thumbnail when requested. Types that are not supported by the media repo
-  # will not be thumbnailed (adding application/json here won't work). Clients may still not request
-  # thumbnails for these types - this won't make clients automatically thumbnail these file types.
-  types:
-    - "image/jpeg"
-    - "image/jpg"
-    - "image/png"
-    - "image/gif"
-    - "image/heif"
-    - "image/webp"
-    #- "image/svg+xml" # Be sure to have ImageMagick installed to thumbnail SVG files
-
-  # Animated thumbnails can be CPU intensive to generate. To disable the generation of animated
-  # thumbnails, set this to false. If disabled, regular thumbnails will be returned.
-  allowAnimated: true
-
-  # Default to animated thumbnails, if available
-  defaultAnimated: false
-
-  # The maximum file size to thumbnail when a capable animated thumbnail is requested. If the image
-  # is larger than this, the thumbnail will be generated as a static image.
-  maxAnimateSizeBytes: 10485760 # 10MB default, 0 to disable
-
-  # On a scale of 0 (start of animation) to 1 (end of animation), where should the thumbnailer try
-  # and thumbnail animated content? Defaults to 0.5 (middle of animation).
-  stillFrame: 0.5
-
-  # How many days after a thumbnail is generated before it expires and is deleted. The thumbnail
-  # can be regenerated safely - this just helps free up some space in your datastores. Set to
-  # zero or negative to disable. Defaults to disabled.
-  expireAfterDays: 0
-
-# Controls for the rate limit functionality
+    - "0.0.0.0/0"
 rateLimit:
-  # Set this to false if rate limiting is handled at a higher level or you don't want it enabled.
-  enabled: false
-
-  # The number of requests per second before an IP will be rate limited. Must be a whole number.
-  requestsPerSecond: 1
-
-  # The number of requests an IP can send at once before the rate limit is actually considered.
-  burst: 10
-
-# Identicons are generated avatars for a given username. Some clients use these to give users a
-# default avatar after signing up. Identicons are not part of the official matrix spec, therefore
-# this feature is completely optional.
-identicons:
-  enabled: true
-
-# The quarantine media settings.
-quarantine:
-  # If true, when a thumbnail of quarantined media is requested an image will be returned. If no
-  # image is given in the thumbnailPath below then a generated image will be provided. This does
-  # not affect regular downloads of files.
-  replaceThumbnails: true
-
-  # If true, when media which has been quarantined is requested an image will be returned. If
-  # no image is given in the thumbnailPath below then a generated image will be provided. This
-  # will replace media which is not an image (ie: quarantining a PDF will replace the PDF with
-  # an image).
-  replaceDownloads: false
-
-  # If provided, the given image will be returned as a thumbnail for media that is quarantined.
-  #thumbnailPath: "/path/to/thumbnail.png"
-
-  # If true, administrators of the configured homeservers may quarantine media for their server
-  # only. Global administrators can quarantine any media (local or remote) regardless of this
-  # flag.
-  allowLocalAdmins: true
-
-# The various timeouts that the media repo will use.
-timeouts:
-  # The maximum amount of time the media repo should spend trying to fetch a resource that is
-  # being previewed.
-  urlPreviewTimeoutSeconds: 10
-
-  # The maximum amount of time the media repo will spend making remote requests to other repos
-  # or homeservers. This is primarily used to download media.
-  federationTimeoutSeconds: 120
-
-  # The maximum amount of time the media repo will spend talking to your configured homeservers.
-  # This is usually used to verify a user's identity.
-  clientServerTimeoutSeconds: 30
-
-# Prometheus metrics configuration
-# For an example Grafana dashboard, import the following JSON:
-# https://github.com/turt2live/matrix-media-repo/blob/master/docs/grafana.json
-metrics:
-  # If true, the bindAddress and port below will serve GET /metrics for Prometheus to scrape.
   enabled: false
-
-  # The address to listen on. Typically "127.0.0.1" or "0.0.0.0" for all interfaces.
-  bindAddress: "127.0.0.1"
-
-  # The port to listen on. Cannot be the same as the general web server port.
-  port: 9000
-
-# Options for controlling various MSCs/unstable features of the media repo
-# Sections of this config might disappear or be added over time. By default all
-# features are disabled in here and must be explicitly enabled to be used.
-featureSupport:
-  # MSC2248 - Blurhash
-  MSC2448:
-    # Whether or not this MSC is enabled for use in the media repo
-    enabled: false
-
-    # Maximum dimensions for converting a blurhash to an image. When no width and
-    # height options are supplied, the default will be half these values.
-    maxWidth: 1024
-    maxHeight: 1024
-
-    # Thumbnail size in pixels to use to generate the blurhash string
-    thumbWidth: 64
-    thumbHeight: 64
-
-    # The X and Y components to use. Higher numbers blur less, lower numbers blur more.
-    xComponents: 4
-    yComponents: 3
-
-    # The amount of contrast to apply when converting a blurhash to an image. Lower values
-    # make the effect more subtle, larger values make it stronger.
-    punch: 1
-
-  # IPFS Support
-  # This is currently experimental and might not work at all.
-  IPFS:
-    # Whether or not IPFS support is enabled for use in the media repo.
-    enabled: false
-
-    # Options for the built in IPFS daemon
-    builtInDaemon:
-      # Enable this to spawn an in-process IPFS node to use instead of a localhost
-      # HTTP agent. If this is disabled, the media repo will assume you have an HTTP
-      # IPFS agent running and accessible. Defaults to using a daemon (true).
-      enabled: true
-
-      # If the Daemon is enabled, set this to the location where the IPFS files should
-      # be stored. If you're using Docker, this should be something like "/data/ipfs"
-      # so it can be mapped to a volume.
-      repoPath: "./ipfs"
-
-  # Support for redis as a cache mechanism
-  #
-  # Note: Enabling Redis support will mean that the existing cache mechanism will do nothing.
-  # It can be safely disabled once Redis support is enabled.
-  #
-  # See docs/redis.md for more information on how this works and how to set it up.
-  redis:
-    # Whether or not use Redis instead of in-process caching.
-    enabled: false
-
-    # The Redis shards that should be used by the media repo in the ring. The names of the
-    # shards are for your reference and have no bearing on the connection, but must be unique.
-    shards:
-      - name: "server1"
-        addr: ":7000"
-      - name: "server2"
-        addr: ":7001"
-      - name: "server3"
-        addr: ":7002"
\ No newline at end of file
diff --git a/matrix/federation.go b/matrix/federation.go
index ff10a4fe..ea92bb63 100644
--- a/matrix/federation.go
+++ b/matrix/federation.go
@@ -8,6 +8,7 @@ import (
 	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"strconv"
 	"strings"
 	"sync"
@@ -211,26 +212,55 @@ func FederatedGet(url string, realHost string, ctx rcontext.RequestContext) (*ht
 		req.Header.Set("User-Agent", "matrix-media-repo")
 		req.Host = realHost
 
-		// This is how we verify the certificate is valid for the host we expect.
-		// Previously using `req.URL.Host` we'd end up changing which server we were
-		// connecting to (ie: matrix.org instead of matrix.org.cdn.cloudflare.net),
-		// which obviously doesn't help us. We needed to do that though because the
-		// HTTP client doesn't verify against the req.Host certificate, but it does
-		// handle it off the req.URL.Host. So, we need to tell it which certificate
-		// to verify.
-
-		h, _, err := net.SplitHostPort(realHost)
-		if err == nil {
-			// Strip the port first, certs are port-insensitive
-			realHost = h
-		}
-		client := http.Client{
-			Transport: &http.Transport{
-				TLSClientConfig: &tls.Config{
-					ServerName: realHost,
+		var client *http.Client
+		if os.Getenv("MEDIA_REPO_UNSAFE_FEDERATION") != "true" {
+			// This is how we verify the certificate is valid for the host we expect.
+			// Previously using `req.URL.Host` we'd end up changing which server we were
+			// connecting to (ie: matrix.org instead of matrix.org.cdn.cloudflare.net),
+			// which obviously doesn't help us. We needed to do that though because the
+			// HTTP client doesn't verify against the req.Host certificate, but it does
+			// handle it off the req.URL.Host. So, we need to tell it which certificate
+			// to verify.
+
+			h, _, err := net.SplitHostPort(realHost)
+			if err == nil {
+				// Strip the port first, certs are port-insensitive
+				realHost = h
+			}
+			client = &http.Client{
+				Transport: &http.Transport{
+					TLSClientConfig: &tls.Config{
+						ServerName: realHost,
+					},
 				},
-			},
-			Timeout: time.Duration(ctx.Config.TimeoutSeconds.Federation) * time.Second,
+				Timeout: time.Duration(ctx.Config.TimeoutSeconds.Federation) * time.Second,
+			}
+		} else {
+			ctx.Log.Warn("Ignoring any certificate errors while making request")
+			tr := &http.Transport{
+				DisableKeepAlives: true,
+				TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
+				// Based on https://github.com/matrix-org/gomatrixserverlib/blob/51152a681e69a832efcd934b60080b92bc98b286/client.go#L74-L90
+				DialTLS: func(network, addr string) (net.Conn, error) {
+					rawconn, err := net.Dial(network, addr)
+					if err != nil {
+						return nil, err
+					}
+					// Wrap a raw connection ourselves since tls.Dial defaults the SNI
+					conn := tls.Client(rawconn, &tls.Config{
+						ServerName:         "",
+						InsecureSkipVerify: true,
+					})
+					if err := conn.Handshake(); err != nil {
+						return nil, err
+					}
+					return conn, nil
+				},
+			}
+			client = &http.Client{
+				Transport: tr,
+				Timeout:   time.Duration(ctx.Config.TimeoutSeconds.UrlPreviews) * time.Second,
+			}
 		}
 
 		resp, err = client.Do(req)
-- 
GitLab