felix-starman

felix-starman

Library for parsing and building RFC-compliant* Content-Disposition headers

I’ve noticed that often when I find myself needing to manually set content-disposition - [MDN] the advice is usually to use URI.encode_www_form or sometimes URI.encode. This has usually resulted in users receiving files with mangled filenames, and sometimes if only using the filename= parameter, a filename that the browser may refuse to honor, or drop the extension and half the name.


I want to start off by saying that there is a library for creating content-disposition headers, and my work on the formatting side is mostly an extension of that library GitHub - jeroenvisser101/content_disposition: A helper package for Elixir to generate Content-Disposition values · GitHub (shout-out to @jeroenvisser101)
I also want to give kudos to Julian Reschke aka greenbytes, whose RFC work was uplifting, and the jshttp/content-disposition npm package for their thorough tests.

Ok, returning to the topic…

So I’ve also noticed that Plug handles this with Plug.Parsers.MULTIPART for the newer, RFC-suggested/modern “filename*=” parameter, but still gives priority to the older, (US-)ASCII-only “filename=” parameter.

What got me here, looking for a RFC-compliant parser, is a scenario where we have to consume our downloads (why is not important), as well as with another issue where by following the old ways, users were ending up with mangled download filenames.

What I’m noticing is that each implementation of both the formatting and the parsing logic across multiple libraries, is either using the wrong RFCs (URI.encode_www_form), encoding the “filename=” parameter when it seems that RFCs say not to, and to use the “filename*=” instead (send_download, and a few other places and libraries), or other parses and encoding issues

For example:
If you have a file called "my 'secrets' file.txt" and encode it in different ways:

  • URI.encode gives "my%20'secrets'%20file.txt", which will give issues with “filename*=” since single-quote ' is the splitting character for parameter-extensions
  • URI.encode_www_form gives "my+%27secrets%27+file.txt", which will work but results in plus-signs in the file, which even the docs point out as a potential downside.
  • A couple places in different libraries where the wrong set of characters were used to do the encoding/percenting (the RFCs are quite dense at times so this seems reasonable)
  • Plug.Conn.Utils.params doesn’t allow spaces before the “quoted-string” value, but I believe they should
  • A number of places where values for filename*= are matched against “utf-8” only and not “UTF-8” or any language tag for example in filename*=UTF-8'en'some%20file.txt (which should be RFC-compliant)

What I am proposing though, is a well-tested library for creating and parsing the content-disposition headers that is used in Plug, and suggested in the docs of different libraries for when you need to extract the filename from a response using your HTTP client of choice

I am thinking something like jshttp/content-disposition but for Elixir, and potentially using greenbytes’ testing XML.
And it seems like it should be configurable for how “strict” it is, defaulting to not decoding the “filename=” parameter, but being a bit more forgiving when parsing with the “filename*=” parameter.

I have created a gist (link below) of some recent work I’ve done on the parsing. It still needs massive amounts of work, and honestly should probably be in a PR to @jeroenvisser101’s repository, but I wanted to start a topic here to get people’s insights, and hopefully raise awareness of how inconsistent it seems the implementations and suggestions I’ve seen are.

What do people think?

p.s. In the gist you’ll see MyApp.Downloadable. It is a protocol we use to make it so we never have to write another bespoke /download route again. We just have a controller responsible for each use-case necessary (authorized & single-use, shared & unlimited downloads within the TTL, etc.) and then some clever LiveView components that drop a Downloadable into an appropriate store with a UUID on-click, then finish opening the link using the new UUID. It’s pretty much solved all our download needs for LiveViews and “dead” views. And API users can still use the protocol, just on different controllers than e.g. the “SingleUseController”.

First Post!

dmitriid

dmitriid

Yes!

I ran into the issue with filenames myself, and tore my hair out before figuring it out :slight_smile:

Where Next?

Popular in RFCs Top

type1fool
After discussing some of my ideas around Event Sourcing at last night’s Elixir Happy Hour in Austin (highly recommend), I spent some time...
New
dergraf
Hey everyone! :waving_hand: I’ve been working on a side project for the last 4 months that I’m excited to share—it’s called ProxyConf! P...
New
adamwight
I’ve written a crude module for retrieving files from a remote server using rsync, since I didn’t see any existing tools already. This c...
New
simulacre7
Hi everyone, I’m building Aethrion, an early alpha Elixir runtime for persistent AI characters. Repo: GitHub - simulacre7/aethrion: A s...
New
borodark
Hey folks, I’m building Zed (not editor) a declarative BEAM deploy tool for FreeBSD and illumos that uses ZFS user properties as the stat...
New
puemos
Hey everyone :waving_hand: Code: GitHub - puemos/overbooked: Overbooked is a self-hosted flexible workplace platform for indie co-wor...
New
jarlah
Hi! I have recently created, after having tried to get in touch with the creator of excontainers for quite some time, a new library call...
New
bortzmeyer
Not a replacement for serious authoritative DNS servers, of course, but useful for some uses (such as returning the IP address of the DNS...
New
KristerV
How I currently use Hexdocs I use hexdocs all day every day, but finding the right module and function takes too long even with my bookma...
New
marick
TL;DR I’ve forked the Lens package, changed the API some, added new lens makers and – most importantly – added a ton of documentation. In...
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement