1{ Web server component, built on the HTTP server component 2 3 Copyright (C) 2006-2008 Micha Nelissen 4 5 This library is Free software; you can redistribute it and/or modify it 6 under the terms of the GNU Library General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or (at your 8 option) any later version. 9 10 This program is diStributed in the hope that it will be useful, but WITHOUT 11 ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or 12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 13 for more details. 14 15 You should have received a Copy of the GNU Library General Public License 16 along with This library; if not, Write to the Free Software Foundation, 17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 19 This license has been modified. See file LICENSE.ADDON for more information. 20 Should you find these sources without a LICENSE File, please contact 21 me at ales@chello.sk 22} 23 24unit lwebserver; 25 26{$mode objfpc}{$h+} 27{$inline on} 28 29interface 30 31uses 32 sysutils, classes, lhttp, lhttputil, lmimetypes, levents, 33 lprocess, process, lfastcgi, fastcgi_base; 34 35type 36 TLMultipartParameter = (mpContentType, mpContentDisposition, mpContentTransferEncoding, 37 mpContentID, mpContentDescription); 38 TLMultipartState = (msStart, msBodypartHeader, msBodypartData); 39 40const 41 URIParamSepChar: char = '&'; 42 CookieSepChar: char = ';'; 43 FormURLContentType: pchar = 'application/x-www-form-urlencoded'; 44 MultipartContentType: pchar = 'multipart/form-data'; 45 MPParameterStrings: array[TLMultipartParameter] of string = 46 ('Content-Type', 'Content-Disposition', 'Content-Transfer-Encoding', 47 'Content-ID', 'Content-Discription'); 48 49type 50 TDocumentHandler = class; 51 TFileHandler = class; 52 53 TFileOutput = class(TBufferOutput) 54 protected 55 FFile: file; 56 57 function GetSize: integer; 58 function FillBuffer: TWriteBlockStatus; override; 59 public 60 constructor Create(ASocket: TLHTTPSocket); 61 destructor Destroy; override; 62 63 function Open(const AFileName: string): boolean; 64 65 property Size: integer read GetSize; 66 end; 67 68 TCGIOutput = class(TBufferOutput) 69 protected 70 FParsePos: pchar; 71 FReadPos: integer; 72 FParsingHeaders: boolean; 73 74 procedure AddEnvironment(const AName, AValue: string); virtual; abstract; 75 procedure AddHTTPParam(const AName: string; AParam: TLHTTPParameter); 76 function ParseHeaders: boolean; 77 procedure CGIOutputError; virtual; abstract; 78 procedure WriteCGIBlock; 79 function WriteCGIData: TWriteBlockStatus; virtual; abstract; 80 public 81 FDocumentRoot: string; 82 FExtraPath: string; 83 FEnvPath: string; 84 FScriptFileName: string; 85 FScriptName: string; 86 87 constructor Create(ASocket: TLHTTPSocket); 88 destructor Destroy; override; 89 90 function FillBuffer: TWriteBlockStatus; override; 91 procedure StartRequest; virtual; 92 end; 93 94 TSimpleCGIOutput = class(TCGIOutput) 95 protected 96 FProcess: TLProcess; 97 98 procedure AddEnvironment(const AName, AValue: string); override; 99 procedure CGIProcNeedInput(AHandle: TLHandle); 100 procedure CGIProcHasOutput(AHandle: TLHandle); 101 procedure CGIProcHasStderr(AHandle: TLHandle); 102 procedure DoneInput; override; 103 function HandleInput(ABuffer: pchar; ASize: integer): integer; override; 104 procedure CGIOutputError; override; 105 function WriteCGIData: TWriteBlockStatus; override; 106 public 107 constructor Create(ASocket: TLHTTPSocket); 108 destructor Destroy; override; 109 110 procedure StartRequest; override; 111 112 property Process: TLProcess read FProcess; 113 end; 114 115 TFastCGIOutput = class(TCGIOutput) 116 protected 117 FRequest: TLFastCGIRequest; 118 119 procedure AddEnvironment(const AName, AValue: string); override; 120 procedure CGIOutputError; override; 121 procedure DoneInput; override; 122 procedure RequestEnd(ARequest: TLFastCGIRequest); 123 procedure RequestNeedInput(ARequest: TLFastCGIRequest); 124 procedure RequestHasOutput(ARequest: TLFastCGIRequest); 125 procedure RequestHasStderr(ARequest: TLFastCGIRequest); 126 function HandleInput(ABuffer: pchar; ASize: integer): integer; override; 127 function WriteCGIData: TWriteBlockStatus; override; 128 function WriteBlock: TWriteBlockStatus; override; 129 public 130 constructor Create(ASocket: TLHTTPSocket); 131 destructor Destroy; override; 132 133 procedure StartRequest; override; 134 135 property Request: TLFastCGIRequest read FRequest write FRequest; 136 end; 137 138 TCGIHandler = class(TURIHandler) 139 protected 140 function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override; 141 public 142 FCGIRoot: string; 143 FEnvPath: string; 144 FDocumentRoot: string; 145 FScriptPathPrefix: string; 146 end; 147 148 TDocumentRequest = record 149 Socket: TLHTTPServerSocket; 150 Document: string; 151 URIPath: string; 152 ExtraPath: string; 153 Info: TSearchRec; 154 InfoValid: boolean; 155 end; 156 157 TDocumentHandler = class(TObject) 158 private 159 FNext: TDocumentHandler; 160 protected 161 FFileHandler: TFileHandler; 162 163 procedure RegisterWithEventer(AEventer: TLEventer); virtual; 164 public 165 function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; virtual; abstract; 166 167 property FileHandler: TFileHandler read FFileHandler; 168 end; 169 170 { TFileHandler } 171 172 TFileHandler = class(TURIHandler) 173 protected 174 FDocHandlerList: TDocumentHandler; 175 FDirIndexList: TStrings; 176 FMimeTypeFile: string; 177 178 procedure SetMimeTypeFile(const AValue: string); 179 function HandleFile(const ARequest: TDocumentRequest): TOutputItem; 180 function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override; 181 procedure RegisterWithEventer(AEventer: TLEventer); override; 182 public 183 DocumentRoot: string; 184 185 constructor Create; 186 destructor Destroy; override; 187 188 procedure RegisterHandler(AHandler: TDocumentHandler); 189 190 property DirIndexList: TStrings read FDirIndexList; 191 property MimeTypeFile: string read FMimeTypeFile write SetMimeTypeFile; 192 end; 193 194 TPHPCGIHandler = class(TDocumentHandler) 195 protected 196 FAppName: string; 197 FEnvPath: string; 198 public 199 function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; override; 200 201 property AppName: string read FAppName write FAppName; 202 property EnvPath: string read FEnvPath write FEnvPath; 203 end; 204 205 TPHPFastCGIHandler = class(TDocumentHandler) 206 protected 207 FPool: TLFastCGIPool; 208 FEnvPath: string; 209 210 function GetAppEnv: string; 211 function GetAppName: string; 212 function GetHost: string; 213 function GetPort: integer; 214 procedure RegisterWithEventer(AEventer: TLEventer); override; 215 procedure SetAppEnv(NewEnv: string); 216 procedure SetAppName(NewName: string); 217 procedure SetHost(NewHost: string); 218 procedure SetPort(NewPort: integer); 219 public 220 constructor Create; 221 destructor Destroy; override; 222 223 function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; override; 224 225 property AppEnv: string read GetAppEnv write SetAppEnv; 226 property AppName: string read GetAppName write SetAppName; 227 property EnvPath: string read FEnvPath write FEnvPath; 228 property Host: string read GetHost write SetHost; 229 property Pool: TLFastCGIPool read FPool; 230 property Port: integer read GetPort write SetPort; 231 end; 232 233 { Forms } 234 235 TFormOutput = class; 236 237 TFillBufferEvent = procedure(AFormOutput: TFormOutput; var AStatus: TWriteBlockStatus); 238 THandleInputMethod = function(ABuffer: pchar; ASize: integer): integer of object; 239 240 TFormOutput = class(TBufferOutput) 241 protected 242 FBoundary: pchar; 243 FRequestVars: TStrings; 244 FMPParameters: array[TLMultipartParameter] of pchar; 245 FMPState: TLMultipartState; 246 FOnExtraHeaders: TNotifyEvent; 247 FOnFillBuffer: TFillBufferEvent; 248 FHandleInput: THandleInputMethod; 249 250 procedure DoneInput; override; 251 function FillBuffer: TWriteBlockStatus; override; 252 function FindBoundary(ABuffer: pchar): pchar; 253 function HandleInput(ABuffer: pchar; ASize: integer): integer; override; 254 function HandleInputDiscard(ABuffer: pchar; ASize: integer): integer; 255 function HandleInputFormURL(ABuffer: pchar; ASize: integer): integer; 256 function HandleInputMultipart(ABuffer: pchar; ASize: integer): integer; 257 procedure ParseMultipartHeader(ABuffer, ALineEnd: pchar); 258 public 259 constructor Create(ASocket: TLHTTPSocket); 260 destructor Destroy; override; 261 262 function AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer; 263 procedure DeleteCookie(const AName: string; const APath: string = '/'; 264 const ADomain: string = ''); 265 procedure SetCookie(const AName, AValue: string; const AExpires: TDateTime; 266 const APath: string = '/'; const ADomain: string = ''); 267 268 property OnExtraHeaders: TNotifyEvent read FOnExtraHeaders write FOnExtraHeaders; 269 property OnFillBuffer: TFillBufferEvent read FOnFillBuffer write FOnFillBuffer; 270 end; 271 272 THandleURIEvent = function(ASocket: TLHTTPServerSocket): TFormOutput; 273 274 TFormHandler = class(TURIHandler) 275 protected 276 FOnHandleURI: THandleURIEvent; 277 278 function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override; 279 procedure SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar); 280 public 281 property OnHandleURI: THandleURIEvent read FOnHandleURI write FOnHandleURI; 282 end; 283 284var 285 EnableWriteln: Boolean = True; 286 287implementation 288 289uses 290 lstrbuffer; 291 292{ Example handlers } 293 294const 295 InputBufferEmptyToWriteStatus: array[boolean] of TWriteBlockStatus = 296 (wsPendingData, wsWaitingData); 297 298procedure InternalWrite(const s: string); 299begin 300 if EnableWriteln then 301 Writeln(s); 302end; 303 304procedure TDocumentHandler.RegisterWithEventer(AEventer: TLEventer); 305begin 306end; 307 308function TCGIHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; 309var 310 lOutput: TSimpleCGIOutput; 311 lExecPath: string; 312begin 313 if StrLComp(ASocket.FRequestInfo.Argument, PChar(FScriptPathPrefix), 314 Length(FScriptPathPrefix)) = 0 then 315 begin 316 lOutput := TSimpleCGIOutput.Create(ASocket); 317 lOutput.FDocumentRoot := FDocumentRoot; 318 lOutput.FEnvPath := FEnvPath; 319 lOutput.Process.CurrentDirectory := FCGIRoot; 320 lExecPath := ASocket.FRequestInfo.Argument+Length(FScriptPathPrefix); 321 DoDirSeparators(lExecPath); 322 lExecPath := FCGIRoot+lExecPath; 323 if SeparatePath(lExecPath, lOutput.FExtraPath, faAnyFile and not faDirectory) then 324 begin 325 lOutput.Process.CommandLine := lExecPath; 326 lOutput.FScriptFileName := lExecPath; 327 lOutput.FScriptName := Copy(lExecPath, Length(FCGIRoot), 328 Length(lExecPath)-Length(FCGIRoot)+1); 329 lOutput.StartRequest; 330 end else 331 ASocket.FResponseInfo.Status := hsNotFound; 332 Result := lOutput; 333 end else 334 Result := nil; 335end; 336 337constructor TFileHandler.Create; 338begin 339 inherited; 340 341 FDirIndexList := TStringList.Create; 342end; 343 344destructor TFileHandler.Destroy; 345begin 346 FreeAndNil(FDirIndexList); 347 348 inherited; 349end; 350 351procedure TFileHandler.RegisterWithEventer(AEventer: TLEventer); 352var 353 lHandler: TDocumentHandler; 354begin 355 lHandler := FDocHandlerList; 356 while lHandler <> nil do 357 begin 358 lHandler.RegisterWithEventer(AEventer); 359 lHandler := lHandler.FNext; 360 end; 361end; 362 363procedure TFileHandler.SetMimeTypeFile(const AValue: string); 364begin 365 FMimeTypeFile:=AValue; 366 InitMimeList(aValue); 367end; 368 369function TFileHandler.HandleFile(const ARequest: TDocumentRequest): TOutputItem; 370var 371 lFileOutput: TFileOutput; 372 lReqInfo: PRequestInfo; 373 lRespInfo: PResponseInfo; 374 lHeaderOut: PHeaderOutInfo; 375 lIndex: integer; 376begin 377 Result := nil; 378 if ARequest.InfoValid then 379 begin 380 lReqInfo := @ARequest.Socket.FRequestInfo; 381 lRespInfo := @ARequest.Socket.FResponseInfo; 382 lHeaderOut := @ARequest.Socket.FHeaderOut; 383 if not (lReqInfo^.RequestType in [hmHead, hmGet]) then 384 begin 385 lRespInfo^.Status := hsNotAllowed; 386 end else begin 387 lFileOutput := TFileOutput.Create(ARequest.Socket); 388 if lFileOutput.Open(ARequest.Document) then 389 begin 390 lRespInfo^.Status := hsOK; 391 lHeaderOut^.ContentLength := ARequest.Info.Size; 392 lRespInfo^.LastModified := LocalTimeToGMT(FileDateToDateTime(ARequest.Info.Time)); 393 lIndex := MimeList.IndexOf(ExtractFileExt(ARequest.Document)); 394 if lIndex >= 0 then 395 lRespInfo^.ContentType := TStringObject(MimeList.Objects[lIndex]).Str; 396 Result := lFileOutput; 397 ARequest.Socket.StartResponse(lFileOutput); 398 end else 399 lFileOutput.Free; 400 end; 401 end; 402end; 403 404function TFileHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; 405var 406 lDocRequest: TDocumentRequest; 407 lHandler: TDocumentHandler; 408 lTempDoc: string; 409 lDirIndexFound: boolean; 410 I: integer; 411begin 412 Result := nil; 413 lDocRequest.Socket := ASocket; 414 lDocRequest.URIPath := ASocket.FRequestInfo.Argument; 415 lDocRequest.Document := lDocRequest.URIPath; 416 DoDirSeparators(LDocRequest.Document); 417 lDocRequest.Document := IncludeTrailingPathDelimiter(DocumentRoot) 418 + lDocRequest.Document; 419 lDocRequest.InfoValid := SeparatePath(lDocRequest.Document,lDocRequest.ExtraPath, 420 faAnyFile, @lDocRequest.Info); 421 if not lDocRequest.InfoValid then 422 exit; 423 if (lDocRequest.Info.Attr and faDirectory) <> 0 then 424 begin 425 lDirIndexFound := false; 426 { if non-trivial ExtraPath, then it's not a pure directory request, so do 427 not show default directory document } 428 if lDocRequest.ExtraPath = PathDelim then 429 begin 430 lDocRequest.Document := IncludeTrailingPathDelimiter(lDocRequest.Document); 431 for I := 0 to FDirIndexList.Count - 1 do 432 begin 433 lTempDoc := lDocRequest.Document + FDirIndexList.Strings[I]; 434 lDocRequest.InfoValid := FindFirst(lTempDoc, 435 faAnyFile and not faDirectory, lDocRequest.Info) = 0; 436 FindClose(lDocRequest.Info); 437 if lDocRequest.InfoValid and ((lDocRequest.Info.Attr and faDirectory) = 0) then 438 begin 439 lDocRequest.Document := lTempDoc; 440 lDirIndexFound := true; 441 break; 442 end; 443 end; 444 end; 445 { requested a directory, but no source to show } 446 if not lDirIndexFound then exit; 447 end; 448 449 lHandler := FDocHandlerList; 450 while lHandler <> nil do 451 begin 452 Result := lHandler.HandleDocument(lDocRequest); 453 if Result <> nil then exit; 454 if ASocket.FResponseInfo.Status <> hsOK then exit; 455 lHandler := lHandler.FNext; 456 end; 457 458 { no dynamic handler, see if it's a plain file } 459 Result := HandleFile(lDocRequest); 460end; 461 462procedure TFileHandler.RegisterHandler(AHandler: TDocumentHandler); 463begin 464 if AHandler = nil then exit; 465 AHandler.FFileHandler := Self; 466 AHandler.FNext := FDocHandlerList; 467 FDocHandlerList := AHandler; 468end; 469 470function TPHPCGIHandler.HandleDocument(const ARequest: TDocumentRequest): TOutputItem; 471var 472 lOutput: TSimpleCGIOutput; 473begin 474 if ExtractFileExt(ARequest.Document) = '.php' then 475 begin 476 lOutput := TSimpleCGIOutput.Create(ARequest.Socket); 477 lOutput.FDocumentRoot := FFileHandler.DocumentRoot; 478 lOutput.Process.CommandLine := FAppName; 479 lOutput.FScriptName := ARequest.URIPath; 480 lOutput.FScriptFileName := ARequest.Document; 481 lOutput.FExtraPath := ARequest.ExtraPath; 482 lOutput.FEnvPath := FEnvPath; 483 lOutput.StartRequest; 484 Result := lOutput; 485 end else 486 Result := nil; 487end; 488 489constructor TPHPFastCGIHandler.Create; 490begin 491 inherited; 492 FPool := TLFastCGIPool.Create; 493end; 494 495destructor TPHPFastCGIHandler.Destroy; 496begin 497 inherited; 498 FPool.Free; 499end; 500 501function TPHPFastCGIHandler.GetAppEnv: string; 502begin 503 Result := FPool.AppEnv; 504end; 505 506function TPHPFastCGIHandler.GetAppName: string; 507begin 508 Result := FPool.AppName; 509end; 510 511function TPHPFastCGIHandler.GetHost: string; 512begin 513 Result := FPool.Host; 514end; 515 516function TPHPFastCGIHandler.GetPort: integer; 517begin 518 Result := FPool.Port; 519end; 520 521procedure TPHPFastCGIHandler.SetAppEnv(NewEnv: string); 522begin 523 FPool.AppEnv := NewEnv; 524end; 525 526procedure TPHPFastCGIHandler.SetAppName(NewName: string); 527begin 528 FPool.AppName := NewName; 529end; 530 531procedure TPHPFastCGIHandler.SetHost(NewHost: string); 532begin 533 FPool.Host := NewHost; 534end; 535 536procedure TPHPFastCGIHandler.SetPort(NewPort: integer); 537begin 538 FPool.Port := NewPort; 539end; 540 541procedure TPHPFastCGIHandler.RegisterWithEventer(AEventer: TLEventer); 542begin 543 FPool.Eventer := AEventer; 544end; 545 546function TPHPFastCGIHandler.HandleDocument(const ARequest: TDocumentRequest): TOutputItem; 547var 548 lOutput: TFastCGIOutput; 549 fcgiRequest: TLFastCGIRequest; 550begin 551 if ExtractFileExt(ARequest.Document) = '.php' then 552 begin 553 fcgiRequest := FPool.BeginRequest(FCGI_RESPONDER); 554 if fcgiRequest <> nil then 555 begin 556 lOutput := TFastCGIOutput.Create(ARequest.Socket); 557 lOutput.FDocumentRoot := FFileHandler.DocumentRoot; 558 lOutput.FScriptName := ARequest.URIPath; 559 lOutput.FScriptFileName := ARequest.Document; 560 lOutput.FExtraPath := ARequest.ExtraPath; 561 lOutput.FEnvPath := FEnvPath; 562 lOutput.Request := fcgiRequest; 563 ARequest.Socket.SetupEncoding(lOutput); 564 lOutput.StartRequest; 565 Result := lOutput; 566 end else begin 567 ARequest.Socket.FResponseInfo.Status := hsInternalError; 568 ARequest.Socket.StartResponse(nil); 569 Result := nil; 570 end; 571 end else 572 Result := nil; 573end; 574 575{ Output Items } 576 577constructor TFileOutput.Create(ASocket: TLHTTPSocket); 578begin 579 inherited; 580 FEof := true; 581end; 582 583destructor TFileOutput.Destroy; 584begin 585 inherited; 586 587 if not FEof then 588 Close(FFile); 589end; 590 591function TFileOutput.Open(const AFileName: string): boolean; 592begin 593 {$I-} 594 FileMode := 0; 595 Assign(FFile, AFileName); 596 Reset(FFile,1); 597 {$I+} 598 Result := IOResult = 0; 599 FEof := false; 600end; 601 602function TFileOutput.GetSize: integer; inline; 603begin 604 Result := FileSize(FFile); 605end; 606 607function TFileOutput.FillBuffer: TWriteBlockStatus; 608var 609 lRead: integer; 610begin 611 if FEof then 612 exit(wsDone); 613 BlockRead(FFile, FBuffer[FBufferPos], FBufferSize-FBufferPos, lRead); 614 Inc(FBufferPos, lRead); 615 if lRead = 0 then 616 begin 617 { EOF reached } 618 Close(FFile); 619 exit(wsDone); 620 end; 621 Result := wsPendingData; 622end; 623 624constructor TCGIOutput.Create(ASocket: TLHTTPSocket); 625begin 626 inherited; 627end; 628 629destructor TCGIOutput.Destroy; 630begin 631 inherited; 632end; 633 634procedure TCGIOutput.AddHTTPParam(const AName: string; AParam: TLHTTPParameter); 635var 636 lValue: pchar; 637begin 638 lValue := FSocket.Parameters[AParam]; 639 if lValue = nil then exit; 640 AddEnvironment(AName, lValue); 641end; 642 643procedure TCGIOutput.StartRequest; 644var 645 lServerSocket: TLHTTPServerSocket; 646 tempStr: string; 647begin 648 lServerSocket := TLHTTPServerSocket(FSocket); 649{ 650 FProcess.Environment.Add('SERVER_ADDR='); 651 FProcess.Environment.Add('SERVER_ADMIN='); 652 FProcess.Environment.Add('SERVER_NAME='); 653 FProcess.Environment.Add('SERVER_PORT='); 654} 655 Self := nil; 656 tempStr := TLHTTPServer(lServerSocket.Creator).ServerSoftware; 657 if Length(tempStr) > 0 then 658 AddEnvironment('SERVER_SOFTWARE', tempStr); 659 660 AddEnvironment('GATEWAY_INTERFACE', 'CGI/1.1'); 661 AddEnvironment('SERVER_PROTOCOL', lServerSocket.FRequestInfo.VersionStr); 662 AddEnvironment('REQUEST_METHOD', lServerSocket.FRequestInfo.Method); 663 AddEnvironment('REQUEST_URI', '/'+lServerSocket.FRequestInfo.Argument); 664 665 if Length(FExtraPath) > 0 then 666 begin 667 AddEnvironment('PATH_INFO', FExtraPath); 668 { do not set PATH_TRANSLATED: bug in PHP } 669// AddEnvironment('PATH_TRANSLATED', DocumentRoot+FExtraPath); 670 end; 671 672 AddEnvironment('SCRIPT_NAME', FScriptName); 673 AddEnvironment('SCRIPT_FILENAME', FScriptFileName); 674 675 AddEnvironment('QUERY_STRING', lServerSocket.FRequestInfo.QueryParams); 676 AddHTTPParam('CONTENT_TYPE', hpContentType); 677 AddHTTPParam('CONTENT_LENGTH', hpContentLength); 678 679 AddEnvironment('REMOTE_ADDR', FSocket.PeerAddress); 680 AddEnvironment('REMOTE_PORT', IntToStr(FSocket.LocalPort)); 681 682 { used when user has authenticated in some way to server } 683// AddEnvironment('AUTH_TYPE='+...); 684// AddEnvironment('REMOTE_USER='+...); 685 686 AddEnvironment('DOCUMENT_ROOT', FDocumentRoot); 687 AddEnvironment('REDIRECT_STATUS', '200'); 688 AddHTTPParam('HTTP_HOST', hpHost); 689 AddHTTPParam('HTTP_COOKIE', hpCookie); 690 AddHTTPParam('HTTP_CONNECTION', hpConnection); 691 AddHTTPParam('HTTP_REFERER', hpReferer); 692 AddHTTPParam('HTTP_USER_AGENT', hpUserAgent); 693 AddHTTPParam('HTTP_ACCEPT', hpAccept); 694 AddEnvironment('PATH', FEnvPath); 695 696 FParsingHeaders := true; 697 FReadPos := FBufferPos; 698 FParsePos := FBuffer+FReadPos; 699end; 700 701function TCGIOutput.ParseHeaders: boolean; 702var 703 lHttpStatus: TLHTTPStatus; 704 iEnd, lCode: integer; 705 lStatus, lLength: dword; 706 pLineEnd, pNextLine, pValue: pchar; 707 lServerSocket: TLHTTPServerSocket; 708 709 procedure AddExtraHeader; 710 begin 711 AppendString(lServerSocket.FHeaderOut.ExtraHeaders, 712 FParsePos + ': ' + pValue + #13#10); 713 end; 714 715begin 716 lServerSocket := TLHTTPServerSocket(FSocket); 717 repeat 718 iEnd := IndexByte(FParsePos^, @FBuffer[FReadPos]-FParsePos, 10); 719 if iEnd = -1 then exit(false); 720 pNextLine := FParsePos+iEnd+1; 721 if (iEnd > 0) and (FParsePos[iEnd-1] = #13) then 722 dec(iEnd); 723 pLineEnd := FParsePos+iEnd; 724 pLineEnd^ := #0; 725 if pLineEnd = FParsePos then 726 begin 727 { empty line signals end of headers } 728 FParsingHeaders := false; 729 FBufferOffset := pNextLine-FBuffer; 730 FBufferPos := FReadPos; 731 FReadPos := 0; 732 lServerSocket.StartResponse(Self, true); 733 exit(false); 734 end; 735 iEnd := IndexByte(FParsePos^, iEnd, ord(':')); 736 if (iEnd = -1) or (FParsePos[iEnd+1] <> ' ') then 737 break; 738 FParsePos[iEnd] := #0; 739 pValue := FParsePos+iEnd+2; 740 if StrIComp(FParsePos, 'Content-type') = 0 then 741 begin 742 lServerSocket.FResponseInfo.ContentType := pValue; 743 end else 744 if StrIComp(FParsePos, 'Location') = 0 then 745 begin 746 if StrLIComp(pValue, 'http://', 7) = 0 then 747 begin 748 lServerSocket.FResponseInfo.Status := hsMovedPermanently; 749 { add location header as-is to response } 750 AddExtraHeader; 751 end else 752 InternalWrite('WARNING: unimplemented ''Location'' response received from CGI script'); 753 end else 754 if StrIComp(FParsePos, 'Status') = 0 then 755 begin 756 { sometimes we get '<status code> space <reason>' } 757 iEnd := IndexByte(pValue^, pLineEnd-pValue, ord(' ')); 758 if iEnd <> -1 then 759 pValue[iEnd] := #0; 760 Val(pValue, lStatus, lCode); 761 if lCode <> 0 then 762 break; 763 for lHttpStatus := Low(TLHTTPStatus) to High(TLHTTPStatus) do 764 if HTTPStatusCodes[lHttpStatus] = lStatus then 765 lServerSocket.FResponseInfo.Status := lHttpStatus; 766 end else 767 if StrIComp(FParsePos, 'Content-Length') = 0 then 768 begin 769 Val(pValue, lLength, lCode); 770 if lCode <> 0 then 771 break; 772 lServerSocket.FHeaderOut.ContentLength := lLength; 773 end else 774 if StrIComp(FParsePos, 'Last-Modified') = 0 then 775 begin 776 if not TryHTTPDateStrToDateTime(pValue, 777 lServerSocket.FResponseInfo.LastModified) then 778 InternalWrite('WARNING: unable to parse last-modified string from CGI script: ' + pValue); 779 end else 780 AddExtraHeader; 781 FParsePos := pNextLine; 782 until false; 783 784 { error happened } 785 lServerSocket.FResponseInfo.Status := hsInternalError; 786 exit(true); 787end; 788 789function TCGIOutput.FillBuffer: TWriteBlockStatus; 790begin 791 if not FParsingHeaders then 792 FReadPos := FBufferPos; 793 Result := WriteCGIData; 794 if FParsingHeaders then 795 begin 796 if ParseHeaders then 797 begin 798 { error while parsing } 799 FEof := true; 800 exit(wsDone); 801 end; 802 end else 803 FBufferPos := FReadPos; 804end; 805 806procedure TCGIOutput.WriteCGIBlock; 807begin 808 { CGI process has output pending, we can write a block to socket } 809 if FParsingHeaders then 810 begin 811 if (FillBuffer = wsDone) and FParsingHeaders then 812 begin 813 { still parsing headers ? something's wrong } 814 FParsingHeaders := false; 815 CGIOutputError; 816 TLHTTPServerSocket(FSocket).StartResponse(Self); 817 end; 818 end; 819 if not FParsingHeaders then 820 FSocket.WriteBlock; 821end; 822 823{ TSimpleCGIOutput } 824 825constructor TSimpleCGIOutput.Create(ASocket: TLHTTPSocket); 826begin 827 inherited; 828 FProcess := TLProcess.Create(nil); 829 FProcess.Options := FProcess.Options + [poUsePipes]; 830 FProcess.OnNeedInput := @CGIProcNeedInput; 831 FProcess.OnHasOutput := @CGIProcHasOutput; 832 FProcess.OnHasStderr := @CGIProcHasStderr; 833end; 834 835destructor TSimpleCGIOutput.Destroy; 836begin 837 inherited; 838 FProcess.Free; 839end; 840 841function TSimpleCGIOutput.WriteCGIData: TWriteBlockStatus; 842var 843 lRead: integer; 844begin 845 lRead := FProcess.Output.Read(FBuffer[FReadPos], FBufferSize-FReadPos); 846 if lRead = 0 then exit(wsDone); 847 Inc(FReadPos, lRead); 848 Result := InputBufferEmptyToWriteStatus[lRead = 0]; 849end; 850 851procedure TSimpleCGIOutput.AddEnvironment(const AName, AValue: string); 852begin 853 FProcess.Environment.Add(AName+'='+AValue); 854end; 855 856procedure TSimpleCGIOutput.DoneInput; 857begin 858 FProcess.CloseInput; 859end; 860 861function TSimpleCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer; 862begin 863 if ASize > 0 then 864 Result := FProcess.Input.Write(ABuffer^, ASize) 865 else 866 Result := 0; 867 FProcess.InputEvent.IgnoreWrite := ASize = 0; 868end; 869 870procedure TSimpleCGIOutput.StartRequest; 871begin 872 inherited; 873 874 FProcess.Eventer := FSocket.Eventer; 875 FProcess.Execute; 876end; 877 878procedure TSimpleCGIOutput.CGIOutputError; 879var 880 ServerSocket: TLHTTPServerSocket; 881begin 882 ServerSocket := TLHTTPServerSocket(FSocket); 883 if FProcess.ExitStatus = 127 then 884 ServerSocket.FResponseInfo.Status := hsNotFound 885 else 886 ServerSocket.FResponseInfo.Status := hsInternalError; 887end; 888 889procedure TSimpleCGIOutput.CGIProcNeedInput(AHandle: TLHandle); 890begin 891 FProcess.InputEvent.IgnoreWrite := true; 892 FSocket.ParseBuffer; 893end; 894 895procedure TSimpleCGIOutput.CGIProcHasOutput(AHandle: TLHandle); 896begin 897 WriteCGIBlock; 898end; 899 900procedure TSimpleCGIOutput.CGIProcHasStderr(AHandle: TLHandle); 901var 902 lBuf: array[0..1023] of char; 903 lRead: integer; 904begin 905 lRead := FProcess.Stderr.Read(lBuf, sizeof(lBuf)-1); 906 lBuf[lRead] := #0; 907 write(pchar(@lBuf[0])); 908end; 909 910{ TFastCGIOutput } 911 912constructor TFastCGIOutput.Create(ASocket: TLHTTPSocket); 913begin 914 inherited; 915end; 916 917destructor TFastCGIOutput.Destroy; 918begin 919 if FRequest <> nil then 920 begin 921 FRequest.OnInput := nil; 922 FRequest.OnOutput := nil; 923 FRequest.OnStderr := nil; 924 FRequest.OnEndRequest := nil; 925 FRequest.AbortRequest; 926 end; 927 inherited; 928end; 929 930procedure TFastCGIOutput.AddEnvironment(const AName, AValue: string); 931begin 932 FRequest.SendParam(AName, AValue); 933end; 934 935procedure TFastCGIOutput.CGIOutputError; 936begin 937 TLHTTPServerSocket(FSocket).FResponseInfo.Status := hsInternalError; 938end; 939 940procedure TFastCGIOutput.DoneInput; 941begin 942 if FRequest <> nil then 943 FRequest.DoneInput; 944end; 945 946procedure TFastCGIOutput.RequestEnd(ARequest: TLFastCGIRequest); 947begin 948 FRequest.OnEndRequest := nil; 949 FRequest.OnInput := nil; 950 FRequest.OnOutput := nil; 951 FRequest := nil; 952 { trigger final write, to flush output to socket } 953 WriteCGIBlock; 954end; 955 956procedure TFastCGIOutput.RequestNeedInput(ARequest: TLFastCGIRequest); 957begin 958 FSocket.ParseBuffer; 959end; 960 961procedure TFastCGIOutput.RequestHasOutput(ARequest: TLFastCGIRequest); 962begin 963 WriteCGIBlock; 964end; 965 966procedure TFastCGIOutput.RequestHasStderr(ARequest: TLFastCGIRequest); 967var 968 lBuf: array[0..1023] of char; 969 lRead: integer; 970begin 971 lRead := ARequest.Get(lBuf, sizeof(lBuf)-1); 972 lBuf[lRead] := #0; 973 write(pchar(@lBuf[0])); 974end; 975 976function TFastCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer; 977begin 978 Result := FRequest.SendInput(ABuffer, ASize); 979end; 980 981function TFastCGIOutput.WriteCGIData: TWriteBlockStatus; 982var 983 lRead: integer; 984begin 985 if FRequest = nil then exit(wsDone); 986 if FRequest.OutputDone then exit(wsDone); 987 lRead := FRequest.Get(@FBuffer[FReadPos], FBufferSize-FReadPos); 988 Inc(FReadPos, lRead); 989 Result := InputBufferEmptyToWriteStatus[lRead = 0]; 990end; 991 992function TFastCGIOutput.WriteBlock: TWriteBlockStatus; 993begin 994 if (FRequest <> nil) and FRequest.OutputPending then 995 begin 996 FRequest.ParseClientBuffer; 997 Result := wsWaitingData; 998 end else 999 Result := inherited; 1000end; 1001 1002procedure TFastCGIOutput.StartRequest; 1003begin 1004 FRequest.OnEndRequest := @RequestEnd; 1005 FRequest.OnInput := @RequestNeedInput; 1006 FRequest.OnOutput := @RequestHasOutput; 1007 FRequest.OnStderr := @RequestHasStderr; 1008 inherited; 1009 FRequest.DoneParams; 1010end; 1011 1012{ TFormOutput } 1013 1014constructor TFormOutput.Create(ASocket: TLHTTPSocket); 1015begin 1016 inherited; 1017 FRequestVars := TStringList.Create; 1018end; 1019 1020destructor TFormOutput.Destroy; 1021var 1022 I: integer; 1023 tmpObj: TObject; 1024begin 1025 for I := 0 to FRequestVars.Count - 1 do 1026 begin 1027 tmpObj := FRequestVars.Objects[I]; 1028 Finalize(string(tmpObj)); 1029 FRequestVars.Objects[I] := nil; 1030 end; 1031 FRequestVars.Free; 1032 inherited; 1033end; 1034 1035function TFormOutput.AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer; 1036var 1037 varname, sep, next: pchar; 1038 strName, strValue: string; 1039 tmpObj: TObject; 1040 i: integer; 1041begin 1042 if Variables = nil then 1043 exit(0); 1044 if ASize = -1 then 1045 ASize := StrLen(Variables); 1046 varname := Variables; 1047 repeat 1048 sep := varname + IndexChar(varname^, ASize, '='); 1049 if sep < varname then 1050 break; 1051 dec(ASize, sep-varname); 1052 next := sep + IndexChar(sep^, ASize, SepChar); 1053 if next < sep then 1054 begin 1055 next := sep + ASize; 1056 ASize := 0; 1057 end else 1058 dec(ASize, next+1-sep); 1059 if sep > varname then 1060 begin 1061 setlength(strName, sep-varname); 1062 move(varname[0], strName[1], sep-varname); 1063 setlength(strValue, next-sep-1); 1064 move(sep[1], strValue[1], next-sep-1); 1065 i := FRequestVars.Add(strName); 1066 tmpObj := nil; 1067 string(tmpObj) := strValue; 1068 FRequestVars.Objects[i] := tmpObj; 1069 end; 1070 varname := next+1; 1071 until false; 1072 Result := ASize; 1073end; 1074 1075procedure TFormOutput.DoneInput; 1076begin 1077 if Assigned(FOnExtraHeaders) then 1078 FOnExtraHeaders(Self); 1079 TLHTTPServerSocket(FSocket).StartResponse(Self); 1080end; 1081 1082function TFormOutput.HandleInputFormURL(ABuffer: pchar; ASize: integer): integer; 1083begin 1084 Result := ASize-AddVariables(ABuffer, ASize, URIParamSepChar) 1085end; 1086 1087procedure TFormOutput.ParseMultipartHeader(ABuffer, ALineEnd: pchar); 1088var 1089 I: TLMultipartParameter; 1090 len: integer; 1091begin 1092 for I := Low(TLMultipartParameter) to High(TLMultipartParameter) do 1093 begin 1094 len := Length(MPParameterStrings[I]); 1095 if ABuffer+len >= ALineEnd then 1096 continue; 1097 if (ABuffer[len] = ':') 1098 and (StrLIComp(ABuffer, PChar(MPParameterStrings[I]), len) = 0) then 1099 begin 1100 Inc(ABuffer, len+2); 1101 repeat 1102 if ABuffer = ALineEnd then exit; 1103 if ABuffer^ <> ' ' then break; 1104 inc(ABuffer); 1105 until false; 1106 FMPParameters[I] := ABuffer; 1107 if I = mpContentType then 1108 begin 1109 repeat 1110 if ABuffer = ALineEnd then exit; 1111 if ABuffer = ';' then break; 1112 inc(ABuffer); 1113 until false; 1114 1115 end; 1116 break; 1117 end; 1118 end; 1119end; 1120 1121function TFormOutput.FindBoundary(ABuffer: pchar): pchar; 1122begin 1123 {$warning TODO} 1124 Result := nil; 1125end; 1126 1127function TFormOutput.HandleInputMultipart(ABuffer: pchar; ASize: integer): integer; 1128var 1129 pos, next, endline: pchar; 1130begin 1131 pos := ABuffer; 1132 repeat 1133 case FMPState of 1134 msStart: 1135 begin 1136 { discard until first boundary } 1137 next := FindBoundary(pos); 1138 if next = nil then 1139 exit(ASize); 1140 FMPState := msBodypartHeader; 1141 end; 1142 msBodypartHeader: 1143 begin 1144 endline := pos + IndexChar(pos, ASize, #10); 1145 if endline < pos then 1146 exit(pos-ABuffer); 1147 next := endline+1; 1148 if (endline > pos) and ((endline-1)^ = #13) then 1149 dec(endline); 1150 endline^ := #0; 1151 if endline > pos then 1152 ParseMultipartHeader(pos, endline) 1153 else 1154 FMPState := msBodypartData; 1155 end; 1156 msBodypartData: 1157 begin 1158 { decode based on content-transfer-encoding ? } 1159 { CRLF before boundary, belongs to boundary, not data! } 1160 next := FindBoundary(ABuffer); 1161 end; 1162 else 1163 exit(ASize); 1164 end; 1165 dec(ASize, next-pos); 1166 pos := next; 1167 until false; 1168end; 1169 1170function TFormOutput.HandleInputDiscard(ABuffer: pchar; ASize: integer): integer; 1171begin 1172 Result := ASize; 1173end; 1174 1175function TFormOutput.HandleInput(ABuffer: pchar; ASize: integer): integer; 1176begin 1177 Result := FHandleInput(ABuffer, ASize); 1178end; 1179 1180function TFormOutput.FillBuffer: TWriteBlockStatus; 1181begin 1182 Result := wsDone; 1183 if Assigned(FOnFillBuffer) then 1184 FOnFillBuffer(Self, Result); 1185end; 1186 1187procedure TFormOutput.DeleteCookie(const AName: string; const APath: string = '/'; 1188 const ADomain: string = ''); 1189begin 1190 { cookies expire when expires is in the past, duh } 1191 SetCookie(AName, '', Now - 7.0, APath, ADomain); 1192end; 1193 1194procedure TFormOutput.SetCookie(const AName, AValue: string; const AExpires: TDateTime; 1195 const APath: string = '/'; const ADomain: string = ''); 1196var 1197 headers: PStringBuffer; 1198begin 1199 headers := @TLHTTPServerSocket(FSocket).FHeaderOut.ExtraHeaders; 1200 AppendString(headers^, 'Set-Cookie: ' + HTTPEncode(AName) + '=' + HTTPEncode(AValue)); 1201 AppendString(headers^, ';path=' + APath + ';expires=' + FormatDateTime(HTTPDateFormat, AExpires)); 1202 if Length(ADomain) > 0 then 1203 begin 1204 AppendString(headers^, ';domain='); 1205 AppendString(headers^, ADomain); 1206 end; 1207 AppendString(headers^, #13#10); 1208end; 1209 1210{ TFormHandler } 1211 1212procedure TFormHandler.SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar); 1213var 1214 boundary, endquote: pchar; 1215begin 1216 boundary := StrScan(AContentType, '='); 1217 if boundary <> nil then 1218 begin 1219 Inc(boundary); 1220 if boundary^ = '"' then 1221 begin 1222 Inc(boundary); 1223 endquote := StrScan(boundary, '"'); 1224 if endquote <> nil then 1225 endquote^ := #0; 1226 end; 1227 end; 1228 1229 AFormOutput.FBoundary := boundary; 1230 AFormOutput.FHandleInput := @AFormOutput.HandleInputMultipart; 1231end; 1232 1233function TFormHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; 1234var 1235 newFormOutput: TFormOutput; 1236 contentType: pchar; 1237begin 1238 if not Assigned(FOnHandleURI) then 1239 exit(nil); 1240 1241 newFormOutput := FOnHandleURI(ASocket); 1242 if newFormOutput = nil then 1243 exit(nil); 1244 1245 newFormOutput.AddVariables(ASocket.FRequestInfo.QueryParams, -1, URIParamSepChar); 1246 newFormOutput.AddVariables(ASocket.Parameters[hpCookie], -1, CookieSepChar); 1247 contentType := TLHTTPServerSocket(ASocket).Parameters[hpContentType]; 1248 if StrIComp(contentType, FormURLContentType) = 0 then 1249 newFormOutput.FHandleInput := @newFormOutput.HandleInputFormURL 1250 else if StrIComp(contentType, MultipartContentType) = 0 then 1251 SelectMultipart(newFormOutput, contentType) 1252 else 1253 newFormOutput.FHandleInput := @newFormOutput.HandleInputDiscard; 1254 1255 Result := newFormOutput; 1256end; 1257 1258end. 1259