RFC 2192

IMAP URL Scheme

πŸ“… published September 1997, from ietf

RFC 2192: IMAP URL Scheme πŸ’–

Okay bestie, here's the tea β˜•. This one is a Standards Track doc from the Network Working Group, written by C. Newman of Innosoft and dated September 1997. The title is "IMAP URL Scheme" πŸ¦‹. Its title block doesn't list anything it obsoletes or updates, so no drama there ✨.

  • RFC number: 2192
  • Title: IMAP URL Scheme
  • Author: C. Newman, Innosoft
  • Category: Standards Track
  • Date: September 1997
  • Obsoletes / Updates: none listed in the title block

Status of this memo ✨

Okay so this is an Internet standards track protocol for the whole Internet community, and it's literally asking for discussion and suggestions for improvements 🫢. For the official standardization state and status of this protocol, check the current edition of the "Internet Official Protocol Standards" (STD 1) πŸ“š. Distribution of this memo is unlimited, so share it with everyone πŸ’….

Abstract 🌸

IMAP [IMAP4] is a rich protocol for accessing remote message stores, and it's honestly so iconic πŸ‘‘. It's an ideal mechanism for reading public mailing list archives, plus private and shared message stores too. This document defines a URL scheme for referencing objects on an IMAP server πŸ”—.

1. Conventions used in this document πŸ“š

Quick vocab check πŸ€“. The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as defined in "Key words for use in RFCs to Indicate Requirement Levels" [KEYWORDS].

2. IMAP scheme πŸ¦‹

The IMAP URL scheme is what designates IMAP servers, mailboxes, messages, MIME bodies [MIME], and search programs on Internet hosts you can reach over the IMAP protocol 🌈.

The IMAP URL follows the common Internet scheme syntax from RFC 1738 [BASIC-URL], with one big exception: clear text passwords are not permitted πŸ™…β€β™€οΈ. If :<port> is left out, the port defaults to 143 πŸ”’.

An IMAP URL takes one of these four forms:

imap://<iserver>/
imap://<iserver>/<enc_list_mailbox>;TYPE=<list_type>
imap://<iserver>/<enc_mailbox>[uidvalidity][?<enc_search>]
imap://<iserver>/<enc_mailbox>[uidvalidity]<iuid>[isection]

So the first form points at a server, the second at a list of mailboxes, the third at a mailbox's contents or a set of messages from a search, and the last at one specific message or message part πŸ’–. Heads up, the syntax here is informal πŸ™ˆ. The authoritative formal syntax for IMAP URLs is in section 11 ✨.

3. IMAP User Name and Authentication Mechanism πŸ”

A user name and/or authentication mechanism can be supplied 🧚. They get used with the "LOGIN" or "AUTHENTICATE" commands once the client has connected to the IMAP server πŸ”Œ. If there's no user name or authentication mechanism, the user name anonymous is used with "LOGIN", and the password is the Internet e-mail address of the end user accessing the resource πŸ’Œ. If the URL doesn't supply a user name, the program interpreting the IMAP URL SHOULD request one from the user if necessary πŸ™‹β€β™€οΈ.

An authentication mechanism can be written by adding ;AUTH=<enc_auth_type> to the end of the user name πŸŽ€. When an <enc_auth_type> is given, the client SHOULD request appropriate credentials from that mechanism and use the "AUTHENTICATE" command instead of "LOGIN" πŸ”‘. If no user name is specified, one SHOULD be obtained from the mechanism or requested from the user, as appropriate πŸ’….

The string ;AUTH=* means the client SHOULD select an appropriate authentication mechanism ✨. It MAY use any mechanism listed in the CAPABILITY command, or use an out of band security service that results in a PREAUTH connection πŸ¦‹. If there's no user name and no appropriate authentication mechanism is available, the client SHOULD fall back to anonymous login as described above 🌸. This lets one URL grant read-write access to authorized users and read-only anonymous access to everyone else πŸ‘‘.

If a user name is included with no authentication mechanism, then ;AUTH=* is assumed 🌈.

Now, real talk 🫢. URLs can easily come from untrusted sources, so you have to be careful when resolving a URL that requires or requests any sort of authentication 🚨. If credentials get handed to the wrong server, the user's account security can be compromised πŸ’€. So the program resolving the URL should make sure it meets at least one of these criteria:

  1. The URL comes from a trusted source, like a referral server the client has validated and trusts according to site policy. User entry of the URL may or may not count as a trusted source, depending on the user's experience level and site policy 🧐.
  2. Explicit local site policy permits the client to connect to the server in the URL. For example, if the client knows the site domain name, site policy may dictate that any hostname ending in that domain is trusted 🏑.
  3. The user confirms that connecting to that domain name with the specified credentials and/or mechanism is permitted πŸ™‹β€β™€οΈ.
  4. A mechanism is used that validates the server before passing potentially compromising client credentials πŸ›‘οΈ.
  5. An authentication mechanism is used that will not reveal information to the server which could be used to compromise future connections πŸ”’.

URLs without a user name must be treated with extra care, since they're more likely to compromise the user's primary account 😬. A URL containing ;AUTH=* must also be treated with extra care, since it might fall back on a weaker security mechanism πŸ₯Ί. Finally, clients are discouraged from using a plain text password as a fallback with ;AUTH=* unless the connection has strong encryption (e.g. a key length of greater than 56 bits) πŸ”.

A program interpreting IMAP URLs MAY cache open connections to an IMAP server for later re-use ♻️. If a URL contains a user name, only connections authenticated as that user may be re-used πŸ‘€. If a URL has no user name or authentication mechanism, only an anonymous connection may be re-used πŸ•ΆοΈ. If a URL contains an authentication mechanism without a user name, then any non-anonymous connection may be re-used 🌸.

Watch out 🚨: if unsafe or reserved characters such as " " or ";" show up in the user name or authentication mechanism, they MUST be encoded as described in RFC 1738 [BASIC-URL]. No skipping this step, bestie πŸ’….

4. IMAP server πŸ–₯️

An IMAP URL that refers to an IMAP server has this form:

imap://<iserver>/

A program interpreting this URL would issue the standard set of commands it uses to show the contents of an IMAP server πŸ‘€. This is likely to be semantically equivalent to one of these URLs:

imap://<iserver>/;TYPE=LIST
imap://<iserver>/;TYPE=LSUB

If the program supports mailbox subscriptions, it SHOULD use the LSUB form πŸ’œ.

5. Lists of mailboxes πŸ“¬

An IMAP URL that refers to a list of mailboxes looks like this:

imap://<iserver>/<enc_list_mailbox>;TYPE=<list_type>

The <list_type> may be either "LIST" or "LSUB", and it's case insensitive πŸ”€. The field ;TYPE=<list_type> MUST be included, no exceptions πŸ™…β€β™€οΈ.

The <enc_list_mailbox> is any argument that works for the list_mailbox field of the IMAP [IMAP4] LIST or LSUB commands 🧩. You can leave <enc_list_mailbox> out, and then the program interpreting the IMAP URL may use "*" or "%" instead 🌈. The program SHOULD use "%" if it supports a hierarchical view, and otherwise it SHOULD use "*" 🌳.

Note that if unsafe or reserved characters such as " " or "%" are present in <enc_list_mailbox>, they MUST be encoded as described in RFC 1738 [BASIC-URL] πŸ”. If the character "/" is present in enc_list_mailbox, it SHOULD NOT be encoded πŸ™…β€β™€οΈ.

6. Lists of messages πŸ’Œ

An IMAP URL that refers to a list of messages has this form:

imap://<iserver>/<enc_mailbox>[uidvalidity][?<enc_search>]

The <enc_mailbox> field is used as the argument to the IMAP4 "SELECT" command 🎯. If unsafe or reserved characters such as " ", ";", or "?" are present in <enc_mailbox>, they MUST be encoded as described in RFC 1738 [BASIC-URL] πŸ”. If the character "/" is present in enc_mailbox, it SHOULD NOT be encoded πŸ™…β€β™€οΈ.

The [uidvalidity] field is optional 🌸. If it's there, it MUST be the argument to the IMAP4 UIDVALIDITY status response at the time the URL was created ⏰. The program interpreting the IMAP URL SHOULD use this to figure out if the URL is stale πŸ•°οΈ.

The [?<enc_search>] field is optional too 🧚. If it's not there, the program interpreting the URL SHOULD present the contents of the mailbox πŸ“©. If it's there, it SHOULD be used as the arguments after an IMAP4 SEARCH command πŸ”. Unsafe characters such as " " (which are likely to show up in <enc_search>) get encoded as described in RFC 1738 [BASIC-URL] πŸ’….

7. A specific message or message part πŸŽ€

An IMAP URL that refers to one specific message or message part looks like this:

imap://<iserver>/<enc_mailbox>[uidvalidity]<iuid>[isection]

The <enc_mailbox> and [uidvalidity] work exactly as defined above πŸ”.

If [uidvalidity] is present in this form, it SHOULD be used by the program interpreting the URL to determine if the URL is stale πŸ•°οΈ.

The <iuid> refers to an IMAP4 message UID, and it SHOULD be used as the <set> argument to the IMAP4 "UID FETCH" command πŸ¦‹.

The [isection] field is optional 🌈. If it's not present, the URL refers to the entire Internet message, as returned by the IMAP command UID FETCH <uid> BODY.PEEK[] πŸ“¨. If it is present, the URL refers to the object returned by a UID FETCH <uid> BODY.PEEK[<section>] command 🎁. To figure out the type of that object, you can use a UID FETCH <uid> BODYSTRUCTURE command and find the right part in the resulting BODYSTRUCTURE πŸ”. Note that unsafe characters in [isection] MUST be encoded as described in [BASIC-URL] πŸ”.

8. Relative IMAP URLs ✨

okay so relative IMAP URLs are totally allowed πŸ’– and they get resolved using the rules in RFC 1808 [REL-URL], with ONE exception πŸ‘€. In IMAP URLs, parameters are treated as part of the normal path when it comes to relative URL resolution. This is believed to be how the installed base already works πŸŽ€, and it's likely to get documented in a future revision of the relative URL spec 🧚

some other things that are lowkey super important 🫢

  • The <iauth> grammar element counts as part of the user name when you're resolving relative IMAP URLs. So unless the relative URL brings its own login/server spec, the authentication mechanism gets inherited from the base IMAP URL πŸ’…
  • URLs always use "/" as the hierarchy delimiter when resolving paths in relative URLs. IMAP4 lets any hierarchy delimiter show up in mailbox names though, so relative mailbox paths will only work if the mailbox uses "/" as its delimiter πŸ™ˆ. Relative URLs can still be used on mailboxes with other delimiters, but then the entire mailbox name MUST be specified in the relative URL, or inherited as a whole from the base URL 😭
  • The base URL for a list of mailboxes or messages that an IMAP URL referred to is always the referring IMAP URL itself πŸ’œ. For a message or message part, it can get more complicated πŸ’€. The program interpreting the relative URL has to check the headers of the MIME entity and any enclosing MIME entities to find the "Content-Base" and "Content-Location" headers. Those headers are used to determine the base URL as defined in [HTTP] 🌸. For example, if the referring IMAP URL contains a "/;SECTION=1.2" parameter, then the MIME headers for section 1.2, for section 1, and for the enclosing message itself SHOULD be checked in that order for "Content-Base" or "Content-Location" headers πŸ”₯

9. Multinational Considerations 🌈

IMAP4 [IMAP4] section 5.1.3 has a convention for encoding non-US-ASCII characters in IMAP mailbox names πŸ“. Since that convention is private to IMAP, IMAP's encoding needs to be converted into one that a URL display program can actually interpret πŸ‘‘. So IMAP's modified UTF-7 encoding for mailboxes MUST be converted to UTF-8 [UTF8] ✨. 8-bit characters aren't allowed in URLs, so the UTF-8 characters get encoded the way the URL specification [BASIC-URL] requires πŸ¦‹. Appendix A has sample code that shows this conversion πŸ’–.


10. Examples πŸŽ€

okay this part is my fave 😭. These examples show how an IMAP4 client program might turn various IMAP4 URLs into a series of IMAP4 commands. Commands sent from the client to the server get the "C:" prefix, and responses from the server to the client get the "S:" prefix πŸ’…

The URL:

<imap://minbari.org/gray-council;UIDVALIDITY=385759045/;UID=20>

Results in the following client commands:

    <connect to minbari.org, port 143>
    C: A001 LOGIN ANONYMOUS sheridan@babylon5.org
    C: A002 SELECT gray-council
    <client verifies the UIDVALIDITY matches>
    C: A003 UID FETCH 20 BODY.PEEK[]

The URL:

<imap://michael@minbari.org/users.*;type=list>

Results in the following client commands:

  <client requests password from user>
  <connect to minbari.org imap server, activate strong encryption>
  C: A001 LOGIN MICHAEL zipper
  C: A002 LIST "" users.*

The URL:

<imap://psicorp.org/~peter/%E6%97%A5%E6%9C%AC%E8%AA%9E/
%E5%8F%B0%E5%8C%97>

Results in the following client commands:

  <connect to psicorp.org, port 143>
  C: A001 LOGIN ANONYMOUS bester@psycop.psicorp.org
  C: A002 SELECT ~peter/&ZeVnLIqe-/&U,BTFw-
  <commands the client uses for viewing the contents of a mailbox>

The URL:

<imap://;AUTH=KERBEROS_V4@minbari.org/gray-council/;uid=20/
;section=1.2>

Results in the following client commands:

    <connect to minbari.org, port 143>
    C: A001 AUTHENTICATE KERBEROS_V4
    <authentication exchange>
    C: A002 SELECT gray-council
    C: A003 UID FETCH 20 BODY.PEEK[1.2]

If the following relative URL is located in that body part:

<;section=1.4>

This could result in the following client commands:

    C: A004 UID FETCH 20 (BODY.PEEK[1.2.MIME]
          BODY.PEEK[1.MIME]
          BODY.PEEK[HEADER.FIELDS (Content-Base Content-Location)])
    <Client looks for Content-Base or Content-Location headers in
     result.  If no such headers, then it does the following>
    C: A005 UID FETCH 20 BODY.PEEK[1.4]

The URL:

<imap://;AUTH=*@minbari.org/gray%20council?SUBJECT%20shadows>

Could result in the following:

    <connect to minbari.org, port 143>
    C: A001 CAPABILITY
    S: * CAPABILITY IMAP4rev1 AUTH=GSSAPI
    S: A001 OK
    C: A002 AUTHENTICATE GSSAPI
    <authentication exchange>
    S: A002 OK user lennier authenticated
    C: A003 SELECT "gray council"
    ...
    C: A004 SEARCH SUBJECT shadows
    S: * SEARCH 8 10 13 14 15 16
    S: A004 OK SEARCH completed
    C: A005 FETCH 8,10,13:16 ALL
    ...

NOTE: In this final example, the client has implementation dependent choices. The authentication mechanism could be anything, including PREAUTH. And the final FETCH command could fetch more or less information about the messages, depending on what it wishes to display to the user πŸ™ˆ


11. Security Considerations πŸ’€

this section is serious, bestie 🚨. Security considerations discussed in the IMAP specification [IMAP4] and the URL specification [BASIC-URL] are relevant πŸ”. Security considerations related to authenticated URLs are covered in section 3 of this document.

Many email clients store the plain text password for later use after logging into an IMAP server. Those clients MUST NOT use a stored password in response to an IMAP URL without explicit permission from the user to supply that password to the specified host name πŸ›‘


12. ABNF for IMAP URL scheme 🧚

this part is the grammar πŸ€“. It uses ABNF as defined in RFC 822 [IMAIL]. Terminals from the BNF for IMAP [IMAP4] and URLs [BASIC-URL] are also used. Strings are not case sensitive, and free insertion of linear-white-space is not permitted πŸ™…β€β™€οΈ

achar            = uchar / "&" / "=" / "~"
                        ; see [BASIC-URL] for "uchar" definition

bchar            = achar / ":" / "@" / "/"

enc_auth_type    = 1*achar
                      ; encoded version of [IMAP-AUTH] "auth_type"

enc_list_mailbox = 1*bchar
                        ; encoded version of [IMAP4] "list_mailbox"

enc_mailbox      = 1*bchar
                        ; encoded version of [IMAP4] "mailbox"

enc_search       = 1*bchar
                        ; encoded version of search_program below

enc_section      = 1*bchar
                        ; encoded version of section below

enc_user         = 1*achar
                        ; encoded version of [IMAP4] "userid"

imapurl          = "imap://" iserver "/" [ icommand ]

iauth            = ";AUTH=" ( "*" / enc_auth_type )

icommand         = imailboxlist / imessagelist / imessagepart

imailboxlist     = [enc_list_mailbox] ";TYPE=" list_type

imessagelist     = enc_mailbox [ "?" enc_search ] [uidvalidity]

imessagepart     = enc_mailbox [uidvalidity] iuid [isection]

isection         = "/;SECTION=" enc_section

iserver          = [iuserauth "@"] hostport
                        ; See [BASIC-URL] for "hostport" definition

iuid             = "/;UID=" nz_number
                        ; See [IMAP4] for "nz_number" definition

iuserauth        = enc_user [iauth] / [enc_user] iauth

list_type        = "LIST" / "LSUB"

search_program   = ["CHARSET" SPACE astring SPACE]
                   search_key *(SPACE search_key)
                      ; IMAP4 literals may not be used
                      ; See [IMAP4] for "astring" and "search_key"

section          = section_text / (nz_number *["." nz_number]
                    ["." (section_text / "MIME")])
                    ; See [IMAP4] for "section_text" and "nz_number"

uidvalidity      = ";UIDVALIDITY=" nz_number
                        ; See [IMAP4] for "nz_number" definition

13. References πŸ“š

[BASIC-URL] Berners-Lee, Masinter, McCahill, "Uniform Resource Locators (URL)", RFC 1738, CERN, Xerox Corporation, University of Minnesota, December 1994.

<ftp://ds.internic.net/rfc/rfc1738.txt>

[IMAP4] Crispin, M., "Internet Message Access Protocol - Version 4rev1", RFC 2060, University of Washington, December 1996.

<ftp://ds.internic.net/rfc/rfc2060.txt>

[IMAP-AUTH] Myers, J., "IMAP4 Authentication Mechanism", RFC 1731, Carnegie-Mellon University, December 1994.

<ftp://ds.internic.net/rfc/rfc1731.txt>

[HTTP] Fielding, Gettys, Mogul, Frystyk, Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", RFC 2068, UC Irvine, DEC, MIT/LCS, January 1997.

<ftp://ds.internic.net/rfc/rfc2068.txt>

[IMAIL] Crocker, "Standard for the Format of ARPA Internet Text Messages", STD 11, RFC 822, University of Delaware, August 1982.

<ftp://ds.internic.net/rfc/rfc822.txt>

[KEYWORDS] Bradner, "Key words for use in RFCs to Indicate Requirement Levels", RFC 2119, Harvard University, March 1997.

<ftp://ds.internic.net/rfc/rfc2119.txt>

[MIME] Freed, N., Borenstein, N., "Multipurpose Internet Mail Extensions", RFC 2045, Innosoft, First Virtual, November 1996.

<ftp://ds.internic.net/rfc/rfc2045.txt>

[REL-URL] Fielding, "Relative Uniform Resource Locators", RFC 1808, UC Irvine, June 1995.

<ftp://ds.internic.net/rfc/rfc1808.txt>

[UTF8] Yergeau, F. "UTF-8, a transformation format of Unicode and ISO 10646", RFC 2044, Alis Technologies, October 1996.

<ftp://ds.internic.net/rfc/rfc2044.txt>


14. Author's Address πŸ¦‹

Chris Newman
Innosoft International, Inc.
1050 Lakes Drive
West Covina, CA 91790 USA
EMail: chris.newman@innosoft.com

Appendix A. Sample code πŸ’…

okay so this is sample C source code for converting between URL paths and IMAP mailbox names 🧁. It takes into account the mapping between IMAP's modified UTF-7 [IMAP4] and hex-encoded UTF-8, which fits URLs way better 🌸. Heads up: this code hasn't been rigorously tested, and it doesn't necessarily behave reasonably with invalid input. But it should still be a useful example ✨. It only converts the mailbox portion of the URL, and it doesn't deal with parameters, query, or server components πŸ™ˆ

#include <stdio.h>
#include <string.h>

/* hexadecimal lookup table */
static char hex[] = "0123456789ABCDEF";

/* URL unsafe printable characters */
static char urlunsafe[] = " \"#%&+:;<=>?@[\\]^`{|}";

/* UTF7 modified base64 alphabet */
static char base64chars[] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
#define UNDEFINED 64

/* UTF16 definitions */
#define UTF16MASK       0x03FFUL
#define UTF16SHIFT      10
#define UTF16BASE       0x10000UL
#define UTF16HIGHSTART  0xD800UL
#define UTF16HIGHEND    0xDBFFUL
#define UTF16LOSTART    0xDC00UL
#define UTF16LOEND      0xDFFFUL

Okay bestie, the big converters are here πŸ’–βœ¨ Both functions are literally the main event: one turns an IMAP mailbox name into a URL path, and the other goes the other way. πŸ¦‹

/* Convert an IMAP mailbox to a URL path
 *  dst needs to have roughly 4 times the storage space of src
 *    Hex encoding can triple the size of the input
 *    UTF-7 can be slightly denser than UTF-8
 *     (worst case: 8 octets UTF-7 becomes 9 octets UTF-8)
 */
void MailboxToURL(char *dst, char *src)
{
    unsigned char c, i, bitcount;
    unsigned long ucs4, utf16, bitbuf;
    unsigned char base64[256], utf8[6];
    /* initialize modified base64 decoding table */
    memset(base64, UNDEFINED, sizeof (base64));
    for (i = 0; i < sizeof (base64chars); ++i) {
        base64[base64chars[i]] = i;
    }

    /* loop until end of string */
    while (*src != '\0') {
        c = *src++;
        /* deal with literal characters and &- */
        if (c != '&' || *src == '-') {
            if (c < ' ' || c > '~' || strchr(urlunsafe, c) != NULL) {
                /* hex encode if necessary */
                dst[0] = '%';
                dst[1] = hex[c >> 4];
                dst[2] = hex[c & 0x0f];
                dst += 3;
            } else {
                /* encode literally */
                *dst++ = c;
            }
            /* skip over the '-' if this is an &- sequence */
            if (c == '&') ++src;
        } else {
        /* convert modified UTF-7 -> UTF-16 -> UCS-4 -> UTF-8 -> HEX */
            bitbuf = 0;
            bitcount = 0;
            ucs4 = 0;
            while ((c = base64[(unsigned char) *src]) != UNDEFINED) {
                ++src;
                bitbuf = (bitbuf << 6) | c;
                bitcount += 6;
                /* enough bits for a UTF-16 character? */
                if (bitcount >= 16) {
                    bitcount -= 16;
                    utf16 = (bitcount ? bitbuf >> bitcount
                             : bitbuf) & 0xffff;
                    /* convert UTF16 to UCS4 */
                    if
                    (utf16 >= UTF16HIGHSTART && utf16 <= UTF16HIGHEND) {
                        ucs4 = (utf16 - UTF16HIGHSTART) << UTF16SHIFT;
                        continue;
                    } else if
                    (utf16 >= UTF16LOSTART && utf16 <= UTF16LOEND) {
                        ucs4 += utf16 - UTF16LOSTART + UTF16BASE;
                    } else {
                        ucs4 = utf16;
                    }
                    /* convert UTF-16 range of UCS4 to UTF-8 */
                    if (ucs4 <= 0x7fUL) {
                        utf8[0] = ucs4;
                        i = 1;
                    } else if (ucs4 <= 0x7ffUL) {
                        utf8[0] = 0xc0 | (ucs4 >> 6);
                        utf8[1] = 0x80 | (ucs4 & 0x3f);
                        i = 2;
                    } else if (ucs4 <= 0xffffUL) {
                        utf8[0] = 0xe0 | (ucs4 >> 12);
                        utf8[1] = 0x80 | ((ucs4 >> 6) & 0x3f);
                        utf8[2] = 0x80 | (ucs4 & 0x3f);
                        i = 3;
                    } else {
                        utf8[0] = 0xf0 | (ucs4 >> 18);
                        utf8[1] = 0x80 | ((ucs4 >> 12) & 0x3f);
                        utf8[2] = 0x80 | ((ucs4 >> 6) & 0x3f);
                        utf8[3] = 0x80 | (ucs4 & 0x3f);
                        i = 4;
                    }
                    /* convert utf8 to hex */
                    for (c = 0; c < i; ++c) {
                        dst[0] = '%';
                        dst[1] = hex[utf8[c] >> 4];
                        dst[2] = hex[utf8[c] & 0x0f];
                        dst += 3;
                    }
                }
            }
            /* skip over trailing '-' in modified UTF-7 encoding */
            if (*src == '-') ++src;
        }
    }
    /* terminate destination string */
    *dst = '\0';
}

/* Convert hex coded UTF-8 URL path to modified UTF-7 IMAP mailbox
 *  dst should be about twice the length of src to deal with non-hex
 *  coded URLs
 */
void URLtoMailbox(char *dst, char *src)
{
   unsigned int utf8pos, utf8total, i, c, utf7mode, bitstogo, utf16flag;
   unsigned long ucs4, bitbuf;
   unsigned char hextab[256];

    /* initialize hex lookup table */
    memset(hextab, 0, sizeof (hextab));
    for (i = 0; i < sizeof (hex); ++i) {
        hextab[hex[i]] = i;
        if (isupper(hex[i])) hextab[tolower(hex[i])] = i;
    }

    utf7mode = 0;
    utf8total = 0;
    bitstogo = 0;
    while ((c = *src) != '\0') {
        ++src;
        /* undo hex-encoding */
        if (c == '%' && src[0] != '\0' && src[1] != '\0') {
            c = (hextab[src[0]] << 4) | hextab[src[1]];
            src += 2;
        }
        /* normal character? */
        if (c >= ' ' && c <= '~') {
            /* switch out of UTF-7 mode */
            if (utf7mode) {
                if (bitstogo) {
                *dst++ = base64chars[(bitbuf << (6 - bitstogo)) & 0x3F];
                }
                *dst++ = '-';
                utf7mode = 0;
            }
            *dst++ = c;
            /* encode '&' as '&-' */
            if (c == '&') {
                *dst++ = '-';
            }
            continue;
        }
        /* switch to UTF-7 mode */
        if (!utf7mode) {
            *dst++ = '&';
            utf7mode = 1;
        }
        /* Encode US-ASCII characters as themselves */
        if (c < 0x80) {
            ucs4 = c;
            utf8total = 1;
        } else if (utf8total) {
            /* save UTF8 bits into UCS4 */
            ucs4 = (ucs4 << 6) | (c & 0x3FUL);
            if (++utf8pos < utf8total) {
                continue;
            }

        } else {
            utf8pos = 1;
            if (c < 0xE0) {
                utf8total = 2;
                ucs4 = c & 0x1F;
            } else if (c < 0xF0) {
                utf8total = 3;
                ucs4 = c & 0x0F;
            } else {
                /* NOTE: can't convert UTF8 sequences longer than 4 */
                utf8total = 4;
                ucs4 = c & 0x03;
            }
            continue;
        }
        /* loop to split ucs4 into two utf16 chars if necessary */
        utf8total = 0;
        do {
            if (ucs4 >= UTF16BASE) {
                ucs4 -= UTF16BASE;
                bitbuf = (bitbuf << 16) | ((ucs4 >> UTF16SHIFT)
                                           + UTF16HIGHSTART);
                ucs4 = (ucs4 & UTF16MASK) + UTF16LOSTART;
                utf16flag = 1;
            } else {
                bitbuf = (bitbuf << 16) | ucs4;
                utf16flag = 0;
            }
            bitstogo += 16;
            /* spew out base64 */
            while (bitstogo >= 6) {
                bitstogo -= 6;
                *dst++ = base64chars[(bitstogo ? (bitbuf >> bitstogo)
                               : bitbuf)
                                     & 0x3F];
            }
        } while (utf16flag);
    }
    /* if in UTF-7 mode, finish in ASCII */
    if (utf7mode) {
        if (bitstogo) {
            *dst++ = base64chars[(bitbuf << (6 - bitstogo)) & 0x3F];
        }
        *dst++ = '-';
    }
    /* tie off string */
    *dst = '\0';
}

That's the whole code party! πŸŽ€ Both converters are here in full, comments and all. πŸŒˆπŸ‘‘

πŸ“¦ where this page comes from