chris-website/source/nl/netsoj/chris/blog/model/page.d
Chris Josten 1d0d1a54b1 Fix crash caused by lastIndexOf returning negative numbers
The ArticleParser would crash when a negative number was returned by
lastIndexOf, since it was casted to an unsigned integer. This unsigned
integer then was used to allocate memory, which would allocate way to
much memory, causing a MemoryException and crashing the process.
2021-10-13 14:07:27 +02:00

144 lines
4 KiB
D

import std.exception;
import std.experimental.logger;
import std.file;
import std.process;
import std.stdio;
import dyaml;
import vibe.vibe;
import utils;
/**
* Exception thrown when a page has syntax errors e.g.
*/
class ArticleParseException : Exception {
mixin basicExceptionCtors;
}
/**
* Represents a page on the blog. Every other page, including blog articles,
* projects and so on derrive from this class.
*/
class Page {
/**
* Internal name of the article. Usually the file name.
*/
protected string m_name;
/**
* Slug either manually assigned or generated based on file name.
* Only used in the url.
*/
protected string m_slug;
protected string m_title;
protected string m_content;
protected string m_contentSource;
protected bool m_hidden;
protected string m_language;
/**
* Option for the markdown parser: the amount of levels the header found in the markdown should
* be shifted. For example, 0 means H1 -> H1, 1 means H1 -> H2, 2 means H1 -> H3 and so on.
*/
protected int m_headerShift = 1;
private bool hasCalledSuper = false;
/**
* Creates a page from a file. This will read from the file and parse it.
*/
this(string file) {
this.m_name = file;
this.m_contentSource = readText(file);
// Find the seperator and split the string in two
const long seperatorIndex = lastIndexOf(m_contentSource, "\n---\n");
enforce!ArticleParseException(seperatorIndex >= 0);
string header = m_contentSource[0..seperatorIndex];
Node node = Loader.fromString(header).load();
loadHeader(node);
assert(hasCalledSuper);
this.m_content = Page.parseMarkdown(m_contentSource[seperatorIndex + 4..$],
this.m_headerShift);
}
/**
* Parse metadata from the header. Subclasses should override this method,
* to parse their own metadata and call super.
* Params:
* headerNode = the YAML node to parse the header metadata from.
*/
@safe
protected void loadHeader(Node headerNode){
this.m_hidden = headerNode.getOr!bool("hidden", false);
if (headerNode.containsKey("title")) {
this.m_title = headerNode["title"].as!string;
} else {
warningf("%s does not contain a title", this.m_name);
}
if (headerNode.containsKey("slug")) {
this.m_slug = headerNode["slug"].as!string;
} else {
this.m_slug = this.m_title;
infof("%s does not have a slug. Using %s", this.m_name, this.m_slug);
}
this.m_language = headerNode.getOr!string("language", "unknown");
hasCalledSuper = true;
}
/**
* Starts pandoc to convert MarkDown to HTML
* Params:
* source = The MarkDown source as a string (not a path!)
* shiftHeader = (Optional) The amount a header needs to be shifted. If for example, it
* is set to 1, first level headings within MarkDown become second level
* headers within HTML.
*/
public static string parseMarkdown(string source, int shiftHeader = 0) {
string[] args = ["pandoc",
"-f", "markdown",
"-t", "html",
"--lua-filter", "defaultClasses.lua"];
if (shiftHeader != 0) args ~= "--shift-heading-level-by=" ~ to!string(shiftHeader);
ProcessPipes pandoc = pipeProcess(args);
pandoc.stdin.write(source);
pandoc.stdin.writeln();
pandoc.stdin.flush();
pandoc.stdin.close();
pandoc.pid.wait();
string result;
string line;
while ((line = pandoc.stdout.readln()) !is null) {
result ~= line;
debug {
//logf("Pandoc stdout: %s", line);
}
}
while ((line = pandoc.stderr.readln()) !is null) {
debug {
logf("Pandoc stderr: %s", line);
}
}
return result;
}
@property string name() { return m_name; }
@property string title() { return m_title; }
@property string slug() { return m_slug; }
@property string content() { return m_content; }
@property string contentSource() { return m_contentSource; }
@property bool isHidden() { return m_hidden; }
@property string language() { return m_language; }
}