Skip to content

Commit e5f30d6

Browse files
Support the RTDB emulator
1 parent 7b9597c commit e5f30d6

File tree

5 files changed

+107
-37
lines changed

5 files changed

+107
-37
lines changed

Example/Database/Tests/Integration/FIRDatabaseTests.m

-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ - (void) testDatabaseForApp {
5353
- (void) testDatabaseForAppWithInvalidURLs {
5454
XCTAssertThrows([selfdatabaseForURL:nil]);
5555
XCTAssertThrows([selfdatabaseForURL:@"not-a-url"]);
56-
XCTAssertThrows([selfdatabaseForURL:@"http://fblocal.com"]);
5756
XCTAssertThrows([selfdatabaseForURL:@"http://x.example.com/paths/are/bad"]);
5857
}
5958

@@ -95,7 +94,6 @@ - (void)testDatabaseForAppWithInvalidCustomURLs {
9594
XCTAssertThrows([FIRDatabase databaseForApp:app URL:nil]);
9695
#pragma clang diagnostic pop
9796
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"not-a-url"]);
98-
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"http://fblocal.com"]);
9997
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"http://x.fblocal.com:9000/paths/are/bad"]);
10098
}
10199

Example/Database/Tests/Unit/FUtilitiesTest.m

+37
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,43 @@ - (void)testUrlParsedWithoutSchema {
5050
XCTAssertEqualObjects(parsedUrl.path, [FPath empty]);
5151
}
5252

53+
- (void)testUrlParsedUsesSpaceInsteadOfPlus {
54+
FParsedUrl *parsedUrl = [FUtilities parseUrl:@"repo.firebaseio.com/+"];
55+
XCTAssertEqualObjects(parsedUrl.path, [FPath pathWithString:@"/ "]);
56+
}
57+
58+
- (void)testUrlParsedWithSpecialCharacters {
59+
FParsedUrl *parsedUrl = [FUtilities parseUrl:@"repo.firebaseio.com/a%b&c@d/space: /non-ascii:ø"];
60+
XCTAssertEqualObjects(parsedUrl.path, [FPath pathWithString:@"/a%b&c@d/space: /non-ascii:ø"]);
61+
}
62+
63+
- (void)testUrlParsedWithNamespace {
64+
FParsedUrl *parsedUrl = [FUtilities parseUrl:@"repo.firebaseio.com/foo/bar?ns=mrschmidt"];
65+
XCTAssertEqualObjects(parsedUrl.path, [FPath pathWithString:@"/foo/bar"]);
66+
// The subdomain takes precedence if one is provided
67+
XCTAssertEqualObjects(parsedUrl.repoInfo.namespace, @"repo");
68+
69+
parsedUrl = [FUtilities parseUrl:@"localhost/?ns=mrschmidt"];
70+
XCTAssertEqualObjects(parsedUrl.repoInfo.namespace, @"mrschmidt");
71+
72+
parsedUrl = [FUtilities parseUrl:@"127.0.0.1:9000/?ns=mrschmidt"];
73+
XCTAssertEqualObjects(parsedUrl.repoInfo.namespace, @"mrschmidt");
74+
}
75+
76+
- (void)testUrlParsedWithSslDetection {
77+
// Hosts with custom ports are considered non-secure
78+
FParsedUrl *parsedUrl = [FUtilities parseUrl:@"repo.firebaseio.com:9000"];
79+
XCTAssertFalse(parsedUrl.repoInfo.secure);
80+
81+
// Hosts with omitted ports are considered secure
82+
parsedUrl = [FUtilities parseUrl:@"repo.firebaseio.com"];
83+
XCTAssertTrue(parsedUrl.repoInfo.secure);
84+
85+
// Localhost is special-cased as insecure
86+
parsedUrl = [FUtilities parseUrl:@"localhost"];
87+
XCTAssertFalse(parsedUrl.repoInfo.secure);
88+
}
89+
5390
- (void)testDefaultCacheSizeIs10MB {
5491
XCTAssertEqual([FTestHelpers defaultConfig].persistenceCacheSizeBytes, (NSUInteger)10*1024*1024);
5592
XCTAssertEqual([FTestHelpers configForName:@"test-config"].persistenceCacheSizeBytes, (NSUInteger)10*1024*1024);

Firebase/Database/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# Unreleased
2+
-[feature] The SDK adds support for the Firebase Database Emulator. To connect
3+
to the emulator, specify "http://<emulator_host>/" as your Database URL
4+
(via `Database.database(url:)`).
5+
If you refer to your emulator host by IP rather than by domain name, you may
6+
also need to specify a namespace ("http://<emulator_host>/?ns=<project_id>").
7+
18
# v6.0.0
29
-[removed] Remove deprecated `childByAppendingPath` API. (#2763)
310

Firebase/Database/Core/FRepoInfo.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ - (id) initWithHost:(NSString*)aHost isSecure:(bool)isSecure withNamespace:(NSSt
3636
self = [superinit];
3737
if (self) {
3838
host = aHost;
39-
domain = [host substringFromIndex:[host rangeOfString:@"."].location+1];
39+
domain = [host containsString:@"."] ? [host substringFromIndex:[host rangeOfString:@"."].location+1] : host;
4040
secure = isSecure;
4141
namespace = aNamespace;
4242

Firebase/Database/Utilities/FUtilities.m

+62-34
Original file line numberDiff line numberDiff line change
@@ -122,54 +122,82 @@ + (NSString *) decodePath:(NSString *)pathString {
122122
return [NSStringstringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]];
123123
}
124124

125-
+ (FParsedUrl *) parseUrl:(NSString *)url {
126-
NSString* original = url;
127-
//NSURL* n = [[NSURL alloc] initWithString:url]
125+
+ (NSString *) extractPathFromUrlString:(NSString *)url {
126+
NSString* path = url;
128127

129-
NSString* host;
130-
NSString* namespace;
131-
bool secure;
128+
NSRange schemeIndex = [path rangeOfString:@"//"];
129+
if (schemeIndex.location != NSNotFound) {
130+
path = [path substringFromIndex:schemeIndex.location + 2];
131+
}
132132

133-
NSString* scheme = nil;
134-
FPath* path = nil;
135-
NSRange colonIndex = [url rangeOfString:@"//"];
136-
if (colonIndex.location != NSNotFound) {
137-
scheme = [url substringToIndex:colonIndex.location - 1];
138-
url = [url substringFromIndex:colonIndex.location + 2];
133+
NSUInteger pathIndex = [path rangeOfString:@"/"].location;
134+
if (pathIndex != NSNotFound) {
135+
path = [path substringFromIndex:pathIndex + 1];
136+
} else {
137+
path = @"";
139138
}
140-
NSInteger slashIndex = [url rangeOfString:@"/"].location;
141-
if (slashIndex == NSNotFound) {
142-
slashIndex = url.length;
139+
140+
NSUInteger queryParamIndex = [path rangeOfString:@"?"].location;
141+
if (queryParamIndex != NSNotFound) {
142+
path = [path substringToIndex:queryParamIndex];
143143
}
144144

145-
host = [[url substringToIndex:slashIndex] lowercaseString];
146-
if (slashIndex >= url.length) {
147-
url = @"";
148-
} else {
149-
url = [url substringFromIndex:slashIndex + 1];
145+
return path;
146+
}
147+
148+
+ (FParsedUrl *) parseUrl:(NSString *)url {
149+
// For backwards compatibility, support URLs without schemes on iOS.
150+
if (![url containsString:@"://"]) {
151+
url = [@"http://"stringByAppendingString:url];
152+
}
153+
154+
NSString* originalPathString = [selfextractPathFromUrlString:url];
155+
156+
// Sanitize the database URL by removing the path component, which may contain invalid URL characters.
157+
NSString* sanitizedUrlWithoutPath = [url stringByReplacingOccurrencesOfString:originalPathString withString:@""];
158+
NSURLComponents *urlComponents = [NSURLComponentscomponentsWithString:sanitizedUrlWithoutPath];
159+
if (!urlComponents) {
160+
[NSExceptionraise:@"Failed to parse database URL"format:@"Failed to parse database URL: %@", url];
150161
}
151162

152-
NSArray *parts = [host componentsSeparatedByString:@"."];
163+
NSString* host = [urlComponents.host lowercaseString];
164+
NSString* namespace;
165+
bool secure;
166+
167+
if (urlComponents.port != nil) {
168+
secure = [urlComponents.scheme isEqualToString:@"https"];
169+
host = [host stringByAppendingFormat:@":%@", urlComponents.port];
170+
} elseif ( [urlComponents.host isEqualToString:@"localhost"]) {
171+
secure = NO;
172+
} else {
173+
secure = YES;
174+
};
175+
176+
NSArray *parts = [urlComponents.host componentsSeparatedByString:@"."];
153177
if([parts count] == 3) {
154-
NSInteger colonIndex = [[parts objectAtIndex:2] rangeOfString:@":"].location;
155-
if (colonIndex != NSNotFound) {
156-
// we have a port, use the provided scheme
157-
secure = [scheme isEqualToString:@"https"];
158-
} else {
159-
secure = YES;
178+
namespace = [parts[0] lowercaseString];
179+
} else {
180+
// Attempt to extract namespace from "ns" query param.
181+
NSArray *queryItems = urlComponents.queryItems;
182+
NSMutableArray *someIDs = [NSMutableArraynew];
183+
for (NSURLQueryItem *item in queryItems) {
184+
if ([item.name isEqualToString:@"ns"]) {
185+
namespace = item.value;
186+
break;
187+
}
160188
}
161189

162-
namespace = [[parts objectAtIndex:0] lowercaseString];
163-
NSString* pathString = [selfdecodePath:[NSStringstringWithFormat:@"/%@", url]];
164-
path = [[FPath alloc] initWith:pathString];
165-
}
166-
else {
167-
[NSExceptionraise:@"No Firebase database specified."format:@"No Firebase database found for input: %@", url];
190+
if (!namespace) {
191+
namespace = [parts[0] lowercaseString];
192+
}
168193
}
169194

195+
196+
NSString* pathString = [selfdecodePath:[NSStringstringWithFormat:@"/%@", originalPathString]];
197+
FPath * path = [[FPath alloc] initWith:pathString];
170198
FRepoInfo* repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:secure withNamespace:namespace];
171199

172-
FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", original, [repoInfo description], [repoInfo connectionURL], repoInfo.namespace, [path description]);
200+
FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", url, [repoInfo description], [repoInfo connectionURL], repoInfo.namespace, [path description]);
173201

174202
FParsedUrl* parsedUrl = [[FParsedUrl alloc] init];
175203
parsedUrl.repoInfo = repoInfo;

0 commit comments

Comments
 (0)
close