Преглед на файлове

Restrict image/gif media host instead of hashing

Zed преди 7 години
родител
ревизия
9c91688497
променени са 7 файла, в които са добавени 65 реда и са изтрити 25 реда
  1. 1 1
      src/formatters.nim
  2. 32 9
      src/routes/media.nim
  3. 20 3
      src/utils.nim
  4. 1 1
      src/views/general.nim
  5. 2 2
      src/views/profile.nim
  6. 1 1
      src/views/renderutils.nim
  7. 8 8
      src/views/tweet.nim

+ 1 - 1
src/formatters.nim

@@ -77,7 +77,7 @@ proc stripTwitterUrls*(text: string): string =
 proc proxifyVideo*(manifest: string; proxy: bool): string =
   proc cb(m: RegexMatch; s: string): string =
     result = "https://video.twimg.com" & s[m.group(0)[0]]
-    if proxy: result = result.getSigUrl("video")
+    if proxy: result = getVidUrl(result)
   result = manifest.replace(re"(.+(.ts|.m3u8|.vmap))", cb)
 
 proc getUserpic*(userpic: string; style=""): string =

+ 32 - 9
src/routes/media.nim

@@ -12,24 +12,26 @@ export utils
 
 proc createMediaRouter*(cfg: Config) =
   router media:
-    get "/pic/@sig/@url":
+    get "/pic/@url":
       cond "http" in @"url"
       cond "twimg" in @"url"
-      let
-        uri = parseUri(decodeUrl(@"url"))
-        path = uri.path.split("/")[2 .. ^1].join("/")
-        filename = cfg.cacheDir / cleanFilename(path & uri.query)
 
-      if getHmac($uri) != @"sig":
-        resp showError("Failed to verify signature", cfg.title)
+      let uri = parseUri(decodeUrl(@"url"))
+      cond isTwitterUrl($uri) == true
+
+      let path = uri.path.split("/")[2 .. ^1].join("/")
+      let filename = cfg.cacheDir / cleanFilename(path & uri.query)
 
       if not existsDir(cfg.cacheDir):
         createDir(cfg.cacheDir)
 
       if not existsFile(filename):
         let client = newAsyncHttpClient()
-        await client.downloadFile($uri, filename)
-        client.close()
+        try:
+          await client.downloadFile($uri, filename)
+          client.close()
+        except:
+          discard
 
       if not existsFile(filename):
         resp Http404
@@ -40,6 +42,27 @@ proc createMediaRouter*(cfg: Config) =
 
       resp buf, mimetype(filename)
 
+    get "/gif/@url":
+      cond "http" in @"url"
+      cond "twimg" in @"url"
+      cond "mp4" in @"url" or "gif" in @"url"
+
+      let url = decodeUrl(@"url")
+      cond isTwitterUrl(url) == true
+
+      let client = newAsyncHttpClient()
+      var content: string
+      try:
+        content = await client.getContent(url)
+        client.close
+      except:
+        discard
+
+      if content.len == 0:
+        resp Http404
+
+      resp content, mimetype(url)
+
     get "/video/@sig/@url":
       cond "http" in @"url"
       var url = decodeUrl(@"url")

+ 20 - 3
src/utils.nim

@@ -1,7 +1,15 @@
 import strutils, strformat, sequtils, uri, tables
 import nimcrypto, regex
 
-const key = "supersecretkey"
+const
+  key = "supersecretkey"
+  twitterDomains = @[
+    "twitter.com",
+    "twimg.com",
+    "abs.twimg.com",
+    "pbs.twimg.com",
+    "video.twimg.com"
+  ]
 
 proc mimetype*(filename: string): string =
   if ".png" in filename:
@@ -16,11 +24,17 @@ proc mimetype*(filename: string): string =
 proc getHmac*(data: string): string =
   ($hmac(sha256, key, data))[0 .. 12]
 
-proc getSigUrl*(link: string; path: string): string =
+proc getVidUrl*(link: string): string =
   let
     sig = getHmac(link)
     url = encodeUrl(link)
-  &"/{path}/{sig}/{url}"
+  &"/video/{sig}/{url}"
+
+proc getGifUrl*(link: string): string =
+  &"/gif/{encodeUrl(link)}"
+
+proc getPicUrl*(link: string): string =
+  &"/pic/{encodeUrl(link)}"
 
 proc cleanFilename*(filename: string): string =
   const reg = re"[^A-Za-z0-9._-]"
@@ -29,3 +43,6 @@ proc cleanFilename*(filename: string): string =
 proc filterParams*(params: Table): seq[(string, string)] =
   let filter = ["name", "id"]
   toSeq(params.pairs()).filterIt(it[0] notin filter)
+
+proc isTwitterUrl*(url: string): bool =
+  parseUri(url).hostname in twitterDomains

+ 1 - 1
src/views/general.nim

@@ -41,7 +41,7 @@ proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="
       meta(property="og:site_name", content="Twitter")
 
       for url in images:
-        meta(property="og:image", content=getSigUrl(url, "pic"))
+        meta(property="og:image", content=getPicUrl(url))
 
       if video.len > 0:
         meta(property="og:video:url", content=video)

+ 2 - 2
src/views/profile.nim

@@ -14,7 +14,7 @@ proc renderStat(num, class: string; text=""): VNode =
 proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode =
   buildHtml(tdiv(class="profile-card")):
     tdiv(class="profile-card-info"):
-      let url = profile.getUserPic().getSigUrl("pic")
+      let url = getPicUrl(profile.getUserPic())
       a(class="profile-card-avatar", href=url, target="_blank"):
         genImg(profile.getUserpic("_200x200"))
 
@@ -72,7 +72,7 @@ proc renderBanner(profile: Profile): VNode =
     if "#" in profile.banner:
       tdiv(class="profile-banner-color", style={backgroundColor: profile.banner})
     else:
-      a(href=getSigUrl(profile.banner, "pic"), target="_blank"):
+      a(href=getPicUrl(profile.banner), target="_blank"):
         genImg(profile.banner)
 
 proc renderProfile*(profile: Profile; timeline: Timeline;

+ 1 - 1
src/views/renderutils.nim

@@ -32,7 +32,7 @@ proc linkUser*(profile: Profile, class=""): VNode =
 
 proc genImg*(url: string; class=""): VNode =
   buildHtml():
-    img(src=url.getSigUrl("pic"), class=class, alt="Image")
+    img(src=getPicUrl(url), class=class, alt="Image")
 
 proc linkText*(text: string; class=""): VNode =
   let url = if "http" notin text: "http://" & text else: text

+ 8 - 8
src/views/tweet.nim

@@ -41,7 +41,7 @@ proc renderAlbum(tweet: Tweet): VNode =
       tdiv(class="gallery-row", style={marginTop: margin}):
         for photo in photos:
           tdiv(class="attachment image"):
-            a(href=getSigUrl(photo & "?name=orig", "pic"), class="still-image",
+            a(href=getPicUrl(photo & "?name=orig"), class="still-image",
               target="_blank", style={display: flex}):
               genImg(photo)
 
@@ -52,7 +52,7 @@ proc isPlaybackEnabled(prefs: Prefs; video: Video): bool =
 
 proc renderVideoDisabled(video: Video; path: string): VNode =
   buildHtml(tdiv):
-    img(src=video.thumb.getSigUrl("pic"))
+    img(src=getPicUrl(video.thumb))
     tdiv(class="video-overlay"):
       case video.playbackType
       of mp4:
@@ -62,7 +62,7 @@ proc renderVideoDisabled(video: Video; path: string): VNode =
 
 proc renderVideoUnavailable(video: Video): VNode =
   buildHtml(tdiv):
-    img(src=video.thumb.getSigUrl("pic"))
+    img(src=getPicUrl(video.thumb))
     tdiv(class="video-overlay"):
       case video.reason
       of "dmcaed":
@@ -74,13 +74,13 @@ proc renderVideo(video: Video; prefs: Prefs; path: string): VNode =
   buildHtml(tdiv(class="attachments")):
     tdiv(class="gallery-video"):
       tdiv(class="attachment video-container"):
-        let thumb = video.thumb.getSigUrl("pic")
+        let thumb = getPicUrl(video.thumb)
         if not video.available:
           renderVideoUnavailable(video)
         elif not prefs.isPlaybackEnabled(video):
           renderVideoDisabled(video, path)
         else:
-          let source = video.url.getSigUrl("video")
+          let source = getVidUrl(video.url)
           case video.playbackType
           of mp4:
             if prefs.muteVideos:
@@ -99,8 +99,8 @@ proc renderGif(gif: Gif; prefs: Prefs): VNode =
   buildHtml(tdiv(class="attachments media-gif")):
     tdiv(class="gallery-gif", style=style(maxHeight, "unset")):
       tdiv(class="attachment"):
-        let thumb = gif.thumb.getSigUrl("pic")
-        let url = gif.url.getSigUrl("video")
+        let thumb = getPicUrl(gif.thumb)
+        let url = getGifUrl(gif.url)
         if prefs.autoplayGifs:
           video(class="gif", poster=thumb, autoplay="", muted="", loop=""):
             source(src=url, `type`="video/mp4")
@@ -123,7 +123,7 @@ proc renderPoll(poll: Poll): VNode =
 proc renderCardImage(card: Card): VNode =
   buildHtml(tdiv(class="card-image-container")):
     tdiv(class="card-image"):
-      img(src=getSigUrl(get(card.image), "pic"))
+      img(src=getPicUrl(get(card.image)))
       if card.kind == player:
         tdiv(class="card-overlay"):
           tdiv(class="overlay-circle"):