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 25static void CheckIsNilOrString(NSString *name, id value) { 26 if (value && ![value isKindOfClass:[NSString class]]) { 27 [NSException raise:NSInvalidArgumentException format:@"%@ must be an NSString", name]; 28 } 29} 30 31// Used by the setter. 32static void CheckIsNonNilASCIIString(NSString *name, id value) { 33 if (!value) { 34 [NSException raise:NSInvalidArgumentException format:@"%@ cannot be nil", name]; 35 } 36 CheckIsNilOrString(name, value); 37 if (![value canBeConvertedToEncoding:NSASCIIStringEncoding]) { 38 [NSException raise:NSInvalidArgumentException 39 format:@"%@ %@ contains non-ASCII characters", name, value]; 40 } 41} 42 43// Precondition: key isn't nil. 44static void CheckKeyValuePairIsValid(NSString *key, id value) { 45 if ([key hasSuffix:@"-bin"]) { 46 if (![value isKindOfClass:[NSData class]]) { 47 [NSException raise:NSInvalidArgumentException 48 format:@"Expected NSData value for header %@ ending in \"-bin\", " 49 @"instead got %@", 50 key, value]; 51 } 52 } else { 53 if (![value isKindOfClass:[NSString class]]) { 54 [NSException raise:NSInvalidArgumentException 55 format:@"Expected NSString value for header %@ not ending in \"-bin\", " 56 @"instead got %@", 57 key, value]; 58 } 59 CheckIsNonNilASCIIString(@"Text header value", value); 60 } 61} 62 63@implementation GRPCRequestHeaders { 64 __weak GRPCCall *_call; 65 // The NSMutableDictionary superclass doesn't hold any storage (so that people can implement their 66 // own in subclasses). As that's not the reason we're subclassing, we just delegate storage to the 67 // default NSMutableDictionary subclass returned by the cluster (e.g. __NSDictionaryM on iOS 9). 68 NSMutableDictionary *_delegate; 69} 70 71- (instancetype)init { 72 return [self initWithCall:nil]; 73} 74 75- (instancetype)initWithCapacity:(NSUInteger)numItems { 76 return [self init]; 77} 78 79- (instancetype)initWithCoder:(NSCoder *)aDecoder { 80 return [self init]; 81} 82 83- (instancetype)initWithCall:(GRPCCall *)call { 84 return [self initWithCall:call storage:[NSMutableDictionary dictionary]]; 85} 86 87// Designated initializer 88- (instancetype)initWithCall:(GRPCCall *)call storage:(NSMutableDictionary *)storage { 89 // TODO(jcanizales): Throw if call or storage are nil. 90 if ((self = [super init])) { 91 _call = call; 92 _delegate = storage; 93 } 94 return self; 95} 96 97- (instancetype)initWithObjects:(const id _Nonnull __unsafe_unretained *)objects 98 forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys 99 count:(NSUInteger)cnt { 100 return [self init]; 101} 102 103- (void)checkCallIsNotStarted { 104 if (_call.state != GRXWriterStateNotStarted) { 105 [NSException raise:@"Invalid modification" 106 format:@"Cannot modify request headers after call is started"]; 107 } 108} 109 110- (id)objectForKey:(id)key { 111 CheckIsNilOrString(@"Header name", key); 112 NSString *stringKey = [(NSString *)key lowercaseString]; 113 return _delegate[stringKey]; 114} 115 116- (void)setObject:(id)obj forKey:(id<NSCopying>)key { 117 CheckIsNonNilASCIIString(@"Header name", key); 118 NSString *stringKey = [(NSString *)key lowercaseString]; 119 CheckKeyValuePairIsValid(stringKey, obj); 120 _delegate[stringKey] = obj; 121} 122 123- (void)removeObjectForKey:(id)key { 124 CheckIsNilOrString(@"Header name", key); 125 NSString *stringKey = [(NSString *)key lowercaseString]; 126 [self checkCallIsNotStarted]; 127 [_delegate removeObjectForKey:stringKey]; 128} 129 130- (NSUInteger)count { 131 return _delegate.count; 132} 133 134- (NSEnumerator *_Nonnull)keyEnumerator { 135 return [_delegate keyEnumerator]; 136} 137 138@end 139