Skip to content

Conversation

@guyest
Copy link

@guyest guyest commented Dec 14, 2018

This is a proof of principle, but fully functional pull request. For my purposes, using the pandoc variant of markdown just makes the most sense. I also think it is the most robust and featureful so require it as an option in any markdown based tool I use. It was pretty easy to implement that in muttdown, leveraging other packages already available. pyliner also does not seem all that well maintained so I opted for premailer to perform the CSS in-lining instead, which seems reasonable since it is well tested and designed for this very purpose.

I currently have this version of muttdown set up as a filter on my local Postfix install and it works great. The last detail at this stage I would like to see changed is to remove the !m sigil from the text/plain part. That is not currently written back into the multipart object.

So, I am going to continue down this path regardless of whether or not you would like to merge, but if this all seems in line with what you wanted to do then I am happy to have that happen in muttdown's birth repo (this one).

Copy link
Owner

@Roguelazer Roguelazer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay using premailer, since it does indeed appear to be better-maintained than pynliner.

I'm less confident about making pandoc a requirement; I would generally not use pypandoc's statically-compiled gigantic pandoc binary, and I imagine a lot of people don't want the entire GHC toolchain in order to send email. Perhaps making it an optional dependency and using it if it's present, and otherwise using the pure-python Markdown interpreter would be a better option. I'm also pretty sure I just found a security bug in pypandoc in 30 seconds of inspection, which is sad-making.

See below for some style notes and bugs.

if not text.startswith('!m'):
return None
text = re.sub('\s*!m\s*', '', text, re.M)
f = tempfile.NamedTemporaryFile(suffix='_panmail.css')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temporary files should always be used as context managers so that they get cleaned up on pypy

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bothered me as well -- to be sure. I did initially implement it that way. The reason I changed was to try and change less of the underlying code structure. I think handling the signature separately is where I ran into an awkward situation so found my way around it by just manually closing the file later (which also deletes it). Can do better on that though.

md += '<br />'.join(signature.split('\n'))
md += '</p></div>'
message = pypandoc.convert_text(pre_signature, 'html5', format='md', \
extra_args=["--css="+f.name, "--self-contained", "--metadata=pagetitle:'email'"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preferred style would be something like

message = pypandoc.convert_text(
    pre_signature, 'html5', format='md',
    extra_args=['--css=' + f.name, ...]
)

return None
text = re.sub('\s*!m\s*', '', text, re.M)
f = tempfile.NamedTemporaryFile(suffix='_panmail.css')
if config.css: f.write(config.css)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never do one-line if statements in python

md = pynliner.fromString(md)
message = MIMEText(md, 'html', _charset="UTF-8")
message = pypandoc.convert_text(text, 'html5', format='md', \
extra_args=["--css="+f.name, "--self-contained", "--metadata=pagetitle:'email'"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above comment regarding correct indentation for multiline function call arguments

extra_args=["--css="+f.name, "--self-contained", "--metadata=pagetitle:'email'"])
message = premailer.transform(message) # In-line the CSS.
message = MIMEText(message, 'html', _charset="UTF-8")
f.close()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will no longer be necessary when you switch to a context manager

print(rebuilt.as_string())
elif args.sendmail_passthru:
cmd = c.sendmail.split() + ['-f', args.envelope_from] + args.addresses
cmd = c.sendmail.split() + ['-G', '-i', '-f', args.envelope_from] + args.addresses
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-G would need to be controlled by a config flag since it would only be appropriate when using this as a postfix tool


proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=False)
proc.communicate(rebuilt.as_string())
proc.communicate(rebuilt.as_string().encode())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling encode without an argument has pretty confusing behavior on a lot of systems since it tries to guess the default encoding

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguable, I think the default is actually the least confusing possible behavior. I would opt instead for just dropping support of legacy python, which is really the root cause of confusion around strings/bytes. Especially with the additional decode bound method that converts the html from base64 to usual text encoding.

@guyest
Copy link
Author

guyest commented Dec 14, 2018

Regarding the size of pandoc as a requirement, this is a sore point with Haskell code in general. The binary file is friggin tiny (29KiB), but yes it can be confusingly obscene to install if you are not used to how Haskell does things. That said, it is probably fair that it should not be the only option for markdown processing, but that just introduces another branch point as slightly different conventions may need to be handled and the html output from either processor will have their own peculiarities.

Could you elaborate on the suspected security vulnerability in pypandoc?

Also, LOL: "All checks have failed." I bet Travis had fun trying to build pandoc like 7 times.

@gfa
Copy link

gfa commented Jan 22, 2019

Just one note, moving out from pynliner would help muttdown from the contrib to main area of the Debian archive.

premailer is not yet in Debian, I read its license and it looks good (pending approval from ftp master) to inclusion in main. I did not check premailer dependencies (if any).

If muttdown switches i'd package premailer, and deps, and when all deps are in Debian i'd switch muttdown from contrib to main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants