Compare commits

...

360 Commits

Author SHA1 Message Date
Leah Neukirchen 6adf988980 mdeliver: fix too eager use of MAILDIR_COLON 21 hours ago
midipix b078f8c19e Maildir: support winnt/ntfs based systems (currently midipix).
Per the Maildir specification, the info (trailing) part of message
filenames is identified by a <colon><spec_version><comma>; however,
on WINNT/NTFS, a colon may not be part of a filename, therefore use
a <semicolon> instead. Since the default remains to use a <colon>,
all targets (other than those definining the __winnt__ macro)
should remain unaffected by this change.
22 hours ago
Silvan Jegen d5e202b30f fix calloc argument order
These were found through gcc warnings.

Message-ID: <20240518093029.4799-1-s.jegen@gmail.com>
4 weeks ago
Leah Neukirchen a4502e83c4 mseq: delay loading of the sequence until we need it
This fixes many cases where mseq is called from a wrapper
but only file names are passed.

Reported by ccx.
1 month ago
Leah Neukirchen cf5493bb19 mmime: don't create empty lines when wrapping long words in headers.
Reported by Solène Rapenne.
3 months ago
Leah Neukirchen 76f5656401 mblaze-profile: mention Delivered-To
This was forgotten as part of e1e5bfe5f.
5 months ago
Johannes Thyssen Tishman 5310c4ea38 mcolor: Add coloring support for diffs
Closes: #249 [via git-merge-pr]
9 months ago
Leah Neukirchen 59ee2e0ab9 mgpg: use quiet to only print errors
Else it can happen that gpg output is prepended to the message we
want to decode, and recursive decoding fails.
11 months ago
Meudwy 07d064fc11 mdirs: add Maildir profile key
When `mdirs` is executed without any arguments, look for the `Maildir`
key in the profile and use that instead (if set).

Closes: #245 [via git-merge-pr]
11 months ago
Meudwy 75de7d47da minc: read directory names from stdin
This matches `mlist` where it can take directories as arguments or via
stdin.

Closes: #244 [via git-merge-pr]
11 months ago
Leah Neukirchen 43f2cb8b49 mscan: only spawn pager when stdout is a tty
We still get the terminal size for when users manually page, e.g.
mscan | less

This is consistent with how mshow already works.

Fixes #243.
11 months ago
Johannes Thyssen Tishman 23a9e7022e Quote command expansion to prevent word splitting
This is necessary to support maildir paths that contain spaces.

Closes: #242 [via git-merge-pr]
12 months ago
Johannes Thyssen Tishman ce435c8f6f Add Drafts profile key
Allow the user to set a Drafts key in profile to store draft messages
and sent messages separately if Outbox is set.
12 months ago
Mark Hills 786238551a Document a reasonable practice for deleting mails
Discussed in #236.
1 year ago
Leah Neukirchen 1838158ef1 blaze822_priv.h: use proper lc/uc
Mail headers may contain characters where the simple definition breaks,
which results in wrong formatting on output.

Fixes #235.
1 year ago
Leah Neukirchen cccf01cb9a mrep/mbnc: use mseq -r as leading whitespace is now significant.
Reported by lhynes.
2 years ago
Leah Neukirchen 2365f48f06 mcolor: print out form-feed 2 years ago
Michael Forney 74e77845c3 mmime: include strings.h for strncasecmp 2 years ago
Michael Forney 1babebc12c mlist: use fixed-width integer types for struct linux_dirent64 d_ino and d_off 2 years ago
codesoap 8d543fdb21 mcom: allow spaces in sequence for mbnc and mrep
Closes: #231 [via git-merge-pr]
2 years ago
Omar Polo 9d66764434 mrefile/mdeliver: -M needs the `fattr' pledge promise for utimes(2)
Message-Id: <2Z5ISO2SNIGZU.2E8B1F41F84RV@venera>
2 years ago
gco e6813fd07c solaris needs termios.h and __EXTENSIONS__ for TIOCGWINSZ and winsize 2 years ago
gco 877569a6f3 if -fstack-protector-strong is used, it needs to be specified for both compiling and linking
Closes: #229 [via git-merge-pr]
2 years ago
jgart bbc754e2a1 List commands ~respectively
Closes: #228 [via git-merge-pr]
2 years ago
Alyssa Ross bb3dacc604 blaze822: don't try to open /dev/stdin
Use the same check is blaze822() as in blaze822_file() to ensure that
we don't try to open /dev/stdin, which is non-POSIX.
Message-Id: <20220523170921.2623516-1-hi@alyssa.is>
2 years ago
Julian Rother 793e22ecb7 mthread: reduce memory usage
mthread keeps header data of all messages in memory until it exits without
ever using it. With this change it frees the header data of each message
right after processing it.

Closes: #222 [via git-merge-pr]
2 years ago
Leah Neukirchen 47c5707d50 add contrib/mopenall 2 years ago
Leah Neukirchen cd5bc471f3 man: misc manpages nits
Found by Omar Polo.
2 years ago
Lucas e951b0ba27 mmime: allow to relax body line length limit
Message-Id: <10e22a318b5c70d89432d85ecd8cb8ce1796a51d.1643116719.git.lucas@sexy.is>
2 years ago
Leah Neukirchen fa27d73ddd mshow: don't spawn pager with -t 2 years ago
Leah Neukirchen 3496545358 mflow: fixed lines were not wrapped, add test suite
Reported by ninewise.
2 years ago
Leah Neukirchen a60147f633 NEWS.md: update for 1.2 3 years ago
Stacy Harper e1e5bfe5ff mcom: take Delivered-To into account for choosing From address
I just received a mail coming from a Google Group mail list and it had
the group email as To. So my reply used the default configured mail as
from and not the correct one.

Googles mails provide the target email as Delivered-To. This patch add
this check before the To to prevent this error.

Closes: #217 [via git-merge-pr]
3 years ago
Leah Neukirchen e44aaad66e mlesskey.example: clarify installation 3 years ago
Leah Neukirchen b962fb6be0 rfc2047: skip whitespace everywhere during base64 decoding
> The encoded output stream must be represented in lines of no more
> than 76 characters each.  All line breaks or other characters not
> found in Table 1 must be ignored by decoding software.  In base64
> data, characters other than those in Table 1, line breaks, and other
> white space probably indicate a transmission error, about which a
> warning message or even a message rejection might be appropriate
> under some circumstances.
3 years ago
Leah Neukirchen 958e3e08bd mless: prefer setting LESSKEYIN and .mlesskey
Fixes #215.
3 years ago
Leah Neukirchen fcd8428b72 mrep/mbnc/mfwd: fix sequence after changing flags of (likely) cur
Else tools like mless lose track of cur and jump around after these
actions.

Closes #214.
3 years ago
Leah Neukirchen 41bd429452 blaze822: blaze822/blaze822_mem: detect line ending before scanning end of header
A mail using CRLF which contained (for some reason) a LFLF pair would
be misparsed as the header was read until the LFLF.

Instead, scan for the first LF, check if it's preceded by CR,
and then search for the proper header terminator only.

Closes #212.
3 years ago
Leah Neukirchen 4be6e0ce91 blaze822: remove blaze822_mmap, never used. 3 years ago
Leah Neukirchen 4ccf2f08c1 mmime: ensure no empty parts are generated after an include.
Reported by lhynes.
3 years ago
Léo Villeveygoux 65d43e3cea mpick: remove dead code
It would load the seq file even when unused (and complain when it's not found).
Message-Id: <20210706174750.246026-1-l@vgx.fr>
3 years ago
Leah Neukirchen 386b65fcb4 mblaze-profile.5: clarify Reply-From 3 years ago
Leah Neukirchen 014f82ef0c t/1000-mmime.t: add tests for recent changes 3 years ago
Leah Neukirchen d324b92dd4 mmime: keep (none) whitespace after quoted strings 3 years ago
Leah Neukirchen 0370916357 mmime: generate valid empty mime multipart/mixed when no body is given 3 years ago
Leah Neukirchen 5c7432aa7b mcom: use mhdr -d -A even when extracting From: headers
We only want to decode the display part of the addresses only.

Mails with a From: like
  From: "Non-ASCII Lastname, Firstname" <mail@example.org>
resulted in To: lines like
  To: Non-ASCII Lastname, Firstname <mail@example.org>
which would send to two addresses.  Use -A to ensure proper decoding
and quoting, even if its just a single address in From.
3 years ago
Leah Neukirchen 4f1f0ea2b3 maddr: decode only display part of address
RFC 2047.6.2:
   NOTE: Decoding and display of encoded-words occurs *after* a
   structured field body is parsed into tokens.
3 years ago
Leah Neukirchen 0ed7f72d31 mhdr: print_addresses: decode only display part of address
RFC 2047.6.2:
   NOTE: Decoding and display of encoded-words occurs *after* a
   structured field body is parsed into tokens.
3 years ago
Leah Neukirchen 4e0de11300 mmime: print_header: encode quoted-strings at once
This is maybe a foul compromise between correctness and complexity of
implementation, but it should do the right thing in most cases, and
does not require fully parsing and reconstructing all headers that can
contain phrases.

An 'encoded-word' MUST NOT appear within a 'quoted-string'.  We thus
completely encode the quoted-string (if necessary) as a single
encoded-word, and strip off the quotes.

This should fix encoding of addresses that have both non-ASCII and
special chars such as , and ;.
3 years ago
Leah Neukirchen 9713264f70 mmime: be more careful when qp-encoding in headers
Characters such as , or ; mustn't appear in qp-encoded strings,
as they have a meaning in phrases.  To be safe, encode all special
characters except for the safe ones in RFC 2047 5.(3).

_ is dealt with already.
3 years ago
Leah Neukirchen 23941c6c19 t/1500-maddr.t: deactivate invalid test case
An 'encoded-word' MUST NOT appear in any portion of an 'addr-spec'.
3 years ago
Dominik Honnef 6e8e4e01ed mless: list same number of mails whether we're at the beginning or end 3 years ago
Leah Neukirchen 6684f74f07 mblaze.7: IRC moved to libera.chat. 3 years ago
Leah Neukirchen 605509cc06 t/1900-mdeliver.t: fix plan 3 years ago
Leah Neukirchen 2b517f97c0 msed: match header names case insensitively
Found by skarnet.
3 years ago
Leah Neukirchen c4008e43d9 mexport: use UTC timestamp in the postmark line 3 years ago
Leah Neukirchen ce900601cb mexport: default timestamp to unix second 0, not -1
This is better recognizable as "no data".
3 years ago
Leah Neukirchen 41c681362d NEWS.md: update 3 years ago
Leah Neukirchen 8ba44643e4 t/1900-mdeliver.t: add test for malformed mboxes 3 years ago
Leah Neukirchen 669af4ffca mdeliver: ignore last empty line of mbox entries
https://www.loc.gov/preservation/digital/formats/fdd/fdd000383.shtml
> Each message is immediately prefaced by a separation line and
> terminated by an empty line.

Bug discovered by skarnet.

Fixes #207.
3 years ago
Leah Neukirchen 7d04932813 mexport: ensure mbox entries are separated by an empty line
https://www.loc.gov/preservation/digital/formats/fdd/fdd000383.shtml
> Each message is immediately prefaced by a separation line and
> terminated by an empty line.

Bug discovered by skarnet.
3 years ago
Leah Neukirchen 5b52110399 mmime: do not duplicate Content* headers
Do not add additional Content-Type and Content-Transfer-Encoding headers
when using mmime on input already containing them.

Do not reencode the message if Content-Transfer-Encoding is set.

Based on a patch by Felix Van der Jeugt and duncaen.
3 years ago
Leah Neukirchen 93e8a4a892 mcom: also read $VISUAL to find the editor
Closes #206.
3 years ago
Leah Neukirchen e8981b723d safe_u8putstr: add oneline mode that prints CR/LF as C0 sequences 3 years ago
Leah Neukirchen 709f8f1121 mdeliver.1: spelling 3 years ago
Leah Neukirchen 41cf6272a5 mdeliver.1: small tweaks 3 years ago
Leah Neukirchen ff7537f6a7 GNUmakefile: run tests without stdin connected to a terminal 3 years ago
Leah Neukirchen 741972a416 t/8000-mflag.t: fix when stdin is not a tty 3 years ago
Leah Neukirchen c2d497b139 t/6000-msort.t: fix when stdin is not a tty 3 years ago
Leah Neukirchen 2d6de31cd7 t/3000-magrep.t: fix when stdin is not a tty 3 years ago
Leah Neukirchen e96f0707de mshow: show last part of multipart/mixed when none matched
This is in conformance with RFC 1341.
3 years ago
Leah Neukirchen 318ac214f1 mshow: add "-A all" to render all attachments
Closes #198.
3 years ago
nicoo 8e0d57425f Fix typo in NEWS
Closes: #199 [via git-merge-pr]
3 years ago
Leah Neukirchen 4fcb4ae0a4 NEWS.md: update for 1.1 3 years ago
codesoap 8ad63494bf mcom: do not match shebang in needs_multipart()
Avoids matching lines like '#!/usr/bin/env sh'.

Closes: #196 [via git-merge-pr]
3 years ago
Leah Neukirchen c525c0097e mmime: allow # in include lines, used to override the content-disposition 4 years ago
Leah Neukirchen f95439a988 rfc2045: blaze822_multipart: parse rest when boundary is missing
Either the mail got truncated or was wrongly generated, try to go on
by taking the remains of the buffer as part.
4 years ago
James Rowe bc021c53b6 Allow gpg and gpg2 executables for gnupg interaction
Closes: #193 [via git-merge-pr]
4 years ago
Leah Neukirchen 6a8543e321 rfc2045: mymemmemnl: make more check idiomatic 4 years ago
Leah Neukirchen c43538fae9 rfc2045: fix overread in blaze822_mime_parameter
This triggered an invalid read in strchr on 'Content-Type: text/html; '
4 years ago
Leah Neukirchen 5167bbfe39 rfc2045: ensure mime boundaries end with a newline or - 4 years ago
Leah Neukirchen 5e6151e3bf mcom: detect and report mmime errors
Closes #191.
4 years ago
Leah Neukirchen 48c20b7efa mmime: propagate errors when attaching a file fails 4 years ago
nicoo a825b4f8f4 Fix typo in NEWS
Closes: #190 [via git-merge-pr]
4 years ago
Leah Neukirchen b076d09f3a add contrib/mmailto
Closes #189.

This does not work with terminal emulators that don't use -e or
properly support arbitrary arguments after it.  Here's a nickel,
get yourself a proper terminal emulator.
4 years ago
James Rowe 5d8f070e26 contrib/_mblaze: silence mseq errors
On the first run or if you manually delete your sequence file, mseq
errors will break your prompt when you hit tab.

Closes: #188 [via git-merge-pr]
4 years ago
Leah Neukirchen 4d566a6afe t/7000-mseq.t: use printf for robustness 4 years ago
James Rowe 0180f649d2 mcom: allow tilde prefixed path for profile's outbox setting
Closes: #187 [via git-merge-pr]
4 years ago
Michael Forney 02e4cf4001 mpick: use function pointer type for callback
Though POSIX requires this conversion to work correctly (for dlsym),
it is not valid in ISO C, so it is better to just uses the appropriate
function pointer type.

Closes: #185 [via git-merge-pr]
4 years ago
Michael Forney 50dfdf5605 include strings.h for str(n)casecmp
These POSIX functions are declared in strings.h, so include this
header explicitly instead of relying on the libc's default feature-test
macros to include it through string.h.
4 years ago
Leah Neukirchen d2621a715b README: update 4 years ago
Leah Neukirchen 2c8acab338 NEWS.md: update for 1.0 4 years ago
Leah Neukirchen 07ee45cf31 mgenmid: fix pledge, needs dns
Found by lhynes.
4 years ago
Leah Neukirchen ccceb10908 mpick: relax pledge for redirects
This allows mpick to create files and run popen.
In the future, we can only pledge this if these features are actually used.
4 years ago
Tim Kuijsten 4763032430 pledge(2) all programs
All programs except mshow have a very tight set of promises. mshow
has a broad set of promises and might be a good future candidate
to further restrict using unveil(2).

This patch is based on commit 0300a112 by Alex Holst (dated
2017-12-07), which was proposed in GH PR #79.

* pledged mpick, mflow and mdate so that now all programs are pledged
* removed some unneeded promises and added some missing promises
* move err.h include and OpenBSD ifdef into a new xpledge.h
* cleaned up code aligning and whitespace

Closes: #179 [via git-merge-pr]
4 years ago
Leah Neukirchen 6bd687bfdc mmime: allow ; and =, for ";charset=" 4 years ago
Leah Neukirchen 1e9f6cd06b t/7000-mseq.t: add test for 74857db19 4 years ago
Leah Neukirchen 6649576fda t/5000-mscan.t: typo 4 years ago
Leah Neukirchen 74857db199 mseq: reinitialize iter for each command line argument
Fixes #181.
4 years ago
Leah Neukirchen 32b5837bfd t/5000-mscan.t: add test for b41d2ef 4 years ago
Leah Neukirchen b41d2efa40 seq: blaze822_loop: reinitialize iter for each command line argument 4 years ago
Leah Neukirchen 92bb41fa73 msort: order: remove dead assignment
Found by scan-build/clang 10.0.0
4 years ago
Leah Neukirchen b2a6e3dcc0 seq: blaze822_seq_load: remove dead assignment
Found by scan-build/clang 10.0.0
4 years ago
Jeremy Bobbin 3f61249c05 seq: fix infinite loop when selecting thread of orphaned message
printf ' a\n' | mseq -S
mseq .=

Closes: #180 [via git-merge-pr]
4 years ago
Leah Neukirchen 04ed2d439a mshow: refactor, use charset logic in reply_mime too 4 years ago
Leah Neukirchen a02c7e451b contrib/mrecode: assume UTF-8 for unknown-8bit or x-unknown charsets 4 years ago
Leah Neukirchen bb8054c2ff mshow: assume UTF-8 for unknown-8bit or x-unknown charsets 4 years ago
Leah Neukirchen fec4e06671 mmime: be more strict when detecting content types for the include syntax
Previously, shebangs would be (mis)detected: #!/bin/sh -e
4 years ago
Leah Neukirchen 7e9e75bd88 mshow: print encoding error message to stderr 4 years ago
Leah Neukirchen 957c09909d mshow: only set cur when there is a new current message 4 years ago
Duncan Overbruck d9c3914924 mpick: breaking cmdline options
Two breaking changes which will make mpick fit more in with
all other mblaze tools by using mmsg(7) arguments:
- Use the -F flag to read script files.
- Remove msglist support
4 years ago
Leah Neukirchen 96c506085f t: use - instead of /dev/stdin 4 years ago
Leah Neukirchen 30f3054bac contrib/malternative: use - instead of /dev/stdin 4 years ago
Leah Neukirchen b063294daf mcom: use - instead of /dev/stdin 4 years ago
Leah Neukirchen e7442fcc72 seq: - now refers to the message on stdin, use .- for previous message 4 years ago
Leah Neukirchen 54283ebeee seq: cleanup 4 years ago
Julian Rother db7b58f24a mpick: fixed freeing pointer returned from getenv
Closes: #177 [via git-merge-pr]
4 years ago
Julian Rother e5206792df t/2000-mpick.t: test environment variables in expressions 4 years ago
Leah Neukirchen 282de65079 mrep: use Reply-From configuration to find From header
This allows replying from different addresses than Local-Mailbox
if they appear in To, Cc or Bcc.

Idea and initial patch by @nmeum.
4 years ago
Leah Neukirchen 3ea0c9c355 mrep: don't add another From: when -from is used already
Fixes #176.
4 years ago
Leah Neukirchen 17fe13a95f add contrib/mcount, a tool to count mails (possibly between pipes) 4 years ago
Leah Neukirchen 41a9a6b40d mthread: remove count output
Closes #174.
4 years ago
Leah Neukirchen d777d370dd t/lib.sh: work around a OpenBSD bug
Upstream report: https://marc.info/?i=871rnjhncc.fsf%20()%20vuxu%20!%20org
4 years ago
Leah Neukirchen fb77179bbd NEWS.md: update for 0.7 4 years ago
Leah Neukirchen 7cd43f4203 mlist: fix dangling else warning 4 years ago
Duncan Overbruck b79e38b686 mpick: improve unknown binding error and add test case 4 years ago
Duncan Overbruck 0645634733 t/2000-mpick.t: check for double free with bindings 4 years ago
Duncan Overbruck 95a9c0837b mpick: fix memory leaks 4 years ago
Duncaen 331d0e4680 mpick: use slurp instead of mmaping files 4 years ago
Duncaen d45d1c4ff5 mpick: fix memory leak in parse_dur 4 years ago
Duncaen d93c1e2fe4 mpick: always point to malloc memory in parse_string 4 years ago
Duncaen aa2868822b t/2000-mpick.t: add tests for all the new features 4 years ago
Duncaen 8ae4801dd4 mpick: add header decodeop for better address matching 4 years ago
Duncaen 32f0fcbc0d mpick: add let expressions 4 years ago
Duncaen d5afda072b mpick: better error for missing ) over multiple lines 4 years ago
Duncaen 4baa330b63 mpick: allow reading expressions from files i.e. using #!/bin/mpick 4 years ago
Duncaen d235a3e64c mpick: minor cleanups/memleak fixes for parse_msglist 4 years ago
Duncaen 482bbffd2c mpick: refactor mailfile handling
- fix some memory leaks
- fix handling of unexisting files/mthread containers
- in oneline mode use one mailinfo struct for each line
4 years ago
Duncaen 1010ae06bf mpick: add support for multiline expressions and comments 4 years ago
Duncaen b8cf02825a mpick: add xcalloc and xstrdup 4 years ago
Duncaen 28d0d51e34 mpick: add freeexpr 4 years ago
Duncaen 8f0b6e2117 mpick: add ternary (conditional) operator 4 years ago
Duncaen 3968f2ef03 mpick: add skip expression 4 years ago
Duncaen 401aa88752 mpick: add pipes and file redirection 4 years ago
Duncaen eec6a4ebfd mdeliver.1: add mrefile to SEE ALSO 4 years ago
Leah Neukirchen 41c8fefb73 blaze822_walk_mime: limit recursion depth 4 years ago
Leah Neukirchen 61caf5a6a3 mthread: comment depth convention 4 years ago
Leah Neukirchen 4927d44d55 mthread: add -p to only add parent messages via -S 4 years ago
Leah Neukirchen 62da7c59ea mthread: add -r to reverse top-level order 4 years ago
Leah Neukirchen a5c77c71bc mlist: print number of matches when message selection is in place 4 years ago
Leah Neukirchen e4e15bb4f0 mflag: list: don't clobber f when flags and -i is used 4 years ago
Leah Neukirchen d83860c34e man/*: normalize references, use %T for RFC titles 4 years ago
Alyssa Ross 3663d7365b msuck: don't init maildir until connected
Previously, a typo in the newsgroup name would leave an empty mailbox
for a non-existent newsgroup.  This can be avoided by delaying maildir
initialization until after the NNTP server has 221ed the group.
4 years ago
Alyssa Ross 99b7d47a10 msuck: exit unsuccessfully if some groups failed
The failing exit status is EX_UNAVAILABLE from BSD's sysexits.h.
4 years ago
Leah Neukirchen e56f8ad294 blaze822: blaze822_addr: null-terminate ttok at the current position before appending 4 years ago
Leah Neukirchen 3370fd5f70 blaze822: blaze822_addr: don't force add space before quoted words 4 years ago
Leah Neukirchen 8c8a4bd422 blaze822: blaze822_addr: ignore leading obs-route 4 years ago
Leah Neukirchen d50bb5772a maddr: add -d to only print display name. 4 years ago
Leah Neukirchen eabdda4405 mytimegm: fix whitespace 4 years ago
Leah Neukirchen 604afb5011 mytimegm: replace with own implementation 4 years ago
Leah Neukirchen 7e6b1c97e6 mrep/mbnc: allow only one message as argument 4 years ago
Leah Neukirchen 5885bbe69f mshow: don't spawn pager for -x 4 years ago
Leah Neukirchen 8c9e0aae05 mmkdir: create subdirectories in order like maildirmake 4 years ago
Leah Neukirchen 079e423438 mlist: extract DT_* handling, follow symlinks
Closes #172.
4 years ago
Leah Neukirchen 8d611a8a3d mseq: extract DT_* handling, follow symlinks 4 years ago
Leah Neukirchen dfc18e0040 seq: extract DT_* handling, follow symlinks 4 years ago
Leah Neukirchen 48b490f379 minc: extract DT_* handling 4 years ago
Leah Neukirchen 6dd0799aab mdirs: extract DT_* handling 4 years ago
Leah Neukirchen 582256618a move uc macro to blaze822_priv.h 4 years ago
Leah Neukirchen 83f9794bb0 contrib/mraw: drop leading spaces 4 years ago
Duncan Overbruck dc15a75b63 mcom: add preview alias for show command 4 years ago
Leah Neukirchen 2636f5127d GNUmakefile: release: check ./VERSION is correct before release 4 years ago
Leah Neukirchen 92dd0f7be5 VERSION: bump 4 years ago
Leah Neukirchen 113ac5a24d mdirs: add -a to list all subfolders, ignoring maildir++ convention
Based on a patch by Kylie McClain <kylie@somas.is>.
4 years ago
Leah Neukirchen 1755fa6279 mblaze.7: mention public-inbox archive 4 years ago
Leah Neukirchen da457c51ee GNUmakefile: use gpg2 4 years ago
Leah Neukirchen e0733f187b NEWS.md: update for 0.6 4 years ago
Leah Neukirchen 513ac7d002 mscan: fix underrun in %F when filename doesn't contain three slashes 4 years ago
Sören Tempel 55035ad8b2 mcom: check if a draft was found when invoking `mcom -r`
Without this patch `mcom -r` would invoke $EDITOR on a directory if the
maildir did not contain any drafts. To prevent this from happening,
check if a draft was found before performing any further processing. If
not, terminate mcom with an error message.

Closes: #169 [via git-merge-pr]
4 years ago
Leah Neukirchen 6705b3ee29 mfwd: prefix Subject with "Fwd:"
Idea by Larry Hynes.  Although no classical mail clients use Fwd:
prefix, it has been around for some time.
5 years ago
Leah Neukirchen 3da5f0e391 COPYING: Leahize 5 years ago
Leah Neukirchen 9e91eb6e8b mscan: add dottime formatting 5 years ago
Silvan Jegen dbf118f6c9 mless: only export LESSKEY once
Closes: #168 [via git-merge-pr]
5 years ago
Leah Neukirchen 1ed8a0ac76 blaze822_loop: delay opening sequence to first use 5 years ago
Leah Neukirchen 48285fe722 man/mcom.1: document defaults for mrep and mbnc 5 years ago
Sebastien Marie a4b975e731 mlist: look at maildir/new too for messages
Closes: #165 [via git-merge-pr]
5 years ago
Konst Mayer 71e2318c81 mshow: don't use --quit-at-eof in less by default
If the user wants it, he can add it to the LESS environment variable.

Closes: #163 [via git-merge-pr]
5 years ago
Leah Neukirchen 5ee756f6a5 mdeliver: better usage when multiple arguments are given 5 years ago
Leah Neukirchen 68272afcba mcom: use file --mime instead of file -i
This also works on macOS.

Closes #162.
5 years ago
Leah Neukirchen a60e705e51 mthread: make malloc failure fatal 5 years ago
Leah Neukirchen 5fa9a7a706 mmime: gen_file: remove check for trailing newline for 7bit transfer
RFC2046 5.1.1 specifies that parts without trailing newlines are coded
without problems:

> NOTE:  The CRLF preceding the boundary delimiter line is conceptually
> attached to the boundary so that it is possible to have a part that
> does not end with a CRLF (line break).

This if-statement now also codes empty files correctly.
5 years ago
Leah Neukirchen 5b0755ca68 slurp: don't special-case empty files, instead behave like strdup("")
This allows the caller to call free(3) in any case.
5 years ago
Leah Neukirchen fd675ef159 seq: blaze822_loop: fix leak 5 years ago
Leah Neukirchen 8a095775d0 maddr: addr: fix leak 5 years ago
Leah Neukirchen 53151f4f89 mdeliver: add fallback to utimes for systems that don't have utimensat
Namely, macOS before 10.13.
5 years ago
Leah Neukirchen 0d73424edf mscan: u8putstr: assume wcwidth of -1 means actually 2 (probably emojis) 5 years ago
Leah Neukirchen b052723eac NEWS.md: update for 0.5.1 5 years ago
Leah Neukirchen 023c26f943 rfc2047: blaze822_decode_rfc2047: account for space for trailing nul 5 years ago
Leah Neukirchen 081a9404cd blaze822: blaze822_addr: fix parsing of () at end of string
Found by lhynes.
5 years ago
Leah Neukirchen bb80642b70 msed: improve zero initialization 5 years ago
Leah Neukirchen 58365527e4 t: update links 5 years ago
Duncaen c8ce6d220c magrep: fix use of uninitialized memory
found by -fsanitize=memory
5 years ago
Duncaen 74d8393fdf msed: fix use of uninitialized memory
found by -fsanitize=memory
5 years ago
Leah Neukirchen 11ae5504fd blaze822: blaze822_addr: don't read over the trailing nul byte when resolving \ 5 years ago
Leah Neukirchen 9dfbf48be4 blaze822: blaze822_addr: take care of string end in comments inside < 5 years ago
Leah Neukirchen 3a97df9990 mmime: print_header: fix for empty lines 5 years ago
Leah Neukirchen 01b2f1abc6 blaze822: blaze822_addr: skip remaining characters when token is full, also with [ 5 years ago
Leah Neukirchen 5a8bb16289 blaze822: blaze822_addr: skip remaining characters when token is full 5 years ago
Leah Neukirchen 5fc3115c3f blaze822: blaze822_addr: don't drop last char when comment is not closed
Found by duncaen with afl.
5 years ago
Leah Neukirchen 3d3fffd7e8 blaze822: blaze822_addr: fix reading over the end of address if it ends with , or ;
Found by duncaen with afl.
5 years ago
Leah Neukirchen 3f2c714b42 blaze822: safe_append: fixes for various border cases
This fixes dstmax == dstlen and when src is completely full.

Found by duncaen with afl.
5 years ago
Leah Neukirchen f8fa6a1e0e mdirs: add -0 to separate folders by NUL characters
Idea by Mario Domgoergen <mdom@taz.de>.

Closes #159.
5 years ago
Leah Neukirchen 085138d6a2 mdeliver: work around macOS, which doesn't implement POSIX.1-2008 stat fields 5 years ago
Leah Neukirchen cb0ffbf6ad rfc2231: fix memleak
Found by clang 7.0.1.
5 years ago
Leah Neukirchen 6ea32480f1 blaze822: blaze822_mmap: fix memleak
Found by clang 7.0.1.
5 years ago
Leah Neukirchen 4198cd42fa blaze822: blaze822_mem: fix memleak
Found by clang 7.0.1.
5 years ago
Leah Neukirchen 4e9237f921 mdeliver: clean up logic 5 years ago
Leah Neukirchen 65ddebc87c mrefile: don't open the file when renaming is enough 5 years ago
Leah Neukirchen bccacce816 mrefile: try using rename first, when possible 5 years ago
Leah Neukirchen 8d09a5339e mdeliver: preserve mtime in mrefile 5 years ago
Leah Neukirchen b85c56f30c mdeliver: free msg opened for getting timestamp 5 years ago
Leah Neukirchen 2c14e800cd NEWS.md: add release date 5 years ago
Duncaen cfba3e1c3b mpick: fix off-by-one in parse_string (expression parsing) 5 years ago
Leah Neukirchen 820c07b7e4 NEWS.md: update 5 years ago
codesoap 33a6c48fb6 mcom: Quote one more variable 5 years ago
codesoap d164496e09 mcom: Quote two more variables
Before quoting these two variables I experienced problems when
trying to use the "encrypt" option while using mcom.
5 years ago
Leah Neukirchen 1aa4977f71 mcom: decode address for Reply-To: and To: header
mmime will recode again if required.
5 years ago
Leah Neukirchen 9d280aa8a3 mcom: do_mime: fix for loop quoting 5 years ago
codesoap 5bd1ef9cff contrib/mencrypt: Quote variables whenever possible 5 years ago
codesoap 1dd3415085 mless: Quote variables whenever possible 5 years ago
codesoap d68287aae5 mcom: Quote variables and subshells whenever possible 5 years ago
Leah Neukirchen 237ad8a2f3 mmime: print_header: use signed variable for linelen 5 years ago
Leah Neukirchen adb5850983 blaze822: safe_append: avoid different signedness in comparison 5 years ago
Leah Neukirchen 6645e80324 mdeliver: force mode 0600 for pipes (BSD gives 0000 here by default) 5 years ago
Leah Neukirchen 5a2c6747b6 mcom: check if mail is formatted sensibly 5 years ago
Leah Neukirchen 79434e45db msed: allow final empty command 5 years ago
Leah Neukirchen 763be1967c mflag.1: tweak description 5 years ago
Vincent Breitmoser 8e4ed10b6c mshow: put message-id into env, if available
Closes: #135 [via git-merge-pr]
5 years ago
Leah Neukirchen a6f48fe371 mcom: add -send 5 years ago
Duncaen 6b3cca21fb mscan.1: fix typo 6 years ago
Leah Neukirchen 3c6f5c3cee mcom: enter the loop again when we should run mime, another place 6 years ago
Leah Neukirchen 577b4497e4 mcom: enter the loop again when we should run mime 6 years ago
Leah Neukirchen 30fba2bcfd mcom: automime can be empty 6 years ago
Leah Neukirchen d6c2470ad1 mcom: check drafts exist, and ensure they contain a / 6 years ago
Leah Neukirchen 8427990730 NEWS.md: update 6 years ago
Leah Neukirchen d2f4294d95 NEWS.md: update 6 years ago
Leah Neukirchen db4c4a118c mflow: avoid adding a space after the leading quotes if there already is one
Found by lhynes.
6 years ago
Leah Neukirchen e9fb9ca796 contrib/mencrypt: use default key for From: 6 years ago
Leah Neukirchen 75980790a6 contrib/msign: use default key for From: 6 years ago
Leah Neukirchen c5cd4df026 seq: sort dir file lists numerically 6 years ago
Leah Neukirchen 518ff4c148 contrib/mmairix: sort by date, newest first 6 years ago
Leah Neukirchen 08a46e6848 contrib/mverify: use gpg2 6 years ago
Leah Neukirchen d9aaac6903 contrib/msign: use gpg2 6 years ago
Leah Neukirchen 87e5a1b2c3 contrib/mgpg: use gpg2 6 years ago
Leah Neukirchen 62706de483 NEWS.md: update 6 years ago
Leah Neukirchen 55c188a0c8 mless: remove mnext/mprev 6 years ago
Leah Neukirchen 1349f617de mshow: exit with 1 if not all attachments were found 6 years ago
Leah Neukirchen ae720c51e0 mrep: add -noquote
Closes #139.
6 years ago
Leah Neukirchen 5280abfa10 mgenmid: do not use raw timestamp
We one-time-pad the timestamp with a random key instead.
This will provide enough entropy to be unique, but not leak the system date.
Even with a bad RNG state it should guarantee uniqueness, however.
6 years ago
Julian Rother 3cc1944877 magrep: fix memory leak in magrep message body search
Closes: #147 [via git-merge-pr]
6 years ago
Leah Neukirchen 0da4342b22 mdeliver: keep permissions of message for freshly created files 6 years ago
Leah Neukirchen ab80765c24 mmkdir: ensure dir permissions for toplevel directory too (using umask)
Fix a quoting bug on the side.
6 years ago
Leah Neukirchen c2e0dcec3c mshow: extract: skip leading spaces for file names
Found by Solene Rapenne.
6 years ago
Leah Neukirchen 9d28d930e4 contrib/menter: more robustness on unexistant messages 6 years ago
Leah Neukirchen cf15239377 safe_u8putstr: whitespace fix 6 years ago
Leah Neukirchen 4235c384ab mcom: aborting the editor is more like delete than cancel 6 years ago
Leah Neukirchen 188aeac3e1 safe_u8putstr: handle NUL bytes
These have length 0, but we still want to progress one byte.
Found by duncaen with afl-fuzz.
6 years ago
Leah Neukirchen 2069a0e913 mshow: choose_alternative: skip empty MIME parts. 6 years ago
Leah Neukirchen 41098c3851 mdirs: reset cwd after each iteration
Else `mdirs dir1/a dir2/b` fails, as the first iteration remains in `dir1`.
Found by milliardo.
6 years ago
Leah Neukirchen 7ea96a6dea mrefile: better error message on ENOENT 6 years ago
Leah Neukirchen d2dfe8bd30 contrib/msearch: errors to stderr 6 years ago
Leah Neukirchen c04d2edd08 contrib/msearch: -- handling is builtin 6 years ago
Leah Neukirchen 67e0beb56b contrib/msearch: add -x for mairix 6 years ago
Thomas Schneider fbb797253d Add contrib/msearch
Closes: #138 [via git-merge-pr]
6 years ago
Duncaen 9d793b3662 mpick: add -v to usage 6 years ago
Duncaen d09efa6aa9 mpick: add -v flag
Closes: #137 [via git-merge-pr]
6 years ago
Leah Neukirchen d5714df6c9 mscan: rename pid1 to pager_pid 6 years ago
Leah Neukirchen ddd7e54fbb mscan: hide message count behind -v
Also ensure output is flushed before printing it.
6 years ago
Leah Neukirchen 82faec5cfe mscan: u8putstr: take length of replacement char into account 6 years ago
Leah Neukirchen 1336ea5512 magrep: add -h, which is like -p but doesn't print the file name. 6 years ago
Leah Neukirchen c818ab8645 contrib/mgpg: ensure gpg errors are shown 6 years ago
Marcin Niestroj 8274295d90 mpick: Increase buffer for decoded header field
When using:

  $ mlist maildir | mpick -t '"cc" ~~ "*vger.kernel.org*"'

with a maildir containing mails from Linux Mailing List, not all
messages were picked correctly. It turned out that the reason was
small (100 bytes) buffer for header fields.

Increase header field buffer to 4096, which is the most commonly used
value.

Closes: #133 [via git-merge-pr]
6 years ago
Chris Lamb 5dbed68b6a GNUmakefile: correct reference to SOURCE_DATE_EPOCH
It's DATE, not TIME.

Debian bug: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=907537

Closes: #132 [via git-merge-pr]
6 years ago
Vincent Breitmoser 562fec2614 man/mshow.1: document -x behavior if no parts are specified
Closes: #131 [via git-merge-pr]
6 years ago
Leah Neukirchen 704c9cec62 mscan: prioritize displaying trashed mail over other markers
Trashing is the most dangerous flag.

Closes #130.
6 years ago
Vincent Breitmoser 0afe5e3638 contrib/_mblaze: small fixes to _mhdr and _minc
Closes: #129 [via git-merge-pr]
6 years ago
Vincent Breitmoser e7c3c08da3 man/msed.1: mention mhdr in see also section of msed
Closes: #128 [via git-merge-pr]
6 years ago
Leah Neukirchen 5495fdd0c4 man/mshow.1: document $MAILFILTER, small tweaks
Closes #126.
6 years ago
Vincent Breitmoser 147301d16c contrib/_mblaze: mflag takes a message argument, not directory
Closes: #125 [via git-merge-pr]
6 years ago
Vincent Breitmoser 0b6080219b contrib/_mblaze: fix _mblaze_message
This ignores lines from mseq | mscan that don't start with a sequence
number, i.e.  dangling messages in threads.

Closes: #124 [via git-merge-pr]
6 years ago
Leah Neukirchen 1288ade9ed mthread: keep toplevel thread date-order when pruning
Else this could result in a toplevel thread getting pruned, but its
child has an older date than was calculated by newest(), messing up the
order.
6 years ago
Leah Neukirchen d82e7d4555 contrib/_mblaze: add zsh completion
By @Valodim.
6 years ago
Leah Neukirchen 9f10db393e mthread: avoid truncating internal buffer 6 years ago
Leah Neukirchen 86bddb5527 VERSION: update for 0.4 6 years ago
Leah Neukirchen ca75530061 NEWS.md: update for 0.4 6 years ago
Leah Neukirchen 6ec116f764 t/1501-maddr-regress.t: adapt to changes of bcd258a 6 years ago
Leah Neukirchen 718caa9b59 t/1500-maddr.t: adapt to changes of bcd258a 6 years ago
Leah Neukirchen 5294c374df mcom: whitespace fixes 6 years ago
Leah Neukirchen eca1ff8087 mrep: don't put To: into Cc:, don't duplicate addresses in Cc: 6 years ago
Leah Neukirchen ebaf9e2df2 mcom: detect Attach: headers for automiming 6 years ago
Leah Neukirchen 0d397f6790 mdeliver: fix stray whitespace 6 years ago
Duncaen 98918783aa magrep: add -l flag
Closes: #121 [via git-merge-pr]
6 years ago
Leah Neukirchen 817a111909 mdeliver: improve message when not delivering to a maildir 6 years ago
Leah Neukirchen ece2717771 mdeliver: more careful Status: parsing 6 years ago
Leah Neukirchen 6eebeaf68f mdeliver: recognize CRLF empty line after header 6 years ago
Thomas Schneider e356ae5e5c Update VERSION
Closes: #120 [via git-merge-pr]
6 years ago
Leah Neukirchen 2e742622ab whitespace fixes 6 years ago
Leah Neukirchen bcd258a8c8 maddr, mhdr: requote display names when needed 6 years ago
Larry Hynes b4df5b006b mrefile.1: update wording
Closes: #118 [via git-merge-pr]
6 years ago
Leah Neukirchen 593b1950d6 mrefile: don't default to current message 6 years ago
Leah Neukirchen 4b4fed9b8a mhdr: use strncasecmp to fix -M -h Received 6 years ago
Leah Neukirchen 469c0af68a README: update 6 years ago
Leah Neukirchen 2f46bd718e add mrefile.1 6 years ago
Leah Neukirchen d261b0cdcf mshow: add -B to decode MIME parameters in broken mails 6 years ago
Leah Neukirchen 5d43ad2404 mdeliver: add refiling capability 6 years ago
Leah Neukirchen 5f250330cb mcom: update flags after mrep/mbnc/mfwd 6 years ago
Leah Neukirchen 14a2c978db mcom: mark drafts as seen after sending 6 years ago
Leah Neukirchen 33ce21af6e mdeliver: style 6 years ago
Larry Hynes afefda9844 mless.1: update wording
Closes: #117 [via git-merge-pr]
6 years ago
Leah Neukirchen 5004b4b3e6 contrib/mp7m: add application/pkcs7-mime decoder 6 years ago
Leah Neukirchen 64da4ff0fa mcom: add -body to prepopulate drafts 6 years ago
Leah Neukirchen d1a1245742 t/1501-maddr-regress.t: fix failing test, maddr never quotes the display name 6 years ago
Leah Neukirchen f83a6fea9a mcom: ensure no empty header lines are generated in mrep 6 years ago
Leah Neukirchen 35c11688eb blaze822: compress_hdr: avoid stepping h too far
This resulted in the message consisting of the empty line to contain a
single header which consisted of only a space.
6 years ago
Leah Neukirchen 372450cd70 mcom: cleanup 6 years ago
Leah Neukirchen c49c0b4a56 mcom: revamp argument parsing, allow setting arbitrary headers from command line 6 years ago
Leah Neukirchen 4b9e7f5952 mcom: put newline in a variable 6 years ago
Leah Neukirchen c12fd15d3f mcom: fix $draftmime for when not using outbox 6 years ago
Duncaen 80dafa1887 man/magrep.1: fix -c description
Closes: #116 [via git-merge-pr]
6 years ago
Leah Neukirchen 205a4a0545 museragent: don't show build date when $SOURCE_TIME_EPOCH is set 6 years ago
Leah Neukirchen bbd8165f46 1501-maddr-regress.t: space in local part 6 years ago
Leah Neukirchen 2cd67e3fe1 README: update 6 years ago
Leah Neukirchen ecb5286e5a NEWS.md: update 6 years ago
Leah Neukirchen 501b301ed4 rfc2045: blaze822_multipart: avoid buffer overflow on empty MIME part 6 years ago
Leah Neukirchen 6c6055b78b mshow: render_mime: don't print any MIME structure markers with -N.
Reported by Larry Hynes.
6 years ago
Duncaen 32144a2eaa mpick: don't allow EXPR_ANYSET with unmatched operator 6 years ago
Larry Hynes 4be7153f5f mthread.1: update wording
Closes: #114 [via git-merge-pr]
6 years ago
Larry Hynes b4dad686d2 msort.1: update wording
Closes: #113 [via git-merge-pr]
6 years ago
Larry Hynes a325acb889 mshow.1: update wording
Closes: #112 [via git-merge-pr]
6 years ago
Larry Hynes dc1531a7bb mseq.1: update wording
Closes: #111 [via git-merge-pr]
6 years ago
Larry Hynes 1377f5ac0a msed.1: update wording
Closes: #109 [via git-merge-pr]
6 years ago
Larry Hynes 080ea4317f mscan.1: update wording
Closes: #108 [via git-merge-pr]
6 years ago
Leah Neukirchen 80a74ddc98 magrep: add *:REGEX to search in any header 6 years ago
Larry Hynes e87140e226 mpick.1: update wording
Closes: #107 [via git-merge-pr]
6 years ago
Larry Hynes 6b21f1dbe0 mmsg.7: update wording
Closes: #106 [via git-merge-pr]
6 years ago
Larry Hynes e7344c41d6 mmkdir.1: Maildir -> maildir
Closes: #105 [via git-merge-pr]
6 years ago
Larry Hynes 30c2563ffc mmime.1: update wording
Closes: #104 [via git-merge-pr]
6 years ago
Larry Hynes 8a2a64601c mlist.1: update wording
Closes: #103 [via git-merge-pr]
6 years ago
Larry Hynes 268fa4898b minc.1: update wording
Closes: #102 [via git-merge-pr]
6 years ago
Larry Hynes 38c0860189 mhdr.1: update wording
Closes: #101 [via git-merge-pr]
6 years ago
Larry Hynes 9513ec97fd mgenmid.1: update wording
Closes: #100 [via git-merge-pr]
6 years ago
Larry Hynes 4bd136883c mflow.1: update wording
Closes: #99 [via git-merge-pr]
6 years ago
Larry Hynes be0297bd56 mflag.1: update wording
Closes: #98 [via git-merge-pr]
6 years ago
Larry Hynes 36b4b914e2 mexport.1: update wording
Closes: #97 [via git-merge-pr]
6 years ago
Larry Hynes 86cc56f293 magrep.1: add reference to re_format(7)
Closes: #96 [via git-merge-pr]
6 years ago
Larry Hynes ef68f21ad6 msed.1: add reference to re_format(7)
Closes: #95 [via git-merge-pr]
6 years ago
Larry Hynes 1cd323ef22 mdirs.1: update wording
Closes: #94 [via git-merge-pr]
6 years ago
Larry Hynes 1082f7581b mdeliver.1: update wording
Closes: #93 [via git-merge-pr]
6 years ago
Larry Hynes 2eb1ac7c10 mcom.1: reword NAME section
Closes: #92 [via git-merge-pr]
6 years ago
Larry Hynes ef51e4c000 mcom.1: update wording 6 years ago

@ -5,8 +5,8 @@ has waived all copyright and related or neighboring rights to this work.
http://creativecommons.org/publicdomain/zero/1.0/
The files mystrverscmp.c and mymemmem.c and mytimegm.c
The files mystrverscmp.c and mymemmem
are MIT-licensed and were written by Rich Felker.
mpick.c is largely based on code by Christian Neukirchen
mpick.c is largely based on code by Leah Neukirchen
and has been contributed by Duncan Overbruck <mail@duncano.de>

@ -1,5 +1,6 @@
CFLAGS?=-g -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2
override CFLAGS:=-Wall -Wno-switch -Wextra $(CFLAGS)
LDFLAGS?=-fstack-protector-strong
LDLIBS=-lrt
OS := $(shell uname)
@ -25,22 +26,22 @@ SCRIPT = mcolor mcom mless mmkdir mquote museragent
all: $(ALL) museragent
$(ALL) : % : %.o
maddr magrep mdeliver mexport mflag mflow mgenmid mhdr mpick mscan msed mshow \
msort mthread : blaze822.o mymemmem.o mytimegm.o
maddr magrep mexport mflag mgenmid mhdr mlist mpick mscan msed mseq mshow msort \
mthread : seq.o slurp.o
maddr magrep mdeliver mdirs mexport mflag mflow mgenmid mhdr mpick mscan msed \
mshow msort mthread : blaze822.o mymemmem.o mytimegm.o
maddr magrep mdeliver mdirs mexport mflag mgenmid mhdr minc mlist mpick mscan \
msed mseq mshow msort mthread : seq.o slurp.o mystrverscmp.o
maddr magrep mflow mhdr mpick mscan mshow : rfc2047.o
magrep mflow mhdr mshow : rfc2045.o
mshow : filter.o safe_u8putstr.o rfc2231.o pipeto.o
mscan : pipeto.o
msort : mystrverscmp.o
mmime : slurp.o
minc mlist : squeeze_slash.o
museragent: FRC
@printf '#!/bin/sh\nprintf "User-Agent: mblaze/%s (%s)\\n"\n' \
@test -n "$$SOURCE_DATE_EPOCH" || BUILDDATE=$$(date '+ (%Y-%m-%d)'); \
printf '#!/bin/sh\nprintf "User-Agent: mblaze/%s%s\\n"\n' \
"$$({ git describe --always --dirty 2>/dev/null || cat VERSION; } | sed 's/^v//')" \
"$$(date +%Y-%m-%d)" >$@
"$$BUILDDATE" >$@
@chmod +x $@
README: man/mblaze.7
@ -50,7 +51,7 @@ clean: FRC
-rm -f $(ALL) *.o museragent
check: FRC all
PATH=$$(pwd):$$PATH prove -v
PATH=$$(pwd):$$PATH prove -v </dev/null
install: FRC all
mkdir -p $(DESTDIR)$(BINDIR) \
@ -58,22 +59,22 @@ install: FRC all
$(DESTDIR)$(MANDIR)/man5 \
$(DESTDIR)$(MANDIR)/man7
install -m0755 $(ALL) $(SCRIPT) $(DESTDIR)$(BINDIR)
ln -sf mless $(DESTDIR)$(BINDIR)/mnext
ln -sf mless $(DESTDIR)$(BINDIR)/mprev
ln -sf mcom $(DESTDIR)$(BINDIR)/mbnc
ln -sf mcom $(DESTDIR)$(BINDIR)/mfwd
ln -sf mcom $(DESTDIR)$(BINDIR)/mrep
ln -sf mdeliver $(DESTDIR)$(BINDIR)/mrefile
install -m0644 man/*.1 $(DESTDIR)$(MANDIR)/man1
install -m0644 man/*.5 $(DESTDIR)$(MANDIR)/man5
install -m0644 man/*.7 $(DESTDIR)$(MANDIR)/man7
release:
VERSION=$$(git describe --tags | sed 's/^v//;s/-[^.]*$$//') && \
[ "$$VERSION" = "$$(cat VERSION)" ] && \
git archive --prefix=mblaze-$$VERSION/ -o mblaze-$$VERSION.tar.gz HEAD
sign:
VERSION=$$(git describe --tags | sed 's/^v//;s/-[^.]*$$//') && \
gpg --armor --detach-sign mblaze-$$VERSION.tar.gz && \
gpg2 --armor --detach-sign mblaze-$$VERSION.tar.gz && \
signify -S -s ~/.signify/mblaze.sec -m mblaze-$$VERSION.tar.gz && \
sed -i '1cuntrusted comment: verify with mblaze.pub' mblaze-$$VERSION.tar.gz.sig

@ -1,3 +1,101 @@
## 1.2 (2021-12-12)
* Caution! The tools mdeliver and mexport were buggy in handling and
generation of trailing empty lines in MBOX-RD. Do not import
mbox files generated by mexport >=1.2 with mdeliver <1.2 if you
require verbatim message delivery.
* mshow: add "-A all" to render all attachments
* msed: match header names case insensitively
* mless: prefer setting LESSKEYIN and using .mlesskey
* mcom: take Delivered-To into account for choosing From address
* Many bug fixes.
## 1.1 (2021-01-14)
* mcom: allow tilde prefixed path for profile's outbox setting
* mcom: detect and report mmime errors
* add contrib/mmailto, a handler for mailto: links
* Bug fixes.
## 1.0 (2020-09-12)
* Caution! Backwards incompatible changes:
* As a message name, `-` now refers to the message on the standard input,
and not the previous message anymore. Use `.-` to refer to the previous
message in a short way.
The tools will print a warning if you use `-` and the standard input
comes from a TTY.
* mpick: use the -F flag to read script files.
* mpick: remove msglist support, use plain mmsg(7) arguments.
* Many mblaze tools now make use of pledge(2) on OpenBSD.
* add contrib/mcount, a tool to count mails
* mrep: use Reply-From configuration to find From header
* Many bug fixes.
## 0.7 (2020-05-15)
* All tools now follow symlinks to mails.
* mdirs: add -a to list all subfolders, ignoring Maildir++ convention.
* mcom: add preview alias for show command.
* mrep/mbnc: allow only one message as argument.
* maddr: add -d to only print display name.
* mthread: add -r to reverse top-level order.
* mlist: print number of matches when message selection is in place.
* mpick: many improvements.
* Many bug fixes.
## 0.6 (2020-01-18)
* mfwd: prefix Subject with "Fwd:".
* mscan: add dottime formatting.
* mlist: look at maildir/new too for messages.
* Many bug and portability fixes.
## 0.5.1 (2019-03-03)
* mdeliver: preserve mtime in mrefile
* mdirs: add -0 to separate folders by NUL characters
* Fixes for buffer-overflows, found by fuzzing.
* Fixes for memleaks.
## 0.5 (2019-02-09)
* New tool msearch to wrap several mail indexers.
* New zsh completion _mblaze.
* mnext/mprev were removed (you can call `mless +`/`mless .-`).
* The GnuPG tools in contrib/ now use gpg2.
* mshow exits with error if it could not extract all attachments
* mrep: add -noquote to disable quoting the message replied to
* mdeliver: keep permissions of messages
* mcom: aborting the editor is now more like delete than cancel
* mcom: add -send to send directly without editing
* mcom: check if mail is formatted sensibly
* mpick: new flag -v for statistics
* mscan: new flag -v for statistics
* magrep: add -h, which is like -p but doesn't print the file name
* mscan: prioritize displaying trashed mail over other markers
* mpick: fix off-by-one in expression parsing
* Many bug fixes.
## 0.4 (2018-08-15)
* New tool mrefile to move or copy messages.
* contrib/mp7m: add application/pkcs7-mime decoder
* mcom: allow setting arbitrary headers from command line
* mcom: add -body to prepopulate drafts
* mcom: mark drafts as seen after sending
* mcom: update flags after mrep/mbnc/mfwd
* mshow: add -B to decode MIME parameters in broken mails
* magrep: add -l flag
* Many bug fixes.
## 0.3.2 (2018-02-13)
* magrep: add *:REGEX to search in any header
* Fix of a buffer overflow in blaze822_multipart.
* Small bug fixes.
* Many documentation improvements by Larry Hynes.
## 0.3.1 (2018-01-30)
* mless: support $NO_COLOR

134
README

@ -1,102 +1,103 @@
MBLAZE(7) Miscellaneous Information Manual MBLAZE(7)
NAME
mblaze introduction to mblaze
mblaze introduction to the mblaze message system
DESCRIPTION
The mblaze message system is a set of Unix utilities to deal with mail
kept in Maildir folders.
The mblaze message system is a set of Unix utilities for processing and
interacting with mail messages which are stored in maildir folders.
Its design is roughly inspired by MH, the RAND Message Handling System,
but it is a complete implementation from scratch.
mblaze consists of these Unix tools that each do one job:
maddr(1) extract addresses from mail
magrep(1) find mails matching a pattern
mbnc(1) bounces mail
mcom(1) compose and send mail
mdeliver(1) deliver messages or import mailboxes
mdirs(1) find Maildir folders
mexport(1) export Maildir folders as mailboxes
mflag(1) change flags (marks) of mail
mflow(1) reflow format=flowed plain text mails
mfwd(1) forward mail
mgenmid(1) generate Message-IDs
mhdr(1) extract mail headers
minc(1) incorporate new mail
mless(1) conveniently read mail in less(1)
mlist(1) list and filter mail messages
mblaze consists of these Unix utilities that each do one job:
maddr(1) extract mail addresses from messages
magrep(1) search messages matching a pattern
mbnc(1) bounce messages
mcom(1) compose and send messages
mdeliver(1) deliver messages or import mbox file
mdirs(1) list maildir folders, recursively
mexport(1) export messages as mbox file
mflag(1) manipulate maildir message flags
mflow(1) reflow format=flowed plain text messages
mfwd(1) forward messages
mgenmid(1) generate a Message-ID
mhdr(1) print message headers
minc(1) incorporate new messages
mless(1) conveniently read messages in less(1)
mlist(1) list and filter messages
mmime(1) create MIME messages
mmkdir(1) create new Maildir
mpick(1) advanced mail filter
mrep(1) reply to mail
mscan(1) generate one-line summaries of mail
msed(1) manipulate mail headers
mseq(1) manipulate mail sequences
mshow(1) render mail and extract attachments
msort(1) sort mail
mthread(1) arrange mail into discussions
PRINCIPLES
mmkdir(1) create new maildir folders
mpick(1) advanced message filter
mrefile(1) move or copy messages between maildir folders
mrep(1) reply to messages
mscan(1) generate one-line message summaries
msed(1) manipulate message headers
mseq(1) manipulate message sequences
mshow(1) render messages and extract MIME parts
msort(1) sort messages
mthread(1) arrange messages into discussions
mblaze is a classic command line MUA and has no features for receiving or
transferring mail; you are expected to fetch your mail using fdm(1),
getmail(1) offlineimap(1), procmail(1), or similar , and send it using
dma(8), msmtp(1), sendmail(8), as provided by OpenSMTPD, Postfix, or
similar. mblaze expects your mail to reside in Maildir folders.
mblaze operates directly on Maildir folders and doesn't use its own
caches or databases. There is no setup needed for many uses. All tools
have been written with performance in mind. Enumeration of all mails in
a Maildir is avoided unless necessary, and then optimized to limit
syscalls. Parsing mail metadata is optimized to limit I/O requests.
Initial operations on a large Maildir may feel slow, but as soon as they
are in the file system cache, everything is blazingly fast. The tools
are written to be memory efficient (i.e. not wasteful), but whole
messages are assumed to fit into RAM easily (one at a time).
mblaze has been written from scratch and tested on a large corpus of
personal mail, but is not actually 100% RFC-conforming (which is neither
worth it nor desirable). There may be issues with very old,
nonconforming, messages.
transferring messages; you can operate on messages in a local maildir
spool, or fetch your messages using fdm(1), getmail(1), offlineimap(1),
or similar utilities, and send it using dma(8), msmtp(1), sendmail(8), as
provided by OpenSMTPD, Postfix, or similar.
mblaze operates directly on maildir folders and doesn't use its own
caches or databases. There is no setup needed for many uses. All
utilities have been written with performance in mind. Enumeration of all
messages in a maildir is avoided unless necessary, and then optimized to
limit syscalls. Parsing message metadata is optimized to limit I/O
requests. Initial operations on a large maildir may feel slow, but as
soon as they are in the file system cache, everything is blazingly fast.
The utilities are written to be memory efficient (i.e. not wasteful), but
whole messages are assumed to fit into RAM easily (one at a time).
mblaze has been written from scratch and is now well tested, but it is
not 100% RFC-conforming (which is neither worth it, nor desirable).
There may be issues with very old, nonconforming, messages.
mblaze is written in portable C, using only POSIX functions (apart from a
tiny Linux-only optimization), and has no external dependencies. It
supports MIME and more than 7-bit messages (everything the host iconv(3)
can decode). It assumes you work in a UTF-8 environment. mblaze works
well together with other Unix mail tools such as mairix(1), mu(1), or
well with other Unix utilities such as mairix(1), mu(1), or
offlineimap(1).
EXAMPLES
mblaze tools are designed to be composed together in a pipe. They are
suitable for interactive use and for scripting, and integrate well into a
Unix workflow.
mblaze utilities are designed to be composed together in a pipe. They
are suitable for interactive use and for scripting, and integrate well
into a Unix workflow.
For example, you could decide you want to look at all unseen mail in your
INBOX, oldest first.
For example, you could decide you want to look at all unseen messages in
your INBOX, oldest first.
mlist -s ~/Maildir/INBOX | msort -d | mscan
To operate on a set of mails in multiple steps, you can save it as a
To operate on a set of messages in multiple steps, you can save it as a
sequence, e.g. add a call to mseq -S to the above command:
mlist -s ~/Maildir/INBOX | msort -d | mseq -S | mscan
Now mscan will show message numbers and you could look at the first five
mails at once, for example:
messages at once, for example:
mshow 1:5
Likewise, you could decide to incorporate (by moving from new to cur) all
new mail in all folders, thread it and look at it interactively:
new messages in all folders, thread it and look at it interactively:
mdirs ~/Maildir | xargs minc | mthread | mless
Or you could list the attachments of the 20 largest mails in your INBOX:
Or you could list the attachments of the 20 largest messages in your
INBOX:
mlist ~/Maildir/INBOX | msort -S | tail -20 | mshow -t
Or apply the patches from the current mail:
Or apply the patches from the current message:
mshow -O. '*.diff' | patch
As usual with pipes, the sky is the limit.
CONCEPTS
mblaze deals with messages (which are files), folders (which are Maildir
mblaze deals with messages (which are files), folders (which are maildir
folders), sequences (which are newline-separated lists of messages,
possibly saved on disk in ${MBLAZE:-$HOME/.mblaze}/seq), and the current
message (kept as a symlink in ${MBLAZE:-$HOME/.mblaze}/cur).
@ -117,9 +118,10 @@ AUTHORS
Leah Neukirchen <leah@vuxu.org>
There is a mailing list available at mblaze@googlegroups.com (to
subscribe, send a mail to mblaze+subscribe@googlegroups.com. Please
report security-related bugs directly to the author), as well as an IRC
channel #vuxu on irc.freenode.net.
subscribe, send a message to mblaze+subscribe@googlegroups.com); archives
are available at https://inbox.vuxu.org/mblaze/. There also is an IRC
channel #vuxu on irc.libera.chat. Please report security-related bugs
directly to the author.
LICENSE
mblaze is in the public domain.
@ -127,6 +129,6 @@ LICENSE
To the extent possible under law, the creator of this work has waived all
copyright and related or neighboring rights to this work.
http://creativecommons.org/publicdomain/zero/1.0/
http://creativecommons.org/publicdomain/zero/1.0/
Void Linux January 6, 2018 Void Linux
Void Linux January 18, 2020 Void Linux

@ -1 +1 @@
0.2
1.2

@ -121,7 +121,7 @@ blaze822_date(char *s) {
tm.tm_isdst = -1;
time_t r = tm_to_secs(&tm);
time_t r = mytimegm(&tm);
return r;
fail:
@ -152,14 +152,20 @@ skip_comment(char *s)
// never writes more than dstmax to dst
// returns how many bytes were appended
static size_t
safe_append(char *dst, size_t dstmax, char *strbeg, char *strend)
safe_append(char *dst, size_t dstmax, char *srcbeg, char *srcend)
{
size_t dstlen = strlen(dst);
if (dstmax - dstlen - 1 < strend - strbeg)
strend = strbeg + (dstmax - dstlen - 1);
memcpy(dst + dstlen, strbeg, strend - strbeg);
dst[dstlen + (strend - strbeg)] = 0;
return strend - strbeg;
size_t srclen = srcend - srcbeg;
size_t dstlen = strnlen(dst, dstmax);
if (dstlen == dstmax)
return 0;
if (dstmax - dstlen < srclen + 1)
srclen = dstmax - dstlen - 1;
memcpy(dst + dstlen, srcbeg, srclen);
dst[dstlen + srclen] = 0;
return srclen;
}
static size_t
@ -213,8 +219,9 @@ blaze822_addr(char *s, char **dispo, char **addro)
s++;
if (*addr || *disp)
break;
} else {
s++;
}
s++;
} else if (*s == '<') {
char tok[1024] = { 0 };
char *c = tok;
@ -222,7 +229,9 @@ blaze822_addr(char *s, char **dispo, char **addro)
s++;
while (*s && c < e && *s != '>') {
s = skip_comment(s);
if (*s == '"') {
if (!*s) {
break;
} else if (*s == '"') {
// local part may be quoted, allow all
s++;
while (*s && c < e && *s != '"') {
@ -232,7 +241,7 @@ blaze822_addr(char *s, char **dispo, char **addro)
}
if (*s == '"')
s++;
} else if (*s == '<') {
} else if (*s == '<' || *s == ':') {
c = tok;
s++;
} else {
@ -258,7 +267,7 @@ blaze822_addr(char *s, char **dispo, char **addro)
char *e = tok + sizeof tok;
s++;
while (*s && c < e && *s != '"') {
if (*s == '\\')
if (*s == '\\' && *(s+1))
s++;
*c++ = *s++;
}
@ -268,13 +277,13 @@ blaze822_addr(char *s, char **dispo, char **addro)
if (memchr(tok, '@', c - tok))
not_addr = 1; // @ inside "" is never an addr
if (tc != ttok)
tc += safe_append_space(ttok, sizeof ttok);
*tc = 0;
tc += safe_append(ttok, sizeof ttok, tok, c);
} else if (*s == '(') {
char *z = skip_comment(s);
if (!*disp && *addr) // user@host (name)
safe_append(disp, sizeof disp, s + 1, z - 1);
safe_append(disp, sizeof disp, s + 1,
*z ? z-1 : (*(z-1) == ')' ? z-1 : z));
else if (*disp) { // copy comment
safe_append_space(disp, sizeof disp);
safe_append(disp, sizeof disp, s, z);
@ -285,6 +294,8 @@ blaze822_addr(char *s, char **dispo, char **addro)
// in ipv6 address
if (tc < te)
*tc++ = *s++;
else
s++;
} else { // ignore group name and start over
s++;
tc = ttok;
@ -298,6 +309,8 @@ blaze822_addr(char *s, char **dispo, char **addro)
s++;
if (tc < te)
*tc++ = *s++;
else
s++;
}
}
@ -342,7 +355,7 @@ compress_hdr(char *s, char *end)
while (h < end && *h) {
if (*h == '\n') {
*t++ = ' ';
while (*h && isfws(*h))
while (*h && isfws(*h) && *(h+1))
h++;
}
*t++ = *h++;
@ -407,10 +420,19 @@ unfold_hdr(char *buf, char *end)
compress_hdr(l, end);
}
static int
is_crlf(char *s, size_t len)
{
char *firsteol = memchr(s, '\n', len);
return firsteol && firsteol > s && firsteol[-1] == '\r';
}
struct message *
blaze822(char *file)
{
int fd;
int crlf;
ssize_t rd;
char *buf;
ssize_t bufalloc;
@ -421,7 +443,10 @@ blaze822(char *file)
if (!mesg)
return 0;
fd = open(file, O_RDONLY);
if (strcmp(file, "/dev/stdin") == 0)
fd = dup(0);
else
fd = open(file, O_RDONLY);
if (fd < 0) {
free(mesg);
return 0;
@ -453,15 +478,21 @@ blaze822(char *file)
close(fd);
return 0;
}
if ((end = mymemmem(buf-overlap+used, rd+overlap, "\n\n", 2))) {
end++;
break;
if (used == 0) {
crlf = is_crlf(buf, rd);
}
if ((end = mymemmem(buf-overlap+used, rd+overlap, "\r\n\r\n", 4))) {
end++;
end++;
break;
if (crlf) {
if ((end = mymemmem(buf-overlap+used, rd+overlap, "\r\n\r\n", 4))) {
end++;
end++;
break;
}
} else {
if ((end = mymemmem(buf-overlap+used, rd+overlap, "\n\n", 2))) {
end++;
break;
}
}
used += rd;
@ -489,11 +520,15 @@ blaze822_mem(char *src, size_t len)
if (!mesg)
return 0;
if ((end = mymemmem(src, len, "\n\n", 2))) {
mesg->body = end+2;
} else if ((end = mymemmem(src, len, "\r\n\r\n", 4))) {
mesg->body = end+4;
if (is_crlf(src, len)) {
if ((end = mymemmem(src, len, "\r\n\r\n", 4)))
mesg->body = end+4;
} else {
if ((end = mymemmem(src, len, "\n\n", 2)))
mesg->body = end+2;
}
if (!end) {
end = src + len;
mesg->body = end;
mesg->bodyend = end;
@ -504,8 +539,10 @@ blaze822_mem(char *src, size_t len)
size_t hlen = end - src;
buf = malloc(hlen+1);
if (!buf)
if (!buf) {
free(mesg);
return 0;
}
memcpy(buf, src, hlen);
end = buf+hlen;
@ -577,7 +614,11 @@ blaze822_file(char *file)
char *buf = 0;
ssize_t rd = 0, n;
int fd = open(file, O_RDONLY);
int fd;
if (strcmp(file, "/dev/stdin") == 0)
fd = dup(0);
else
fd = open(file, O_RDONLY);
if (fd < 0)
return 0;
@ -645,54 +686,6 @@ error:
return 0;
}
struct message *
blaze822_mmap(char *file)
{
int fd = open(file, O_RDONLY);
if (fd < 0)
return 0;
struct stat st;
if (fstat(fd, &st) < 0)
goto error;
size_t len = st.st_size;
struct message *mesg = malloc(sizeof (struct message));
if (!mesg)
goto error;
char *buf = mmap(0, len+1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED) {
perror("mmap");
goto error;
}
close(fd);
char *end;
if ((end = mymemmem(buf, len, "\n\n", 2))) {
mesg->body = end+2;
} else if ((end = mymemmem(buf, len, "\r\n\r\n", 4))) {
mesg->body = end+4;
} else {
end = buf + len;
mesg->body = end;
}
unfold_hdr(buf, end);
mesg->msg = mesg->bodychunk = buf;
mesg->end = end;
mesg->bodyend = buf + len;
mesg->orig_header = 0;
return mesg;
error:
close(fd);
return 0;
}
size_t
blaze822_headerlen(struct message *mesg)
{

@ -8,13 +8,23 @@
#define PATH_MAX 4096
#endif
// maildir filename suffix: use a semicolon on winnt/ntfs, otherwise a colon
#ifdef __winnt__
#define MAILDIR_COLON ';'
#define MAILDIR_COLON_SPEC_VER ";2"
#define MAILDIR_COLON_SPEC_VER_COMMA ";2,"
#else
#define MAILDIR_COLON ':'
#define MAILDIR_COLON_SPEC_VER ":2"
#define MAILDIR_COLON_SPEC_VER_COMMA ":2,"
#endif
struct message;
// blaze822.c
struct message *blaze822(char *file); // just header
struct message *blaze822_file(char *file); // header + body (read(2))
struct message *blaze822_mmap(char *file); // header + body (mmap(2))
struct message *blaze822_mem(char *buf, size_t len); // header + body
char *blaze822_hdr_(struct message *mesg, const char *hdr, size_t len);
@ -85,7 +95,7 @@ int filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno
// mygmtime.c
time_t tm_to_secs(const struct tm *tm);
time_t mytimegm(const struct tm *tm);
// slurp.c
@ -95,7 +105,7 @@ int slurp(char *filename, char **bufo, off_t *leno);
// safe_u8putstr.c
#include <stdio.h>
void safe_u8putstr(char *s0, size_t l, FILE *stream);
void safe_u8putstr(char *s0, size_t l, int oneline, FILE *stream);
// pipeto.c

@ -12,7 +12,17 @@ struct message {
#define isfws(c) (((unsigned char)(c) == ' ' || (unsigned char)(c) == '\t' || (unsigned char)(c) == '\n' || (unsigned char)(c) == '\r'))
// ASCII lowercase without alpha check (wrong for "@[\]^_")
#define lc(c) ((c) | 0x20)
// 7bit-ASCII only lowercase/uppercase
#define lc(c) (((unsigned)(c)-'A') < 26 ? ((c) | 0x20) : (c))
#define uc(c) (((unsigned)(c)-'a') < 26 ? ((c) & 0xdf) : (c))
// dirent type that can be a mail/dir (following symlinks)
#if defined(DT_REG) && defined(DT_LNK) && defined(DT_UNKNOWN)
#define MAIL_DT(t) (t == DT_REG || t == DT_LNK || t == DT_UNKNOWN)
#define DIR_DT(t) (t == DT_DIR || t == DT_UNKNOWN)
#else
#define MAIL_DT(t) (1)
#define DIR_DT(t) (1)
#endif
void *mymemmem(const void *h0, size_t k, const void *n0, size_t l);

@ -0,0 +1,402 @@
#compdef maddr magrep mbnc mcom mdeliver mdirs mexport mflag mflow mfwd mgenmid mhdr minc mless mlist mmime mmkdir mpick mrep mscan msed mseq mshow msort mthread
_mblaze_colon_separated_headers() {
_message 'headers (colon separated)'
}
_mblaze_message() {
local ret=1 expl tmp curmsg
local -a mseq mseqnums mshortcuts mshortcutdescrs
setopt localoptions extendedglob
if [[ -prefix './' ]]; then
_description files expl 'message file'
_files "$expl[@]" && ret=0
fi
curmsg=$(mseq . 2>/dev/null)
if [[ -z $curmsg || ! -f $curmsg ]]; then
_message 'no current sequence'
return $ret
fi
mseq=( ${(f)"$(mscan -f '%3n %c%u%r %10d %17f %t %2i%s' : 2>/dev/null)"} )
mseq=( ${(M)mseq:# #<->*} )
mseqnums=( ${(M)${mseq## #}##<->} )
mseq=( ${mseq//:/\\:} )
_describe -V -t mseq 'seq messages' mseq mseqnums && ret=0
mshortcuts=( '.' '+' '$' '^' '=' '_' )
mshortcutdescrs=(
' . current message'
' + next message'
# ' - previous message'
' $ last message'
' ^ parent message'
' = current thread'
' _ current subthread'
)
_describe -t mshortcuts 'shortcuts' mshortcutdescrs mshortcuts -S '' && ret=0
return $ret
}
_mblaze_message_part() {
setopt localoptions noksharrays extendedglob
local ret=1 expl tmp msgarg parts partnums
tmp=$words[(i)(-O|-x)]
if (( tmp >= $#words )); then
_message -e mparts 'message parts (parse error)'
return 1
fi
msgarg=$words[$tmp+1]
parts=( ${(f)"$(mshow -t $msgarg)"} )
parts=( ${(M)parts:# ##<->:*} )
if [[ -n $parts ]]; then
partnums=( ${${parts## #}%%:*} )
parts=( ${parts//:/\\:} )
_describe -V -t mparts 'message parts' parts partnums -l && ret=0
else
_message -e mparts 'message parts (none found)'
fi
return $ret
}
_mblaze_dirs() {
local ret=1 expl countnew countcur
local -a mailboxes mailbox_descriptions
setopt localoptions extendedglob
mailboxes=( */cur(:h) )
mailbox_descriptions=( ${(f)"$(mlist -i $mailboxes)"} )
_describe -t mailboxes "mailboxes" mailbox_descriptions mailboxes && ret=0
_description files expl 'directories'
_path_files "$expl[@]" -g "^(${(j:|:)mailboxes})(/)" && ret=0
}
_mblaze_header_regex() {
_message header\ regex
}
_mblaze_flags() {
_message flags
}
_maddr() {
local context state line expl
local -A opt_args
_arguments \
'-a[print addr-spec address without display name]' \
'-h[search only given headers]:header list:_mblaze_colon_separated_headers' \
'*:message:_mblaze_message'
}
_magrep() {
local context state line expl
local -A opt_args
_arguments \
'-a[search for regex in RFC 2822 address header parts only]' \
'-c[only print a count of matching messages]' \
'-d[decode the header according to RFC 2047 prior to searching]' \
'-i[match regex case insensitively]' \
'-m[do not show more than max matches]:max: ' \
'(-c -q -v)-o[print matches only]' \
'(-c -q -v)-p[print filename, header and matching]' \
'-q[quiet mode: do not print anything]' \
'-v[invert match]' \
'*:message:_mblaze_header_regex'
}
_mbnc() {
local context state line expl
local -A opt_args
_arguments \
':message:_mblaze_message'
}
_mcom() {
local context state line expl
local -A opt_args
_arguments \
- resume \
'-r[resume editing]:draft: ' \
- recip \
'*:recipients: '
}
_mdeliver() {
local context state line expl
local -A opt_args
_arguments \
'-c[deliver into cur/ instead of new/]' \
'-v[print message filename after delivery]' \
'-X[override flags]:flags:_mblaze_flags' \
'-M[deliver messages from mbox]' \
':dir:_mblaze_dirs'
}
_mdirs() {
_mblaze_dirs
}
_mexport() {
local context state line expl
local -A opt_args
_arguments \
'-S[add Status and X-Status headers according to flags]' \
'*:messages:_mblaze_message'
}
_mflag() {
local context state line expl
local -A opt_args
_arguments \
'(-d)-D[mark as draft]' \
'(-f)-F[mark as flagged]' \
'(-p)-P[mark as passed (resent/forwarded/bounced)]' \
'(-r)-R[mark as replied-to]' \
'(-s)-S[mark as seen]' \
'(-t)-T[mark as trashed]' \
'-X[mark with characters]:marking characters: ' \
'(-D)-d[unmark as draft]' \
'(-F)-f[unmark as flagged]' \
'(-P)-p[unmark as passed (resent/forwarded/bounced)]' \
'(-R)-r[unmark as replied-to]' \
'(-S)-s[unmark as seen]' \
'(-T)-t[unmark as trashed]' \
'-x[unmark with characters]:marked chacaters: ' \
'-v[read messages from standard input or current sequence]' \
'*:messages:_mblaze_message'
}
_mflow() {
local context state line expl
local -A opt_args
_arguments \
'-f[force wrapping of long lines]' \
'*-q[prefix lines with >]' \
'-w[set maximum line length]:width: ' \
'*:dirs:_mblaze_dirs'
}
_mfwd() {
local context state line expl
local -A opt_args
_arguments \
'-r[forward as plain text]' \
':*:message:_mblaze_message'
}
_mgenmid() {
_message 'no arguments'
}
_mhdr() {
local context state line expl
local -A opt_args
_arguments \
'-h[print specific headers only]:headers: ' \
'-p[print specific parameter from header only]:parameter: ' \
'-d[print headers decoded]' \
'-H[prefix output with filename]' \
'-M[print all occurrences, not only the first]' \
'(-D)-A[print addresses from headers]' \
'(-A)-D[print date as unix timestamp]' \
'*:message:_mblaze_message'
}
_minc() {
local context state line expl
local -A opt_args
_arguments \
'-q[quiet mode: don'\''t print filenames]' \
'*:dirs:_mblaze_dirs'
}
_mless() {
local context state line expl
local -A opt_args
_arguments \
':message:_mblaze_message'
}
_mlist() {
local context state line expl
local -A opt_args
_arguments \
'(-d)-D[only list messages marked as draft]' \
'(-f)-F[only list messages marked as flagged]' \
'(-p)-P[only list messages marked as passed (resent/forwarded/bounced)]' \
'(-r)-R[only list messages marked as replied-to]' \
'(-s)-S[only list messages marked as seen]' \
'(-t)-T[only list messages marked as trashed]' \
'-X[only list messages marked with characters]:marked characters: ' \
'(-D)-d[don'\''t list messages marked as draft]' \
'(-F)-f[don'\''t list messages marked as flagged]' \
'(-P)-p[don'\''t list messages marked as passed (resent/forwarded/bounced)]' \
'(-R)-r[don'\''t list messages marked as replied-to]' \
'(-S)-s[don'\''t list messages marked as seen]' \
'(-T)-t[don'\''t list messages marked as trashed]' \
'-x[don'\''t list messages marked with characters]:marked chacaters: ' \
'(-c -N)-C[only list messages in cur]' \
'(-n -C)-N[only list messages in new]' \
'(-C -N)-c[don'\''t list messages in cur]' \
'(-N -C)-n[don'\''t list messages in new]' \
'-i[print summaries instead of folder names]' \
'*:dirs:_mblaze_dirs'
}
_mmkdir() {
local context state line expl
local -A opt_args
_arguments \
'*:directory name: '
}
_mmime() {
local context state line expl
local -A opt_args
_arguments \
'(-r)-c[check mode (don'\''t output anything)]' \
'(-c)-r[raw mode (generate text/plain)]' \
'-t[override Content-Type of toplevel part]:Content-Type: '
}
_mpick() {
local context state line expl
local -A opt_args
_arguments \
'-T[include whole thread]' \
'-t[limit messages to test]:test: ' \
'*:message:_mblaze_message'
}
_mrep() {
local context state line expl
local -A opt_args
_arguments \
':message:_mblaze_message'
}
_mscan() {
local context state line expl
local -A opt_args
_arguments \
'-n[only print message numbers]' \
'-I[force ISO date output]' \
'-f[specify output format]:format: ' \
'*:messages:_mblaze_message'
}
_msed() {
local context state line expl
local -A opt_args
_arguments \
':sed script: ' \
'*:messages:_mblaze_message'
}
_mseq() {
local context state line expl
local -A opt_args
_arguments \
'(Cmode)-f[fix non-existing filenames]' \
'(Cmode)-r[remove leading indentation from filenames]' \
- argmode \
'-c[use current message]:message:_mblaze_message' \
'*:messages:_mblaze_message' \
- Smode \
'-S[set message sequence from stdin]' \
- Amode \
'-A[append message sequence from stdin]' \
- Cmode \
'-C[set current message]:message:_mblaze_message'
}
_mshow() {
local context state line expl
local -A opt_args
_arguments \
- regular \
'-n[only print message numbers]' \
'-h[display only given headers]:header list:_mblaze_colon_separated_headers' \
'-A[multipart/alternative preference]:mime type: ' \
'-n[don'\''t update current message link]' \
'(-r)-q[print only header]' \
'(-q)-r[print body raw]' \
'(-q)-F[don'\''t apply MIME filters]' \
'-H[print headers raw]' \
'-L[don'\''t filter headers]' \
'(-q)-N[don'\''t show MIME structure markers]' \
'*:messages:_mblaze_message' \
- xmode \
'-x[extraction mode]:message:_mblaze_message:*:part:_mblaze_message_part' \
- Omode \
'-O[extraction mode to standard output]:message:_mblaze_message:*:part:_mblaze_message_part' \
- tmode \
'-t[list mode]:*:message:_mblaze_message' \
- Rmode \
'-R[render text parts]:message:_mblaze_message'
}
_msort() {
local context state line expl
local -A opt_args
_arguments \
'-r[reverse order]' \
'*:message:_mblaze_message' \
+ '(order)' \
'-f[sort by From]' \
'-d[sort by Date]' \
'-s[sort by Subject]' \
'-F[sort by filename]' \
'-M[sort by file modification time]' \
'-S[sort by file size]' \
'-U[sort unread messages after read messages]' \
'-I[sort flagged messages before unflagged messages]'
}
_mthread() {
local context state line expl
local -A opt_args
_arguments \
'-a[don'\''t prune unresolved Message-IDs]' \
'-S[treat as optional messages]:optional message:_mblaze_message' \
'*:message:_mblaze_message'
}
_mblaze() {
local ret=1
_call_function ret _$service
return ret
}
_mblaze "$@"

@ -5,7 +5,7 @@
echo "Content-Type: $PIPE_CONTENTTYPE"
echo
cat
} | mshow -t /dev/stdin | awk '
} | mshow -t - | awk '
BEGIN { split("", ct) }
/^ [0-9]/ { ct[++n] = $2 }
function prefer(t) { for (i in ct) if (ct[i] == t) exit(64+i) }

@ -0,0 +1,10 @@
#!/bin/sh
# mcount - count mails from sequence on stdin
[ -t 1 ] && stdout=1
awk -v stdout=$stdout '
!/^ *</ { n++ }
!stdout { print }
END { fflush(); print n " mails processed" > "/dev/stderr" }
'

@ -5,17 +5,26 @@
IFS='
'
FLAGS=$(mhdr -d -A -h from:to:cc:bcc: "$1" |sort -u |sed 's/^/--recipient=/')
FLAGS=$(maddr -a -h from:to:cc:bcc: "$1" |sort -u |sed 's/^/--recipient=/')
FROM=$(maddr -a -h from "$1" | sed 1q)
[ "$FROM" ] && key="--default-key=$FROM"
if command -v gpg2 >/dev/null; then
GPG=gpg2
else
GPG=gpg
fi
TMPD=$(mktemp -d -t mencrypt.XXXXXX)
trap "rm -rf '$TMPD'" INT TERM EXIT
awk '/^$/,0' "$1" |
mmime |
gpg --armor --encrypt --sign $FLAGS -o $TMPD/msg.asc ||
$GPG "$key" --armor --encrypt --sign $FLAGS -o "$TMPD/msg.asc" ||
exit $?
printf 'Version: 1\n' >$TMPD/version
printf 'Version: 1\n' >"$TMPD/version"
{
sed '/^$/q' "$1"

@ -1,14 +1,17 @@
#!/bin/sh -e
# menter [MSG] - run subshell in temporary directory with MSG extracted
[ "$#" -eq 0 ] && set -- :
[ "$#" -eq 0 ] && set -- .
f="$(mseq "$1" | sed 1q)"
[ -z "$f" ] && printf 'No message %s.\n' "$1" 1>&2 && exit 1
dir=$(mktemp -d -t menter.XXXXXX)
cd "$dir"
mshow -t "$1"
mshow -x "$1" 2>/dev/null
ls -l
ln -s "$(mseq "$1")" msg
ln -s "$f" msg
"${SHELL:-/bin/sh}" || true
echo rm -r "$dir"
rm -r "$dir"

@ -1,5 +1,11 @@
#!/bin/sh -e
if command -v gpg2 >/dev/null; then
GPG=gpg2
else
GPG=gpg
fi
tmp=$(mktemp -t mgpg.XXXXXX)
trap "rm -f '$tmp'" INT TERM EXIT
@ -14,7 +20,7 @@ n=$(mshow -t "$tmp" | awk -F: '
/: application\/octet-stream/ {if (supported) print $1}')
if [ "$n" ]; then
mshow -O "$tmp" "$n" | gpg -d 2>&1
mshow -O "$tmp" "$n" | $GPG --quiet -d 2>&1 || exit 0
exit 64
fi
exit 63

@ -0,0 +1,58 @@
#!/bin/sh
# mmailto mailto:... - mailto: handler spawning mcom in a terminal emulator
tryterm() {
if [ -z "$TERMINAL" ] && command -v "$1" >/dev/null; then
TERMINAL="$*"
fi
}
tryterm x-terminal-emulator
tryterm urxvt
tryterm xterm
if [ -z "$TERMINAL" ]; then
echo 'No terminal emulator found, set $TERMINAL.' 1>&2
exit 1
fi
IFS='
'
exec $TERMINAL -e mcom $(
awk -v url="$1" '
function decode(s) {
hexdigits = "0123456789abcdef"
for (i = 1; i < length(s); i++) {
if (substr(s, i, 3) ~ /%[0-9a-fA-F][0-9a-fA-F]/) {
c = sprintf("%c", (index(hexdigits, tolower(substr(s, i+1, 1)))-1) * 16 + \
index(hexdigits, tolower(substr(s, i+2, 1)))-1)
if (c == "\n") c = " "
s = substr(s, 1, i-1) c substr(s, i+3)
i += 2
}
}
return s
}
BEGIN {
url = decode(url)
sub(/^mailto:/, "", url)
split(url, parts, "?")
to = parts[1]
split(parts[2], fields, "&")
args[1] = to
for (i in fields) {
split(fields[i], kv, "=")
if (kv[1] != "r") {
args[length(args)+1] = "-" kv[1]
args[length(args)+1] = kv[2]
}
}
for (i in args) {
print decode(args[i])
}
}
'
)

@ -1,4 +1,4 @@
#!/bin/sh
# mmairix QUERY - mblaze wrapper around mairix
mairix -r "$@" | sed 's,//*,/,g' | mless
mairix -r "$@" | sed 's,//*,/,g' | msort -rd | mless

@ -0,0 +1,18 @@
#!/bin/sh -e
# mopenall [MSG] - open every attachements in xdg-open
[ "$#" -eq 0 ] && set -- .
f="$(mseq "$1" | sed 1q)"
[ -z "$f" ] && printf 'No message %s.\n' "$1" 1>&2 && exit 1
dir=$(mktemp -d -t menter.XXXXXX)
cd "$dir"
mshow -t "$1"
mshow -B -x "$1" 2>/dev/null
for f in * ; do
xdg-open "$f" &
done
wait
echo rm -r "$dir"
rm -r "$dir"

@ -0,0 +1,15 @@
#!/bin/sh
# mp7m - decode S/MIME .p7m format
tmp=$(mktemp -t mp7m.XXXXXX)
trap "rm -f '$tmp'" INT TERM EXIT
cat >"$tmp"
if openssl pkcs7 -print_certs <"$tmp" |
openssl smime -verify -in "$tmp" -inform DER -noverify -signer /dev/stdin
then
exit 64
else
exit 63
fi

@ -3,4 +3,4 @@
IFS='
'
exec cat -- $(mseq "${@:-.}")
exec cat -- $(mseq "${@:-.}" | sed 's/^ *//')

@ -1,8 +1,8 @@
#!/bin/sh
# mrecode - recode stdin respecting PIPE_CHARSET into UTF-8
if [ -n "$PIPE_CHARSET" ]; then
exec iconv -f "$PIPE_CHARSET" -t UTF-8
else
exec cat
fi
case "$PIPE_CHARSET" in
''|*[Uu][Nn][Kk][Nn][Oo][Ww][Nn]*) exec cat;;
*) exec iconv -f "$PIPE_CHARSET" -t UTF-8;;
esac

@ -0,0 +1,45 @@
#!/bin/sh
MBLAZE=${MBLAZE:-$HOME/.mblaze}
engine=$(mhdr -h search-engine "$MBLAZE/profile")
while getopts nmx opt; do
case $opt in
n)
engine=notmuch
;;
m)
engine=mu
;;
x)
engine=mairix
;;
'?')
printf "Usage: %s: [-n | -m | -x] query\n" "$0" 1>&2
exit 1
;;
esac
done
shift $(($OPTIND - 1))
[ -z "$engine" ] && engine=notmuch
case $engine in
notmuch)
exec notmuch search --output=files "$@"
;;
mu)
exec mu find --fields l "$@"
;;
mairix)
if [ "$#" -eq 0 ]; then
printf "Usage: %s -x query\n" "$0" 1>&2
exit 1
fi
exec mairix -r "$@"
;;
*)
echo "Unsupported search engine: $engine"
exit 1
;;
esac

@ -0,0 +1,75 @@
.Dd September 27, 2018
.Dt MSEARCH 1
.Os
.Sh NAME
.Nm msearch
.Nd search messages
.Sh SYNOPSIS
.Nm
.Op Fl n | Fl m | Fl x
.Op Fl -
.Op Ar query
.Sh DESCRIPTION
.Nm
searches for messages matching
.Ar query ,
using an external search engine, and prints file names separated by newlines.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl n
Search using
.Xr notmuch-search 1 .
.It Fl m
Search using
.Xr mu-find 1 .
.It Fl x
Search using
.Xr mairix 1 .
.It Fl -
Stop scanning for arguments.
.It Ar query
The search query, whose interpretation is up to the search engine.
This can also contain additional arguments for them
.El
.Pp
If no search engine is specified as an argument,
.Sq Li Search-Engine\&:
from
.Pa "${MBLAZE:-$HOME/.mblaze}/profile"
is used.
Valid values for this setting are
.Sq notmuch ,
.Sq mairix ,
and
.Sq mu .
If that is unset,
.Xr notmuch-search 1
is used.
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev MBLAZE
Directory containing mblaze configuration files.
(Default:
.Pa $HOME/.mblaze )
.El
.Sh EXIT STATUS
.Ex -std
Note that the called programs may have their own exit values.
.Sh SEE ALSO
.Xr mairix 1 ,
.Xr mu-find 1 ,
.Xr notmuch-search 1 ,
.Xr mblaze-profile 5
.Sh AUTHORS
.An Thomas Schneider Aq Mt qsx@chaotikum.eu
.Sh LICENSE
.Nm
is in the public domain.
.Pp
To the extent possible under law,
the creator of this work
has waived all copyright and related or
neighboring rights to this work.
.Pp
.Lk http://creativecommons.org/publicdomain/zero/1.0/

@ -3,14 +3,23 @@
[ -f "$1" ] || exit 1
if command -v gpg2 >/dev/null; then
GPG=gpg2
else
GPG=gpg
fi
IFS='
'
TMPD=$(mktemp -d -t msign.XXXXXX)
trap "rm -rf '$TMPD'" INT TERM EXIT
FROM=$(maddr -a -h from "$1" | sed 1q)
[ "$FROM" ] && key="--default-key=$FROM"
awk '/^$/,0' "$1" | mmime | sed 's/$/ /' >"$TMPD"/content
gpg --armor --detach-sign -o "$TMPD"/signature.asc "$TMPD"/content ||
$GPG $key --armor --detach-sign -o "$TMPD"/signature.asc "$TMPD"/content ||
exit $?
{

@ -57,15 +57,12 @@ STDOUT.sync = true
ARGV.each { |group|
FileUtils.mkdir_p(File.join(dir, group, "cur"))
FileUtils.mkdir_p(File.join(dir, group, "new"))
FileUtils.mkdir_p(File.join(dir, group, "tmp"))
nntp.write("GROUP #{group}\r\n")
msg = nntp.gets
unless msg =~ /^211 /
STDERR.puts msg
$exit = 69
next
end
@ -78,6 +75,10 @@ ARGV.each { |group|
low = high - LIMIT + 1 if number > LIMIT - 1
low = 1 if low <= 0
FileUtils.mkdir_p(File.join(dir, group, "cur"))
FileUtils.mkdir_p(File.join(dir, group, "new"))
FileUtils.mkdir_p(File.join(dir, group, "tmp"))
have = Dir.entries(File.join(dir, group, "cur")).
map { |f| $1.to_i if f =~ /N=(\d+)/ }.compact
@ -135,3 +136,5 @@ ARGV.each { |group|
puts
}
exit $exit || 0

@ -1,7 +1,13 @@
#!/bin/sh
# mverify MSG - verify a OpenPGP or SMIME message
# Needs gpg (for OpenPGP) and openssl (for SMIME).
# Needs gpg2 (for OpenPGP) and openssl (for SMIME).
if command -v gpg2 >/dev/null; then
GPG=gpg2
else
GPG=gpg
fi
[ "$#" -eq 0 ] && set -- .
@ -14,7 +20,7 @@ signed && content && !signature && indent == si+2 { signature = 0+$1; type = $2
function q(a) { gsub("\\47", "\47\\\47\47", a); return "\47"a"\47" }
END {
if (type == "" && plain) { // guess plain text armored signature
exit(system("mshow -r " q(msg) " | gpg --verify"));
exit(system("mshow -r " q(msg) " | '$GPG' --verify"));
} else if (type == "") {
print("No signature found.")
exit(100)
@ -22,7 +28,7 @@ END {
exit(system("mshow -r -O " q(msg) " " q(content) \
" | sed $DOS2UNIX | " \
" { mshow -O " q(msg) " " q(signature) \
" | gpg --verify - /dev/fd/3; } 3<&0"))
" | '$GPG' --verify - /dev/fd/3; } 3<&0"))
} else if (type == "application/pkcs7-signature") {
exit(system("mshow -r -O " q(msg) " " q(signed) \
" | openssl smime -verify"))

@ -7,12 +7,37 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static int aflag;
static int dflag;
static char defaulthflags[] = "from:sender:reply-to:to:cc:bcc:"
"resent-from:resent-sender:resent-to:resent-cc:resent-bcc:";
static char *hflag = defaulthflags;
void
print_quoted(char *s)
{
char *t;
for (t = s; *t; t++)
if ((unsigned char)*t < 32 || strchr("()<>[]:;@\\,.\"", *t))
goto quote;
printf("%s", s);
return;
quote:
putchar('"');
for (t = s; *t; t++) {
if (*t == '"' || *t == '\\')
putchar('\\');
putchar(*t);
}
putchar('"');
}
void
addr(char *file)
{
@ -32,21 +57,32 @@ addr(char *file)
v = blaze822_chdr(msg, h);
if (v) {
char *disp, *addr;
char vdec[16384];
blaze822_decode_rfc2047(vdec, v, sizeof vdec - 1, "UTF-8");
vdec[sizeof vdec - 1] = 0;
v = vdec;
char ddec[16384];
while ((v = blaze822_addr(v, &disp, &addr))) {
if (disp) {
blaze822_decode_rfc2047(ddec, disp, sizeof ddec - 1, "UTF-8");
ddec[sizeof ddec - 1] = 0;
disp = ddec;
}
if (disp && addr && strcmp(disp, addr) == 0)
disp = 0;
if (disp && addr) {
if (aflag)
if (aflag) {
if (addr)
printf("%s\n", addr);
} else if (dflag) {
if (disp) {
print_quoted(disp);
printf("\n");
}
} else {
if (disp && addr) {
print_quoted(disp);
printf(" <%s>\n", addr);
} else if (addr) {
printf("%s\n", addr);
else
printf("%s <%s>\n", disp, addr);
} else if (addr) {
printf("%s\n", addr);
}
}
}
}
@ -57,22 +93,26 @@ addr(char *file)
break;
}
}
blaze822_free(msg);
}
int
main(int argc, char *argv[])
{
int c;
while ((c = getopt(argc, argv, "ah:")) != -1)
while ((c = getopt(argc, argv, "adh:")) != -1)
switch (c) {
case 'a': aflag = 1; break;
case 'd': dflag = 1; break;
case 'h': hflag = optarg; break;
default:
fprintf(stderr,
"Usage: maddr [-a] [-h headers] [msgs...]\n");
"Usage: maddr [-ad] [-h headers] [msgs...]\n");
exit(1);
}
xpledge("stdio rpath", "");
if (argc == optind && isatty(0))
blaze822_loop1(":", addr);
else

@ -7,14 +7,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static int aflag;
static int cflag;
static int dflag;
static int hflag;
static int iflag;
static int lflag;
static int oflag;
static int pflag;
static int qflag;
@ -29,8 +33,8 @@ static char *curfile;
int
match(char *file, char *hdr, char *s)
{
if (oflag && !cflag && !qflag && !vflag) {
regmatch_t pmatch;
if (oflag && !cflag && !qflag && !vflag && !lflag) {
regmatch_t pmatch = {0};
int len, matched;
matched = 0;
while (*s && regexec(&pattern, s, 1, &pmatch, 0) == 0) {
@ -41,6 +45,8 @@ match(char *file, char *hdr, char *s)
}
if (pflag)
printf("%s: %s: ", file, hdr);
else if (hflag)
printf("%s: ", hdr);
printf("%.*s\n", len, s);
s += len;
matched++;
@ -51,9 +57,12 @@ match(char *file, char *hdr, char *s)
exit(0);
matches++;
if (!cflag) {
printf("%s", file);
if (vflag || !hflag)
printf("%s", file);
if (pflag && !vflag)
printf(": %s: %s", hdr, s);
else if (hflag && !vflag)
printf("%s: %s", hdr, s);
putchar('\n');
}
if (mflag && matches >= mflag)
@ -81,7 +90,7 @@ match_part(int depth, struct message *msg, char *body, size_t bodylen)
strcasecmp(charset, "utf-8") == 0 ||
strcasecmp(charset, "utf8") == 0 ||
strcasecmp(charset, "us-ascii") == 0) {
if (pflag && !cflag && !oflag && !vflag) {
if ((hflag || pflag) && !cflag && !oflag && !vflag) {
char *s, *p;
for (p = s = body; p < body+bodylen+1; p++) {
if (*p == '\r' || *p == '\n') {
@ -116,13 +125,33 @@ match_body(char *file)
return;
blaze822_walk_mime(msg, 0, match_part);
blaze822_free(msg);
}
int
match_value(char *file, char *h, char *v)
{
if (dflag) {
char d[4096];
blaze822_decode_rfc2047(d, v, sizeof d, "UTF-8");
return match(file, h, d);
} else if (aflag) {
char *disp, *addr;
while ((v = blaze822_addr(v, &disp, &addr))) {
if (addr && match(file, h, addr))
return 1;
}
} else {
return match(file, h, v);
}
return 0;
}
void
magrep(char *file)
{
if (!*header) {
char *flags = strstr(file, ":2,");
char *flags = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA);
if (flags)
match(file, "flags", flags+3);
return;
@ -139,21 +168,21 @@ magrep(char *file)
if (!msg)
return;
char *v = blaze822_chdr(msg, header);
if (v) {
if (dflag) {
char d[4096];
blaze822_decode_rfc2047(d, v, sizeof d, "UTF-8");
match(file, header, d);
} else if (aflag) {
char *disp, *addr;
while ((v = blaze822_addr(v, &disp, &addr))) {
if (addr && match(file, header, addr))
if (strcmp(header, "*") == 0) {
char *hdr = 0;
while ((hdr = blaze822_next_header(msg, hdr))) {
char *v = strchr(hdr, ':');
if (v) {
*v = 0;
if (match_value(file, hdr, v + 1 + (v[1] == ' ')) && lflag)
break;
*v = ':';
}
} else {
match(file, header, v);
}
} else {
char *v = blaze822_chdr(msg, header);
if (v)
(void)match_value(file, header, v);
}
blaze822_free(msg);
@ -163,12 +192,14 @@ int
main(int argc, char *argv[])
{
int c;
while ((c = getopt(argc, argv, "acdim:opqv")) != -1)
while ((c = getopt(argc, argv, "acdhilm:opqv")) != -1)
switch (c) {
case 'a': aflag = 1; break;
case 'c': cflag = 1; break;
case 'd': dflag = 1; break;
case 'h': hflag = 1; break;
case 'i': iflag = REG_ICASE; break;
case 'l': lflag = 1; break;
case 'm': mflag = atol(optarg); break;
case 'o': oflag = 1; break;
case 'p': pflag = 1; break;
@ -177,7 +208,7 @@ main(int argc, char *argv[])
default:
usage:
fprintf(stderr,
"Usage: magrep [-c|-o|-p|-q|-m max] [-v] [-i] [-a|-d] header:regex [msgs...]\n");
"Usage: magrep [-c|-h|-o|-p|-q|-m max] [-v] [-i] [-l] [-a|-d] header:regex [msgs...]\n");
exit(2);
}
@ -189,6 +220,8 @@ usage:
if (!rx)
goto usage;
xpledge("stdio rpath", "");
*rx++ = 0;
int r = regcomp(&pattern, rx, REG_EXTENDED | iflag);
if (r != 0) {

@ -1,4 +1,4 @@
.Dd July 22, 2016
.Dd March 30, 2020
.Dt MADDR 1
.Os
.Sh NAME
@ -6,7 +6,7 @@
.Nd extract mail addresses from messages
.Sh SYNOPSIS
.Nm
.Op Fl a
.Op Fl ad
.Op Fl h Ar headers
.Op Ar msgs\ ...
.Sh DESCRIPTION
@ -36,6 +36,8 @@ The options are as follows:
.Bl -tag -width Ds
.It Fl a
Only print the addr-spec address, not the display name.
.It Fl d
Only print the display name.
.It Fl h Ar headers
Only search the colon-separated list of
.Ar headers

@ -1,4 +1,4 @@
.Dd February 15, 2017
.Dd September 10, 2018
.Dt MAGREP 1
.Os
.Sh NAME
@ -6,9 +6,10 @@
.Nd search messages matching a pattern
.Sh SYNOPSIS
.Nm
.Op Fl c | Fl q | Fl m Ar max
.Op Fl c | Fl h | Fl o | Fl p | Fl q | Fl m Ar max
.Op Fl v
.Op Fl i
.Op Fl l
.Op Fl a | Fl d
.Ar header Ns Cm \&: Ns Ar regex
.Op Ar msgs\ ...
@ -36,6 +37,13 @@ matches against the maildir flags of
If
.Ar header
is
.Sq Cm \&* ,
.Nm
searches for the pattern in any header.
.Pp
If
.Ar header
is
.Sq Cm \&/ ,
.Nm
searches any plain text parts of the
@ -58,15 +66,25 @@ in RFC 2822 address
.Ar header
parts only.
.It Fl c
Only print a count of matching messages.
Only print a count of matching headers.
If
.Fl v
is specified,
count non-matching headers instead.
.It Fl d
Decode the
.Ar header
according to RFC 2047 prior to searching.
.It Fl h
Like
.Fl p
but do not print the file name.
.It Fl i
Match
.Ar regex
case insensitively.
.It Fl l
Only search a message until a match has been found.
.It Fl m Ar max
Do not show more than
.Ar max
@ -111,7 +129,8 @@ and >1 if an error occurs.
.Sh SEE ALSO
.Xr grep 1 ,
.Xr mmsg 7 ,
.Xr regex 7
.Xr regex 7 /
.Xr re_format 7
.Sh AUTHORS
.An Leah Neukirchen Aq Mt leah@vuxu.org
.Sh TRIVIA

@ -1,4 +1,4 @@
.Dd February 20, 2017
.Dd January 27, 2024
.Dt MBLAZE-PROFILE 5
.Os
.Sh NAME
@ -49,11 +49,34 @@ The fully qualified domain name used for
.Li Message\&-Id\&:
generation in
.Xr mgenmid 1 .
.It Li Maildir\&:
If set,
.Xr mdirs 1
will use this maildir when no directories are supplied.
.It Li Outbox\&:
If set,
.Xr mcom 1
will create draft messages in this maildir,
and save messages there after sending.
will save messages in this maildir after sending.
.It Li Drafts\&:
If set,
.Xr mcom 1
will create draft messages in this maildir (defaults to Outbox).
.It Li Reply-From\&:
A comma-separated list of display name and mail address pairs,
formatted like this:
.Dl Li Primary Name <myname1@domain1>, Name v.2 <myname2@domain2>, \[dq]Name, My Third\[dq] <myname3@domain3>, ...
The first of these that appears in the
.Li Delivered-To\&: ,
.Li To\&: ,
.Li Cc\&: ,
or
.Li Bcc\&:
header will be used as the
.Li From\&:
address in a reply.
If not set,
.Li Alternate\&-Mailboxes\&:
will be used as a default.
.It Li Scan\&-Format\&:
The default format string for
.Xr mscan 1 .

@ -1,4 +1,4 @@
.Dd January 6, 2018
.Dd January 18, 2020
.Dt MBLAZE 7
.Os
.Sh NAME
@ -32,7 +32,7 @@ list maildir folders, recursively
.It Xr mexport 1
export messages as mbox file
.It Xr mflag 1
manipulate maildir flags
manipulate maildir message flags
.It Xr mflow 1
reflow format=flowed plain text messages
.It Xr mfwd 1
@ -54,6 +54,8 @@ create MIME messages
create new maildir folders
.It Xr mpick 1
advanced message filter
.It Xr mrefile 1
move or copy messages between maildir folders
.It Xr mrep 1
reply to messages
.It Xr mscan 1
@ -161,6 +163,9 @@ thread it and look at it interactively:
Or you could list the attachments of the 20 largest messages in your INBOX:
.Dl mlist ~/Maildir/INBOX | msort -S | tail -20 | mshow -t
.Pp
Or delete messages beyond a certain age:
.Dl mlist ~/Maildir/INBOX | mpick -t 'mtime < \&"-365d\&"' | xargs rm
.Pp
Or apply the patches from the current message:
.Dl mshow -O . '*.diff' | patch
.Pp
@ -204,10 +209,12 @@ There is a mailing list available at
.Po
to subscribe, send a message to
.Mt mblaze+subscribe@googlegroups.com
.Pc
and an IRC channel
.Pc ;
archives are available at
.Lk https://inbox.vuxu.org/mblaze/ .
There also is an IRC channel
.Li #vuxu
on irc.freenode.net.
on irc.libera.chat.
Please report security-related bugs directly to the author.
.Sh LICENSE
.Nm

@ -1,4 +1,4 @@
.Dd January 6, 2017
.Dd April 21, 2021
.Dt MCOM 1
.Os
.Sh NAME
@ -6,33 +6,46 @@
.Nm mfwd ,
.Nm mbnc ,
.Nm mrep
.Nd compose, forward, bounce, reply to, and send mail
.Nd compose, forward, bounce, reply, send messages
.Sh SYNOPSIS
.Nm mcom
.Op Fl Ar header Ar values\ ...
.Op Ar recipients\ ...
.Nm mcom
.Fl r Op draft
.Nm mrep
.Op Fl noquote
.Op Fl Ar header Ar values\ ... Fl -
.Op Ar msg
.Nm mfwd
.Op Fl r
.Op Fl Ar header Ar values\ ... Fl -
.Op Ar msgs\ ...
.Nm mbnc
.Ar msg
.Nm mrep
.Ar msg
.Op Fl Ar header Ar values\ ... Fl -
.Op Ar msg
.Sh DESCRIPTION
.Nm mcom
creates a new draft mail and opens it in an editor.
After editing, a loop is started where the user can re-edit,
send or cancel the mail.
creates a new draft message and opens it in an editor.
After editing, a loop is started where the user can send,
re-edit or cancel the message.
Use
.Sq Nm Fl r
to resume the editing of a draft;
by default, the last modified draft will be edited.
.Pp
.Nm mrep
creates the draft such that the message will be a reply to
.Ar msg
.Po
or the current message
.Pc .
Unless
.Fl noquote
is passed, it will contain the original mail quoted.
.Pp
.Nm mfwd
behaves like
.Nm mcom
but creates the draft with a subject and body appropriate
creates the draft with a subject and body appropriate
for forwarding the message.
By default, messages are forwarded verbatim as MIME
.Sq Li message/rfc822
@ -41,32 +54,68 @@ Use
.Fl r
to forward as
.Xr mshow 1
rendered plain text, using RFC934 message encapsulation.
rendered plain text, using RFC 934 message encapsulation.
.Pp
.Nm mbnc
behaves like
.Nm mcom
but creates the draft from the original
creates the draft from the original
.Ar msg
(including headers)
.Po
or the current message
.Pc
including headers,
and adds a Resent-To header, to which the message will
be bounced directly.
.Pp
.Nm mrep
behaves like
.Nm mcom
but creates the draft such that the mail will be a reply to
.Ar msg .
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
All commands can take optional
.Ar header
flags
.Pq which consist of two or more characters
to prefill header fields,
e.g. you can use
.Sq Nm mcom Fl to No merrilyn Fl cc No elea becci Fl subject No 'Party invite'
to create a draft with the To, Cc and Subject fields already set.
The flag
.Fl body Ar file
can be used to prepopulate the draft for
.Nm mcom
and
.Nm mrep
with the contents of
.Ar file .
.Pp
Note that these flags apply to
.Em all
arguments after them
.Po e.g.
.Sq mcom Fl attach No *.c
works
.Pc ,
so you
need to use
.Sq Fl -
when you want to use this feature together with
.Nm mrep ,
.Nm mfwd ,
or
.Nm mbnc .
.Pp
If the flag
.Fl send
is passed,
the message will be sent directly using the data on
the command line, and no editor or edit loop will be run.
.Sh MENU COMMANDS
.Bl -tag -width 2n
.It Ic s
Send the mail.
Send the message.
The MIME version will be used when one has been created.
.It Ic c
Cancel sending and quit after displaying
Cancel sending and quit, after displaying
the filename of the saved draft.
.It Ic m
Run
@ -74,14 +123,19 @@ Run
on the draft, and print the result of
.Ic mshow -t .
.It Ic e
Re-run the editor on the draft.
Re-edit the draft.
.It Ic d
Delete the draft and quit.
.It Ic p
Preview the draft, using
.Xr mshow 1 .
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev EDITOR
Editor used to compose mail.
.It Ev EDITOR , Ev VISUAL
Editor used to compose messages.
(Default:
.Xr vi 1 )
.It Ev MBLAZE
Directory containing mblaze configuration files.
(Default:
@ -94,7 +148,7 @@ Draft messages, kept in current directory.
This can be configured in
.Xr mblaze-profile 5 .
.It Pa ${MBLAZE:-$HOME/.mblaze}/headers
Default headers for each mail.
Default headers for each message.
.It Pa ${MBLAZE:-$HOME/.mblaze}/signature No or Pa ~/.signature
Default signature.
.El

@ -1,9 +1,9 @@
.Dd July 26, 2016
.Dd February 4, 2021
.Dt MDELIVER 1
.Os
.Sh NAME
.Nm mdeliver
.Nd deliver message or mailbox into Maildir
.Nd deliver messages or import mbox file
.Sh SYNOPSIS
.Nm
.Op Fl c
@ -22,28 +22,29 @@
.Ar mbox
.Sh DESCRIPTION
.Nm
adds a message given on standard input
as a new message in the Maildir
adds a message given on the standard input
as a new message in the maildir
.Ar dir .
.Pp
When
.Fl M
is used,
.Nm
will regard standard input as
will regard the standard input as
an MBOXRD mailbox, split it on
.Dq Li "From "
and deliver each message,
decoding it according to the MBOXRD convention.
In this case,
.Nm
will set the mtime according to the value of
.Sq Li Date\&:
and the Maildir flags according to the value of
and the maildir flags according to the value of
.Sq Li Status\&:
or
.Sq Li X-Status\&: .
.Pp
The messages are delivered in a reliable way and use default
The messages are delivered in a reliable way and use the default
.Xr umask 2 .
.Pp
Please note that no syntactical checks are performed
@ -60,7 +61,7 @@ not
.Pa new/
(the default).
.It Fl v
Print each message filename after delivery.
Print each new message filename after delivery.
.It Fl X Ar flg
Override the flags of the new message file to be
.Ar flg .
@ -69,6 +70,7 @@ Override the flags of the new message file to be
.Ex -std
.Sh SEE ALSO
.Xr mexport 1 ,
.Xr mrefile 1 ,
.Xr maildir 5 ,
.Xr mbox 5
.Pp

@ -1,34 +1,63 @@
.Dd July 22, 2016
.Dd July 25, 2023
.Dt MDIRS 1
.Os
.Sh NAME
.Nm mdirs
.Nd list Maildir folders, recursively
.Nd list maildir folders, recursively
.Sh SYNOPSIS
.Nm
.Op Fl 0a
.Ar dirs\ ...
.Sh DESCRIPTION
.Nm
scans the given directories for Maildir folders and prints them,
one per line.
It understands Maildir and Maildir++ formats.
recursively scans the given
.Ar dirs
for maildir
.Pq and maildir++
folders and prints them,
separated by newlines.
.Pp
There are currently no options.
If
.Ar dirs
is not present then use
.Sq Li Maildir\&:
from
.Pa "${MBLAZE:-$HOME/.mblaze}/profile"
.Pq if set .
.Pp
To
.Nm ,
a Maildir folder is a directory containing
a maildir folder is a directory containing
the directories
.Pa cur
and
.Pa new .
By the Maildir++ convention, nested Maildir folders
need to start with
By the maildir++ convention, nested maildir folder
names must begin with
.Sq Li \&. .
.Pq This can be disabled using Fl a .
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl 0
Print folders separated by a NUL character.
.It Fl a
Traverse into all subfolders, without considering the maildir++ name conventions.
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev MBLAZE
Directory containing mblaze configuration.
.Po
Default:
.Pa $HOME/.mblaze
.Pc
.El
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
.Xr find 1
.Xr find 1 ,
.Xr mblaze-profile 5
.Sh AUTHORS
.An Leah Neukirchen Aq Mt leah@vuxu.org
.Sh LICENSE

@ -10,18 +10,22 @@
.Ar msgs\ ...
.Sh DESCRIPTION
.Nm
exports the specified messages as an MBOXRD file,
to the standard output.
writes the specified
.Ar msgs
to the standard output
as an MBOXRD file.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
If no
.Ar msgs
are specified,
.Nm
reads filenames from the standard input,
or uses the mails in the current sequence if used interactively.
or uses the messages in the current sequence if used interactively.
.Pp
.Nm
uses the
@ -30,7 +34,7 @@ uses the
.Sq Li X\&-Envelope\&-To\&: )
and
.Sq Li Date\&:
headers from the message for the mbox
headers from each message for the mbox
.Sq Li "From "
line.
.Pp
@ -43,7 +47,7 @@ and
.Sq Li X\&-Status\&:
headers according to the
.Ar msgs
Maildir flags.
maildir flags.
.El
.Sh EXIT STATUS
.Ex -std

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm mflag
.Nd manipulate Maildir mail flags
.Nd manipulate maildir message flags
.Sh SYNOPSIS
.Nm
.Op Fl DFPRST
@ -16,12 +16,14 @@
.Op Ar msgs\ ...
.Sh DESCRIPTION
.Nm
changes the flags of the specified Maildir
changes the flags of the specified maildir
.Ar msgs
according to the options supplied.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
The options are as follows:
.Bl -tag -width Ds
@ -36,7 +38,8 @@ as flagged.
.It Fl P
Mark
.Ar msgs
as passed (resent/forwarded/bounced).
as passed
.Pq resent/forwarded/bounced .
.It Fl R
Mark
.Ar msgs
@ -65,7 +68,8 @@ as flagged.
.It Fl p
Unmark
.Ar msgs
as passed (resent/forwarded/bounced).
as passed
.Pq resent/forwarded/bounced .
.It Fl r
Unmark
.Ar msgs
@ -83,12 +87,11 @@ Unmark
.Ar msgs
with the characters in
.Ar str .
(Remember to use uppercase characters.)
.Pq Remember to use uppercase characters.
.It Fl v
Read Maildir mails from the standard input
(or use the whole current sequence, if used interactively)
Read messages from the standard input
.Pq or use the whole current sequence, if used interactively
and print the transformed list of filenames.
.Pp
This can be used to keep the sequence intact in the case of renames.
.El
.Pp

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm mflow
.Nd reflow format=flowed plain text mails
.Nd reflow format=flowed plain text messages
.Sh SYNOPSIS
.Nm
.Op Fl f
@ -21,9 +21,9 @@ for
.Sq text/plain
messages for
.Xr mshow 1 .
Mails not using
Messages not specified as
.Sq format=flowed
are output as is.
are output unchanged.
.Pp
Text is reflowed (where allowed) to
fit the width given in the environment variable
@ -38,7 +38,7 @@ specifies the maximum line length.
The options are as follows:
.Bl -tag -width Ds
.It Fl f
Force line wrapping of long lines.
Force wrapping of long lines.
.It Fl q
Prefix lines with
.Sq Li \&> .

@ -1,4 +1,4 @@
.Dd August 1, 2016
.Dd December 25, 2018
.Dt MGENMID 1
.Os
.Sh NAME
@ -8,27 +8,27 @@
.Nm
.Sh DESCRIPTION
.Nm
generates a fresh Message-ID and prints it.
The Message-ID consists of a timestamp,
generates and prints a unique Message-ID.
The Message-ID consists of an encrypted timestamp,
a random value,
and a fully qualified domain name.
.Pp
The fully qualified domain name is computed by:
The fully qualified domain name is arrived at by:
.Bl -enum
.It
Using
.Sq Li FQDN\&:
from
.Pa "${MBLAZE:-$HOME/.mblaze}/profile"
(if set).
.Pq if set .
.It
Resolving the current hostname.
.It
Using the host part of the address in
Using the domain component of the mail address in
.Sq Li Local\&-Mailbox\&:
from
.Pa "${MBLAZE:-$HOME/.mblaze}/profile"
(if set).
.Pq if set .
.El
.Pp
If these steps don't result in a fully qualified domain name,

@ -3,10 +3,10 @@
.Os
.Sh NAME
.Nm mhdr
.Nd show mail headers
.Nd print message headers
.Sh SYNOPSIS
.Nm
.Op Fl h Ar header Op Fl p Ar parameter
.Op Fl h Ar hdrs Op Fl p Ar parameter
.Op Fl d
.Op Fl H
.Op Fl M
@ -14,11 +14,13 @@
.Op Ar msgs\ ...
.Sh DESCRIPTION
.Nm
prints the mail headers of the specified
prints the headers of the specified
.Ar msgs .
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
If no
.Ar msgs
@ -26,25 +28,37 @@ are specified,
.Nm
will default to the current message.
.Pp
If no
.Ar hdrs
are specified,
.Nm
will print all headers.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl h Ar header
Only print the values of the headers in the colon-separated list
.Ar header .
.It Fl h Ar hdrs
Only print the values of headers in the colon-separated list
.Ar hdrs .
.It Fl p Ar parameter
Extract a particular RFC 2045
.Ar parameter
from the specified headers.
from the specified
.Ar hdrs .
.It Fl d
Decode the headers according to RFC 2047.
Decode the
.Ar hdrs
according to RFC 2047.
.It Fl H
Prefix output lines with the filename of the message,
followed by a tab.
.It Fl M
Search for all occurrences of the headers
(default: only the first).
Search for all occurrences of the
.Ar hdrs
.Pq default: only the first .
.It Fl A
Scan for RFC 5322 addresses in the headers and print them, one per line.
Scan for RFC 5322 addresses in the
.Ar hdrs
and print them, one per line.
.It Fl D
Assume header contains RFC 5322 date and print as Unix timestamp.
.El
@ -59,22 +73,22 @@ and >1 if an error occurs.
.Rs
.%A N. Freed
.%A N. Borenstein
.%D November 1996
.%R RFC 2045
.%D November 1996
.%T Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies
.Re
.Rs
.%A N. Freed
.%A N. Borenstein
.%B MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text
.%R RFC 2047
.%D November 1996
.%R RFC 2047
.%T MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text
.Re
.Rs
.%A P. Resnick (ed.)
.%B Internet Message Format
.%R RFC 5322
.%D October 2008
.%R RFC 5322
.%T Internet Message Format
.Re
.Sh AUTHORS
.An Leah Neukirchen Aq Mt leah@vuxu.org

@ -3,23 +3,26 @@
.Os
.Sh NAME
.Nm minc
.Nd incorporate new Maildir mail
.Nd incorporate new messages
.Sh SYNOPSIS
.Nm
.Op Fl q
.Ar dirs\ ...
.Sh DESCRIPTION
.Nm
incorporates new mail in the specified Maildir folders
incorporates new messages in the maildir folders
.Ar dirs
by moving them from
.Pa new
to
.Pa cur
.Pa cur ,
and adjusting the filenames.
If used non-interactively with no specified folders,
.Nm
reads directory names from the standard input.
.Pp
By default, the new Maildir filenames
are printed, one per line
By default, the new filenames are printed,
separated by newlines.
.Pp
The options are as follows:
.Bl -tag -width Ds

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm mless
.Nd interactive wrapper around mshow
.Nd conveniently read messages in less(1)
.Sh SYNOPSIS
.Nm
.Op Ar msg
@ -17,30 +17,40 @@ for the current sequence.
Display starts with the current message, or
.Ar msg
if passed.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
When no messages are passed and standard input is from a pipe,
When no messages are passed and the standard input is from a pipe,
.Ql mseq -S
will be called to set the current sequence and
is called to set the current sequence;
.Nm
is run on the resulting sequence.
is then run on the resulting sequence.
The first "file" of the less instance will be the output of
.Xr mscan 1 .
This makes
.Nm
convenient for use at the end of a pipe.
convenient for use at the end of a pipeline.
.Pp
.Nm
will start
.Xr less 1
displaying the current message if possible.
and display the current message, if possible.
Use
.Sq Ic ":n"
and
.Sq Ic ":p"
to read the next (resp. previous) message.
.Sh FILES
.Bl -tag -width Ds
.It Pa ${MBLAZE:-$HOME/.mblaze}/mlesskey , Pa $HOME/.mlesskey
Additional keybindings loaded for convenience.
.It Pa ${MBLAZE:-$HOME/.mblaze}/mless , Pa $HOME/.mless
Additional compiled keybindings loaded for convenience.
(Only needed for less earlier than version 590.)
.El
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO

@ -1,9 +1,9 @@
.Dd December 13, 2016
.Dd April 30, 2020
.Dt MLIST 1
.Os
.Sh NAME
.Nm mlist
.Nd list messages in Maildir folders
.Nd list and filter messages
.Sh SYNOPSIS
.Nm
.Op Fl DFPRST
@ -18,13 +18,12 @@
.Op Ar dirs\ ...
.Sh DESCRIPTION
.Nm
lists all messages in the specified Maildir folders
lists messages in the specified maildir folders
.Ar dirs ,
one per line.
If used non-interactively,
with no specified folders,
separated by newlines.
If used non-interactively with no specified folders,
.Nm
reads directory names from standard input.
reads directory names from the standard input.
.Pp
The options are as follows:
.Bl -tag -width Ds
@ -33,7 +32,8 @@ Only list messages marked as draft.
.It Fl F
Only list messages marked as flagged.
.It Fl P
Only list messages marked as passed (resent/forwarded/bounced).
Only list messages marked as passed
.Pq resent/forwarded/bounced .
.It Fl R
Only list messages marked as replied-to.
.It Fl S
@ -48,7 +48,8 @@ Don't list messages marked as draft.
.It Fl f
Don't list messages marked as flagged.
.It Fl p
Don't list messages marked as passed (resent/forwarded/bounced).
Don't list messages marked as passed
.Pq resent/forwarded/bounced .
.It Fl r
Don't list messages marked as replied-to.
.It Fl s
@ -58,7 +59,7 @@ Don't list messages marked as trashed.
.It Fl x Ar str
Don't list messages marked with the characters in
.Ar str .
(Remember to use uppercase characters.)
.Pq Remember to use uppercase characters.
.It Fl C
Only list messages in
.Pa cur .
@ -76,8 +77,10 @@ Don't print filenames.
Instead, print a one-line summary for each folder,
showing the number of unseen, flagged and total messages,
along with the folder name.
.Pp
If two or more folders are specified, a total will also be printed.
If any of the message selection flags are used,
the number of matching messages is printed as well.
If two or more folders are specified,
a total for all folders will also be printed at the end.
.El
.Pp
Multiple options are regarded as a conjunction.

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm mmime
.Nd encode MIME mails
.Nd create MIME messages
.Sh SYNOPSIS
.Nm
.Op Fl c | Fl r
@ -14,9 +14,9 @@
.Nm
generates a
.Sq Li multipart/mixed
mail from standard input,
message from the standard input,
extending, wrapping, and encoding the header as necessary,
and replacing lines in the message body, of the form
and replacing lines in the message body of the form
.Pp
.D1 Li # Ns Ar content Ns Li / Ns Ar type Ns Oo Ns Li # Ns Ar content-disposition Oc Pa path Ns Oo Li > Ns Ar filename Oc
.Pp
@ -36,7 +36,7 @@ The options are as follows:
.It Fl c
Check mode: don't output anything,
exit with status 1 if MIME-encoding the message is required,
or with status 0 else.
or else exit with status 0.
.It Fl r
Raw mode: don't expand MIME parts in the body, generate a
.Sq Li text/plain
@ -46,6 +46,14 @@ Override Content-Type for the toplevel part.
Defaults to
.Sq Li multipart/mixed .
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev MBLAZE_RELAXED_MIME
If set,
.Nm Fl c
will relax the body line length check and only consider bodies with lines
longer than 998 characters need to be MIME formatted.
.El
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
@ -80,9 +88,9 @@ Defaults to
.Re
.Rs
.%A P. Resnick (ed.)
.%B Internet Message Format
.%R RFC 5322
.%D October 2008
.%R RFC 5322
.%T Internet Message Format
.Re
.Sh AUTHORS
.An Leah Neukirchen Aq Mt leah@vuxu.org

@ -3,19 +3,19 @@
.Os
.Sh NAME
.Nm mmkdir
.Nd create new Maildir folders
.Nd create new maildir folders
.Sh SYNOPSIS
.Nm
.Ar dirs\ ...
.Ar dir\ ...
.Sh DESCRIPTION
.Nm
creates new Maildir folders.
creates new maildir folders.
.Pp
Parent directories are created as needed,
and attempting to create an existing
Maildir is not an error.
maildir is not an error.
.Pp
New Maildir folders are created with mode 0700, i.e.
New maildir folders are created with mode 0700, i.e.
readable and writable by the owner only.
.Sh EXIT STATUS
.Ex -std
@ -27,8 +27,8 @@ readable and writable by the owner only.
.An Leah Neukirchen Aq Mt leah@vuxu.org
.Sh CAVEATS
.Nm
does not support Maildir++,
you need to transform nested Maildir++ folder names yourself.
does not support maildir++,
you need to transform nested maildir++ folder names yourself.
.Sh LICENSE
.Nm
is in the public domain.

@ -1,4 +1,4 @@
.Dd July 22, 2016
.Dd July 3, 2020
.Dt MMSG 7
.Os
.Sh NAME
@ -6,7 +6,7 @@
.Nd mblaze message argument syntax
.Sh DESCRIPTION
This document outlines the message syntax used by many
of the tools in the
of the utilities in the
.Xr mblaze 7
message system.
.Pp
@ -14,10 +14,12 @@ In general, you can always specify a filename as a message,
if it contains a
.Sq Li \&/
character.
(Use
.Po
Use
.Sq Li \&./
to prefix messages in the current directory.)
You can also specify a Maildir folder, which will be expanded
to prefix messages in the current directory.
.Pc
You can also specify a maildir folder, which will be expanded
to all messages in the
.Pa cur/
directory.
@ -68,10 +70,16 @@ and may be repeated to refer to grandparents.
.Sq Ar msg Ns Cm \&_
refers to the subthread headed by
.Ar msg
(i.e. all messages below, with more indentation).
.Po
i.e. all messages below
.Ar msg ,
with more indentation
.Pc .
.Pp
The following special shortcuts may be used:
.Bl -tag -width 3n
.Bl -tag -width 4n
.It Sq Li \&-
refers to the message read directly from the standard input.
.It Sq Li \&.
refers to the current message.
Additionally, the syntax
@ -80,23 +88,41 @@ and
.Sq Li \&.- Ns Ar N
can be used to refer to messages relative to the current message.
.It Sq Li \&+
refers to the next message (like
.Sq Li \&.+1 ) .
.It Sq Li \&-
refers to the previous message (like
.Sq Li \&.-1 ) .
refers to the next message
.Po
like
.Sq Li \&.+1
.Pc
.It Sq Li \&.-
refers to the previous message
.Po
like
.Sq Li \&.-1
.Pc
.It Sq Li \&$
refers to the last message (like
.Sq Li -1 ) .
refers to the last message
.Po
like
.Sq Li -1
.Pc
.It Sq Li \&^
refers to the current parent message (like
.Sq Li \&.^ ) .
refers to the current parent message
.Po
like
.Sq Li \&.^
.Pc
.It Sq Li \&=
refers to the current thread (like
.Sq Li \&.= ) .
refers to the current thread
.Po
like
.Sq Li \&.=
.Pc
.It Sq Li \&_
refers to the current subthread (like
.Sq Li \&._ ) .
refers to the current subthread
.Po
like
.Sq Li \&._
.Pc
.El
.Sh SEE ALSO
.Xr mblaze 7

@ -1,14 +1,16 @@
.Dd July 27, 2016
.Dd July 30, 2020
.Dt MPICK 1
.Os
.Sh NAME
.Nm mpick
.Nd filter mail messages
.Nd advanced message filter
.Sh SYNOPSIS
.Nm
.Op Fl F Ar file
.Op Fl T
.Op Fl v
.Op Fl t Ar test
.Op Ar msglist\ ...
.Op Ar msgs\ ...
.Sh DESCRIPTION
.Nm
prints all matching messages.
@ -22,6 +24,11 @@ will read filenames from the standard input.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl F Ar file
Read expression from
.Ar file
and only show matching messages, see
.Sx TESTS .
.It Fl T
Include whole thread.
.It Fl t Ar test
@ -29,71 +36,31 @@ Only show messages matching the expression
.Ar test ,
see
.Sx TESTS .
.El
.Sh MSGLISTS
.Nm
message lists (msglist) are mostly compatible with
.Xr mailx 1 .
They are message specifications used as shortened
.Sx TESTS ,
and can include:
.Bl -tag -width Ds
.It Ar n
Message number
.Ar n .
.It Ar n Ns Cm \&: Ns Ar m , Ar n Ns Cm \&- Ns Ar m
An inclusive range of message numbers between
.Ar n
and
.Ar m .
.It Ar address
All messages from
.Ar address .
.It Cm \&/ Ns Ar string
All messages with
.Ar string
in the subject line (case ignored).
.It Cm \&: Ns Ar c
All messages of type
.Ar c ,
where
.Ar c
shall be one of:
.Bl -tag -width Ds
.It Cm D
Draft messages.
.It Cm P
Passed (resent, forwarded or bounced) messages.
.It Cm R
Replied messages.
.It Cm F
Flagged messages.
.It Cm d , Cm T
Deleted messages.
.It Cm n
New messages.
.It Cm o
Old messages.
.It Cm r , Cm S
Read messages.
.It Cm u
Unread messages.
.El
.It Fl v
Print how many messages were tested and picked to standard error.
.El
.Sh TESTS
.Nm
tests are given by the following EBNF:
.Bd -literal
<expr> ::= <expr> || <expr> -- disjunction
<expr> ::= <expr> ? <expr> : <expr> -- ternary operator
| <expr> || <expr> -- disjunction
| <expr> && <expr> -- conjunction
| ! <expr> -- negation
| ( <expr> )
| <expr> "|" <str> -- pipe current mail to command
| <expr> ">>" <str> -- append current mail to file
| <expr> ">" <str> -- write current mail to file
| <flagprop>
| <timeprop> <numop> <dur>
| <numprop> <numop> <num>
| <hdrprop> <decodeop> <strop> <str>
| <strprop> <strop> <str>
| prune -- do not match further messages in thread
| print -- always true value
| skip -- always false value
| <let>
| <ident>
<flagprop> ::= child | draft | flagged | info | new | parent | passed
| replied | seen | selected | trashed
@ -124,8 +91,13 @@ tests are given by the following EBNF:
| T )? -- *1024*1024*1024*1024
| cur -- index of cur message
<strprop> ::= from | subject | to
| <str> -- header name
<hdrprop> ::= from | to | subject | <str>
<decodeop> ::= . addr -- match address parts
| . disp -- match address display parts
| -- empty matches raw headers
<strprop> ::= path
<strop> ::= == | = | != -- string (in)equality
| === | !=== -- case insensitive string (in)equality
@ -136,6 +108,15 @@ tests are given by the following EBNF:
<str> ::= " ([^"] | "")+ " -- use "" for a single " inside "
| $[A-Za-z0-9_]+ -- environment variable
-- let expressions evaluate the expression following the `in` keyword,
-- the bindings are lazily evaluated.
<let> ::= { let <ident> = <scope> } in <scope>
-- Inside the scope previously defined idents are replaced with expressions
<scope> ::= <expr>
<ident> ::= [A-Za-z_][A-Za-z0-9_]+
.Ed
.Sh EXIT STATUS
.Ex -std

@ -0,0 +1,55 @@
.Dd April 19, 2018
.Dt MREFILE 1
.Os
.Sh NAME
.Nm mrefile
.Nd move or copy messages between maildir folders
.Sh SYNOPSIS
.Nm
.Op Fl k
.Op Fl v
.Op Ar msgs\ ...
.Ar dir
.Sh DESCRIPTION
.Nm
moves the
.Ar msgs
given on the command line
.Pq or the standard input
into the maildir
.Ar dir ,
assigning new filenames but keeping any message flags.
Messages are moved directly into the
.Pa cur/
directory.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl k
keep the messages in their respective folders,
i.e. copy into
.Ar dir .
.It Fl v
Print each resulting message filename.
.El
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
.Xr mdeliver 1
.Sh AUTHORS
.An Leah Neukirchen Aq Mt leah@vuxu.org
.Sh LICENSE
.Nm
is in the public domain.
.Pp
To the extent possible under law,
the creator of this work
has waived all copyright and related or
neighboring rights to this work.
.Pp
.Lk http://creativecommons.org/publicdomain/zero/1.0/

@ -1,21 +1,24 @@
.Dd June 28, 2017
.Dd January 2, 2020
.Dt MSCAN 1
.Os
.Sh NAME
.Nm mscan
.Nd print a one line per message mail listing
.Nd generate one-line message summaries
.Sh SYNOPSIS
.Nm
.Op Fl I
.Op Fl n
.Op Fl v
.Op Fl f Ar format
.Op Fl I
.Ar msgs\ ...
.Sh DESCRIPTION
.Nm
prints a one line summary for each message.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
If no
.Ar msgs
@ -34,14 +37,18 @@ for details on how to control this behaviour.
The default
.Ar format
is
.Sq Li "%c%u%r %-3n %10d %17f %t %2i%s" ;
.Pp
.Dl %c%u%r %-3n %10d %17f %t %2i%s
.Pp
that is, for each message,
.Nm
prints relevant flags,
the sequence number (if applicable),
the sequence number
.Pq if applicable ,
the date,
the originator,
and the subject of the message (possibly indented).
and the subject of the message
.Pq possibly indented .
A default
.Nm
format may be specified in the user's
@ -49,13 +56,15 @@ format may be specified in the user's
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl n
Only print message numbers
(or filenames, if the message is not in the current sequence).
.It Fl I
Force ISO date output,
even for
.Sq Cm "%d" .
.It Fl n
Only print message numbers
.Pq or filenames, if the message is not in the current sequence .
.It Fl v
Print how many messages were scanned to standard error.
.It Fl f Ar format
Format according to the string
.Ar format ,
@ -63,8 +72,10 @@ inspired by
.Xr printf 3 .
.Pp
The following formatting codes may be used
.Ar ( wd
overrides the default width):
.Po
.Ar wd
overrides the default width
.Pc :
.Bl -tag -width Ds
.It Cm \en
Newline.
@ -74,7 +85,8 @@ Tab.
A plain
.Sq Li \&% .
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "b"
Human-readable size of the message (in kilobytes).
Human-readable size of the message
.Pq in kilobytes .
.It Cm "%c"
A
.Sq Li >
@ -82,8 +94,14 @@ on the current message,
otherwise a blank.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "d"
Adaptive date of the message.
When
.Ar wd
is greater or equal to 19,
the timestamp is shown in dottime format
.Pq Lk https://dotti.me/ .
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "D"
ISO date of the message (year, month, day).
ISO date of the message
.Pq year, month, day .
When
.Ar wd
is greater or equal to 16,
@ -95,21 +113,23 @@ seconds will also be shown.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "f"
The
.Sq Li From\&:
(or
.Po
or
.Sq Li To\&: ,
if the message is from us).
if the message is from us
.Pc .
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "F"
The Maildir folder the message resides in.
The maildir folder the message resides in.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "i"
.Ar wd
(default: 1)
.Pq default: 1
spaces per indentation depth in the thread tree.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "I"
The
.Sq Li Message\&-ID\&:
of the message.
.It Cm "%M"
The raw Maildir flags of the message.
The raw maildir flags of the message.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "n"
The number of the message in the current sequence.
.It Cm "%r"
@ -121,9 +141,11 @@ on a forwarded message, or a blank.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "R"
The filename of the message.
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "s"
The subject of the message (defaults to remaining width).
The subject of the message
.Pq defaults to remaining width .
.It Cm "%" Ns Oo Ar wd Oc Ns Cm "S"
The subject of the message (defaults to remaining width),
The subject of the message
.Pq defaults to remaining width ,
with leading
.Sq Li Re\&: ,
.Sq Li Fwd\&:
@ -169,13 +191,13 @@ A replied-to message
.Bl -tag -width 2n -compact
.It Li \&>
You are in
.Sq Li To\&: .
.Sq Li To\&:
.It Li \&+
You are in
.Sq Li Cc\:& .
.Sq Li Cc\&:
.It Li \&:
You are in
.Sq Li Resent\&-To\&: .
.Sq Li Resent\&-To\&:
.El
.Sh ENVIRONMENT
.Bl -tag -width MBLAZE_PAGER

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm msed
.Nd stream editor for transforming mail
.Nd manipulate message headers
.Sh SYNOPSIS
.Nm
.Ar script
@ -12,11 +12,13 @@
.Nm
prints the messages
.Ar msgs
with mail headers transformed by the commands in
with message headers transformed by the commands in
.Ar script .
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
If no
.Ar msgs
are passed,
@ -26,14 +28,14 @@ will default to the current message.
.Nm
scripts are akin to a subset of
.Xr sed 1
scripts, but optimized for modifying mail.
scripts, but optimized for modifying messages.
Note that
.Nm
unfolds and normalizes mail headers,
unfolds and normalizes message headers,
so they may need to be passed through
.Xr mmime 7
to ensure RFC 5322 conformance.
The mail body is copied as-is.
The message body is not affected.
.Pp
.Nm
supports the following commands.
@ -47,7 +49,7 @@ Multiple commands can be separated by
.It Cm \&/ Ns Ar header Ns Cm \&/ Ns Ic a Ns Cm \&/ Ns Ar value Ns Cm \&/
If the header
.Sq Ar header Ns Cm \&:
is not set in the message, add it with given
is not set in the message, add it with the given
.Ar value .
.It Cm \&/ Ns Ar headers Ns Cm \&/ Ns Ic c Ns Cm \&/ Ns Ar value Ns Cm \&/
Change colon-separated headers matching the regular expression
@ -67,7 +69,7 @@ For example,
.Sq Li "/x-.*/d"
will delete all headers starting with
.Sq Li "X-"
(always case insensitive),
.Pq always case insensitive ,
and
.Sq Li "/from:to:cc/d"
will delete the headers
@ -81,7 +83,7 @@ Substitute matches of the POSIX Basic Regular Expression
in headers matching the POSIX Basic Regular Expression
.Ar headers ,
with implicit anchoring to the header name
(or all headers, if omitted),
.Pq or all headers, if omitted ,
with the string
.Ar replacement ,
expanding
@ -121,8 +123,10 @@ contains the letter
.Ex -std
.Sh SEE ALSO
.Xr sed 1 ,
.Xr mhdr 1 ,
.Xr mmsg 7 ,
.Xr regex 7
.Xr regex 7 /
.Xr re_format 7
.Sh AUTHORS
.An Leah Neukirchen Aq Mt leah@vuxu.org
.Sh LICENSE

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm mseq
.Nd manipulate mail sequence
.Nd manipulate message sequences
.Sh SYNOPSIS
.Nm
.Op Fl fr
@ -23,10 +23,12 @@
.Fl C Ar msg
.Sh DESCRIPTION
.Nm
prints, fixes and sets messages in the mail sequence.
prints, fixes and sets the message sequence.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
If no
.Ar msgs
@ -41,8 +43,11 @@ With
or
.Fl A ,
.Nm
will replace (resp. append) the default sequence.
If standard output is not a terminal, the new sequence is also printed.
will replace
.Pq resp. append
the default sequence.
If the standard output is not a terminal,
the new sequence is also printed.
.Pp
The options are as follows:
.Bl -tag -width Ds
@ -52,15 +57,16 @@ Behave as if
was the current message.
.It Fl f
Fix non-existing filenames by searching for a message with the same
Maildir id (but different flags).
maildir id
.Pq but different flags .
.It Fl r
Remove leading indentation from the filenames.
.It Fl S
Set the mail sequence to the filenames passed on standard input.
Set the message sequence to the filenames passed on standard input.
.It Fl A
Like
.Fl S ,
but append to the mail sequence instead of replacing it.
but append to the message sequence instead of replacing it.
.It Fl C Ar msg
Set the current message to
.Ar msg
@ -70,21 +76,29 @@ and exit.
.Bl -tag -width Ds
.It Ev MBLAZE
Directory containing mblaze configuration.
(Default:
.Pa $HOME/.mblaze )
.Po
Default:
.Pa $HOME/.mblaze
.Pc
.It Ev MAILCUR
Symbolic link referring to the current message.
(Default:
.Pa ${MBLAZE:-$HOME/.mblaze}/cur )
.Po
Default:
.Pa ${MBLAZE:-$HOME/.mblaze}/cur
.Pc
.It Ev MAILDOT
When set to a filename, overrides the current message.
(Prefer using
.Po
Prefer using
.Fl c
instead.)
instead.
.Pc
.It Ev MAILSEQ
File were the sequence is stored.
(Default:
.Pa ${MBLAZE:-$HOME/.mblaze}/seq )
.Po
Default:
.Pa ${MBLAZE:-$HOME/.mblaze}/seq
.Pc
.El
.Sh EXIT STATUS
.Ex -std

@ -1,14 +1,14 @@
.Dd July 22, 2016
.Dd January 17, 2021
.Dt MSHOW 1
.Os
.Sh NAME
.Nm mshow
.Nd render mail and extract MIME parts
.Nd render messages and extract MIME parts
.Sh SYNOPSIS
.Nm
.Op Fl h Ar headers
.Op Fl A Ar mimetypes
.Op Fl nqrFHLN
.Op Fl nqrBFHLN
.Op Ar msgs\ ...
.Nm
.Fl x Ar msg
@ -23,13 +23,18 @@
.Fl R
.Ar msg
.Sh DESCRIPTION
By default,
.Nm
renders the specified messages to the standard output.
renders the specified
.Ar msgs
to the standard output, by default.
.Po
See
.Xr mmsg 7
for the message argument syntax.
If used interactively, and no messages are specified,
.Pc
If used interactively and no
.Ar msgs
are specified,
.Nm
displays the current message using colorization and a pager.
.Pp
@ -42,16 +47,25 @@ instead of the default headers
.Sq Li from\&:subject\&:to\&:cc\&:date\&:reply\&-to\&: .
.It Fl A Ar mimetypes
Define
.Sq Li "mixed/alternative"
.Sq Li "multipart/alternative"
preference.
.Ar mimetypes
is a colon-separated list of
MIME types which will be preferred,
in the order given,
when rendering
.Sq Li "mixed/alternative"
.Sq Li "multipart/alternative"
parts.
If no MIME type matches, the first MIME part will be rendered.
If no MIME type matches, the last MIME part will be rendered.
.Pp
When
.Ar mimetypes
is
.Sq Li all ,
.Nm
will render all parts of a
.Sq Li "multipart/alternative"
part.
.Pp
Defaults to
.Sq Li "text/plain:text/html" .
@ -62,6 +76,11 @@ Don't render the body, stop after header output.
.It Fl r
Don't render the body, print raw body.
This may be dangerous to use on a tty.
.It Fl B
Decode encoded-words also in MIME parameters in direct violation
of RFC 2047.
This is useful if the attachment names look like
.Sq Li =?UTF-8?Q?stuff?= .
.It Fl F
Don't apply filters to MIME parts.
.It Fl H
@ -81,6 +100,9 @@ into files.
can be specified by number, filename or
.Xr fnmatch 3
pattern.
If no
.Ar parts
are specified, extracts all attachments with a filename.
.It Fl O Ar msg
Like
.Fl x
@ -112,14 +134,21 @@ and re-encodes them into UTF-8 if necessary.
.Pp
Other filters can be specified in the file
.Pa ${MBLAZE:-$HOME/.mblaze}/filter
in the format
.Po
or via
.Ev MAILFILTER
.Pc ,
in the format:
.Pp
.D1 Ar type/subtype Ns Li \&: Ar command
or
.D1 Ar type Ns Li \&: Ar command
.Pp
.Nm
will then spawn a pipe to
.Ar command ,
write the MIME part
to standard input
and display the output.
The environment variable
.Ev PIPE_CHARSET
@ -137,7 +166,7 @@ The output is printed raw, without escaping.
.It 63
Behave as if the filter never ran.
.It 64
The output is a RFC5322 message that should be rendered again.
The output is an RFC 5322 message that should be rendered again.
.It 65 to 80
Render the
.Va n Ns \&- Ns 64th
@ -146,6 +175,14 @@ part of this text/multipart part.
All other exit statuses are regarded as errors.
.Sh ENVIRONMENT
.Bl -tag -width MBLAZE_NOCOLOR
.It Ev MAILFILTER
Path to an alternative
.Pa filter
file.
.It Ev MBLAZE_NOCOLOR
If non-empty,
.Nm
will not spawn a colorization filter.
.It Ev MBLAZE_PAGER
Any non-empty value of the environment variable
.Ev MBLAZE_PAGER
@ -154,10 +191,6 @@ is used instead of the standard pagination program, specified in
When empty or set to
.Sq Ic cat ,
no pager is spawned.
.It Ev MBLAZE_NOCOLOR
If non-empty,
.Nm
will not spawn a colorization filter.
.El
.Sh EXIT STATUS
.Ex -std

@ -3,7 +3,7 @@
.Os
.Sh NAME
.Nm msort
.Nd sort mail messages
.Nd sort messages
.Sh SYNOPSIS
.Nm
.Op Fl r
@ -11,11 +11,13 @@
.Op Ar msgs\ ...
.Sh DESCRIPTION
.Nm
sorts mail messages according to various orders,
and prints, one per line, the sorted message names.
sorts messages according to various orders,
and prints, separated by newlines, the resulting message names.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
If no messages are specified,
.Nm

@ -1,21 +1,23 @@
.Dd July 22, 2016
.Dd May 4, 2020
.Dt MTHREAD 1
.Os
.Sh NAME
.Nm mthread
.Nd thread mail messages
.Nd arrange messages into discussions
.Sh SYNOPSIS
.Nm
.Op Fl v
.Op Fl vpr
.Op Fl S Ar msg
.Op Ar msgs\ ...
.Sh DESCRIPTION
.Nm
groups messages together in parent/child relationships, based on
which messages are replies to which others.
.Po
See
.Xr mmsg 7
for the message argument syntax.
.Pc
.Pp
If no messages are specified,
.Nm
@ -23,7 +25,7 @@ will read filenames from the standard input,
or use the default sequence if used interactively.
.Pp
.Nm
prints the threaded messages one per line,
prints the threaded messages separated by newlines and
indented according to their depth in the message tree.
Unresolved Message-IDs are printed as-is.
.Pp
@ -31,6 +33,12 @@ The options are as follows:
.Bl -tag -width Ds
.It Fl v
Do not prune unresolved Message-IDs at the top-level.
.It Fl p
With
.Fl S ,
only add parents, not unrelated subthreads.
.It Fl r
Sort the top-level threads in reverse order (newest threads first).
.It Fl S Ar msg
Treat
.Ar msg

@ -7,15 +7,19 @@ function so(s) { return sprintf("\033[1m%s\033[0m", s) }
BEGIN { hdr = 1; if ("NO_COLOR" in ENVIRON || match(ENVIRON["TERM"], "^(dumb|network|9term)")) no_color = 1 }
no_color { print; next }
/\r$/ { sub(/\r$/, "") }
/^\014$/ { nextmail = 1; next }
/^$/ { hdr = 0 }
/^\014$/ { nextmail = 1; print(fg(co("FF",232), $0)); next }
/^$/ { hdr = 0; diff = 0 }
/^-- $/ { ftr = 1 }
/^diff -/ { diff = 1 }
/^--- .* ---/ { print fg(co("SEP",242), $0); ftr = 0; sig = 0; next }
/^-----BEGIN .* SIGNATURE-----/ { sig = 1 }
nextmail && /^From:/ { hdr = 1 }
hdr && /^From:/ { print so(fg(co("FROM",119), $0)); next }
hdr { print fg(co("HEADER",120), $0); next }
ftr { print fg(co("FOOTER",244), $0); next }
diff && /^-/ { print fg(co("DIFF_D",160), $0); next }
diff && /^\+/ { print fg(co("DIFF_I",40), $0); next }
diff && /^@/ { print fg(co("DIFF_R",226), $0); next }
/^-----BEGIN .* MESSAGE-----/ ||
/^-----END .* SIGNATURE-----/ { print fg(co("SIG",244), $0); sig = 0; next }
sig { print fg(co("SIG",244), $0); next }

410
mcom

@ -1,6 +1,9 @@
#!/bin/sh
# mcom [TO] - compose mail
NL='
'
commajoin() {
awk 'NR==1 {l=$0; next}
{l=l", "$0}
@ -8,10 +11,20 @@ commajoin() {
}
notmine() {
mine="$(maddr -a -h local-mailbox:alternate-mailboxes: $MBLAZE/profile)"
mine="$(maddr -a -h local-mailbox:alternate-mailboxes: "$MBLAZE/profile")"
grep -Fv -e "$mine"
}
replyfrom() {
addrs="$(maddr -a -h reply-from: "$MBLAZE/profile")"
[ -z "$addrs" ] && addrs="$(maddr -a -h alternate-mailboxes: "$MBLAZE/profile")"
grep -F -e "$addrs"
}
ouniq() {
awk '!seen[$0]++'
}
reffmt() {
sed 's/^[^<]*//g;s/[^>]*$//g;s/>[^<]*</>\
</g' | uniq | sed 's/^/ /'
@ -33,6 +46,27 @@ stampdate() {
fi
}
checksensible() {
awk '
/^$/ {
seenheader=1
exit
}
!(/^[^ \t][^ \t]*[ \t]*:/ || /^[ \t]/) {
bad=1
print "invalid header line: "$0 >"/dev/stderr"
}
END {
if (!seenheader) {
print "warning: message does not contain an empty line between headers and body." >"/dev/stderr"
exit 1
}
if (bad)
exit 1
}
' "$1"
}
stripempty() {
tmp=$(mktemp -t mcom.XXXXXX)
msed 's/^[ \t]*$//d' "$1" >"$tmp"
@ -41,24 +75,27 @@ stripempty() {
needs_multipart() {
mhdr -h attach "$1" >/dev/null ||
grep -q '^#[^ ]*/[^ ]* ' "$1"
grep -qE '^#[a-zA-Z]+/[a-zA-Z0-9+.;=#-]+ ' "$1"
}
do_mime() {
if needs_multipart "$draft"; then
(
IFS='
'
msed '/attach/d' $draft
for f in $(mhdr -M -h attach $draft); do
IFS=$NL
msed '/attach/d' "$draft"
for f in $(mhdr -M -h attach "$draft"); do
printf '#%s %s\n' \
"$(file -Lbi $f | sed 's/ //g')" \
"$(file -Lb --mime "$f" | sed 's/ //g')" \
"$f"
done
) | mmime >$draftmime
) | mmime >"$draftmime"
else
mmime -r <"$draft" >"$draftmime"
fi
if [ $? -ne 0 ]; then
printf 'mcom: fix above errors before continuing\n' 1>&2
rm -f "$draftmime"
fi
}
MBLAZE=${MBLAZE:-$HOME/.mblaze}
@ -66,24 +103,142 @@ MBLAZE=${MBLAZE:-$HOME/.mblaze}
sendmail=$(mhdr -h sendmail "$MBLAZE/profile")
sendmail_args=$(mhdr -h sendmail-args "$MBLAZE/profile")
sendmail="${sendmail:-sendmail} ${sendmail_args:--t}"
default_from=$(mhdr -h local-mailbox "$MBLAZE/profile")
defaultc=e
hdrs=
resume=
noquote=
case "$0" in
*mcom*)
if [ "$1" = -r ]; then
*mcom*)
hdr=to
while [ $# -gt 0 ]; do
case "$1" in
--)
# -- is like -to, really
shift
hdr=to
;;
-r)
shift
resume=1
if [ "$#" -gt 0 ]; then
echo "used draft $1"
draft="$1"
case "$1" in
/*|./*) draft="$1";;
*) draft="./$1";;
esac
if ! [ -f "$draft" ]; then
printf 'mcom: no such draft %s\n' \
"$draft" 1>&2
exit 1
fi
echo "using draft $draft"
shift
fi
fi
;;
;;
-send)
defaultc=justsend
shift;;
-??*)
hdr=${1#-}
shift;;
[!-]*)
hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")"
shift;;
*)
printf 'mcom: invalid argument %s\n' "$1" 1>&2
exit 1;;
esac
done
;;
*mfwd*)
hdr=
raw=
while [ $# -gt 0 ]; do
case "$1" in
--)
shift
break;;
-r)
shift
raw=1;;
-send)
defaultc=justsend
shift;;
-??*)
hdr=${1#-}
shift;;
[!-]*)
[ -z "$hdr" ] && break
hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")"
shift;;
*)
printf 'mfwd: invalid argument %s\n' "$1" 1>&2
exit 1;;
esac
done
[ "$#" -eq 0 ] && set -- .
;;
*mbnc*)
hdr=
while [ $# -gt 0 ]; do
case "$1" in
--)
shift
break;;
-send)
defaultc=justsend
shift;;
-??*)
hdr=${1#-}
shift;;
[!-]*)
[ -z "$hdr" ] && break
hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")"
shift;;
*)
printf 'mbnc: invalid argument %s\n' "$1" 1>&2
exit 1;;
esac
done
[ "$#" -eq 0 ] && set -- .
;;
*mrep*)
hdr=
while [ $# -gt 0 ]; do
case "$1" in
--)
shift
break;;
-send)
defaultc=justsend
shift;;
-noquote)
noquote=1
shift;;
-??*)
hdr=${1#-}
shift;;
[!-]*)
[ -z "$hdr" ] && break
hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")"
shift;;
*)
printf 'mrep: invalid argument %s\n' "$1" 1>&2
exit 1;;
esac
done
[ "$#" -eq 0 ] && set -- .
;;
esac
outbox=$(mhdr -h outbox "$MBLAZE/profile")
if [ -z "$outbox" ]; then
hdrs="$(printf '%s\n' "${hdrs#$NL}" | mhdr -)"
outbox=$(mhdr -h outbox "$MBLAZE/profile" | sed "s:^~/:$HOME/:")
draftbox=$(mhdr -h drafts "$MBLAZE/profile" | sed "s:^~/:$HOME/:")
draftbox="${draftbox:-$outbox}"
if [ -z "$draftbox" ]; then
if [ -z "$resume" ]; then
i=0
while [ -f "snd.$i" ]; do
@ -93,91 +248,140 @@ if [ -z "$outbox" ]; then
elif [ -z "$draft" ]; then
draft=$(ls -1t ./snd.*[0-9] | sed 1q)
fi
draftmime="./snd.$i.mime"
draftmime="$draft.mime"
else
if [ -z "$resume" ]; then
draft="$(true | mdeliver -v -c -XD "$outbox")"
draft="$(true | mdeliver -v -c -XD "$draftbox")"
if [ -z "$draft" ]; then
printf '%s\n' "$0: failed to create draft in outbox $outbox." 1>&2
printf '%s\n' "$0: failed to create draft in outbox $draftbox." 1>&2
exit 1
fi
elif [ -z "$draft" ]; then
draft=$(mlist -D "$outbox" | msort -r -M | sed 1q)
draft=$(mlist -D "$draftbox" | msort -r -M | sed 1q)
fi
draftmime="$(printf '%s\n' "$draft" | sed 's,\(.*\)/cur/,\1/tmp/mime-,')"
fi
if [ -n "$resume" ] && [ -z "$draft" ]; then
echo "mcom: no draft found"
exit 1
fi
[ -z "$resume" ] &&
{
case "$0" in
*mcom*)
printf 'To: '
printf '%s\n' "$@" | commajoin
printf '%s: \n' Cc Bcc Subject
from=$(mhdr -h local-mailbox "$MBLAZE/profile")
[ "$from" ] && printf 'From: %s\n' "$from"
cat "$MBLAZE/headers" 2>/dev/null
{
printf '%s' "$hdrs" | mhdr -M -h to - |
commajoin | sed 's/^/To: /'
printf '%s' "$hdrs" | mhdr -M -h cc - |
commajoin | sed 's/^/Cc: /'
printf '%s' "$hdrs" | mhdr -M -h bcc - |
commajoin | sed 's/^/Bcc: /'
printf '%s\n' "$hdrs" | awk '{ print }' |
msed "/to/d; /cc/d; /bcc/d; /body/d" -
} | msed "/cc/a//; /bcc/a//; /subject/a//; /from/a/$default_from/" - | sed '/^$/d'
msgid
museragent
printf '\n\n'
cat "$MBLAZE/headers" 2>/dev/null
printf '\n'
(
IFS=$NL
cat -- /dev/null $(printf '%s' "$hdrs" | mhdr -M -h body -)
)
printf '\n'
;;
*mfwd*)
raw=
[ "$1" = -r ] && raw=1 && shift
[ "$#" -eq 0 ] && set -- .
printf '%s: \n' To Cc Bcc
COLUMNS=10000 mscan -f 'Subject: [%f] %s' "$@" | sed 1q
from=$(mhdr -h local-mailbox "$MBLAZE/profile")
[ "$from" ] && printf 'From: %s\n' "$from"
cat "$MBLAZE/headers" 2>/dev/null
{
printf '%s' "$hdrs" | mhdr -M -h to - |
commajoin | sed 's/^/To: /'
printf '%s' "$hdrs" | mhdr -M -h cc - |
commajoin | sed 's/^/Cc: /'
printf '%s' "$hdrs" | mhdr -M -h bcc - |
commajoin | sed 's/^/Bcc: /'
COLUMNS=10000 mscan -f 'Subject: Fwd: [%f] %s' "$@" 2>/dev/null | sed 1q
printf '%s\n' "$hdrs" | awk '{ print }' |
msed "/to/d; /cc/d; /bcc/d" -
} | msed "/cc/a//; /bcc/a//; /from/a/$default_from/" - | sed '/^$/d'
msgid
museragent
cat "$MBLAZE/headers" 2>/dev/null
printf '\n\n'
if [ -z "$raw" ]; then
mseq -r "$@" | sed 's:^:#message/rfc822#inline :; s:$:>:'
else (
SEP=-----
IFS='
'
IFS=$NL
for f in $(mseq -r "$@"); do
printf '%s Forwarded message from %s %s\n\n' \
$SEP "$(mhdr -d -h from "$f")" $SEP
$SEP "$(mhdr -d -A -h from "$f")" $SEP
DISPLAY= mshow -n -N "$f" </dev/null |
sed 's/^-/- &/' # RFC934
sed 's/^-/- &/' # RFC 934
printf '\n%s %s %s\n\n' \
$SEP 'End forwarded message' $SEP
done
) fi
;;
*mbnc*)
printf '%s: \n' Resent-To
from=$(mhdr -h local-mailbox "$MBLAZE/profile")
[ "$from" ] && printf 'Resent-From: %s\n' "$from"
old_ifs="$IFS"
IFS=$NL
set -- $(mseq -r -- "$@")
IFS="$old_ifs"
if [ "$#" -ne 1 ]; then
printf 'mbnc: needs exactly one mail to bounce\n' 1>&2
exit 1
fi
{
printf '%s' "$hdrs" | mhdr -M -h resent-to - |
commajoin | sed 's/^/Resent-To: /'
printf '%s' "$hdrs" | mhdr -M -h resent-cc - |
commajoin | sed 's/^/Resent-Cc: /'
printf '%s\n' "$hdrs" | awk '{ print }' |
msed "/resent-to/d; /resent-cc/d" -
} |
msed "/resent-to/a//; /resent-from/a/$default_from/" - | sed '/^$/d'
msgid | sed 's/^/Resent-/'
printf 'Resent-Date: %s\n' "$(mdate)"
(
IFS='
'
cat $(mseq "${@:-.}")
IFS=$NL
cat $(mseq -r -- "$@")
)
;;
*mrep*)
[ "$#" -eq 0 ] && set -- .
old_ifs="$IFS"
IFS=$NL
set -- $(mseq -r -- "$@")
IFS="$old_ifs"
if [ "$#" -ne 1 ]; then
printf 'mrep: needs exactly one mail to reply to\n' 1>&2
exit 1
fi
ng=$(mhdr -h newsgroups "$1")
if [ "$ng" ]; then
printf 'Newsgroups: %s\n' "$ng"
else
to=$(mhdr -h reply-to "$1")
[ -z "$to" ] && to=$(mhdr -h from "$1")
to=$(mhdr -d -h reply-to "$1")
[ -z "$to" ] && to=$(mhdr -d -A -h from "$1")
printf 'To: %s\n' "$to"
printf 'Cc: %s\n' \
"$(mhdr -d -A -h to:cc: "$1" |notmine |commajoin)"
"$(mhdr -d -A -h to:cc: "$1" |
notmine |grep -Fv -e "$to" |
ouniq |commajoin)"
printf 'Bcc: \n'
fi
printf '%s\n' "$hdrs" | awk '{ print }' |
msed "/body/d" -
fi | sed '/^$/d'
printf 'Subject: Re: %s\n' "$(COLUMNS=10000 mscan -f '%S' "$1")"
from=$(mhdr -h local-mailbox "$MBLAZE/profile")
[ "$from" ] && printf 'From: %s\n' "$from"
cat "$MBLAZE/headers" 2>/dev/null
if ! printf '%s\n' "$hdrs" | awk '{ print }' |
mhdr -h from: - >/dev/null; then
addr=$(maddr -a -h delivered-to:to:cc:bcc: "$1" | replyfrom | head -n1)
[ -n "$addr" ] && from=$(maddr -h reply-from "$MBLAZE/profile" | grep -Fi "<$addr>" | head -n1)
[ -n "$addr" ] && [ -z "$from" ] && from=$(maddr -h alternate-mailboxes "$MBLAZE/profile" | grep -Fi "<$addr>" | head -n1)
[ -z "$from" ] && from=$(mhdr -h local-mailbox "$MBLAZE/profile")
[ -n "$from" ] && printf 'From: %s\n' "$from"
fi
mid=$(mhdr -h message-id "$1")
if [ "$mid" ]; then
printf 'References:'
@ -189,9 +393,17 @@ fi
fi
msgid
museragent
cat "$MBLAZE/headers" 2>/dev/null
printf '\n'
mquote "$1"
if [ -z "$noquote" ]; then
mquote "$1"
printf '\n'
fi
(
IFS=$NL
cat -- /dev/null $(printf '%s' "$hdrs" | mhdr -M -h body -)
)
printf '\n'
;;
esac
@ -210,10 +422,10 @@ fi
cat "$SIGNATURE"
fi
esac
} >$draft
} >"$draft"
automime=
c=e
c=$defaultc
while :; do
case "$c" in
s|send)
@ -230,17 +442,16 @@ while :; do
;;
esac
if [ -e $draftmime ]; then
if [ $draft -ot $draftmime ] || [ "$automime" -eq 1 ]; then
stampdate $draftmime
if $sendmail <$draftmime; then
if [ -e "$draftmime" ]; then
if [ "$draft" -ot "$draftmime" ] || [ "$automime" = 1 ]; then
stampdate "$draftmime"
if $sendmail <"$draftmime"; then
if [ "$outbox" ]; then
mv $draftmime $draft
mflag -d $draft
mv "$draftmime" "$draft"
mrefile "$(mflag -d -S "$draft")" "$outbox"
else
rm $draft $draftmime
rm "$draft" "$draftmime"
fi
exit 0
else
printf '%s\n' "mcom: $sendmail failed, kept draft $draft"
exit 2
@ -248,17 +459,17 @@ while :; do
else
printf 'mcom: re-run mmime first.\n'
c=
continue
fi
else
if mmime -c <$draft; then
stampdate $draft
if $sendmail <$draft; then
if mmime -c <"$draft" && ! [ "$automime" = 1 ]; then
stampdate "$draft"
if $sendmail <"$draft"; then
if [ "$outbox" ]; then
mflag -d $draft
mrefile "$(mflag -d -S "$draft")" "$outbox"
else
rm $draft
rm "$draft"
fi
exit 0
else
printf '%s\n' "mcom: $sendmail failed, kept draft $draft"
exit 2
@ -266,53 +477,80 @@ while :; do
else
printf '%s\n' "mcom: message needs to be MIME-encoded first."
c=
continue
fi
fi
case "$0" in
*mrep*) mflag -R -- "$1" ;;
*mbnc*) mflag -P -- "$1" ;;
*mfwd*) mflag -P -- "$@" ;;
esac
case "$0" in
*mrep*|*mbnc*|*mfwd*) mseq -f | mseq -S ;;
esac
exit 0
;;
c|cancel)
stampdate $draft
stampdate "$draft"
printf '%s\n' "mcom: cancelled draft $draft"
exit 1
;;
m|mime)
do_mime
mshow -t $draftmime
mshow -t "$draftmime"
c=
;;
e|edit)
c=
if ! ${EDITOR:-vi} $draft; then
c=c
if ! ${VISUAL:-${EDITOR:-vi}} "$draft"; then
c=d
else
if checksensible "$draft"; then
stripempty "$draft"
if mmime -c <"$draft" && ! needs_multipart "$draft"; then
automime=
else
automime=1
do_mime
fi
else
printf '\n'
fi
fi
stripempty $draft
if mmime -c <$draft; then
;;
justsend)
stripempty "$draft"
if mmime -c <"$draft" && ! needs_multipart "$draft"; then
automime=
else
automime=1
do_mime
fi
c=send
;;
d|delete)
rm -i $draft
if ! [ -f $draft ]; then
rm -f $draftmime
rm -i "$draft"
if ! [ -f "$draft" ]; then
rm -f "$draftmime"
printf '%s\n' "mcom: deleted draft $draft"
exit 0
fi
c=
;;
sign)
msign $draft >$draftmime
mshow -t $draftmime
msign "$draft" >"$draftmime"
mshow -t "$draftmime"
c=
;;
encrypt)
mencrypt $draft >$draftmime
mshow -t $draftmime
mencrypt "$draft" >"$draftmime"
mshow -t "$draftmime"
c=
;;
show)
if [ -e $draftmime ]; then
p|preview|show)
if [ -e "$draftmime" ]; then
mshow "$draftmime"
else
mshow "$draft"
@ -320,7 +558,7 @@ while :; do
c=
;;
*)
printf 'What now? (%s[s]end, [c]ancel, [d]elete, [e]dit, [m]ime, sign, encrypt) ' "${automime:+mime and }"
printf 'What now? (%s[s]end, [c]ancel, [d]elete, [e]dit, [m]ime, [p]review, sign, encrypt) ' "${automime:+mime and }"
read -r c
;;
esac

@ -1,11 +1,17 @@
#include <time.h>
#include <unistd.h>
#include "xpledge.h"
int
main()
{
char buf[64];
time_t now = time(0);
time_t now;
xpledge("stdio", "");
now = time(0);
ssize_t l = strftime(buf, sizeof buf,
"%a, %d %b %Y %T %z\n", localtime(&now));

@ -1,3 +1,4 @@
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
@ -9,9 +10,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
/*
design rationale:
@ -23,12 +26,15 @@ design rationale:
*/
static int cflag;
static int kflag;
static int Mflag;
static int vflag;
static char *Xflag;
char *targetdir;
long delivery;
int preserve_mtime;
int try_rename;
char host[64];
void
@ -44,9 +50,10 @@ gethost() {
}
int
deliver(FILE *infile)
deliver(char *infilename)
{
int outfd;
FILE *infile;
FILE *outfile;
char dst[PATH_MAX];
char tmp[PATH_MAX];
@ -56,6 +63,22 @@ deliver(FILE *infile)
char *line = 0;
size_t linelen = 0;
if (infilename) {
if (try_rename) {
infile = 0;
} else {
infile = fopen(infilename, "r");
if (!infile) {
fprintf(stderr, "mrefile: %s: %s\n",
infilename, strerror(errno));
return -1;
}
}
} else {
// mdeliver
infile = stdin;
}
if (Mflag) {
// skip to first "From " line
while (1) {
@ -63,9 +86,8 @@ deliver(FILE *infile)
ssize_t rd = getdelim(&line, &linelen, '\n', infile);
if (rd == -1) {
if (errno == 0)
// invalid mbox file
errno = EINVAL;
return -1;
errno = EINVAL; // invalid mbox file
goto fail;
}
if (strncmp("From ", line, 5) == 0)
@ -73,9 +95,9 @@ deliver(FILE *infile)
}
}
while (!feof(infile)) {
do {
delivery++;
tryagain:
try_again:
gettimeofday(&tv, 0);
snprintf(id, sizeof id, "%ld.M%06ldP%ldQ%ld.%s",
@ -84,11 +106,39 @@ tryagain:
snprintf(tmp, sizeof tmp, "%s/tmp/%s", targetdir, id);
outfd = open(tmp, O_CREAT | O_WRONLY | O_EXCL, 0666);
if (try_rename) {
snprintf(dst, sizeof dst, "%s/%s/%s"MAILDIR_COLON_SPEC_VER_COMMA"%s",
targetdir, cflag ? "cur" : "new", id, Xflag);
if (rename(infilename, dst) == 0)
goto success;
/* rename failed, open file and try copying */
infile = fopen(infilename, "r");
if (!infile) {
fprintf(stderr, "mrefile: %s: %s\n",
infilename, strerror(errno));
return -1;
}
}
struct stat st;
if (fstat(fileno(infile), &st) < 0)
st.st_mode = 0600;
if (S_ISFIFO(st.st_mode))
st.st_mode = 0600;
outfd = open(tmp, O_CREAT | O_WRONLY | O_EXCL,
st.st_mode & 07777);
if (outfd < 0) {
if (errno == EEXIST)
goto tryagain;
return -1;
goto try_again;
if (errno == ENOENT)
fprintf(stderr, "mrefile: %s/tmp: %s\n",
targetdir, strerror(errno));
else
fprintf(stderr, "mrefile: %s: %s\n",
tmp, strerror(errno));
goto fail;
}
outfile = fdopen(outfd, "w");
@ -97,32 +147,40 @@ tryagain:
int in_header = 1;
int is_old = 0;
int prev_line_empty = 0;
int this_line_empty = 0; // only for mbox parsing
while (1) {
errno = 0;
ssize_t rd = getdelim(&line, &linelen, '\n', infile);
if (rd == -1) {
if (errno != 0)
return -1;
goto fail;
break;
}
char *line_start = line;
if (line[0] == '\n' && !line[1])
if (line[0] == '\n' && (!line[1] ||
(line[1] == '\r' && !line[2]))) {
this_line_empty = Mflag ? 1 : 0;
in_header = 0;
} else {
this_line_empty = 0;
}
if (Mflag && strncmp("From ", line, 5) == 0)
break;
if (Mflag && in_header &&
(strncasecmp("status:", line, 6) == 0 ||
strncasecmp("x-status:", line, 8) == 0)) {
(strncasecmp("status:", line, 7) == 0 ||
strncasecmp("x-status:", line, 9) == 0)) {
char *v = strchr(line, ':');
if (strchr(v, 'F')) statusflags[0] = 'F';
if (strchr(v, 'A')) statusflags[1] = 'R';
if (strchr(v, 'R')) statusflags[2] = 'S';
if (strchr(v, 'D')) statusflags[3] = 'T';
if (strchr(v, 'O')) is_old = 1;
if (v) {
if (strchr(v, 'F')) statusflags[0] = 'F';
if (strchr(v, 'A')) statusflags[1] = 'R';
if (strchr(v, 'R')) statusflags[2] = 'S';
if (strchr(v, 'D')) statusflags[3] = 'T';
if (strchr(v, 'O')) is_old = 1;
}
continue; // drop header
}
@ -137,15 +195,22 @@ tryagain:
}
}
if (fwrite(line_start, 1, rd, outfile) != (size_t)rd)
return -1;
// print delayed empty line
if (prev_line_empty)
if (fputc('\n', outfile) == EOF)
goto fail;
if (!this_line_empty)
if (fwrite(line_start, 1, rd, outfile) != (size_t)rd)
goto fail;
prev_line_empty = this_line_empty;
}
if (fflush(outfile) == EOF)
return -1;
goto fail;
if (fsync(outfd) < 0)
return -1;
goto fail;
if (fclose(outfile) == EOF)
return -1;
goto fail;
// compress flags
int i, j;
@ -169,23 +234,108 @@ tryagain:
utimes(tmp, times);
}
}
blaze822_free(msg);
}
if (preserve_mtime) {
#if defined(AT_FDCWD) && defined(UTIME_NOW) && defined(UTIME_OMIT)
const struct timespec times[2] = {
{ tv.tv_sec, tv.tv_usec * 1000L },
#if (defined(__APPLE__) && defined(__MACH__))
st.st_mtimespec
#else /* POSIX.1-2008 */
st.st_mtim
#endif
};
utimensat(AT_FDCWD, tmp, times, 0);
#else
const struct timeval times[2] = {
tv,
{ st.st_mtime, 0 }
};
utimes(tmp, times);
#endif
}
snprintf(dst, sizeof dst, "%s/%s/%s:2,%s",
snprintf(dst, sizeof dst, "%s/%s/%s"MAILDIR_COLON_SPEC_VER_COMMA"%s",
targetdir, (cflag || is_old) ? "cur" : "new", id,
Xflag ? Xflag : statusflags);
if (rename(tmp, dst) != 0)
return -1;
goto fail;
success:
if (vflag)
printf("%s\n", dst);
}
} while (Mflag && !feof(infile));
if (infile)
fclose(infile);
return 0;
fail:
if (infile)
fclose(infile);
return -1;
}
void
refile(char *file)
{
while (*file == ' ' || *file == '\t')
file++;
// keep flags
char *flags = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA);
if (flags)
Xflag = flags + 3;
else
Xflag = "";
if (deliver(file) < 0) {
perror("mrefile");
return;
}
if (!kflag && !try_rename)
unlink(file);
}
int
main(int argc, char *argv[])
{
if (strchr(argv[0], 'f')) {
// mrefile(1)
cflag = 1; // use cur/
preserve_mtime = 1;
try_rename = 1;
int c;
while ((c = getopt(argc, argv, "kv")) != -1)
switch (c) {
case 'k': kflag = 1; try_rename = 0; break;
case 'v': vflag = 1; break;
default:
usage:
fprintf(stderr,
"Usage: mrefile [-kv] [msgs...] maildir\n");
exit(1);
}
if (argc == optind)
goto usage;
targetdir = argv[argc - 1];
gethost();
if (argc == optind + 1 && isatty(0))
goto usage;
else
blaze822_loop(argc - 1 - optind, argv + optind, refile);
return 0;
}
int c;
while ((c = getopt(argc, argv, "cMvX:")) != -1)
switch (c) {
@ -194,6 +344,7 @@ main(int argc, char *argv[])
case 'v': vflag = 1; break;
case 'X': Xflag = optarg; break;
default:
usage2:
fprintf(stderr,
"Usage: mdeliver [-c] [-v] [-X flags] dir < message\n"
" mdeliver -M [-c] [-v] [-X flags] dir < mbox\n"
@ -201,16 +352,20 @@ main(int argc, char *argv[])
exit(1);
}
if (argc != optind+1) {
fprintf(stderr, "usage: mdeliver DIR\n");
return 1;
if (argc != optind+1)
goto usage2;
xpledge("stdio rpath wpath cpath fattr", "");
if (!preserve_mtime && !Mflag) {
/* drop fattr */
xpledge("stdio rpath wpath cpath", "");
}
targetdir = argv[optind];
gethost();
if (deliver(stdin) < 0) {
if (deliver(0) < 0) {
perror("mdeliver");
return 2;
}

@ -3,18 +3,25 @@
#include <dirent.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "blaze822.h"
#include "blaze822_priv.h"
#include "xpledge.h"
static char sep = '\n';
int aflag;
void
pwd()
{
char cwd[PATH_MAX];
if (getcwd(cwd, sizeof cwd))
puts(cwd);
printf("%s%c", cwd, sep);
}
void
@ -44,10 +51,9 @@ mdirs(char *fpath)
}
while ((d = readdir(dir))) {
#if defined(DT_DIR) && defined(DT_UNKNOWN)
if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN)
if (!DIR_DT(d->d_type))
continue;
#endif
if (d->d_name[0] == '.' &&
d->d_name[1] == 0)
continue;
@ -56,7 +62,7 @@ mdirs(char *fpath)
d->d_name[2] == 0)
continue;
if (dotonly && d->d_name[0] != '.')
if (!aflag && dotonly && d->d_name[0] != '.')
continue;
mdirs(d->d_name);
@ -68,23 +74,71 @@ mdirs(char *fpath)
closedir(dir);
}
char *
profile_maildir()
{
char *f = blaze822_home_file("profile");
struct message *config = blaze822(f);
char *maildir;
static char path[PATH_MAX];
if (!config)
return 0;
if (!(maildir = blaze822_hdr(config, "maildir")))
return 0;
if (strncmp(maildir, "~/", 2) == 0) {
const char *home = getenv("HOME");
if (!home) {
struct passwd *pw = getpwuid(getuid());
home = pw ? pw->pw_dir : "/dev/null/homeless";
}
snprintf(path, sizeof path, "%s/%s", home, maildir+2);
maildir = path;
}
return maildir;
}
int
main(int argc, char *argv[])
{
int c, i;
while ((c = getopt(argc, argv, "")) != -1)
while ((c = getopt(argc, argv, "0a")) != -1)
switch (c) {
case '0': sep = '\0'; break;
case 'a': aflag = 1; break;
default:
usage:
fprintf(stderr, "Usage: mdirs dirs...\n");
fprintf(stderr, "Usage: mdirs [-0a] dirs...\n");
exit(1);
}
if (argc == optind)
xpledge("stdio rpath", "");
if (argc == optind) {
char *maildir = profile_maildir();
if (maildir) {
mdirs(maildir);
return 0;
}
goto usage;
}
char toplevel[PATH_MAX];
if (!getcwd(toplevel, sizeof toplevel)) {
perror("mdirs: getcwd");
exit(-1);
}
for (i = 0; i < argc; i++)
for (i = 0; i < argc; i++) {
mdirs(argv[i]);
if (chdir(toplevel) < 0) {
perror("mdirs: chdir");
exit(-1);
}
}
return 0;
}

@ -10,6 +10,7 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static int Sflag;
@ -32,7 +33,7 @@ export(char *file)
}
char from[1024] = "nobody";
time_t date = -1;
time_t date = 0;
if (fseek(infile, 0L, SEEK_SET) && errno == ESPIPE) {
date = time(0);
@ -67,7 +68,7 @@ export(char *file)
char *line = 0;
size_t linelen = 0;
printf("From %s %s", from, ctime(&date));
printf("From %s %s", from, asctime(gmtime(&date)));
int in_header = 1;
int final_nl = 0;
@ -86,7 +87,7 @@ export(char *file)
if (in_header && line[0] == '\n' && !line[1]) {
if (Sflag) {
char *flags = strstr(file, ":2,");
char *flags = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA);
if (!flags)
flags = "";
@ -124,6 +125,9 @@ export(char *file)
if (!final_nl)
putchar('\n');
// ensure empty line at end of message
putchar('\n');
fclose(infile);
}
@ -141,6 +145,8 @@ main(int argc, char *argv[])
status = 0;
xpledge("stdio rpath", "");
if (argc == optind && isatty(0))
blaze822_loop1(":", export);
else

@ -12,8 +12,8 @@
#include <unistd.h>
#include "blaze822.h"
#define uc(c) ((c) & 0xdf)
#include "blaze822_priv.h"
#include "xpledge.h"
static int8_t flags[255];
static int vflag = 0;
@ -47,7 +47,7 @@ flag(char *file)
while (file[indent] == ' ' || file[indent] == '\t')
indent++;
char *f = strstr(file, ":2,");
char *f = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA);
if (!f)
goto skip;
@ -135,6 +135,8 @@ main(int argc, char *argv[])
exit(1);
}
xpledge("stdio rpath cpath", "");
curfile = blaze822_seq_cur();
if (vflag) {
@ -143,7 +145,7 @@ main(int argc, char *argv[])
return 0;
}
args = calloc(sizeof (char *), argsalloc);
args = calloc(argsalloc, sizeof (char *));
if (!args)
exit(-1);

@ -1,3 +1,7 @@
#ifdef __sun
#define __EXTENSIONS__ /* to get TIOCGWINSZ */
#endif
#include <sys/ioctl.h>
#include <sys/stat.h>
@ -7,9 +11,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <termios.h>
#include "blaze822.h"
#include "xpledge.h"
int column = 0;
int maxcolumn = 80;
@ -27,28 +34,6 @@ chgquote(int quotes)
}
}
void
fixed(int quotes, char *line, size_t linelen)
{
chgquote(quotes);
if (column && linelen > (size_t)(maxcolumn - column)) {
putchar('\n');
column = 0;
}
if (column == 0) {
for (; column < quotes; column++)
putchar('>');
if (quotes)
putchar(' ');
}
fwrite(line, 1, linelen, stdout);
putchar('\n');
column = 0;
}
void
flowed(int quotes, char *line, ssize_t linelen)
{
@ -60,7 +45,7 @@ flowed(int quotes, char *line, ssize_t linelen)
for (; column < quotes; column++)
putchar('>');
column++;
if (quotes)
if (quotes && *line != ' ')
putchar(' ');
}
@ -94,6 +79,15 @@ flowed(int quotes, char *line, ssize_t linelen)
}
}
void
fixed(int quotes, char *line, size_t linelen)
{
flowed(quotes, line, linelen);
putchar('\n');
column = 0;
}
int
main(int argc, char *argv[])
{
@ -107,6 +101,8 @@ main(int argc, char *argv[])
int force = 0;
int delsp = 0;
xpledge("stdio rpath tty", "");
char *ct = getenv("PIPE_CONTENTTYPE");
if (ct) {
char *s, *se;
@ -130,6 +126,8 @@ main(int argc, char *argv[])
}
}
xpledge("stdio", "");
char *maxcols = getenv("MAXCOLUMNS");
if (maxcols && isdigit(*maxcols)) {
int m = atoi(maxcols);
@ -194,6 +192,11 @@ main(int argc, char *argv[])
if (delsp)
line[--rd] = 0;
flowed(quotes, line, rd);
} else if (rd == 0) { // empty line is fixed
if (column > 0)
putchar('\n');
putchar('\n');
column = 0;
} else {
if (force && rd > maxcolumn) {
flowed(quotes, line, rd);

@ -13,6 +13,7 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
void
printb36(uint64_t x)
@ -36,6 +37,8 @@ int main()
char *f = blaze822_home_file("profile");
struct message *config = blaze822(f);
xpledge("stdio rpath dns", "");
if (config) // try FQDN: first
host = blaze822_hdr(config, "fqdn");
@ -85,30 +88,35 @@ int main()
struct timeval tp;
gettimeofday(&tp, (struct timezone *)0);
uint64_t rnd;
uint64_t rnd1, rnd2;
int rndfd = open("/dev/urandom", O_RDONLY);
if (rndfd >= 0) {
unsigned char rndb[8];
unsigned char rndb[16];
if (read(rndfd, rndb, sizeof rndb) != sizeof rndb)
goto fallback;
close(rndfd);
int i;
for (i = 0, rnd = 0; i < 8; i++)
rnd = rnd*256 + rndb[i];
for (i = 0, rnd1 = 0; i < 8; i++)
rnd1 = rnd1*256 + rndb[i];
for (i = 0, rnd2 = 0; i < 8; i++)
rnd2 = rnd2*256 + rndb[i+8];
} else {
fallback:
srand48(tp.tv_sec ^ tp.tv_usec ^ getpid());
rnd = ((uint64_t)lrand48() << 32) + lrand48();
rnd1 = ((uint64_t)lrand48() << 32) + lrand48();
rnd2 = ((uint64_t)lrand48() << 32) + lrand48();
}
rnd |= (1ULL << 63); // set highest bit to force full width
rnd1 ^= ((uint64_t)tp.tv_sec * 1000000LL + tp.tv_usec);
rnd1 |= (1ULL << 63); // set highest bit to force full width
rnd2 |= (1ULL << 63); // set highest bit to force full width
putchar('<');
printb36(((uint64_t)tp.tv_sec * 1000000LL + tp.tv_usec));
printb36(rnd1);
putchar('.');
printb36(rnd);
printb36(rnd2);
putchar('@');
fputs(host, stdout);
putchar('>');

@ -7,9 +7,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static char *hflag;
static char *pflag;
@ -59,25 +61,50 @@ headerall(struct message *msg)
}
void
print_addresses(char *s)
print_quoted(char *s)
{
char *disp, *addr;
char sdec[4096];
char *t;
for (t = s; *t; t++)
if ((unsigned char)*t < 32 || strchr("()<>[]:;@\\,.\"", *t))
goto quote;
printf("%s", s);
return;
if (dflag) {
blaze822_decode_rfc2047(sdec, s, sizeof sdec, "UTF-8");
sdec[sizeof sdec - 1] = 0;
s = sdec;
quote:
putchar('"');
for (t = s; *t; t++) {
if (*t == '"' || *t == '\\')
putchar('\\');
putchar(*t);
}
putchar('"');
}
void
print_addresses(char *s)
{
char *disp, *addr;
char ddec[4096];
while ((s = blaze822_addr(s, &disp, &addr))) {
if (Hflag && addr)
printf("%s\t", curfile);
if (disp && addr)
printf("%s <%s>\n", disp, addr);
else if (addr)
if (disp && addr) {
if (dflag) {
blaze822_decode_rfc2047(ddec, disp, sizeof ddec, "UTF-8");
ddec[sizeof ddec - 1] = 0;
disp = ddec;
}
print_quoted(disp);
printf(" <%s>\n", addr);
} else if (addr) {
printf("%s\n", addr);
}
}
}
@ -138,7 +165,7 @@ headermany(struct message *msg)
*n = 0;
size_t l = strlen(h);
if (strncmp(hdr, h, l) == 0 && hdr[l] == ':') {
if (strncasecmp(hdr, h, l) == 0 && hdr[l] == ':') {
hdr += l + 1;
while (*hdr == ' ' || *hdr == '\t')
hdr++;
@ -220,6 +247,8 @@ main(int argc, char *argv[])
status = 1;
xpledge("stdio rpath", "");
if (argc == optind && isatty(0))
blaze822_loop1(".", header);
else

@ -11,6 +11,8 @@
#include <unistd.h>
#include "blaze822.h"
#include "blaze822_priv.h"
#include "xpledge.h"
static int qflag;
static int status;
@ -35,17 +37,17 @@ inc(char *dir)
}
while ((d = readdir(fd))) {
#if defined(DT_REG) && defined(DT_UNKNOWN)
if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN)
if (!MAIL_DT(d->d_type))
continue;
#endif
if (d->d_name[0] == '.')
continue;
snprintf(src, sizeof src, "%s/new/%s",
dir, d->d_name);
snprintf(dst, sizeof dst, "%s/cur/%s%s",
dir, d->d_name, strstr(d->d_name, ":2,") ? "" : ":2,");
dir, d->d_name,
strstr(d->d_name, MAILDIR_COLON_SPEC_VER_COMMA) ? "" : MAILDIR_COLON_SPEC_VER_COMMA);
if (rename(src, dst) < 0) {
fprintf(stderr, "minc: can't rename '%s' to '%s': %s\n",
src, dst, strerror(errno));
@ -73,12 +75,17 @@ usage:
exit(1);
}
if (optind == argc)
goto usage;
xpledge("stdio rpath cpath", "");
status = 0;
for (i = optind; i < argc; i++)
inc(argv[i]);
if (optind == argc) {
if (isatty(0))
goto usage;
blaze822_loop(0, 0, inc);
} else {
for (i = optind; i < argc; i++)
inc(argv[i]);
}
return status;
}

41
mless

@ -22,7 +22,16 @@ if [ "$1" = --filter ]; then
fi
mseq -C "$2"
mscan .-2:.+3 2>/dev/null | colorscan
total=$(mscan -n -- -1)
case $2 in
1) mscan .-0:.+5 ;;
2) mscan .-1:.+4 ;;
$((total - 2))) mscan .-3:.+2 ;;
$((total - 1))) mscan .-4:.+1 ;;
$total) mscan .-5:.+0 ;;
*) mscan .-2:.+3 ;;
esac 2>/dev/null | colorscan
echo
if ! [ -f "$(mseq -r "$2")" ]; then
@ -37,9 +46,9 @@ if [ "$1" = --filter ]; then
mshow "$2"
fi | mcolor
else
mseq -r $2
mseq -r "$2"
echo
cat "$(mseq -r $2)"
cat "$(mseq -r "$2")"
fi
exit $?
fi
@ -53,11 +62,7 @@ if ! [ -t 1 ]; then
exec mseq :
fi
case "$0" in
*mnext) set -- +;;
*mprev) set -- -;;
*) [ "$#" -eq 1 ] && set -- ${1:-.};;
esac
[ "$#" -eq 1 ] && set -- ${1:-.}
if [ "$#" -ge 1 ]; then
mseq -C "$1"
@ -67,14 +72,20 @@ nl="
"
export MLESS_RAW=0
export MLESS_HTML=0
if [ -f "$MBLAZE/mlesskey" ]; then
export LESSKEYIN="$MBLAZE/mlesskey"
elif [ -f "$HOME/.mblaze/mlesskey" ]; then
export LESSKEYIN="$HOME/.mblaze/mlesskey"
elif [ -f "$HOME/.mlesskey" ]; then
export LESSKEYIN="$HOME/.mlesskey"
elif [ -f "$MBLAZE/mless" ]; then
export LESSKEY="$MBLAZE/mless"
elif [ -f "$HOME/.mblaze/mless" ]; then
export LESSKEY="$HOME/.mblaze/mless"
elif [ -f "$HOME/.mless" ]; then
export LESSKEY="$HOME/.mless"
fi
while :; do
if [ -f $MBLAZE/mless ]; then
export LESSKEY=$MBLAZE/mless
elif [ -f $HOME/.mblaze/mless ]; then
export LESSKEY=$HOME/.mblaze/mless
elif [ -f $HOME/.mless ]; then
export LESSKEY=$HOME/.mless
fi
LESSOPEN="|$0 --filter %s" \
less -Ps"mless %f?m (message %i of %m).." -R \
"+:e $(mscan -n .)$nl" //scan $(mscan -n :)

@ -1,5 +1,7 @@
# mless(1) keybindings
# to update: lesskey -o ~/.mless ~/.mlesskey
# When using less <590:
# compile with lesskey -o .mless mlesskey and install as ~/.mless
# When using less >=590: copy this file to ~/.mlesskey
Q quit \1
:cq quit \1
[ prev-file

@ -12,9 +12,8 @@
#include <unistd.h>
#include "blaze822.h"
#define lc(c) ((c) | 0x20)
#define uc(c) ((c) & 0xdf)
#include "blaze822_priv.h"
#include "xpledge.h"
/*
@ -49,34 +48,47 @@ static long tdirs;
static long tunseen;
static long tflagged;
static long tcount;
static long tmatched;
void
list(char *prefix, char *file)
{
char *f = 0;
if (flagset || iflag) {
size_t prefixlen;
f = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA);
if (!f &&
prefix &&
(prefixlen = strlen(prefix)) &&
prefixlen >= 4 &&
strcmp(prefix + prefixlen - 4, "/new") == 0)
f = MAILDIR_COLON_SPEC_VER_COMMA;
}
if (flagset) {
int sum = 0;
char *f = strstr(file, ":2,");
if (!f)
return;
icount++;
tcount++;
f += 3;
while (*f) {
if (flags[(unsigned int)*f] == -1)
char *g;
for (g = f + 3; *g; g++)
if (flags[(unsigned int)*g] == -1)
return;
if (flags[(unsigned int)*f] == 1)
else if (flags[(unsigned int)*g] == 1)
sum++;
f++;
}
if (sum != flagsum)
return;
}
if (iflag) {
char *f = strstr(file, ":2,");
if (!f)
return;
imatched++;
tmatched++;
if (!flagset)
icount++, tcount++;
if (!strchr(f, 'S'))
@ -99,8 +111,8 @@ list(char *prefix, char *file)
#include <sys/syscall.h>
struct linux_dirent64 {
ino64_t d_ino; /* 64-bit inode number */
off64_t d_off; /* 64-bit offset to next structure */
uint64_t d_ino; /* 64-bit inode number */
int64_t d_off; /* 64-bit offset to next structure */
unsigned short d_reclen; /* Size of this dirent */
unsigned char d_type; /* File type */
char d_name[]; /* Filename (null-terminated) */
@ -134,7 +146,7 @@ listdir(char *dir)
for (bpos = 0; bpos < nread; bpos += d->d_reclen) {
d = (struct linux_dirent64 *)(buf + bpos);
if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN)
if (!MAIL_DT(d->d_type))
continue;
if (d->d_name[0] == '.')
continue;
@ -155,7 +167,7 @@ listdir(char *dir)
if (!fd)
return;
while ((d = readdir(fd))) {
if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN)
if (!MAIL_DT(d->d_type))
continue;
if (d->d_name[0] == '.')
continue;
@ -206,8 +218,12 @@ listarg(char *arg)
if (iflag && (imatched || (maildir && !flagset))) {
tdirs++;
printf("%6ld unseen %3ld flagged %6ld msg %s\n",
iunseen, iflagged, icount, arg);
if (flagset && imatched)
printf("%6ld matched %6ld unseen %3ld flagged %6ld msg %s\n",
imatched, iunseen, iflagged, icount, arg);
else
printf("%6ld unseen %3ld flagged %6ld msg %s\n",
iunseen, iflagged, icount, arg);
}
icount = gcount;
@ -257,6 +273,8 @@ usage:
int i;
xpledge("stdio rpath", "");
for (i = 0, flagsum = 0, flagset = 0; (size_t)i < sizeof flags; i++) {
if (flags[i] != 0)
flagset++;
@ -273,9 +291,14 @@ usage:
listarg(argv[i]);
}
if (iflag && tdirs > 1)
printf("%6ld unseen %3ld flagged %6ld msg\n",
tunseen, tflagged, tcount);
if (iflag && tdirs > 1) {
if (flagset)
printf("%6ld matched %6ld unseen %3ld flagged %6ld msg\n",
tmatched, tunseen, tflagged, tcount);
else
printf("%6ld unseen %3ld flagged %6ld msg\n",
tunseen, tflagged, tcount);
}
return 0;
}

@ -1,6 +1,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@ -11,10 +12,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static int cflag;
static int rflag;
@ -57,6 +60,11 @@ int gen_b64(uint8_t *s, off_t size)
return 0;
}
#define qphrasevalid(c) ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || \
(c >= 'a' && c <= 'z') || \
c == '!' || c == '*' || c == '+' || c == '-' || \
c == '/')
size_t
gen_qp(uint8_t *s, off_t size, size_t maxlinelen, size_t linelen)
{
@ -121,6 +129,10 @@ gen_qp(uint8_t *s, off_t size, size_t maxlinelen, size_t linelen)
putc_unlocked('\n', stdout);
linelen = 0;
prev = 0;
} else if (header && !qphrasevalid(s[i])) {
printf("=%02X", s[i]);
linelen += 3;
prev = '_';
} else {
putc_unlocked(s[i], stdout);
linelen++;
@ -246,7 +258,7 @@ gen_file(char *file, char *ct)
}
if (bitlow == 0 && bithigh == 0 &&
maxlinelen <= 78 && content[size-1] == '\n') {
maxlinelen <= 78) {
if (!ct)
ct = "text/plain";
printf("Content-Type: %s\n", ct);
@ -282,6 +294,9 @@ print_header(char *line) {
char *s, *e;
size_t l = strlen(line);
if (l == 0)
return;
if (line[l-1] == '\n')
line[l-1] = 0;
@ -298,23 +313,41 @@ print_header(char *line) {
}
int prevq = 0; // was the previous word encoded as qp?
char prevqs = 0; // was the previous word a quoted-string?
size_t linelen = s - line;
ssize_t linelen = s - line;
while (*s) {
size_t highbit = 0;
int qs = 0;
e = s;
while (*e && *e == ' ')
e++;
for (; *e && *e != ' '; e++) {
if ((uint8_t)*e >= 127)
highbit++;
if (*e == '"') { // scan quoted-string, encode at once
s = e;
for (e++; *e && *e != '"'; e++) {
if (*e == '\\')
e++;
if ((uint8_t)*e >= 127)
highbit++;
}
if (*e == '"')
e++;
qs = 1;
} else { // scan word
while (*e && *e == ' ')
e++;
for (; *e && *e != ' '; e++) {
if ((uint8_t)*e >= 127)
highbit++;
}
}
if (!highbit) {
if (e-s >= 998)
goto force_qp;
if (e-s >= 78 - linelen) {
if (e-s >= 78 - linelen && linelen > 0) {
// wrap in advance before long word
printf("\n");
linelen = 0;
@ -323,7 +356,7 @@ print_header(char *line) {
// space at beginning of line
goto force_qp;
}
if (*s != ' ') {
if (*s != ' ' && !(prevqs && !prevq && *(s-1) != ' ')) {
printf(" ");
linelen++;
}
@ -334,6 +367,12 @@ print_header(char *line) {
force_qp:
if (!prevq && *s == ' ')
s++;
if (qs && *s == '"')
s++;
if (qs && e > s && *(e-1) == '"')
e--;
if (linelen >= 78 - 13 - 4 ||
(e-s < (78 - 13)/3 &&
e-s >= (78 - linelen - 13)/3)) {
@ -347,12 +386,33 @@ force_qp:
printf("?=");
linelen += 2;
prevq = 1;
if (qs && *e == '"')
e++;
}
prevqs = qs;
s = e;
}
printf("\n");
}
static int
valid_content_type(char *s)
{
int slash = 0;
for (; *s; s++)
if (*s == '/')
slash++;
else if (isalnum(*s) || *s == '-' || *s == '+' || *s == '.' ||
*s == ';' || *s == '=' || *s == '#')
; /* ok */
else
return 0;
return slash == 1;
}
int
gen_build()
{
@ -364,30 +424,45 @@ gen_build()
size_t linelen = 0;
int inheader = 1;
int intext = 0;
int emptybody = 1;
int ret = 0;
char *contenttype = 0;
char *contenttransferenc = 0;
while (1) {
ssize_t read = getdelim(&line, &linelen, '\n', stdin);
if (read == -1) {
if (feof(stdin))
break;
else
if (feof(stdin)) {
if (!emptybody)
break;
line = strdup(inheader ? "\n" : "");
} else { // errored
exit(1);
}
}
if (inheader) {
if (line[0] == '\n') {
inheader = 0;
printf("MIME-Version: 1.0\n");
if (rflag) {
printf("Content-Type: text/plain; charset=UTF-8\n");
printf("Content-Transfer-Encoding: quoted-printable\n\n");
printf("Content-Type:%s", contenttype ? contenttype : " text/plain; charset=UTF-8\n");
printf("Content-Transfer-Encoding:%s", contenttransferenc ? contenttransferenc : " quoted-printable\n");
printf("\n");
} else {
printf("Content-Type: %s; boundary=\"%s\"\n", tflag, sep);
printf("\n");
printf("This is a multipart message in MIME format.\n");
}
} else {
print_header(line);
if (strncasecmp(line, "Content-Type:", 13) == 0) {
free(contenttype);
contenttype = strdup(line+13);
} else if (strncasecmp(line, "Content-Transfer-Encoding:", 26) == 0) {
free(contenttransferenc);
contenttransferenc = strdup(line+26);
} else {
print_header(line);
}
}
continue;
}
@ -397,12 +472,14 @@ gen_build()
if (f) {
char of = *f;
*f = 0;
if (strchr(line, '/')) {
if (valid_content_type(line+1)) {
printf("\n--%s\n", sep);
if (line[read-1] == '\n')
line[read-1] = 0;
gen_file(f+1, (char *)line+1);
if (gen_file(f+1, line+1) != 0)
ret = 1;
intext = 0;
emptybody = 0;
continue;
}
*f = of;
@ -411,20 +488,26 @@ gen_build()
if (!rflag && !intext) {
printf("\n--%s\n", sep);
printf("Content-Type: text/plain; charset=UTF-8\n");
printf("Content-Type:%s", contenttype ? contenttype : " text/plain; charset=UTF-8\n");
printf("Content-Disposition: inline\n");
printf("Content-Transfer-Encoding: quoted-printable\n\n");
printf("Content-Transfer-Encoding:%s", contenttransferenc ? contenttransferenc : " quoted-printable\n");
printf("\n");
intext = 1;
}
gen_qp((uint8_t *)line, strlen(line), 78, 0);
if (contenttransferenc)
printf("%s", line);
else
gen_qp((uint8_t *)line, strlen(line), 78, 0);
emptybody = 0;
}
if (!rflag && !inheader)
printf("\n--%s--\n", sep);
free(line);
return 0;
return ret;
}
int
@ -435,6 +518,7 @@ check()
off_t linelen = 0;
off_t maxheadlinelen = 0;
off_t maxbodylinelen = 0;
off_t bodylinelenlimit = getenv("MBLAZE_RELAXED_MIME") ? 998 : 78;
int c;
int l = -1;
@ -472,7 +556,7 @@ check()
}
if (bitlow == 0 && bithigh == 0 &&
maxheadlinelen < 998 && maxbodylinelen <= 78 &&
maxheadlinelen < 998 && maxbodylinelen <= bodylinelenlimit &&
l == '\n')
return 0;
else
@ -500,6 +584,8 @@ usage:
if (argc != optind)
goto usage;
xpledge("stdio rpath", "");
if (cflag)
return check();

@ -1,9 +1,11 @@
#!/bin/sh
# mmkdir DIRS... - create new maildirs
umask 077
r=0
for dir; do
mkdir -p -m 0700 $dir/cur $dir/new $dir/tmp || r=1
mkdir -p "$dir"/tmp "$dir"/new "$dir"/cur || r=1
done
exit $r

@ -1 +0,0 @@
mless

File diff suppressed because it is too large Load Diff

@ -1 +0,0 @@
mless

@ -1,11 +1,17 @@
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#ifdef __sun
#define __EXTENSIONS__ /* to get TIOCGWINSZ */
#endif
#include "xpledge.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
@ -16,6 +22,7 @@
#include <time.h>
#include <unistd.h>
#include <wchar.h>
#include <termios.h>
#include "blaze822.h"
#include "u8decode.h"
@ -29,6 +36,7 @@ static int alias_idx;
static int Iflag;
static int nflag;
static int vflag;
static int curyear;
static time_t now;
static char default_fflag[] = "%c%u%r %-3n %10d %17f %t %2i%s";
@ -52,9 +60,13 @@ u8putstr(FILE *out, char *s, ssize_t l, int pad)
int r = u8decode(s, &c);
if (r < 0) {
r = 1;
l--;
fprintf(out, "%lc", (wint_t)replacement);
} else {
l -= wcwidth((wchar_t)c);
int w = wcwidth((wchar_t)c);
if (w < 0)
w = 2; /* assume worst width */
l -= w;
if (l >= 0)
fwrite(s, 1, r, out);
}
@ -133,6 +145,33 @@ fmt_date(struct message *msg, int w, int iso)
strftime(date, sizeof date, "%Y-%m-%d %H:%M", tm);
else
strftime(date, sizeof date, "%Y-%m-%d", tm);
} else if (w >= 19) {
// https://dotti.me/
tm = gmtime(&t);
strftime(date, sizeof date, "%Y-%m-%dT%H\xc2\xb7%M", tm);
char *d = date + strlen(date);
char *e = v + strlen(v);
while (e > v && (*e != '+' && *e != '-'))
e--;
if (isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4])) {
*d++ = e[0];
*d++ = e[1];
*d++ = e[2];
if (e[3] != '0' || e[4] != '0') {
*d++ = ':';
*d++ = e[3];
*d++ = e[4];
}
*d = 0;
} else {
*d++ = '+';
*d++ = '0';
*d++ = '0';
*d = 0;
}
} else if (w < 10) {
if (tm->tm_year != curyear)
strftime(date, sizeof date, "%b%y", tm);
@ -283,7 +322,7 @@ oneline(char *file)
}
struct message *msg = blaze822(file);
char *flags = msg ? strstr(file, ":2,") : 0;
char *flags = msg ? strstr(file, MAILDIR_COLON_SPEC_VER_COMMA) : 0;
if (!flags)
flags = "";
else
@ -338,12 +377,12 @@ oneline(char *file)
wleft--;
break;
case 'u': // unseen
if (strchr(flags, 'F'))
if (strchr(flags, 'T'))
putchar('x');
else if (strchr(flags, 'F'))
putchar('*');
else if (msg && !strchr(flags, 'S'))
putchar('.');
else if (strchr(flags, 'T'))
putchar('x');
else
putchar(' ');
wleft--;
@ -432,13 +471,21 @@ oneline(char *file)
case 'F':
{
char *e = file + strlen(file);
if (!msg)
goto empty;
while (file < e && *e != '/')
e--;
if (file == e)
goto empty;
e--;
while (file < e && *e != '/')
e--;
while (file < e && *e == '/')
e--;
if (file == e)
goto empty;
char *b = e;
e++;
while (file < b && *b != '/')
@ -447,6 +494,11 @@ oneline(char *file)
b++;
if (*b == '.')
b++;
if (0) {
empty:
b = e = "";
}
if (w) {
if (w < 0)
w = -w;
@ -489,19 +541,22 @@ oneline(char *file)
int
main(int argc, char *argv[])
{
pid_t pid1 = -1;
pid_t pager_pid = -1;
int c;
while ((c = getopt(argc, argv, "If:n")) != -1)
while ((c = getopt(argc, argv, "If:nv")) != -1)
switch (c) {
case 'I': Iflag++; break;
case 'f': fflag = optarg; break;
case 'n': nflag = 1; break;
case 'v': vflag = 1; break;
default:
fprintf(stderr, "Usage: mscan [-n] [-f format] [-I] [msgs...]\n");
fprintf(stderr, "Usage: mscan [-Inv] [-f format] [msgs...]\n");
exit(1);
}
xpledge("stdio rpath tty proc exec", NULL);
if (nflag) {
if (argc == optind && isatty(0))
blaze822_loop1(":", numline);
@ -520,23 +575,28 @@ main(int argc, char *argv[])
struct winsize w;
int ttyfd = open("/dev/tty", O_RDONLY | O_NOCTTY);
if (ttyfd >= 0 && ioctl(ttyfd, TIOCGWINSZ, &w) == 0) {
cols = w.ws_col;
if (ttyfd >= 0) {
if (ioctl(ttyfd, TIOCGWINSZ, &w) == 0)
cols = w.ws_col;
close(ttyfd);
}
if (isatty(1)) {
char *pg;
pg = getenv("MBLAZE_PAGER");
if (!pg)
pg = getenv("PAGER");
if (pg && *pg && strcmp(pg, "cat") != 0) {
pid1 = pipeto(pg);
if (pid1 < 0)
pager_pid = pipeto(pg);
if (pager_pid < 0)
fprintf(stderr,
"mscan: spawning pager '%s': %s\n",
pg, strerror(errno));
}
}
if (ttyfd >= 0)
close(ttyfd);
xpledge("stdio rpath", "");
if (getenv("COLUMNS"))
cols = atoi(getenv("COLUMNS"));
if (cols <= 40)
@ -567,10 +627,12 @@ main(int argc, char *argv[])
i = blaze822_loop1(":", oneline);
else
i = blaze822_loop(argc-optind, argv+optind, oneline);
fprintf(stderr, "%ld mails scanned\n", i);
if (pid1 > 0)
pipeclose(pid1);
if (pager_pid > 0)
pipeclose(pager_pid);
if (vflag)
fprintf(stderr, "%ld mails scanned\n", i);
return 0;
}

@ -11,6 +11,7 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static char *expr;
@ -28,7 +29,7 @@ subst(char *str, char *srch, char *repl, char *flags)
#define APPC(c) do { if (b >= bufe) return str; *b++ = c; } while (0)
regex_t srchrx;
regmatch_t pmatch[10];
regmatch_t pmatch[10] = {{0}};
if (regcomp(&srchrx, srch, iflag ? REG_ICASE : 0) != 0)
return str;
@ -137,7 +138,7 @@ sed(char *file)
if (*s == ':')
*s = '|';
int rv;
if ((rv = regcomp(&headerrx, headersel, REG_EXTENDED)) != 0) {
if ((rv = regcomp(&headerrx, headersel, REG_EXTENDED | REG_ICASE)) != 0) {
char buf[100];
regerror(rv, &headerrx, buf, sizeof buf);
fprintf(stderr, "msed: %s\n", buf);
@ -217,6 +218,8 @@ sed(char *file)
free(to);
free(flags);
break;
case '\0':
break;
default:
fprintf(stderr, "msed: unknown command: '%c'\n", *e);
@ -321,6 +324,8 @@ main(int argc, char *argv[])
exit(1);
}
xpledge("stdio rpath", "");
expr = argv[optind];
optind++;

@ -12,6 +12,8 @@
#include <unistd.h>
#include "blaze822.h"
#include "blaze822_priv.h"
#include "xpledge.h"
static int fflag;
static int rflag;
@ -58,10 +60,9 @@ namescan(char *dir)
if (!fd)
return;
while ((d = readdir(fd))) {
#if defined(DT_REG) && defined(DT_UNKNOWN)
if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN)
if (!MAIL_DT(d->d_type))
continue;
#endif
if (d->d_name[0] == '.')
continue;
@ -69,7 +70,7 @@ namescan(char *dir)
snprintf(file, sizeof file, "%s/%s", dir, d->d_name);
char *e;
if ((e = strstr(d->d_name, ":2,")))
if ((e = strstr(d->d_name, MAILDIR_COLON_SPEC_VER_COMMA)))
*e = 0;
struct name *c = malloc(sizeof (struct name));
@ -100,7 +101,7 @@ search(char *file)
if (!namefind(dir))
namescan(dir);
if ((e = strstr(file, ":2,")))
if ((e = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA)))
*e = 0;
return namefind(file);
@ -130,11 +131,11 @@ fix(FILE *out, char *file)
char *e;
char *sep;
if ((e = strstr(file, ":2,"))) {
if ((e = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA))) {
sep = "";
e[3] = 0;
} else {
sep = ":2,";
sep = MAILDIR_COLON_SPEC_VER_COMMA;
}
snprintf(buf, sizeof buf, "%s%s", file, sep);
if (access(buf, F_OK) == 0) goto ok;
@ -298,6 +299,8 @@ usage:
exit(1);
}
xpledge("stdio rpath wpath cpath", "");
if (cflag)
blaze822_loop1(cflag, overridecur);
@ -314,14 +317,10 @@ usage:
if (optind == argc && !isatty(0))
return stdinmode();
char *seq = blaze822_seq_open(0);
if (!seq)
return 1;
int i;
char *f;
char *a;
struct blaze822_seq_iter iter = { 0 };
char *seq = 0;
if (optind == argc) {
a = ":";
@ -335,6 +334,14 @@ hack:
printf("%s\n", a);
continue;
}
if (!seq) {
seq = blaze822_seq_open(0);
if (!seq)
return 1;
}
struct blaze822_seq_iter iter = { 0 };
while ((f = blaze822_seq_next(seq, a, &iter))) {
char *s = f;
if (rflag)

@ -14,7 +14,9 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static int Bflag;
static int rflag;
static int Rflag;
static int qflag;
@ -34,6 +36,7 @@ static char fallback_ct[] = "text/plain";
struct message *filters;
static int mimecount;
static int extractcount;
static int safe_output;
static int reply_found;
@ -48,10 +51,10 @@ printable(int c)
}
size_t
print_ascii(char *body, size_t bodylen)
print_ascii(char *body, size_t bodylen, int oneline)
{
if (safe_output) {
safe_u8putstr(body, bodylen, stdout);
safe_u8putstr(body, bodylen, oneline, stdout);
return bodylen;
} else {
return fwrite(body, 1, bodylen, stdout);
@ -70,7 +73,7 @@ printhdr(char *hdr)
}
if (*hdr) {
print_ascii(hdr, strlen(hdr));
print_ascii(hdr, strlen(hdr), 1);
fputc('\n', stdout);
}
}
@ -82,7 +85,7 @@ print_u8recode(char *body, size_t bodylen, char *srcenc)
ic = iconv_open("UTF-8", srcenc);
if (ic == (iconv_t)-1) {
printf("unsupported encoding: %s\n", srcenc);
fprintf(stderr, "unsupported encoding: %s\n", srcenc);
return;
}
@ -95,7 +98,7 @@ print_u8recode(char *body, size_t bodylen, char *srcenc)
size_t r = iconv(ic, &body, &bodylen, &bufptr, &buflen);
if (bufptr != buf) {
print_ascii(buf, bufptr-buf);
print_ascii(buf, bufptr-buf, 0);
final_char = bufptr[-1];
}
@ -104,7 +107,7 @@ print_u8recode(char *body, size_t bodylen, char *srcenc)
buflen = sizeof buf;
r = iconv(ic, 0, 0, &bufptr, &buflen);
if (bufptr != buf) {
print_ascii(buf, bufptr-buf);
print_ascii(buf, bufptr-buf, 0);
final_char = bufptr[-1];
}
if (r != (size_t)-1)
@ -166,6 +169,12 @@ mime_filename(struct message *msg)
filename = buf;
}
if (Bflag && filename) {
static char buf2[512];
blaze822_decode_rfc2047(buf2, filename, sizeof buf2, "UTF-8");
filename = buf2;
}
return filename;
}
@ -176,11 +185,23 @@ print_filename(char *filename)
{
if (filename) {
printf(" name=\"");
safe_u8putstr(filename, strlen(filename), stdout);
safe_u8putstr(filename, strlen(filename), 1, stdout);
printf("\"");
}
}
static int
probably_utf8(char *charset)
{
return !charset ||
strcasecmp(charset, "utf-8") == 0 ||
strcasecmp(charset, "utf8") == 0 ||
strcasecmp(charset, "us-ascii") == 0 ||
/* best guess for: */
strcasecmp(charset, "unknown-8bit") == 0 ||
strcasecmp(charset, "x-unknown") == 0;
}
blaze822_mime_action
render_mime(int depth, struct message *msg, char *body, size_t bodylen)
{
@ -210,12 +231,18 @@ render_mime(int depth, struct message *msg, char *body, size_t bodylen)
char *charset = 0, *cs, *cse;
if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) {
charset = strndup(cs, cse-cs);
printf(" charset=\"%s\"", charset);
if (!Nflag)
printf(" charset=\"%s\"", charset);
setenv("PIPE_CHARSET", charset, 1);
free(charset);
}
setenv("PIPE_CONTENTTYPE", ct, 1);
char *messageid = blaze822_hdr(msg, "message-id");
if (messageid) {
setenv("MESSAGE_ID", messageid, 1);
}
char *output;
size_t outlen;
int e = filter(body, bodylen, cmd, &output, &outlen);
@ -225,7 +252,7 @@ render_mime(int depth, struct message *msg, char *body, size_t bodylen)
printf(" render=\"%s\" ---\n", cmd);
if (outlen) {
if (e == 0)
print_ascii(output, outlen);
print_ascii(output, outlen, 0);
else
fwrite(output, 1, outlen, stdout);
if (output[outlen-1] != '\n')
@ -244,14 +271,16 @@ render_mime(int depth, struct message *msg, char *body, size_t bodylen)
} else if (e >= 65 && e <= 80) { // choose N-64th part
struct message *imsg = 0;
int n = e - 64;
printf(" selector=\"%s\" part=%d ---\n", cmd, n);
if (!Nflag)
printf(" selector=\"%s\" part=%d ---\n", cmd, n);
while (blaze822_multipart(msg, &imsg)) {
if (--n == 0)
blaze822_walk_mime(imsg, depth+1, render_mime);
}
blaze822_free(imsg);
} else {
printf(" filter=\"%s\" FAILED status=%d", cmd, e);
if (!Nflag)
printf(" filter=\"%s\" FAILED status=%d", cmd, e);
free(output);
goto nofilter;
}
@ -269,11 +298,8 @@ nofilter:
char *charset = 0, *cs, *cse;
if (blaze822_mime_parameter(ct, "charset", &cs, &cse))
charset = strndup(cs, cse-cs);
if (!charset ||
strcasecmp(charset, "utf-8") == 0 ||
strcasecmp(charset, "utf8") == 0 ||
strcasecmp(charset, "us-ascii") == 0) {
print_ascii(body, bodylen);
if (probably_utf8(charset)) {
print_ascii(body, bodylen, 0);
if (bodylen > 0 && body[bodylen-1] != '\n')
putchar('\n');
} else {
@ -290,9 +316,10 @@ nofilter:
}
printf("\n");
} else if (strncmp(ct, "multipart/alternative", 21) == 0) {
choose_alternative(msg, depth);
r = MIME_PRUNE;
if (strcmp(Aflag, "all") != 0) {
choose_alternative(msg, depth);
r = MIME_PRUNE;
} // else default blaze822_mime_walk action
} else if (strncmp(ct, "multipart/", 10) == 0) {
; // default blaze822_mime_walk action
} else {
@ -309,13 +336,17 @@ nofilter:
static void
choose_alternative(struct message *msg, int depth)
{
int n = 1;
int n = 0;
int m = 0;
char *p = Aflag + strlen(Aflag);
struct message *imsg = 0;
while (blaze822_multipart(msg, &imsg)) {
m++;
if (blaze822_bodylen(imsg) == 0)
continue;
char *ict = blaze822_hdr(imsg, "content-type");
if (!ict)
ict = fallback_ct;
@ -332,6 +363,13 @@ choose_alternative(struct message *msg, int depth)
}
blaze822_free(imsg);
if (n == 0) {
// No part matched, use last part as per RFC1341 7.2.3
// "In general, choosing the best type means displaying
// only the LAST part that can be displayed."
n = m;
}
imsg = 0;
while (blaze822_multipart(msg, &imsg))
if (--n == 0)
@ -352,11 +390,8 @@ reply_mime(int depth, struct message *msg, char *body, size_t bodylen)
char *charset = 0, *cs, *cse;
if (blaze822_mime_parameter(ct, "charset", &cs, &cse))
charset = strndup(cs, cse-cs);
if (!charset ||
strcasecmp(charset, "utf-8") == 0 ||
strcasecmp(charset, "utf8") == 0 ||
strcasecmp(charset, "us-ascii") == 0)
print_ascii(body, bodylen);
if (probably_utf8(charset))
print_ascii(body, bodylen, 0);
else
print_u8recode(body, bodylen, charset);
reply_found++;
@ -467,7 +502,7 @@ extract_mime(int depth, struct message *msg, char *body, size_t bodylen)
fwrite(body, 1, bodylen, stdout);
} else { // extract all named attachments
if (filename) {
safe_u8putstr(filename, strlen(filename), stdout);
safe_u8putstr(filename, strlen(filename), 1, stdout);
printf("\n");
writefile(filename, body, bodylen);
}
@ -511,6 +546,7 @@ extract_mime(int depth, struct message *msg, char *body, size_t bodylen)
printf("%s\n", bufptr);
writefile(bufptr, body, bodylen);
}
extractcount++;
} else if (filename &&
fnmatch(a, filename, FNM_PATHNAME) == 0) {
// extract by name
@ -527,10 +563,11 @@ extract_mime(int depth, struct message *msg, char *body, size_t bodylen)
fwrite(body, 1, bodylen, stdout);
}
} else {
safe_u8putstr(filename, strlen(filename), stdout);
safe_u8putstr(filename, strlen(filename), 1, stdout);
printf("\n");
writefile(filename, body, bodylen);
}
extractcount++;
}
}
}
@ -541,6 +578,8 @@ extract_mime(int depth, struct message *msg, char *body, size_t bodylen)
void
extract_cb(char *file)
{
while (*file == ' ' || *file == '\t')
file++;
struct message *msg = blaze822_file(file);
if (!msg)
return;
@ -570,7 +609,7 @@ print_date_header(char *v)
}
printf("Date: ");
print_ascii(v, strlen(v));
print_ascii(v, strlen(v), 1);
time_t t = blaze822_date(v);
if (t == -1) {
@ -656,7 +695,7 @@ print_decode_header(char *h, char *v)
printhdr(h);
fputc(':', stdout);
fputc(' ', stdout);
print_ascii(d, strlen(d));
print_ascii(d, strlen(d), 1);
fputc('\n', stdout);
}
@ -723,7 +762,7 @@ show(char *file)
printf("\n");
if (rflag) { // raw body
print_ascii(blaze822_body(msg), blaze822_bodylen(msg));
print_ascii(blaze822_body(msg), blaze822_bodylen(msg), 0);
goto done;
}
@ -740,10 +779,11 @@ main(int argc, char *argv[])
pid_t pid1 = -1, pid2 = -1;
int c;
while ((c = getopt(argc, argv, "h:A:qrtFHLNx:O:Rn")) != -1)
while ((c = getopt(argc, argv, "h:A:BqrtFHLNx:O:Rn")) != -1)
switch (c) {
case 'h': hflag = optarg; break;
case 'A': Aflag = optarg; break;
case 'B': Bflag = 1; break;
case 'q': qflag = 1; break;
case 'r': rflag = 1; break;
case 'F': Fflag = 1; break;
@ -766,7 +806,9 @@ main(int argc, char *argv[])
exit(1);
}
if (!rflag && !Oflag && !Rflag)
xpledge("stdio rpath wpath cpath proc exec", NULL);
if (!rflag && !xflag && !Oflag && !Rflag && !tflag)
safe_output = 1;
if (safe_output && isatty(1)) {
@ -775,7 +817,7 @@ main(int argc, char *argv[])
if (!pg) {
pg = getenv("PAGER");
if (pg && strcmp(pg, "less") == 0) {
static char lesscmd[] = "less -RFXe";
static char lesscmd[] = "less -RFX";
pg = lesscmd;
}
}
@ -791,17 +833,22 @@ main(int argc, char *argv[])
}
if (xflag) { // extract
xpledge("stdio rpath wpath cpath", NULL);
extract(xflag, argc-optind, argv+optind, 0);
} else if (Oflag) { // extract to stdout
xpledge("stdio rpath", NULL);
extract(Oflag, argc-optind, argv+optind, 1);
} else if (tflag) { // list
xpledge("stdio rpath", NULL);
if (argc == optind && isatty(0))
blaze822_loop1(".", list);
else
blaze822_loop(argc-optind, argv+optind, list);
} else if (Rflag) { // render for reply
xpledge("stdio rpath", NULL);
blaze822_loop(argc-optind, argv+optind, reply);
} else { // show
/* XXX pledge: still r/w on the whole file-system + fork/exec */
if (!(qflag || rflag || Fflag)) {
char *f = getenv("MAILFILTER");
if (!f)
@ -814,7 +861,8 @@ main(int argc, char *argv[])
else
blaze822_loop(argc-optind, argv+optind, show);
if (!nflag) // don't set cur
blaze822_seq_setcur(newcur);
if (newcur)
blaze822_seq_setcur(newcur);
}
if (pid2 > 0)
@ -822,5 +870,10 @@ main(int argc, char *argv[])
if (pid1 > 0)
pipeclose(pid1);
if ((xflag || Oflag) && extractcount < argc-optind) {
fprintf(stderr, "mshow: could not extract all attachments\n");
return 1;
}
return 0;
}

@ -10,6 +10,7 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
struct mail {
char *file;
@ -218,8 +219,8 @@ unreadorder(const void *a, const void *b)
struct mail *ia = (struct mail *)a;
struct mail *ib = (struct mail *)b;
char *fa = strstr(ia->file, ":2,");
char *fb = strstr(ib->file, ":2,");
char *fa = strstr(ia->file, MAILDIR_COLON_SPEC_VER_COMMA);
char *fb = strstr(ib->file, MAILDIR_COLON_SPEC_VER_COMMA);
int unreada = fa ? !strchr(fa, 'S') : 0;
int unreadb = fb ? !strchr(fb, 'S') : 0;
@ -233,8 +234,8 @@ flaggedorder(const void *a, const void *b)
struct mail *ia = (struct mail *)a;
struct mail *ib = (struct mail *)b;
char *fa = strstr(ia->file, ":2,");
char *fb = strstr(ib->file, ":2,");
char *fa = strstr(ia->file, MAILDIR_COLON_SPEC_VER_COMMA);
char *fb = strstr(ib->file, MAILDIR_COLON_SPEC_VER_COMMA);
int unreada = fa ? !!strchr(fa, 'F') : 0;
int unreadb = fb ? !!strchr(fb, 'F') : 0;
@ -278,9 +279,9 @@ add(char *file)
int
order(const void *a, const void *b)
{
int i, r;
for (i = 0, r = 0; i < order_idx; i++) {
r = (sortorders[i])(a, b);
int i;
for (i = 0; i < order_idx; i++) {
int r = (sortorders[i])(a, b);
if (r != 0)
return r;
}
@ -316,8 +317,9 @@ main(int argc, char *argv[])
exit(1);
}
xpledge("stdio rpath", "");
mails = calloc(sizeof (struct mail), mailalloc);
mails = calloc(mailalloc, sizeof (struct mail));
if (!mails)
exit(-1);

@ -19,14 +19,16 @@
#include <unistd.h>
#include "blaze822.h"
#include "xpledge.h"
static int vflag;
static int pflag;
static int rflag;
static int optional;
struct container {
char *mid;
char *file;
struct message *msg;
time_t date;
struct container *parent;
struct container *child;
@ -64,7 +66,7 @@ mid(struct message *msg)
} else {
// invent new message-id for internal tracking
static long i;
char buf[32];
char buf[40];
snprintf(buf, sizeof buf, "thread%08ld@localhost", ++i);
return strdup(buf);
}
@ -74,13 +76,16 @@ struct container *
midcont(char *mid)
{
struct container key, **result;
if (!mid)
exit(111);
key.mid = mid;
if (!(result = tfind(&key, &mids, midorder))) {
struct container *c = malloc(sizeof (struct container));
if (!c)
exit(111);
c->mid = mid;
c->file = 0;
c->msg = 0;
c->date = -1;
c->optional = 0;
c->parent = c->child = c->next = 0;
@ -97,7 +102,6 @@ store_id(char *file, struct message *msg)
c = midcont(mid(msg));
c->file = strdup(file);
c->msg = msg;
c->optional = optional;
return c;
@ -232,6 +236,8 @@ out2:
c->child = 0;
}
}
blaze822_free(msg);
}
time_t
@ -281,7 +287,8 @@ void
find_roots()
{
top = malloc(sizeof (struct container));
top->msg = 0;
if (!top)
exit(111);
top->date = -1;
top->file = 0;
top->next = top->child = top->parent = 0;
@ -306,8 +313,8 @@ prune_tree(struct container *c, int depth)
// turn into child if we don't exist and only have a child
c->mid = c->child->mid;
c->file = c->child->file;
c->msg = c->child->msg;
c->date = c->child->date;
if (c->child->date > c->date)
c->date = c->child->date;
c->optional = c->child->optional;
c->child = c->child->child;
}
@ -340,6 +347,12 @@ dateorder(const void *a, const void *b)
return 0;
}
static int
reverse_dateorder(const void *a, const void *b)
{
return dateorder(b, a);
}
void
sort_tree(struct container *c, int depth)
{
@ -352,14 +365,15 @@ sort_tree(struct container *c, int depth)
if (i == 1) // no sort needed
return;
struct container **a = calloc(sizeof (struct container *), i);
struct container **a = calloc(i, sizeof (struct container *));
if (!a)
return;
for (r = c->child, i = 0; r; r = r->next, i++)
a[i] = r;
qsort(a, i, sizeof (struct container *), dateorder);
qsort(a, i, sizeof (struct container *),
rflag && depth < 0 ? reverse_dateorder : dateorder);
c->child = a[0];
for (j = 0; j+1 < i; j++)
@ -375,7 +389,8 @@ print_tree(struct container *c, int depth)
{
do {
// skip toplevel threads when they are unresolved or all optional
if (depth <= 1 &&
// (or when -p is given, skip those subthreads)
if ((depth <= 1 || pflag) &&
(c->optional || !c->file) &&
(!c->child || alloptional(c->child)))
continue;
@ -399,25 +414,31 @@ int
main(int argc, char *argv[])
{
int c;
long i;
optional = 1;
while ((c = getopt(argc, argv, "S:v")) != -1)
xpledge("stdio rpath", "");
while ((c = getopt(argc, argv, "S:prv")) != -1)
switch (c) {
case 'S': blaze822_loop1(optarg, thread); break;
case 'v': vflag = 1; break;
case 'p': pflag = 1; break;
case 'r': rflag = 1; break;
default:
fprintf(stderr, "Usage: mthread [-v] [-S dir] [msgs...]\n");
fprintf(stderr, "Usage: mthread [-vpr] [-S dir] [msgs...]\n");
exit(1);
}
optional = 0;
if (argc == optind && isatty(0))
i = blaze822_loop1(":", thread);
blaze822_loop1(":", thread);
else
i = blaze822_loop(argc-optind, argv+optind, thread);
blaze822_loop(argc-optind, argv+optind, thread);
// the tree of all toplevel threads has depth -1,
// so toplevel threads have depth 0.
find_roots();
if (!vflag)
@ -425,7 +446,5 @@ main(int argc, char *argv[])
sort_tree(top, -1);
print_tree(top, -1);
fprintf(stderr, "%ld mails threaded\n", i);
return 0;
}

@ -1,86 +1,28 @@
#include <time.h>
// from musl@1cc81f5cb, slightly tweaked
static long long
__year_to_secs(long long year, int *is_leap)
time_t
mytimegm(struct tm *tm)
{
if (year-2ULL <= 136) {
int y = year;
int leaps = (y-68)>>2;
if (!((y-68)&3)) {
leaps--;
if (is_leap) *is_leap = 1;
} else if (is_leap) *is_leap = 0;
return 31536000*(y-70) + 86400*leaps;
}
int cycles, centuries, leaps, rem;
cycles = (year-100) / 400;
rem = (year-100) % 400;
if (rem < 0) {
cycles--;
rem += 400;
}
if (!rem) {
*is_leap = 1;
centuries = 0;
leaps = 0;
} else {
if (rem >= 200) {
if (rem >= 300) centuries = 3, rem -= 300;
else centuries = 2, rem -= 200;
} else {
if (rem >= 100) centuries = 1, rem -= 100;
else centuries = 0;
}
if (!rem) {
*is_leap = 0;
leaps = 0;
} else {
leaps = rem / 4U;
rem %= 4U;
*is_leap = !rem;
}
}
leaps += 97*cycles + 24*centuries - *is_leap;
int mon = tm->tm_mon + 1 - 2; /* put March first, Feb last */
long long year = tm->tm_year + 1900;
return (year-100) * 31536000LL + leaps * 86400LL + 946684800 + 86400;
}
static int
__month_to_secs(int month, int is_leap)
{
static const int secs_through_month[] = {
0, 31*86400, 59*86400, 90*86400,
120*86400, 151*86400, 181*86400, 212*86400,
243*86400, 273*86400, 304*86400, 334*86400 };
int t = secs_through_month[month];
if (is_leap && month >= 2) t+=86400;
return t;
}
time_t tm_to_secs(const struct tm *tm)
{
int is_leap;
long long year = tm->tm_year;
int month = tm->tm_mon;
if (month >= 12 || month < 0) {
int adj = month / 12;
month %= 12;
if (month < 0) {
if (mon <= 0 || mon >= 12) {
int adj = mon / 12;
mon %= 12;
if (mon <= 0) {
adj--;
month += 12;
mon += 12;
}
year += adj;
}
long long t = __year_to_secs(year, &is_leap);
t += __month_to_secs(month, is_leap);
t += 86400LL * (tm->tm_mday-1);
t += 3600LL * tm->tm_hour;
t += 60LL * tm->tm_min;
t += tm->tm_sec;
time_t t = 0;
t += tm->tm_sec;
t += 60 * tm->tm_min;
t += 60*60 * tm->tm_hour;
t += 24*60*60 * (tm->tm_mday - 1);
t += 24*60*60 * (367*mon/12);
t += 24*60*60 * (year/4 - year/100 + year/400);
t += 24*60*60 * (365*year - 719498L);
return t;
}

@ -86,6 +86,8 @@ blaze822_mime_parameter(char *s, char *name, char **starto, char **stopo)
while (*s) {
while (iswsp(*s))
s++;
if (!*s)
return 0;
if (strncasecmp(s, name, namelen) == 0 && s[namelen] == '=') {
s += namelen + 1;
break;
@ -114,6 +116,26 @@ blaze822_mime_parameter(char *s, char *name, char **starto, char **stopo)
return 1;
}
// like mymemmem but check the match is followed by \r, \n or -.
static char *
mymemmemnl(const char *h0, size_t k, const char *n0, size_t l)
{
char *r;
while (k && (r = mymemmem(h0, k, n0, l))) {
if (r + l < h0 + k && // check if r[l] safe to access
(r[l] == '\r' || r[l] == '\n' || r[l] == '-'))
return r;
else {
// skip over this match
k -= (r - h0) + 1;
h0 = r + 1;
}
}
return 0;
}
int
blaze822_multipart(struct message *msg, struct message **imsg)
{
@ -144,7 +166,7 @@ blaze822_multipart(struct message *msg, struct message **imsg)
else
prevpart = msg->body;
char *part = mymemmem(prevpart, msg->bodyend - prevpart, mboundary, boundarylen);
char *part = mymemmemnl(prevpart, msg->bodyend - prevpart, mboundary, boundarylen);
if (!part)
return 0;
@ -158,8 +180,10 @@ blaze822_multipart(struct message *msg, struct message **imsg)
else
return 0; // XXX error condition?
char *nextpart = mymemmem(part, msg->bodyend - part, mboundary, boundarylen);
char *nextpart = mymemmemnl(part, msg->bodyend - part, mboundary, boundarylen);
if (!nextpart)
nextpart = msg->bodyend; // no boundary found, take all
else if (nextpart == part) // invalid empty MIME part
return 0; // XXX error condition
if (*(nextpart-1) == '\n')
@ -180,6 +204,9 @@ blaze822_walk_mime(struct message *msg, int depth, blaze822_mime_callback visit)
blaze822_mime_action r = MIME_CONTINUE;
if (depth > 64)
return MIME_PRUNE;
if (blaze822_mime_body(msg, &ct, &body, &bodylen, &bodychunk)) {
r = visit(depth, msg, body, bodylen);

@ -82,16 +82,25 @@ blaze822_decode_b64(char *s, char *e, char **deco, size_t *decleno)
*deco = buf;
while (s + 4 <= e) {
while (s < e && isfws((unsigned char)*s))
s++;
if (s >= e)
break;
uint32_t v = 0;
unsigned char t = 0;
unsigned char c0 = s[0], c1 = s[1], c2 = s[2], c3 = s[3];
s += 4;
#define SKIP_WS \
while (s < e && isfws((unsigned char)*s)) \
s++; \
if (s >= e) \
break;
SKIP_WS;
unsigned char c0 = *s++;
SKIP_WS;
unsigned char c1 = *s++;
SKIP_WS;
unsigned char c2 = *s++;
SKIP_WS;
unsigned char c3 = *s++;
#undef SKIP_WS
if ((c0 | c1 | c2 | c3) > 127)
goto error;
@ -130,6 +139,10 @@ blaze822_decode_rfc2047(char *dst, char *src, size_t dlen, char *tgtenc)
iconv_t ic = (iconv_t)-1;
char *srcenc = 0;
// need space for trailing nul
if (dlen-- == 0)
return 0;
char *startdst = dst;
size_t startdlen = dlen;
@ -188,7 +201,8 @@ blaze822_decode_rfc2047(char *dst, char *src, size_t dlen, char *tgtenc)
if (ic == (iconv_t)-1)
goto nocode;
char enc = lc(*e++);
char enc = lc(*e);
e++;
if (*e++ != '?')
goto nocode;
char *start = e;

@ -54,8 +54,10 @@ found_extended:
if (!srcenc)
return 0;
sbuf = strchr(sbuf+1, '\'');
if (!sbuf)
if (!sbuf) {
free(srcenc);
return 0;
}
sbuf++;
}
while (sbuf < ebuf && dst < dstend) {

@ -4,7 +4,7 @@
#include "u8decode.h"
void
safe_u8putstr(char *s0, size_t l, FILE *stream)
safe_u8putstr(char *s0, size_t l, int oneline, FILE *stream)
{
// tty-safe output of s, with relaxed utf-8 semantics:
// - C0 and C1 are displayed as escape sequences
@ -34,8 +34,12 @@ safe_u8putstr(char *s0, size_t l, FILE *stream)
fputc(0xc0 | (*s >> 6), stream);
fputc(0x80 | (*s & 0x3f), stream);
}
} else if (c < 32 &&
*s != ' ' && *s != '\t' && *s != '\n' && *s != '\r') {
} else if (c < 32 &&
*s != ' ' && *s != '\t' &&
(oneline || (*s != '\n' && *s != '\r'))) {
// NUL
if (l == 0)
l = 1;
// C0
fputc(0xe2, stream);
fputc(0x90, stream);

90
seq.c

@ -110,15 +110,14 @@ blaze822_seq_load(char *map)
char *e = t;
while (s < e && isfws(*(e-1)))
e--;
// printf("{%.*s}\n", e-s, s);
char *f = strndup(s, e-s);
if (!f)
return -1;
struct msgnum key, **result;
struct msgnum key;
key.file = f;
if (!(result = tfind(&key, &msgnums, msgnumorder))) {
if (!tfind(&key, &msgnums, msgnumorder)) {
struct msgnum *c = malloc(sizeof (struct msgnum));
c->file = f;
c->pos = line;
@ -152,6 +151,9 @@ blaze822_seq_cur(void)
int
blaze822_seq_setcur(char *s)
{
if (strcmp(s, "/dev/stdin") == 0)
return 0;
char *override = getenv("MAILDOT");
if (override)
return 0;
@ -181,7 +183,7 @@ parse_relnum(char *a, long cur, long start, long last, long *out)
if (strcmp(a, "+") == 0)
a = ".+1";
else if (strcmp(a, "-") == 0)
else if (strcmp(a, ".-") == 0)
a = ".-1";
else if (strcmp(a, ".") == 0)
a = ".+0";
@ -394,7 +396,6 @@ find_cur(char *map, struct blaze822_seq_iter *iter)
while (*s == ' ' || *s == '\t')
s++;
// printf("{%.*s}\n", t-s, s);
iter->lines++;
if (!cur && curfile &&
strncmp(s, curfile, strlen(curfile)) == 0 &&
@ -426,7 +427,7 @@ blaze822_seq_next(char *map, char *range, struct blaze822_seq_iter *iter)
if (!iter->lines) // count total lines
find_cur(map, iter);
if (!iter->start) {
if (!iter->s) {
int ret = parse_range(map, range, &iter->start, &iter->stop,
iter->cur, iter->lines);
if (ret == 1) {
@ -467,44 +468,49 @@ blaze822_seq_next(char *map, char *range, struct blaze822_seq_iter *iter)
return r;
}
int mystrverscmp(const char *, const char *);
static int
mailsort(const struct dirent **a, const struct dirent **b)
{
return mystrverscmp((*a)->d_name, (*b)->d_name);
}
static long
iterdir(char *dir, void (*cb)(char *))
{
DIR *fd, *fd2;
struct dirent *d;
struct dirent **namelist;
long i = 0;
int n;
char sub[PATH_MAX];
snprintf(sub, sizeof sub, "%s/cur", dir);
fd = opendir(dir);
if (!fd) {
char *m = "/cur";
n = scandir(sub, &namelist, 0, mailsort);
if (n == -1 && (errno == ENOENT || errno == ENOTDIR)) {
m = "";
n = scandir(dir, &namelist, 0, mailsort);
}
if (n == -1) {
if (errno == ENOTDIR)
cb(dir);
return 1;
}
char sub[PATH_MAX];
snprintf(sub, sizeof sub, "%s/cur", dir);
fd2 = opendir(sub);
if (fd2) {
closedir(fd);
fd = fd2;
}
while ((d = readdir(fd))) {
#if defined(DT_REG) && defined(DT_UNKNOWN)
if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN)
continue;
#endif
if (d->d_name[0] == '.')
continue;
if (fd2)
snprintf(sub, sizeof sub, "%s/cur/%s", dir, d->d_name);
else
snprintf(sub, sizeof sub, "%s/%s", dir, d->d_name);
cb(sub);
i++;
long i = 0;
for (i = 0; i < n; i++) {
if (namelist[i]->d_name[0] != '.' &&
MAIL_DT(namelist[i]->d_type)) {
snprintf(sub, sizeof sub, "%s%s/%s",
dir, m, namelist[i]->d_name);
cb(sub);
}
free(namelist[i]);
}
closedir(fd);
free(namelist);
return i;
}
@ -528,13 +534,26 @@ blaze822_loop(int argc, char *argv[], void (*cb)(char *))
return i;
}
char *map = blaze822_seq_open(0);
struct blaze822_seq_iter iter = { 0 };
char *map = 0;
int map_opened = 0;
int j = 0;
for (i = 0; i < argc; i++) {
if (strchr(argv[i], '/')) { // a file name
j += iterdir(argv[i], cb);
} else if (strcmp(argv[i], "-") == 0) {
if (isatty(0)) {
fprintf(stderr, "mblaze: warning: - now means "
"read mail text from standard input, "
"use .- to refer to previous mail\n");
}
cb("/dev/stdin");
j++;
} else {
if (!map_opened) {
map = blaze822_seq_open(0);
map_opened = 1;
}
struct blaze822_seq_iter iter = { 0 };
while ((line = blaze822_seq_next(map, argv[i], &iter))) {
cb(line);
free(line);
@ -542,6 +561,7 @@ blaze822_loop(int argc, char *argv[], void (*cb)(char *))
}
}
}
free(map);
return j;
}

@ -24,11 +24,6 @@ slurp(char *filename, char **bufo, off_t *leno)
r = errno;
goto out;
}
if (st.st_size == 0) {
*bufo = "";
*leno = 0;
return 0;
}
*bufo = malloc(st.st_size + 1);
if (!*bufo) {
r = ENOMEM;

@ -1,7 +1,8 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 4
plan 21
cat <<EOF >tmp
References: <aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@a> <bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb@b> <ccccccccccccccccccccccccccccccc@c>
@ -9,9 +10,143 @@ References: <aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@a> <bbbbbbbbbbbbbbbbbbbbbbbbbbbb
Body
EOF
# https://github.com/chneukirchen/mblaze/issues/20
# https://github.com/leahneukirchen/mblaze/issues/20
check 'mime -r runs' 'mmime -r <tmp >tmp2'
check 'no overlong lines' 'awk "{if(length(\$0)>=80)exit 1}" <tmp2'
check 'no QP when unecessary' ! grep -qF "=?" tmp2
check 'no further mime necessary' 'mmime -c <tmp2'
cat <<EOF >tmp2
Subject: inclusion test
#message/rfc822 $PWD/tmp
EOF
check 'include works' 'mmime <tmp2 | grep Body'
check 'include sets filename' 'mmime <tmp2 | grep filename=tmp'
cat <<EOF >tmp2
Subject: inclusion test no filename
#message/rfc822 $PWD/tmp>
EOF
check 'include works, overriding filename' 'mmime <tmp2 | grep Disposition | grep -v filename=tmp'
cat <<EOF >tmp2
Subject: inclusion test with other disposition
#message/rfc822#inline $PWD/tmp>
EOF
check 'include works, overriding filename' 'mmime <tmp2 | grep Disposition | grep inline'
cat <<EOF >tmp2
Subject: message with content-type
Content-Type: text/plain; format=flowed
This message has format-flowed.
EOF
check 'content-type is respected if found in input' 'mmime -r <tmp2 |grep format=flowed'
cat <<EOF >tmp2
Subject: message with content-transfer-encoding
Content-Transfer-Encoding: quoted-printable
This message has already encoded. f=C3=B6=C3=B6.
EOF
check 'content-transfer-encoding is respected if found in input' 'mmime -r <tmp2 |grep f=C3=B6=C3=B6'
cat <<EOF >tmp2
Subject: message with content-type
Content-Type: text/plain; format=flowed
This message has format-flowed.
#message/rfc822 $PWD/tmp
This part too.
EOF
check 'content-type is respected if found in input, for multipart/mixed' 'mmime <tmp2 |grep format=flowed'
cat <<EOF >tmp2
Subject: message with content-transfer-encoding
Content-Transfer-Encoding: Quoted-Printable
This message has already encoded. f=C3=B6=C3=B6.
#message/rfc822 $PWD/tmp
This part too.
EOF
check 'content-transfer-encoding is respected if found in input, for multipart/mixed' 'mmime <tmp2 |grep f=C3=B6=C3=B6'
cat <<EOF >tmp2
From: Kerstin Krüger <krueger@example.com>
Body.
EOF
check 'non-ASCII is encoded as UTF-8' 'mmime <tmp2 | grep "UTF-8.*=C3=BC"'
cat <<EOF >tmp2
From: "Krüger, Kerstin" <krueger@example.com>
Body.
EOF
check 'non-ASCII quoted-strings are encoded as one encoded-word' 'mmime <tmp2 | grep "UTF-8.*=2C_"'
check 'non-ASCII quoted-strings are encoded without quotes' 'mmime <tmp2 | grep -v "=22"'
cat <<EOF >tmp2
From: "kerstin krueger"@example.com
Body.
EOF
check 'non-encoded quoted-strings are kept correctly' 'mmime <tmp2 | grep \"@'
cat <<EOF >tmp2
Subject: inclusion without further content
#message/rfc822#inline $PWD/tmp
EOF
check 'no empty parts are generated after inclusion lines' '! mmime <tmp2 | mshow -t - | grep -q size=0'
cat <<EOF >tmp2
Subject: Strict mode
Body with lines longer than 78 characters
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
EOF
check 'body lines longer than 78 characters needs MIME formatting' '! mmime -c <tmp2'
check 'MBLAZE_RELAXED_MIME allows body lines longer than 78 characters' 'MBLAZE_RELAXED_MIME= mmime -c <tmp2'
cat <<EOF >tmp2
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1710013705;
s=s1; d=tuta.io;
h=From:From:To:To:Subject:Subject:Content-Description:Content-ID:Content-Type:Content-Type:Content-Transfer-Encoding:Cc:Date:Date:In-Reply-To:MIME-Version:MIME-Version:Message-ID:Message-ID:Reply-To:References:Sender; bh=Jr8DQlZ7RwdJv94m7ZT/v+cv/WFsgjxpMRsHvnNfgGY=;
b=NXRl0YxYtVsWrR8v7tVKnvsnCSrBqqaf2h3m8OVGlzG0OqMqGcWg7fVk6x4nTYV+
+05afZrGfIwcfFwIe/LLvT0d3/12t4+cs/FQvmEcFUN+n2buQwt5sn8f76UUlvNMrGz
Xbq8HAdwhA364yWABa7DrF1EGysC8bEDJcCtSs/Wz3TL2A/MEeItEF+VijtgWUwoOwn
rFKkCg5Df+IOd4gEBS/KYLbzcMB1dvqy+ut2LA2+NZpzJQPgbJzWAYieT9KYgoS+hKS
5FfknNT+hKZz18IBEWH1UWbI+CcLRR8Sr80x2DZUKq8ryC5RmV5/uAc5Up03b/KZGRU
NsiBAQCx3w==
EOF
check 'header words longer then 78 characters do not cause empty lines (#257)' 'mmime < tmp2 | awk "NR < 5 && length == 0 { exit 1 }"'
check 'header words longer then 78 characters are printed on their own line' 'mmime < tmp2 |grep "^[ ]*h=From.*Sender;$"'

@ -0,0 +1,26 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 9
cat <<EOF >tmp
Header: foo
Header2: bar
Header-Three: quux
Header_Four: ding
Body
EOF
check_same 'Header' 'mhdr -h Header ./tmp' 'echo foo'
check_same 'Header2' 'mhdr -h Header2 ./tmp' 'echo bar'
check_same 'Header-Three' 'mhdr -h Header-Three ./tmp' 'echo quux'
check_same 'Header_Four' 'mhdr -h Header_Four ./tmp' 'echo ding'
check_same 'header' 'mhdr -h header ./tmp' 'echo foo'
check_same 'header2' 'mhdr -h header2 ./tmp' 'echo bar'
check_same 'header-Three' 'mhdr -h header-Three ./tmp' 'echo quux'
check_same 'header_Four' 'mhdr -h header_Four ./tmp' 'echo ding'
check 'issue 235' 'mhdr ./tmp |grep -i header_four'

@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 10
plan 11
rm -rf test.dir
mkdir test.dir
@ -12,6 +12,7 @@ mkdir -p "inbox/cur"
cat <<! | mmime >"inbox/cur/1:2,"
From: Rajwinder Kaur <rajwinder@example.com>
Obs-Test: Rajwinder Kaur <@example.org:rajwinder@example.com>
Subject: namaste
Date: Thu, 30 Mar 2017 15:42:05 +0200
Message-Id: <EOH1F3NUOY.2KBVMHSBFATNY@example.org>
@ -19,7 +20,7 @@ Message-Id: <EOH1F3NUOY.2KBVMHSBFATNY@example.org>
!
cat <<! | mmime >"inbox/cur/2:2,"
From: имярек <имярек@example.com>, Rajwinder Kaur <rajwinder@example.com>
From: имярек <namearek@example.com>, Rajwinder Kaur <rajwinder@example.com>
Subject: Здравствуйте
Date: Thu, 30 Mar 2017 15:42:10 +0200
Message-Id: <EOH8EKEA1Z.2YAIFN5KVCO6Z@example.org>
@ -66,9 +67,10 @@ export MAILSEQ=seq
check_same 'from one' 'maddr 1' 'echo "Rajwinder Kaur <rajwinder@example.com>"'
check_same 'from address' 'maddr -a 1' 'echo "rajwinder@example.com"'
check_same 'from one' 'maddr -h obs-test 1' 'echo "Rajwinder Kaur <rajwinder@example.com>"'
cat <<! >expect
имярек <имярек@example.com>
имярек <namearek@example.com>
Rajwinder Kaur <rajwinder@example.com>
!
check_same 'from two' 'maddr 2' 'cat expect'
@ -78,7 +80,7 @@ check_test 'from name only' -eq 0 'maddr 4 | wc -l'
check_same 'specific header' 'maddr -h foo 4' 'echo "Perico de los palotes <palotes@example.com>"'
check_same 'long addr' 'maddr -h long 4' 'echo "heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelloooooooooooooooooo@woooooooooooooooooooooooooooooooooooooorld.com"'
check_same 'decode iso8859' 'maddr -h DecodeISO8859 5' 'echo "Keld Jørn Simonsen <keld@dkuug.dk>"'
check_same 'decode long iso8859' 'maddr -h DecodeLongISO8859 5' 'echo "If you can read this you understand the example. z a b <foo@example.com>"'
check_same 'decode long iso8859' 'maddr -h DecodeLongISO8859 5' 'echo "\"If you can read this you understand the example. z a b\" <foo@example.com>"'
check_same 'decode utf8' 'maddr -h DecodeUTF8 5' 'echo "zz <bar@example.com>"'
)

@ -1,10 +1,10 @@
#!/bin/sh
cd ${0%/*}
. ./lib.sh
plan 26
plan 33
check_addr() {
printf "From: %s\n" "$1" | check_test "parse $1" = "$2" "maddr /dev/stdin"
printf "From: %s\n" "$1" | check_test "parse $1" = "$2" "maddr -"
}
check_addr 'foo@example.org' 'foo@example.org'
@ -14,11 +14,11 @@ check_addr 'bar quux <foo@example.org>' 'bar quux <foo@example.org>'
check_addr 'bar quux <foo@example.org>' 'bar quux <foo@example.org>'
check_addr '<foo@example.org> (bar)' 'bar <foo@example.org>'
check_addr '"Real Name" <foo@example.org>' 'Real Name <foo@example.org>'
check_addr '"Dr. Real Name" <foo@example.org>' 'Dr. Real Name <foo@example.org>'
check_addr '"Real Name" (show this) <foo@example.org>' 'Real Name (show this) <foo@example.org>'
check_addr '"Real Name" <foo@example.org> (ignore this)' 'Real Name (ignore this) <foo@example.org>'
check_addr '"Dr. Real Name" <foo@example.org>' '"Dr. Real Name" <foo@example.org>'
check_addr '"Real Name" (show this) <foo@example.org>' '"Real Name (show this)" <foo@example.org>'
check_addr '"Real Name" <foo@example.org> (ignore this)' '"Real Name (ignore this)" <foo@example.org>'
check_addr '(nested (comments mail@here ) heh) "yep (yap)" <foo@example.org>' 'yep (yap) <foo@example.org>'
check_addr '(nested (comments mail@here ) heh) "yep (yap)" <foo@example.org>' '"yep (yap)" <foo@example.org>'
check_addr 'ignore-this: <foo@example.org>' 'foo@example.org'
check_addr 'ignore-this : <foo@example.org>' 'foo@example.org'
@ -39,4 +39,13 @@ check_addr 'foo@[::1] (ipv6)' 'ipv6 <foo@[::1]>'
# invalid addresses
check_addr '<Foo Bar <foobar@qux.com>' 'foobar@qux.com'
check_addr '"abc@def"@ghi' ''
check_addr '"abc@def"@ghi' ''
check_addr '"foo@" <bar.com foo@bar.com>' '"foo@" <bar.comfoo@bar.com>'
check_addr 'test."test"@example.org' 'test.test@example.org'
check_addr '<test."test"@example.org>' 'test.test@example.org'
check_addr 'test"test"@example.org' 'testtest@example.org'
check_addr '<test"test"@example.org>' 'testtest@example.org'
check_addr 'foo<bar@example.org>' 'foo <bar@example.org>'
check_addr 'xxxxxxxxx a"test"@example.org' "xxxxxxxxx <atest@example.org>"

@ -0,0 +1,41 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 2
# Nested MIME where the outer boundary is a prefix of the inner boundary
cat <<EOF >tmp
MIME-Version: 1.0
Content-type: multipart/mixed; charset=iso-8859-1;
boundary="_xec5AqfRYxfhARmklHx"
--_xec5AqfRYxfhARmklHx
Content-type: Multipart/alternative; charset=iso-8859-1;
boundary="_xec5AqfRYxfhARmklHx8"
--_xec5AqfRYxfhARmklHx8
Content-type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
foo
--_xec5AqfRYxfhARmklHx8
Content-type: text/html; charset=iso-8859-1
Content-Transfer-Encoding: Quoted-printable
Content-Disposition: inline
bar
--_xec5AqfRYxfhARmklHx8--
--_xec5AqfRYxfhARmklHx
Content-Type: application/zip
Content-Transfer-Encoding: Base64
quux
--_xec5AqfRYxfhARmklHx--
EOF
check 'nested mail has 5 attachments' 'mshow -t ./tmp | wc -l | grep 6'
check 'nested mail has text/html attachment' 'mshow -t ./tmp | grep text/html'

@ -0,0 +1,53 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 3
# Mail with \n\n and \r\n\r\n
cr=$(printf '\r')
cat <<EOF >tmp
Content-Type: multipart/form-data; boundary=------------------------55a586f81559face$cr
$cr
--------------------------55a586f81559face$cr
Content-Disposition: form-data; name="a"; filename="foo"$cr
Content-Type: application/octet-stream$cr
$cr
foo$cr
previously there are two NL$cr
$cr
--------------------------55a586f81559face$cr
Content-Disposition: form-data; name="a"; filename="bar"$cr
Content-Type: application/octet-stream$cr
$cr
bar$cr
$cr
--------------------------55a586f81559face--$cr
EOF
check 'mail has 3 attachments' 'mshow -t ./tmp | wc -l | grep 4'
check 'mail attachment foo has size 35' 'mshow -t ./tmp | grep size=35.*name=\"foo\"'
# Mail with linebreaks in base64 quartets
cat <<EOF >tmp
Subject: base64
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----_=_2f8f1e2243b55f8618eaf0d9_=_"
This is a multipart message in MIME format.
------_=_2f8f1e2243b55f8618eaf0d9_=_
Content-Disposition: attachment; filename=base64
Content-Type: application/binary
Content-Transfer-Encoding: base64
dGhp
cyBpc
yBzb21
lIGJhc2
U2NAo=
------_=_2f8f1e2243b55f8618eaf0d9_=_--
EOF
check 'mail decodes correctly' 'mshow -O ./tmp 2 | grep -q "this is some base64"'

@ -0,0 +1,26 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 2
cat <<EOF >tmp.1
Subject: message 1
This is message 1.
EOF
cat <<EOF >tmp.2
Subject: message 2
This is message 2. It has a trailing empty line.
EOF
printf >tmp.3 'Subject: message 3
This is message 3. It has a no trailing newline, oops.'
mexport ./tmp.1 ./tmp.2 ./tmp.3 >./tmp.mbox
check 'generated mbox has 16 lines' 'cat ./tmp.mbox | wc -l | grep 16'
check 'generated mbox has 7 empty lines' 'grep -c "^$" ./tmp.mbox | grep 7'

@ -0,0 +1,85 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 9
rm -rf test.dir
mkdir test.dir
cd test.dir
mmkdir inbox
cat <<EOF >tmp.1
Subject: message 1
This is message 1.
EOF
cat <<EOF >tmp.2
Subject: message 2
This is message 2. It has a trailing empty line.
EOF
printf >tmp.3 'Subject: message 3
This is message 3. It has a no trailing newline, oops.'
cat <<EOF >tmp.4
Subject: message 4
This is message 4. It has multiple trailing empty lines.
EOF
mexport ./tmp.1 | mdeliver -M inbox/
check 'message 1 is delivered verbatim via mbox' 'cmp ./tmp.1 ./inbox/new/*:2,'
rm -f ./inbox/new/*
mexport ./tmp.2 | mdeliver -M inbox/
check 'message 2 is delivered verbatim via mbox' 'cmp ./tmp.2 ./inbox/new/*:2,'
rm -f ./inbox/new/*
mexport ./tmp.3 | mdeliver -M inbox/
check 'message 3 gets a newline via mbox' 'awk 1 ./tmp.3 | cmp - ./inbox/new/*:2,'
rm -f ./inbox/new/*
mexport ./tmp.4 | mdeliver -M inbox/
check 'message 4 gets delivered verbatim via mbox' 'cmp ./tmp.4 ./inbox/new/*:2,'
rm -f ./inbox/new/*
mdeliver inbox/ <./tmp.1
check 'message 1 is delivered verbatim via stdin' 'cmp ./tmp.1 ./inbox/new/*:2,'
rm -f ./inbox/new/*
mdeliver inbox/ <./tmp.2
check 'message 2 is delivered verbatim via stdin' 'cmp ./tmp.2 ./inbox/new/*:2,'
rm -f ./inbox/new/*
mdeliver inbox/ <./tmp.3
check 'message 3 gets a newline via stdin' 'cmp ./tmp.3 ./inbox/new/*:2,'
rm -f ./inbox/new/*
mdeliver inbox/ <./tmp.4
check 'message 4 is delivered verbatim via stdin' 'cmp ./tmp.4 ./inbox/new/*:2,'
rm -f ./inbox/new/*
cat <<EOF >tmp.mbox
From nobody Thu Jan 1 00:59:59 1970
Subject: message 1
This is message 1.
From nobody Thu Jan 1 00:59:59 1970
Subject: message 2
This is message 2.
EOF
mdeliver -M inbox/ <./tmp.mbox
check 'mdeliver -M is tolerant with missing empty lines' 'ls inbox/new | wc -l | grep 2'

@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 13
plan 29
rm -rf test.dir
mkdir test.dir
@ -19,14 +19,14 @@ touch "inbox/cur/7:2,SR"
touch "inbox/cur/8:2,SF"
touch "inbox/cur/9:2,"
check_same 'flag trashed' 'mlist inbox | mpick :T' 'mlist -T inbox'
check_same 'flag not trashed' 'mlist inbox | mpick -t "!trashed"' 'mlist -t inbox'
check_same 'flag seen' 'mlist inbox | mpick :S' 'mlist -S inbox'
check_same 'flag trashed' 'mlist inbox | mpick -t trashed' 'mlist -T inbox'
check_same 'flag not trashed' 'mlist inbox | mpick -t !trashed' 'mlist -t inbox'
check_same 'flag seen' 'mlist inbox | mpick -t seen' 'mlist -S inbox'
check_same 'flag not seen' 'mlist inbox | mpick -t !seen' 'mlist -s inbox'
check_same 'flag seen and trashed' 'mlist inbox | mpick :S :T' 'mlist -ST inbox'
check_same 'flag seen and not trashed' 'mlist inbox | mpick -t "seen && !trashed"' 'mlist -St inbox'
check_same 'flag replied' 'mlist inbox | mpick :R' 'mlist -R inbox'
check_same 'flag forwarded' 'mlist inbox | mpick :F' 'mlist -F inbox'
check_same 'flag seen and trashed' 'mlist inbox | mpick -t seen -t trashed' 'mlist -ST inbox'
check_same 'flag seen and not trashed' 'mlist inbox | mpick -t seen -t !trashed' 'mlist -St inbox'
check_same 'flag replied' 'mlist inbox | mpick -t replied' 'mlist -R inbox'
check_same 'flag forwarded' 'mlist inbox | mpick -t passed' 'mlist -P inbox'
cat <<! | mmime >"inbox/cur/1:2,S"
@ -61,10 +61,75 @@ Greetings
#application/pdf ../../mshow
!
check 'search subject' 'mlist inbox | mpick /wow | grep -q inbox/cur/9:2,'
check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l'
check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l'
cat <<! >shebang
#!$(command -v mpick) -F
from.addr == "peter@example.org" && from.disp == "Peter Example"
!
chmod +x shebang
check 'search subject' 'mlist inbox | mpick -t "subject =~~ \"wow\"" | grep -q inbox/cur/9:2,'
check_test 'search addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l'
check_test 'search name' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l'
check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"pdf\"" | wc -l'
check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l'
check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l'
check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l'
check_test 'shebang' -eq 2 'mlist inbox | ./shebang | wc -l'
check_test 'ternary' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\" ? print : skip" | wc -l'
check_same 'pipe command' 'mlist inbox | mpick -t "print |\"cat -n\" && skip"' 'mlist inbox | cat -n'
check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox'
check_same 'unknown ident' 'mlist inbox | mpick -t "let x = x in x" 2>&1' \
"echo \"mpick: parse error: argv:1:9: unknown expression at 'x in x'\""
cat <<! >expr
let foo = from.addr == "peter@example.org"
let bar = from.disp == "Peter Example"
# random comment
in
foo && bar # another comment
!
check_test 'let expression' -eq 2 'mlist inbox | mpick -F ./expr | wc -l'
cat <<! >expr
let foo = from.addr == "peter@example.org"
let bar = from.disp == "Peter Example"
# random comment
in
foo && foo
!
check_test 'let expression double free' -eq 2 'mlist inbox | mpick -F ./expr | wc -l'
cat <<! >expr
let foo =
let bar = from.disp == "Peter Example"
in
bar && from.addr == "peter@example.org"
in
foo
!
check_test 'let expression nested' -eq 2 'mlist inbox | mpick -F ./expr | wc -l'
cat <<! >expr
let foo = from.addr == "peter@example.org"
let bar = foo && subject =~ "wow"
in
bar
!
check_test 'let scoping' -eq 1 'mlist inbox | mpick -F ./expr | wc -l'
cat <<! >expr
let foo = from.addr == "peter@example.org"
let bar = from.disp == "Peter Example"
in
foo |"sed ""s/^/1:&/""" && bar |"sed ""s/^/2:&/""" && skip
!
check_test 'multi redir' -eq 4 'mlist inbox | mpick -F ./expr | wc -l'
check_test 'multi redir prefixes' -eq 2 'mlist inbox | mpick -F ./expr | cut -d: -f1 | sort -u | wc -l'
)
check 'environment variable' 'mlist inbox | MSGID="<EOH1F3NUOY.2KBVMHSBFATNY@example.org>" mpick -t "\"message-id\" == \$MSGID"'

@ -172,23 +172,23 @@ inbox/cur/4:2,
export MAILSEQ=seq
check_test 'subject' -eq 1 'magrep subject:nice | wc -l'
check_test 'ignorecase' -eq 1 'magrep -i subject:NICE | wc -l'
check_test 'invert' -eq 2 'magrep -v subject:nice | wc -l'
check_test 'max matches' -eq 2 'magrep -m 2 from:Piet | wc -l'
check_test 'long subject' -eq 1 'magrep subject:aliqua | wc -l'
check_test 'decode large rfc2047 header' -eq 1 'magrep -d to:John | wc -l'
check_test 'subject' -eq 1 'magrep subject:nice : | wc -l'
check_test 'ignorecase' -eq 1 'magrep -i subject:NICE : | wc -l'
check_test 'invert' -eq 2 'magrep -v subject:nice : | wc -l'
check_test 'max matches' -eq 2 'magrep -m 2 from:Piet : | wc -l'
check_test 'long subject' -eq 1 'magrep subject:aliqua : | wc -l'
check_test 'decode large rfc2047 header' -eq 1 'magrep -d to:John : | wc -l'
echo 'inbox/cur/1:2,: subject: wow nice subject' >expect
check_same 'print' 'magrep -p subject:nice' 'cat expect'
check_same 'print' 'magrep -p subject:nice :' 'cat expect'
echo 'inbox/cur/1:2,: subject: nice' >expect
check_same 'print match' 'magrep -po subject:nice' 'cat expect'
check_same 'print match' 'magrep -po subject:nice :' 'cat expect'
echo 'nice' >expect
check_same 'print match only' 'magrep -o subject:nice' 'cat expect'
check_same 'print match only' 'magrep -o subject:nice :' 'cat expect'
echo 'inbox/cur/3:2,' >expect
check_same 'multiple subjects' 'magrep subject:multi' 'cat expect'
check_same 'multiple subjects' 'magrep subject:multi :' 'cat expect'
)

@ -0,0 +1,166 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 12
rm -rf test.dir
mkdir test.dir
(
cd test.dir
export PIPE_CONTENTTYPE='text/plain; format=flowed'
export COLUMNS=80
cat <<! >a
this
is
flowed.
!
cat <<! >b
this is flowed.
!
check 'simple reflow' 'mflow <a | cmp - b'
cat <<! >a
this
is
two spaces.
!
cat <<! >b
this is two spaces.
!
check 'simple space stuffing' 'mflow <a | cmp - b'
cat <<! >a
this
is
flowed.
this is fixed.
!
cat <<! >b
this is flowed.
this is fixed.
!
check 'simple fixed' 'mflow <a | cmp - b'
cat <<! >a
> this
> is
> quoted.
this
is
unquoted.
!
cat <<! >b
> this is quoted.
this is unquoted.
!
check 'simple quoted' 'mflow <a | cmp - b'
(
export PIPE_CONTENTTYPE='text/plain; format=flowed; delsp=yes'
cat <<! >a
> this
> is
> delsp.
> double
> spaced
!
cat <<! >b
> thisisdelsp.
> double spaced
!
check 'simple delsp' 'mflow <a | cmp - b'
)
cat <<! >a
this
is
way more than eighty chars which is the terminal width to flow.
this
is
way more than eighty chars which is the terminal width to flow.
!
cat <<! >b
this is way more than eighty chars which is the terminal width to flow. this is
way more than eighty chars which is the terminal width to flow.
!
check 'simple wrap' 'mflow <a | cmp - b'
cat <<! >a
this
is
way more than eighty chars which is the terminal width to flow.
averylongwordcomeshere.
this
is
way more than eighty chars which is the terminal width to flow.
!
cat <<! >b
this is way more than eighty chars which is the terminal width to flow.
averylongwordcomeshere. this is way more than eighty chars which is the
terminal width to flow.
!
check 'more complex wrap' 'mflow <a | cmp - b'
cat <<! >a
foo
bar.
quux.
!
cat <<! >b
foo bar.
quux.
!
check 'space before empty line' 'mflow <a | cmp - b'
cat <<! >a
Aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee
aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee
aaaaa bbbbb ccccc
ffffff gggggg hhhhhh iiiiii.
!
cat <<! >b
Aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc
ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ffffff gggggg
hhhhhh iiiiii.
!
check 'fixed lines are wrapped too' 'mflow <a | cmp - b'
cat <<! >a
some
wrapped.
--
signature
!
cat <<! >b
some wrapped.
--
signature
!
check 'passes usenet signature marker as is' 'mflow <a | cmp - b'
cat <<! >a
some regular text being force wrapped because the line is way too long oh no who writes so long lines.
!
cat <<! >b
some regular text being force wrapped because the line is way too long oh no
who writes so long lines.
!
check 'force wrapping' 'mflow -f <a | cmp - b'
cat <<! >a
> some regular text being force wrapped because the line is way too long oh no who writes so long lines.
!
cat <<! >b
> some regular text being force wrapped because the line is way too long oh no
> who writes so long lines.
!
check 'force wrapping of quoted text' 'mflow -f <a | cmp - b'
)

@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 2
plan 3
rm -rf test.dir
mkdir test.dir
@ -21,11 +21,13 @@ body
cat <<! >seq
inbox/cur/1:2,
inbox/cur/1:2,
!
export MAILSEQ=seq
check_same 'ISO date' 'TZ=utc mscan -f "%16D" 1' 'echo "2017-03-30 13:42"'
check_same 'from name' 'mscan -f "%f" 1' 'echo "Rajwinder Kaur"'
check_test 'multiple mmsg' -eq 2 'mscan 1 1 | wc -l'
)

@ -51,7 +51,7 @@ inbox/cur/4:2,
export MAILSEQ=seq
check_same 'filename' 'msort -F' 'cat seq'
check_same 'filename' 'msort -F :' 'cat seq'
cat <<! >expect
inbox/cur/3:2,
@ -59,7 +59,7 @@ inbox/cur/1:2,
inbox/cur/2:2,
inbox/cur/4:2,
!
check_same 'date' 'msort -d' 'cat expect'
check_same 'date' 'msort -d :' 'cat expect'
cat <<! >expect
inbox/cur/4:2,
@ -67,7 +67,7 @@ inbox/cur/2:2,
inbox/cur/1:2,
inbox/cur/3:2,
!
check_same 'reverse date' 'msort -dr' 'cat expect'
check_same 'reverse date' 'msort -dr :' 'cat expect'
cat <<! >expect
inbox/cur/2:2,
@ -75,7 +75,7 @@ inbox/cur/3:2,
inbox/cur/4:2,
inbox/cur/1:2,
!
check_same 'from' 'msort -f' 'cat expect'
check_same 'from' 'msort -f :' 'cat expect'
cat <<! >expect
inbox/cur/3:2,
@ -83,6 +83,6 @@ inbox/cur/2:2,
inbox/cur/4:2,
inbox/cur/1:2,
!
check_same 'subject' 'msort -s' 'cat expect'
check_same 'subject' 'msort -s :' 'cat expect'
)

@ -1,7 +1,7 @@
#!/bin/sh -e
cd ${0%/*}
. ./lib.sh
plan 10
plan 11
rm -rf test.dir
mkdir test.dir
@ -25,12 +25,13 @@ export MAILCUR=cur MAILSEQ=seq
check 'set current' 'mseq -C 1 && mseq . | grep "inbox/cur/1:2,"'
check 'set next' 'mseq -C + && mseq . | grep "inbox/cur/2:2,"'
check 'set prev' 'mseq -C - && mseq . | grep "inbox/cur/1:2,"'
check 'set prev' 'mseq -C .- && mseq . | grep "inbox/cur/1:2,"'
check 'last' 'mseq "$" | grep "inbox/cur/10:2,"'
check_test 'whole thread' -eq 4 'mseq 6= | wc -l'
check_test 'subthread' -eq 2 'mseq 7_ | wc -l'
check 'parent' 'mseq 6^ | grep "inbox/cur/5_1:2,"'
check_test 'range' -eq 3 'mseq 1:3 | wc -l'
check_same 'multiple mmsg' 'mseq 1 2' 'printf "inbox/cur/1:2,\ninbox/cur/2:2,\n"'
cat <<! >seq
inbox/cur/1:2,

@ -20,20 +20,20 @@ ln -sf inbox/cur/1:2, cur
export MAILSEQ=seq MAILCUR=cur
check 'mark seen' 'mflag -S 1 && [ -e "inbox/cur/1:2,S" ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'mark replied' 'mflag -R 1 && [ -e "inbox/cur/1:2,RS" ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'unmark replied' 'mflag -r 1 && [ -e "inbox/cur/1:2,S" ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'mark flagged' 'mflag -F 1 && [ -e "inbox/cur/1:2,FS" ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'unmark flagged' 'mflag -f 1 && [ -e "inbox/cur/1:2,S" ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'unmark seen' 'mflag -s 1 && [ -e "inbox/cur/1:2," ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'mark trashed' 'mflag -T 1 && [ -e "inbox/cur/1:2,T" ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
check 'unmark trashed' 'mflag -t 1 && [ -e "inbox/cur/1:2," ]'
check_test 'fix seq' -eq 2 'mseq -f | mseq -S | wc -l'
check_test 'fix seq' -eq 2 'mseq -f : | mseq -S | wc -l'
)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save