catching up on random stuff

This commit is contained in:
Adam D. Ruppe 2013-02-16 11:18:15 -05:00
parent 4ff49846d4
commit 79b2a25b92
8 changed files with 334 additions and 39 deletions

4
cgi.d
View File

@ -1163,10 +1163,12 @@ class Cgi {
/// This gets a full url for the current request, including port, protocol, host, path, and query
string getCurrentCompleteUri() const {
ushort defaultPort = https ? 443 : 80;
return format("http%s://%s%s%s",
https ? "s" : "",
host,
port == 80 ? "" : ":" ~ to!string(port),
port == defaultPort ? "" : ":" ~ to!string(port),
requestUri);
}

24
dom.d
View File

@ -20,6 +20,9 @@
*/
module arsd.dom;
// FIXME: something like <ol>spam <ol> with no closing </ol> should read the second tag as the closer in garbage mode
// FIXME: failing to close a paragraph sometimes messes things up too
// FIXME: it would be kinda cool to have some support for internal DTDs
// and maybe XPath as well, to some extent
/*
@ -1583,7 +1586,7 @@ class Element {
/**
Takes some html and replaces the element's children with the tree made from the string.
*/
Element innerHTML(string html) {
Element innerHTML(string html, bool strict = false) {
if(html.length)
selfClosed = false;
@ -1595,7 +1598,7 @@ class Element {
}
auto doc = new Document();
doc.parse("<innerhtml>" ~ html ~ "</innerhtml>"); // FIXME: this should preserve the strictness of the parent document
doc.parse("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document
children = doc.root.children;
foreach(c; children) {
@ -3029,7 +3032,7 @@ class TableCell : Element {
///.
class MarkupError : Exception {
class MarkupException : Exception {
///.
this(string message) {
@ -3208,7 +3211,7 @@ class Document : FileResource {
if(dataEncoding is null) {
if(strict)
throw new MarkupError("I couldn't figure out the encoding of this document.");
throw new MarkupException("I couldn't figure out the encoding of this document.");
else
// if we really don't know by here, it means we already tried UTF-8,
// looked for utf 16 and 32 byte order marks, and looked for xml or meta
@ -3282,7 +3285,7 @@ class Document : FileResource {
}
void parseError(string message) {
throw new MarkupError(format("char %d (line %d): %s", pos, getLineNumber(pos), message));
throw new MarkupException(format("char %d (line %d): %s", pos, getLineNumber(pos), message));
}
void eatWhitespace() {
@ -3312,7 +3315,7 @@ class Document : FileResource {
data[pos] != ' ' && data[pos] != '\n' && data[pos] != '\t')
{
if(data[pos] == '<')
throw new MarkupError("The character < can never appear in an attribute name.");
throw new MarkupException("The character < can never appear in an attribute name.");
pos++;
}
@ -3326,11 +3329,14 @@ class Document : FileResource {
switch(data[pos]) {
case '\'':
case '"':
auto started = pos;
char end = data[pos];
pos++;
auto start = pos;
while(data[pos] != end)
while(pos < data.length && data[pos] != end)
pos++;
if(strict && pos == data.length)
throw new MarkupException("Unclosed attribute value, started on char " ~ to!string(started));
string v = htmlEntitiesDecode(data[start..pos], strict);
pos++; // skip over the end
return v;
@ -3387,7 +3393,7 @@ class Document : FileResource {
if(pos >= data.length)
{
if(strict) {
throw new MarkupError("Gone over the input (is there no root element?), chain: " ~ to!string(parentChain));
throw new MarkupException("Gone over the input (is there no root element?), chain: " ~ to!string(parentChain));
} else {
if(parentChain.length)
return Ele(1, null, parentChain[0]); // in loose mode, we just assume the document has ended
@ -3672,7 +3678,7 @@ class Document : FileResource {
}
if(strict && attrName in attributes)
throw new MarkupError("Repeated attribute: " ~ attrName);
throw new MarkupException("Repeated attribute: " ~ attrName);
attributes[attrName] = attrValue;
goto moreAttributes;

View File

@ -8,6 +8,13 @@
*/
module arsd.engine; //@-L-lSDL -L-lSDL_mixer -L-lSDL_ttf -L-lSDL_image -L-lGL -L-lSDL_net
pragma(lib, "SDL");
pragma(lib, "SDL_mixer");
pragma(lib, "SDL_ttf");
pragma(lib, "SDL_image");
pragma(lib, "SDL_net");
pragma(lib, "GL");
// FIXME: the difference between directions and buttons should be removed
@ -45,8 +52,6 @@ else
import std.stdio;
//version(linux) pragma(lib, "kbhit.o");
extern(C) bool kbhit();
int randomNumber(int min, int max){
if(min == max)
return min;
@ -1211,3 +1216,37 @@ bool directionIsDown(Engine.Direction d, int which = 0){
*/
version(linux) {
version(D_Version2) {
import sys = core.sys.posix.sys.select;
version=CustomKbhit;
int kbhit()
{
sys.timeval tv;
sys.fd_set read_fd;
tv.tv_sec=0;
tv.tv_usec=0;
sys.FD_ZERO(&read_fd);
sys.FD_SET(0,&read_fd);
if(sys.select(1, &read_fd, null, null, &tv) == -1)
return 0;
if(sys.FD_ISSET(0,&read_fd))
return 1;
return 0;
}
}
// else, use kbhit.o from the C file
}
version(CustomKbhit) {} else
extern(C) bool kbhit();

View File

@ -21,8 +21,8 @@ string htmlToText(string html, bool wantWordWrap = true, int wrapAmount = 74) {
html = html.replace("&nbsp;", " ");
html = html.replace("&#160;", " ");
html = html.replace("&#xa0;", " ");
html = html.replace("\n", "");
html = html.replace("\r", "");
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>");
@ -75,6 +75,7 @@ string htmlToText(string html, bool wantWordWrap = true, int wrapAmount = 74) {
ele.stripOut();
goto again;
break;
case "td":
case "p":
/*
if(ele.innerHTML.length > 1)
@ -121,7 +122,14 @@ string htmlToText(string html, bool wantWordWrap = true, int wrapAmount = 74) {
start.innerHTML = start.innerHTML().replace("\u0001", "\n");
foreach(ele; start.tree) {
if(ele.tagName == "p") {
if(ele.tagName == "td") {
if(ele.directText().strip().length) {
ele.prependText("\r");
ele.appendText("\r");
}
ele.stripOut();
goto again2;
} else if(ele.tagName == "p") {
if(strip(ele.innerText()).length > 1) {
string res = "";
string all = ele.innerText().replace("\n \n", "\n\n");
@ -136,6 +144,7 @@ string htmlToText(string html, bool wantWordWrap = true, int wrapAmount = 74) {
}
result = start.innerText();
result = squeeze(result, " ");
result = result.replace("\r ", "\r");
result = result.replace(" \r", "\r");

16
jpg.d
View File

@ -5,7 +5,6 @@ import std.stdio;
import std.conv;
struct JpegSection {
ushort length;
ubyte identifier;
ubyte[] data;
}
@ -37,16 +36,16 @@ struct LazyJpegFile {
throw new Exception("not lined up in file");
_front.identifier = startingBuffer[1];
_front.length = cast(ushort) (startingBuffer[2]) * 256 + startingBuffer[3];
ushort length = cast(ushort) (startingBuffer[2]) * 256 + startingBuffer[3];
if(_front.length < 2)
if(length < 2)
throw new Exception("wtf");
_front.length -= 2; // the length in the file includes the block header, but we just want the data here
length -= 2; // the length in the file includes the block header, but we just want the data here
_front.data = new ubyte[](_front.length);
_front.data = new ubyte[](length);
read = f.rawRead(_front.data);
if(read.length != _front.length)
throw new Exception("didn't read the file right, got " ~ to!string(read.length) ~ " instead of " ~ to!string(_front.length));
if(read.length != length)
throw new Exception("didn't read the file right, got " ~ to!string(read.length) ~ " instead of " ~ to!string(length));
_frontIsValid = true;
}
@ -60,7 +59,7 @@ struct LazyJpegFile {
}
}
// http://www.obrador.com/essentialjpeg/headerinfo.htm
// returns width, height
Tuple!(int, int) getSizeFromFile(string filename) {
import std.stdio;
@ -71,6 +70,7 @@ Tuple!(int, int) getSizeFromFile(string filename) {
auto firstSection = jpeg.front();
jpeg.popFront();
// commented because exif and jfif are both readable by this so no need to be picky
//if(firstSection.identifier != 0xe0)
//throw new Exception("bad header");

10
mysql.d
View File

@ -699,16 +699,22 @@ Ret queryOneRow(Ret = Row, DB, string file = __FILE__, size_t line = __LINE__, T
static if(is(Ret : DataObject) && is(DB == MySql)) {
auto res = db.queryDataObject!Ret(sql, t);
if(res.empty)
throw new Exception("result was empty", file, line);
throw new EmptyResultException("result was empty", file, line);
return res.front;
} else static if(is(Ret == Row)) {
auto res = db.query(sql, t);
if(res.empty)
throw new Exception("result was empty", file, line);
throw new EmptyResultException("result was empty", file, line);
return res.front;
} else static assert(0, "Unsupported single row query return value, " ~ Ret.stringof);
}
class EmptyResultException : Exception {
this(string message, string file = __FILE__, size_t line = __LINE__) {
super(message, file, line);
}
}
/*
void main() {

35
png.d
View File

@ -230,6 +230,41 @@ ubyte[] writePng(PNG* p) {
return a;
}
PNGHeader getHeaderFromFile(string filename) {
import std.stdio;
auto file = File(filename, "rb");
ubyte[12] initialBuffer; // file header + size of first chunk (should be IHDR)
auto data = file.rawRead(initialBuffer[]);
if(data.length != 12)
throw new Exception("couldn't get png file header off " ~ filename);
if(data[0..8] != [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
throw new Exception("file " ~ filename ~ " is not a png");
auto pos = 8;
uint size;
size |= data[pos++] << 24;
size |= data[pos++] << 16;
size |= data[pos++] << 8;
size |= data[pos++] << 0;
size += 4; // chunk type
size += 4; // checksum
ubyte[] more;
more.length = size;
auto chunk = file.rawRead(more);
if(chunk.length != size)
throw new Exception("couldn't get png image header off " ~ filename);
more = data ~ chunk;
auto png = readPng(more);
return getHeader(png);
}
PNG* readPng(ubyte[] data) {
auto p = new PNG;

226
web.d
View File

@ -1,6 +1,8 @@
module arsd.web;
enum RequirePost;
enum RequireHttps;
enum NoAutomaticForm;
/// Attribute for the default formatting (html, table, json, etc)
struct DefaultFormat {
@ -624,7 +626,10 @@ class ApiProvider : WebDotDBaseType {
document.cookie = \"timezone=\" + tz + \"; path=/\";
}
</script>
<style>.format-row { display: none; }</style>
<style>
.format-row { display: none; }
.validation-failed { background-color: #ffe0e0; }
</style>
</head>
<body>
<div id=\"body\"></div>
@ -784,6 +789,8 @@ struct FunctionInfo {
bool returnTypeIsDocument; // internal used when wrapping
bool returnTypeIsElement; // internal used when wrapping
bool requireHttps;
Document delegate(in string[string] args) createForm; /// This is used if you want a custom form - normally, on insufficient parameters, an automatic form is created. But if there's a functionName_Form method, it is used instead. FIXME: this used to work but not sure if it still does
}
@ -978,6 +985,7 @@ immutable(ReflectionInfo*) prepareReflectionImpl(alias PM, alias Parent)(Parent
FunctionInfo* f = new FunctionInfo;
ParameterTypeTuple!(__traits(getMember, Class, member)) fargs;
f.requireHttps = hasAnnotation!(__traits(getMember, Class, member), RequireHttps);
f.returnType = ReturnType!(__traits(getMember, Class, member)).stringof;
f.returnTypeIsDocument = is(ReturnType!(__traits(getMember, Class, member)) : Document);
f.returnTypeIsElement = is(ReturnType!(__traits(getMember, Class, member)) : Element);
@ -1248,6 +1256,10 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
}
bool returnedHoldsADocument = false;
string[][string] want;
string format, secondaryFormat;
void delegate(Document d) moreProcessing;
WrapperReturn ret;
try {
if(fun is null) {
@ -1276,6 +1288,12 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
assert(fun.dispatcher !is null);
assert(cgi !is null);
if(fun.requireHttps && !cgi.https) {
cgi.setResponseLocation("https://" ~ cgi.host ~ cgi.logicalScriptName ~ cgi.pathInfo ~
(cgi.queryString.length ? "?" : "") ~ cgi.queryString);
envelopeFormat = "no-processing";
goto do_nothing_else;
}
if(instantiator.length) {
assert(fun !is null);
@ -1287,14 +1305,14 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
result.type = fun.returnType;
string format = cgi.request("format", reflection.defaultOutputFormat);
string secondaryFormat = cgi.request("secondaryFormat", "");
format = cgi.request("format", reflection.defaultOutputFormat);
secondaryFormat = cgi.request("secondaryFormat", "");
if(secondaryFormat.length == 0) secondaryFormat = null;
JSONValue res;
// FIXME: hackalicious garbage. kill.
string[][string] want = cast(string[][string]) (cgi.requestMethod == Cgi.RequestMethod.POST ? cgi.postArray : cgi.getArray);
want = cast(string[][string]) (cgi.requestMethod == Cgi.RequestMethod.POST ? cgi.postArray : cgi.getArray);
version(fb_inside_hack) {
if(cgi.referrer.indexOf("apps.facebook.com") != -1) {
auto idx = cgi.referrer.indexOf("?");
@ -1313,7 +1331,7 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
}
realObject.cgi = cgi;
auto ret = fun.dispatcher(cgi, realObject, want, format, secondaryFormat);
ret = fun.dispatcher(cgi, realObject, want, format, secondaryFormat);
if(ret.completed) {
envelopeFormat = "no-processing";
goto do_nothing_else;
@ -1336,8 +1354,38 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
debug result.dFullString = e.toString();
if(envelopeFormat == "document" || envelopeFormat == "html") {
auto ipe = cast(InsufficientParametersException) e;
if(ipe !is null) {
if(auto fve = cast(FormValidationException) e) {
auto thing = fve.formFunction;
if(thing is null)
thing = fun;
fun = thing;
ret = fun.dispatcher(cgi, realObject, want, format, secondaryFormat);
result.result = ret.value;
if(fun.returnTypeIsDocument)
returnedHoldsADocument = true; // we don't replace the success flag, so this ensures no double document
moreProcessing = (Document d) {
Form f;
if(fve.getForm !is null)
f = fve.getForm(d);
else
f = d.requireSelector!Form("form");
foreach(k, v; want)
f.setValue(k, v[$-1]);
foreach(idx, failure; fve.failed) {
auto ele = f.querySelector("[name=\""~failure~"\"]");
ele.addClass("validation-failed");
ele.dataset.validationMessage = fve.messagesForUser[idx];
ele.parentNode.addChild("span", fve.messagesForUser[idx]).addClass("validation-message");
}
if(fve.postProcessor !is null)
fve.postProcessor(d, f, fve);
};
} else if(auto ipe = cast(InsufficientParametersException) e) {
assert(fun !is null);
Form form;
if(fun.createForm !is null) {
@ -1376,6 +1424,8 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
foreach(k, v; cgi.get)
form.setValue(k, v); // carry what we have for params over
foreach(k, v; cgi.post)
form.setValue(k, v); // carry what we have for params over
result.result.str = form.toString();
} else {
@ -1504,6 +1554,7 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
document = e.parentDocument;
//assert(0, document.toString());
// FIXME: a wee bit slow, esp if func return element
e.innerHTML = returned;
if(fun !is null)
@ -1532,6 +1583,9 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
}
}
if(moreProcessing !is null)
moreProcessing(document);
returned = document.toString;
}
}
@ -1623,6 +1677,13 @@ mixin template FancyMain(T, Args...) {
/// Like FancyMain, but you can pass a custom subclass of Cgi
mixin template CustomCgiFancyMain(CustomCgi, T, Args...) if(is(CustomCgi : Cgi)) {
void fancyMainFunction(Cgi cgi) { //string[] args) {
version(catch_segfault) {
import etc.linux.memoryerror;
// NOTE: this is private on stock dmd right now, just
// open the file (src/druntime/import/etc/linux/memoryerror.d) and make it public
registerMemoryErrorHandler();
}
// auto cgi = new Cgi;
// there must be a trailing slash for relative links..
@ -2192,16 +2253,146 @@ Element toXmlElement(T)(Document document, T t) {
/// Done automatically by the wrapper function
class InsufficientParametersException : Exception {
this(string functionName, string msg) {
super(functionName ~ ": " ~ msg);
this(string functionName, string msg, string file = __FILE__, size_t line = __LINE__) {
this.functionName = functionName;
super(functionName ~ ": " ~ msg, file, line);
}
string functionName;
string argumentName;
string formLocation;
}
/// helper for param checking
bool isValidLookingEmailAddress(string e) {
import std.net.isemail;
return isEmail(e, CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid;
}
/// Looks for things like <a or [url - the kind of stuff I often see in blatantly obvious comment spam
bool isFreeOfTypicalPlainTextSpamLinks(string txt) {
if(txt.indexOf("href=") != -1)
return false;
if(txt.indexOf("[url") != -1)
return false;
return true;
}
/**
---
auto checker = new ParamCheckHelper();
checker.finish(); // this will throw if any of the checks failed
// now go ahead and use the params
---
*/
class ParamCheckHelper {
this(in Cgi cgi) {
this.cgi = cgi;
}
string[] failed;
string[] messagesForUser;
const(Cgi) cgi;
void failure(string name, string messageForUser = null) {
failed ~= name;
messagesForUser ~= messageForUser;
}
string checkParam(in string[string] among, string name, bool delegate(string) ok, string messageForUser = null) {
string value = null;
auto ptr = "name" in among;
if(ptr !is null) {
value = *ptr;
}
if(!ok(value)) {
failure(name, messageForUser is null ? "Please complete this field" : messageForUser);
}
return value;
}
// int a = checkParam!int(cgi, "cool", (a) => a > 10);
T checkCgiParam(T)(string name, T defaultValue, bool delegate(T) ok, string messageForUser = null) {
auto value = cgi.request(name, defaultValue);
if(!ok(value)) {
failure(name, messageForUser);
}
return value;
}
void finish(
immutable(FunctionInfo)* formFunction,
Form delegate(Document) getForm,
void delegate(Document, Form, FormValidationException) postProcessor,
string file = __FILE__, size_t line = __LINE__)
{
if(failed.length)
throw new FormValidationException(
formFunction, getForm, postProcessor,
failed, messagesForUser,
to!string(failed), file, line);
}
}
auto check(alias field)(ParamCheckHelper helper, bool delegate(typeof(field)) ok, string messageForUser = null) {
if(!ok(field)) {
helper.failure(field.stringof, messageForUser);
}
return field;
}
class FormValidationException : Exception {
this(
immutable(FunctionInfo)* formFunction,
Form delegate(Document) getForm,
void delegate(Document, Form, FormValidationException) postProcessor,
string[] failed, string[] messagesForUser,
string msg, string file = __FILE__, size_t line = __LINE__)
{
this.formFunction = formFunction;
this.getForm = getForm;
this.postProcessor = postProcessor;
this.failed = failed;
this.messagesForUser = messagesForUser;
super(msg, file, line);
}
// this will be called by the automatic catch
// it goes: Document d = formatAs(formFunction, document);
// then : Form f = getForm(d);
// it will add the values used in the current call to the form with the error conditions
// and finally, postProcessor(d, f, this);
immutable(FunctionInfo)* formFunction;
Form delegate(Document) getForm;
void delegate(Document, Form, FormValidationException) postProcessor;
string[] failed;
string[] messagesForUser;
}
/// throw this if a paramater is invalid. Automatic forms may present this to the user in a new form. (FIXME: implement that)
class InvalidParameterException : Exception {
this(string param, string value, string expected) {
super("bad param: " ~ param ~ ". got: " ~ value ~ ". Expected: " ~expected);
this(string param, string value, string expected, string file = __FILE__, size_t line = __LINE__) {
this.param = param;
super("bad param: " ~ param ~ ". got: " ~ value ~ ". Expected: " ~expected, file, line);
}
/*
The way these are handled automatically is if something fails, web.d will
redirect the user to
formLocation ~ "?" ~ encodeVariables(cgi.get|postArray)
*/
string functionName;
string param;
string formLocation;
}
/// convenience for throwing InvalidParameterExceptions
@ -2347,7 +2538,9 @@ WrapperFunction generateWrapper(alias ObjectType, string funName, alias f, R)(Re
args[i] = cast() cgi.files[using]; // casting away const for the assignment to compile FIXME: shouldn't be needed
} else {
if(using !in sargs) {
static if(parameterHasDefault!(f)(i)) {
static if(isArray!(type) && !isSomeString!(type)) {
args[i] = null;
} else static if(parameterHasDefault!(f)(i)) {
args[i] = mixin(parameterDefaultOf!(f)(i));
} else {
throw new InsufficientParametersException(funName, "arg " ~ name ~ " is not present");
@ -3132,10 +3325,10 @@ struct TemplateFilters {
}
string plural(string replacement, string[] args, in Element, string) {
return pluralHelper(args.length ? args[0] : null, replacement);
return pluralHelper(args.length ? args[0] : null, replacement, args.length > 1 ? args[1] : null);
}
string pluralHelper(string number, string word) {
string pluralHelper(string number, string word, string pluralWord = null) {
if(word.length == 0)
return word;
@ -3146,12 +3339,17 @@ struct TemplateFilters {
if(count == 1)
return word; // it isn't actually plural
if(pluralWord !is null)
return pluralWord;
switch(word[$ - 1]) {
case 's':
case 'a', 'e', 'i', 'o', 'u':
return word ~ "es";
case 'f':
return word[0 .. $-1] ~ "ves";
case 'y':
return word[0 .. $-1] ~ "ies";
default:
return word ~ "s";
}