I have an odd issue regarding a route to download PDF files that have chinese characters in their filename
I supply the filename to download as part of the route like this
[Route("/api/v1/downloads/{path*}", Verbs = “GET”)]
The route returns an HttpResult populated by a FileInfo object and parameter attachment = true
Some files throw this exception
ServiceStack.ServiceStackHost - Error occured while Processing Request: ContentType not supported ‘application/pdf’
System.NotSupportedException: ContentType not supported ‘application/pdf’
at ServiceStack.Host.ContentTypes.d__26.MoveNext()
with an HTTP 400
while other PDF files with chinese characters in their name succeed with a HTTP 200
I test the route with a Razor UI we built into the service application (comparable to the swaggerui but with more features)
I do not register the application/pdf contenttype in the apphost (tried that, same result)
It seems that some files are handled differently within servicestack than others, yielding the exception shown
Btw, I had the same issue with image files that have chinese characters in its name so it is not a PDF specific issue
NOTE: the preview does not render the Accept header correctly, the last type is * / * (without the extra spaces) but I see only /
// Interfaces
public interface IDownloadResponse : IReturn<IHttpResult>, IReturn
{
}
// Commands
[Route("/api/v1/downloads/{path*}", Verbs = "GET")]
public class TestDownloadCommand : IDownloadResponse
{
public string Path { get; set; }
}
// Return types
public class DownloadResponse : HttpResult
{
public DownloadResponse(FileInfo fileToReturn)
: base(fileToReturn, true)
{
// Add some custom headers named DowloadFileName and DownloadFileSize
Headers.Add(CustomHttpHeaders.DownloadFileName, fileToReturn.Name);
Headers.Add(CustomHttpHeaders.DownloadFileSize, fileToReturn.Length.ToString());
}
public DownloadResponse(DownloadErrorType errorType, string message)
{
StatusCode = HttpStatusCode.NotFound;
StatusDescription = message;
Headers.Add(CustomHttpHeaders.DownloadErrorType, errorType.ToString());
}
}
// Service
public DownloadResponse Get(TestDownloadCommand command)
{
// Construct the full path from the provided partial path
var path = Path.Combine("c:\\data\\fileattachments", command.Path.Replace("/", "\\"));
return new DownloadResponse(new FileInfo(path));
}
The above service code is just the happy flow (it does not test for file existence, in that case we’d return a DownloadResponse with the error message instead of the FileInfo object)
This isn’t the happy path, the Response indicates an Exception was thrown. Can you debug or inspect the Error Handling callbacks to see what Exception was thrown?
Specified value has invalid Control characters.
Parameter name: value
at System.Net.WebHeaderCollection.CheckBadChars(String name, Boolean isHeaderValue)
at System.Net.WebHeaderCollection.SetInternal(String name, String value)
at ServiceStack.HttpResponseExtensionsInternal.d__7.MoveNext()
and then
ContentType not supported ‘application/pdf’
at ServiceStack.Host.ContentTypes.d__26.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.HttpResponseExtensionsInternal.d__14.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.HostContext.d__67.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.HttpResponseExtensionsInternal.d__8.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.HttpResponseExtensionsInternal.d__7.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at ServiceStack.HttpResponseExtensionsInternal.d__7.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.Host.Handlers.ServiceStackHandlerBase.d__13.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.Host.Handlers.ServiceStackHandlerBase.d__12.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.Host.RestHandler.d__14.MoveNext()
My guess is that some files we have contain odd characters in their name, as the first exception is thrown by a .NET framework method to set headers called from within ServiceStack.
Putting the filename that is in the FileInfo object in a header value throws this exception, I suppose the header that fails is the Content-Disposition header because it contains the filename and -size
Funny thing is that File.Exists(…) does not complain about these characters
The underlying Exception is from using invalid characters in your HTTP Headers. This is from .NET Framework WebHeaderCollection collection so the only solution would be to avoid using the illegal characters. The illegal character validation isn’t related to validation that File.Exists() uses.
The StackTrace only says that the error occurs when writing the custom HttpResult to the Response it doesn’t indicate that this error is with the Content-Disposition header, it could be any header.
Thanks for the reply
If the filename contains characters invalid to use in an HTTP header, how would I fix that without renaming these files?
I have to pass in a FileInfo object in the HttpResult’s ctor, which checks for file existence (looked that up in the HttpResult class code)
I agree the stacktrace does not show which HTTP header fails, but when I set the asAttachment attribute to false, I get no exception and a normal response without the Content-Disposition header. So I have a strong feeling it is that header that fails
Code from the ServiceStack repo:
public HttpResult(FileInfo fileResponse, string contentType = null, bool asAttachment = false)
: this(null, contentType ?? MimeTypes.GetMimeType(fileResponse.Name), HttpStatusCode.OK)
{
this.FileInfo = fileResponse ?? throw new ArgumentNullException(nameof(fileResponse));
this.LastModified = fileResponse.LastWriteTime;
this.AllowsPartialResponse = true;
if (!FileInfo.Exists)
throw HttpError.NotFound($"{FileInfo.Name} was not found");
if (!asAttachment) return;
var headerValue = $$"attachment; filename=\"{fileResponse.Name}\"; size={fileResponse.Length}; " +
$$"creation-date={fileResponse.CreationTimeUtc.ToString("R").Replace(",", "")}; " +
$$"modification-date={fileResponse.LastWriteTimeUtc.ToString("R").Replace(",", "")}; " +
$$"read-date={fileResponse.LastAccessTimeUtc.ToString("R").Replace(",", "")}";
this.Headers = new Dictionary<string, string>
{
{ HttpHeaders.ContentDisposition, headerValue },
};
}
If you don’t want to remove the invalid characters from the file name you can try encoding the file name, see this StackOverflow answer for different encoding approaches:
Going to leave the default behavior in HttpResult as using filename*=UTF-8''... has breaking behavior for some clients.