Percent-Encode a String Introduction

As some of you may know, URLs actually have a list of characters that do special things. For example, the / character separates parts of the URL, and the ?, &amp;, and = characters are used to pass query parameters to the server. In fact, there is a bunch of characters with special functions: $&amp;+,/:;=?@. When you need to use these characters in the URL for any other reason besides the special functions, you have to do something called percent-encoding.

Percent encoding is when you take a character's hexadecimal value and prepend a % character to the beginning of it. For example, the character ? would be encoded as %3F, and the character &amp; would be encoded as %26. In a URL specifically, this allows you to send these characters as data via the URL without causing parsing problems. Your challenge will be to take a string, and percent-encode all of the characters that need to be encoded.

The Challenge

You shall write a program or function that takes in a single string consisting of characters with codepoints 00-FF (ASCII and Extended ASCII characters). You will then have to output or return the same string with each character percent-encoded if necessary. Built-ins that accomplish this task are not allowed, nor are standard loopholes. For reference, here is a list of every character that needs to be percent encoded:


Control characters (Codepoints 00-1F and 7F)
Extended ASCII characters (Codepoints 80-FF)
Reserved characters ($&amp;+,/:;=?@, i.e. codepoints 24, 26, 2B, 2C, 2F, 3A, 3B, 3D, 3F, 40)
Unsafe characters (" &lt;&gt;#%{}|\^~[]`, i.e. codepoints 20, 22, 3C, 3E, 23, 25, 7B, 7D, 7C, 5C, 5E, 7E, 5B, 5D, 60)


Here is a the same list, but instead as a list of decimal codepoints: 

0-31, 32, 34, 35, 36, 37, 38, 43, 44, 47, 58, 59, 60, 62, 61, 63, 64, 91, 92, 93, 94, 96, 123, 124, 125, 126, 127, 128-255


This is code golf, so shortest code in bytes (or approved alternative scoring method) wins!

Test Cases

http://codegolf.stackexchange.com/  =&gt;  http%3A%2F%2Fcodegolf.stackexchange.com%2F
[@=&gt;]{#}  =&gt;  %5B%40%3D%3E%5D%7B%23%7D
Test String  =&gt;  Test%20String
ÑÉÐÔ®  =&gt;  %D1%C9%D0%D4%AE
  =&gt;  %0F%16%7F (Control characters 0F, 16, and 7F)
 ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ  =&gt;  %80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF (Extended ASCII characters 80-FF)
 !"#$%&amp;'()*+,-./0123456789:;&lt;=&gt;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  =&gt;  %20!%22%23%24%25%26'()*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E
Vim, 67 bytes/keystrokes
:s/\c[^a-z!'()*0-9._-]/\='%'.printf(&quot;%02x&quot;,char2nr(submatch(0)))/g&lt;cr&gt;

Note that &lt;cr&gt; represents the enter key, e.g. 0x0D which is a single byte.
This is a pretty straightforward solution. Explanation:
:s/                                                                    &quot;Search and replace
   \c                                                                  &quot;Case-insensitive
     [^a-z!'()*0-9._-]/                                                &quot;A negative range. Matches any character not alphabetc, numeric or in &quot;!'()*0-9._-&quot;
                       \=                                              &quot;Evaluate
                         '%'                                           &quot;a percent sign string
                            .                                          &quot;Concatenated with
                             printf(&quot;%02x&quot;,char2nr(submatch(0)))       &quot;The hex value of the character we just matched
                                                                /g     &quot;Make this apply to ever match
                                                                  &lt;cr&gt; &quot;Actually run the command

That printf(&quot;%02x&quot;,char2nr(submatch(0))) garbage is terribly ungolfy.