File: WebCommon.k

package info (click to toggle)
kaya 0.4.2-4
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 4,448 kB
  • ctags: 1,694
  • sloc: cpp: 9,536; haskell: 7,461; sh: 3,013; yacc: 910; makefile: 816; perl: 90
file content (549 lines) | stat: -rw-r--r-- 29,205 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
/** -*-C-*-ish
    WebCommon.k Copyright (C) 2004, 2005, 2006 Edwin Brady, Chris Morris
    This file is distributed under the terms of the GNU Lesser General
    Public Licence. See COPYING for licence.
*/
/* Functions common to Webapp.k and CGI.k */

"<summary>Common web application functions</summary>
<prose>This module contains functions used in both the Webapp and CGI programming models, mostly covering the processing of user-supplied data and file uploading. The following tutorials from the Kaya website may be useful:</prose>
<list><item><link url='http://kayalang.org/tutorial/web/userdata'>Handling user-supplied data</link></item>
<item><link url='http://kayalang.org/tutorial/web/upload'>Processing file uploads</link></item></list>"
module WebCommon;

import Prelude;
import Dict;
import Time;
import System;
import Strings;
import Regex;
import IO;
import Mime;

foreign "stdfuns.o" {
    String do_urlencode(Ptr vm, String str, Int len) = do_urlencode;
    String do_urldecode(Ptr vm, String str, Int len) = do_urldecode;
}

globals {
  Dict<String,[String]> get_vars;
  Dict<String,[String]> post_vars;
  Dict<String,[String]> cookie_vars;
  Dict<String,[Uploaded]> uploaded_files;
  Bool kaya_allow_fileuploads = false;
  String kaya_upload_dir = webProgTmp();
  Int kaya_max_post_size = webProgPostMax();
}

"<summary>An uploaded file.</summary>
<prose>Represents a file uploaded using HTTP POST. These files can be retrieved with <functionref>incomingFile</functionref> or <functionref>incomingFiles</functionref>, and information about the file can be returned with <functionref>originalName</functionref> (to retrieve the original filename), <functionref>tempPath</functionref> (to get the temporary filesystem path) and <functionref>contentType</functionref> (to get the browser-reported content-type).</prose>
<prose>There are various ways to set a temporary directory for file uploads. If none of these are used, the directory containing the CGI will be used, which is <emphasis>strongly discouraged</emphasis> except for testing purposes.</prose>
<related><functionref>incomingFile</functionref></related>
<related><functionref>incomingFiles</functionref></related>
<related><functionref>originalName</functionref></related>
<related><functionref>tempPath</functionref></related>
<related><functionref>contentType</functionref></related>"
abstract data Uploaded(String origname, String tmppath, String ctype);

"<argument>The String contains the name of the missing key</argument>
<summary>Specified key not in incoming data</summary>
<prose>This Exception is thrown by <functionref>incomingData</functionref> and <functionref>incomingValue</functionref> if the specified key is not found in the specified data source. This can be avoided by using <functionref>incomingExists</functionref> to check if the key exists before trying to retrieve data.</prose>
<related><dataref>DataSource</dataref></related>
<related><functionref>incomingData</functionref></related>
<related><functionref>incomingExists</functionref></related>
<related><functionref>incomingValue</functionref></related>"
Exception NotIncoming(String key);

"<summary>URL encoding error</summary>
<prose>This Exception is thrown if URL encoding fails. In practice, this should not happen.</prose>
<related><functionref>urlEncode</functionref></related>"
Exception urlEncodeError();
"<summary>URL decoding error</summary>
<prose>This Exception is thrown if URL decoding fails. This happens if a % character in the string being decoded is not followed by a pair of hexadecimal digits.</prose>
<related><functionref>urlDecode</functionref></related>"
Exception urlDecodeError();

"<argument name='details'>A description of the error that caused this Exception</argument>
<summary>The cookie was invalid</summary>
<prose>This Exception is thrown if <functionref>setCookie</functionref> is given a parameter that does not meet the cookie standards.</prose>"
Exception InvalidCookie(String details);

"<argument name='x'>The string to encode</argument>
<summary>URL encode a string</summary>
<prose>URL encode a string. You should use this to encode reserved characters in URLs that you wish to be passed literally.</prose>
<example> // trying to pass \"http://www.example.com\" as a parameter
url = \"http://localhost/test.cgi?url=\";
url += urlEncode(\"http://www.example.com\");
// url = http://localhost/test.cgi?url=http%3A%2F%2Fwww.example.com</example>
<prose>Note that in the example above, encoding the whole URL would be incorrect - that would create a relative URL beginning \"http://\".</prose>
<related><functionref>urlDecode</functionref></related>"
public String urlEncode(String x) {
    try {
	return do_urlencode(getVM, x, length(x));
    } catch(e) {
	throw(urlEncodeError);
    }
}

"<argument name='x'>The string to decode</argument>
<summary>URL decode a string</summary>
<prose>URL decode a string. This reverses the effect of <functionref>urlEncode</functionref>. This processing is already done by Kaya when handling GET and POST data, and so it is not necessary (and will usually cause data errors) to use this function on data from these sources.</prose>
<related><functionref>urlEncode</functionref></related>"
public String urlDecode(String x) {
    try {
	return do_urldecode(getVM, x, length(x));
    } catch(e) {
	throw(urlDecodeError);
    }
}

"<summary>Data sources for incoming data</summary>
<prose>Data sources for user-supplied data. <code>DataGet</code> is GET data, <code>DataPost</code> is POST data. <code>DataRequest</code> is a combination of the two (except that if any keys that occur in both sources, only the values from POST will be available for these sources). <code>DataCookie</code> retrieves cookies, and <code>DataNone</code> is always empty.</prose>
<prose><code>DataRequest</code> should only be used where the application is genuinely expected to receive user-supplied data by both request methods (for example, a search form).</prose>
<related><functionref>incomingData</functionref></related>
<related><functionref>incomingExists</functionref></related>
<related><functionref>incomingKeys</functionref></related>
<related><functionref>incomingValue</functionref></related>"
public data DataSource = DataGet | DataPost | DataRequest | DataCookie | DataNone;

/*** Initialisation and shutdown functions ***/

"<summary>Initialise the CGI or webapp.</summary>
<prose><emphasis>Do not call this function in a CGI or webapp</emphasis></prose>
<prose>This function initialises a CGI or webapp program, reading in GET, POST and cookie data. It is automatically called by the CGI and webapp 'template', and so should never need to be called again in a CGI or webapp (doing so will damage the GET and POST values).</prose>"
public Void initWebApp(String queryString, String(String) env, Char() getbody, Bool() endbody) {
  // hopefully a reasonable amount of unpredictability here.
  srand((now()*microTime())%(now()+microTime())*getPID());
  initVars(queryString,env,@getbody,@endbody);
}

"<summary>Remove uploaded files</summary>
<prose>Uploaded files will be deleted from their temporary location at the end of CGI or webapp execution by this function, and so must be moved to some form of permanent storage if necessary. This function is automatically run at the end of CGI or webapp execution, but it is safe to call it earlier if you want to delete files before then.</prose>"
public Void removeTemporaryUploaded() {
  uploaded = vals(uploaded_files);
  for uploads in uploaded {
    for upload in uploads {
      tmpfile = upload.tmppath;
      try {
	unlink(tmpfile);
      } catch(e) {
	// let's just ignore this...
      }
    }
  }  
}

Void initVars(String queryString, String(String) env, Char() getbody, Bool() endbody) {
  if (queryString == "") {
      query = env("QUERY_STRING");
  } else {
      query = queryString;
  }
  cookie = env("HTTP_COOKIE");
  len = Int(env("CONTENT_LENGTH"));
  postvars = "";
  if (len > 0 && len <= kaya_max_post_size) {
    ctype = env("CONTENT_TYPE");
    if (ctype == "application/x-www-form-urlencoded") {
      for x in [1..len] {
	postvars += getbody();
      }
      post_vars = parseVars(postvars,['&']);
      uploaded_files = Dict::new(1);
    } else if (kaya_allow_fileuploads && length(ctype) >= 19 && substr(ctype,0,19) == "multipart/form-data") {
      mimeobjs = mimeDecode(@getbody,@endbody,len,findMimeBoundary(ctype),kaya_upload_dir);
      parseMime(mimeobjs);
    } else { // unrecognised content type for post
      post_vars = Dict::new(1,strHash);
      uploaded_files = Dict::new(1);
    }

  } else { // no post data
    post_vars = Dict::new(1,strHash);
    uploaded_files = Dict::new(1);
  }
  get_vars = parseVars(query,['&',';']);
  cookie_vars = parseVars(cookie,[';'],true);
}

Void parseMime([Mime] mimeobjs) {
  post_vars = Dict::new(47,strHash);
  uploaded_files = Dict::new(47);
  for mimeobj in mimeobjs {
    case mimeobj.mdata of {
      MimeString(text) -> case lookup(post_vars,mimeobj.name) of {
	nothing -> add(post_vars,copy(mimeobj.name),[copy(text)]);
	| just(x) -> push(x,copy(text));
      }
      | MimeFile(fname,tname) -> case lookup(uploaded_files,mimeobj.name) of {
	nothing -> add(uploaded_files,copy(mimeobj.name),[Uploaded(copy(fname),copy(tname),copy(mimeobj.ctype))]);
	| just(y) -> push(y,Uploaded(copy(fname),copy(tname),copy(mimeobj.ctype)));
      }
    }
  }
}

Dict<String,[String]> parseVars(String variables,[Char] separators,Bool oldcookies=false) {
  dict = Dict::new(47,strHash);
  if (variables == "") {
    return dict;
  }
  units = split("["+string(separators)+"]",variables);
  eqre = compile("=");
  for unit in units {
    if (unit != "") {
      parseUnit(dict,unit,eqre,oldcookies);
    }
  }
  return dict;
}

Void parseUnit(Dict<String,[String]> dict, String unit, Regex eqre, Bool oldcookies=false) {
    dat = split(eqre,unit);
    if (size(dat) == 1) {
      val = "";
    } else {
      val = dat[1];
    }
    name = urlDecode(dat[0]);
    if (oldcookies && head(name) == '$') {
      return; // Set-Cookie or Set-Cookie2 -style cookies. Can be ignored.
    }
    val = urlDecode(val);
    case lookup(dict,name) of {
      nothing -> add(dict,name,[val]);
      | just(x) -> push(x,val);
    }
}

/*** GET/POST/Cookie/Files inspection functions ***/

"<argument name='ds'>The source for user-supplied data</argument>
<summary>Return keys for user-supplied data</summary>
<prose>Returns all keys (in no particular order) with at least one value for
the specified <dataref>DataSource</dataref>.</prose>
<example> // called with ?a=1;b=2;b=5
keys = incomingKeys(DataGet);
// keys = [\"a\",\"b\"] or [\"b\",\"a\"]</example>
<related><dataref>DataSource</dataref></related>
<related><functionref>incomingData</functionref></related>
<related><functionref>incomingExists</functionref></related>
<related><functionref>incomingValue</functionref></related>"
public [String] incomingKeys(DataSource ds) {
  case ds of {
    DataGet -> return keys(get_vars);
    | DataPost -> return keys(post_vars);
    | DataCookie -> return keys(cookie_vars);
    | DataRequest -> dkeys = keys(post_vars);
    concat(dkeys,keys(get_vars));
    nub(dkeys);
    return dkeys;
    | DataNone -> return createArray(1);
  }
}

"<argument name='key'>The key to retrieve data for</argument>
<argument name='ds'>The source for user-supplied data</argument>
<summary>Returns all values for the specified key from user-supplied data</summary>
<prose>Returns an array of all values for the key from the specified data source, or throws a <exceptref>NotIncoming</exceptref> Exception if no such key exists.</prose>
<example> // GET /example.cgi?a=1;b=3;b=5;z=;z;z
vals = incomingData(\"a\",DataGet);
// vals = [\"1\"]
vals = incomingData(\"b\",DataGet);
// vals = [\"3\",\"5\"]
vals = incomingData(\"c\",DataGet);
// NotIncoming exception thrown
vals = incomingData(\"z\",DataGet);
// vals = [\"\",\"\",\"\"]</example>
<prose>This is useful for processing the output from forms containing checkboxes with the same name or multiple selects.</prose>
<example>&lt;form action='example.cgi' method='get'>
 &lt;fieldset>&lt;legend>Example form&lt;/legend>
  &lt;select multiple='multiple' name='a'>
   &lt;option value='1'>Option 1&lt;/option>
   &lt;option value='2'>Option 2&lt;/option>
   &lt;option value='3'>Option 3&lt;/option>
   &lt;option value='4'>Option 4&lt;/option>
  &lt;/select>
  &lt;input type='submit' value='Test!'>
 &lt;/fieldset>
&lt;/form></example>
<related><dataref>DataSource</dataref></related>
<related><functionref>incomingExists</functionref></related>
<related><functionref>incomingKeys</functionref></related>
<related><functionref>incomingValue</functionref></related>"
// note, if same key in get and post, and DataRequest used, *all*
// values in get will be ignored.
public [String] incomingData(String key, DataSource ds) {
  lookups = createArray(2);
  case ds of {
    DataGet -> push(lookups,lookup(get_vars,key));
    | DataPost -> push(lookups,lookup(post_vars,key));
    | DataCookie -> push(lookups,lookup(cookie_vars,key));
    | DataRequest -> push(lookups,lookup(post_vars,key));
    push(lookups,lookup(get_vars,key));
    | DataNone -> return createArray(1); // always succeeds but returns no values
  }
  for l in lookups {
    case l of {
      nothing -> ;
      | just(x) -> return copy(x); // incomingData must be read only!
    }
  }
  throw(NotIncoming(key));
}

"<argument name='key'>The key to retrieve data for</argument>
<argument name='ds'>The source for user-supplied data</argument>
<summary>Returns the first value for the specified key from user-supplied data</summary>
<prose>Returns the first value for the key from the specified data source, or throws a <exceptref>NotIncoming</exceptref> Exception if no such key exists.</prose>
<example> // GET /example.cgi?a=1;b=3;b=5;z=;z;z
val = incomingData(\"a\",DataGet);
// val = \"1\"
val = incomingData(\"b\",DataGet);
// val = \"3\"
val = incomingData(\"c\",DataGet);
// NotIncoming exception thrown
val = incomingData(\"z\",DataGet);
// val = \"\"</example>
<prose>This is generally easier to use than <functionref>incomingData</functionref> if only one value is expected for that key.</prose>
<related><dataref>DataSource</dataref></related>
<related><functionref>incomingData</functionref></related>
<related><functionref>incomingExists</functionref></related>
<related><functionref>incomingKeys</functionref></related>"
public String incomingValue(String key, DataSource ds) {
  return incomingData(key,ds)[0];
}

"<argument name='key'>The key to check</argument>
<argument name='ds'>The source for user-supplied data</argument>
<summary>Checks a key exists in user-supplied data</summary>
<prose>Returns <code>true</code> if the key exists in the data source, and <code>false</code> otherwise.</prose>
<example> // GET /example.cgi?a=1;b=3;b=5;z=;z;z
val = incomingExists(\"a\",DataGet);
// val = true
val = incomingExists(\"b\",DataGet);
// val = true
val = incomingExists(\"c\",DataGet);
// val = false
val = incomingExists(\"z\",DataGet);
// val = true</example>
<prose>This is useful for avoiding Exceptions when using <functionref>incomingData</functionref> or <functionref>incomingValue</functionref>, or for checking if a checkbox has been selected.</prose>
<related><dataref>DataSource</dataref></related>
<related><functionref>incomingData</functionref></related>
<related><functionref>incomingKeys</functionref></related>
<related><functionref>incomingValue</functionref></related>"
public Bool incomingExists(String key, DataSource ds) {
  case ds of {
    DataGet -> return exists(get_vars,key);
    | DataPost -> return exists(post_vars,key);
    | DataCookie -> return exists(cookie_vars,key);
    | DataRequest -> if (exists(post_vars,key)) { return true; }
    else { return exists(get_vars,key); }
    | DataNone -> return false; // never exists
  }
}

"<summary>Return keys for user-uploaded files</summary>
<prose>Returns all keys (in no particular order) with at least one user-uploaded file associated. Browser behaviour differs on whether a key is sent when no file is uploaded, but in Kaya unused file upload keys are treated as not existing regardless of whether the browser sent no key or an empty key.</prose>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>incomingFile</functionref></related>
<related><functionref>incomingFileExists</functionref></related>
<related><functionref>incomingFiles</functionref></related>
<related><functionref>incomingKeys</functionref></related>"
public [String] incomingFileKeys() {
  return keys(uploaded_files);
}

"<argument name='key'>The key to return files for</argument>
<summary>Return all files uploaded with a particular key name</summary>
<prose>Returns an array of <dataref>Uploaded</dataref> for all user-uploaded files with the specified key, or throws a <exceptref>NotIncoming</exceptref> Exception if these are not present.</prose>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>incomingData</functionref></related>
<related><functionref>incomingFile</functionref></related>
<related><functionref>incomingFileExists</functionref></related>
<related><functionref>incomingFileKeys</functionref></related>"
public [Uploaded] incomingFiles(String key) {
  case lookup(uploaded_files,key) of {
    nothing -> throw(NotIncoming(key));
    | just(uploaded) -> return uploaded;
  }
} 

"<argument name='key'>The key to return files for</argument>
<summary>Return the first file uploaded with a particular key name</summary>
<prose>Returns an <dataref>Uploaded</dataref> for the first user-uploaded file with the specified key, or throws a <exceptref>NotIncoming</exceptref> Exception if these are not present.</prose>
<prose>This is generaly easier than using <functionref>incomingFiles</functionref> if you are expecting multiple files.</prose>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>incomingFileExists</functionref></related>
<related><functionref>incomingFileKeys</functionref></related>
<related><functionref>incomingFiles</functionref></related>
<related><functionref>incomingValue</functionref></related>"
public Uploaded incomingFile(String key) {
  return incomingFiles(key)[0];
} 

"<argument name='key'>The key to check for files</argument>
<summary>Check if any files are uploaded with a particular key name</summary>
<prose>Returns <code>true</code> if any files have been uploaded with this key name. This is generally better than catching <exceptref>NotIncoming</exceptref> exceptions from <functionref>incomingFile</functionref>.</prose>
<example>if (incomingFileExists(\"picture\")) {
    pic = incomingFile(\"picture\");
    myUploadProcessor(pic);
}</example>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>incomingExists</functionref></related>
<related><functionref>incomingFile</functionref></related>
<related><functionref>incomingFileKeys</functionref></related>
<related><functionref>incomingFiles</functionref></related>"
public Bool incomingFileExists(String key) {
  case lookup(uploaded_files,key) of {
    nothing -> return false;
    | just(uploaded) -> return true;
  }
} 

/*** Configuration options ***/

// much easier if this isn't public - it doesn't need to be
String webProgTmp() {
  if (getEnv("HTTP_KAYA_UPLOAD_DIR") != "") {
    return getEnv("HTTP_KAYA_UPLOAD_DIR");
  } else {
    return ".";
  }
}

"<argument name='newdir'>The temporary directory.</argument>
<summary>Set a temporary directory for Kaya file uploads.</summary>
<prose>User file uploads are placed in a temporary directory. The default directory is the current directory for the CGI program or webapp, which is <emphasis>strongly</emphasis> discouraged for production use. There are two ways to set the new temporary directory, which should ideally be an otherwise empty directory outside the public website, only readable and writeable by the user the program runs as.</prose>
<prose>Firstly, calling this function from within the CGI or webapp's <code>webconfig</code> function will set the directory. (Calls from any other location have no effect, because the uploaded files have already been saved to the temporary location by then!)</prose>
<prose>Secondly, if this function has <emphasis>not</emphasis> been used, the <code>HTTP_KAYA_UPLOAD_DIR</code> environment variable (consult your webserver documentation for setting environment variables), will be read and used. This feature should not be used by application developers, who should use this function and provide an installer-configurable way to set the directory - it is intended to provide a means for Kaya application users to deal with applications which do not use this function.</prose>
<example>webapp example;

import Webapp;
import HTMLDocument;

Void webconfig() {
    allowFileUploads();
    setKayaUploadDir(\"/users/kaya/tmp\");
}

HTMLDocument webmain() {
    // program goes here!
}</example>
<prose>Note that Kaya (from 0.2.4 onwards) will not accept file uploads at all unless <functionref>allowFileUploads</functionref> is called first, so you don't need to use this function in applications that do not require file uploads unless your Kaya version is 0.2.2 or 0.2.3</prose>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>setKayaMaxPost</functionref></related>"
public Void setKayaUploadDir(String newdir) {
  kaya_upload_dir = newdir;
}

"<summary>Allow file uploads</summary>
<prose>This function must be called from a webapp or CGI program's <code>webconfig</code> function to have any effect. It enables handling of user-uploaded files. If it is not called, HTTP POSTs with a content type of \"multipart/form-data\" (the type required for file uploads) will be treated as an unrecognised POST content type, and the POST data will be ignored.</prose>
<prose>This is a security feature to avoid all webapps needing to use <functionref>setKayaUploadDir</functionref> even if they never need to handle files. If you do need file upload support and so use this function, you <emphasis>must</emphasis> also set a temporary upload directory.</prose>
<related><functionref>setKayaUploadDir</functionref></related>"
public Void allowFileUploads() {
  kaya_allow_fileuploads = true;
}

// again, easier if this is private
Int webProgPostMax() {
  if (getEnv("HTTP_KAYA_POST_MAX_SIZE") != "") {
    return Int(getEnv("HTTP_KAYA_POST_MAX_SIZE"));
  } else {
    return 2097152; // 2Mb
  }
}

"<argument name='newmax'>The maximum size in bytes</argument>
<summary>Sets the maximum size of POST data</summary>
<prose>Sets the maximum size of POST data (in bytes) that the webapp or CGI will accept. This has no useful effect unless called from a <code>webconfig</code> function, as it will have already been called by then. POST data received above this size will be <emphasis>entirely</emphasis> discarded.</prose>
<prose>The default value if this function is not called is 2097152 bytes (2 megabytes), unless the <code>HTTP_KAYA_POST_MAX_SIZE</code> environment variable is set. This will generally be sufficient unless you are handling large file uploads.</prose>
<related><functionref>setKayaUploadDir</functionref></related>"
public Void setKayaMaxPost(Int newmax) {
  kaya_max_post_size = newmax;
}

/*** retrieving Uploaded values */
// mustn't ever return a reference to the original value here.

"<argument name='file'>A user-uploaded file</argument>
<summary>Get the real name for the file</summary>
<prose>Returns the name for the file supplied by the web browser. Note that this is usually but <emphasis>not necessarily</emphasis> the same as its name on the user's computer, but could be any string. You should filter this value for acceptable characters before using it!</prose>
<related><dataref>Uploaded</dataref></related>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>contentType</functionref></related>
<related><functionref>tempPath</functionref></related>"
public String originalName(Uploaded file) = copy(file.origname);

"<argument name='file'>A user-uploaded file</argument>
<summary>Get the temporary filesystem path of the file</summary>
<prose>Returns the temporary location of the user-uploaded file. The file will be deleted from this location when the webapp exits, so you must either copy it to a permanent location (filesystem, database, etc), or complete all your processing by the end of the webapp.</prose>
<related><dataref>Uploaded</dataref></related>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>contentType</functionref></related>
<related><functionref>originalName</functionref></related>"
public String tempPath(Uploaded file) = copy(file.tmppath);

"<argument name='file'>A user-uploaded file</argument>
<summary>Get the content type of the file</summary>
<prose>Returns the user-reported content type of the uploaded file (e.g. <code>text/plain</code>, <code>application/pdf</code> or <code>image/png</code>). Depending on the configuration of the user's system, this information may not be completely reliable, and of course a malicious user could deliberately give an incorrect content type, so you may need to perform additional checks in some circumstances.</prose>
<related><dataref>Uploaded</dataref></related>
<related><functionref>allowFileUploads</functionref></related>
<related><functionref>originalName</functionref></related>
<related><functionref>tempPath</functionref></related>"
public String contentType(Uploaded file) = copy(file.ctype);

"<summary>Return a local link URL to the current webapp or CGI</summary>
<prose>Returns the URL to the current webapp or CGI. This function assumes that './<variable>executable</variable>' is a valid URL. If you are using Apache's mod_rewrite or another URL-rewriting system then this assumption may not be true and you should construct your own URL.</prose>"
public String webappName() {
  pn = getArgs()[0];
  if (quickMatch("/",pn)) { // can't be true on windows
    replace(".*/","",pn);
  } else {
    // windows
    replace(".*\\\\","",pn);
  }
  // FIXME: will give the wrong answer if a file with a '\' in the filename
  // is an executable on a POSIX system. Unlikely as that is...
  return "./"+pn;
}

"<argument name='key'>The name of the cookie</argument>
<argument name='value'>The value of the cookie</argument>
<argument name='expires'>The time that the cookie expires, which must be in GMT, or <code>nothing</code> if the cookie is a \"session cookie\" that expires when the web browser is closed (the default).</argument>
<argument name='path'>The URL path to which the cookie will be returned. The default is <code>nothing</code> which sets it to the URL path of the current document. The URL path must begin with a '/' if it is explicitly set.</argument>
<argument name='domain'>The domain that the cookies will be sent to. The default of <code>nothing</code> gives the current hostname.</argument>
<argument name='secure'>If true (the default is false) this cookie will only be sent back to the server over HTTPS connections. Use this if the cookie contains or can be used to access any sensitive information unless you do not have HTTPS available.</argument>
<summary>Create a cookie header</summary>
<prose>Creates a cookie header that can be passed to <functionref>HTMLDocument::addHTTPHeader</functionref> or added to the headers list for functions such as <functionref index='1'>Webapp::displayPage</functionref>. Remember that cookies are not available in the page that sets them, since the web browser has not yet received them to send back at that point.</prose>
<example>expires = gmTime(time()+3600); // one hour later
cookie = setCookie(\"session\",newSession(),just(expires));
addHTTPHeader(doc,cookie);</example>
<prose>The <link url='http://wp.netscape.com/newsref/std/cookie_spec.html'>original Netscape cookie specification</link> has more detail on the use of these fields.</prose>
<prose>The functioning of the <code>domain</code> part is sometimes problematic. For example, there is no way to set a cookie that should be returned to <code>server.example.com</code> but not to <code>test.server.example.com</code>.</prose>
<related><functionref>CGI::header</functionref></related>
<related><functionref>HTMLDocument::addHTTPHeader</functionref></related>
<related><functionref index='1'>Webapp::displayPage</functionref></related>"
public (String,String) setCookie(String key, String value, Maybe<Time> expires=nothing, Maybe<String> path=nothing, Maybe<String> domain=nothing, Bool secure=false) {
  hvalue = urlEncode(key)+"="+urlEncode(value);
  case expires of {
    nothing -> ;
    | just(t) -> hvalue += "; expires="+cookieTime(t);
  }
  case path of {
    nothing -> ;
    | just(p) -> if (head(p) != '/') {
      throw(InvalidCookie("Path must begin with a '/'"));
    } else {
      hvalue += "; path="+p;
    }
  }
  case domain of {
    nothing -> ;
    | just(d) -> hvalue += "; domain="+d;
  }
  if (secure) {
    hvalue += "; secure";
  }
  return ("Set-Cookie",hvalue);
}