create or replace package body pck_net_storage as c_log_module constant lg_app_log.log_module%type := 'NETZLAUFWERK'; -- ==================== Private Helpers ==================== function f_build_url ( i_object_key in varchar2 default null ,i_action in varchar2 default null ) return varchar2 /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Baut die vollständige OCI Object Storage URL aus den Konfigurationsparametern. -- Entweder für eine Bucket-Action, ein einzelnes Objekt oder den Bucket-Root. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Objektschlüssel (Pfad im Bucket); null für Bucket-Root oder Action-URL -- i_action OCI Bucket-Action (z.B. renameObject); null für Objekt-URL ------------------------------------------------------------------------------------------------------ -- Rückgabe: Vollständige URL als VARCHAR2 ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_base varchar2(1024); begin l_base := 'https://objectstorage.' || pck_system.f_get_par_wert_by_programmid('NETSTORE_REGION') || '.oraclecloud.com/n/' || pck_system.f_get_par_wert_by_programmid('NETSTORE_NAMESPACE') || '/b/' || pck_system.f_get_par_wert_by_programmid('NETSTORE_BUCKET'); if i_action is not null then return l_base || '/actions/' || i_action; elsif i_object_key is not null then -- Sonderzeichen kodieren, Schrägstriche im Key unverändert lassen return l_base || '/o/' || utl_url.escape(i_object_key, false); else return l_base || '/o'; end if; end f_build_url; -- Normalisiert einen Ordnerpfad: stellt sicher, dass er mit / endet. -- null bleibt null (= Bucket-Root). function f_normalize_prefix (i_prefix in varchar2) return varchar2 is begin if i_prefix is null then return null; end if; return rtrim(i_prefix, '/') || '/'; end f_normalize_prefix; procedure p_assert_allowed (i_object_key in varchar2) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Prüft den Objektschlüssel auf Gültigkeit, Path-Traversal-Angriffe und Tenant-Scope. -- Wirft Application Error -20008 bei null-Key, -20004 bei Path Traversal, -- -20005 bei Scope-Verletzung. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Zu prüfender Objektschlüssel ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt -- SCK 2026-04-10 Null-Prüfung und führender-Slash-Check ergänzt ------------------------------------------------------------------------------------------------Kopf*/ is l_tenant_prefix varchar2(256); begin if i_object_key is null then raise_application_error(-20008, 'Object key darf nicht null sein'); end if; if instr(i_object_key, '..') > 0 or substr(i_object_key, 1, 1) = '/' then raise_application_error(-20004, 'Path traversal attempt detected'); end if; l_tenant_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_TENANT_ID'); if l_tenant_prefix is not null and length(l_tenant_prefix) > 0 then if substr(i_object_key, 1, length(l_tenant_prefix)) != l_tenant_prefix then raise_application_error(-20005, 'Access denied: outside tenant scope'); end if; end if; end p_assert_allowed; function f_make_request ( i_method in varchar2 ,i_url in varchar2 ,i_body_clob in clob default null ,i_body_blob in blob default null ,i_content_type in varchar2 default null ) return clob /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Führt einen HTTP-Request gegen die OCI Object Storage API aus. -- Wertet den HTTP-Statuscode aus und löst bei Fehler einen Application Error aus. -- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID). ------------------------------------------------------------------------------------------------------ -- Parameter: i_method HTTP-Methode (GET, PUT, DELETE, POST, HEAD) -- i_url Vollständige Ziel-URL -- i_body_clob Optionaler Request-Body als CLOB (z.B. JSON) -- i_body_blob Optionaler Request-Body als BLOB (Binärinhalt) -- i_content_type Optionaler Content-Type Header ------------------------------------------------------------------------------------------------------ -- Rückgabe: Response-Body als CLOB (bei HEAD-Requests leer) ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_response clob; l_status number; l_header_index number := 1; begin if i_content_type is not null then apex_web_service.g_request_headers(l_header_index).name := 'Content-Type'; apex_web_service.g_request_headers(l_header_index).value := i_content_type; l_header_index := l_header_index + 1; end if; l_response := apex_web_service.make_rest_request( p_url => i_url ,p_http_method => i_method ,p_body => coalesce(i_body_clob, empty_clob()) ,p_body_blob => coalesce(i_body_blob, empty_blob()) ,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID') ,p_wallet_path => pck_system.f_get_par_wert_by_programmid('NETSTORE_WALLET_PATH') ); l_status := apex_web_service.g_status_code; if l_status = 404 then raise_application_error(-20001, 'Object not found'); elsif l_status in (401, 403) then raise_application_error(-20002, 'OCI authentication failed'); elsif l_status = 409 then raise_application_error(-20007, 'Object already exists'); elsif l_status >= 400 then raise_application_error(-20006, 'OCI API error ' || l_status || ': ' || dbms_lob.substr(l_response, 500, 1)); end if; return l_response; end f_make_request; -- Interne Implementierung ohne Rechteprüfung — wird von f_list_objects und p_delete_folder genutzt function f_list_objects_internal ( i_parent_folder in varchar2 ,i_include_subfolders in varchar2 ,i_start_with in varchar2 ,i_limit in number ) return t_net_storage_tab /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Listet Objekte und Unterordner im Bucket ohne Rechte- oder Scope-Prüfung. -- Paginiert automatisch über nextStartWith bis alle Ergebnisse geladen sind. -- Wird von f_list_objects (öffentlich) und p_delete_folder intern genutzt. ------------------------------------------------------------------------------------------------------ -- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/) -- i_include_subfolders 'Y' = alle Dateien rekursiv, 'N' = nur direkte Kinder des Ordners -- i_start_with Optionaler Startpunkt für Paginierung -- i_limit Maximale Anzahl Ergebnisse (0 = unbegrenzt) ------------------------------------------------------------------------------------------------------ -- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_url varchar2(4000); l_response clob; l_result t_net_storage_tab := t_net_storage_tab(); l_next_start varchar2(1024); l_count number := 0; l_done boolean := false; l_cur_start varchar2(1024) := i_start_with; l_parent_folder varchar2(1024) := f_normalize_prefix(i_parent_folder); c_page_size constant number := 1000; begin while not l_done loop l_url := f_build_url() || '?limit=' || c_page_size || (case when l_parent_folder is not null then '&prefix=' || utl_url.escape(l_parent_folder, false) else '' end) || (case when i_include_subfolders = 'N' then '&delimiter=/' else '' end); if l_cur_start is not null then l_url := l_url || '&start=' || utl_url.escape(l_cur_start, false); end if; l_response := f_make_request('GET', l_url); -- Dateien aus objects-Array einlesen for rec in ( select jt.object_name ,jt.object_size ,jt.last_modified ,jt.etag from json_table(l_response, '$.objects[*]' columns ( object_name varchar2(1024) path '$.name' ,object_size number path '$.size' ,last_modified varchar2(50) path '$.timeModified' ,etag varchar2(256) path '$.etag' )) jt ) loop l_result.extend; l_result(l_result.last) := t_net_storage_row( rec.object_name ,rec.object_size ,to_date(substr(rec.last_modified, 1, 19), 'YYYY-MM-DD"T"HH24:MI:SS') -- Explizit angelegte Ordner sind Zero-Byte-Objekte mit trailing / ,(case when rec.object_name like '%/' then 'Y' else 'N' end) ,rec.etag ); l_count := l_count + 1; if i_limit > 0 and l_count >= i_limit then l_done := true; exit; end if; end loop; -- Unterordner aus prefixes-Array einlesen if not l_done then for rec in ( select jt.prefix_name from json_table(l_response, '$.prefixes[*]' columns ( prefix_name varchar2(1024) path '$' )) jt ) loop l_result.extend; l_result(l_result.last) := t_net_storage_row( rec.prefix_name ,0 ,null ,'Y' ,null ); l_count := l_count + 1; if i_limit > 0 and l_count >= i_limit then l_done := true; exit; end if; end loop; end if; -- Nächste Seite prüfen if not l_done then l_next_start := json_value(l_response, '$.nextStartWith'); if l_next_start is null then l_done := true; else l_cur_start := l_next_start; end if; end if; end loop; return l_result; end f_list_objects_internal; -- ==================== Öffentliche Funktionen ==================== function f_split_object_key (i_object_key in varchar2) return t_object_path /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel. -- Bei Ordner-Keys (trailing Slash) wird der Ordnername als Dateiname zurückgegeben. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Vollständiger Objektschlüssel (z.B. mandant/Eingang/Import/datei.pdf) ------------------------------------------------------------------------------------------------------ -- Rückgabe: t_object_path Record mit path (inkl. trailing Slash) und filename ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-09 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_key varchar2(1024); l_pos number; l_result t_object_path; begin -- Trailing Slash entfernen damit Ordner-Keys genauso behandelt werden wie Datei-Keys l_key := rtrim(i_object_key, '/'); l_pos := instr(l_key, '/', -1); if l_pos > 0 then l_result.path := substr(l_key, 1, l_pos); l_result.filename := substr(l_key, l_pos + 1); else l_result.path := null; l_result.filename := l_key; end if; return l_result; end f_split_object_key; function f_list_objects ( i_parent_folder in varchar2 ,i_include_subfolders in varchar2 default 'N' ,i_start_with in varchar2 default null ,i_limit in number default 0 ) return t_net_storage_tab /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Listet Objekte und Unterordner im Bucket mit Rechteprüfung und Scope-Validierung. ------------------------------------------------------------------------------------------------------ -- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/) -- i_include_subfolders 'Y' = alle Dateien rekursiv inkl. Unterordner, 'N' = nur direkte Dateien im Ordner (Standard) -- i_start_with Optionaler Startpunkt für Paginierung -- i_limit Maximale Anzahl Ergebnisse (0 = unbegrenzt) ------------------------------------------------------------------------------------------------------ -- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_parent_folder varchar2(1024) := f_normalize_prefix(i_parent_folder); begin pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES'); if l_parent_folder is not null then p_assert_allowed(l_parent_folder); end if; return f_list_objects_internal(l_parent_folder, i_include_subfolders, i_start_with, i_limit); end f_list_objects; function f_download_object (i_object_key in varchar2) return blob /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Lädt ein einzelnes Objekt aus dem OCI Bucket als BLOB herunter. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket ------------------------------------------------------------------------------------------------------ -- Rückgabe: Dateiinhalt als BLOB ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_response blob; l_status number; begin pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES'); p_assert_allowed(i_object_key); l_response := apex_web_service.make_rest_request_b( p_url => f_build_url(i_object_key) ,p_http_method => 'GET' ,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID') ); l_status := apex_web_service.g_status_code; if l_status = 404 then raise_application_error(-20001, 'Object not found: ' || i_object_key); elsif l_status in (401, 403) then raise_application_error(-20002, 'OCI authentication failed'); elsif l_status >= 400 then raise_application_error(-20006, 'OCI API error ' || l_status); end if; return l_response; end f_download_object; procedure p_upload_object ( i_object_key in varchar2 ,i_content in blob ,i_content_type in varchar2 ) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Lädt ein Objekt in den OCI Bucket hoch (PUT). Überschreibt vorhandene Objekte. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Zielpfad im Bucket -- i_content Dateiinhalt als BLOB -- i_content_type MIME-Type des Inhalts (z.B. application/octet-stream) ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_response clob; l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); if substr(i_object_key, -1) = '/' then raise_application_error(-20012, 'Object Key darf nicht mit / enden — zum Anlegen von Ordnern p_create_folder verwenden'); end if; -- TEST --l_response := f_make_request( -- i_method => 'PUT' -- ,i_url => f_build_url(i_object_key) -- ,i_body_blob => i_content -- ,i_content_type => i_content_type --); l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'UPLOAD' ,i_message => 'Datei "' || l_obj_path.filename || '" hochgeladen | Ordner: ' || l_obj_path.path ,i_object_ref => i_object_key ); end p_upload_object; procedure p_delete_object (i_object_key in varchar2) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Löscht ein einzelnes Objekt aus dem OCI Bucket (DELETE). ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_response clob; l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('ADMIN'); p_assert_allowed(i_object_key); -- TEST --l_response := f_make_request( -- i_method => 'DELETE' -- ,i_url => f_build_url(i_object_key) --); l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'DELETE' ,i_message => 'Datei "' || l_obj_path.filename || '" gelöscht | Ordner: ' || l_obj_path.path ,i_object_ref => i_object_key ); end p_delete_object; procedure p_delete_folder (i_prefix in varchar2) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Löscht rekursiv alle Objekte unterhalb eines Präfixes im OCI Bucket. -- Pseudo-Ordner (is_folder = Y) werden übersprungen. ------------------------------------------------------------------------------------------------------ -- Parameter: i_prefix Ordnerpräfix (z.B. eingang/batch-001/) ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_objects t_net_storage_tab; l_response clob; l_obj_path t_object_path; l_prefix varchar2(1024) := f_normalize_prefix(i_prefix); begin pck_mitarbeiterrecht.p_hat_recht('ADMIN'); p_assert_allowed(l_prefix); -- Alle Objekte im Ordner auflisten (rekursiv, alle Tiefen) --l_objects := f_list_objects_internal( -- i_parent_folder => l_prefix -- ,i_include_subfolders => 'Y' -- ,i_start_with => null -- ,i_limit => 0 --); -- Nur echte Objekte löschen, keine Pseudo-Ordner --for rec in (select object_name, is_folder from table(l_objects)) --loop -- if rec.is_folder = 'N' -- then -- l_response := f_make_request( -- i_method => 'DELETE' -- ,i_url => f_build_url(rec.object_name) -- ); -- end if; --end loop; l_obj_path := f_split_object_key(l_prefix); pck_log.p_info( i_module => c_log_module ,i_action => 'DELETE_FOLDER' ,i_message => 'Ordner "' || l_obj_path.filename || '" rekursiv gelöscht | Pfad: ' || l_obj_path.path ,i_object_ref => l_prefix ); end p_delete_folder; procedure p_rename_object ( i_object_key in varchar2 ,i_new_name in varchar2 ) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Benennt ein Objekt innerhalb desselben Verzeichnisses um. -- Verwendet die OCI renameObject-Action (kein physisches Kopieren). ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Vollständiger Objektschlüssel des Quelldatei -- i_new_name Neuer Dateiname (ohne Pfad) ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_prefix varchar2(1024); l_new_key varchar2(1024); l_body clob; l_response clob; l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); if i_new_name is null or length(trim(i_new_name)) = 0 then raise_application_error(-20013, 'Neuer Dateiname darf nicht leer sein'); end if; if instr(i_new_name, '/') > 0 then raise_application_error(-20014, 'Dateiname darf keinen Schrägstrich enthalten — zum Verschieben p_move_object verwenden'); end if; -- Verzeichnispfad aus dem aktuellen Key extrahieren if instr(i_object_key, '/') > 0 then l_prefix := substr(i_object_key, 1, instr(i_object_key, '/', -1)); else l_prefix := null; end if; l_new_key := l_prefix || i_new_name; p_assert_allowed(l_new_key); select json_object( 'sourceName' value i_object_key ,'newName' value l_new_key ) into l_body from dual; -- TEST --l_response := f_make_request( -- i_method => 'POST' -- ,i_url => f_build_url(i_action => 'renameObject') -- ,i_body_clob => l_body -- ,i_content_type => 'application/json' --); l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'RENAME' ,i_message => 'Datei "' || l_obj_path.filename || '" umbenannt in "' || i_new_name || '" | Ordner: ' || l_obj_path.path ,i_object_ref => i_object_key ); end p_rename_object; procedure p_move_object ( i_object_key in varchar2 ,i_target_prefix in varchar2 ) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Verschiebt ein Objekt in einen anderen Ordner im selben Bucket. -- Verwendet die OCI renameObject-Action (kein physisches Kopieren). -- Der Dateiname bleibt erhalten; nur der Pfad ändert sich. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Vollständiger Objektschlüssel der Quelldatei -- i_target_prefix Zielpräfix inkl. trailing Slash (z.B. verarbeitet/batch-001/) ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_filename varchar2(1024); l_new_key varchar2(1024); l_body clob; l_response clob; l_obj_path t_object_path; l_target_prefix varchar2(1024); begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); if i_target_prefix is null then raise_application_error(-20015, 'Zielpräfix darf nicht null sein'); end if; l_target_prefix := f_normalize_prefix(i_target_prefix); p_assert_allowed(l_target_prefix); -- Dateinamen aus dem aktuellen Key extrahieren if instr(i_object_key, '/') > 0 then l_filename := substr(i_object_key, instr(i_object_key, '/', -1) + 1); else l_filename := i_object_key; end if; l_new_key := l_target_prefix || l_filename; p_assert_allowed(l_new_key); select json_object( 'sourceName' value i_object_key ,'newName' value l_new_key ) into l_body from dual; -- TEST --l_response := f_make_request( -- i_method => 'POST' -- ,i_url => f_build_url(i_action => 'renameObject') -- ,i_body_clob => l_body -- ,i_content_type => 'application/json' --); l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'MOVE' ,i_message => 'Datei "' || l_obj_path.filename || '" verschoben | Von: ' || l_obj_path.path || ' | Nach: ' || l_target_prefix ,i_object_ref => i_object_key ); end p_move_object; procedure p_create_folder ( i_prefix in varchar2 ,i_folder_name in varchar2 ) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Legt einen neuen Ordner im OCI Bucket an. -- Ordner werden als leeres Objekt mit trailing Slash simuliert. ------------------------------------------------------------------------------------------------------ -- Parameter: i_prefix Übergeordneter Pfad inkl. trailing Slash (z.B. eingang/) -- i_folder_name Name des neuen Ordners (ohne Slash) ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_folder_key varchar2(1024); l_response clob; l_obj_path t_object_path; l_prefix varchar2(1024) := f_normalize_prefix(i_prefix); begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); if i_folder_name is null or length(trim(i_folder_name)) = 0 then raise_application_error(-20010, 'Ordnername darf nicht leer sein'); end if; if instr(i_folder_name, '/') > 0 then raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten'); end if; if l_prefix is not null then p_assert_allowed(l_prefix); end if; -- Ordner als leeres Objekt mit trailing Slash anlegen l_folder_key := l_prefix || i_folder_name || '/'; p_assert_allowed(l_folder_key); l_response := f_make_request( i_method => 'PUT' ,i_url => f_build_url(l_folder_key) ,i_body_blob => empty_blob() ,i_content_type => 'application/octet-stream' ); l_obj_path := f_split_object_key(l_folder_key); pck_log.p_info( i_module => c_log_module ,i_action => 'CREATE_FOLDER' ,i_message => 'Ordner "' || l_obj_path.filename || '" angelegt | Pfad: ' || l_obj_path.path ,i_object_ref => l_folder_key ); end p_create_folder; function f_get_object_metadata (i_object_key in varchar2) return t_object_meta /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Ruft die Metadaten eines Objekts per HEAD-Request ab (kein Download des Inhalts). -- Liest Größe, Content-Type, Last-Modified und ETag aus den Response-Headern. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket ------------------------------------------------------------------------------------------------------ -- Rückgabe: t_object_meta Record mit object_name, object_size, last_modified, content_type, etag ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_response clob; l_result t_object_meta; l_hdr_name varchar2(256); begin pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES'); p_assert_allowed(i_object_key); -- HEAD-Anfrage: leerer Response-Body, Metadaten in Response-Headern l_response := f_make_request( i_method => 'HEAD' ,i_url => f_build_url(i_object_key) ); l_result.object_name := i_object_key; for i in 1..apex_web_service.g_headers.count loop l_hdr_name := lower(apex_web_service.g_headers(i).name); case l_hdr_name when 'content-length' then l_result.object_size := to_number(apex_web_service.g_headers(i).value); when 'content-type' then l_result.content_type := apex_web_service.g_headers(i).value; when 'last-modified' then -- HTTP RFC 7231 Format: "Thu, 01 Jan 2026 00:00:00 GMT" l_result.last_modified := to_date( apex_web_service.g_headers(i).value ,'DY, DD MON YYYY HH24:MI:SS "GMT"' ,'NLS_DATE_LANGUAGE=AMERICAN' ); when 'etag' then l_result.etag := apex_web_service.g_headers(i).value; else null; end case; end loop; return l_result; end f_get_object_metadata; end pck_net_storage; /