Mercurial > hg > orthanc
diff OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4640:66109d24d26e
"ETag" headers for metadata and attachments now allow strong comparison (MD5 is included)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 26 Apr 2021 15:22:44 +0200 |
parents | f7d5372b59b3 |
children | e8967149d87a |
line wrap: on
line diff
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Apr 22 13:27:57 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Apr 26 15:22:44 2021 +0200 @@ -1450,7 +1450,49 @@ } + static void SetStringContentETag(RestApiOutput& output, + int64_t revision, + const std::string& value) + { + std::string md5; + Toolbox::ComputeMD5(md5, value); + const std::string etag = "\"" + boost::lexical_cast<std::string>(revision) + "-" + md5 + "\""; + output.GetLowLevelOutput().AddHeader("ETag", etag); + } + + + static void SetBufferContentETag(RestApiOutput& output, + int64_t revision, + const void* data, + size_t size) + { + std::string md5; + Toolbox::ComputeMD5(md5, data, size); + const std::string etag = "\"" + boost::lexical_cast<std::string>(revision) + "-" + md5 + "\""; + output.GetLowLevelOutput().AddHeader("ETag", etag); + } + + + static void SetAttachmentETag(RestApiOutput& output, + int64_t revision, + const FileInfo& info) + { + const std::string etag = ("\"" + boost::lexical_cast<std::string>(revision) + "-" + + info.GetUncompressedMD5() + "\""); + output.GetLowLevelOutput().AddHeader("ETag", etag); + } + + + static std::string GetMD5(const std::string& value) + { + std::string md5; + Toolbox::ComputeMD5(md5, value); + return md5; + } + + static bool GetRevisionHeader(int64_t& revision /* out */, + std::string& md5 /* out */, const RestApiCall& call, const std::string& header) { @@ -1469,14 +1511,20 @@ try { - revision = boost::lexical_cast<int64_t>(value); - return true; + size_t comma = value.find('-'); + if (comma != std::string::npos) + { + revision = boost::lexical_cast<int64_t>(value.substr(0, comma)); + md5 = value.substr(comma + 1); + return true; + } } catch (boost::bad_lexical_cast&) { - throw OrthancException(ErrorCode_ParameterOutOfRange, "The \"" + header + - "\" HTTP header should contain the revision as an integer, but found: " + value); } + + throw OrthancException(ErrorCode_ParameterOutOfRange, "The \"" + header + + "\" HTTP header should contain the ETag (revision followed by MD5 hash), but found: " + value); } } @@ -1510,12 +1558,13 @@ int64_t revision; if (OrthancRestApi::GetIndex(call).LookupMetadata(value, revision, publicId, level, metadata)) { - call.GetOutput().GetLowLevelOutput(). - AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(revision) + "\""); // New in Orthanc 1.9.2 + SetStringContentETag(call.GetOutput(), revision, value); // New in Orthanc 1.9.2 int64_t userRevision; - if (GetRevisionHeader(userRevision, call, "If-None-Match") && - revision == userRevision) + std::string userMD5; + if (GetRevisionHeader(userRevision, userMD5, call, "If-None-Match") && + userRevision == revision && + userMD5 == GetMD5(value)) { call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified); } @@ -1555,9 +1604,10 @@ { bool found; int64_t revision; - if (GetRevisionHeader(revision, call, "if-match")) + std::string md5; + if (GetRevisionHeader(revision, md5, call, "if-match")) { - found = OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata, true, revision); + found = OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata, true, revision, md5); } else { @@ -1569,7 +1619,7 @@ } else { - found = OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata, false, -1 /* dummy value */); + found = OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata, false, -1 /* dummy value */, ""); } } @@ -1619,7 +1669,8 @@ if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata { int64_t oldRevision; - bool hasOldRevision = GetRevisionHeader(oldRevision, call, "if-match"); + std::string oldMD5; + bool hasOldRevision = GetRevisionHeader(oldRevision, oldMD5, call, "if-match"); if (!hasOldRevision) { @@ -1631,15 +1682,15 @@ // inexistent as expected hasOldRevision = true; oldRevision = -1; // dummy value + oldMD5.clear(); // dummy value } } int64_t newRevision; - OrthancRestApi::GetIndex(call).SetMetadata(newRevision, publicId, metadata, value, hasOldRevision, oldRevision); - - call.GetOutput().GetLowLevelOutput(). - AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(newRevision) + "\""); // New in Orthanc 1.9.2 - + OrthancRestApi::GetIndex(call).SetMetadata(newRevision, publicId, metadata, value, + hasOldRevision, oldRevision, oldMD5); + + SetStringContentETag(call.GetOutput(), newRevision, value); // New in Orthanc 1.9.2 call.GetOutput().AnswerBuffer("", MimeType_PlainText); } else @@ -1709,12 +1760,13 @@ int64_t revision; if (OrthancRestApi::GetIndex(call).LookupAttachment(info, revision, publicId, contentType)) { - call.GetOutput().GetLowLevelOutput(). - AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(revision) + "\""); // New in Orthanc 1.9.2 + SetAttachmentETag(call.GetOutput(), revision, info); // New in Orthanc 1.9.2 int64_t userRevision; - if (GetRevisionHeader(userRevision, call, "If-None-Match") && - revision == userRevision) + std::string userMD5; + if (GetRevisionHeader(userRevision, userMD5, call, "If-None-Match") && + revision == userRevision && + info.GetUncompressedMD5() == userMD5) { call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified); return false; @@ -1810,33 +1862,34 @@ std::string publicId = call.GetUriComponent("id", ""); FileContentType type = StringToContentType(call.GetUriComponent("name", "")); - if (uncompress) + FileInfo info; + if (GetAttachmentInfo(info, call)) { - FileInfo info; - if (GetAttachmentInfo(info, call)) + // NB: "SetAttachmentETag()" is already invoked by "GetAttachmentInfo()" + + if (uncompress) { context.AnswerAttachment(call.GetOutput(), publicId, type); } - } - else - { - // Return the raw data (possibly compressed), as stored on the filesystem - std::string content; - int64_t revision; - context.ReadAttachment(content, revision, publicId, type, false); - - call.GetOutput().GetLowLevelOutput(). - AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(revision) + "\""); // New in Orthanc 1.9.2 - - int64_t userRevision; - if (GetRevisionHeader(userRevision, call, "If-None-Match") && - revision == userRevision) - { - call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified); - } else { - call.GetOutput().AnswerBuffer(content, MimeType_Binary); + // Return the raw data (possibly compressed), as stored on the filesystem + std::string content; + int64_t revision; + context.ReadAttachment(content, revision, publicId, type, false); + + int64_t userRevision; + std::string userMD5; + if (GetRevisionHeader(userRevision, userMD5, call, "If-None-Match") && + revision == userRevision && + info.GetUncompressedMD5() == userMD5) + { + call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified); + } + else + { + call.GetOutput().AnswerBuffer(content, MimeType_Binary); + } } } } @@ -2037,7 +2090,8 @@ if (IsUserContentType(contentType)) // It is forbidden to modify internal attachments { int64_t oldRevision; - bool hasOldRevision = GetRevisionHeader(oldRevision, call, "if-match"); + std::string oldMD5; + bool hasOldRevision = GetRevisionHeader(oldRevision, oldMD5, call, "if-match"); if (!hasOldRevision) { @@ -2049,16 +2103,15 @@ // inexistent as expected hasOldRevision = true; oldRevision = -1; // dummy value + oldMD5.clear(); // dummy value } } int64_t newRevision; context.AddAttachment(newRevision, publicId, StringToContentType(name), call.GetBodyData(), - call.GetBodySize(), hasOldRevision, oldRevision); - - call.GetOutput().GetLowLevelOutput(). - AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(newRevision) + "\""); // New in Orthanc 1.9.2 - + call.GetBodySize(), hasOldRevision, oldRevision, oldMD5); + + SetBufferContentETag(call.GetOutput(), newRevision, call.GetBodyData(), call.GetBodySize()); // New in Orthanc 1.9.2 call.GetOutput().AnswerBuffer("{}", MimeType_Json); } else @@ -2119,9 +2172,10 @@ { bool found; int64_t revision; - if (GetRevisionHeader(revision, call, "if-match")) + std::string md5; + if (GetRevisionHeader(revision, md5, call, "if-match")) { - found = OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType, true, revision); + found = OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType, true, revision, md5); } else { @@ -2133,7 +2187,8 @@ } else { - found = OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType, false, -1 /* dummy value */); + found = OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType, + false, -1 /* dummy value */, "" /* dummy value */); } } @@ -2986,7 +3041,8 @@ for (std::list<std::string>::const_iterator instance = instances.begin(); instance != instances.end(); ++instance) { - index.DeleteAttachment(*instance, FileContentType_DicomAsJson, false /* no revision checks */, -1 /* dummy */); + index.DeleteAttachment(*instance, FileContentType_DicomAsJson, + false /* no revision checks */, -1 /* dummy */, "" /* dummy */); } }