mirror of https://github.com/adamdruppe/arsd.git
more stuff
This commit is contained in:
parent
b0bd5cd1fe
commit
4b5a1a916e
8
cgi.d
8
cgi.d
|
@ -52,6 +52,14 @@
|
|||
+/
|
||||
module arsd.cgi;
|
||||
|
||||
/*
|
||||
|
||||
To do a file download offer in the browser:
|
||||
|
||||
cgi.setResponseContentType("text/csv");
|
||||
cgi.header("Content-Disposition: attachment; filename=\"customers.csv\"");
|
||||
*/
|
||||
|
||||
// FIXME: the location header is supposed to be an absolute url I guess.
|
||||
|
||||
// FIXME: would be cool to flush part of a dom document before complete
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
module arsd.email;
|
||||
|
||||
import std.net.curl;
|
||||
pragma(lib, "curl");
|
||||
|
||||
import std.base64;
|
||||
|
||||
// SEE ALSO: std.net.curl.SMTP
|
||||
|
||||
struct RelayInfo {
|
||||
string server;
|
||||
string username;
|
||||
string password;
|
||||
}
|
||||
|
||||
class EmailMessage {
|
||||
void setHeader(string name, string value) {
|
||||
headers ~= name ~ ": " ~ value;
|
||||
}
|
||||
|
||||
string[] to;
|
||||
string[] cc;
|
||||
string[] bcc;
|
||||
string from;
|
||||
string replyTo;
|
||||
string inReplyTo;
|
||||
string textBody;
|
||||
string htmlBody;
|
||||
string subject;
|
||||
|
||||
string[] headers;
|
||||
|
||||
private bool isMime = false;
|
||||
private bool isHtml = false;
|
||||
|
||||
void setTextBody(string text) {}
|
||||
void setHtmlBody(string html) {
|
||||
isMime = true;
|
||||
isHtml = true;
|
||||
htmlBody = html;
|
||||
|
||||
import arsd.htmltotext;
|
||||
if(textBody is null)
|
||||
textBody = htmlToText(html);
|
||||
}
|
||||
|
||||
struct MimeAttachment {
|
||||
string type;
|
||||
string filename;
|
||||
const(void)[] content;
|
||||
}
|
||||
|
||||
const(MimeAttachment)[] attachments;
|
||||
|
||||
void addAttachment(string mimeType, string filename, in void[] content) {
|
||||
isMime = true;
|
||||
attachments ~= MimeAttachment(mimeType, filename, content);
|
||||
}
|
||||
|
||||
|
||||
string toString() {
|
||||
string boundary = "0016e64be86203dd36047610926a"; // FIXME
|
||||
|
||||
assert(!isHtml || (isHtml && isMime));
|
||||
|
||||
auto headers = this.headers;
|
||||
|
||||
string toHeader = "To: ";
|
||||
bool toHeaderOutputted = false;
|
||||
foreach(t; to) {
|
||||
if(toHeaderOutputted)
|
||||
toHeader ~= ", ";
|
||||
else
|
||||
toHeaderOutputted = true;
|
||||
|
||||
toHeader ~= t;
|
||||
}
|
||||
|
||||
if(to.length)
|
||||
headers ~= toHeader;
|
||||
|
||||
if(subject !is null)
|
||||
headers ~= "Subject: " ~ subject;
|
||||
|
||||
if(isMime)
|
||||
headers ~= "MIME-Version: 1.0";
|
||||
|
||||
if(attachments.length)
|
||||
headers ~= "Content-Type: multipart/mixed; boundary=" ~ boundary;
|
||||
else if(isHtml)
|
||||
headers ~= "Content-Type: multipart/alternative; boundary=" ~ boundary;
|
||||
else
|
||||
headers ~= "Content-Type: text/plain; charset=UTF-8";
|
||||
|
||||
string msg;
|
||||
msg.reserve(htmlBody.length + textBody.length + 1024);
|
||||
|
||||
foreach(header; headers)
|
||||
msg ~= header ~ "\r\n";
|
||||
if(msg.length) // has headers
|
||||
msg ~= "\r\n";
|
||||
|
||||
if(isMime) {
|
||||
msg ~= "--" ~ boundary ~ "\r\n";
|
||||
msg ~= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
|
||||
}
|
||||
|
||||
msg ~= textBody;
|
||||
|
||||
if(isMime)
|
||||
msg ~= "\r\n--" ~ boundary;
|
||||
if(isHtml) {
|
||||
msg ~= "\r\n";
|
||||
msg ~= "Content-Type: text/html; charset=UTF-8\r\n\r\n";
|
||||
msg ~= htmlBody;
|
||||
msg ~= "\r\n--" ~ boundary;
|
||||
}
|
||||
|
||||
foreach(attachment; attachments) {
|
||||
assert(isMime);
|
||||
msg ~= "\r\n";
|
||||
msg ~= "Content-Type: " ~ attachment.type ~ "\r\n";
|
||||
msg ~= "Content-Disposition: attachment; filename=\""~attachment.filename~"\"\r\n";
|
||||
msg ~= "Content-Transfer-Encoding: base64\r\n";
|
||||
msg ~= "\r\n";
|
||||
msg ~= Base64.encode(cast(const(ubyte)[]) attachment.content);
|
||||
msg ~= "\r\n--" ~ boundary;
|
||||
}
|
||||
|
||||
if(isMime)
|
||||
msg ~= "--\r\n";
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
void send(RelayInfo mailServer = RelayInfo("smtp://localhost")) {
|
||||
auto smtp = new SMTP(mailServer.server);
|
||||
const(char)[][] allRecipients = cast(const(char)[][]) (to ~ cc ~ bcc); // WTF cast
|
||||
smtp.mailTo(allRecipients);
|
||||
smtp.mailFrom = from;
|
||||
smtp.message = this.toString();
|
||||
smtp.perform();
|
||||
}
|
||||
}
|
||||
|
||||
void email(string to, string subject, string message, string from) {
|
||||
auto msg = new EmailMessage();
|
||||
msg.from = from;
|
||||
msg.to = [to];
|
||||
msg.subject = subject;
|
||||
msg.textBody = message;
|
||||
msg.send();
|
||||
}
|
55
html.d
55
html.d
|
@ -233,61 +233,10 @@ Html linkify(string text) {
|
|||
/// Returns true of the string appears to be html/xml - if it matches the pattern
|
||||
/// for tags or entities.
|
||||
bool appearsToBeHtml(string src) {
|
||||
return false;
|
||||
import std.regex;
|
||||
return cast(bool) match(src, `.*\<[A-Za-z]+>.*`);
|
||||
}
|
||||
|
||||
/+
|
||||
void qsaFilter(string logicalScriptName) {
|
||||
string logicalScriptName = siteBase[0 .. $-1];
|
||||
|
||||
foreach(a; document.querySelectorAll("a[qsa]")) {
|
||||
string href = logicalScriptName ~ _cgi.pathInfo ~ "?";
|
||||
|
||||
int matches, possibilities;
|
||||
|
||||
string[][string] vars;
|
||||
foreach(k, v; _cgi.getArray)
|
||||
vars[k] = cast(string[]) v;
|
||||
foreach(k, v; decodeVariablesSingle(a.qsa)) {
|
||||
if(k in _cgi.get && _cgi.get[k] == v)
|
||||
matches++;
|
||||
possibilities++;
|
||||
|
||||
if(k !in vars || vars[k].length <= 1)
|
||||
vars[k] = [v];
|
||||
else
|
||||
assert(0, "qsa doesn't work here");
|
||||
}
|
||||
|
||||
string[] clear = a.getAttribute("qsa-clear").split("&");
|
||||
clear ~= "ajaxLoading";
|
||||
if(a.parentNode !is null)
|
||||
clear ~= a.parentNode.getAttribute("qsa-clear").split("&");
|
||||
|
||||
bool outputted = false;
|
||||
varskip: foreach(k, varr; vars) {
|
||||
foreach(item; clear)
|
||||
if(k == item)
|
||||
continue varskip;
|
||||
foreach(v; varr) {
|
||||
if(outputted)
|
||||
href ~= "&";
|
||||
else
|
||||
outputted = true;
|
||||
|
||||
href ~= std.uri.encodeComponent(k) ~ "=" ~ std.uri.encodeComponent(v);
|
||||
}
|
||||
}
|
||||
|
||||
a.href = href;
|
||||
|
||||
a.removeAttribute("qsa");
|
||||
|
||||
if(matches == possibilities)
|
||||
a.addClass("current");
|
||||
}
|
||||
}
|
||||
+/
|
||||
string favicon(Document document) {
|
||||
auto item = document.querySelector("link[rel~=icon]");
|
||||
if(item !is null)
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
module arsd.htmltotext;
|
||||
|
||||
import arsd.dom;
|
||||
import std.string;
|
||||
import std.array : replace;
|
||||
|
||||
string repeat(string s, int num) {
|
||||
string ret;
|
||||
foreach(i; 0 .. num)
|
||||
ret ~= s;
|
||||
return ret;
|
||||
}
|
||||
|
||||
import std.stdio;
|
||||
|
||||
static import std.regex;
|
||||
string htmlToText(string html, bool wantWordWrap = true, int wrapAmount = 74) {
|
||||
Document document = new Document;
|
||||
|
||||
|
||||
html = html.replace(" ", " ");
|
||||
html = html.replace(" ", " ");
|
||||
html = html.replace(" ", " ");
|
||||
html = html.replace("\n", "");
|
||||
html = html.replace("\r", "");
|
||||
html = std.regex.replace(html, std.regex.regex("[\n\r\t \u00a0]+", "gm"), " ");
|
||||
|
||||
document.parse("<roottag>" ~ html ~ "</roottag>");
|
||||
|
||||
Element start;
|
||||
auto bod = document.getElementsByTagName("body");
|
||||
if(bod.length)
|
||||
start = bod[0];
|
||||
else
|
||||
start = document.root;
|
||||
|
||||
start.innerHTML = start.innerHTML().replace("<br />", "\u0001");
|
||||
|
||||
again:
|
||||
string result = "";
|
||||
foreach(ele; start.tree) {
|
||||
if(ele is start) continue;
|
||||
if(ele.nodeType != 1) continue;
|
||||
|
||||
switch(ele.tagName) {
|
||||
case "b":
|
||||
case "strong":
|
||||
ele.innerText = "*" ~ ele.innerText ~ "*";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "i":
|
||||
case "em":
|
||||
ele.innerText = "/" ~ ele.innerText ~ "/";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "u":
|
||||
ele.innerText = "_" ~ ele.innerText ~ "_";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "h1":
|
||||
ele.innerText = "\r" ~ ele.innerText ~ "\n" ~ repeat("=", ele.innerText.length) ~ "\r";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "h2":
|
||||
ele.innerText = "\r" ~ ele.innerText ~ "\n" ~ repeat("-", ele.innerText.length) ~ "\r";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "h3":
|
||||
ele.innerText = "\r" ~ ele.innerText.toUpper ~ "\r";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "p":
|
||||
/*
|
||||
if(ele.innerHTML.length > 1)
|
||||
ele.innerHTML = "\r" ~ wrap(ele.innerHTML) ~ "\r";
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
*/
|
||||
break;
|
||||
case "a":
|
||||
string href = ele.getAttribute("href");
|
||||
if(href) {
|
||||
if(ele.innerText != href)
|
||||
ele.innerText = ele.innerText ~ " <" ~ href ~ "> ";
|
||||
}
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
break;
|
||||
case "ol":
|
||||
case "ul":
|
||||
ele.innerHTML = "\r" ~ ele.innerHTML ~ "\r";
|
||||
break;
|
||||
case "li":
|
||||
ele.innerHTML = "\t* " ~ ele.innerHTML ~ "\r";
|
||||
ele.stripOut();
|
||||
break;
|
||||
case "sup":
|
||||
ele.innerText = "^" ~ ele.innerText;
|
||||
ele.stripOut();
|
||||
break;
|
||||
/*
|
||||
case "img":
|
||||
string alt = ele.getAttribute("alt");
|
||||
if(alt)
|
||||
result ~= ele.alt;
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
ele.stripOut();
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
|
||||
again2:
|
||||
start.innerHTML = start.innerHTML().replace("\u0001", "\n");
|
||||
|
||||
foreach(ele; start.tree) {
|
||||
if(ele.tagName == "p") {
|
||||
if(strip(ele.innerText()).length > 1) {
|
||||
string res = "";
|
||||
string all = ele.innerText().replace("\n \n", "\n\n");
|
||||
foreach(part; all.split("\n\n"))
|
||||
res ~= "\r" ~ strip( wantWordWrap ? wrap(part, /*74*/ wrapAmount) : part ) ~ "\r";
|
||||
ele.innerText = res;
|
||||
} else
|
||||
ele.innerText = strip(ele.innerText);
|
||||
ele.stripOut();
|
||||
goto again2;
|
||||
}
|
||||
}
|
||||
|
||||
result = start.innerText();
|
||||
|
||||
result = result.replace("\r ", "\r");
|
||||
result = result.replace(" \r", "\r");
|
||||
|
||||
//result = result.replace("\u00a0", " ");
|
||||
|
||||
|
||||
result = squeeze(result, "\r");
|
||||
result = result.replace("\r", "\n\n");
|
||||
|
||||
result = result.replace("舗", "'"); // HACK: this shouldn't be needed, but apparently is in practice surely due to a bug elsewhere
|
||||
result = result.replace(""", "\""); // HACK: this shouldn't be needed, but apparently is in practice surely due to a bug elsewhere
|
||||
//result = htmlEntitiesDecode(result); // for special chars mainly
|
||||
|
||||
//a = std.regex.replace(a, std.regex.regex("(\n\t)+", "g"), "\n"); //\t");
|
||||
return result.strip;
|
||||
}
|
52
web.d
52
web.d
|
@ -3201,6 +3201,58 @@ Table structToTable(T)(Document document, T s, string[] fieldsToSkip = null) if(
|
|||
}
|
||||
}
|
||||
|
||||
/// This adds a custom attribute to links in the document called qsa which modifies the values on the query string
|
||||
void translateQsa(Document document, Cgi cgi, string logicalScriptName = null) {
|
||||
if(logicalScriptName is null)
|
||||
logicalScriptName = cgi.scriptName;
|
||||
|
||||
foreach(a; document.querySelectorAll("a[qsa]")) {
|
||||
string href = logicalScriptName ~ cgi.pathInfo ~ "?";
|
||||
|
||||
int matches, possibilities;
|
||||
|
||||
string[][string] vars;
|
||||
foreach(k, v; cgi.getArray)
|
||||
vars[k] = cast(string[]) v;
|
||||
foreach(k, v; decodeVariablesSingle(a.qsa)) {
|
||||
if(k in cgi.get && cgi.get[k] == v)
|
||||
matches++;
|
||||
possibilities++;
|
||||
|
||||
if(k !in vars || vars[k].length <= 1)
|
||||
vars[k] = [v];
|
||||
else
|
||||
assert(0, "qsa doesn't work here");
|
||||
}
|
||||
|
||||
string[] clear = a.getAttribute("qsa-clear").split("&");
|
||||
clear ~= "ajaxLoading";
|
||||
if(a.parentNode !is null)
|
||||
clear ~= a.parentNode.getAttribute("qsa-clear").split("&");
|
||||
|
||||
bool outputted = false;
|
||||
varskip: foreach(k, varr; vars) {
|
||||
foreach(item; clear)
|
||||
if(k == item)
|
||||
continue varskip;
|
||||
foreach(v; varr) {
|
||||
if(outputted)
|
||||
href ~= "&";
|
||||
else
|
||||
outputted = true;
|
||||
|
||||
href ~= std.uri.encodeComponent(k) ~ "=" ~ std.uri.encodeComponent(v);
|
||||
}
|
||||
}
|
||||
|
||||
a.href = href;
|
||||
|
||||
a.removeAttribute("qsa");
|
||||
|
||||
if(matches == possibilities)
|
||||
a.addClass("current");
|
||||
}
|
||||
}
|
||||
|
||||
/// This uses reflection info to generate Javascript that can call the server with some ease.
|
||||
/// Also includes javascript base (see bottom of this file)
|
||||
|
|
Loading…
Reference in New Issue