WebHTTP.Mod 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. MODULE WebHTTP; (** AUTHOR "tf/be"; PURPOSE "HTTP parsing"; *)
  2. (* 02.04.2003 es, additional result codes, WebDAV methods. *)
  3. (* 12.04.2003 es, WebDAV result codes. *)
  4. IMPORT IP, TFLog, Streams, Dates, Strings;
  5. CONST
  6. HTTPPort* = 80;
  7. HTTPSPort*= 443;
  8. (** HTTP Result Codes *)
  9. (* Informational *)
  10. Continue* = 100;
  11. SwitchingProtocols* = 101;
  12. Processing* = 102; (* RFC 2518 *)
  13. (* Successful *)
  14. OK* = 200;
  15. Created* = 201;
  16. Accepted*= 202;
  17. NonAuthoritativeInformation*= 203;
  18. NoContent*= 204;
  19. ResetContent*= 205;
  20. PartialContent*= 206;
  21. MultiStatus* = 207; (* RFC 2518 *)
  22. (* Redirection *)
  23. MultipleChoices*= 300;
  24. ObjectMoved* = 301; (* moved permananently *)
  25. ObjectMovedTemporarily* = 302; (* found *)
  26. SeeOther*= 303;
  27. NotModified* = 304;
  28. UseProxy*= 305;
  29. TemporaryRedirect*= 307;
  30. (* Client Error *)
  31. BadRequest* = 400;
  32. Unauthorized* = 401;
  33. PaymentRequired*= 402;
  34. Forbidden* = 403;
  35. NotFound* = 404;
  36. MethodNotAllowed*= 405;
  37. NotAcceptable*= 406;
  38. ProxyAuthenticationRequested*= 407;
  39. RequestTimeout*= 408;
  40. Conflict* = 409;
  41. Gone*= 410;
  42. LengthRequired* = 411;
  43. PreconditionFailed* = 412;
  44. RequestEntityTooLarge*= 413;
  45. RequestURITooLong* = 414;
  46. UnsupportedMediaType*= 415;
  47. RequestedRangeNotSatisfiable*= 416;
  48. ExpectationFailed*= 417;
  49. UnprocessableEntity* = 422; (* RFC 2518 *)
  50. Locked* = 423; (* RFC 2518 *)
  51. FailedDependency*= 424; (* RFC 2518 *)
  52. (* Server Error *)
  53. InternalServerError* = 500;
  54. NotImplemented* = 501;
  55. BadGateway*= 502;
  56. ServiceUnavailable*= 503;
  57. GatewayTimeout*= 504;
  58. VersionNotSupported* = 505;
  59. InsufficientStorage* = 507; (* RFC 2518 *)
  60. (** HTTP methods RFC 2616 Section 5.1.1*)
  61. UnknownM* = 0; GetM* = 1; HeadM* = 2; PutM* = 3; PostM* = 4; OptionsM* = 5;
  62. TraceM* = 6; DeleteM* = 7; ConnectM* = 8;
  63. (** new HTTP methods RFC 2518 Section 8: HTTP Extensions for Distributed Authoring -- WebDAV *)
  64. PropfindM* = 10; ProppatchM* = 11; MkcolM* = 12; CopyM* = 13; MoveM* = 14; LockM* = 15; UnlockM* = 16;
  65. (** new HTTP methods RFC 3253 Versioning Extensions to WebDAV *)
  66. VersionControlM* = 17; ReportM* = 18; CheckoutM* = 19; CheckinM* = 20; UncheckoutM* = 21;
  67. MkworkspaceM* = 22; UpdateM* = 23; LabelM* = 24; MergeM* = 25; BaselineControlM* = 26; MkactivityM* = 27;
  68. (** HTTP date & time format *)
  69. DateTimeFormat* = "www, dd mmm yyyy hh:nn:ss GMT";
  70. (* Chunker stuff *)
  71. BufSize = 400H;
  72. TokenSize = 10H;
  73. MaxRequestHeaderFields* = 47+10; (* at most 47 standard headers of RFC 2616 plus a number of additional header fields*)
  74. DocType* = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">';
  75. TYPE
  76. AdditionalField* = POINTER TO RECORD
  77. key* : ARRAY 64 OF CHAR;
  78. value* : ARRAY 1024 OF CHAR;
  79. next* : AdditionalField;
  80. END;
  81. RequestHeader* = RECORD
  82. fadr* : IP.Adr;
  83. fport* : LONGINT;
  84. method* : LONGINT;
  85. maj*, min* : LONGINT;
  86. uri* : ARRAY 4096 OF CHAR;
  87. host* : ARRAY 256 OF CHAR;
  88. referer* : ARRAY 256 OF CHAR;
  89. useragent* : ARRAY 256 OF CHAR;
  90. accept* : ARRAY 256 OF CHAR;
  91. transferencoding* : ARRAY 64 OF CHAR;
  92. additionalFields* : AdditionalField;
  93. END;
  94. ResponseHeader* = RECORD
  95. maj*, min* : LONGINT;
  96. statuscode* : LONGINT;
  97. reasonphrase* : ARRAY 256 OF CHAR;
  98. server* : ARRAY 256 OF CHAR;
  99. date* : ARRAY 32 OF CHAR;
  100. location*: ARRAY 1024 OF CHAR;
  101. contenttype* : ARRAY 64 OF CHAR;
  102. contentlength* : LONGINT;
  103. contentlocation*: ARRAY 1024 OF CHAR;
  104. transferencoding* : ARRAY 64 OF CHAR;
  105. lastmodified*: ARRAY 32 OF CHAR;
  106. additionalFields* : AdditionalField;
  107. END;
  108. ChunkedOutStream* = OBJECT
  109. VAR (* General vars: *)
  110. outW: Streams.Writer;
  111. buf: ARRAY BufSize OF CHAR;
  112. bufPos: LONGINT;
  113. chunked: BOOLEAN;
  114. (* Chunked mode vars *)
  115. token: ARRAY TokenSize OF CHAR;
  116. PROCEDURE &Init*(VAR inW: Streams.Writer; outW: Streams.Writer; VAR request: RequestHeader; VAR reply: ResponseHeader);
  117. BEGIN
  118. SELF.outW := outW;
  119. chunked := Version(request, 1,1);
  120. IF chunked THEN
  121. Streams.OpenWriter(inW, Sender);
  122. COPY("chunked", reply.transferencoding);
  123. reply.contentlength := -1
  124. ELSE
  125. inW := outW
  126. END
  127. END Init;
  128. PROCEDURE Sender(CONST inBuf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: WORD);
  129. VAR i: LONGINT;
  130. BEGIN
  131. ASSERT(chunked);
  132. i := ofs;
  133. WHILE (i < ofs+len) DO
  134. buf[bufPos] := inBuf[i];
  135. INC(i);
  136. INC(bufPos);
  137. IF bufPos = BufSize THEN WriteChunked END;
  138. IF propagate THEN outW.Update END
  139. END
  140. END Sender;
  141. PROCEDURE WriteChunked;
  142. BEGIN (* inv: chunked=TRUE *)
  143. Strings.IntToHexStr(bufPos, 8, token);
  144. outW.String(token);
  145. outW.Ln;
  146. outW.Bytes(buf, 0, bufPos);
  147. outW.Ln;
  148. bufPos := 0
  149. END WriteChunked;
  150. PROCEDURE Update*;
  151. BEGIN
  152. IF chunked THEN WriteChunked END;
  153. outW.Update
  154. END Update;
  155. PROCEDURE Close*;
  156. BEGIN
  157. IF chunked THEN
  158. IF bufPos > 0 THEN WriteChunked END;
  159. outW.Char("0");
  160. outW.Ln;
  161. outW.Ln
  162. END;
  163. outW.Update
  164. END Close;
  165. END ChunkedOutStream;
  166. ChunkedInStream* = OBJECT
  167. VAR (* General vars: *)
  168. inR: Streams.Reader;
  169. remain: LONGINT;
  170. eof : BOOLEAN;
  171. (* Chunked mode vars: *)
  172. chunkSize: LONGINT;
  173. first : BOOLEAN;
  174. PROCEDURE &Init*(VAR inR, outR: Streams.Reader);
  175. BEGIN
  176. SELF.inR := inR;
  177. Streams.OpenReader(outR, Receiver);
  178. eof := FALSE; first := TRUE;
  179. END Init;
  180. PROCEDURE Receiver(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len: LONGINT; VAR res: WORD);
  181. VAR i: LONGINT; token: ARRAY 16 OF CHAR; ch: CHAR;
  182. BEGIN
  183. IF ~eof THEN
  184. ASSERT((size > 0) & (min <= size) & (min >= 0));
  185. len := 0; i := ofs; res := Streams.Ok; chunkSize := -1;
  186. WHILE (chunkSize # 0) & (res = Streams.Ok) & (len < size) DO
  187. (* Read the chunk size *)
  188. IF remain = 0 THEN
  189. IF ~first THEN inR.SkipLn END; first := FALSE;
  190. inR.Token(token);
  191. inR.SkipLn;
  192. Strings.HexStrToInt(token, chunkSize, res);
  193. remain := chunkSize
  194. END;
  195. (* Fill data into out buffer *)
  196. WHILE (res = Streams.Ok) & (len < size) & (remain > 0) DO
  197. inR.Char(ch);
  198. res := inR.res;
  199. buf[i] := ch;
  200. INC(len); INC(i); DEC(remain)
  201. END;
  202. END;
  203. IF chunkSize = 0 THEN eof := TRUE END
  204. ELSE
  205. res := Streams.EOF
  206. END
  207. END Receiver;
  208. END ChunkedInStream;
  209. (* writing to stream 'inW' writes 'size' characters to 'outW' and then stops, returning Streams.EOF when attempting to write beyond stream end *)
  210. (* implementation limination: 'remainder' bookkeeping and EOF detection occurs with Update(); but not after each Char() or Bytes(); a too large last data chunk may therefore be written only in part. when EOF is detected *)
  211. LimitedOutStream* = OBJECT
  212. VAR outW: Streams.Writer;
  213. buf: ARRAY BufSize OF CHAR;
  214. bufPos: LONGINT;
  215. remain-: LONGINT;
  216. PROCEDURE &Init*(VAR inW, outW: Streams.Writer; size : LONGINT);
  217. BEGIN
  218. SELF.outW := outW;
  219. remain := size;
  220. bufPos:=0;
  221. Streams.OpenWriter(inW, Sender);
  222. END Init;
  223. PROCEDURE Sender(CONST outBuf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: WORD);
  224. VAR i: LONGINT;
  225. BEGIN
  226. i := ofs;
  227. res:=outW.res ;
  228. WHILE (i < ofs+len) & (remain>0) DO
  229. buf[bufPos] := outBuf[i];
  230. INC(i);
  231. INC(bufPos);
  232. DEC(remain);
  233. IF (bufPos = BufSize) OR (remain=0) THEN Write END;
  234. IF propagate THEN outW.Update END
  235. END;
  236. IF (remain=0) & (i < ofs+len) THEN res:= Streams.EOF END;
  237. END Sender;
  238. PROCEDURE Write;
  239. BEGIN
  240. outW.Bytes(buf, 0, bufPos);
  241. bufPos := 0
  242. END Write;
  243. PROCEDURE Update*;
  244. BEGIN
  245. Write;
  246. outW.Update;
  247. END Update;
  248. PROCEDURE Padding*(ch: CHAR);
  249. VAR i:LONGINT;
  250. BEGIN
  251. Update; (*compute 'remain'*)
  252. WHILE remain>0 DO outW.Char(ch); DEC(remain) END;
  253. outW.Update;
  254. END Padding;
  255. END LimitedOutStream;
  256. LimitedInStream* = OBJECT
  257. VAR inR: Streams.Reader;
  258. remain-: LONGINT;
  259. PROCEDURE &Init*(VAR inR, outR: Streams.Reader; size : LONGINT);
  260. BEGIN
  261. SELF.inR := inR; remain := size;
  262. Streams.OpenReader(outR, Receiver);
  263. END Init;
  264. PROCEDURE Receiver(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len: LONGINT; VAR res: WORD);
  265. VAR l: LONGINT;
  266. BEGIN
  267. IF remain > 0 THEN
  268. ASSERT((size > 0) & (min <= size) & (min >= 0));
  269. res := Streams.Ok;
  270. l := size; IF l > remain THEN l := remain END;
  271. inR.Bytes(buf, ofs, l, len);
  272. DEC(remain, len);
  273. ELSE res := Streams.EOF
  274. END
  275. END Receiver;
  276. END LimitedInStream;
  277. PROCEDURE EOL(VAR in: Streams.Reader): BOOLEAN;
  278. BEGIN
  279. in.SkipSpaces;
  280. RETURN in.EOLN();
  281. END EOL;
  282. PROCEDURE GetToken(VAR in: Streams.Reader; VAR token: ARRAY OF CHAR);
  283. BEGIN
  284. in.SkipSpaces; in.Token(token)
  285. END GetToken;
  286. PROCEDURE GetInt(VAR i: LONGINT; CONST buf: ARRAY OF CHAR; VAR x: LONGINT);
  287. VAR ch: CHAR;
  288. BEGIN
  289. x := 0;
  290. LOOP
  291. ch := buf[i];
  292. IF (ch < "0") OR (ch > "9") THEN EXIT END;
  293. x := x * 10 + (ORD(ch)-ORD("0")); INC(i)
  294. END
  295. END GetInt;
  296. PROCEDURE Match(CONST buf: ARRAY OF CHAR; with: ARRAY OF CHAR; VAR i: LONGINT): BOOLEAN;
  297. VAR j: LONGINT;
  298. BEGIN
  299. j := 0; WHILE (j<LEN(with)) & (with[j] # 0X) & (i<LEN(buf)) & (buf[i] = with[j]) DO INC(i); INC(j) END;
  300. RETURN with[j] = 0X
  301. END Match;
  302. PROCEDURE EqualsI(CONST buf: ARRAY OF CHAR; with: ARRAY OF CHAR): BOOLEAN;
  303. VAR j: LONGINT;
  304. BEGIN
  305. j := 0; WHILE (with[j] # 0X) & (CAP(buf[j]) = CAP(with[j])) DO INC(j) END;
  306. RETURN CAP(with[j]) = CAP(buf[j])
  307. END EqualsI;
  308. (** Currently only for additional fields *)
  309. PROCEDURE HasAdditionalField*(af : AdditionalField; fieldName: ARRAY OF CHAR) : BOOLEAN;
  310. BEGIN
  311. WHILE (af # NIL) & (~EqualsI(af.key, fieldName)) DO af := af.next END;
  312. RETURN af # NIL
  313. END HasAdditionalField;
  314. (** Currently only for additional fields *)
  315. PROCEDURE GetAdditionalField*(af : AdditionalField; fieldName: ARRAY OF CHAR) : AdditionalField;
  316. BEGIN
  317. WHILE (af # NIL) & (~EqualsI(af.key, fieldName)) DO af := af.next END;
  318. RETURN af
  319. END GetAdditionalField;
  320. (** Currently only for additional fields *)
  321. PROCEDURE GetAdditionalFieldValue*(af: AdditionalField; fieldName: ARRAY OF CHAR; VAR value : ARRAY OF CHAR) : BOOLEAN;
  322. BEGIN
  323. WHILE (af # NIL) & (~EqualsI(af.key, fieldName)) DO af := af.next END;
  324. IF af # NIL THEN
  325. COPY(af.value, value);
  326. RETURN TRUE
  327. ELSE
  328. RETURN FALSE
  329. END
  330. END GetAdditionalFieldValue;
  331. (** return request property as a string *)
  332. PROCEDURE GetRequestPropertyValue*(VAR header : RequestHeader; propertyName : ARRAY OF CHAR; VAR result : ARRAY OF CHAR);
  333. BEGIN
  334. IF propertyName = "#ip" THEN IP.AdrToStr(header.fadr, result)
  335. ELSIF propertyName = "#port" THEN Strings.IntToStr(header.fport, result)
  336. ELSIF propertyName = "#method" THEN
  337. CASE header.method OF
  338. |GetM : COPY("GET", result)
  339. |HeadM : COPY("HEAD", result)
  340. |PutM : COPY("PUT", result)
  341. |PostM : COPY("POST", result)
  342. |OptionsM : COPY("OPTIONS", result)
  343. ELSE COPY("unknown", result)
  344. END
  345. ELSIF propertyName = "host" THEN COPY(header.host, result)
  346. ELSIF propertyName = "referer" THEN COPY(header.referer, result)
  347. ELSIF propertyName = "useragent" THEN COPY(header.useragent, result)
  348. ELSIF propertyName = "accept" THEN COPY(header.accept, result)
  349. ELSIF propertyName = "transferencoding" THEN COPY(header.transferencoding, result)
  350. ELSE
  351. IF ~GetAdditionalFieldValue(header.additionalFields, propertyName, result) THEN COPY("", result) END
  352. END
  353. END GetRequestPropertyValue;
  354. (** Currently only for additional fields *)
  355. PROCEDURE SetAdditionalFieldValue*(VAR af: AdditionalField; fieldName, value: ARRAY OF CHAR);
  356. VAR a: AdditionalField;
  357. BEGIN
  358. IF (af = NIL) THEN NEW(a); af := a
  359. ELSE
  360. a := af; WHILE (a.next # NIL) & (a.key # fieldName) DO a := a.next END;
  361. IF (a.key # fieldName) THEN
  362. NEW(a.next); a := a.next
  363. END
  364. END;
  365. COPY(fieldName, a.key); COPY(value, a.value)
  366. END SetAdditionalFieldValue;
  367. PROCEDURE GetVersion(VAR ver: ARRAY OF CHAR; VAR maj, min: LONGINT):BOOLEAN;
  368. VAR i: LONGINT;
  369. BEGIN
  370. i := 0; maj := 0; min := 0;
  371. IF Match(ver, "HTTP/", i) THEN
  372. GetInt(i, ver, maj);
  373. IF ver[i] = "." THEN INC(i) END;
  374. GetInt(i, ver, min);
  375. RETURN TRUE
  376. ELSE RETURN FALSE
  377. END
  378. END GetVersion;
  379. (** Version - returns TRUE iff the HTTP version specified in h.maj/h.min is bigger or equal to Maj/Min *)
  380. PROCEDURE Version*(VAR h: RequestHeader; Maj, Min: LONGINT): BOOLEAN;
  381. BEGIN
  382. RETURN (h.maj > Maj) OR ((h.maj = Maj) & (h.min >= Min))
  383. END Version;
  384. PROCEDURE GetMethod*(VAR s: ARRAY OF CHAR; VAR method: LONGINT);
  385. BEGIN
  386. IF s = "GET" THEN method := GetM
  387. ELSIF s = "HEAD" THEN method := HeadM
  388. ELSIF s = "OPTIONS" THEN method := OptionsM
  389. ELSIF s = "POST" THEN method := PostM
  390. ELSIF s = "PUT" THEN method := PutM
  391. ELSIF s = "DELETE" THEN method := DeleteM
  392. ELSIF s = "TRACE" THEN method := TraceM
  393. ELSIF s = "CONNECT" THEN method := ConnectM
  394. (* WebDAV *)
  395. ELSIF s = "PROPFIND" THEN method := PropfindM
  396. ELSIF s = "PROPPATCH" THEN method := ProppatchM
  397. ELSIF s = "MKCOL" THEN method := MkcolM
  398. ELSIF s = "COPY" THEN method := CopyM
  399. ELSIF s = "MOVE" THEN method := MoveM
  400. ELSIF s = "LOCK" THEN method := LockM
  401. ELSIF s = "UNLOCK" THEN method := UnlockM
  402. (* DeltaV *)
  403. ELSIF s = "VERSION-CONTROL" THEN method := VersionControlM
  404. ELSIF s = "REPORT" THEN method := ReportM
  405. ELSIF s = "CHECKOUT" THEN method := CheckoutM
  406. ELSIF s = "CHECKIN" THEN method := CheckinM
  407. ELSIF s = "UNCHECKOUT" THEN method := UncheckoutM
  408. ELSIF s = "MKWORKSPACE" THEN method := MkworkspaceM
  409. ELSIF s = "UPDATE" THEN method := UpdateM
  410. ELSIF s = "LABEL" THEN method := LabelM
  411. ELSIF s = "MERGE" THEN method := MergeM
  412. ELSIF s = "BASELINE-CONTROL" THEN method := BaselineControlM
  413. ELSIF s = "MKACTIVITY" THEN method := MkactivityM
  414. ELSE method := UnknownM
  415. END
  416. END GetMethod;
  417. PROCEDURE GetMethodName*(code: LONGINT; VAR name: ARRAY OF CHAR);
  418. BEGIN
  419. CASE code OF
  420. GetM : COPY("GET", name)
  421. |HeadM : COPY("HEAD", name);
  422. |OptionsM : COPY("OPTIONS", name);
  423. |PostM : COPY("POST", name);
  424. |PutM : COPY("PUT", name);
  425. |DeleteM : COPY("DELETE", name);
  426. |TraceM : COPY("TRACE", name);
  427. |ConnectM : COPY("CONNECT", name);
  428. (* WebDAV *)
  429. |PropfindM: COPY("PROPFIND", name);
  430. |ProppatchM: COPY("PROPPATCH", name);
  431. |MkcolM: COPY("MKCOL", name);
  432. |CopyM: COPY("COPY", name);
  433. |MoveM: COPY("MOVE", name);
  434. |LockM: COPY("LOCK", name);
  435. |UnlockM: COPY("UNLOCK", name);
  436. (* DeltaV *)
  437. |VersionControlM: COPY("VERSION-CONTROL", name);
  438. |ReportM: COPY("REPORT", name);
  439. |CheckoutM: COPY("CHECKOUT", name);
  440. |CheckinM: COPY("CHECKIN", name);
  441. |UncheckoutM: COPY("UNCHECKOUT", name);
  442. |MkworkspaceM: COPY("MKWORKSPACE", name);
  443. |UpdateM: COPY("UPDATE", name);
  444. |LabelM: COPY("LABEL", name);
  445. |MergeM: COPY("MERGE", name);
  446. |BaselineControlM: COPY("BASELINE-CONTROL", name);
  447. |MkactivityM: COPY("MKACTIVITY", name);
  448. ELSE COPY("UNKOWN", name)
  449. END;
  450. END GetMethodName;
  451. PROCEDURE ParseRequest*(VAR in: Streams.Reader; VAR header: RequestHeader; VAR res: WORD; log : TFLog.Log);
  452. VAR s: ARRAY 32 OF CHAR; af: AdditionalField; ch :CHAR; wellformed: BOOLEAN;
  453. BEGIN
  454. header.host[0] := 0X;
  455. (*in.SkipWhitespace; *)(* optimization PH 2012: to avoid unnecessary work in malformed requests*)
  456. GetToken(in, s); GetMethod(s, header.method);
  457. GetToken(in, header.uri);
  458. GetToken(in, s);
  459. wellformed:=GetVersion(s, header.maj, header.min);
  460. header.host := ""; header.referer := ""; header.useragent := ""; header.accept := ""; header.transferencoding := "";
  461. header.additionalFields := NIL;
  462. IF wellformed & EOL(in) & (header.method # UnknownM) & (header.uri # "") THEN
  463. in.SkipLn();
  464. IF header.maj >= 1 THEN
  465. ParseRequestHeaderFields(in,header,res); (* PH120209 disentangled ParseRequestHeaderFields*)
  466. in.SkipLn;
  467. ELSE
  468. IF log # NIL THEN log.Enter; log.String("Unsupported HTTP version :"); log.Int(header.maj, 5); log.Exit END;
  469. res := VersionNotSupported
  470. END
  471. ELSE
  472. IF EOL(in) THEN in.SkipLn(); END; (*PH Jan 2012*)
  473. IF log # NIL THEN log.Enter; log.String("Bad request :"); log.Int(header.method, 5); log.Exit END;
  474. res := BadRequest
  475. END
  476. END ParseRequest;
  477. PROCEDURE ParseRequestHeaderFields*(VAR in: Streams.Reader; VAR header: RequestHeader; VAR res: WORD);
  478. VAR s: ARRAY 32 OF CHAR; af: AdditionalField; ch :CHAR; i:LONGINT;
  479. BEGIN
  480. i:=0;
  481. header.additionalFields:=NIL; (*PH 120210*)
  482. REPEAT
  483. GetToken(in, s);
  484. Strings.TrimRight(s, ":");
  485. IF s = "Host" THEN in.Char(ch); in.Ln(header.host)
  486. ELSIF s = "Referer" THEN in.Char(ch); in.Ln(header.referer)
  487. ELSIF s = "User-Agent" THEN in.Char(ch); in.Ln(header.useragent)
  488. ELSIF s = "Accept" THEN in.Char(ch); in.Ln(header.accept)
  489. ELSIF s = "Transfer-Encoding" THEN in.Char(ch); in.Ln( header.transferencoding)
  490. ELSE
  491. NEW(af); COPY(s, af.key); in.Char(ch); in.Ln(af.value);
  492. af.next := header.additionalFields; header.additionalFields := af;
  493. INC(i);
  494. END;
  495. IF i > MaxRequestHeaderFields-5 THEN res:=RequestEntityTooLarge; RETURN END; (* hardening against malignant requests*)
  496. UNTIL (in.res # Streams.Ok) OR in.EOLN();
  497. res := OK
  498. END ParseRequestHeaderFields;
  499. PROCEDURE ParseReply*(VAR in: Streams.Reader; VAR header: ResponseHeader; VAR res: WORD; log : TFLog.Log);
  500. VAR s, sLow: ARRAY 32 OF CHAR; af: AdditionalField;
  501. i :LONGINT; ch :CHAR; wellformed: BOOLEAN;
  502. BEGIN
  503. GetToken(in, s);
  504. wellformed:=GetVersion(s, header.maj, header.min);
  505. GetToken(in, s); i := 0; GetInt(i, s, header.statuscode); in.Ln(header.reasonphrase);
  506. header.server := ""; header.date := ""; header.contenttype := "";
  507. header.contentlength := -1;
  508. header.transferencoding := ""; header.additionalFields := NIL;
  509. header.contentlocation := "";
  510. IF header.maj >= 1 THEN
  511. REPEAT
  512. GetToken(in, s);
  513. Strings.TrimRight(s, ":");
  514. (* to understand the Micros**t IIS replies *)
  515. Strings.Copy(s, 0, 32, sLow);
  516. Strings.LowerCase(sLow);
  517. IF sLow = "server" THEN in.Char(ch); in.Ln(header.server)
  518. ELSIF sLow = "date" THEN in.Char(ch);in.Ln(header.date)
  519. ELSIF sLow = "location" THEN in.Char(ch);in.Ln(header.location)
  520. ELSIF sLow = "content-type" THEN in.Char(ch); in.Ln(header.contenttype)
  521. ELSIF sLow = "content-length" THEN in.Char(ch); in.Ln(s); Strings.StrToInt(s, header.contentlength)
  522. ELSIF sLow = "content-location" THEN in.Char(ch);in.Ln(header.contentlocation)
  523. ELSIF sLow = "transfer-encoding" THEN in.Char(ch); in.Ln(header.transferencoding)
  524. ELSIF sLow = "last-modified" THEN in.Char(ch);in.Ln(header.lastmodified)
  525. ELSE
  526. NEW(af); COPY(s, af.key); in.Char(ch); in.Ln(af.value);
  527. af.next := header.additionalFields; header.additionalFields := af
  528. END;
  529. UNTIL (in.res # Streams.Ok) OR in.EOLN();
  530. in.SkipLn();
  531. res := OK
  532. ELSE
  533. IF log # NIL THEN log.Enter; log.String("Unsupported HTTP version :"); log.Int(header.maj, 5); log.Exit END;
  534. res := VersionNotSupported
  535. END;
  536. END ParseReply;
  537. PROCEDURE ModifyReply*(VAR in: Streams.Reader; VAR header: ResponseHeader; VAR res: WORD; log : TFLog.Log);
  538. VAR s, sLow: ARRAY 32 OF CHAR; af: AdditionalField;
  539. i :LONGINT; ch :CHAR;
  540. BEGIN
  541. REPEAT
  542. GetToken(in, s);
  543. Strings.TrimRight(s, ":");
  544. (* to understand the Microsoft IIS replies *)
  545. Strings.Copy(s, 0, 32, sLow);
  546. Strings.LowerCase(sLow);
  547. IF sLow = "server" THEN in.Char(ch); in.Ln(header.server)
  548. ELSIF sLow = "date" THEN in.Char(ch);in.Ln(header.date)
  549. ELSIF sLow = "location" THEN in.Char(ch);in.Ln(header.location)
  550. ELSIF sLow = "content-type" THEN in.Char(ch); in.Ln(header.contenttype)
  551. ELSIF sLow = "content-length" THEN in.Char(ch); in.Ln(s); Strings.StrToInt(s, header.contentlength)
  552. ELSIF sLow = "content-location" THEN in.Char(ch);in.Ln(header.contentlocation)
  553. ELSIF sLow = "transfer-encoding" THEN in.Char(ch); in.Ln(header.transferencoding)
  554. ELSIF sLow = "last-modified" THEN in.Char(ch);in.Ln(header.lastmodified)
  555. ELSE
  556. NEW(af); COPY(s, af.key); in.Char(ch); in.Ln(af.value); (*! to do: check if a field already exists -> replace instead of append *)
  557. af.next := header.additionalFields; header.additionalFields := af
  558. END;
  559. UNTIL (in.res # Streams.Ok) OR in.EOLN();
  560. in.SkipLn();
  561. res := OK
  562. END ModifyReply;
  563. PROCEDURE LogRequestHeader*(log : TFLog.Log; VAR header : RequestHeader);
  564. VAR s : ARRAY 32 OF CHAR; x: AdditionalField;
  565. BEGIN
  566. log.Enter;
  567. log.String("BEGIN HTTP-Request Header information ("); log.TimeStamp; log.String(")"); log.Ln;
  568. log.String(" HTTP request from "); IP.AdrToStr(header.fadr, s); log.String(s); log.String(" : "); log.Int(header.fport, 5); log.Ln;
  569. log.String("Request: ");
  570. GetMethodName(header.method, s); log.String(s);
  571. log.String(" "); log.String(header.uri); log.Ln;
  572. IF header.host # "" THEN log.String("Host: "); log.String(header.host); log.Ln END;
  573. IF header.referer # "" THEN log.String("Referer: "); log.String(header.referer); log.Ln END;
  574. IF header.useragent # "" THEN log.String("User-Agent: "); log.String(header.useragent); log.Ln END;
  575. IF header.accept # "" THEN log.String("Accept: "); log.String(header.accept); log.Ln END;
  576. x := header.additionalFields;
  577. WHILE x # NIL DO
  578. log.String(x.key); log.String(": "); log.String(x.value); log.Ln;
  579. x := x.next
  580. END;
  581. log.String("END HTTP-Request Header information"); log.Ln; log.Ln;
  582. log.Exit;
  583. END LogRequestHeader;
  584. PROCEDURE LogResponseHeader*(log : TFLog.Log; VAR header : ResponseHeader);
  585. VAR x: AdditionalField;
  586. BEGIN
  587. log.Enter;
  588. log.String("BEGIN HTTP-Reply Header information ("); log.TimeStamp; log.String(")"); log.Ln;
  589. log.String("Status Code: "); log.Int(header.statuscode, 5); log.String(" Reason: "); log.String(header.reasonphrase); log.Ln;
  590. IF header.server # "" THEN log.String("Server: "); log.String(header.server); log.Ln END;
  591. IF header.date # "" THEN log.String("Date: "); log.String(header.date); log.Ln END;
  592. IF header.location # "" THEN log.String("Location: "); log.String(header.location); log.Ln END;
  593. IF header.contenttype # "" THEN log.String("Content-Type: "); log.String(header.contenttype); log.Ln END;
  594. IF header.contentlength # 0 THEN log.String("Content-Length: "); log.Int(header.contentlength, 0); log.Ln END;
  595. IF header.contentlocation # "" THEN log.String("Content-Location: "); log.String(header.contentlocation); log.Ln END;
  596. IF header.transferencoding # "" THEN log.String("Transfer-Encoding: "); log.String(header.transferencoding); log.Ln END;
  597. IF header.lastmodified # "" THEN log.String("Last-Modified: "); log.String(header.lastmodified); log.Ln END;
  598. x := header.additionalFields;
  599. WHILE x # NIL DO
  600. log.String(x.key); log.String(": "); log.String(x.value); log.Ln;
  601. x := x.next
  602. END;
  603. log.String("END HTTP-Reply Header information"); log.Ln; log.Ln;
  604. log.Exit;
  605. END LogResponseHeader;
  606. PROCEDURE WriteRequestLine*(s: Streams.Writer; maj, min : LONGINT; method : LONGINT; uri, host : ARRAY OF CHAR);
  607. VAR name: ARRAY 32 OF CHAR;
  608. BEGIN
  609. GetMethodName(method, name);
  610. IF name = "UNKNOWN" THEN RETURN ELSE s.String(name) END;
  611. s.String(" "); s.String(uri); s.String(" ");
  612. s.String("HTTP/"); s.Int(maj, 1); s.String("."); s.Int(min, 1);
  613. s.Ln();
  614. IF host # "" THEN s.String("Host: "); s.String(host); s.Ln() END
  615. END WriteRequestLine;
  616. PROCEDURE GetReasonPhrase*(code: LONGINT; VAR phrase: ARRAY OF CHAR);
  617. BEGIN
  618. (* Informational *)
  619. IF (code = Continue) THEN COPY("Continue", phrase)
  620. ELSIF (code = SwitchingProtocols) THEN COPY("Switching Protocols", phrase)
  621. ELSIF (code = Processing) THEN COPY("Processing", phrase)
  622. (* successful *)
  623. ELSIF (code = OK) THEN COPY("OK", phrase);
  624. ELSIF (code = Created) THEN COPY("Created", phrase)
  625. ELSIF (code = Accepted) THEN COPY("Accepted", phrase)
  626. ELSIF (code = NonAuthoritativeInformation) THEN COPY("Non-Authoritative Information", phrase)
  627. ELSIF (code = NoContent) THEN COPY("No Content", phrase)
  628. ELSIF (code = ResetContent) THEN COPY("Reset Content", phrase)
  629. ELSIF (code = PartialContent) THEN COPY("Partial Content", phrase)
  630. ELSIF (code = MultiStatus) THEN COPY("Multi-Status", phrase)
  631. (* Redirection *)
  632. ELSIF (code = MultipleChoices) THEN COPY("Multiple Choices", phrase)
  633. ELSIF (code = ObjectMoved) THEN COPY("Object moved", phrase)
  634. ELSIF (code = ObjectMovedTemporarily) THEN COPY("Object Moved Temporarily", phrase)
  635. ELSIF (code = SeeOther) THEN COPY("See Other", phrase)
  636. ELSIF (code = NotModified) THEN COPY("Not modified", phrase)
  637. ELSIF (code = UseProxy) THEN COPY("Use Proxy", phrase)
  638. ELSIF (code = TemporaryRedirect) THEN COPY("Temporary Redirect", phrase)
  639. (* Client Error *)
  640. ELSIF (code = BadRequest) THEN COPY("Bad request", phrase)
  641. ELSIF (code = Unauthorized) THEN COPY("Unauthorized", phrase)
  642. ELSIF (code = PaymentRequired) THEN COPY("Payment Required", phrase)
  643. ELSIF (code = Forbidden) THEN COPY("Forbidden", phrase)
  644. ELSIF (code = NotFound) THEN COPY("Not found", phrase)
  645. ELSIF (code = MethodNotAllowed) THEN COPY("Method Not Allowed", phrase)
  646. ELSIF (code = NotAcceptable) THEN COPY("Not Acceptable", phrase)
  647. ELSIF (code = ProxyAuthenticationRequested) THEN COPY("Proxy Authentication Requested", phrase)
  648. ELSIF (code = RequestTimeout) THEN COPY("Request Timeout", phrase)
  649. ELSIF (code = Conflict) THEN COPY("Conflict", phrase)
  650. ELSIF (code = Gone) THEN COPY("Gone", phrase)
  651. ELSIF (code = LengthRequired) THEN COPY("Length required", phrase)
  652. ELSIF (code = PreconditionFailed) THEN COPY("Precondition failed", phrase)
  653. ELSIF (code = RequestEntityTooLarge) THEN COPY("Request Entity Too Large", phrase)
  654. ELSIF (code = RequestURITooLong) THEN COPY("Request URI too long", phrase)
  655. ELSIF (code = UnsupportedMediaType) THEN COPY("Unsupported Media Type", phrase)
  656. ELSIF (code = RequestedRangeNotSatisfiable) THEN COPY("Requested Range Not Satisfiable", phrase)
  657. ELSIF (code = ExpectationFailed) THEN COPY("Expectation Failed", phrase)
  658. ELSIF (code = UnprocessableEntity) THEN COPY("Unprocessable Entity", phrase)
  659. ELSIF (code = Locked) THEN COPY("Locked", phrase)
  660. ELSIF (code = FailedDependency) THEN COPY("Failed Dependency", phrase)
  661. (* Server Error *)
  662. ELSIF (code = InternalServerError) THEN COPY("Internal server error", phrase)
  663. ELSIF (code = NotImplemented) THEN COPY("Operation not implemented", phrase)
  664. ELSIF (code = BadGateway) THEN COPY("Bad Gateway", phrase)
  665. ELSIF (code = ServiceUnavailable) THEN COPY("Service Unavailable", phrase)
  666. ELSIF (code = GatewayTimeout) THEN COPY("Gateway Timeout", phrase)
  667. ELSIF (code = VersionNotSupported) THEN COPY("HTTP Version not supported", phrase)
  668. ELSIF (code = InsufficientStorage) THEN COPY("Insufficient Storage", phrase)
  669. ELSE COPY("Unknown Status Code", phrase) (* Was "HTTP server error" *)
  670. END;
  671. END GetReasonPhrase;
  672. PROCEDURE WriteStatus*(VAR h: ResponseHeader; VAR dst: Streams.Writer);
  673. BEGIN
  674. dst.String("HTTP/"); dst.Int(h.maj, 1); dst.String("."); dst.Int(h.min, 1);
  675. dst.String(" ");dst.Int(h.statuscode, 1); dst.String(" ");
  676. GetReasonPhrase(h.statuscode, h.reasonphrase);
  677. dst.String(h.reasonphrase); dst.Ln();
  678. dst.String("Server: "); dst.String(h.server); dst.Ln()
  679. END WriteStatus;
  680. (* precondition: header statuscode and header reasonphrase are already filled in, e.g. by use of WriteStatus() *)
  681. PROCEDURE WriteHTMLStatus*(VAR h: ResponseHeader; dst: Streams.Writer);
  682. VAR reasonphrase: ARRAY 64 OF CHAR;
  683. BEGIN
  684. dst.String(DocType); dst.Ln;
  685. dst.String("<html><head><title>"); dst.Int(h.statuscode,0); dst.String(" - "); dst.String(h.reasonphrase); dst.String("</title></head>");
  686. dst.String("<body>HTTP "); dst.Int(h.statuscode,0); dst.String(" - "); dst.String(h.reasonphrase); dst.String("<hr><address>");
  687. dst.String(h.server); dst.String( "</address></body></html>"); dst.Ln;
  688. END WriteHTMLStatus;
  689. PROCEDURE SendResponseHeader*(VAR h: ResponseHeader; VAR dst: Streams.Writer);
  690. VAR s: ARRAY 32 OF CHAR; af: AdditionalField;
  691. BEGIN
  692. WriteStatus(h, dst);
  693. Strings.FormatDateTime("www, dd mmm yyyy, hh:nn:ss GMT", Dates.Now(), s);
  694. dst.String("Date: "); dst.String(s); dst.Ln();
  695. IF (h.statuscode # NotModified) THEN
  696. IF (h.location # "") THEN
  697. dst.String("Location: "); dst.String(h.location); dst.Ln()
  698. END;
  699. dst.String("Content-Type: "); dst.String(h.contenttype); dst.Ln();
  700. IF (h.contentlength >= 0) THEN
  701. dst.String("Content-Length: "); dst.Int( h.contentlength, 1); dst.Ln()
  702. END;
  703. IF (h.contentlocation # "") THEN
  704. dst.String("Content-Location: "); dst.String(h.contentlocation); dst.Ln()
  705. END;
  706. IF (h.transferencoding # "") THEN
  707. dst.String("Transfer-Encoding: "); dst.String(h.transferencoding); dst.Ln()
  708. END;
  709. IF (h.lastmodified # "") THEN
  710. dst.String("Last-Modified: ");dst.String(h.lastmodified); dst.Ln()
  711. END;
  712. af := h.additionalFields;
  713. WHILE (af # NIL) DO
  714. dst.String(af.key); dst.String(": "); dst.String(af.value); dst.Ln();
  715. af := af.next
  716. END
  717. END;
  718. dst.Ln()
  719. END SendResponseHeader;
  720. PROCEDURE SendStatusReply*(code:LONGINT; VAR request: RequestHeader; VAR reply: ResponseHeader; VAR out: Streams.Writer);
  721. VAR w : Streams.Writer;
  722. chunker: ChunkedOutStream;
  723. BEGIN
  724. reply.statuscode := code;
  725. GetReasonPhrase(code, reply.reasonphrase);
  726. reply.contenttype := "text/html; charset=UTF-8";
  727. NEW(chunker, w, out, request, reply);
  728. SendResponseHeader(reply, out);
  729. WriteHTMLStatus(reply, w);
  730. w.Update;
  731. chunker.Close
  732. END SendStatusReply;
  733. PROCEDURE GetPath*(VAR url, path : ARRAY OF CHAR);
  734. VAR i, j : LONGINT;
  735. protocol : ARRAY 8 OF CHAR;
  736. BEGIN
  737. IF Strings.Length(url) < 7 THEN COPY(url, path)
  738. ELSE
  739. Strings.Copy(url, 0, 7, protocol); Strings.UpperCase(protocol);
  740. i := 0;
  741. IF protocol = "HTTP://" THEN i := 7
  742. ELSIF protocol = "HTTPS:/" THEN i := 8
  743. END;
  744. IF i > 0 THEN
  745. WHILE (url[i] # "/") & (url[i] # 0X) DO INC(i) END;
  746. IF url[i] # 0X THEN j := 0; REPEAT path[j] := url[i]; INC(i); INC(j) UNTIL url[i] = 0X
  747. ELSE path := "/"
  748. END
  749. ELSE COPY(url, path)
  750. END
  751. END
  752. END GetPath;
  753. PROCEDURE SplitHTTPAdr*(url : ARRAY OF CHAR; VAR host, path: ARRAY OF CHAR; VAR port: LONGINT): BOOLEAN;
  754. VAR i, j : LONGINT;
  755. BEGIN
  756. (*assuming HTTP or HTTPS*)
  757. IF (LEN(url)>7) &(url[4] = ":") & (url[5] = "/") & (url[6] = "/") THEN i:=7; port:=HTTPPort;
  758. ELSIF (LEN(url)>8) & (url[5] = ":") & (url[6] = "/") & (url[7] = "/") THEN i:=8; port:=HTTPSPort;
  759. ELSE RETURN FALSE
  760. END;
  761. (* get host *)
  762. j := 0;
  763. WHILE (url[i] # ":") & (url[i] # "/") & (url[i] # 0X) DO
  764. IF j < LEN(host) - 1 THEN host[j] := url[i] ELSE RETURN FALSE END;
  765. INC(i); INC(j);
  766. IF i = LEN(url) THEN RETURN FALSE END
  767. END;
  768. host[j] := 0X;
  769. (* get port *)
  770. IF url[i] = ":" THEN
  771. port := 0;
  772. INC(i);
  773. WHILE (i < LEN(url)) & (ORD(url[i]) >= ORD("0")) & (ORD(url[i]) <= ORD("9")) DO
  774. port := port * 10 + (ORD(url[i]) - ORD("0"));
  775. INC(i)
  776. END
  777. END;
  778. j := 0;
  779. WHILE (i < LEN(url)) & (url[i] # 0X) DO
  780. IF j < LEN(host) - 1 THEN path[j] := url[i] ELSE RETURN FALSE END;
  781. INC(i); INC(j);
  782. IF i = LEN(url) THEN RETURN FALSE END
  783. END;
  784. path[j] := 0X;
  785. RETURN TRUE
  786. END SplitHTTPAdr;
  787. END WebHTTP.
  788. System.FreeDownTo WebHTTP~