MHFIXMSG(1mh)

πŸ“¦
version 1.8 Β· 3 versions
, in debian trixie, ubuntu noble, ubuntu questing, ubuntu resolute, ubuntu stonking

πŸ’– MHFIXMSG πŸ’–

okay bestie, welcome to mhfixmsg β€” that's the nmh MIME-email rewriter with various transformations. it lives in section 1mh of the manual, and it is genuinely one of the most useful little gremlins in the whole nmh toolbox. buckle up πŸŽ€


✨ NAME ✨

mhfixmsg β€” nmh's MIME-email rewriter with various transformations. it fixes your messy mail. it's giving glow-up energy fr fr πŸ’…


🌸 SYNOPSIS 🌸

mhfixmsg [-help] [-version] [+folder] [msgs | absolute pathname | -file
     file] [-decodetext 8bit|7bit|binary | -nodecodetext] [-decodetypes
     type/[subtype][,...]]  [-decodeheaderfieldbodies utf-8 |
     -nodecodeheaderfieldbodies] [-crlflinebreaks | -nocrlflinebreaks]
     [-textcharset charset | -notextcharset] [-reformat | -noreformat]
     [-replacetextplain | -noreplacetextplain] [-fixboundary |
     -nofixboundary] [-fixcte | -nofixcte] [-checkbase64 |
     -nocheckbase64] [-fixtype mimetype] [-outfile outfile] [-rmmproc
     program] [-normmproc] [-changecur | -nochangecur] [-verbose |
     -noverbose]

πŸ¦‹ DESCRIPTION πŸ¦‹

okay so here's the whole vibe: mhfixmsg rewrites MIME messages, applying specific transformations such as decoding of MIME-encoded message parts and repairing invalid MIME headers. that's the whole job description. simple. iconic. πŸ‘‘

MIME messages are specified in RFC 2045 to RFC 2049 (see mhbuild(1)). and bestie, let me put you on: the mhlist command is invaluable for viewing the content structure of MIME messages. like actually invaluable, no cap. meanwhile mhfixmsg passes non-MIME messages through without any transformations. and here's the part that makes it safe to spam: if no transformations apply to a MIME message, the original message or file is not modified or removed. so you can run mhfixmsg multiple times on a message and it will not eat your mail. safe. 🫢


πŸŽ€ the decoding switches πŸŽ€

The -decodetext switch enables a transformation to decode each base64 and quoted-printable text message part to the selected 8-bit, 7-bit, or binary encoding. if 7-bit is selected for a base64 part but it will only fit 8-bit, as defined by RFC 2045, then it will be decoded to 8-bit quoted-printable. similarly, with 8-bit, if the decoded text would be binary, then the part is not decoded (and a message will be displayed if -verbose is enabled).

Note that -decodetext binary can produce messages that are not compliant with RFC 5322, Β§2.1.1. so like… do that on purpose, not by accident, bestie 😭

When the -decodetext switch is enabled, each carriage return character that precedes a linefeed character is removed from text parts encoded in ASCII, ISO-8859-x, UTF-8, or Windows-12xx. just quietly tidying up behind the scenes. ✨

The -decodetypes switch specifies the message parts, by type and optionally subtype, to which -decodetext applies. its argument is a comma-separated list of type/subtype elements. if an element does not contain a subtype, then -decodetext applies to all subtypes of the type. the default -decodetypes includes text; it can be overridden, e.g., with -decodetypes text/plain to restrict -decodetext to just text/plain parts.


🌈 header field bodies 🌈

The -decodeheaderfieldbodies switch enables decoding of header field bodies to the specified character set. the -nodecodeheaderfieldbodies inhibits this transformation.

⚠️ heads up: the transformation can produce a message that does not conform with RFC 2047, §1, paragraph 6, because the decoded header field body could contain unencoded non-ASCII characters. that's why it is not enabled by default.

Decoding of most header field bodies, or to a character set that is different from that of the user's locale, requires that nmh be built with iconv(3); see mhparam(1) for how to determine whether your nmh installation includes that. (you can literally just go check. it's fine. go check. πŸƒβ™€οΈ)


πŸ’œ carriage returns, the whole saga πŸ’œ

By default, carriage return characters are preserved or inserted at the end of each line of text content. The -crlflinebreaks switch selects this behavior and is enabled by default.

The -nocrlflinebreaks switch causes carriage return characters to be stripped from, and not inserted in, text content when it is decoded and encoded.

Note that its use can cause the generation of MIME messages that do not conform with RFC 2046, Β§4.1.1, paragraph 1. so like, know what you're doing, queen πŸ’€

πŸ“ textcharset & reformat πŸ“

The -textcharset switch specifies that all text/plain parts of the message(s) should be converted to charset. charset conversions require that nmh be built with iconv(3); see mhparam(1) for how to determine whether your nmh installation includes that. to convert text parts other than text/plain, an external program can be used, via the -reformat switch. the -textcharset switch can also be used, depending on the nmh installation as described below, to specify the Content-Type charset parameter for text/plain parts added with -reformat.

The -reformat switch enables a transformation for text parts in the message. for each text part that is not text/plain and that does not have a corresponding text/plain in a multipart/alternative part, mhfixmsg looks for a mhfixmsg-format-text/subtype profile entry that matches the subtype of the part. if one is found and can be used to successfully convert the part to text/plain, mhfixmsg inserts that text/plain part at the beginning of the containing multipart/alternative part, if present. if not, it creates a multipart/alternative part. she's building structure out here. main character energy. πŸ—οΈ

With the -reformat switch, multipart/related parts are handled differently than multipart/alternative. if the multipart/related has only a single part that is not text/plain and can be converted to text/plain, a text/plain part is added and the type of the part is changed to multipart/alternative. if the multipart/related has more than one part but does not have a text/plain part, mhfixmsg tries to add one. iykyk. πŸ’…

The -replacetextplain switch broadens the applicability of -reformat, by always replacing a corresponding text/plain part, if one exists. if -verbose is enabled, the replacement will be shown as two steps: a removal of the text/plain part, followed by the usual insertion of a new part. out with the old, in with the new. periodt.

-reformat requires a profile entry for each text part subtype to be reformatted. the mhfixmsg-format-text/subtype profile entries are based on external conversion programs, and are used in the same way that mhshow uses its mhshow-show-text/subtype entries. when nmh is installed, it searches for a conversion program for text/html content, and if one is found, inserts a mhfixmsg-format-text/html entry in /etc/nmh/mhn.defaults. an entry of the same name in the user's profile takes precedence. the user can add entries for other text subtypes to their profile. your profile, your rules. πŸ‘‘

The character set (charset) of text/plain parts added by -reformat is determined by the external program that generates the content. detection of the content charset depends on how the nmh installation was configured. if a program, such as file with a --mime-encoding option, was found that can specify the charset of a file, then that will be used for the Content-Type charset parameter. to determine if your nmh was so configured, run mhparam mimeencodingproc and see if a non-empty string is displayed. (if it's empty… yeah. πŸ™ˆ)

If your nmh was not configured with a program to determine the charset of a file, then the value of the -textcharset switch is used. it is up to the user to ensure that the -textcharset value corresponds to the character set of the content generated by the external program. that's on you, bestie, not on nmh. πŸ’…


🧚 the fixer switches 🧚

The -fixboundary switch enables a transformation to repair the boundary portion of the Content-Type header field of the message to match the boundaries of the outermost multipart part of the message, if it does not. that condition is indicated by a "bogus multipart content in message" error message from mhlist and other nmh programs that parse MIME messages.

The -fixcte switch enables a transformation to change the Content-Transfer-Encoding from an invalid value to 8-bit in message parts with a Content-Type of multipart and message, as required by RFC 2045, Β§6.4. that condition is indicated by a "must be encoded in 7bit, 8bit, or binary" error message from mhlist and other nmh programs that parse MIME messages.

The -checkbase64 switch enables a check of the encoding validity in base64-encoded MIME parts. the check looks for a non-encoded text footer appended to a base64-encoded part. per RFC 2045 Β§6.8, the occurrence of a "=" character signifies the end of base-64 encoded content. if none is found, a heuristic is used: specifically, two consecutive invalid base64 characters signify the beginning of a plain text footer. if a text footer is found and this switch is enabled, mhfixmsg separates the base64-encoded and non-encoded content and places them in a pair of subparts to a newly constructed multipart/mixed part. that multipart/mixed part replaces the original base64-encoded part in the MIME structure of the message. she restructured it. πŸ’…

The -fixtype switch ensures that each part of the message has the correct MIME type shown in its Content-Type header. it may be repeated. it is typically used to replace "application/octet-stream" with a more descriptive MIME type. it may not be used for multipart and message types.


⭐ the unconditional ones ⭐

mhfixmsg applies two transformations unconditionally. the first removes an extraneous trailing semicolon from the parameter lists of MIME header field values. the second replaces RFC 2047 encoding with RFC 2231 encoding of name and filename parameters in Content-Type and Content-Disposition header field values, respectively. no switches needed, she just does it. slay. ✨


🫢 verbosity, exit status & files 🫢

The -verbose switch directs mhfixmsg to output informational message for each transformation applied. it's the narration track. πŸŽ™οΈ

The return status of mhfixmsg is 0 if all of the requested transformations are performed, or non-zero otherwise. (mhfixmsg will not decode to binary content with the default -decodetext setting, but a request to do so is not considered a failure, and is noted with -verbose.) if a problem is detected with any one of multiple messages such that the return status is non-zero, then none of the messages will be modified. all or nothing, bestie. it's giving commitment. πŸ’€

The -file file switch directs mhfixmsg to use the specified file as the source message, rather than a message from a folder. only one file argument may be provided. the -file switch is implied if file is an absolute pathname. if the file is -, then mhfixmsg accepts the source message on the standard input stream. if the -outfile switch is not enabled when using the standard input stream, mhfixmsg will not produce a transformed output message. (so like… nothing comes out. that's it. that's the output. πŸ«₯)

mhfixmsg, by default, transforms the message in place. if the -outfile switch is enabled, then mhfixmsg does not modify the input message or file, but instead places its output in the specified file. an outfile name of - specifies the standard output stream.

Combined with the -verbose switch, the -outfile switch can be used to show what transformations mhfixmsg would apply without actually applying them, e.g.,

mhfixmsg -outfile /dev/null -verbose

As always, this usage obeys any mhfixmsg switches in the user's profile. your profile is always in the room, like a chaperone. 🧐

-outfile can be combined with rcvstore to add a single transformed message to a different folder, e.g.,

            mhfixmsg -outfile - | \
            /usr/lib/mh/rcvstore +folder

okay so like, that's the pipeline bestie ✨ β€” and now let's get into which transformations actually hit which parts, because the answer is genuinely "it depends" and you deserve to know. 🫢


✨ Summary of Applicability ✨

So the transformations only touch the parts of a message that match a given content type and/or encoding. Here's the whole rundown, no gatekeeping:

  • -decodetext β€” hits base64 and quoted-printable encoded text parts
  • -decodetypes β€” this one limits which parts -decodetext actually applies to
  • -decodeheaderfieldbodies β€” applies to all message parts
  • -crlflinebreaks β€” text parts
  • -textcharset β€” text/plain parts
  • -reformat β€” text parts that are not text/plain
  • -fixboundary β€” the outermost multipart part
  • -fixcte β€” multipart or message part
  • -checkbase64 β€” base64 encoded parts
  • -fixtype β€” everything except multipart and message parts

🌸 Backup of Original Message/File 🌸

If mhfixmsg is gonna transform a message or a file, and you did not pass -outfile, bestie it backs up the original the exact same way rmm does. Like, same energy, same mechanics.

Specifically: it uses the rmmproc profile component, if it's there. If it's not there, mhfixmsg just moves the original message into a backup file. Simple as that.

  • -rmmproc β€” lets you override that profile component
  • -normmproc β€” disables the use of any rmmproc profile component entirely, and negates all the -rmmproc switches you gave it before
ngl, if you're the kind of person who panics when a file moves, maybe peek at your rmmproc settings first. just saying. πŸ™ˆ

πŸ”₯ Integration with inc πŸ”₯

mhfixmsg can run as an add-hook, exactly as described in /usr/share/doc/nmh/README-HOOKS. Quick heads-up though: add-hooks get called from all nmh programs that add a message to a folder, not just inc. So it's giving ✨main character✨ across the whole suite.

If that's too much for you, alternatively a simple shell alias or function can just call mhfixmsg right after a successful inc. One approach could be based on this:

msgs=`inc -format '%(msg)'`  &&  [ -n "$msgs" ]  &&  scan $msgs  &&
mhfixmsg -nochangecur $msgs

Another approach relies on adding a sequence to Unseen-Sequence β€” that's the one inc sets with the newly incorporated messages. Then you can just hand those over to mhfixmsg. An example is shown below. πŸ’…


πŸŽ€ Integration with procmail πŸŽ€

By way of example, here's an excerpt from a procmailrc file that filters messages through mhfixmsg before storing them in the user's nmh-workers folder. It also stashes the incoming message in the Backups folder under a filename generated by mkstemp β€” which is a non-POSIX utility for generating a temporary file. Alternatively, mhfixmsg could just be called on the message after it's stored. Either way, chef's kiss. πŸ‘‘

PATH = /usr/bin/mh:$PATH
LANG = en_US.utf8
MAILDIR = `mhparam path`
#### The Backups directory is relative to MAILDIR.
MKSTEMP = 'mkstemp -directory Backups -prefix mhfixmsg'
MHFIXMSG = 'mhfixmsg -noverbose -file - -outfile -'
STORE = /usr/lib/mh/rcvstore

:0 w: nmh-workers/procmail.$LOCKEXT
* ^TOnmh-workers@nongnu.org
| tee `$MKSTEMP` | $MHFIXMSG | $STORE +nmh-workers

🌈 EXAMPLES 🌈

⭐ Basic usage ⭐

Wanna run mhfixmsg on the current message in the current folder, with all the default transformations β€” fixing MIME boundaries and Content-Transfer-Encoding, decoding text and application/ics content parts to 8 bit, and adding a matching text/plain part where it's missing? Say less:

mhfixmsg -verbose

πŸ’– Specified folder and messages πŸ’–

Running it on specific messages but you don't want the informational chatter? Valid:

mhfixmsg +inbox last:4

πŸ¦‹ View without modification πŸ¦‹

By default mhfixmsg transforms the message in place. So if you just wanna peek at the MIME structure that would result from running it on the current message β€” without touching the actual message β€” do this:

mhfixmsg -outfile - | mhlist -file -

πŸ“ Search message without modification πŸ“

Wanna search the current message β€” which might contain base64 or quoted printable encoded text parts β€” without modifying it? The -outfile switch is your bestie:

mhfixmsg -outfile - | grep pattern

Little pro tip: -outfile can be abbreviated in usual MH fashion, e.g. down to -o. And heads up β€” the search runs on the entire message, not just the text parts. iykyk.

πŸ’œ Translate text/plain parts to UTF-8 πŸ’œ

Translating every text/plain part in the current message to UTF-8, on top of all the default transformations:

mhfixmsg -textcharset utf-8

✨ Fix all messages in a folder ✨

Run it across every message in a folder:

mhfixmsg +folder all

Alternatively, you can run it on each message separately, e.g. with a Bourne shell loop:

for msg in `pick +folder`; do mhfixmsg +folder $msg; done
The two appearances of the +folder switch in that command protect against concurrent context changes by other nmh command invocations. Not me being paranoid, it's literally in the manual. 😭

🌸 Run on newly incorporated messages 🌸

To run mhfixmsg on messages as they're incorporated:

inc  &&  mhfixmsg -nochangecur unseen

This assumes the Unseen-Sequence profile entry is set to unseen, as shown in mh-profile(5).


🌟 FILES 🌟

mhfixmsg hunts for mhn.defaults in multiple locations: absolute pathnames are accessed directly, tilde expansion is done on usernames, and files are searched for in the user's Mail directory as specified in their profile. If it's not there, the directory β€œ/etc/nmh” gets checked.

  • $HOME/.mh_profile β€” the user profile
  • /etc/nmh/mhn.defaults β€” default mhfixmsg conversion entries

πŸ‘‘ PROFILE COMPONENTS πŸ‘‘

  • Path: β€” to determine the user's nmh directory
  • Current-Folder: β€” to find the default current folder
  • rmmproc: β€” program to delete original messages or files

🫢 SEE ALSO 🫢

iconv(3), inc(1), mh-mkstemp(1), mh-profile(5), mhbuild(1), mhlist(1), mhparam(1), mhshow(1), procmail(1), procmailrc(5), rcvstore(1), rmm(1)


πŸ’… DEFAULTS πŸ’…

  • +folder defaults to the current folder
  • msgs defaults to cur
  • -decodetext 8bit
  • -decodetypes text,application/ics
  • -nodecodeheaderfieldbodies
  • -crlflinebreaks
  • -notextcharset
  • -reformat
  • -noreplacetextplain
  • -fixboundary
  • -fixcte
  • -checkbase64
  • -changecur
  • -noverbose

🧚 CONTEXT 🧚

If a folder is given, it becomes the current folder. The last message selected from a folder becomes the current message β€” unless the -nochangecur switch is enabled. And if you use the -file switch or an absolute pathname, the context will not be modified. no cap. ✨


and that's the whole page bestie, you ate it πŸ’– this was nmh-1.8, dated 2022-02-27, and yes it's MHFIXMSG(1mh) β€” go fix those messages, you absolute icon πŸŽ€πŸ¦‹β­

πŸ“¦ where this page comes from