11import ast
2+ import shlex
3+ import shutil
24from django .db import connections
35from importer .publisher import DataPublisher
46from importer .utils import call_rollback_function
@@ -160,6 +162,8 @@ def create_ogr2ogr_command(files, original_name, ovverwrite_layer, alternate):
160162 This is a default command that is needed to import a vector file
161163 """
162164 _datastore = settings .DATABASES ["datastore" ]
165+ layers = ogr .Open (files .get ("base_file" ))
166+ layer = layers .GetLayer (original_name )
163167
164168 options = "--config PG_USE_COPY YES"
165169 copy_with_dump = ast .literal_eval (os .getenv ("OGR2OGR_COPY_WITH_DUMP" , "False" ))
@@ -169,19 +173,24 @@ def create_ogr2ogr_command(files, original_name, ovverwrite_layer, alternate):
169173 options += " -f PGDump /vsistdout/ "
170174 else :
171175 # default option with postgres copy
172- options += (
173- " -f PostgreSQL PG:\" dbname='%s' host=%s port=%s user='%s' password='%s' \" "
174- % (
175- _datastore ["NAME" ],
176- _datastore ["HOST" ],
177- _datastore .get ("PORT" , 5432 ),
178- _datastore ["USER" ],
179- _datastore ["PASSWORD" ],
180- )
176+ options += " -f PostgreSQL PG:\" dbname='%s' host=%s port=%s user='%s' password='%s' \" " % (
177+ _datastore ["NAME" ],
178+ _datastore ["HOST" ],
179+ _datastore .get ("PORT" , 5432 ),
180+ _datastore ["USER" ],
181+ _datastore ["PASSWORD" ],
181182 )
182- options += f'"{ files .get ("base_file" )} "' + " "
183183
184- options += f'-nln { alternate } "{ original_name } "'
184+ # vrt file fallback logic
185+ input_file = files .get ("temp_vrt_file" ) or files .get ("base_file" )
186+
187+ # Securely quote the input file and layer names
188+ options += f" { shlex .quote (input_file )} "
189+ options += f" -nln { shlex .quote (alternate )} { shlex .quote (original_name )} "
190+
191+ # Geometry promotion logic
192+ if layer is not None and "Point" not in ogr .GeometryTypeToName (layer .GetGeomType ()):
193+ options += " -nlt PROMOTE_TO_MULTI"
185194
186195 if ovverwrite_layer :
187196 options += " -overwrite"
@@ -904,28 +913,48 @@ def import_with_ogr2ogr(
904913 If the layer should be overwritten, the option is appended dynamically
905914 """
906915 try :
907- ogr_exe = "/usr/bin/ogr2ogr"
916+ # Use shutil to find the binary path safely
917+ ogr_exe = shutil .which ("ogr2ogr" ) or "ogr2ogr"
908918
909- options = orchestrator .load_handler (handler_module_path ).create_ogr2ogr_command (
919+ # Load the command string from the handler
920+ options_str = orchestrator .load_handler (handler_module_path ).create_ogr2ogr_command (
910921 files , original_name , ovverwrite_layer , alternate
911922 )
912- _datastore = settings .DATABASES ["datastore" ]
923+
924+ # Split the string into a list for Popen
925+ cmd_list = [ogr_exe ] + shlex .split (options_str )
913926
914927 copy_with_dump = ast .literal_eval (os .getenv ("OGR2OGR_COPY_WITH_DUMP" , "False" ))
915928
916929 if copy_with_dump :
917- options += f" | PGPASSWORD={ _datastore ['PASSWORD' ]} psql -d { _datastore ['NAME' ]} -h { _datastore ['HOST' ]} -p { _datastore .get ('PORT' , 5432 )} -U { _datastore ['USER' ]} -f -"
930+ _datastore = settings .DATABASES ["datastore" ]
931+ psql_cmd = [
932+ "psql" , "-d" , _datastore ["NAME" ], "-h" , _datastore ["HOST" ],
933+ "-p" , str (_datastore .get ("PORT" , 5432 )), "-U" , _datastore ["USER" ], "-f" , "-"
934+ ]
918935
919- commands = [ogr_exe ] + options .split (" " )
936+ env = os .environ .copy ()
937+ env ["PGPASSWORD" ] = _datastore ["PASSWORD" ]
920938
921- process = Popen (" " .join (commands ), stdout = PIPE , stderr = PIPE , shell = True )
922- stdout , stderr = process .communicate ()
939+ p1 = Popen (cmd_list , stdout = PIPE , stderr = PIPE )
940+ p2 = Popen (psql_cmd , stdin = p1 .stdout , stdout = PIPE , stderr = PIPE , env = env )
941+
942+ p1 .stdout .close ()
943+ stdout , stderr = p2 .communicate ()
944+ else :
945+ # Standard execution with shell=False
946+ process = Popen (cmd_list , stdout = PIPE , stderr = PIPE , shell = False )
947+ stdout , stderr = process .communicate ()
948+
949+ # Cleanup temporary files
950+ if files .get ("temp_vrt_file" ) and os .path .exists (files ["temp_vrt_file" ]):
951+ os .remove (files ["temp_vrt_file" ])
952+
953+ # OGR error handling
923954 if (
924955 stderr is not None
925956 and stderr != b""
926- and b"ERROR" in stderr
927- and b"error" in stderr
928- or b"Syntax error" in stderr
957+ and (b"ERROR" in stderr or b"error" in stderr or b"Syntax error" in stderr )
929958 ):
930959 try :
931960 err = stderr .decode ()
@@ -934,8 +963,14 @@ def import_with_ogr2ogr(
934963 logger .error (f"Original error returned: { err } " )
935964 message = normalize_ogr2ogr_error (err , original_name )
936965 raise Exception (f"{ message } for layer { alternate } " )
966+
937967 return "ogr2ogr" , alternate , execution_id
968+
938969 except Exception as e :
970+ # Cleanup on failure
971+ if files .get ("temp_vrt_file" ) and os .path .exists (files .get ("temp_vrt_file" )):
972+ os .remove (files ["temp_vrt_file" ])
973+
939974 call_rollback_function (
940975 execution_id ,
941976 handlers_module_path = handler_module_path ,
0 commit comments