brookstaticfilebroker.pas 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. (*
  2. Brook for Free Pascal
  3. Copyright (C) 2014-2019 Mario Ray Mahardhika
  4. See the file LICENSE.txt, included in this distribution,
  5. for details about the copyright.
  6. This library is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. *)
  10. { Static file broker. }
  11. unit BrookStaticFileBroker;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, BrookUtils;
  16. { This is the only thing that user may know from this unit. }
  17. procedure BrookStaticFileRegisterDirectory(ARequestPath, ADirectory: string);
  18. implementation
  19. uses
  20. StrUtils,
  21. {$if fpc_fullversion >= 20701}
  22. ghashmap
  23. {$else fpc_fullversion >= 20701}
  24. fgl
  25. {$endif fpc_fullversion >= 20701}
  26. ,fpmimetypes, HTTPDefs, BrookAction;
  27. resourcestring
  28. SEmptyRequestPathErrMsg = 'Request path may not be empty.';
  29. SRequestPathAlreadyRegisteredErrMsg = 'Request path "%s" already registered.';
  30. SDirectoryNotExistErrMsg = 'Directory not exists: %s.';
  31. type
  32. {$if fpc_fullversion >= 20701}
  33. { TStringHash }
  34. TStringHash = class
  35. class function Hash(S: string; N: Integer): Integer;
  36. end;
  37. TRequestDirectoryMap = specialize THashmap<string, string, TStringHash>;
  38. {$else fpc_fullversion >= 20701}
  39. TStrMap = specialize TFPGMap<string, string>;
  40. TRequestDirectoryMap = class(TStrMap)
  41. public
  42. function Contains(const s: String): Boolean;
  43. end;
  44. {$endif fpc_fullversion >= 20701}
  45. { TStaticFileAction }
  46. TStaticFileAction = class(TBrookAction)
  47. public
  48. procedure Get; override;
  49. end;
  50. var
  51. RequestDirectoryMap: TRequestDirectoryMap;
  52. {$IFNDEF VER3_0}
  53. {$PUSH}{$WARN 6058 OFF}
  54. {$ENDIF}
  55. {$if fpc_fullversion >= 20701}
  56. { TStringHash }
  57. class function TStringHash.Hash(S: String; N: Integer): Integer;
  58. var
  59. C: Char;
  60. begin
  61. Result := 0;
  62. for C in LowerCase(S) do
  63. Inc(Result, Ord(C));
  64. Result := Result mod N;
  65. end;
  66. {$else fpc_fullversion >= 20701}
  67. function TRequestDirectoryMap.Contains(const s: String): Boolean;
  68. var
  69. dummy: Integer;
  70. begin
  71. Result := inherited Find(s,dummy);
  72. end;
  73. {$endif fpc_fullversion >= 20701}
  74. { TStaticFileAction }
  75. procedure TStaticFileAction.Get;
  76. var
  77. VLastSlashPos: Integer;
  78. VPathInfo, VFilePath, VBuffer, VContentType: string;
  79. begin
  80. VBuffer := '';
  81. VPathInfo := HttpRequest.PathInfo;
  82. VLastSlashPos := RPos('/', VPathInfo);
  83. System.Delete(VPathInfo, VLastSlashPos + 1, Length(VPathInfo) - VLastSlashPos);
  84. VFilePath := RequestDirectoryMap[VPathInfo] + Variables.Values['file'];
  85. if FileExists(VFilePath) then
  86. begin
  87. VContentType := MimeTypes.GetMimeType(ExtractFileExt(VFilePath));
  88. if VContentType = '' then
  89. VContentType := 'application/octet-stream';
  90. HttpResponse.ContentType := VContentType;
  91. with TFileStream.Create(VFilePath, fmOpenRead) do
  92. try
  93. SetLength(VBuffer, Size);
  94. Read(VBuffer[1], Size);
  95. Self.Write(VBuffer);
  96. finally
  97. Free;
  98. end;
  99. end;
  100. end;
  101. procedure BrookStaticFileRegisterDirectory(ARequestPath, ADirectory: string);
  102. begin
  103. if Length(ARequestPath) = 0 then
  104. raise Exception.Create(SEmptyRequestPathErrMsg);
  105. if not DirectoryExists(ADirectory) then
  106. raise Exception.CreateFmt(SDirectoryNotExistErrMsg, [ADirectory]);
  107. // add required slashes
  108. if ARequestPath[1] <> '/' then
  109. ARequestPath := '/' + ARequestPath;
  110. if ARequestPath[Length(ARequestPath)] <> '/' then
  111. ARequestPath := ARequestPath + '/';
  112. if RequestDirectoryMap.Contains(ARequestPath) then
  113. raise Exception.CreateFmt(SRequestPathAlreadyRegisteredErrMsg,[ARequestPath]);
  114. RequestDirectoryMap[ARequestPath] := IncludeTrailingPathDelimiter(ADirectory);
  115. TStaticFileAction.Register(ARequestPath + ':file', rmGet);
  116. end;
  117. {$IFNDEF VER3_0}
  118. {$POP}
  119. {$ENDIF}
  120. initialization
  121. RequestDirectoryMap := TRequestDirectoryMap.Create;
  122. finalization
  123. RequestDirectoryMap.Free;
  124. end.