summaryrefslogtreecommitdiffstats
path: root/scripts/parts
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2020-11-24 23:09:16 +0100
committertv <tv@krebsco.de>2022-08-29 00:19:48 +0200
commiteb0d785c1be0ef31d38b95b9a53b79081126eea4 (patch)
tree08667ef17fe2ebff89640436cdc3614ef5be123e /scripts/parts
parenta25fc32eceefc10a91ef77ff2763b3f1b9324aaf (diff)
add script for listing and adding parts
Diffstat (limited to 'scripts/parts')
-rwxr-xr-xscripts/parts191
1 files changed, 191 insertions, 0 deletions
diff --git a/scripts/parts b/scripts/parts
new file mode 100755
index 0000000..ae73c2f
--- /dev/null
+++ b/scripts/parts
@@ -0,0 +1,191 @@
+#! /bin/sh
+# usage: parts list
+# usage: parts add PATH [NAME [DESCRIPTION]]
+set -efu
+
+
+list_parts() {
+ jq -r '
+ def replicate(n; x):
+ if n == 0 then "" else n * x end
+ ;
+ def flattenMap(inner):
+ def addDepth(depth):
+ if type == "array" then
+ map(addDepth(depth + 1))
+ else
+ { depth: depth, value: . }
+ end
+ ;
+ def addIndex:
+ to_entries | map(.value + { index: .key })
+ ;
+ def getPrefix:
+ match("( *).*").captures[0].string
+ ;
+ def stripPrefix:
+ match(" *(.*)").captures[0].string
+ ;
+ def shiftPrefix:
+ match("( *[^ ]+)( *)( .*)").captures|map(.string)|.[1]+.[0]+.[2]
+ ;
+ addDepth(0) | flatten | addIndex | map(inner) | map(shiftPrefix)
+ ;
+ def parseContentType:
+ if . != null then . else "text/plain; charset=UTF-8" end |
+ split(";\\s*";"") |
+ (.[0] | split("/")) as $compoundType |
+ $compoundType[0] as $type |
+ $compoundType[1] as $subtype |
+ (.[1:] | map(split("=") | { key: .[0], value: .[1] }) | from_entries)
+ as $params |
+ {
+ $type, $subtype, $params,
+ }
+ ;
+ def formatPart:
+ .body as $body |
+ ((.headers // []) | from_entries) as $headers |
+ ($headers["content-type"] | parseContentType) as $ct |
+ "\($ct.type)/\($ct.subtype)" as $compoundType |
+ $ct.params.name as $name |
+ [ "\($compoundType) \($name | tojson) \($body | length)" ] +
+ if $ct.type == "multipart" then .body | map(formatPart) else [] end
+ ;
+
+ formatPart |
+ flattenMap("\(replicate(.depth - 1; " "))part#\(.index + 1) \(.value)")[]
+ '
+}
+
+add_part() {(
+ filepath=$1
+ filename=${2-$(basename "$filepath")}
+ description=${3-$filename}
+
+ contentType="$(file -Lib "$filepath"); name=$filename"
+ case $contentType in
+ text/plain|text/plain\;*)
+ contentTransferEncoding=8bit
+ content() {
+ cat "$filepath"
+ }
+ ;;
+ *)
+ contentTransferEncoding=base64
+ content() {
+ base64 "$filepath"
+ }
+ ;;
+ esac
+ contentDisposition="attachment; filename=$filename"
+ contentDescription=$description
+
+ boundary=$(date -Ins | sha1sum | cut -d\ -f1)
+
+ {
+ jq '{ mail: . }'
+ content | jq -Rs '{ content: . }'
+ } |
+ jq -sr \
+ --arg boundary "$boundary" \
+ --arg contentType "$contentType" \
+ --arg contentTransferEncoding "$contentTransferEncoding" \
+ --arg contentDisposition "$contentDisposition" \
+ --arg contentDescription "$contentDescription" \
+ '
+ add |
+
+ .mail as $mail |
+ .content as $content |
+
+ def parseContentType:
+ if . == null then null else
+ split(";\\s*";"") |
+ (.[0] | split("/")) as $type |
+ (.[1:] | map(split("=") | { key: .[0], value: .[1] }) | from_entries)
+ as $parameters |
+ {
+ type: $type[0],
+ subtype: $type[1],
+ parameters: $parameters,
+ }
+ end;
+
+ ($mail.headers | from_entries) as $headers |
+ ($headers["content-type"] | if . == null then
+ {
+ type: "text",
+ subtype: "plain",
+ parameters: {
+ charset: "UTF-8",
+ },
+ }
+ else parseContentType
+ end)
+ as $ct |
+
+
+ def add_part:
+ {
+ headers: ({
+ "content-type": $contentType,
+ "content-transfer-encoding": $contentTransferEncoding,
+ "content-disposition": $contentDisposition,
+ "content-description": $contentDescription,
+ } | to_entries),
+ body: $content,
+ }
+ as $part |
+ { headers: .headers
+ , body: (.body + [$part])
+ };
+
+ ($mail.headers | from_entries) as $headers |
+
+ def is_multipart_mixed:
+ $headers["content-type"] // "" | test("^multipart/mixed\\b.*");
+
+ if is_multipart_mixed then
+ $mail | add_part
+ else
+ { headers: (
+ $mail.headers |
+ map(select(.key != "mime-version")) |
+ map(select(.key != "content-type")) |
+ map(select(.key != "content-transfer-encoding")) |
+ . + ({
+ "mime-version": "1.0",
+ "Content-Type": "multipart/mixed; boundary=\"----=\($boundary)\"",
+ } | to_entries)
+ )
+ , body: [{
+ headers: ({
+ "content-type":
+ ($headers["content-type"] // "text/plain; charset=UTF-8"),
+ "content-transfer-encoding":
+ ($headers["content-transfer-encoding"] // "8bit"),
+ } | to_entries),
+ body: $mail.body,
+ }]
+ } |
+ add_part
+ end
+ '
+)}
+
+
+
+command=$1; shift
+case $command in
+ list)
+ mailaid | list_parts
+ ;;
+ add)
+ mailaid | add_part "$@" | mailaid -d
+ ;;
+ *)
+ echo "$0: error: unknown command: $command" >&2
+ exit 1
+ ;;
+esac