Restrict image/gif media host instead of hashing
This commit is contained in:
		
							parent
							
								
									ec43987363
								
							
						
					
					
						commit
						9c91688497
					
				| 
						 | 
				
			
			@ -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 =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue