mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-23 06:12:17 -06:00
*Skylark: fixed cookies in redirect
git-svn-id: svn://ultimatepp.org/upp/trunk@5190 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
f18d29f38f
commit
21305f6f70
5 changed files with 995 additions and 998 deletions
|
|
@ -1,402 +1,401 @@
|
|||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) // DLOG(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
Value Raw(const String& s)
|
||||
{
|
||||
RawHtmlText r;
|
||||
r.text = s;
|
||||
return RawToValue(r);
|
||||
}
|
||||
|
||||
VectorMap<String, Value (*)(const Vector<Value>&, const Renderer *)>& Compiler::functions()
|
||||
{
|
||||
static VectorMap<String, Value (*)(const Vector<Value>&, const Renderer *)> x;
|
||||
return x;
|
||||
}
|
||||
|
||||
void Compiler::Register(const String& id, Value (*fn)(const Vector<Value>&, const Renderer *))
|
||||
{
|
||||
functions().GetAdd(id) = fn;
|
||||
}
|
||||
|
||||
int Compiler::ForVar(String id, int i)
|
||||
{
|
||||
if(i + 1 < var.GetCount() && forvar[i])
|
||||
return i + 1;
|
||||
p.ThrowError(id + " is not 'for' iterator");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CountLinkArgs(const Vector<String>& part)
|
||||
{
|
||||
int args = 0;
|
||||
for(int i = 0; i < part.GetCount(); i++) {
|
||||
int p = (byte)*part[i];
|
||||
if(p >= 0 && p < 30)
|
||||
args = max(args, p + 1);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Prim()
|
||||
{
|
||||
One<Exe> result;
|
||||
DDUMP(p.GetPtr());
|
||||
if(p.Char('!'))
|
||||
result = Create<ExeNot>(Prim());
|
||||
else
|
||||
if(p.Char('-'))
|
||||
result = Create<ExeNeg>(Prim());
|
||||
else
|
||||
if(p.Char('+'))
|
||||
result = Prim();
|
||||
else
|
||||
if(p.IsId() || p.IsChar('.') || p.IsChar('@')) {
|
||||
String id;
|
||||
if(p.Char('.'))
|
||||
id = ".";
|
||||
else
|
||||
if(p.Char('@'))
|
||||
id = "@";
|
||||
id << p.ReadId();
|
||||
int n = var.Find(id);
|
||||
if(p.Char('(')) {
|
||||
Value (*f)(const Vector<Value>&, const Renderer *) = functions().Get(id, NULL);
|
||||
if(!f) {
|
||||
Vector<String> *part = GetUrlViewLinkParts(id);
|
||||
if(!part)
|
||||
p.ThrowError("function nor link not found '" + id + "'");
|
||||
ExeLink& ln = result.Create<ExeLink>();
|
||||
ln.part = part;
|
||||
if(!p.Char(')')) {
|
||||
do
|
||||
ln.arg.AddPick(Exp());
|
||||
while(p.Char(','));
|
||||
p.PassChar(')');
|
||||
}
|
||||
if(CountLinkArgs(*part) > ln.arg.GetCount())
|
||||
p.ThrowError("invalid number of link arguments '" + id + "'");
|
||||
}
|
||||
else {
|
||||
ExeFn& fn = result.Create<ExeFn>();
|
||||
fn.fn = f;
|
||||
if(!p.Char(')')) {
|
||||
do
|
||||
fn.arg.AddPick(Exp());
|
||||
while(p.Char(','));
|
||||
p.PassChar(')');
|
||||
}
|
||||
while(p.Char('.')) {
|
||||
One<Exe> r;
|
||||
ExeField& f = r.Create<ExeField>();
|
||||
f.value = result;
|
||||
f.id = p.ReadId();
|
||||
result = r;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if(n < 0) {
|
||||
Vector<String> *part = GetUrlViewLinkParts(id);
|
||||
ExeConst& c = result.Create<ExeConst>();
|
||||
if(!part) {
|
||||
return result;
|
||||
}
|
||||
String l = "\"/";
|
||||
for(int i = 0; i < (*part).GetCount(); i++) {
|
||||
if(i)
|
||||
l << '/';
|
||||
l << UrlEncode((*part)[i]);
|
||||
}
|
||||
l << '\"';
|
||||
c.value = Raw(l);
|
||||
}
|
||||
else
|
||||
if(p.Char('.')) {
|
||||
if(p.Id("_first"))
|
||||
result.Create<ExeFirst>().var_index = ForVar(id, n);
|
||||
else
|
||||
if(p.Id("_last"))
|
||||
result.Create<ExeLast>().var_index = ForVar(id, n);
|
||||
else
|
||||
if(p.Id("_index"))
|
||||
result.Create<ExeIndex>().var_index = ForVar(id, n);
|
||||
else
|
||||
if(p.Id("_key"))
|
||||
result.Create<ExeKey>().var_index = ForVar(id, n);
|
||||
else {
|
||||
result.Create<ExeVar>().var_index = n;
|
||||
do {
|
||||
One<Exe> r;
|
||||
ExeField& f = r.Create<ExeField>();
|
||||
f.value = result;
|
||||
f.id = p.ReadId();
|
||||
result = r;
|
||||
}
|
||||
while(p.Char('.'));
|
||||
}
|
||||
}
|
||||
else
|
||||
result.Create<ExeVar>().var_index = n;
|
||||
}
|
||||
else
|
||||
if(p.Char('{')) {
|
||||
ExeMap& m = result.Create<ExeMap>();
|
||||
do {
|
||||
m.key.AddPick(Exp());
|
||||
p.PassChar(':');
|
||||
m.value.AddPick(Exp());
|
||||
}
|
||||
while(p.Char(','));
|
||||
p.PassChar('}');
|
||||
}
|
||||
else
|
||||
if(p.Char('[')) {
|
||||
ExeArray& m = result.Create<ExeArray>();
|
||||
do {
|
||||
m.item.AddPick(Exp());
|
||||
}
|
||||
while(p.Char(','));
|
||||
p.PassChar(']');
|
||||
}
|
||||
else
|
||||
if(p.Char('(')) {
|
||||
result = Exp();
|
||||
p.PassChar(')');
|
||||
}
|
||||
else {
|
||||
ExeConst& c = result.Create<ExeConst>();
|
||||
if(p.Char2('0', 'x') || p.Char2('0', 'X'))
|
||||
c.value = (int)p.ReadNumber(16);
|
||||
else
|
||||
if(p.Char('0'))
|
||||
c.value = int(p.IsNumber() ? p.ReadNumber(8) : 0);
|
||||
else
|
||||
c.value = p.IsString() ? Value(p.ReadString()) : Value(p.ReadDouble());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Mul()
|
||||
{
|
||||
One<Exe> result = Prim();
|
||||
for(;;)
|
||||
if(p.Char('*'))
|
||||
result = Create<ExeMul>(result, Prim());
|
||||
else
|
||||
if(p.Char('/'))
|
||||
result = Create<ExeDiv>(result, Prim());
|
||||
else
|
||||
if(p.Char('%'))
|
||||
result = Create<ExeMod>(result, Prim());
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Add()
|
||||
{
|
||||
One<Exe> result = Mul();
|
||||
for(;;)
|
||||
if(p.Char('+'))
|
||||
result = Create<ExeAdd>(result, Mul());
|
||||
else
|
||||
if(p.Char('-'))
|
||||
result = Create<ExeSub>(result, Mul());
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Shift()
|
||||
{
|
||||
One<Exe> result = Add();
|
||||
for(;;)
|
||||
if(p.Char3('>', '>', '>'))
|
||||
result = Create<ExeSrl>(result, Add());
|
||||
else
|
||||
if(p.Char2('>', '>'))
|
||||
result = Create<ExeSra>(result, Add());
|
||||
else
|
||||
if(p.Char2('<', '<'))
|
||||
result = Create<ExeSll>(result, Add());
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Rel()
|
||||
{
|
||||
One<Exe> result = Shift();
|
||||
for(;;)
|
||||
if(p.Char2('<', '='))
|
||||
result = Create<ExeLte>(result, Shift());
|
||||
else
|
||||
if(p.Char2('>', '='))
|
||||
result = Create<ExeLte>(Shift(), result);
|
||||
else
|
||||
if(p.Char('<'))
|
||||
result = Create<ExeLt>(result, Shift());
|
||||
else
|
||||
if(p.Char('>'))
|
||||
result = Create<ExeLt>(Shift(), result);
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Eq()
|
||||
{
|
||||
One<Exe> result = Rel();
|
||||
for(;;)
|
||||
if(p.Char2('=', '='))
|
||||
result = Create<ExeEq>(result, Rel());
|
||||
else
|
||||
if(p.Char2('!', '='))
|
||||
result = Create<ExeNeq>(Rel(), result);
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::And()
|
||||
{
|
||||
One<Exe> result = Eq();
|
||||
while(!p.IsChar2('&', '&') && p.Char('&'))
|
||||
result = Create<ExeAnd>(result, Eq());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Xor()
|
||||
{
|
||||
One<Exe> result = And();
|
||||
while(p.Char('^'))
|
||||
result = Create<ExeXor>(result, And());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Or()
|
||||
{
|
||||
One<Exe> result = Xor();
|
||||
while(!p.IsChar2('|', '|') && p.Char('|'))
|
||||
result = Create<ExeOr>(result, Xor());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::LogAnd()
|
||||
{
|
||||
One<Exe> result = Or();
|
||||
while(p.Char2('&', '&'))
|
||||
result = Create<ExeAnl>(result, Or());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::LogOr()
|
||||
{
|
||||
One<Exe> result = LogAnd();
|
||||
while(p.Char2('|', '|'))
|
||||
result = Create<ExeOrl>(result, LogAnd());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Conditional()
|
||||
{
|
||||
One<Exe> result = LogOr();
|
||||
if(p.Char('?')) {
|
||||
One<Exe> r;
|
||||
ExeCond& c = r.Create<ExeCond>();
|
||||
c.cond = result;
|
||||
c.ontrue = LogOr();
|
||||
p.PassChar(':');
|
||||
c.onfalse = LogOr();
|
||||
result = r;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Exp()
|
||||
{
|
||||
return Conditional();
|
||||
}
|
||||
|
||||
void Compiler::ExeBlock::AddText(const char *b, const char *s)
|
||||
{
|
||||
if(s > b) {
|
||||
RawHtmlText t;
|
||||
t.text = String(b, s);
|
||||
item.Add().Create<ExeConst>().value = RawToValue(t);
|
||||
}
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Block()
|
||||
{
|
||||
One<Exe> result;
|
||||
ExeBlock& blk = result.Create<ExeBlock>();
|
||||
const char *s = p.GetSpacePtr();
|
||||
const char *b = s;
|
||||
int line = 1;
|
||||
while(*s) {
|
||||
if(*s == '$') {
|
||||
if(s[1] == '$')
|
||||
s += 2;
|
||||
else {
|
||||
blk.AddText(b, s);
|
||||
p.Set(s + 1, NULL, line);
|
||||
if(p.Id("if")) {
|
||||
ExeCond& c = blk.item.Add().Create<ExeCond>();
|
||||
p.PassChar('(');
|
||||
c.cond = Exp();
|
||||
p.PassChar(')');
|
||||
c.ontrue = Block();
|
||||
if(p.Id("else"))
|
||||
c.onfalse = Block();
|
||||
if(!p.Char('/'))
|
||||
p.PassId("endif");
|
||||
}
|
||||
else
|
||||
if(p.Id("for")) {
|
||||
ExeFor& c = blk.item.Add().Create<ExeFor>();
|
||||
p.PassChar('(');
|
||||
int q = var.GetCount();
|
||||
var.Add(p.ReadId());
|
||||
var.Add(Null); // LoopInfo placeholder
|
||||
forvar.Add(true);
|
||||
forvar.Add(true);
|
||||
p.PassId("in");
|
||||
c.value = Exp();
|
||||
p.PassChar(')');
|
||||
c.body = Block();
|
||||
var.Trim(q);
|
||||
forvar.SetCount(q);
|
||||
if(p.Id("else"))
|
||||
c.onempty = Block();
|
||||
if(!p.Char('/'))
|
||||
p.PassId("endfor");
|
||||
}
|
||||
else
|
||||
if(p.IsId("else") || p.IsId("endif") || p.IsId("endfor") || p.IsChar('/'))
|
||||
return result;
|
||||
else
|
||||
blk.item.AddPick(Prim());
|
||||
b = s = p.GetSpacePtr();
|
||||
line = p.GetLine();
|
||||
}
|
||||
}
|
||||
else
|
||||
if(*s++ == '\n')
|
||||
line++;
|
||||
}
|
||||
blk.AddText(b, s);
|
||||
p.Set(s, NULL, line);
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compile(const char *code, const Index<String>& vars)
|
||||
{
|
||||
Compiler c(code, vars);
|
||||
One<Exe> exe = c.Block();
|
||||
LLOG("Before optimization node count: " << c.GetNodeCount(exe));
|
||||
c.Optimize(exe);
|
||||
LLOG("After optimization node count: " << c.GetNodeCount(exe));
|
||||
return exe;
|
||||
}
|
||||
|
||||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) // DLOG(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
Value Raw(const String& s)
|
||||
{
|
||||
RawHtmlText r;
|
||||
r.text = s;
|
||||
return RawToValue(r);
|
||||
}
|
||||
|
||||
VectorMap<String, Value (*)(const Vector<Value>&, const Renderer *)>& Compiler::functions()
|
||||
{
|
||||
static VectorMap<String, Value (*)(const Vector<Value>&, const Renderer *)> x;
|
||||
return x;
|
||||
}
|
||||
|
||||
void Compiler::Register(const String& id, Value (*fn)(const Vector<Value>&, const Renderer *))
|
||||
{
|
||||
functions().GetAdd(id) = fn;
|
||||
}
|
||||
|
||||
int Compiler::ForVar(String id, int i)
|
||||
{
|
||||
if(i + 1 < var.GetCount() && forvar[i])
|
||||
return i + 1;
|
||||
p.ThrowError(id + " is not 'for' iterator");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CountLinkArgs(const Vector<String>& part)
|
||||
{
|
||||
int args = 0;
|
||||
for(int i = 0; i < part.GetCount(); i++) {
|
||||
int p = (byte)*part[i];
|
||||
if(p >= 0 && p < 30)
|
||||
args = max(args, p + 1);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Prim()
|
||||
{
|
||||
One<Exe> result;
|
||||
if(p.Char('!'))
|
||||
result = Create<ExeNot>(Prim());
|
||||
else
|
||||
if(p.Char('-'))
|
||||
result = Create<ExeNeg>(Prim());
|
||||
else
|
||||
if(p.Char('+'))
|
||||
result = Prim();
|
||||
else
|
||||
if(p.IsId() || p.IsChar('.') || p.IsChar('@')) {
|
||||
String id;
|
||||
if(p.Char('.'))
|
||||
id = ".";
|
||||
else
|
||||
if(p.Char('@'))
|
||||
id = "@";
|
||||
id << p.ReadId();
|
||||
int n = var.Find(id);
|
||||
if(p.Char('(')) {
|
||||
Value (*f)(const Vector<Value>&, const Renderer *) = functions().Get(id, NULL);
|
||||
if(!f) {
|
||||
Vector<String> *part = GetUrlViewLinkParts(id);
|
||||
if(!part)
|
||||
p.ThrowError("function nor link not found '" + id + "'");
|
||||
ExeLink& ln = result.Create<ExeLink>();
|
||||
ln.part = part;
|
||||
if(!p.Char(')')) {
|
||||
do
|
||||
ln.arg.AddPick(Exp());
|
||||
while(p.Char(','));
|
||||
p.PassChar(')');
|
||||
}
|
||||
if(CountLinkArgs(*part) > ln.arg.GetCount())
|
||||
p.ThrowError("invalid number of link arguments '" + id + "'");
|
||||
}
|
||||
else {
|
||||
ExeFn& fn = result.Create<ExeFn>();
|
||||
fn.fn = f;
|
||||
if(!p.Char(')')) {
|
||||
do
|
||||
fn.arg.AddPick(Exp());
|
||||
while(p.Char(','));
|
||||
p.PassChar(')');
|
||||
}
|
||||
while(p.Char('.')) {
|
||||
One<Exe> r;
|
||||
ExeField& f = r.Create<ExeField>();
|
||||
f.value = result;
|
||||
f.id = p.ReadId();
|
||||
result = r;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if(n < 0) {
|
||||
Vector<String> *part = GetUrlViewLinkParts(id);
|
||||
ExeConst& c = result.Create<ExeConst>();
|
||||
if(!part) {
|
||||
return result;
|
||||
}
|
||||
String l = "\"/";
|
||||
for(int i = 0; i < (*part).GetCount(); i++) {
|
||||
if(i)
|
||||
l << '/';
|
||||
l << UrlEncode((*part)[i]);
|
||||
}
|
||||
l << '\"';
|
||||
c.value = Raw(l);
|
||||
}
|
||||
else
|
||||
if(p.Char('.')) {
|
||||
if(p.Id("_first"))
|
||||
result.Create<ExeFirst>().var_index = ForVar(id, n);
|
||||
else
|
||||
if(p.Id("_last"))
|
||||
result.Create<ExeLast>().var_index = ForVar(id, n);
|
||||
else
|
||||
if(p.Id("_index"))
|
||||
result.Create<ExeIndex>().var_index = ForVar(id, n);
|
||||
else
|
||||
if(p.Id("_key"))
|
||||
result.Create<ExeKey>().var_index = ForVar(id, n);
|
||||
else {
|
||||
result.Create<ExeVar>().var_index = n;
|
||||
do {
|
||||
One<Exe> r;
|
||||
ExeField& f = r.Create<ExeField>();
|
||||
f.value = result;
|
||||
f.id = p.ReadId();
|
||||
result = r;
|
||||
}
|
||||
while(p.Char('.'));
|
||||
}
|
||||
}
|
||||
else
|
||||
result.Create<ExeVar>().var_index = n;
|
||||
}
|
||||
else
|
||||
if(p.Char('{')) {
|
||||
ExeMap& m = result.Create<ExeMap>();
|
||||
do {
|
||||
m.key.AddPick(Exp());
|
||||
p.PassChar(':');
|
||||
m.value.AddPick(Exp());
|
||||
}
|
||||
while(p.Char(','));
|
||||
p.PassChar('}');
|
||||
}
|
||||
else
|
||||
if(p.Char('[')) {
|
||||
ExeArray& m = result.Create<ExeArray>();
|
||||
do {
|
||||
m.item.AddPick(Exp());
|
||||
}
|
||||
while(p.Char(','));
|
||||
p.PassChar(']');
|
||||
}
|
||||
else
|
||||
if(p.Char('(')) {
|
||||
result = Exp();
|
||||
p.PassChar(')');
|
||||
}
|
||||
else {
|
||||
ExeConst& c = result.Create<ExeConst>();
|
||||
if(p.Char2('0', 'x') || p.Char2('0', 'X'))
|
||||
c.value = (int)p.ReadNumber(16);
|
||||
else
|
||||
if(p.Char('0'))
|
||||
c.value = int(p.IsNumber() ? p.ReadNumber(8) : 0);
|
||||
else
|
||||
c.value = p.IsString() ? Value(p.ReadString()) : Value(p.ReadDouble());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Mul()
|
||||
{
|
||||
One<Exe> result = Prim();
|
||||
for(;;)
|
||||
if(p.Char('*'))
|
||||
result = Create<ExeMul>(result, Prim());
|
||||
else
|
||||
if(p.Char('/'))
|
||||
result = Create<ExeDiv>(result, Prim());
|
||||
else
|
||||
if(p.Char('%'))
|
||||
result = Create<ExeMod>(result, Prim());
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Add()
|
||||
{
|
||||
One<Exe> result = Mul();
|
||||
for(;;)
|
||||
if(p.Char('+'))
|
||||
result = Create<ExeAdd>(result, Mul());
|
||||
else
|
||||
if(p.Char('-'))
|
||||
result = Create<ExeSub>(result, Mul());
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Shift()
|
||||
{
|
||||
One<Exe> result = Add();
|
||||
for(;;)
|
||||
if(p.Char3('>', '>', '>'))
|
||||
result = Create<ExeSrl>(result, Add());
|
||||
else
|
||||
if(p.Char2('>', '>'))
|
||||
result = Create<ExeSra>(result, Add());
|
||||
else
|
||||
if(p.Char2('<', '<'))
|
||||
result = Create<ExeSll>(result, Add());
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Rel()
|
||||
{
|
||||
One<Exe> result = Shift();
|
||||
for(;;)
|
||||
if(p.Char2('<', '='))
|
||||
result = Create<ExeLte>(result, Shift());
|
||||
else
|
||||
if(p.Char2('>', '='))
|
||||
result = Create<ExeLte>(Shift(), result);
|
||||
else
|
||||
if(p.Char('<'))
|
||||
result = Create<ExeLt>(result, Shift());
|
||||
else
|
||||
if(p.Char('>'))
|
||||
result = Create<ExeLt>(Shift(), result);
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Eq()
|
||||
{
|
||||
One<Exe> result = Rel();
|
||||
for(;;)
|
||||
if(p.Char2('=', '='))
|
||||
result = Create<ExeEq>(result, Rel());
|
||||
else
|
||||
if(p.Char2('!', '='))
|
||||
result = Create<ExeNeq>(Rel(), result);
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::And()
|
||||
{
|
||||
One<Exe> result = Eq();
|
||||
while(!p.IsChar2('&', '&') && p.Char('&'))
|
||||
result = Create<ExeAnd>(result, Eq());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Xor()
|
||||
{
|
||||
One<Exe> result = And();
|
||||
while(p.Char('^'))
|
||||
result = Create<ExeXor>(result, And());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Or()
|
||||
{
|
||||
One<Exe> result = Xor();
|
||||
while(!p.IsChar2('|', '|') && p.Char('|'))
|
||||
result = Create<ExeOr>(result, Xor());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::LogAnd()
|
||||
{
|
||||
One<Exe> result = Or();
|
||||
while(p.Char2('&', '&'))
|
||||
result = Create<ExeAnl>(result, Or());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::LogOr()
|
||||
{
|
||||
One<Exe> result = LogAnd();
|
||||
while(p.Char2('|', '|'))
|
||||
result = Create<ExeOrl>(result, LogAnd());
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Conditional()
|
||||
{
|
||||
One<Exe> result = LogOr();
|
||||
if(p.Char('?')) {
|
||||
One<Exe> r;
|
||||
ExeCond& c = r.Create<ExeCond>();
|
||||
c.cond = result;
|
||||
c.ontrue = LogOr();
|
||||
p.PassChar(':');
|
||||
c.onfalse = LogOr();
|
||||
result = r;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Exp()
|
||||
{
|
||||
return Conditional();
|
||||
}
|
||||
|
||||
void Compiler::ExeBlock::AddText(const char *b, const char *s)
|
||||
{
|
||||
if(s > b) {
|
||||
RawHtmlText t;
|
||||
t.text = String(b, s);
|
||||
item.Add().Create<ExeConst>().value = RawToValue(t);
|
||||
}
|
||||
}
|
||||
|
||||
One<Exe> Compiler::Block()
|
||||
{
|
||||
One<Exe> result;
|
||||
ExeBlock& blk = result.Create<ExeBlock>();
|
||||
const char *s = p.GetSpacePtr();
|
||||
const char *b = s;
|
||||
int line = 1;
|
||||
while(*s) {
|
||||
if(*s == '$') {
|
||||
if(s[1] == '$')
|
||||
s += 2;
|
||||
else {
|
||||
blk.AddText(b, s);
|
||||
p.Set(s + 1, NULL, line);
|
||||
if(p.Id("if")) {
|
||||
ExeCond& c = blk.item.Add().Create<ExeCond>();
|
||||
p.PassChar('(');
|
||||
c.cond = Exp();
|
||||
p.PassChar(')');
|
||||
c.ontrue = Block();
|
||||
if(p.Id("else"))
|
||||
c.onfalse = Block();
|
||||
if(!p.Char('/'))
|
||||
p.PassId("endif");
|
||||
}
|
||||
else
|
||||
if(p.Id("for")) {
|
||||
ExeFor& c = blk.item.Add().Create<ExeFor>();
|
||||
p.PassChar('(');
|
||||
int q = var.GetCount();
|
||||
var.Add(p.ReadId());
|
||||
var.Add(Null); // LoopInfo placeholder
|
||||
forvar.Add(true);
|
||||
forvar.Add(true);
|
||||
p.PassId("in");
|
||||
c.value = Exp();
|
||||
p.PassChar(')');
|
||||
c.body = Block();
|
||||
var.Trim(q);
|
||||
forvar.SetCount(q);
|
||||
if(p.Id("else"))
|
||||
c.onempty = Block();
|
||||
if(!p.Char('/'))
|
||||
p.PassId("endfor");
|
||||
}
|
||||
else
|
||||
if(p.IsId("else") || p.IsId("endif") || p.IsId("endfor") || p.IsChar('/'))
|
||||
return result;
|
||||
else
|
||||
blk.item.AddPick(Prim());
|
||||
b = s = p.GetSpacePtr();
|
||||
line = p.GetLine();
|
||||
}
|
||||
}
|
||||
else
|
||||
if(*s++ == '\n')
|
||||
line++;
|
||||
}
|
||||
blk.AddText(b, s);
|
||||
p.Set(s, NULL, line);
|
||||
return result;
|
||||
}
|
||||
|
||||
One<Exe> Compile(const char *code, const Index<String>& vars)
|
||||
{
|
||||
Compiler c(code, vars);
|
||||
One<Exe> exe = c.Block();
|
||||
LLOG("Before optimization node count: " << c.GetNodeCount(exe));
|
||||
c.Optimize(exe);
|
||||
LLOG("After optimization node count: " << c.GetNodeCount(exe));
|
||||
return exe;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -380,6 +380,8 @@ void Http::Dispatch(TcpSocket& socket)
|
|||
SKYLARKLOG("Redirect to: " << redirect);
|
||||
r << "HTTP/1.1 " << code << " Found\r\n";
|
||||
r << "Location: " << redirect << "\r\n";
|
||||
for(int i = 0; i < cookies.GetCount(); i++)
|
||||
r << cookies[i];
|
||||
}
|
||||
else {
|
||||
r <<
|
||||
|
|
@ -390,8 +392,6 @@ void Http::Dispatch(TcpSocket& socket)
|
|||
"Connection: close\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Content-Type: " << content_type << "\r\n";
|
||||
for(int i = 0; i < cookies.GetCount(); i++)
|
||||
r << cookies[i];
|
||||
r << "\r\n";
|
||||
}
|
||||
socket.PutAll(r);
|
||||
|
|
|
|||
|
|
@ -1,350 +1,350 @@
|
|||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) LOG(x)
|
||||
#define LTIMING(x) RTIMING(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
Http::Http(SkylarkApp& app)
|
||||
: app(app)
|
||||
{
|
||||
code = 200;
|
||||
content_type = "text/html; charset=UTF-8";
|
||||
session_dirty = false;
|
||||
lang = LNG_ENGLISH;
|
||||
}
|
||||
|
||||
void Http::ParseRequest(const char *p)
|
||||
{
|
||||
while(*p) {
|
||||
const char *last = p;
|
||||
while(*p && *p != '=' && *p != '&')
|
||||
p++;
|
||||
String key = UrlDecode(last, p);
|
||||
if(*p == '=')
|
||||
p++;
|
||||
last = p;
|
||||
while(*p && *p != '&')
|
||||
p++;
|
||||
if(*key != '.' && *key != '@')
|
||||
var.GetAdd(key) = UrlDecode(last, p);
|
||||
if(*p)
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
String HttpResponse(int code, const char *phrase, const String& data, const char *content_type,
|
||||
const char *cookies)
|
||||
{
|
||||
String r;
|
||||
r <<
|
||||
"HTTP/1.0 " << code << ' ' << phrase << "\r\n"
|
||||
"Date: " << WwwFormat(GetUtcTime()) << "\r\n"
|
||||
"Server: U++\r\n"
|
||||
"Content-Length: " << data.GetCount() << "\r\n"
|
||||
"Connection: close\r\n"
|
||||
<< cookies;
|
||||
if(content_type)
|
||||
r << "Content-Type: " << content_type << "\r\n";
|
||||
r << "\r\n" << data;
|
||||
return r;
|
||||
}
|
||||
|
||||
Http& Http::SetRawCookie(const char *id, const String& value, Time expires,
|
||||
const char *path, const char *domain, bool secure,
|
||||
bool httponly)
|
||||
{
|
||||
if(*id == '.')
|
||||
id++;
|
||||
var.GetAdd('@' + id) = value;
|
||||
String& c = cookies.GetAdd(id);
|
||||
c.Clear();
|
||||
c << "Set-Cookie:" << ' ' << id << '=' << value;
|
||||
if(!IsNull(expires))
|
||||
c << "; " << WwwFormat(expires);
|
||||
c << "; Path=" << (path && *path ? path : "/");
|
||||
if(domain && *domain)
|
||||
c << "; Domain=" << domain;
|
||||
if(secure)
|
||||
c << "; Secure";
|
||||
if(httponly)
|
||||
c << "; HttpOnly";
|
||||
c << "\r\n";
|
||||
return *this;
|
||||
}
|
||||
|
||||
int Http::Int(const char *id) const
|
||||
{
|
||||
Value v = operator[](id);
|
||||
if(v.Is<int>())
|
||||
return v;
|
||||
if(IsString(v))
|
||||
return ScanInt((String)v);
|
||||
if(IsNull(v))
|
||||
return Null;
|
||||
if(IsNumber(v)) {
|
||||
double d = v;
|
||||
if(d > INT_MIN && d <= INT_MAX)
|
||||
return (int)d;
|
||||
}
|
||||
return Null;
|
||||
}
|
||||
|
||||
int Http::Int(int i) const
|
||||
{
|
||||
return ScanInt(operator[](i));
|
||||
}
|
||||
|
||||
String HttpAsString(const Value& v)
|
||||
{
|
||||
if(v.Is<RawHtmlText>())
|
||||
return v.To<RawHtmlText>().text;
|
||||
return AsString(v);
|
||||
}
|
||||
|
||||
Http& Http::Content(const char *s, const Value& data)
|
||||
{
|
||||
content_type = s;
|
||||
response = HttpAsString(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::operator<<(const Value& s)
|
||||
{
|
||||
response << HttpAsString(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::SetCookie(const char *id, const String& value, Time expires,
|
||||
const char *path, const char *domain, bool secure, bool httponly)
|
||||
{
|
||||
return SetRawCookie(id, UrlEncode(value), expires, path, domain, secure);
|
||||
}
|
||||
|
||||
void Http::ReadMultiPart(const String& buffer)
|
||||
{
|
||||
const char *p = buffer;
|
||||
while(p[0] != '-' || p[1] != '-') {
|
||||
while(*p != '\n')
|
||||
if(*p++ == 0)
|
||||
return; // end of file, boundary not found
|
||||
p++;
|
||||
}
|
||||
String delimiter;
|
||||
{ // read multipart delimiter
|
||||
const char *b = (p += 2);
|
||||
while(*p && *p++ != '\n')
|
||||
;
|
||||
const char *e = p;
|
||||
while(e > b && (byte)e[-1] <= ' ')
|
||||
e--;
|
||||
delimiter = String(b, e);
|
||||
}
|
||||
|
||||
int delta = 4 + delimiter.GetLength();
|
||||
const char *e = buffer.End();
|
||||
if(e - p < delta)
|
||||
return;
|
||||
e -= delta;
|
||||
while(p < e) { // read individual parts
|
||||
String filename, content_type, name;
|
||||
while(!MemICmp(p, "content-", 8)) { // parse content specifiers
|
||||
p += 8;
|
||||
if(!MemICmp(p, "disposition:", 12)) {
|
||||
p += 12;
|
||||
while(*p && *p != '\n')
|
||||
if((byte)*p <= ' ')
|
||||
p++;
|
||||
else { // fetch key-value pair
|
||||
const char *kp = p;
|
||||
while(*p && *p != '\n' && *p != '=' && *p != ';')
|
||||
p++;
|
||||
const char *ke = p;
|
||||
String value;
|
||||
if(*p == '=') {
|
||||
const char *b = ++p;
|
||||
if(*p == '\"') { // quoted value
|
||||
b++;
|
||||
while(*++p && *p != '\n' && *p != '\"')
|
||||
;
|
||||
value = String(b, p);
|
||||
if(*p == '\"')
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
while(*p && *p != '\n' && *p != ';')
|
||||
p++;
|
||||
value = String(b, p);
|
||||
}
|
||||
}
|
||||
if(ke - kp == 4 && !MemICmp(kp, "name", 4))
|
||||
name = value;
|
||||
else if(ke - kp == 8 && !MemICmp(kp, "filename", 8))
|
||||
filename = value;
|
||||
if(*p == ';')
|
||||
p++;
|
||||
}
|
||||
}
|
||||
else if(!MemICmp(p, "type:", 5)) {
|
||||
p += 5;
|
||||
while(*p && *p != '\n' && (byte)*p <= ' ')
|
||||
p++;
|
||||
const char *b = p;
|
||||
while(*p && *p != '\n')
|
||||
p++;
|
||||
const char *e = p;
|
||||
while(e > b && (byte)e[-1] <= ' ')
|
||||
e--;
|
||||
content_type = String(b, e);
|
||||
}
|
||||
;
|
||||
while(*p && *p++ != '\n')
|
||||
;
|
||||
}
|
||||
if(*p++ != '\r' || *p++ != '\n')
|
||||
return;
|
||||
const char *b = p;
|
||||
while(p < e) {
|
||||
p = (const char *)memchr(p, '\r', e - p);
|
||||
if(!p)
|
||||
return;
|
||||
if(p[0] == '\r' && p[1] == '\n' && p[2] == '-' && p[3] == '-'
|
||||
&& !memcmp(p + 4, delimiter, delimiter.GetLength()))
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
if(!name.IsEmpty() && *name != '.' && *name != '@') { // add variables
|
||||
if(!filename.IsEmpty())
|
||||
var.GetAdd(name + ".filename") = filename;
|
||||
if(!content_type.IsEmpty())
|
||||
var.GetAdd(name + ".content_type") = content_type;
|
||||
var.Add(name, String(b, p));
|
||||
}
|
||||
p += delta;
|
||||
while(*p && *p++ != '\n')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static const char hex_digits[] = "0123456789ABCDEF";
|
||||
|
||||
void UrlEncode(StringBuffer& out, const String& s)
|
||||
{
|
||||
static bool ok[256];
|
||||
ONCELOCK {
|
||||
for(int ch = 0; ch < 256; ch++)
|
||||
ok[ch] = IsAlNum(ch) || ch == ',' || ch == '.' || ch == '-' || ch == '_';
|
||||
}
|
||||
const char *p = s, *e = s.End();
|
||||
for(; p < e; p++)
|
||||
{
|
||||
const char *b = p;
|
||||
while(p < e && ok[(byte)*p])
|
||||
p++;
|
||||
if(p > b)
|
||||
out.Cat(b, int(p - b));
|
||||
if(p >= e)
|
||||
break;
|
||||
if(*p == ' ')
|
||||
out << '+';
|
||||
else
|
||||
out << '%' << hex_digits[(*p >> 4) & 15] << hex_digits[*p & 15];
|
||||
}
|
||||
}
|
||||
|
||||
void MakeLink(StringBuffer& out, const Vector<String>& part, const Vector<Value>& arg)
|
||||
{
|
||||
LTIMING("MakeLink");
|
||||
out.Cat("/");
|
||||
for(int i = 0; i < part.GetCount(); i++) {
|
||||
const String& p = part[i];
|
||||
if(i)
|
||||
out << '/';
|
||||
int q = (byte)*p;
|
||||
if(q < 32) {
|
||||
if(q >= 0 && q < arg.GetCount())
|
||||
UrlEncode(out, AsString(arg[q]));
|
||||
}
|
||||
else
|
||||
UrlEncode(out, p);
|
||||
}
|
||||
bool get = false;
|
||||
for(int i = 0; i < arg.GetCount(); i++)
|
||||
if(IsValueMap(arg[i])) {
|
||||
if(get)
|
||||
out << '&';
|
||||
else
|
||||
out << '?';
|
||||
get = true;
|
||||
ValueMap m = arg[i];
|
||||
for(int i = 0; i < m.GetCount(); i++) {
|
||||
if(i)
|
||||
out << '&';
|
||||
UrlEncode(out, AsString(m.GetKeys()[i]));
|
||||
out << '=';
|
||||
UrlEncode(out, AsString(m.GetValues()[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Http& Http::RenderResult(const char *template_name)
|
||||
{
|
||||
LTIMING("Render");
|
||||
response << UPP::Render(GetTemplate(template_name), this, var.GetValues());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&), const Vector<Value>& arg)
|
||||
{
|
||||
Redirect(MakeLink(view, arg));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&))
|
||||
{
|
||||
Vector<Value> arg;
|
||||
Redirect(view, arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&), const Value& v1)
|
||||
{
|
||||
Vector<Value> arg;
|
||||
arg.Add(v1);
|
||||
Redirect(view, arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&), const Value& v1, const Value& v2)
|
||||
{
|
||||
Vector<Value> arg;
|
||||
arg.Add(v1);
|
||||
arg.Add(v2);
|
||||
Redirect(view, arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Ux(const char *id, const String& text)
|
||||
{
|
||||
if(response.GetCount())
|
||||
response << '\1';
|
||||
response << id << ':' << text;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::UxRender(const char *id, const char *template_name)
|
||||
{
|
||||
return Ux(id, RenderString(template_name));
|
||||
}
|
||||
|
||||
Http& Http::UxSet(const char *id, const String& value)
|
||||
{
|
||||
return Ux(String(">") + id, value);
|
||||
}
|
||||
|
||||
Http& Http::UxRun(const String& js_code)
|
||||
{
|
||||
return Ux("!", js_code);
|
||||
}
|
||||
|
||||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) // DLOG(x)
|
||||
#define LTIMING(x) // RTIMING(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
Http::Http(SkylarkApp& app)
|
||||
: app(app)
|
||||
{
|
||||
code = 200;
|
||||
content_type = "text/html; charset=UTF-8";
|
||||
session_dirty = false;
|
||||
lang = LNG_ENGLISH;
|
||||
}
|
||||
|
||||
void Http::ParseRequest(const char *p)
|
||||
{
|
||||
while(*p) {
|
||||
const char *last = p;
|
||||
while(*p && *p != '=' && *p != '&')
|
||||
p++;
|
||||
String key = UrlDecode(last, p);
|
||||
if(*p == '=')
|
||||
p++;
|
||||
last = p;
|
||||
while(*p && *p != '&')
|
||||
p++;
|
||||
if(*key != '.' && *key != '@')
|
||||
var.GetAdd(key) = UrlDecode(last, p);
|
||||
if(*p)
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
String HttpResponse(int code, const char *phrase, const String& data, const char *content_type,
|
||||
const char *cookies)
|
||||
{
|
||||
String r;
|
||||
r <<
|
||||
"HTTP/1.0 " << code << ' ' << phrase << "\r\n"
|
||||
"Date: " << WwwFormat(GetUtcTime()) << "\r\n"
|
||||
"Server: U++\r\n"
|
||||
"Content-Length: " << data.GetCount() << "\r\n"
|
||||
"Connection: close\r\n"
|
||||
<< cookies;
|
||||
if(content_type)
|
||||
r << "Content-Type: " << content_type << "\r\n";
|
||||
r << "\r\n" << data;
|
||||
return r;
|
||||
}
|
||||
|
||||
Http& Http::SetRawCookie(const char *id, const String& value, Time expires,
|
||||
const char *path, const char *domain, bool secure,
|
||||
bool httponly)
|
||||
{
|
||||
if(*id == '.')
|
||||
id++;
|
||||
var.GetAdd('@' + id) = value;
|
||||
String& c = cookies.GetAdd(id);
|
||||
c.Clear();
|
||||
c << "Set-Cookie:" << ' ' << id << '=' << value;
|
||||
if(!IsNull(expires))
|
||||
c << "; " << WwwFormat(expires);
|
||||
c << "; Path=" << (path && *path ? path : "/");
|
||||
if(domain && *domain)
|
||||
c << "; Domain=" << domain;
|
||||
if(secure)
|
||||
c << "; Secure";
|
||||
if(httponly)
|
||||
c << "; HttpOnly";
|
||||
c << "\r\n";
|
||||
return *this;
|
||||
}
|
||||
|
||||
int Http::Int(const char *id) const
|
||||
{
|
||||
Value v = operator[](id);
|
||||
if(v.Is<int>())
|
||||
return v;
|
||||
if(IsString(v))
|
||||
return ScanInt((String)v);
|
||||
if(IsNull(v))
|
||||
return Null;
|
||||
if(IsNumber(v)) {
|
||||
double d = v;
|
||||
if(d > INT_MIN && d <= INT_MAX)
|
||||
return (int)d;
|
||||
}
|
||||
return Null;
|
||||
}
|
||||
|
||||
int Http::Int(int i) const
|
||||
{
|
||||
return ScanInt(operator[](i));
|
||||
}
|
||||
|
||||
String HttpAsString(const Value& v)
|
||||
{
|
||||
if(v.Is<RawHtmlText>())
|
||||
return v.To<RawHtmlText>().text;
|
||||
return AsString(v);
|
||||
}
|
||||
|
||||
Http& Http::Content(const char *s, const Value& data)
|
||||
{
|
||||
content_type = s;
|
||||
response = HttpAsString(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::operator<<(const Value& s)
|
||||
{
|
||||
response << HttpAsString(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::SetCookie(const char *id, const String& value, Time expires,
|
||||
const char *path, const char *domain, bool secure, bool httponly)
|
||||
{
|
||||
return SetRawCookie(id, UrlEncode(value), expires, path, domain, secure);
|
||||
}
|
||||
|
||||
void Http::ReadMultiPart(const String& buffer)
|
||||
{
|
||||
const char *p = buffer;
|
||||
while(p[0] != '-' || p[1] != '-') {
|
||||
while(*p != '\n')
|
||||
if(*p++ == 0)
|
||||
return; // end of file, boundary not found
|
||||
p++;
|
||||
}
|
||||
String delimiter;
|
||||
{ // read multipart delimiter
|
||||
const char *b = (p += 2);
|
||||
while(*p && *p++ != '\n')
|
||||
;
|
||||
const char *e = p;
|
||||
while(e > b && (byte)e[-1] <= ' ')
|
||||
e--;
|
||||
delimiter = String(b, e);
|
||||
}
|
||||
|
||||
int delta = 4 + delimiter.GetLength();
|
||||
const char *e = buffer.End();
|
||||
if(e - p < delta)
|
||||
return;
|
||||
e -= delta;
|
||||
while(p < e) { // read individual parts
|
||||
String filename, content_type, name;
|
||||
while(!MemICmp(p, "content-", 8)) { // parse content specifiers
|
||||
p += 8;
|
||||
if(!MemICmp(p, "disposition:", 12)) {
|
||||
p += 12;
|
||||
while(*p && *p != '\n')
|
||||
if((byte)*p <= ' ')
|
||||
p++;
|
||||
else { // fetch key-value pair
|
||||
const char *kp = p;
|
||||
while(*p && *p != '\n' && *p != '=' && *p != ';')
|
||||
p++;
|
||||
const char *ke = p;
|
||||
String value;
|
||||
if(*p == '=') {
|
||||
const char *b = ++p;
|
||||
if(*p == '\"') { // quoted value
|
||||
b++;
|
||||
while(*++p && *p != '\n' && *p != '\"')
|
||||
;
|
||||
value = String(b, p);
|
||||
if(*p == '\"')
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
while(*p && *p != '\n' && *p != ';')
|
||||
p++;
|
||||
value = String(b, p);
|
||||
}
|
||||
}
|
||||
if(ke - kp == 4 && !MemICmp(kp, "name", 4))
|
||||
name = value;
|
||||
else if(ke - kp == 8 && !MemICmp(kp, "filename", 8))
|
||||
filename = value;
|
||||
if(*p == ';')
|
||||
p++;
|
||||
}
|
||||
}
|
||||
else if(!MemICmp(p, "type:", 5)) {
|
||||
p += 5;
|
||||
while(*p && *p != '\n' && (byte)*p <= ' ')
|
||||
p++;
|
||||
const char *b = p;
|
||||
while(*p && *p != '\n')
|
||||
p++;
|
||||
const char *e = p;
|
||||
while(e > b && (byte)e[-1] <= ' ')
|
||||
e--;
|
||||
content_type = String(b, e);
|
||||
}
|
||||
;
|
||||
while(*p && *p++ != '\n')
|
||||
;
|
||||
}
|
||||
if(*p++ != '\r' || *p++ != '\n')
|
||||
return;
|
||||
const char *b = p;
|
||||
while(p < e) {
|
||||
p = (const char *)memchr(p, '\r', e - p);
|
||||
if(!p)
|
||||
return;
|
||||
if(p[0] == '\r' && p[1] == '\n' && p[2] == '-' && p[3] == '-'
|
||||
&& !memcmp(p + 4, delimiter, delimiter.GetLength()))
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
if(!name.IsEmpty() && *name != '.' && *name != '@') { // add variables
|
||||
if(!filename.IsEmpty())
|
||||
var.GetAdd(name + ".filename") = filename;
|
||||
if(!content_type.IsEmpty())
|
||||
var.GetAdd(name + ".content_type") = content_type;
|
||||
var.Add(name, String(b, p));
|
||||
}
|
||||
p += delta;
|
||||
while(*p && *p++ != '\n')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static const char hex_digits[] = "0123456789ABCDEF";
|
||||
|
||||
void UrlEncode(StringBuffer& out, const String& s)
|
||||
{
|
||||
static bool ok[256];
|
||||
ONCELOCK {
|
||||
for(int ch = 0; ch < 256; ch++)
|
||||
ok[ch] = IsAlNum(ch) || ch == ',' || ch == '.' || ch == '-' || ch == '_';
|
||||
}
|
||||
const char *p = s, *e = s.End();
|
||||
for(; p < e; p++)
|
||||
{
|
||||
const char *b = p;
|
||||
while(p < e && ok[(byte)*p])
|
||||
p++;
|
||||
if(p > b)
|
||||
out.Cat(b, int(p - b));
|
||||
if(p >= e)
|
||||
break;
|
||||
if(*p == ' ')
|
||||
out << '+';
|
||||
else
|
||||
out << '%' << hex_digits[(*p >> 4) & 15] << hex_digits[*p & 15];
|
||||
}
|
||||
}
|
||||
|
||||
void MakeLink(StringBuffer& out, const Vector<String>& part, const Vector<Value>& arg)
|
||||
{
|
||||
LTIMING("MakeLink");
|
||||
out.Cat("/");
|
||||
for(int i = 0; i < part.GetCount(); i++) {
|
||||
const String& p = part[i];
|
||||
if(i)
|
||||
out << '/';
|
||||
int q = (byte)*p;
|
||||
if(q < 32) {
|
||||
if(q >= 0 && q < arg.GetCount())
|
||||
UrlEncode(out, AsString(arg[q]));
|
||||
}
|
||||
else
|
||||
UrlEncode(out, p);
|
||||
}
|
||||
bool get = false;
|
||||
for(int i = 0; i < arg.GetCount(); i++)
|
||||
if(IsValueMap(arg[i])) {
|
||||
if(get)
|
||||
out << '&';
|
||||
else
|
||||
out << '?';
|
||||
get = true;
|
||||
ValueMap m = arg[i];
|
||||
for(int i = 0; i < m.GetCount(); i++) {
|
||||
if(i)
|
||||
out << '&';
|
||||
UrlEncode(out, AsString(m.GetKeys()[i]));
|
||||
out << '=';
|
||||
UrlEncode(out, AsString(m.GetValues()[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Http& Http::RenderResult(const char *template_name)
|
||||
{
|
||||
LTIMING("Render");
|
||||
response << UPP::Render(GetTemplate(template_name), this, var.GetValues());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&), const Vector<Value>& arg)
|
||||
{
|
||||
Redirect(MakeLink(view, arg));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&))
|
||||
{
|
||||
Vector<Value> arg;
|
||||
Redirect(view, arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&), const Value& v1)
|
||||
{
|
||||
Vector<Value> arg;
|
||||
arg.Add(v1);
|
||||
Redirect(view, arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Redirect(void (*view)(Http&), const Value& v1, const Value& v2)
|
||||
{
|
||||
Vector<Value> arg;
|
||||
arg.Add(v1);
|
||||
arg.Add(v2);
|
||||
Redirect(view, arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::Ux(const char *id, const String& text)
|
||||
{
|
||||
if(response.GetCount())
|
||||
response << '\1';
|
||||
response << id << ':' << text;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::UxRender(const char *id, const char *template_name)
|
||||
{
|
||||
return Ux(id, RenderString(template_name));
|
||||
}
|
||||
|
||||
Http& Http::UxSet(const char *id, const String& value)
|
||||
{
|
||||
return Ux(String(">") + id, value);
|
||||
}
|
||||
|
||||
Http& Http::UxRun(const String& js_code)
|
||||
{
|
||||
return Ux("!", js_code);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -1,82 +1,81 @@
|
|||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) // DLOG(x)
|
||||
#define LTIMING(x) // RTIMING(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
Renderer& Renderer::operator()(const ValueMap& map)
|
||||
{
|
||||
ValueArray v = map.GetValues();
|
||||
const Index<Value>& k = map.GetKeys();
|
||||
for(int i = 0; i < map.GetCount(); i++)
|
||||
var.Add(k[i], v[i]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Renderer& Renderer::Link(const char *id, void (*view)(Http&), const Vector<Value>& arg)
|
||||
{
|
||||
var.Add(id, Raw('\"' + MakeLink(view, arg) + '\"'));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Renderer& Renderer::operator()(const char *id, void (*view)(Http&))
|
||||
{
|
||||
return Link(id, view, Vector<Value>());
|
||||
}
|
||||
|
||||
Renderer& Renderer::operator()(const char *id, void (*view)(Http&), const Value& arg1)
|
||||
{
|
||||
return Link(id, view, Vector<Value>() << arg1);
|
||||
}
|
||||
|
||||
Renderer& Renderer::operator()(const char *id, void (*view)(Http&), const Value& arg1, const Value& arg2)
|
||||
{
|
||||
return Link(id, view, Vector<Value>() << arg1 << arg2);
|
||||
}
|
||||
|
||||
StaticMutex template_cache_lock;
|
||||
ArrayMap<String, One<Exe> > template_cache;
|
||||
|
||||
const One<Exe>& Renderer::GetTemplate(const char *template_name)
|
||||
{
|
||||
DDUMPM(var);
|
||||
LTIMING("GetTemplate");
|
||||
StringBuffer s;
|
||||
{
|
||||
LTIMING("MakeSignature");
|
||||
for(int i = 0; i < var.GetCount(); i++)
|
||||
s << var.GetKey(i) << ';';
|
||||
s << ':' << template_name;
|
||||
}
|
||||
if(!SkylarkApp::Config().use_caching) // Templates get overwritten is not cached, MT hazard
|
||||
s << ';' << Thread::GetCurrentId();
|
||||
String sgn = s;
|
||||
LLOG("Trying to retrieve " << sgn << " from cache");
|
||||
Mutex::Lock __(template_cache_lock);
|
||||
int q = template_cache.Find(sgn);
|
||||
if(q >= 0 && SkylarkApp::Config().use_caching)
|
||||
return template_cache[q];
|
||||
LLOG("About to compile: " << sgn);
|
||||
LTIMING("Compile");
|
||||
One<Exe>& exe = q >= 0 ? template_cache[q] : template_cache.Add(sgn);
|
||||
exe = Compile(GetPreprocessedTemplate(template_name, lang), var.GetIndex());
|
||||
return exe;
|
||||
}
|
||||
|
||||
String Renderer::RenderString(const String& template_name)
|
||||
{
|
||||
return UPP::Render(GetTemplate(template_name), this, var.GetValues());
|
||||
}
|
||||
|
||||
Renderer& Renderer::Render(const char *id, const String& template_name)
|
||||
{
|
||||
var.Add(id, Render(template_name));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
}
|
||||
|
||||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) // DLOG(x)
|
||||
#define LTIMING(x) // RTIMING(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
Renderer& Renderer::operator()(const ValueMap& map)
|
||||
{
|
||||
ValueArray v = map.GetValues();
|
||||
const Index<Value>& k = map.GetKeys();
|
||||
for(int i = 0; i < map.GetCount(); i++)
|
||||
var.Add(k[i], v[i]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Renderer& Renderer::Link(const char *id, void (*view)(Http&), const Vector<Value>& arg)
|
||||
{
|
||||
var.Add(id, Raw('\"' + MakeLink(view, arg) + '\"'));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Renderer& Renderer::operator()(const char *id, void (*view)(Http&))
|
||||
{
|
||||
return Link(id, view, Vector<Value>());
|
||||
}
|
||||
|
||||
Renderer& Renderer::operator()(const char *id, void (*view)(Http&), const Value& arg1)
|
||||
{
|
||||
return Link(id, view, Vector<Value>() << arg1);
|
||||
}
|
||||
|
||||
Renderer& Renderer::operator()(const char *id, void (*view)(Http&), const Value& arg1, const Value& arg2)
|
||||
{
|
||||
return Link(id, view, Vector<Value>() << arg1 << arg2);
|
||||
}
|
||||
|
||||
StaticMutex template_cache_lock;
|
||||
ArrayMap<String, One<Exe> > template_cache;
|
||||
|
||||
const One<Exe>& Renderer::GetTemplate(const char *template_name)
|
||||
{
|
||||
LTIMING("GetTemplate");
|
||||
StringBuffer s;
|
||||
{
|
||||
LTIMING("MakeSignature");
|
||||
for(int i = 0; i < var.GetCount(); i++)
|
||||
s << var.GetKey(i) << ';';
|
||||
s << ':' << template_name;
|
||||
}
|
||||
if(!SkylarkApp::Config().use_caching) // Templates get overwritten is not cached, MT hazard
|
||||
s << ';' << Thread::GetCurrentId();
|
||||
String sgn = s;
|
||||
LLOG("Trying to retrieve " << sgn << " from cache");
|
||||
Mutex::Lock __(template_cache_lock);
|
||||
int q = template_cache.Find(sgn);
|
||||
if(q >= 0 && SkylarkApp::Config().use_caching)
|
||||
return template_cache[q];
|
||||
LLOG("About to compile: " << sgn);
|
||||
LTIMING("Compile");
|
||||
One<Exe>& exe = q >= 0 ? template_cache[q] : template_cache.Add(sgn);
|
||||
exe = Compile(GetPreprocessedTemplate(template_name, lang), var.GetIndex());
|
||||
return exe;
|
||||
}
|
||||
|
||||
String Renderer::RenderString(const String& template_name)
|
||||
{
|
||||
return UPP::Render(GetTemplate(template_name), this, var.GetValues());
|
||||
}
|
||||
|
||||
Renderer& Renderer::Render(const char *id, const String& template_name)
|
||||
{
|
||||
var.Add(id, Render(template_name));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -1,166 +1,165 @@
|
|||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) //DLOG(x)
|
||||
#define LDUMPC(x) //DDUMPC(x)
|
||||
#define LDUMPM(x) //DDUMPM(x)
|
||||
#define LLOGHEX(x) //DLOGHEX(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
SessionConfig::SessionConfig()
|
||||
{
|
||||
cookie = "__skylark_session_cookie__";
|
||||
dir = ConfigFile("session");
|
||||
format = SESSION_FORMAT_BINARY;
|
||||
id_column = "ID";
|
||||
data_column = "DATA";
|
||||
lastwrite_column = "LASTWRITE";
|
||||
expire = 3600 * 24 * 365; // one year to expire the session
|
||||
}
|
||||
|
||||
String Http::SessionFile(const String& sid)
|
||||
{
|
||||
ONCELOCK
|
||||
RealizeDirectory(app.session.dir);
|
||||
return AppendFileName(app.session.dir, sid);
|
||||
}
|
||||
|
||||
void Http::LoadSession()
|
||||
{
|
||||
const SessionConfig& cfg = app.session;
|
||||
session_var.Clear();
|
||||
session_id = (*this)['@' + cfg.cookie];
|
||||
if(IsNull(session_id))
|
||||
return;
|
||||
String data;
|
||||
if(cfg.table.IsNull())
|
||||
data = LoadFile(SessionFile(session_id));
|
||||
else
|
||||
data = SQLR % Select(cfg.data_column).From(cfg.table)
|
||||
.Where(cfg.id_column == session_id);
|
||||
LLOGHEX(data);
|
||||
switch(cfg.format) {
|
||||
case SESSION_FORMAT_JSON:
|
||||
LoadFromJson(session_var, data);
|
||||
break;
|
||||
case SESSION_FORMAT_XML:
|
||||
LoadFromXML(session_var, data);
|
||||
break;
|
||||
case SESSION_FORMAT_BINARY:
|
||||
LoadFromString(session_var, data);
|
||||
break;
|
||||
}
|
||||
SKYLARKLOG("Loaded session: " << session_id);
|
||||
LDUMPM(session_var);
|
||||
for(int i = 0; i < session_var.GetCount(); i++)
|
||||
var.GetAdd('.' + session_var.GetKey(i)) = session_var[i];
|
||||
}
|
||||
|
||||
thread__ int s_exp;
|
||||
|
||||
static int s_last_expiration_check;
|
||||
|
||||
StaticMutex expire_mutex;
|
||||
|
||||
void Http::SaveSession()
|
||||
{
|
||||
const SessionConfig& cfg = app.session;
|
||||
SetCookie(cfg.cookie, session_id);
|
||||
if(IsNull(session_id))
|
||||
return;
|
||||
String data;
|
||||
switch(cfg.format) {
|
||||
case SESSION_FORMAT_JSON:
|
||||
data = StoreAsJson(session_var);
|
||||
break;
|
||||
case SESSION_FORMAT_XML:
|
||||
data = StoreAsXML(session_var, "session");
|
||||
break;
|
||||
case SESSION_FORMAT_BINARY:
|
||||
data = StoreAsString(session_var);
|
||||
break;
|
||||
}
|
||||
if(cfg.table.IsNull())
|
||||
SaveFile(SessionFile(session_id), data);
|
||||
else {
|
||||
SqlVal d = SqlBinary(data);
|
||||
Time tm = GetSysTime();
|
||||
SQL * Update(cfg.table)
|
||||
(cfg.data_column, d)
|
||||
(cfg.lastwrite_column, tm)
|
||||
.Where(cfg.id_column == session_id);
|
||||
if(SQL.GetRowsProcessed() == 0)
|
||||
SQL * Insert(cfg.table)
|
||||
(cfg.id_column, session_id)
|
||||
(cfg.data_column, d)
|
||||
(cfg.lastwrite_column, tm);
|
||||
}
|
||||
SKYLARKLOG("Stored session: " << session_id);
|
||||
LDUMPM(session_var);
|
||||
|
||||
if((s_exp++ % 1000) == 0) {
|
||||
bool expire_sessions = false;
|
||||
{
|
||||
Mutex::Lock __(expire_mutex);
|
||||
if((dword)msecs(s_last_expiration_check) > 1000 * 60 * 10) {
|
||||
expire_sessions = true;
|
||||
s_last_expiration_check = msecs();
|
||||
}
|
||||
}
|
||||
if(expire_sessions) {
|
||||
Time tm = GetSysTime() - cfg.expire;
|
||||
SKYLARKLOG("Expiring sessions older than " << tm);
|
||||
if(cfg.table.IsNull()) {
|
||||
FindFile ff(AppendFileName(cfg.dir, "*.*"));
|
||||
Vector<String> todelete;
|
||||
while(ff) {
|
||||
if(ff.GetLastWriteTime() < tm)
|
||||
todelete.Add(ff.GetPath());
|
||||
ff.Next();
|
||||
}
|
||||
for(int i = 0; i < todelete.GetCount(); i++)
|
||||
FileDelete(todelete[i]);
|
||||
}
|
||||
else
|
||||
SQL * Delete(cfg.table).Where(cfg.lastwrite_column < tm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Http& Http::ClearSession()
|
||||
{
|
||||
session_var.Clear();
|
||||
session_id.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::SessionSet(const char *id, const Value& value)
|
||||
{
|
||||
LLOG("SessionSet " << id << " = " << value);
|
||||
if(*id == '.')
|
||||
id++;
|
||||
if(IsNull(session_id))
|
||||
NewSessionId();
|
||||
session_var.GetAdd(id) = value;
|
||||
var.GetAdd('.' + id) = value;
|
||||
session_dirty = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::NewSessionId()
|
||||
{
|
||||
session_id = AsString(Uuid::Create());
|
||||
session_dirty = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::SetLanguage(int lang_)
|
||||
{
|
||||
lang = lang_;
|
||||
Upp::SetLanguage(lang_);
|
||||
SessionSet("__lang__", lang);
|
||||
SessionSet("language", ToLower(LNGAsText(lang)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
#include "Skylark.h"
|
||||
|
||||
#define LLOG(x) //DLOG(x)
|
||||
#define LDUMPC(x) //DDUMPC(x)
|
||||
#define LDUMPM(x) //DDUMPM(x)
|
||||
#define LLOGHEX(x) //DLOGHEX(x)
|
||||
|
||||
namespace Upp {
|
||||
|
||||
SessionConfig::SessionConfig()
|
||||
{
|
||||
cookie = "__skylark_session_cookie__";
|
||||
dir = ConfigFile("session");
|
||||
format = SESSION_FORMAT_BINARY;
|
||||
id_column = "ID";
|
||||
data_column = "DATA";
|
||||
lastwrite_column = "LASTWRITE";
|
||||
expire = 3600 * 24 * 365; // one year to expire the session
|
||||
}
|
||||
|
||||
String Http::SessionFile(const String& sid)
|
||||
{
|
||||
ONCELOCK
|
||||
RealizeDirectory(app.session.dir);
|
||||
return AppendFileName(app.session.dir, sid);
|
||||
}
|
||||
|
||||
void Http::LoadSession()
|
||||
{
|
||||
const SessionConfig& cfg = app.session;
|
||||
session_var.Clear();
|
||||
session_id = (*this)['@' + cfg.cookie];
|
||||
if(IsNull(session_id))
|
||||
return;
|
||||
String data;
|
||||
if(cfg.table.IsNull())
|
||||
data = LoadFile(SessionFile(session_id));
|
||||
else
|
||||
data = SQLR % Select(cfg.data_column).From(cfg.table)
|
||||
.Where(cfg.id_column == session_id);
|
||||
switch(cfg.format) {
|
||||
case SESSION_FORMAT_JSON:
|
||||
LoadFromJson(session_var, data);
|
||||
break;
|
||||
case SESSION_FORMAT_XML:
|
||||
LoadFromXML(session_var, data);
|
||||
break;
|
||||
case SESSION_FORMAT_BINARY:
|
||||
LoadFromString(session_var, data);
|
||||
break;
|
||||
}
|
||||
SKYLARKLOG("Loaded session: " << session_id);
|
||||
LDUMPM(session_var);
|
||||
for(int i = 0; i < session_var.GetCount(); i++)
|
||||
var.GetAdd('.' + session_var.GetKey(i)) = session_var[i];
|
||||
}
|
||||
|
||||
thread__ int s_exp;
|
||||
|
||||
static int s_last_expiration_check;
|
||||
|
||||
StaticMutex expire_mutex;
|
||||
|
||||
void Http::SaveSession()
|
||||
{
|
||||
const SessionConfig& cfg = app.session;
|
||||
SetCookie(cfg.cookie, session_id);
|
||||
if(IsNull(session_id))
|
||||
return;
|
||||
String data;
|
||||
switch(cfg.format) {
|
||||
case SESSION_FORMAT_JSON:
|
||||
data = StoreAsJson(session_var);
|
||||
break;
|
||||
case SESSION_FORMAT_XML:
|
||||
data = StoreAsXML(session_var, "session");
|
||||
break;
|
||||
case SESSION_FORMAT_BINARY:
|
||||
data = StoreAsString(session_var);
|
||||
break;
|
||||
}
|
||||
if(cfg.table.IsNull())
|
||||
SaveFile(SessionFile(session_id), data);
|
||||
else {
|
||||
SqlVal d = SqlBinary(data);
|
||||
Time tm = GetSysTime();
|
||||
SQL * Update(cfg.table)
|
||||
(cfg.data_column, d)
|
||||
(cfg.lastwrite_column, tm)
|
||||
.Where(cfg.id_column == session_id);
|
||||
if(SQL.GetRowsProcessed() == 0)
|
||||
SQL * Insert(cfg.table)
|
||||
(cfg.id_column, session_id)
|
||||
(cfg.data_column, d)
|
||||
(cfg.lastwrite_column, tm);
|
||||
}
|
||||
SKYLARKLOG("Stored session: " << session_id);
|
||||
LDUMPM(session_var);
|
||||
|
||||
if((s_exp++ % 1000) == 0) {
|
||||
bool expire_sessions = false;
|
||||
{
|
||||
Mutex::Lock __(expire_mutex);
|
||||
if((dword)msecs(s_last_expiration_check) > 1000 * 60 * 10) {
|
||||
expire_sessions = true;
|
||||
s_last_expiration_check = msecs();
|
||||
}
|
||||
}
|
||||
if(expire_sessions) {
|
||||
Time tm = GetSysTime() - cfg.expire;
|
||||
SKYLARKLOG("Expiring sessions older than " << tm);
|
||||
if(cfg.table.IsNull()) {
|
||||
FindFile ff(AppendFileName(cfg.dir, "*.*"));
|
||||
Vector<String> todelete;
|
||||
while(ff) {
|
||||
if(ff.GetLastWriteTime() < tm)
|
||||
todelete.Add(ff.GetPath());
|
||||
ff.Next();
|
||||
}
|
||||
for(int i = 0; i < todelete.GetCount(); i++)
|
||||
FileDelete(todelete[i]);
|
||||
}
|
||||
else
|
||||
SQL * Delete(cfg.table).Where(cfg.lastwrite_column < tm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Http& Http::ClearSession()
|
||||
{
|
||||
session_var.Clear();
|
||||
session_id.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::SessionSet(const char *id, const Value& value)
|
||||
{
|
||||
LLOG("SessionSet " << id << " = " << value);
|
||||
if(*id == '.')
|
||||
id++;
|
||||
if(IsNull(session_id))
|
||||
NewSessionId();
|
||||
session_var.GetAdd(id) = value;
|
||||
var.GetAdd('.' + id) = value;
|
||||
session_dirty = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::NewSessionId()
|
||||
{
|
||||
session_id = AsString(Uuid::Create());
|
||||
session_dirty = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::SetLanguage(int lang_)
|
||||
{
|
||||
lang = lang_;
|
||||
Upp::SetLanguage(lang_);
|
||||
SessionSet("__lang__", lang);
|
||||
SessionSet("language", ToLower(LNGAsText(lang)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue