none
Error 405. Metodo POST no permitido al pasar nombre de archivo. RRS feed

  • Pregunta

  • Buenos días.

    Necesito crear una URL para poder compartir un fichero (algo como: https://midominio/docrepo/documento/archivo.pdf) de modo que:

    - 1) Se pueda acceder al archivo en un navegador, pegando la URL, y el navegador pida usuario y contraseña

    - 2) Se pueda hacer un POST contra la misma URL con autentificación básica y recuperar el fichero. Por imposición del cliente debe ser un POST

    Para ello he creado una Web API "docrepo" en IIS con un controlador "documento" que recibe al nombre del archivo en el metodo POST. A su vez he creado un usuario en Windows y a la carpeta donde van los documentos que esta en inetpub\wwwroot\docrepo\documento\ le he dado los permisos necesarios para que IIS solicite el usuario al acceder por el navegador.

    En resumen, la intención es que puedas pegar la URL "https://midominio/docrepo/documento/archivo.pdf" en el navegador y acceder a "archivo.pdf" o hacer un POST a la misma URL en el sentido de llamar a la Web API docrepo solicitando el recurso "documento" de nombre "archivo.pdf".

    Todo funciona perfectamente excepto que al utilizar el verbo POST me suelta un error 405 de metodo no permitido. 

    He implementado GET y POST con el mismo código. Funciona a la perfección con GET, pero al utilizar POST y pasar un nombre de archivo válido y existente, obtengo error 405. Si le paso un nombre inventado, de un fichero que no existe entonces si funciona (me devuelve not found y he verificado que el metodo POST recibe la llamada).

    Sospecho que el método POST no es invocado al pasar como parámetro un nombre de archivo que existe en el servidor. Algo en el IIS captura la llamada y la Web API no llega a recibir la solicitud:

    Solicito recuperar el fichero "ubvlvbtzPrueba.pdf" con un GET. Todo OK (pegaria las imágenes capturadas del fiddler pero esto no me deja pegar imágenes.:

    GET /docrepo/documento/Prueba.pdf

     

    HTTP/1.1 200 OK
    Content-Type: application/pdf
    Last-Modified: Tue, 26 Sep 2017 12:40:23 GMT
    Accept-Ranges: bytes
    ETag: "9eda5b9fc436d31:0"
    Server: Microsoft-IIS/8.5
    X-Powered-By: ASP.NET
    Date: Mon, 09 Oct 2017 07:49:34 GMT
    Content-Length: 88127

    La misma llamada sobre el método POST, error 405. Ademas la respuesta dice que solo se permiten GET, HEAD, OPTIONS y TRACE:

    POST /docrepo/documento/Prueba.pdf

    HTTP/1.1 405 Method Not Allowed
    Allow: GET, HEAD, OPTIONS, TRACE
    Content-Type: text/html
    Server: Microsoft-IIS/8.5
    X-Powered-By: ASP.NET
    Date: Mon, 09 Oct 2017 07:54:23 GMT
    Content-Length: 1346
    Accept-Ranges: none

    Nueva invocación POST pero solicitando un fichero que no existe (quito una "a"). Funciona OK (devuelve not found que es lo que tiene que devolver si el fichero no existe):

    POST /docrepo/documento/Prueb.pdf

    HTTP/1.1 404 Not Found
    Cache-Control: no-cache
    Pragma: no-cache
    Expires: -1
    Server: Microsoft-IIS/8.5
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Mon, 09 Oct 2017 07:54:58 GMT
    Content-Length: 0

    Para verificar si el servicio REST es invocado, quito el token de autentificación en la llamada. Nuevo POST sin autentificación, pidiendo un fichero inventado me devuelve correctamente "unauthoriced":

    POST /docrepo/documento/Prueb.pdf

    HTTP/1.1 401 Unauthorized
    Cache-Control: no-cache
    Pragma: no-cache
    Expires: -1
    Server: Microsoft-IIS/8.5
    X-AspNet-Version: 4.0.30319
    WWW-Authenticate: Negotiate
    WWW-Authenticate: NTLM
    X-Powered-By: ASP.NET
    Date: Mon, 09 Oct 2017 07:57:34 GMT
    Content-Length: 0
    Proxy-Support: Session-Based-Authentication

    Y ahora de nuevo con el token de autenticación quitado pero solicitando el fichero en cuestión. Error 405 de nuevo, así que no llega ni a autentificar la llamada:

    POST /docrepo/documento/Prueba.pdf

    HTTP/1.1 405 Method Not Allowed
    Allow: GET, HEAD, OPTIONS, TRACE
    Content-Type: text/html
    Server: Microsoft-IIS/8.5
    X-Powered-By: ASP.NET
    Date: Mon, 09 Oct 2017 07:59:11 GMT
    Content-Length: 1346
    Accept-Ranges: none


    Previamente he probado también a hacer que el POST no hiciera nada mas que devolver el string que se le pasa y funciona al pesarle cualquier cadena siempre que no sea una ruta de fichero válida.

    El código de la WEB API es este:

     public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Configuración y servicios de API web
                var cors = new EnableCorsAttribute("*", "*", "*");
                config.EnableCors(cors);
                // Rutas de API web
                config.MapHttpAttributeRoutes();
    
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "{controller}/{nombrefichero}",
                    defaults: new { nombrefichero = RouteParameter.Optional }
                );
            }
        }

     [BasicAuthentication]
        public class DocumentoController : ApiController
        {
            public IHttpActionResult Get(string nombrefichero)
            {
                var serverPath = Path.Combine(Path.Combine(HostingEnvironment.MapPath("/"), "documento"),nombrefichero); 
                var fileInfo = new FileInfo(serverPath);
    
                return !fileInfo.Exists
                    ? (IHttpActionResult)NotFound()
                    : new FileResult(fileInfo.FullName);
    
               
            }
            public IHttpActionResult Post(string nombrefichero)
            {
                var serverPath = Path.Combine(Path.Combine(HostingEnvironment.MapPath("/"), "documento"), nombrefichero);
                var fileInfo = new FileInfo(serverPath);
    
                return !fileInfo.Exists
                    ? (IHttpActionResult)NotFound()
                    : new FileResult(fileInfo.FullName);
            }
        }
    class FileResult : IHttpActionResult
        {
            private readonly string _filePath;
            private readonly string _contentType;
    
            public FileResult(string filePath, string contentType = null)
            {
                if (filePath == null) throw new ArgumentNullException("filePath");
    
                _filePath = filePath;
                _contentType = contentType;
            }
    
            public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new StreamContent(File.OpenRead(_filePath))
                };
    
                var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
                response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
    
                return Task.FromResult(response);
            }
        }
    <appSettings>
        <add key="webpages:Version" value="3.0.0.0" />
        <add key="webpages:Enabled" value="false" />
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
      </appSettings>
      <system.web>
        <compilation targetFramework="4.7" />
        <httpRuntime targetFramework="4.7" />
      </system.web>
      <system.webServer>
    	<security>
          <requestFiltering>
            <verbs>
              <add verb="GET" allowed="true" />
              <add verb="POST" allowed="true" />
              <add verb="PUT" allowed="true" />
              <add verb="DELETE" allowed="true" />
              <add verb="OPTIONS" allowed="true" />
            </verbs>
          </requestFiltering>
        </security>
    	<modules runAllManagedModulesForAllRequests="true"/> 
      </system.webServer>

    Llevo varios días con esto y ya estoy desesperado. A ver si alguien puede descubrir que esta pasando.

    ¿Donde va a parar la llamada cuando se invoca al POST con un nombre de fichero válido?

    ¿Por que la respuesta en ese caso dice que solo se permiten GET, HEAD, OPTIONS y TRACE? ¿De donde sale esto?

    Agradeceré cualquier ayuda para solucionar el problema. Y si alguien tiene una idea mejor de como hacer esto estoy abierto a todo, no se si el camino de hacer una web API es acertado o si hay otro modo mas sencilllo.

    Muchas gracias.

    lunes, 9 de octubre de 2017 7:59

Todas las respuestas

  • El problema es que cuando la url es "....loquesea.extension", si el ".extension" corresponde a una extension conocida, entonces lo procesa directamente IIS en ligar de pasárselo a tu aplicación MVC. Y claro, cuando IIS procesa el POST a ese archivo, contesta que ese archivo no admite el verbo POST.

    El remedio que he encontrado es el de no incluir el punto en la url, por ejemplo, poniendo ""POST /docrepo/documento/Prueba-pdf", y luego en el método del controlador sustituir el guión por un punto antes de buscar el fichero. Otro remedio es codificar el nombre del fichero en Base64 y uego decodificarlo en el controlador.

    OJO si estás permitiendo que te pidan ficheros a través de la URL, asegúrate de que tu código filtra los nombres para que tu aplicación no sea vulnerable ante ataques de "Path Traversal".

    lunes, 9 de octubre de 2017 13:58
  • Gracias por la respuesta. Me ha confirmado mas o menos lo que imaginaba. 

    La solución al final, ha sido renombrar la carpeta "documento" a "document" para que no coincida la ruta del archivo con el metodo de la Web API. Con eso el POST ya funciona. Luego para que el documento sea recuperable pegando la URL en el navegador, hago que el metodo GET redirija a una nueva URL cambiando el "documento" por "document". Entonces es IIS el que toma el control gestionando según se configure el acceso y solicitando usuario y contraseña.

    EL resultado final es que ya puedo pasar "https://midominio/docrepo/documento/nombrefichero" y el usuario final llegar al documento vía navegador (es redirigido) o vía solicitud POST

     public class DocumentoController : ApiController
        {
            public IHttpActionResult Get(string nombrefichero)
            {
                try
                {
                    string url = @"https://midominio/docrepo/document/" + nombrefichero;
                    System.Uri uri = new System.Uri(url);
                    return Redirect(uri);
                }
                catch (Exception ex)
                {
                    return (IHttpActionResult)Content(HttpStatusCode.BadRequest, ex.Message);
                }
            }
            [BasicAuthentication]
            public IHttpActionResult Post(string nombrefichero)
            {
                var serverPath = Path.Combine(Path.Combine(Path.Combine(HostingEnvironment.MapPath("/"), "docrepo"), "document"), nombrefichero);
                var fileInfo = new FileInfo(serverPath);
    
                return !fileInfo.Exists
                   ? (IHttpActionResult)Content(HttpStatusCode.NotFound, "No encontrado " + nombrefichero)
                    : new FileResult(fileInfo.FullName);
            }
        }
    Un saludo
     
    jueves, 19 de octubre de 2017 15:11