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