Srevarshan1502 commited on
Commit
81c8cf0
·
verified ·
1 Parent(s): 62326fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -24
app.py CHANGED
@@ -35,6 +35,7 @@ class WebSocketInputOutput:
35
  self.output_buffer = io.StringIO()
36
  self.error_buffer = io.StringIO()
37
  self.closed = False
 
38
 
39
  async def write(self, s: str):
40
  """Writes to stdout/stderr and sends to WebSocket."""
@@ -42,11 +43,12 @@ class WebSocketInputOutput:
42
  try:
43
  # Send output in chunks or lines to prevent large messages
44
  await self.websocket.send_text(json.dumps({"type": "output", "content": s}))
 
45
  except WebSocketDisconnect:
46
  self.closed = True
47
- print("WebSocket disconnected during write.")
48
  except Exception as e:
49
- print(f"Error sending output over WebSocket: {e}")
50
  self.closed = True
51
 
52
  def flush(self):
@@ -56,44 +58,37 @@ class WebSocketInputOutput:
56
  async def readline(self) -> str:
57
  """Reads a line from stdin, waiting for input from WebSocket."""
58
  try:
 
59
  await self.websocket.send_text(json.dumps({"type": "input_request"}))
60
  line = await self.input_queue.get() # Wait for input from client
 
61
  return line
62
  except WebSocketDisconnect:
63
  self.closed = True
64
- print("WebSocket disconnected during readline.")
65
  raise EOFError("Input stream closed due to WebSocket disconnect")
66
  except Exception as e:
67
- print(f"Error requesting input over WebSocket: {e}")
68
  self.closed = True
69
  raise EOFError(f"Input stream error: {e}")
70
 
71
  # For compatibility with sys.stdin, which expects a file-like object
72
  def _get_input_line(self):
73
  """Synchronous wrapper for readline for use with exec."""
74
- # This is a bit tricky: exec is synchronous, but WebSocket is async.
75
- # We need to bridge this. A simple way is to run the async part
76
- # in the event loop, but it's usually better to refactor the executor
77
- # to be fully async if input() is truly interactive.
78
- # For this example, we'll use a blocking call to asyncio.run
79
- # which is generally discouraged in a running event loop,
80
- # but works for simple cases or if the exec is in a separate thread.
81
- # A more robust solution involves a custom Future/Event loop integration.
82
  try:
83
- # This will block the current thread until input is available
84
  return asyncio.run(self.readline())
85
  except Exception as e:
86
- print(f"Synchronous input wrapper error: {e}")
87
  raise
88
 
89
  def read(self, n=-1):
90
  """Reads n characters. For simplicity, we'll treat it as readline."""
 
91
  return self._get_input_line() # Or implement more complex buffering
92
 
93
  def __getattr__(self, name):
94
  """Delegate other attributes if needed, or raise AttributeError."""
95
- # This is a placeholder; real implementation would be more robust
96
- # For now, we only need write, flush, and readline.
97
  raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
98
 
99
 
@@ -314,11 +309,13 @@ class SecurePythonExecutor:
314
  async def execute_code(self, code: str, websocket_io: WebSocketInputOutput) -> Dict[str, Any]:
315
  """Execute Python code in a secure environment with WebSocket IO."""
316
  start_time = time.time()
 
317
 
318
  # Input validation
319
  if not code or not code.strip():
320
  error_msg = 'No code provided'
321
  await websocket_io.write(f"Error: {error_msg}\n")
 
322
  return {
323
  'success': False,
324
  'output': '',
@@ -341,6 +338,7 @@ class SecurePythonExecutor:
341
  if not restricted_check['allowed']:
342
  error_msg = f"Security violation: Restricted module '{restricted_check['module']}' is not allowed"
343
  await websocket_io.write(f"Error: {error_msg}\n")
 
344
  return {
345
  'success': False,
346
  'output': '',
@@ -352,6 +350,7 @@ class SecurePythonExecutor:
352
  # Execute code with timeout
353
  result = await self.run_with_timeout(code, websocket_io) # Pass websocket_io
354
  execution_time = time.time() - start_time
 
355
 
356
  return {
357
  'success': result['success'],
@@ -374,12 +373,14 @@ class SecurePythonExecutor:
374
  if node.module and node.module.split('.')[0] in self.restricted_modules:
375
  return {'allowed': False, 'module': node.module}
376
  return {'allowed': True, 'module': None}
377
- except Exception:
 
378
  return {'allowed': True, 'module': None} # If parsing fails, let execution handle it
379
 
380
  async def run_with_timeout(self, code: str, websocket_io: WebSocketInputOutput) -> Dict[str, Any]:
381
  """Run code with timeout protection, interacting via WebSocketIO."""
382
  result = {'success': False, 'output': '', 'error': ''}
 
383
 
384
  # Store original stdin/stdout/stderr
385
  original_stdin = sys.stdin
@@ -396,7 +397,8 @@ class SecurePythonExecutor:
396
  sys.stdin = websocket_io
397
  sys.stdout = websocket_io
398
  sys.stderr = websocket_io
399
-
 
400
  # Create restricted execution environment
401
  restricted_globals = {
402
  '__builtins__': self.safe_builtins,
@@ -404,21 +406,27 @@ class SecurePythonExecutor:
404
  '__doc__': None,
405
  }
406
 
 
407
  # Execute the code
408
  exec(code, restricted_globals)
 
409
  result['success'] = True
410
 
411
  except EOFError as e:
412
  # This happens if the WebSocket disconnects during input()
413
  result['error'] = f"Input stream closed: {str(e)}"
414
  result['success'] = False
 
415
  except Exception as e:
416
  error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
 
417
  # Write error to WebSocket and internal buffer
418
  try:
 
 
419
  asyncio.run(websocket_io.write(f"Error: {error_msg}\n"))
420
  except Exception as write_e:
421
- print(f"Failed to write error to WebSocket: {write_e}")
422
  websocket_io.error_buffer.write(error_msg)
423
  result['success'] = False
424
  finally:
@@ -427,16 +435,19 @@ class SecurePythonExecutor:
427
  sys.stdout = original_stdout
428
  sys.stderr = original_stderr
429
  execution_finished_event.set() # Signal completion
 
430
 
431
  # Run the execution in a separate thread to allow for timeout
432
- thread = threading.Thread(target=target)
433
  thread.daemon = True # Allow the program to exit even if thread is running
434
  thread.start()
 
435
 
436
  # Wait for the thread to finish or timeout
437
  thread.join(timeout=self.timeout)
438
 
439
  if thread.is_alive():
 
440
  result['error'] = f"Code execution timed out after {self.timeout} seconds"
441
  result['success'] = False
442
  # Attempt to send timeout message to client
@@ -445,7 +456,7 @@ class SecurePythonExecutor:
445
  except Exception:
446
  pass # Ignore if websocket is already closed
447
  else:
448
- # Execution finished within timeout
449
  result['output'] = websocket_io.output_buffer.getvalue()
450
  if websocket_io.error_buffer.getvalue():
451
  result['error'] = websocket_io.error_buffer.getvalue()
@@ -455,12 +466,16 @@ class SecurePythonExecutor:
455
 
456
  # Send a final message to the client indicating execution is complete
457
  try:
 
458
  await websocket_io.send_text(json.dumps({"type": "execution_complete", "result": result}))
 
459
  except WebSocketDisconnect:
 
460
  pass # Ignore if websocket is already closed
461
  except Exception as e:
462
- print(f"Error sending execution_complete message: {e}")
463
 
 
464
  return result
465
 
466
 
@@ -479,6 +494,7 @@ app = FastAPI(
479
  async def get_index():
480
  """Serves the interactive frontend."""
481
  # This assumes index.html is in the same directory as app.py
 
482
  with open("index.html", "r") as f:
483
  return HTMLResponse(content=f.read())
484
 
@@ -493,6 +509,7 @@ async def websocket_endpoint(websocket: WebSocket):
493
  while True:
494
  message = await websocket.receive_text()
495
  data = json.loads(message)
 
496
 
497
  if data["type"] == "code":
498
  code = data["content"]
@@ -506,6 +523,7 @@ async def websocket_endpoint(websocket: WebSocket):
506
  elif data["type"] == "ping":
507
  # Respond to pings to keep connection alive
508
  await websocket.send_text(json.dumps({"type": "pong"}))
 
509
  else:
510
  await websocket_io.write(f"Unknown message type: {data['type']}\n")
511
 
@@ -548,5 +566,4 @@ if __name__ == "__main__":
548
  )
549
  except Exception as e:
550
  print(f"Failed to start server: {e}")
551
- sys.exit(1)
552
-
 
35
  self.output_buffer = io.StringIO()
36
  self.error_buffer = io.StringIO()
37
  self.closed = False
38
+ print("DEBUG: WebSocketInputOutput initialized.")
39
 
40
  async def write(self, s: str):
41
  """Writes to stdout/stderr and sends to WebSocket."""
 
43
  try:
44
  # Send output in chunks or lines to prevent large messages
45
  await self.websocket.send_text(json.dumps({"type": "output", "content": s}))
46
+ print(f"DEBUG: Sent output chunk: {s.strip()[:100]}...")
47
  except WebSocketDisconnect:
48
  self.closed = True
49
+ print("ERROR: WebSocket disconnected during write.")
50
  except Exception as e:
51
+ print(f"ERROR: Error sending output over WebSocket: {e}")
52
  self.closed = True
53
 
54
  def flush(self):
 
58
  async def readline(self) -> str:
59
  """Reads a line from stdin, waiting for input from WebSocket."""
60
  try:
61
+ print("DEBUG: Sending input_request to client.")
62
  await self.websocket.send_text(json.dumps({"type": "input_request"}))
63
  line = await self.input_queue.get() # Wait for input from client
64
+ print(f"DEBUG: Received input from queue: {line.strip()[:50]}...")
65
  return line
66
  except WebSocketDisconnect:
67
  self.closed = True
68
+ print("ERROR: WebSocket disconnected during readline.")
69
  raise EOFError("Input stream closed due to WebSocket disconnect")
70
  except Exception as e:
71
+ print(f"ERROR: Error requesting input over WebSocket: {e}")
72
  self.closed = True
73
  raise EOFError(f"Input stream error: {e}")
74
 
75
  # For compatibility with sys.stdin, which expects a file-like object
76
  def _get_input_line(self):
77
  """Synchronous wrapper for readline for use with exec."""
 
 
 
 
 
 
 
 
78
  try:
79
+ print("DEBUG: Synchronous input wrapper called.")
80
  return asyncio.run(self.readline())
81
  except Exception as e:
82
+ print(f"ERROR: Synchronous input wrapper error: {e}")
83
  raise
84
 
85
  def read(self, n=-1):
86
  """Reads n characters. For simplicity, we'll treat it as readline."""
87
+ print(f"DEBUG: Read called with n={n}. Delegating to readline.")
88
  return self._get_input_line() # Or implement more complex buffering
89
 
90
  def __getattr__(self, name):
91
  """Delegate other attributes if needed, or raise AttributeError."""
 
 
92
  raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
93
 
94
 
 
309
  async def execute_code(self, code: str, websocket_io: WebSocketInputOutput) -> Dict[str, Any]:
310
  """Execute Python code in a secure environment with WebSocket IO."""
311
  start_time = time.time()
312
+ print(f"DEBUG: execute_code called for client: {websocket_io.websocket.client}")
313
 
314
  # Input validation
315
  if not code or not code.strip():
316
  error_msg = 'No code provided'
317
  await websocket_io.write(f"Error: {error_msg}\n")
318
+ print(f"DEBUG: No code provided for execution. Client: {websocket_io.websocket.client}")
319
  return {
320
  'success': False,
321
  'output': '',
 
338
  if not restricted_check['allowed']:
339
  error_msg = f"Security violation: Restricted module '{restricted_check['module']}' is not allowed"
340
  await websocket_io.write(f"Error: {error_msg}\n")
341
+ print(f"DEBUG: Restricted import detected: {restricted_check['module']}. Client: {websocket_io.websocket.client}")
342
  return {
343
  'success': False,
344
  'output': '',
 
350
  # Execute code with timeout
351
  result = await self.run_with_timeout(code, websocket_io) # Pass websocket_io
352
  execution_time = time.time() - start_time
353
+ print(f"DEBUG: Code execution finished. Success: {result['success']}, Client: {websocket_io.websocket.client}")
354
 
355
  return {
356
  'success': result['success'],
 
373
  if node.module and node.module.split('.')[0] in self.restricted_modules:
374
  return {'allowed': False, 'module': node.module}
375
  return {'allowed': True, 'module': None}
376
+ except Exception as e:
377
+ print(f"WARNING: AST parsing failed for restricted import check: {e}")
378
  return {'allowed': True, 'module': None} # If parsing fails, let execution handle it
379
 
380
  async def run_with_timeout(self, code: str, websocket_io: WebSocketInputOutput) -> Dict[str, Any]:
381
  """Run code with timeout protection, interacting via WebSocketIO."""
382
  result = {'success': False, 'output': '', 'error': ''}
383
+ print(f"DEBUG: run_with_timeout called. Client: {websocket_io.websocket.client}")
384
 
385
  # Store original stdin/stdout/stderr
386
  original_stdin = sys.stdin
 
397
  sys.stdin = websocket_io
398
  sys.stdout = websocket_io
399
  sys.stderr = websocket_io
400
+ print(f"DEBUG: Thread '{threading.current_thread().name}' started. IO redirected.")
401
+
402
  # Create restricted execution environment
403
  restricted_globals = {
404
  '__builtins__': self.safe_builtins,
 
406
  '__doc__': None,
407
  }
408
 
409
+ print(f"DEBUG: Entering exec context for code (thread): {code[:100]}...") # NEW
410
  # Execute the code
411
  exec(code, restricted_globals)
412
+ print("DEBUG: Code execution completed successfully in thread.") # NEW
413
  result['success'] = True
414
 
415
  except EOFError as e:
416
  # This happens if the WebSocket disconnects during input()
417
  result['error'] = f"Input stream closed: {str(e)}"
418
  result['success'] = False
419
+ print(f"ERROR: EOFError during execution in thread: {e}") # NEW
420
  except Exception as e:
421
  error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
422
+ print(f"ERROR: Unhandled exception during execution in thread: {error_msg}") # NEW
423
  # Write error to WebSocket and internal buffer
424
  try:
425
+ # Use asyncio.run for writing within a synchronous thread, if necessary
426
+ # This is generally safe if it's a new event loop per call or in a separate thread.
427
  asyncio.run(websocket_io.write(f"Error: {error_msg}\n"))
428
  except Exception as write_e:
429
+ print(f"ERROR: Failed to write error to WebSocket from thread: {write_e}")
430
  websocket_io.error_buffer.write(error_msg)
431
  result['success'] = False
432
  finally:
 
435
  sys.stdout = original_stdout
436
  sys.stderr = original_stderr
437
  execution_finished_event.set() # Signal completion
438
+ print(f"DEBUG: Thread '{threading.current_thread().name}' finished. IO restored.")
439
 
440
  # Run the execution in a separate thread to allow for timeout
441
+ thread = threading.Thread(target=target, name=f"CodeExecutorThread-{id(websocket_io)}")
442
  thread.daemon = True # Allow the program to exit even if thread is running
443
  thread.start()
444
+ print(f"DEBUG: Code execution thread '{thread.name}' started. Waiting for join.")
445
 
446
  # Wait for the thread to finish or timeout
447
  thread.join(timeout=self.timeout)
448
 
449
  if thread.is_alive():
450
+ print(f"DEBUG: Thread '{thread.name}' timed out after {self.timeout} seconds.") # NEW
451
  result['error'] = f"Code execution timed out after {self.timeout} seconds"
452
  result['success'] = False
453
  # Attempt to send timeout message to client
 
456
  except Exception:
457
  pass # Ignore if websocket is already closed
458
  else:
459
+ print(f"DEBUG: Thread '{thread.name}' finished within timeout.") # NEW
460
  result['output'] = websocket_io.output_buffer.getvalue()
461
  if websocket_io.error_buffer.getvalue():
462
  result['error'] = websocket_io.error_buffer.getvalue()
 
466
 
467
  # Send a final message to the client indicating execution is complete
468
  try:
469
+ print(f"DEBUG: Attempting to send execution_complete message (success={result['success']}).") # NEW
470
  await websocket_io.send_text(json.dumps({"type": "execution_complete", "result": result}))
471
+ print("DEBUG: execution_complete message sent.")
472
  except WebSocketDisconnect:
473
+ print("DEBUG: WebSocketDisconnect when sending execution_complete.") # NEW
474
  pass # Ignore if websocket is already closed
475
  except Exception as e:
476
+ print(f"ERROR: Error sending execution_complete message: {e}") # Warning if this fails
477
 
478
+ print(f"DEBUG: run_with_timeout returning result for client: {websocket_io.websocket.client}")
479
  return result
480
 
481
 
 
494
  async def get_index():
495
  """Serves the interactive frontend."""
496
  # This assumes index.html is in the same directory as app.py
497
+ [cite_start]# [cite: 9] Explicitly copy index.html to /app/index.html to avoid any ambiguity with '.'
498
  with open("index.html", "r") as f:
499
  return HTMLResponse(content=f.read())
500
 
 
509
  while True:
510
  message = await websocket.receive_text()
511
  data = json.loads(message)
512
+ print(f"DEBUG: Received message from {websocket.client}: type={data['type']}")
513
 
514
  if data["type"] == "code":
515
  code = data["content"]
 
523
  elif data["type"] == "ping":
524
  # Respond to pings to keep connection alive
525
  await websocket.send_text(json.dumps({"type": "pong"}))
526
+ print(f"DEBUG: Sent pong to {websocket.client}")
527
  else:
528
  await websocket_io.write(f"Unknown message type: {data['type']}\n")
529
 
 
566
  )
567
  except Exception as e:
568
  print(f"Failed to start server: {e}")
569
+ sys.exit(1)