assets_servlet.cpp 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. #include "assets_servlet.h"
  2. #include <library/cpp/http/misc/httpdate.h>
  3. #include <library/cpp/mime/types/mime.h>
  4. #include <library/cpp/uri/uri.h>
  5. #include <util/system/fstat.h>
  6. #include <util/system/file.h>
  7. namespace NYql {
  8. namespace NHttp {
  9. ///////////////////////////////////////////////////////////////////////////////
  10. // TAssetsServlet
  11. ///////////////////////////////////////////////////////////////////////////////
  12. TAssetsServlet::TAssetsServlet(
  13. const TString& baseUrl,
  14. const TString& baseDir,
  15. const TString& indexFile)
  16. : BaseUrl_(baseUrl)
  17. , BaseDir_(baseDir)
  18. , IndexFile_(indexFile)
  19. {
  20. }
  21. void TAssetsServlet::DoGet(const TRequest& req, TResponse& resp) const
  22. {
  23. TAsset asset;
  24. LoadAsset(req, &asset);
  25. TString lastModified = FormatHttpDate(asset.LastModified);
  26. resp.Headers.AddHeader("Last-Modified", lastModified);
  27. if (asset.ContentType) {
  28. resp.ContentType = asset.ContentType;
  29. }
  30. resp.Body = asset.Data;
  31. }
  32. TString TAssetsServlet::SafeFilePath(const TString& basePath, TStringBuf reqPath) const
  33. {
  34. Y_ENSURE_EX(reqPath.Head(BaseUrl_.size()) == BaseUrl_,
  35. THttpError(HTTP_BAD_REQUEST) << "invalid url prefix");
  36. size_t shift = BaseUrl_ == TStringBuf("/") ? 0 : BaseUrl_.size();
  37. TStringBuf skipped = reqPath.Skip(shift);
  38. const char* p = skipped.data();
  39. size_t len = skipped.size();
  40. if (p[0] != '/' || (len > 2 && p[1] == '.' && p[2] == '.'))
  41. ythrow THttpError(HTTP_BAD_REQUEST);
  42. TString buf(skipped.data(), len);
  43. char* b = buf.begin();
  44. char* e = b + len;
  45. ::NUri::TUri::PathOperation(b, e, 1);
  46. return basePath + TStringBuf(b, e);
  47. }
  48. bool TAssetsServlet::IsCachedOnClient(const TRequest& req, const TAsset& asset) const
  49. {
  50. const TString* value = req.RD.HeaderIn("If-Modified-Since");
  51. if (value) {
  52. time_t mt = parse_http_date(*value);
  53. return (mt >= asset.LastModified);
  54. }
  55. return false;
  56. }
  57. void TAssetsServlet::LoadAsset(const TRequest& req, TAsset* asset) const
  58. {
  59. TString filePath = SafeFilePath(BaseDir_, req.RD.ScriptName());
  60. TFileStat stat(filePath);
  61. if (stat.IsDir()) {
  62. filePath += "/" + IndexFile_;
  63. stat = TFileStat(filePath);
  64. }
  65. if (!stat.IsFile()) {
  66. ythrow THttpError(HTTP_NOT_FOUND)
  67. << "File '" << filePath << "' not found";
  68. }
  69. asset->LastModified = stat.MTime;
  70. asset->ContentType = mimetypeByExt(filePath.data());
  71. if (IsCachedOnClient(req, *asset)) {
  72. ythrow THttpError(HTTP_NOT_MODIFIED);
  73. }
  74. TFile file(filePath, EOpenModeFlag::RdOnly);
  75. asset->Data = TBlob::FromFile(file);
  76. }
  77. } // namspace NNttp
  78. } // namspace NYql