LinuxParty

Última Actualización: 26/09/2026

Este código fuente de Woof, contiene las modificaciones del artículo:

Woof: Compartir archivos/ficheros fácilmente en su LAN o red local, sin Samba, entre Linux, Mac, Windows, Android e iPhone

#!/usr/bin/env python3
################################################################
# Para trabajar con varios archivos, meterlo en un bucle...
#
# for i in {1..10} ; do echo $i ; woof -U ; done
# 
# o utilizar el script "multiples-woof.sh", abajo descrito.
###############################################################
# -*- encoding: utf-8 -*-
#
#  woof -- an ad-hoc single file webserver
#  Copyright (C) 2004-2009 Simon Budig  <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>
# 
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
# 
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
# 
#  A copy of the GNU General Public License is available at
#  https://www.fsf.org/licenses/gpl.txt, you can also write to the
#  Free Software  Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.

# Darwin support with the help from Mat Caughron, <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>
# Solaris support by Colin Marquardt, <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>
# FreeBSD support with the help from Andy Gimblett, <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>
# Cygwin support by Stefan Reichör <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>
# tarfile usage suggested by Morgan Lefieux <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>
# File upload support loosely based on code from Stephen English <Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.>

import sys, os, errno, socket, getopt, tempfile
import urllib.request, urllib.parse, http.server
import readline
import configparser
import shutil, tarfile, zipfile
import struct
from email.parser import BytesFeedParser
from email.policy import HTTP

maxdownloads = 1
TM = object
cpid = -1
compressed = 'gz'
upload = False

class EvilZipStreamWrapper(TM):
   def __init__ (self, victim):
      self.victim_fd = victim
      self.position = 0
      self.tells = []
      self.in_file_data = 0

   def tell (self):
      self.tells.append (self.position)
      return self.position

   def seek (self, offset, whence = 0):
      if offset != 0:
         if offset == self.tells[0] + 14:
            self.write (b"PK\007\010")
         elif offset == self.tells[1]:
            self.tells = []
            self.in_file_data = 0
         else:
            raise IOError ("unexpected seek for EvilZipStreamWrapper")

   def write (self, data):
      if self.in_file_data == 0:
         if data[:4] == zipfile.stringFileHeader:
            hdr = list (struct.unpack (zipfile.structFileHeader, data[:30]))
            hdr[3] |= (1 << 3)
            data = struct.pack (zipfile.structFileHeader, *hdr) + data[30:]
            self.in_file_data = 1
         elif data[:4] == zipfile.stringCentralDir:
            hdr = list (struct.unpack (zipfile.structCentralDir, data[:46]))
            hdr[5] |= (1 << 3)
            data = struct.pack (zipfile.structCentralDir, *hdr) + data[46:]

      self.position += len (data)
      self.victim_fd.write (data)

   def __getattr__ (self, name):
      return getattr (self.victim_fd, name)

def find_ip ():
   candidates = []
   for test_ip in ["192.0.2.0", "198.51.100.0", "203.0.113.0"]:
      s = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
      s.connect ((test_ip, 80))
      ip_addr = s.getsockname ()[0]
      s.close ()
      if ip_addr in candidates:
         return ip_addr
      candidates.append (ip_addr)
   return candidates[0]

class ForkingHTTPServer (http.server.HTTPServer):
   def process_request (self, request, client_address):
      self.finish_request (request, client_address)
      self.close_request (request)

class FileServHTTPRequestHandler (http.server.BaseHTTPRequestHandler):
   server_version = "Simons FileServer"
   protocol_version = "HTTP/1.0"
   filename = "."

   def log_request (self, code='-', size='-'):
      if code == 200:
         http.server.BaseHTTPRequestHandler.log_request (self, code, size)

   def parse_multipart_binary(self):
      content_length = int(self.headers.get('Content-Length', 0))
      if content_length <= 0:
         return None, None

      parser = BytesFeedParser(policy=HTTP)
      headers_raw = f"Content-Type: {self.headers.get('Content-Type')}\r\n\r\n".encode('iso-8859-1')
      parser.feed(headers_raw)

      remaining = content_length
      chunk_size = 65536
      received = 0

      while remaining > 0:
         chunk = self.rfile.read(min(remaining, chunk_size))
         if not chunk:
            break
         parser.feed(chunk)
         received += len(chunk)
         remaining -= len(chunk)

         # Barra de progreso numérica en la terminal
         print(f"\rRecibiendo: {received / (1024*1024):.2f} MB / {content_length / (1024*1024):.2f} MB ({int(received/content_length*100)}%)", end="", file=sys.stderr)

      print("", file=sys.stderr)
      msg = parser.close()

      if not msg.is_multipart():
         return None, None

      for part in msg.walk():
         if part.get_param('name', header='content-disposition') == 'upfile':
            filename = part.get_filename()
            payload = part.get_payload(decode=True)
            return filename, payload

      return None, None

   def do_POST (self):
      global maxdownloads, upload

      if not upload:
         self.send_error (501, "Unsupported method (POST)")
         return

      print ("\nSe pulsó enviar. Recibiendo archivo...", file=sys.stderr)

      upfilename, file_data = self.parse_multipart_binary()

      if not upfilename or file_data is None:
         self.send_error (403, "No upload provided")
         return

      if "\\" in upfilename:
         upfilename = upfilename.split ("\\")[-1]

      upfilename = os.path.basename (upfilename)

      destfile = None
      for suffix in ["", ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"]:
         destfilename = os.path.join (".", upfilename + suffix)
         try:
            destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
            break
         except OSError as e:
            if e.errno == errno.EEXIST:
               continue
            raise

      if not destfile:
         upfilename += "."
         destfile, destfilename = tempfile.mkstemp (prefix = upfilename, dir = ".")

      with os.fdopen (destfile, "wb") as f:
         f.write(file_data)

      print ("Descarga finalizada. Guardado en: %s -> %s\n" % (upfilename, destfilename), file=sys.stderr)

      txt = b"""<html>
                <head><title>Woof Upload</title></head>
                <body>
                  <h1>Woof Upload complete</h1>
                  <p>Thanks a lot!</p>
                  <center>
                  <p style="font-size:250%"><a href="/"> INICIO </a></p>
                  </center>
                </body>
              </html>"""
      self.send_response (200)
      self.send_header ("Content-Type", "text/html")
      self.send_header ("Content-Length", str (len (txt)))
      self.end_headers ()
      self.wfile.write (txt)

      maxdownloads -= 1
      return

   def do_GET (self):
      global maxdownloads, cpid, compressed, upload

      if upload:
         txt = b"""<html>
                   <head><title>Woof Upload</title></head>
                   <body>
                    <table border="1" cellpadding="1" cellspacing="1" style="height:100%; width:100%">
                     <tr><td><h1><span style="font-size:45px">Woof Upload</span></h1></td></tr>
                     <tr><td>
                     <form class="formatoReporte" name="upload" method="POST" enctype="multipart/form-data">
                       <p><input style="font-size:250%" type="file" name="upfile" /></p>
                       <p><input style="font-size:250%" type="submit" value="Enviar!" /></p>
                     </form>
                     </td></tr>
                     </table>
                   </body>
                 </html>"""
         self.send_response (200)
         self.send_header ("Content-Type", "text/html")
         self.send_header ("Content-Length", str (len (txt)))
         self.end_headers ()
         self.wfile.write (txt)
         return

      self.path = urllib.parse.quote (urllib.parse.unquote (self.path))
      location = "/" + urllib.parse.quote (os.path.basename (self.filename))
      if os.path.isdir (self.filename):
         if compressed == 'gz':
            location += ".tar.gz"
         elif compressed == 'bz2':
            location += ".tar.bz2"
         elif compressed == 'zip':
            location += ".zip"
         else:
            location += ".tar"

      if self.path != location:
         txt = ("""<html><head><title>302 Found</title></head>"""
                """<body>302 Found <a href="/%s">here</a>.</body></html>\n""" % location).encode ('ascii')
         self.send_response (302)
         self.send_header ("Location", location)
         self.send_header ("Content-Type", "text/html")
         self.send_header ("Content-Length", str (len (txt)))
         self.end_headers ()
         self.wfile.write (txt)
         return

      maxdownloads -= 1
      cpid = os.fork ()

      if cpid == 0:
         child = None
         type = None

         if os.path.isfile (self.filename):
            type = "file"
         elif os.path.isdir (self.filename):
            type = "dir"

         if not type:
            print ("can only serve files or directories. Aborting.", file=sys.stderr)
            sys.exit (1)

         self.send_response (200)
         self.send_header ("Content-Type", "application/octet-stream")
         self.send_header ("Content-Disposition", "attachment;filename=%s" % urllib.parse.quote (os.path.basename (self.filename)))
         if os.path.isfile (self.filename):
            self.send_header ("Content-Length", os.path.getsize (self.filename))
         self.end_headers ()

         try:
            if type == "file":
               datafile = open (self.filename, "rb")
               shutil.copyfileobj (datafile, self.wfile)
               datafile.close ()
            elif type == "dir":
               if compressed == 'zip':
                  ezfile = EvilZipStreamWrapper (self.wfile)
                  zfile = zipfile.ZipFile (ezfile, 'w', zipfile.ZIP_DEFLATED)
                  stripoff = os.path.dirname (self.filename) + os.sep

                  for root, dirs, files in os.walk (self.filename):
                     for f in files:
                        filename = os.path.join (root, f)
                        if filename[:len (stripoff)] != stripoff:
                           raise RuntimeError ("invalid filename assumptions, please report!")
                        zfile.write (filename, filename[len (stripoff):])
                  zfile.close ()
               else:
                  tfile = tarfile.open (mode=('w|' + compressed), fileobj=self.wfile)
                  tfile.add (self.filename, arcname=os.path.basename (self.filename))
                  tfile.close ()
         except Exception as e:
            print (e)
            print ("Connection broke. Aborting", file=sys.stderr)

def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
   global maxdownloads
   maxdownloads = maxdown
   FileServHTTPRequestHandler.filename = filename

   try:
      httpd = ForkingHTTPServer ((ip_addr, port), FileServHTTPRequestHandler)
   except socket.error:
      print ("cannot bind to IP address '%s' port %d" % (ip_addr, port), file=sys.stderr)
      sys.exit (1)

   if not ip_addr:
      ip_addr = find_ip ()
   if ip_addr:
      if filename:
         location = "http://%s:%s/%s" % (ip_addr, httpd.server_port,
                                         urllib.parse.quote (os.path.basename (filename)))
         if os.path.isdir (filename):
            if compressed == 'gz':
               location += ".tar.gz"
            elif compressed == 'bz2':
               location += ".tar.bz2"
            elif compressed == 'zip':
               location += ".zip"
            else:
               location += ".tar"
      else:
         location = "http://%s:%s/" % (ip_addr, httpd.server_port)

      print ("Now serving on %s" % location)

   while cpid != 0 and maxdownloads > 0:
      httpd.handle_request ()

def usage (defport, defmaxdown, errmsg = None):
   name = os.path.basename (sys.argv[0])
   print ("""    Usage: %s [-i <ip_addr>] [-p <port>] [-c <count>] <file>
           %s [-i <ip_addr>] [-p <port>] [-c <count>] [-z|-j|-Z|-u] <dir>
           %s [-i <ip_addr>] [-p <port>] [-c <count>] -s
           %s [-i <ip_addr>] [-p <port>] [-c <count>] -U

           %s <url>
   """ % (name, name, name, name, name), file=sys.stderr)

   if errmsg:
      print (errmsg, file=sys.stderr)
   sys.exit (1)

def woof_client (url):
   urlparts = urllib.parse.urlparse (url, "http")
   if urlparts[0] not in [ "http", "https" ] or urlparts[1] == '':
      return None

   fname = None
   f = urllib.request.urlopen (url)
   f_meta = f.info ()
   disp = f_meta["Content-Disposition"]

   if disp:
      disp = disp.split (";")

   if disp and disp[0].lower () == 'attachment':
      fname = [x[9:] for x in disp[1:] if x[:9].lower () == "filename="]
      fname = fname[0] if len(fname) else None

   if fname == None:
      url = f.geturl ()
      urlparts = urllib.parse.urlparse (url)
      fname = urlparts[2]

   if not fname:
      fname = "woof-out.bin"

   fname = os.path.basename (urllib.parse.unquote (fname))

   readline.set_startup_hook (lambda: readline.insert_text (fname))
   fname = input ("Enter target filename: ")
   readline.set_startup_hook (None)

   override = False
   destfile = None
   destfilename = os.path.join (".", fname)

   try:
      destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
   except OSError as e:
      if e.errno == errno.EEXIST:
         override = input ("File exists. Overwrite (y/n)? ").lower() in [ "y", "yes" ]
      else:
         raise

   if destfile == None:
      if override:
         destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT, 0o644)
      else:
         for suffix in [".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"]:
            destfilename = os.path.join (".", fname + suffix)
            try:
               destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
               break
            except OSError as e:
               if e.errno == errno.EEXIST:
                  continue
               raise

         if not destfile:
            destfile, destfilename = tempfile.mkstemp (prefix = fname + ".", dir = ".")
         print ("alternate filename is:", destfilename)

   print ("downloading file: %s -> %s" % (fname, destfilename))
   shutil.copyfileobj (f, os.fdopen (destfile, "wb"))
   return 1

def main ():
   global cpid, upload, compressed

   maxdown = 1
   port = 8080
   ip_addr = ''

   config = configparser.ConfigParser ()
   config.read (['/etc/woofrc', os.path.expanduser ('~/.woofrc')])

   if config.has_option ('main', 'port'):
      port = config.getint ('main', 'port')
   if config.has_option ('main', 'count'):
      maxdown = config.getint ('main', 'count')
   if config.has_option ('main', 'ip'):
      ip_addr = config.get ('main', 'ip')
   if config.has_option ('main', 'compressed'):
      formats = { 'gz': 'gz', 'true': 'gz', 'bz': 'bz2', 'bz2': 'bz2', 'zip': 'zip', 'off': '', 'false': '' }
      compressed = formats.get (config.get ('main', 'compressed'), 'gz')

   defaultport = port
   defaultmaxdown = maxdown

   try:
      options, filenames = getopt.getopt (sys.argv[1:], "hUszjZui:c:p:")
   except getopt.GetoptError as desc:
      usage (defaultport, defaultmaxdown, desc)

   for option, val in options:
      if option == '-c':
         try:
            maxdown = int (val)
            if maxdown <= 0: raise ValueError
         except ValueError:
            usage (defaultport, defaultmaxdown, "invalid download count: %r" % val)
      elif option == '-i':
         ip_addr = val
      elif option == '-p':
         try:
            port = int (val)
         except ValueError:
            usage (defaultport, defaultmaxdown, "invalid port number: %r" % val)
      elif option == '-s':
         filenames.append (__file__)
      elif option == '-h':
         usage (defaultport, defaultmaxdown)
      elif option == '-U':
         upload = True
      elif option == '-z':
         compressed = 'gz'
      elif option == '-j':
         compressed = 'bz2'
      elif option == '-Z':
         compressed = 'zip'
      elif option == '-u':
         compressed = ''

   if upload:
      if len (filenames) > 0:
         usage (defaultport, defaultmaxdown, "Conflicting usage: simultaneous up- and download not supported.")
      filename = None
   else:
      if len (filenames) == 1:
         if woof_client (filenames[0]) != None:
            sys.exit (0)
         filename = os.path.abspath (filenames[0])
      else:
         usage (defaultport, defaultmaxdown, "Can only serve single files/directories.")

      if not os.path.exists (filename):
         usage (defaultport, defaultmaxdown, "%s: No such file or directory" % filenames[0])

   serve_files (filename, maxdown, ip_addr, port)

   if cpid != 0:
      try:
         while 1:
            os.wait ()
      except OSError:
         pass

if __name__=='__main__':
   try:
      main ()
   except KeyboardInterrupt:
      print ()

Con el objetivo de poder meter "woof" en una iteración (un bucle) y poder sacar varios archivos del... por ejemplo móvil al portatil creamos este script, que lo puedes ejecutar desde consola o crear un acceso en el escritorio (recuerda hacerlo visible desde la consola, si quieres) para hacerte más fácil éste trabajo... si lo quieres mejorar tú... mejor.

#!/bin/bash
# Nombre del script: multiples-woof.sh
# Para enviar multiples archivos...
#
for i in {1..10} ; do echo $i ; woof -U ; done
exit

Escribir un comentario

Código de seguridad
Refescar



Redes:



   

 

Encuestas

Cuál es tu escritorio gráfico favorito en 2026

Suscribete / Newsletter

Suscribete a nuestras Newsletter y periódicamente recibirás un resumen de las noticias publicadas.

Donar a LinuxParty

 

Dona a LInuxParty

Donar 10

Donar 20

 

 

Tutorial de Linux

Formulario de acceso