Jo Shields 181b81b4a4 Imported Upstream version 3.12.0
Former-commit-id: cf92446697332992ec36726e78eb8703e1f259d7
2015-01-13 10:44:36 +00:00

432 lines
9.8 KiB
C#

//
// ContentDispositionHeaderValue.cs
//
// Authors:
// Marek Safar <marek.safar@gmail.com>
//
// Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections.Generic;
using System.Text;
using System.Globalization;
namespace System.Net.Http.Headers
{
public class ContentDispositionHeaderValue : ICloneable
{
string dispositionType;
List<NameValueHeaderValue> parameters;
private ContentDispositionHeaderValue ()
{
}
public ContentDispositionHeaderValue (string dispositionType)
{
DispositionType = dispositionType;
}
protected ContentDispositionHeaderValue (ContentDispositionHeaderValue source)
{
if (source == null)
throw new ArgumentNullException ("source");
dispositionType = source.dispositionType;
if (source.parameters != null) {
foreach (var item in source.parameters)
Parameters.Add (new NameValueHeaderValue (item));
}
}
public DateTimeOffset? CreationDate {
get {
return GetDateValue ("creation-date");
}
set {
SetDateValue ("creation-date", value);
}
}
public string DispositionType {
get {
return dispositionType;
}
set {
Parser.Token.Check (value);
dispositionType = value;
}
}
public string FileName {
get {
var value = FindParameter ("filename");
if (value == null)
return null;
return DecodeValue (value, false);
}
set {
if (value != null)
value = EncodeBase64Value (value);
SetValue ("filename", value);
}
}
public string FileNameStar {
get {
var value = FindParameter ("filename*");
if (value == null)
return null;
return DecodeValue (value, true);
}
set {
if (value != null)
value = EncodeRFC5987 (value);
SetValue ("filename*", value);
}
}
public DateTimeOffset? ModificationDate {
get {
return GetDateValue ("modification-date");
}
set {
SetDateValue ("modification-date", value);
}
}
public string Name {
get {
var value = FindParameter ("name");
if (value == null)
return null;
return DecodeValue (value, false);
}
set {
if (value != null)
value = EncodeBase64Value (value);
SetValue ("name", value);
}
}
public ICollection<NameValueHeaderValue> Parameters {
get {
return parameters ?? (parameters = new List<NameValueHeaderValue> ());
}
}
public DateTimeOffset? ReadDate {
get {
return GetDateValue ("read-date");
}
set {
SetDateValue ("read-date", value);
}
}
public long? Size {
get {
var found = FindParameter ("size");
long result;
if (Parser.Long.TryParse (found, out result))
return result;
return null;
}
set {
if (value == null) {
SetValue ("size", null);
return;
}
if (value < 0)
throw new ArgumentOutOfRangeException ("value");
SetValue ("size", value.Value.ToString (CultureInfo.InvariantCulture));
}
}
object ICloneable.Clone ()
{
return new ContentDispositionHeaderValue (this);
}
public override bool Equals (object obj)
{
var source = obj as ContentDispositionHeaderValue;
return source != null &&
string.Equals (source.dispositionType, dispositionType, StringComparison.OrdinalIgnoreCase) &&
source.parameters.SequenceEqual (parameters);
}
string FindParameter (string name)
{
if (parameters == null)
return null;
foreach (var entry in parameters) {
if (string.Equals (entry.Name, name, StringComparison.OrdinalIgnoreCase))
return entry.Value;
}
return null;
}
DateTimeOffset? GetDateValue (string name)
{
var value = FindParameter (name);
if (value == null || value == null)
return null;
if (value.Length < 3)
return null;
if (value[0] == '\"')
value = value.Substring (1, value.Length - 2);
DateTimeOffset offset;
if (Lexer.TryGetDateValue (value, out offset))
return offset;
return null;
}
static string EncodeBase64Value (string value)
{
bool quoted = value.Length > 1 && value [0] == '"' && value [value.Length - 1] == '"';
if (quoted)
value = value.Substring (1, value.Length - 2);
for (int i = 0; i < value.Length; ++i) {
var ch = value[i];
if (ch > 127) {
var encoding = Encoding.UTF8;
return string.Format ("\"=?{0}?B?{1}?=\"",
encoding.WebName, Convert.ToBase64String (encoding.GetBytes (value)));
}
}
if (quoted || !Lexer.IsValidToken (value))
return "\"" + value + "\"";
return value;
}
static string EncodeRFC5987 (string value)
{
var encoding = Encoding.UTF8;
StringBuilder sb = new StringBuilder (value.Length + 11);
sb.Append (encoding.WebName);
sb.Append ('\'');
sb.Append ('\'');
for (int i = 0; i < value.Length; ++i) {
var ch = value[i];
if (ch > 127) {
foreach (var b in encoding.GetBytes (new[] { ch })) {
sb.Append ('%');
sb.Append (b.ToString ("X2"));
}
continue;
}
if (!Lexer.IsValidCharacter (ch) || ch == '*' || ch == '?' || ch == '%') {
sb.Append (Uri.HexEscape (ch));
continue;
}
sb.Append (ch);
}
return sb.ToString ();
}
static string DecodeValue (string value, bool extendedNotation)
{
//
// A short (length <= 78 characters)
// parameter value containing only non-`tspecials' characters SHOULD be
// represented as a single `token'. A short parameter value containing
// only ASCII characters, but including `tspecials' characters, SHOULD
// be represented as `quoted-string'. Parameter values longer than 78
// characters, or which contain non-ASCII characters, MUST be encoded as
// specified in [RFC 2184].
//
if (value.Length < 2)
return value;
string[] sep;
Encoding encoding;
// Quoted string
if (value[0] == '\"') {
//
// Is Base64 encoded ?
// encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
//
sep = value.Split ('?');
if (sep.Length != 5 || sep[0] != "\"=" || sep[4] != "=\"" || (sep[2] != "B" && sep[2] != "b"))
return value;
try {
encoding = Encoding.GetEncoding (sep[1]);
return encoding.GetString (Convert.FromBase64String (sep[3]));
} catch {
return value;
}
}
if (!extendedNotation)
return value;
//
// RFC 5987: Charset/Language Encoding
//
sep = value.Split ('\'');
if (sep.Length != 3)
return null;
try {
encoding = Encoding.GetEncoding (sep[0]);
} catch {
return null;
}
// TODO: What to do with sep[1] language
value = sep[2];
int pct_encoded = value.IndexOf ('%');
if (pct_encoded < 0)
return value;
StringBuilder sb = new StringBuilder ();
byte[] buffer = null;
int buffer_pos = 0;
for (int i = 0; i < value.Length;) {
var ch = value[i];
if (ch == '%') {
var unescaped = ch;
ch = Uri.HexUnescape (value, ref i);
if (ch != unescaped) {
if (buffer == null)
buffer = new byte[value.Length - i + 1];
buffer[buffer_pos++] = (byte) ch;
continue;
}
} else {
++i;
}
if (buffer_pos != 0) {
sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
buffer_pos = 0;
}
sb.Append (ch);
}
if (buffer_pos != 0) {
sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
}
return sb.ToString ();
}
public override int GetHashCode ()
{
return dispositionType.ToLowerInvariant ().GetHashCode () ^
HashCodeCalculator.Calculate (parameters);
}
public static ContentDispositionHeaderValue Parse (string input)
{
ContentDispositionHeaderValue value;
if (TryParse (input, out value))
return value;
throw new FormatException (input);
}
void SetDateValue (string key, DateTimeOffset? value)
{
SetValue (key, value == null ? null : ("\"" + value.Value.ToString ("r", CultureInfo.InvariantCulture)) + "\"");
}
void SetValue (string key, string value)
{
if (parameters == null)
parameters = new List<NameValueHeaderValue> ();
parameters.SetValue (key, value);
}
public override string ToString ()
{
return dispositionType + CollectionExtensions.ToString (parameters);
}
public static bool TryParse (string input, out ContentDispositionHeaderValue parsedValue)
{
parsedValue = null;
var lexer = new Lexer (input);
var t = lexer.Scan ();
if (t.Kind != Token.Type.Token)
return false;
List<NameValueHeaderValue> parameters = null;
var type = lexer.GetStringValue (t);
t = lexer.Scan ();
switch (t.Kind) {
case Token.Type.SeparatorSemicolon:
if (!NameValueHeaderValue.TryParseParameters (lexer, out parameters, out t) || t != Token.Type.End)
return false;
break;
case Token.Type.End:
break;
default:
return false;
}
parsedValue = new ContentDispositionHeaderValue () {
dispositionType = type,
parameters = parameters
};
return true;
}
}
}