mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-05-13 00:02:02 +08:00
#38 Added support for digest authentication
This commit is contained in:
@@ -58,6 +58,7 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; //
|
||||
#endif
|
||||
|
||||
extern NSString* const GCDWebServerAuthenticationMethod_Basic;
|
||||
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||
|
||||
@class GCDWebServer;
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
NSString* _serverName;
|
||||
NSString* _authenticationRealm;
|
||||
NSString* _authenticationBasicAccount;
|
||||
NSString* _authenticationDigestAccount;
|
||||
Class _connectionClass;
|
||||
BOOL _mapHEADToGET;
|
||||
CFTimeInterval _disconnectDelay;
|
||||
@@ -95,6 +96,7 @@ NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"Automati
|
||||
#endif
|
||||
|
||||
NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
|
||||
NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
#ifdef NDEBUG
|
||||
@@ -155,7 +157,8 @@ static void _SignalHandler(int signal) {
|
||||
@implementation GCDWebServer
|
||||
|
||||
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
|
||||
authenticationBasicAccount=_authenticationBasicAccount, shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
||||
authenticationBasicAccount=_authenticationBasicAccount, authenticationDigestAccount=_authenticationDigestAccount,
|
||||
shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
|
||||
@@ -383,6 +386,11 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
NSString* user = _GetOption(_options, GCDWebServerOption_AuthenticationUser, @"");
|
||||
NSString* password = _GetOption(_options, GCDWebServerOption_AuthenticationPassword, @"");
|
||||
_authenticationBasicAccount = ARC_RETAIN(_EncodeBase64([NSString stringWithFormat:@"%@:%@", user, password]));
|
||||
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
NSString* user = _GetOption(_options, GCDWebServerOption_AuthenticationUser, @"");
|
||||
NSString* password = _GetOption(_options, GCDWebServerOption_AuthenticationPassword, @"");
|
||||
_authenticationDigestAccount = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@:%@:%@", user, _authenticationRealm, password));
|
||||
}
|
||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||
@@ -502,6 +510,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
_authenticationRealm = nil;
|
||||
ARC_RELEASE(_authenticationBasicAccount);
|
||||
_authenticationBasicAccount = nil;
|
||||
ARC_RELEASE(_authenticationDigestAccount);
|
||||
_authenticationDigestAccount = nil;
|
||||
|
||||
LOG_INFO(@"%@ stopped", [self class]);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
||||
|
||||
@@ -48,6 +48,7 @@ static NSData* _CRLFData = nil;
|
||||
static NSData* _CRLFCRLFData = nil;
|
||||
static NSData* _continueData = nil;
|
||||
static NSData* _lastChunkData = nil;
|
||||
static NSString* _digestAuthenticationNonce = nil;
|
||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||
static int32_t _connectionCounter = 0;
|
||||
#endif
|
||||
@@ -357,6 +358,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
if (_lastChunkData == nil) {
|
||||
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
|
||||
}
|
||||
if (_digestAuthenticationNonce == nil) {
|
||||
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
||||
_digestAuthenticationNonce = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@", ARC_BRIDGE_RELEASE(CFUUIDCreateString(kCFAllocatorDefault, uuid))));
|
||||
CFRelease(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
||||
@@ -707,15 +713,46 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc2617
|
||||
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
||||
LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||
GCDWebServerResponse* response = nil;
|
||||
if (_server.authenticationBasicAccount) {
|
||||
BOOL authenticated = NO;
|
||||
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
|
||||
if (![authorizationHeader hasPrefix:@"Basic "] || ![[authorizationHeader substringFromIndex:6] isEqualToString:_server.authenticationBasicAccount]) {
|
||||
if ([authorizationHeader hasPrefix:@"Basic "]) {
|
||||
NSString* basicAccount = [authorizationHeader substringFromIndex:6];
|
||||
if ([basicAccount isEqualToString:_server.authenticationBasicAccount]) {
|
||||
authenticated = YES;
|
||||
}
|
||||
}
|
||||
if (!authenticated) {
|
||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
|
||||
[response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"];
|
||||
}
|
||||
} else if (_server.authenticationDigestAccount) {
|
||||
BOOL authenticated = NO;
|
||||
BOOL isStaled = NO;
|
||||
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
|
||||
if ([authorizationHeader hasPrefix:@"Digest "]) {
|
||||
NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
|
||||
if ([nonce isEqualToString:_digestAuthenticationNonce]) { // TODO: Also check "realm" and "username" provided by client
|
||||
NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
|
||||
NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
|
||||
NSString* ha1 = _server.authenticationDigestAccount;
|
||||
NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required
|
||||
NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
|
||||
if ([actualResponse isEqualToString:expectedResponse]) {
|
||||
authenticated = YES;
|
||||
}
|
||||
} else if (nonce.length) {
|
||||
isStaled = YES;
|
||||
}
|
||||
}
|
||||
if (!authenticated) {
|
||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
|
||||
[response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop")
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#else
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#endif
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#import <ifaddrs.h>
|
||||
#import <net/if.h>
|
||||
@@ -264,3 +265,22 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
const char* string = [ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]) UTF8String];
|
||||
va_end(arguments);
|
||||
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
||||
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
|
||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
|
||||
unsigned char byte = md5[i];
|
||||
unsigned char byteHi = (byte & 0xF0) >> 4;
|
||||
buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
|
||||
unsigned char byteLo = byte & 0x0F;
|
||||
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
|
||||
}
|
||||
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
|
||||
return [NSString stringWithUTF8String:buffer];
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSStr
|
||||
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
||||
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
||||
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
@interface GCDWebServerConnection ()
|
||||
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
||||
@@ -131,6 +132,7 @@ extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||
@property(nonatomic, readonly) NSString* serverName;
|
||||
@property(nonatomic, readonly) NSString* authenticationRealm;
|
||||
@property(nonatomic, readonly) NSString* authenticationBasicAccount;
|
||||
@property(nonatomic, readonly) NSString* authenticationDigestAccount;
|
||||
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
||||
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
||||
|
||||
+13
-4
@@ -130,12 +130,13 @@ int main(int argc, const char* argv[]) {
|
||||
BOOL recording = NO;
|
||||
NSString* rootDirectory = NSHomeDirectory();
|
||||
NSString* testDirectory = nil;
|
||||
NSString* authenticationMethod = nil;
|
||||
NSString* authenticationRealm = nil;
|
||||
NSString* authenticationUser = nil;
|
||||
NSString* authenticationPassword = nil;
|
||||
|
||||
if (argc == 1) {
|
||||
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
|
||||
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
|
||||
} else {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (argv[i][0] != '-') {
|
||||
@@ -164,6 +165,9 @@ int main(int argc, const char* argv[]) {
|
||||
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
|
||||
++i;
|
||||
testDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
||||
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
|
||||
++i;
|
||||
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
|
||||
} else if (!strcmp(argv[i], "-authenticationRealm") && (i + 1 < argc)) {
|
||||
++i;
|
||||
authenticationRealm = [NSString stringWithUTF8String:argv[i]];
|
||||
@@ -296,11 +300,16 @@ int main(int argc, const char* argv[]) {
|
||||
NSMutableDictionary* options = [NSMutableDictionary dictionary];
|
||||
[options setObject:@8080 forKey:GCDWebServerOption_Port];
|
||||
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
|
||||
if (authenticationUser && authenticationPassword) {
|
||||
if ([authenticationMethod isEqualToString:@"Basic"]) {
|
||||
[options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod];
|
||||
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
|
||||
[options setObject:authenticationUser forKey:GCDWebServerOption_AuthenticationUser];
|
||||
[options setObject:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword];
|
||||
[options setValue:authenticationUser forKey:GCDWebServerOption_AuthenticationUser];
|
||||
[options setValue:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword];
|
||||
} else if ([authenticationMethod isEqualToString:@"Digest"]) {
|
||||
[options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod];
|
||||
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
|
||||
[options setValue:authenticationUser forKey:GCDWebServerOption_AuthenticationUser];
|
||||
[options setValue:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword];
|
||||
}
|
||||
if ([webServer runWithOptions:options]) {
|
||||
result = 0;
|
||||
|
||||
@@ -18,7 +18,7 @@ Extra built-in features:
|
||||
* [HTTP compression](https://en.wikipedia.org/wiki/HTTP_compression) with gzip for request and response HTTP bodies
|
||||
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
|
||||
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
|
||||
* [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) for simple password protection
|
||||
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) Authentications for password protection
|
||||
|
||||
Included extensions:
|
||||
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
||||
|
||||
Reference in New Issue
Block a user