void ExportedResource::Format(Json::Value& item) const { item = Json::objectValue; item["Seq"] = static_cast<int>(seq_); item["ResourceType"] = EnumerationToString(resourceType_); item["ID"] = publicId_; item["Path"] = GetBasePath(resourceType_, publicId_); item["RemoteModality"] = modality_; item["Date"] = date_; // WARNING: Do not add "break" below and do not reorder the case items! switch (resourceType_) { case ResourceType_Instance: item["SOPInstanceUID"] = sopInstanceUid_; case ResourceType_Series: item["SeriesInstanceUID"] = seriesInstanceUid_; case ResourceType_Study: item["StudyInstanceUID"] = studyInstanceUid_; case ResourceType_Patient: item["PatientID"] = patientId_; break; default: throw OrthancException(ErrorCode_InternalError); } }
bool ServerContext::AddAttachment(const std::string& resourceId, FileContentType attachmentType, const void* data, size_t size) { LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; if (compressionEnabled_) { accessor_.SetCompressionForNextOperations(CompressionType_Zlib); } else { accessor_.SetCompressionForNextOperations(CompressionType_None); } FileInfo info = accessor_.Write(data, size, attachmentType); StoreStatus status = index_.AddAttachment(info, resourceId); if (status != StoreStatus_Success) { accessor_.Remove(info.GetUuid(), info.GetContentType()); return false; } else { return true; } }
static void SetDefaultEncoding(RestApiPutCall& call) { Encoding encoding = StringToEncoding(call.GetBodyData()); Configuration::SetDefaultEncoding(encoding); call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); }
void ServerContext::ChangeAttachmentCompression(const std::string& resourceId, FileContentType attachmentType, CompressionType compression) { LOG(INFO) << "Changing compression type for attachment " << EnumerationToString(attachmentType) << " of resource " << resourceId << " to " << compression; FileInfo attachment; if (!index_.LookupAttachment(attachment, resourceId, attachmentType)) { throw OrthancException(ErrorCode_UnknownResource); } if (attachment.GetCompressionType() == compression) { // Nothing to do return; } std::string content; StorageAccessor accessor(area_); accessor.Read(content, attachment); FileInfo modified = accessor.Write(content.empty() ? NULL : content.c_str(), content.size(), attachmentType, compression, storeMD5_); try { StoreStatus status = index_.AddAttachment(modified, resourceId); if (status != StoreStatus_Success) { accessor.Remove(modified); throw OrthancException(ErrorCode_Database); } } catch (OrthancException&) { accessor.Remove(modified); throw; } }
bool ServerContext::AddAttachment(const std::string& resourceId, FileContentType attachmentType, const void* data, size_t size) { LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; // TODO Should we use "gzip" instead? CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); StorageAccessor accessor(area_); FileInfo attachment = accessor.Write(data, size, attachmentType, compression, storeMD5_); StoreStatus status = index_.AddAttachment(attachment, resourceId); if (status != StoreStatus_Success) { accessor.Remove(attachment); return false; } else { return true; } }
void ReconstructMainDicomTags(IDatabaseWrapper& database, IStorageArea& storageArea, ResourceType level) { // WARNING: The database should be locked with a transaction! // TODO: This function might consume much memory if level == // ResourceType_Instance. To improve this, first download the // list of studies, then remove the instances for each single // study (check out OrthancRestApi::InvalidateTags for an // example). Take this improvement into consideration for the // next upgrade of the database schema. const char* plural = NULL; switch (level) { case ResourceType_Patient: plural = "patients"; break; case ResourceType_Study: plural = "studies"; break; case ResourceType_Series: plural = "series"; break; case ResourceType_Instance: plural = "instances"; break; default: throw OrthancException(ErrorCode_InternalError); } LOG(WARNING) << "Upgrade: Reconstructing the main DICOM tags of all the " << plural << "..."; std::list<std::string> resources; database.GetAllPublicIds(resources, level); for (std::list<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it) { // Locate the resource and one of its child instances int64_t resource, instance; ResourceType tmp; if (!database.LookupResource(resource, tmp, *it) || tmp != level || !FindOneChildInstance(instance, database, resource, level)) { LOG(ERROR) << "Cannot find an instance for " << EnumerationToString(level) << " with identifier " << *it; throw OrthancException(ErrorCode_InternalError); } // Get the DICOM file attached to some instances in the resource FileInfo attachment; if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom)) { LOG(ERROR) << "Cannot retrieve the DICOM file associated with instance " << database.GetPublicId(instance); throw OrthancException(ErrorCode_InternalError); } try { // Read and parse the content of the DICOM file StorageAccessor accessor(storageArea); std::string content; accessor.Read(content, attachment); ParsedDicomFile dicom(content); // Update the tags of this resource DicomMap dicomSummary; dicom.ExtractDicomSummary(dicomSummary); database.ClearMainDicomTags(resource); StoreMainDicomTags(database, resource, level, dicomSummary); } catch (OrthancException&) { LOG(ERROR) << "Cannot decode the DICOM file with UUID " << attachment.GetUuid() << " associated with instance " << database.GetPublicId(instance); throw; } } }
static void GetDefaultEncoding(RestApiGetCall& call) { Encoding encoding = Configuration::GetDefaultEncoding(); call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); }
bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::string& callingAETitle) { /** * Ensure that the calling modality is known to Orthanc. **/ RemoteModalityParameters modality; if (!Configuration::LookupDicomModalityUsingAETitle(modality, callingAETitle)) { throw OrthancException("Unknown modality"); } // ModalityManufacturer manufacturer = modality.GetManufacturer(); bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false); /** * Retrieve the query level. **/ const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); if (levelTmp == NULL) { throw OrthancException(ErrorCode_BadRequest); } ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); if (level != ResourceType_Patient && level != ResourceType_Study && level != ResourceType_Series && level != ResourceType_Instance) { throw OrthancException(ErrorCode_NotImplemented); } DicomArray query(input); LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level); for (size_t i = 0; i < query.GetSize(); i++) { if (!query.GetElement(i).GetValue().IsNull()) { LOG(INFO) << " " << query.GetElement(i).GetTag() << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) << " = " << query.GetElement(i).GetValue().AsString(); } } /** * Build up the query object. **/ CFindQuery findQuery(answers, context_.GetIndex(), query); findQuery.SetLevel(level); for (size_t i = 0; i < query.GetSize(); i++) { const DicomTag tag = query.GetElement(i).GetTag(); if (query.GetElement(i).GetValue().IsNull() || tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) { continue; } std::string value = query.GetElement(i).GetValue().AsString(); if (value.size() == 0) { // An empty string corresponds to a "*" wildcard constraint, so we ignore it continue; } if (tag == DICOM_TAG_MODALITIES_IN_STUDY) { findQuery.SetModalitiesInStudy(value); } else { findQuery.SetConstraint(tag, value, caseSensitivePN); } } /** * Run the query. **/ ResourceFinder finder(context_); switch (level) { case ResourceType_Patient: case ResourceType_Study: case ResourceType_Series: finder.SetMaxResults(maxResults_); break; case ResourceType_Instance: finder.SetMaxResults(maxInstances_); break; default: throw OrthancException(ErrorCode_InternalError); } std::list<std::string> tmp; bool finished = finder.Apply(tmp, findQuery); LOG(INFO) << "Number of matching resources: " << tmp.size(); return finished; }
void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length) { if (state_ == State_Done) { if (length == 0) { return; } else { LOG(ERROR) << "Because of keep-alive connections, the entire body must be sent at once or Content-Length must be given"; throw OrthancException(ErrorCode_BadSequenceOfCalls); } } if (state_ == State_WritingMultipart) { throw OrthancException(ErrorCode_InternalError); } if (state_ == State_WritingHeader) { // Send the HTTP header before writing the body stream_.OnHttpStatusReceived(status_); std::string s = "HTTP/1.1 " + boost::lexical_cast<std::string>(status_) + " " + std::string(EnumerationToString(status_)) + "\r\n"; if (keepAlive_) { s += "Connection: keep-alive\r\n"; } for (std::list<std::string>::const_iterator it = headers_.begin(); it != headers_.end(); ++it) { s += *it; } if (status_ != HttpStatus_200_Ok) { hasContentLength_ = false; } uint64_t contentLength = (hasContentLength_ ? contentLength_ : length); s += "Content-Length: " + boost::lexical_cast<std::string>(contentLength) + "\r\n\r\n"; stream_.Send(true, s.c_str(), s.size()); state_ = State_WritingBody; } if (hasContentLength_ && contentPosition_ + length > contentLength_) { LOG(ERROR) << "The body size exceeds what was declared with SetContentSize()"; throw OrthancException(ErrorCode_BadSequenceOfCalls); } if (length > 0) { stream_.Send(false, buffer, length); contentPosition_ += length; } if (!hasContentLength_ || contentPosition_ == contentLength_) { state_ = State_Done; } }
void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list<DicomTag>& sequencesToReturn, const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet) { /** * Ensure that the remote modality is known to Orthanc. **/ RemoteModalityParameters modality; if (!Configuration::LookupDicomModalityUsingAETitle(modality, remoteAet)) { throw OrthancException(ErrorCode_UnknownModality); } bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false); /** * Possibly apply the user-supplied Lua filter. **/ DicomMap lua; const DicomMap* filteredInput = &input; if (ApplyLuaFilter(lua, input, remoteIp, remoteAet, calledAet)) { filteredInput = &lua; } /** * Retrieve the query level. **/ assert(filteredInput != NULL); const DicomValue* levelTmp = filteredInput->TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); if (levelTmp == NULL || levelTmp->IsNull() || levelTmp->IsBinary()) { LOG(ERROR) << "C-FIND request without the tag 0008,0052 (QueryRetrieveLevel)"; throw OrthancException(ErrorCode_BadRequest); } ResourceType level = StringToResourceType(levelTmp->GetContent().c_str()); if (level != ResourceType_Patient && level != ResourceType_Study && level != ResourceType_Series && level != ResourceType_Instance) { throw OrthancException(ErrorCode_NotImplemented); } DicomArray query(*filteredInput); LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level); for (size_t i = 0; i < query.GetSize(); i++) { if (!query.GetElement(i).GetValue().IsNull()) { LOG(INFO) << " " << query.GetElement(i).GetTag() << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) << " = " << query.GetElement(i).GetValue().GetContent(); } } for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin(); it != sequencesToReturn.end(); ++it) { LOG(INFO) << " (" << it->Format() << ") " << FromDcmtkBridge::GetName(*it) << " : sequence tag whose content will be copied"; } /** * Build up the query object. **/ LookupResource finder(level); for (size_t i = 0; i < query.GetSize(); i++) { const DicomTag tag = query.GetElement(i).GetTag(); if (query.GetElement(i).GetValue().IsNull() || tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) { continue; } std::string value = query.GetElement(i).GetValue().GetContent(); if (value.size() == 0) { // An empty string corresponds to a "*" wildcard constraint, so we ignore it continue; } if (FilterQueryTag(value, level, tag, modality.GetManufacturer())) { ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); // DICOM specifies that searches must be case sensitive, except // for tags with a PN value representation bool sensitive = true; if (vr == ValueRepresentation_PersonName) { sensitive = caseSensitivePN; } finder.AddDicomConstraint(tag, value, sensitive); } else { LOG(INFO) << "Because of a patch for the manufacturer of the remote modality, " << "ignoring constraint on tag (" << tag.Format() << ") " << FromDcmtkBridge::GetName(tag); } } /** * Run the query. **/ size_t maxResults = (level == ResourceType_Instance) ? maxInstances_ : maxResults_; std::vector<std::string> resources, instances; context_.GetIndex().FindCandidates(resources, instances, finder); assert(resources.size() == instances.size()); bool complete = true; for (size_t i = 0; i < instances.size(); i++) { Json::Value dicom; context_.ReadJson(dicom, instances[i]); if (finder.IsMatch(dicom)) { if (maxResults != 0 && answers.GetSize() >= maxResults) { complete = false; break; } else { std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instances[i], level, input)); AddAnswer(answers, dicom, query, sequencesToReturn, counters.get()); } } } LOG(INFO) << "Number of matching resources: " << answers.GetSize(); answers.SetComplete(complete); }
static void InternalCallback(struct mg_connection *connection, const struct mg_request_info *request) { MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data); MongooseOutputStream stream(connection); HttpOutput output(stream, that->IsKeepAliveEnabled()); // Check remote calls if (!that->IsRemoteAccessAllowed() && request->remote_ip != LOCALHOST) { output.SendUnauthorized(ORTHANC_REALM); return; } // Extract the HTTP headers IHttpHandler::Arguments headers; for (int i = 0; i < request->num_headers; i++) { std::string name = request->http_headers[i].name; std::transform(name.begin(), name.end(), name.begin(), ::tolower); headers.insert(std::make_pair(name, request->http_headers[i].value)); } // Extract the GET arguments IHttpHandler::GetArguments argumentsGET; if (!strcmp(request->request_method, "GET")) { HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); } // Compute the HTTP method, taking method faking into consideration HttpMethod method = HttpMethod_Get; if (!ExtractMethod(method, request, headers, argumentsGET)) { output.SendStatus(HttpStatus_400_BadRequest); return; } // Authenticate this connection if (that->IsAuthenticationEnabled() && !IsAccessGranted(*that, headers)) { output.SendUnauthorized(ORTHANC_REALM); return; } // Apply the filter, if it is installed const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter(); if (filter != NULL) { std::string username = GetAuthenticatedUsername(headers); char remoteIp[24]; sprintf(remoteIp, "%d.%d.%d.%d", reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]); if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str())) { output.SendUnauthorized(ORTHANC_REALM); return; } } // Extract the body of the request for PUT and POST // TODO Avoid unneccessary memcopy of the body std::string body; if (method == HttpMethod_Post || method == HttpMethod_Put) { PostDataStatus status; IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); if (ct == headers.end()) { // No content-type specified. Assume no multi-part content occurs at this point. status = ReadBody(body, connection, headers); } else { std::string contentType = ct->second; if (contentType.size() >= multipartLength && !memcmp(contentType.c_str(), multipart, multipartLength)) { status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore()); } else { status = ReadBody(body, connection, headers); } } switch (status) { case PostDataStatus_NoLength: output.SendStatus(HttpStatus_411_LengthRequired); return; case PostDataStatus_Failure: output.SendStatus(HttpStatus_400_BadRequest); return; case PostDataStatus_Pending: output.SendBody(); return; default: break; } } // Decompose the URI into its components UriComponents uri; try { Toolbox::SplitUriComponents(uri, request->uri); } catch (OrthancException) { output.SendStatus(HttpStatus_400_BadRequest); return; } LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); bool found = false; try { if (that->HasHandler()) { found = that->GetHandler().Handle(output, method, uri, headers, argumentsGET, body.c_str(), body.size()); } } catch (OrthancException& e) { // Using this candidate handler results in an exception LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); try { switch (e.GetErrorCode()) { case ErrorCode_InexistentFile: case ErrorCode_InexistentItem: case ErrorCode_UnknownResource: output.SendStatus(HttpStatus_404_NotFound); break; case ErrorCode_BadRequest: case ErrorCode_UriSyntax: output.SendStatus(HttpStatus_400_BadRequest); break; default: output.SendStatus(HttpStatus_500_InternalServerError); } } catch (OrthancException&) { // An exception here reflects the fact that an exception was // triggered after the status code was sent by the HTTP handler. } return; } catch (boost::bad_lexical_cast&) { LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast"; output.SendStatus(HttpStatus_400_BadRequest); return; } catch (std::runtime_error&) { LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request"; output.SendStatus(HttpStatus_400_BadRequest); return; } if (!found) { output.SendStatus(HttpStatus_404_NotFound); } }
static void AnonymizeOrModifyResource(DicomModification& modification, MetadataType metadataType, ChangeType changeType, ResourceType resourceType, RestApiPostCall& call) { bool isFirst = true; Json::Value result(Json::objectValue); ServerContext& context = OrthancRestApi::GetContext(call); typedef std::list<std::string> Instances; Instances instances; std::string id = call.GetUriComponent("id", ""); context.GetIndex().GetChildInstances(instances, id); if (instances.empty()) { return; } /** * Loop over all the instances of the resource. **/ for (Instances::const_iterator it = instances.begin(); it != instances.end(); ++it) { LOG(INFO) << "Modifying instance " << *it; std::auto_ptr<ServerContext::DicomCacheLocker> locker; try { locker.reset(new ServerContext::DicomCacheLocker(OrthancRestApi::GetContext(call), *it)); } catch (OrthancException&) { // This child instance has been removed in between continue; } ParsedDicomFile& original = locker->GetDicom(); DicomInstanceHasher originalHasher = original.GetHasher(); /** * Compute the resulting DICOM instance. **/ std::auto_ptr<ParsedDicomFile> modified(original.Clone()); modification.Apply(*modified); DicomInstanceToStore toStore; toStore.SetParsedDicomFile(*modified); /** * Prepare the metadata information to associate with the * resulting DICOM instance (AnonymizedFrom/ModifiedFrom). **/ DicomInstanceHasher modifiedHasher = modified->GetHasher(); if (originalHasher.HashSeries() != modifiedHasher.HashSeries()) { toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher.HashSeries()); } if (originalHasher.HashStudy() != modifiedHasher.HashStudy()) { toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher.HashStudy()); } if (originalHasher.HashPatient() != modifiedHasher.HashPatient()) { toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher.HashPatient()); } assert(*it == originalHasher.HashInstance()); toStore.AddMetadata(ResourceType_Instance, metadataType, *it); /** * Store the resulting DICOM instance into the Orthanc store. **/ std::string modifiedInstance; if (context.Store(modifiedInstance, toStore) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << *it; return; } // Sanity checks in debug mode assert(modifiedInstance == modifiedHasher.HashInstance()); /** * Compute the JSON object that is returned by the REST call. **/ if (isFirst) { std::string newId; switch (resourceType) { case ResourceType_Series: newId = modifiedHasher.HashSeries(); break; case ResourceType_Study: newId = modifiedHasher.HashStudy(); break; case ResourceType_Patient: newId = modifiedHasher.HashPatient(); break; default: throw OrthancException(ErrorCode_InternalError); } result["Type"] = EnumerationToString(resourceType); result["ID"] = newId; result["Path"] = GetBasePath(resourceType, newId); result["PatientID"] = modifiedHasher.HashPatient(); isFirst = false; } } call.GetOutput().AnswerJson(result); }