1/* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19#import "GRPCRequestHeaders.h" 20 21#import <Foundation/Foundation.h> 22 23#import "NSDictionary+GRPC.h" 24 25// Used by the setter. 26static void CheckIsNonNilASCII(NSString *name, NSString *value) { 27 if (!value) { 28 [NSException raise:NSInvalidArgumentException format:@"%@ cannot be nil", name]; 29 } 30 if (![value canBeConvertedToEncoding:NSASCIIStringEncoding]) { 31 [NSException raise:NSInvalidArgumentException 32 format:@"%@ %@ contains non-ASCII characters", name, value]; 33 } 34} 35 36// Precondition: key isn't nil. 37static void CheckKeyValuePairIsValid(NSString *key, id value) { 38 if ([key hasSuffix:@"-bin"]) { 39 if (![value isKindOfClass:NSData.class]) { 40 [NSException raise:NSInvalidArgumentException 41 format: 42 @"Expected NSData value for header %@ ending in \"-bin\", " 43 @"instead got %@", 44 key, value]; 45 } 46 } else { 47 if (![value isKindOfClass:NSString.class]) { 48 [NSException raise:NSInvalidArgumentException 49 format: 50 @"Expected NSString value for header %@ not ending in \"-bin\", " 51 @"instead got %@", 52 key, value]; 53 } 54 CheckIsNonNilASCII(@"Text header value", (NSString *)value); 55 } 56} 57 58@implementation GRPCRequestHeaders { 59 __weak GRPCCall *_call; 60 // The NSMutableDictionary superclass doesn't hold any storage (so that people can implement their 61 // own in subclasses). As that's not the reason we're subclassing, we just delegate storage to the 62 // default NSMutableDictionary subclass returned by the cluster (e.g. __NSDictionaryM on iOS 9). 63 NSMutableDictionary *_delegate; 64} 65 66- (instancetype)init { 67 return [self initWithCall:nil]; 68} 69 70- (instancetype)initWithCapacity:(NSUInteger)numItems { 71 return [self init]; 72} 73 74- (instancetype)initWithCoder:(NSCoder *)aDecoder { 75 return [self init]; 76} 77 78- (instancetype)initWithCall:(GRPCCall *)call { 79 return [self initWithCall:call storage:[NSMutableDictionary dictionary]]; 80} 81 82// Designated initializer 83- (instancetype)initWithCall:(GRPCCall *)call storage:(NSMutableDictionary *)storage { 84 // TODO(jcanizales): Throw if call or storage are nil. 85 if ((self = [super init])) { 86 _call = call; 87 _delegate = storage; 88 } 89 return self; 90} 91 92- (instancetype)initWithObjects:(const id _Nonnull __unsafe_unretained *)objects 93 forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys 94 count:(NSUInteger)cnt { 95 return [self init]; 96} 97 98- (void)checkCallIsNotStarted { 99 if (_call.state != GRXWriterStateNotStarted) { 100 [NSException raise:@"Invalid modification" 101 format:@"Cannot modify request headers after call is started"]; 102 } 103} 104 105- (id)objectForKey:(NSString *)key { 106 return _delegate[key.lowercaseString]; 107} 108 109- (void)setObject:(id)obj forKey:(NSString *)key { 110 CheckIsNonNilASCII(@"Header name", key); 111 key = key.lowercaseString; 112 CheckKeyValuePairIsValid(key, obj); 113 _delegate[key] = obj; 114} 115 116- (void)removeObjectForKey:(NSString *)key { 117 [self checkCallIsNotStarted]; 118 [_delegate removeObjectForKey:key.lowercaseString]; 119} 120 121- (NSUInteger)count { 122 return _delegate.count; 123} 124 125- (NSEnumerator *_Nonnull)keyEnumerator { 126 return [_delegate keyEnumerator]; 127} 128 129@end 130