Mercurial > projects > ldc
view tango/tango/net/http/HttpTokens.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
line wrap: on
line source
/******************************************************************************* copyright: Copyright (c) 2004 Kris Bell. All rights reserved license: BSD style: $(LICENSE) version: Initial release: April 2004 author: Kris *******************************************************************************/ module tango.net.http.HttpTokens; private import tango.time.Time; private import tango.io.Buffer; private import tango.io.model.IBuffer; private import tango.net.http.HttpStack, tango.net.http.HttpConst; private import Text = tango.text.Util; private import tango.io.protocol.model.IWriter; private import Integer = tango.text.convert.Integer; private import TimeStamp = tango.text.convert.TimeStamp; /****************************************************************************** Struct used to expose freachable HttpToken instances. ******************************************************************************/ struct HttpToken { char[] name, value; } /****************************************************************************** Maintains a set of HTTP tokens. These tokens include headers, query- parameters, and anything else vaguely related. Both input and output are supported, though a subclass may choose to expose as read-only. All tokens are mapped directly onto a buffer, so there is no memory allocation or copying involved. Note that this class does not support deleting tokens, per se. Instead it marks tokens as being 'unused' by setting content to null, avoiding unwarranted reshaping of the token stack. The token stack is reused as time goes on, so there's only minor runtime overhead. ******************************************************************************/ class HttpTokens : IWritable { protected HttpStack stack; private IBuffer input, output; private bool parsed; private bool inclusive; private char separator; private char[1] sepString; /********************************************************************** Construct a set of tokens based upon the given delimiter, and an indication of whether said delimiter should be considered part of the left side (effectively the name). The latter is useful with headers, since the seperating ':' character should really be considered part of the name for purposes of subsequent token matching. **********************************************************************/ this (char separator, bool inclusive = false) { stack = new HttpStack; this.inclusive = inclusive; this.separator = separator; // convert separator into a string, for later use sepString[0] = separator; // pre-construct an empty buffer for wrapping char[] parsing input = new Buffer; } /********************************************************************** Clone a source set of HttpTokens **********************************************************************/ this (HttpTokens source) { stack = source.stack.clone; input = null; output = source.output; parsed = true; inclusive = source.inclusive; separator = source.separator; sepString[0] = source.sepString[0]; } /********************************************************************** Read all tokens. Everything is mapped rather than being allocated & copied **********************************************************************/ abstract void parse (IBuffer input); /********************************************************************** Parse an input string. **********************************************************************/ void parse (char[] content) { input.setContent (content); parse (input); } /********************************************************************** Reset this set of tokens. **********************************************************************/ HttpTokens reset () { stack.reset; parsed = false; // reset output buffer, if it was configured if (output) output.clear; return this; } /********************************************************************** Have tokens been parsed yet? **********************************************************************/ bool isParsed () { return parsed; } /********************************************************************** Indicate whether tokens have been parsed or not. **********************************************************************/ void setParsed (bool parsed) { this.parsed = parsed; } /********************************************************************** Return the value of the provided header, or null if the header does not exist **********************************************************************/ char[] get (char[] name, char[] ret = null) { Token token = stack.findToken (name); if (token) { HttpToken element; if (split (token, element)) ret = trim (element.value); } return ret; } /********************************************************************** Return the integer value of the provided header, or the provided default-vaule if the header does not exist **********************************************************************/ int getInt (char[] name, int ret = -1) { char[] value = get (name); if (value.length) ret = cast(int) Integer.parse (value); return ret; } /********************************************************************** Return the date value of the provided header, or the provided default-value if the header does not exist **********************************************************************/ Time getDate (char[] name, Time date = Time.epoch) { char[] value = get (name); if (value.length) date = TimeStamp.parse (value); return date; } /********************************************************************** Iterate over the set of tokens **********************************************************************/ int opApply (int delegate(inout HttpToken) dg) { HttpToken element; int result = 0; foreach (Token t; stack) if (split (t, element)) { result = dg (element); if (result) break; } return result; } /********************************************************************** Output the token list to the provided writer **********************************************************************/ void write (IWriter writer) { produce (&writer.buffer.consume, HttpConst.Eol); } /********************************************************************** Output the token list to the provided consumer **********************************************************************/ void produce (void delegate (void[]) consume, char[] eol) { foreach (Token token; stack) { auto content = token.toString(); if (content.length) consume (content), consume (eol); } } /********************************************************************** overridable method to handle the case where a token does not have a separator. Apparently, this can happen in HTTP usage **********************************************************************/ protected bool handleMissingSeparator (char[] s, inout HttpToken element) { return false; } /********************************************************************** split basic token into an HttpToken **********************************************************************/ final private bool split (Token t, inout HttpToken element) { auto s = t.toString(); if (s.length) { auto i = Text.locate (s, separator); // we should always find the separator if (i < s.length) { auto j = (inclusive) ? i+1 : i; element.name = s[0 .. j]; element.value = (++i < s.length) ? s[i .. $] : null; return true; } else // allow override to specialize this case return handleMissingSeparator (s, element); } return false; } /********************************************************************** Create a filter for iterating over the tokens matching a particular name. **********************************************************************/ FilteredTokens createFilter (char[] match) { return new FilteredTokens (this, match); } /********************************************************************** Implements a filter for iterating over tokens matching a particular name. We do it like this because there's no means of passing additional information to an opApply() method. **********************************************************************/ private static class FilteredTokens { private char[] match; private HttpTokens tokens; /************************************************************** Construct this filter upon the given tokens, and set the pattern to match against. **************************************************************/ this (HttpTokens tokens, char[] match) { this.match = match; this.tokens = tokens; } /************************************************************** Iterate over all tokens matching the given name **************************************************************/ int opApply (int delegate(inout HttpToken) dg) { HttpToken element; int result = 0; foreach (Token token; tokens.stack) if (tokens.stack.isMatch (token, match)) if (tokens.split (token, element)) { result = dg (element); if (result) break; } return result; } } /********************************************************************** Is the argument a whitespace character? **********************************************************************/ private bool isSpace (char c) { return cast(bool) (c is ' ' || c is '\t' || c is '\r' || c is '\n'); } /********************************************************************** Trim the provided string by stripping whitespace from both ends. Returns a slice of the original content. **********************************************************************/ private char[] trim (char[] source) { int front, back = source.length; if (back) { while (front < back && isSpace(source[front])) ++front; while (back > front && isSpace(source[back-1])) --back; } return source [front .. back]; } /********************************************************************** ****************** these should be exposed carefully ****************** **********************************************************************/ /********************************************************************** Set the output buffer for adding tokens to. This is used by the various mutating classes. **********************************************************************/ protected void setOutputBuffer (IBuffer output) { this.output = output; } /********************************************************************** Return the buffer used for output. **********************************************************************/ protected IBuffer getOutputBuffer () { return output; } /********************************************************************** Return a char[] representing the output. An empty array is returned if output was not configured. This perhaps could just return our 'output' buffer content, but that would not reflect deletes, or seperators. Better to do it like this instead, for a small cost. **********************************************************************/ char[] formatTokens (IBuffer dst, char[] delim) { int adjust = 0; foreach (Token token; stack) { char[] content = token.toString; if (content.length) { dst.append(content).append(delim); adjust = delim.length; } } dst.truncate (dst.limit - adjust); return cast(char[]) dst.slice; } /********************************************************************** Add a token with the given name. The content is provided via the specified delegate. We stuff this name & content into the output buffer, and map a new Token onto the appropriate buffer slice. **********************************************************************/ protected void add (char[] name, void delegate (IBuffer) dg) { // save the buffer write-position int prior = output.limit; // add the name output.append (name); // don't append separator if it's already part of the name if (! inclusive) output.append (sepString); // add the value dg (output); // map new token onto buffer slice stack.push (cast(char[]) output.slice [prior .. $]); } /********************************************************************** Add a simple name/value pair to the output **********************************************************************/ protected void add (char[] name, char[] value) { void addValue (IBuffer buffer) { buffer.append (value); } add (name, &addValue); } /********************************************************************** Add a name/integer pair to the output **********************************************************************/ protected void addInt (char[] name, int value) { char[16] tmp = void; add (name, Integer.format (tmp, cast(long) value)); } /********************************************************************** Add a name/date(long) pair to the output **********************************************************************/ protected void addDate (char[] name, Time value) { char[40] tmp = void; add (name, TimeStamp.format (tmp, value)); } /********************************************************************** remove a token from our list. Returns false if the named token is not found. **********************************************************************/ protected bool remove (char[] name) { return stack.removeToken (name); } }