Sophie

Sophie

distrib > Fedora > 14 > x86_64 > media > updates > by-pkgid > cd38b09e3cb8d6c675b02d30393e68af > files > 25

kaya-doc-0.5.2-8.fc14.noarch.rpm

/* This web application demonstrates using cookies and a database to
   store state. */
webapp cookie2;

// use the SQLite database; it's easiest.
import SQLiteDB;
import DB;
import Time;
import Webapp;
import HTMLDocument;

// this stores the user state
data User(Int userid, Int visited);

globals {
  // this isn't ideal, but it makes the rest of the code simpler
  HTMLDocument doc;
  SQLiteDB db = connect("/users/kaya/share/databases/cookie2");
}

HTMLDocument webmain() {
  // this is a useful thing to do when testing
  // but should be commented out for a real application
  //  enableDebugging();
  doc = new(HTML4Strict,"Cookie demonstration");

  try {

    // Create the sessions table if it doesn't exist
    // This is just to make this example entirely self-contained.
    // You'd probably want to do this differently in a real application
    void(exec(db,"CREATE TABLE IF NOT EXISTS Sessions (user int, session text, lastused int)"));

    // for simplicity, the entire page is generated in the handler
    // in a real application, there'd probably be some template code
    // above and below this call
    try {
      key = incomingValue("cookie_demo_login",DataCookie);
    } catch(e) {
      key = "";
    } 
    appendExisting(doc.body,
		   retrieveFunction(@notLoggedIn,
				    cookieRetriever,
				    key));
  } catch(SQLiteError(err)) {
    // only display details of the error to the browser when debugging
    // to help security
    if (debugMode()) {
      void(addParagraph(doc.body,"Database error: "+err));
    } else {
      void(addParagraph(doc.body,"Database error: sorry"));
      // in a real application it might be nice to log the error
      // details somewhere
    }
  }
  source = addParagraph(doc.body,"Read the ");
  void(appendInlineElement(source,Hyperlink("../files/cookie2.k"),"source code"));

  // close the database now
  close(db);
  return doc;
}

// this is the default function if the cookie doesn't exist.
ElementTree notLoggedIn() {
  // if the form has been submitted
  if (incomingExists("username",DataPost)) {
    // then log in
    user = Int(incomingValue("username",DataPost));
    // and switch to the logged-in handler
    if (user > 0) {
      return loggedIn(User(user,0));
    }
  } 
  // otherwise display the log in form
  content = anonymousBlock;
  void(addHeading(content,1,"Please log in"));
  void(addParagraph(content,"You are not logged in. Please make up a user ID (numbers only) and log in to see the rest of this application."));
  form = addLocalForm(content);
  f1 = addFieldset(form,"Log in");
  void(addLabelledInput(f1,"User ID",InputText,"username","",10));
  void(addRemoteControlInput(f1,InputSubmit,"","Log in"));
  return content;
}

ElementTree loggedIn(User user) {
  if (incomingExists("logout",DataGet) && !incomingExists("username",DataPost)) {
    // erase the login cookie
    if (incomingExists("logout",DataGet)) {
      addHTTPHeader(doc,setCookie("cookie_demo_login",""));
    }
    // the second half of the condition should always be false, but
    // if it's not included, someone could mischievously send the
    // application into an infinite loop.
    return notLoggedIn();
    // anyway, if they're logging out, print the log in form
    // we've already deleted the cookie above.
  } else {
    // but usually, it'll just be a page.
    content = anonymousBlock();
    // keep track of the number of pages visited, just to prove that
    // state can be updated as the user navigates the application
    user.visited++;
    void(addParagraph(content,"Hello, user "+user.userid+". You have viewed "+user.visited+" pages this session."));

    // while logged in, the page parameter says what page to view.
    if (incomingExists("page",DataGet)) {
      pageid = Int(incomingValue("page",DataGet));
    } else {
      pageid = 100;
    }
    // now show the page-specific content
    showPage(content,pageid);

    logout = addParagraph(content,"When you're finished, ");
    void(appendInlineElement(logout,Hyperlink(webappName()+"?logout"),"log out"));

    void(storeFunction(cookieStorer@(doc,user.userid),@loggedIn,user));
    return content;
  }
}

Void linkToPage(ElementTree list, Int linkid) {
  li = pushListItem(list,"Go to ");
  void(appendInlineElement(li,Hyperlink(webappName()+"?page="+linkid),"page "+linkid));
}

Void showPage(ElementTree content, Int pageid) {
  cpage = addParagraph(content,"You are now on page "+pageid+".");
  if (pageid == 1) {
    addString(cpage," Well done - you found page 1! You can log out and try to find it again in fewer moves if you're really bored.");
    // Exercise for the reader: modify this application so that a visitor
    // can't cheat and go straight to page 1 by changing the URL. Fixing
    // things like this is a crucial part of web application security.
    // Hint: you will probably need to expand the state data type a bit
  } else {
    addString(cpage," If you get bored, try to find page 1.");
  }
  // add some navigation to simulate links to the next stages of the
  // application.
  ul = addList(content,Unordered,0);
  if (pageid % 2 == 0) {
    linkToPage(ul,pageid/2);
  } else {
    linkToPage(ul,(pageid*7)+1);
  }
  if (pageid % 5 == 1) {
    linkToPage(ul,pageid+4);
  } else {
    linkToPage(ul,pageid+10);
  }
  if (pageid % 3 == 0) {
    linkToPage(ul,(pageid*2)-1);
  } else if (pageid % 3 == 1) {
    linkToPage(ul,pageid-13);
  } else {
    linkToPage(ul,pageid/3);
  }
  if (pageid < 10) {
    linkToPage(ul,pageid*pageid);
  } else if (pageid > 1000) {
    linkToPage(ul,pageid/20);
  }

}

String cookieStorer(HTMLDocument hdoc, Int uid, String state) {
// just store the username in the state
    key = encode(String(uid));
// encrypt it to stop easy takeover of other accounts
    cookie = setCookie("cookie_demo_login",key);
    addHTTPHeader(hdoc,cookie);

// state is base64-encoded and uid is an integer, so they're safe
// but in general you need to escape Strings before placing them
// into SQL queries to prevent SQL injection attacks

// also delete sessions more than an hour old to save database space
    void(exec(db,"DELETE FROM Sessions WHERE user = '"+uid+"' OR lastused+3600 < '"+now()+"'"));
    void(exec(db,"INSERT INTO Sessions (user,session,lastused) VALUES ('"+uid+"','"+state+"','"+now()+"')"));
    return key;
}

Exception SessionExpired;

String cookieRetriever(String key) {
// if the key isn't a valid key, then this will throw an Exception
// another good reason to encrypt them
    uid = Int(decode(key));
    getstate = exec(db,"SELECT session FROM Sessions WHERE user = '"+uid+"'");
    if(getstate.rows == 0) {
// maybe the session has expired
        throw(SessionExpired);
    } else {
        return string(getstate.table[0][0]);
    }
}