ObjFW  Check-in [0ca94307e6]

Overview
Comment:OFIRI: Add methods for path extension
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0ca94307e6b7b3a1ea2204c2d4b2d42cd61daf93243541df9c84b5ee507651c6
User & Date: js on 2024-03-10 18:44:57
Other Links: manifest | tags
Context
2024-03-10
21:30
OFZIPArchive: More fixes for disk number 0 vs 1 check-in: 0ecd85c8e3 user: js tags: trunk
18:44
OFIRI: Add methods for path extension check-in: 0ca94307e6 user: js tags: trunk
16:49
Avoid some unnecessary I/O check-in: 6060a03291 user: js tags: trunk
Changes

Modified src/OFIRI.h from [d4857e1b44] to [48519fe9fc].

109
110
111
112
113
114
115





116
117
118
119
120
121
122
/**
 * @brief The last path component of the IRI.
 *
 * Returns the empty string if the path is the root.
 */
@property (readonly, copy, nonatomic) OFString *lastPathComponent;






/**
 * @brief The query part of the IRI.
 */
@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *query;

/**
 * @brief The query part of the IRI in percent-encoded form.







>
>
>
>
>







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
 * @brief The last path component of the IRI.
 *
 * Returns the empty string if the path is the root.
 */
@property (readonly, copy, nonatomic) OFString *lastPathComponent;

/**
 * @brief The path extension of the IRI.
 */
@property (readonly, copy, nonatomic) OFString *pathExtension;

/**
 * @brief The query part of the IRI.
 */
@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *query;

/**
 * @brief The query part of the IRI in percent-encoded form.
162
163
164
165
166
167
168





169
170
171
172
173
174
175
@property (readonly, nonatomic) OFIRI *IRIByStandardizingPath;

/**
 * @brief The IRI with the last path component deleted.
 */
@property (readonly, nonatomic) OFIRI *IRIByDeletingLastPathComponent;






/**
 * @brief The IRI with percent-encoding added for all Unicode characters.
 */
@property (readonly, nonatomic)
    OFIRI *IRIByAddingPercentEncodingForUnicodeCharacters;

#ifdef OF_HAVE_FILES







>
>
>
>
>







167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
@property (readonly, nonatomic) OFIRI *IRIByStandardizingPath;

/**
 * @brief The IRI with the last path component deleted.
 */
@property (readonly, nonatomic) OFIRI *IRIByDeletingLastPathComponent;

/**
 * @brief The IRI with the path extension deleted.
 */
@property (readonly, nonatomic) OFIRI *IRIByDeletingPathExtension;

/**
 * @brief The IRI with percent-encoding added for all Unicode characters.
 */
@property (readonly, nonatomic)
    OFIRI *IRIByAddingPercentEncodingForUnicodeCharacters;

#ifdef OF_HAVE_FILES
302
303
304
305
306
307
308








309
310
311
312
313
314
315
 *		    instead.
 * @param isDirectory Whether the appended component is a directory, meaning
 *		      that the IRI path should have a trailing slash
 * @return A new IRI with the specified path component appended
 */
- (OFIRI *)IRIByAppendingPathComponent: (OFString *)component
			   isDirectory: (bool)isDirectory;








@end

@interface OFCharacterSet (IRICharacterSets)
#ifdef OF_HAVE_CLASS_PROPERTIES
@property (class, readonly, nonatomic)
    OFCharacterSet *IRISchemeAllowedCharacterSet;
@property (class, readonly, nonatomic)







>
>
>
>
>
>
>
>







312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
 *		    instead.
 * @param isDirectory Whether the appended component is a directory, meaning
 *		      that the IRI path should have a trailing slash
 * @return A new IRI with the specified path component appended
 */
- (OFIRI *)IRIByAppendingPathComponent: (OFString *)component
			   isDirectory: (bool)isDirectory;

/**
 * @brief Returns a new IRI with the specified path extension appended.
 *
 * @param extension The path extension to append
 * @return A new IRI with the specified path extension appended.
 */
- (OFIRI *)IRIByAppendingPathExtension: (OFString *)extension;
@end

@interface OFCharacterSet (IRICharacterSets)
#ifdef OF_HAVE_CLASS_PROPERTIES
@property (class, readonly, nonatomic)
    OFCharacterSet *IRISchemeAllowedCharacterSet;
@property (class, readonly, nonatomic)

Modified src/OFIRI.m from [f981acd3b0] to [a3a7757ab9].

1150
1151
1152
1153
1154
1155
1156





















1157
1158
1159
1160
1161
1162
1163
			  length: length - (lastComponent - UTF8String)];
	ret = [ret.stringByRemovingPercentEncoding retain];

	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}






















- (OFString *)query
{
	return _percentEncodedQuery.stringByRemovingPercentEncoding;
}

- (OFString *)percentEncodedQuery







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
			  length: length - (lastComponent - UTF8String)];
	ret = [ret.stringByRemovingPercentEncoding retain];

	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}

- (OFString *)pathExtension
{
	void *pool = objc_autoreleasePoolPush();
	OFString *ret, *fileName;
	size_t pos;

	fileName = self.lastPathComponent;
	pos = [fileName rangeOfString: @"."
			      options: OFStringSearchBackwards].location;
	if (pos == OFNotFound || pos == 0) {
		objc_autoreleasePoolPop(pool);
		return @"";
	}

	ret = [fileName substringFromIndex: pos + 1];

	[ret retain];
	objc_autoreleasePoolPop(pool);
	return [ret autorelease];
}

- (OFString *)query
{
	return _percentEncodedQuery.stringByRemovingPercentEncoding;
}

- (OFString *)percentEncodedQuery
1316
1317
1318
1319
1320
1321
1322
















1323
1324
1325
1326
1327
1328
1329
- (OFIRI *)IRIByDeletingLastPathComponent
{
	OFMutableIRI *IRI = [[self mutableCopy] autorelease];
	[IRI deleteLastPathComponent];
	[IRI makeImmutable];
	return IRI;
}

















- (OFIRI *)IRIByStandardizingPath
{
	OFMutableIRI *IRI = [[self mutableCopy] autorelease];
	[IRI standardizePath];
	[IRI makeImmutable];
	return IRI;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
- (OFIRI *)IRIByDeletingLastPathComponent
{
	OFMutableIRI *IRI = [[self mutableCopy] autorelease];
	[IRI deleteLastPathComponent];
	[IRI makeImmutable];
	return IRI;
}

- (OFIRI *)IRIByAppendingPathExtension: (OFString *)extension
{
	OFMutableIRI *IRI = [[self mutableCopy] autorelease];
	[IRI appendPathExtension: extension];
	[IRI makeImmutable];
	return IRI;
}

- (OFIRI *)IRIByDeletingPathExtension
{
	OFMutableIRI *IRI = [[self mutableCopy] autorelease];
	[IRI deletePathExtension];
	[IRI makeImmutable];
	return IRI;
}

- (OFIRI *)IRIByStandardizingPath
{
	OFMutableIRI *IRI = [[self mutableCopy] autorelease];
	[IRI standardizePath];
	[IRI makeImmutable];
	return IRI;

Modified src/OFMutableIRI.h from [f1d3d3e8dc] to [7eb45ca8a2].

204
205
206
207
208
209
210







211
212
213
214
215





216
217
218
219
220
221
222
223
224
225
226
227
 * @param component The component to append
 * @param isDirectory Whether the path is a directory, in which case a slash is
 *		      appended if there is no slash yet
 */
- (void)appendPathComponent: (OFString *)component
		isDirectory: (bool)isDirectory;








/**
 * @brief Deletes the last path component.
 */
- (void)deleteLastPathComponent;






/**
 * @brief Resolves relative subpaths.
 */
- (void)standardizePath;

/**
 * @brief Converts the mutable IRI to an immutable IRI.
 */
- (void)makeImmutable;
@end

OF_ASSUME_NONNULL_END







>
>
>
>
>
>
>





>
>
>
>
>












204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
 * @param component The component to append
 * @param isDirectory Whether the path is a directory, in which case a slash is
 *		      appended if there is no slash yet
 */
- (void)appendPathComponent: (OFString *)component
		isDirectory: (bool)isDirectory;

/**
 * @brief Appends the specified path extension
 *
 * @param extension The path extension to append
 */
- (void)appendPathExtension: (OFString *)extension;

/**
 * @brief Deletes the last path component.
 */
- (void)deleteLastPathComponent;

/**
 * @brief Deletes the path extension.
 */
- (void)deletePathExtension;

/**
 * @brief Resolves relative subpaths.
 */
- (void)standardizePath;

/**
 * @brief Converts the mutable IRI to an immutable IRI.
 */
- (void)makeImmutable;
@end

OF_ASSUME_NONNULL_END

Modified src/OFMutableIRI.m from [f07aac1192] to [a9164c7ec2].

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
	[copy makeImmutable];

	return copy;
}

- (void)appendPathComponent: (OFString *)component
{
	[self appendPathComponent: component isDirectory: false];

#ifdef OF_HAVE_FILES
	if ([_scheme isEqual: @"file"] &&
	    ![_percentEncodedPath hasSuffix: @"/"] &&
	    [[OFFileManager defaultManager] directoryExistsAtIRI: self]) {
		void *pool = objc_autoreleasePoolPush();
		OFString *path = [_percentEncodedPath
		    stringByAppendingString: @"/"];

		[_percentEncodedPath release];
		_percentEncodedPath = [path retain];

		objc_autoreleasePoolPop(pool);
	}
#endif
}

- (void)appendPathComponent: (OFString *)component
		isDirectory: (bool)isDirectory
{
	void *pool;
	OFString *path;







|



<
|
<
|
|

<
<
|
<
<
<







320
321
322
323
324
325
326
327
328
329
330

331

332
333
334


335



336
337
338
339
340
341
342
	[copy makeImmutable];

	return copy;
}

- (void)appendPathComponent: (OFString *)component
{
	bool isDirectory = false;

#ifdef OF_HAVE_FILES
	if ([_scheme isEqual: @"file"] &&

	    [[OFFileManager defaultManager] directoryExistsAtIRI: self])

		isDirectory = true;
#endif



	[self appendPathComponent: component isDirectory: isDirectory];



}

- (void)appendPathComponent: (OFString *)component
		isDirectory: (bool)isDirectory
{
	void *pool;
	OFString *path;
372
373
374
375
376
377
378

































379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401































402
403
404
405
406
407
408
		path = [path stringByAppendingString: @"/"];

	[_percentEncodedPath release];
	_percentEncodedPath = [path retain];

	objc_autoreleasePoolPop(pool);
}


































- (void)deleteLastPathComponent
{
	void *pool = objc_autoreleasePoolPush();
	OFString *path = _percentEncodedPath;
	size_t pos;

	if (path.length == 0 || [path isEqual: @"/"]) {
		objc_autoreleasePoolPop(pool);
		return;
	}

	if ([path hasSuffix: @"/"])
		 path = [path substringToIndex: path.length - 1];

	pos = [path rangeOfString: @"/"
			  options: OFStringSearchBackwards].location;
	if (pos == OFNotFound) {
		objc_autoreleasePoolPop(pool);
		return;
	}

	path = [path substringToIndex: pos + 1];































	[_percentEncodedPath release];
	_percentEncodedPath = [path retain];

	objc_autoreleasePoolPop(pool);
}

- (void)standardizePath







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













|









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
		path = [path stringByAppendingString: @"/"];

	[_percentEncodedPath release];
	_percentEncodedPath = [path retain];

	objc_autoreleasePoolPop(pool);
}

- (void)appendPathExtension: (OFString *)extension
{
	void *pool;
	OFMutableString *path;
	bool isDirectory = false;

	if (_percentEncodedPath.length == 0)
		return;

	pool = objc_autoreleasePoolPush();
	path = [[_percentEncodedPath mutableCopy] autorelease];

	extension = [extension
	    stringByAddingPercentEncodingWithAllowedCharacters:
	    [OFCharacterSet IRIPathAllowedCharacterSet]];

	if ([path hasSuffix: @"/"]) {
		[path deleteCharactersInRange: OFMakeRange(path.length - 1, 1)];
		isDirectory = true;
	}

	[path appendFormat: @".%@", extension];

	if (isDirectory)
		[path appendString: @"/"];

	[path makeImmutable];
	[_percentEncodedPath release];
	_percentEncodedPath = [path retain];

	objc_autoreleasePoolPop(pool);
}

- (void)deleteLastPathComponent
{
	void *pool = objc_autoreleasePoolPush();
	OFString *path = _percentEncodedPath;
	size_t pos;

	if (path.length == 0 || [path isEqual: @"/"]) {
		objc_autoreleasePoolPop(pool);
		return;
	}

	if ([path hasSuffix: @"/"])
		path = [path substringToIndex: path.length - 1];

	pos = [path rangeOfString: @"/"
			  options: OFStringSearchBackwards].location;
	if (pos == OFNotFound) {
		objc_autoreleasePoolPop(pool);
		return;
	}

	path = [path substringToIndex: pos + 1];
	[_percentEncodedPath release];
	_percentEncodedPath = [path retain];

	objc_autoreleasePoolPop(pool);
}

- (void)deletePathExtension
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableString *path = [[_percentEncodedPath mutableCopy] autorelease];
	bool isDirectory = false;
	size_t pos;

	if ([path hasSuffix: @"/"]) {
		[path deleteCharactersInRange: OFMakeRange(path.length - 1, 1)];
		isDirectory = true;
	}

	pos = [path rangeOfString: @"."
			  options: OFStringSearchBackwards].location;
	if (pos == OFNotFound) {
		objc_autoreleasePoolPop(pool);
		return;
	}

	[path deleteCharactersInRange: OFMakeRange(pos, path.length - pos)];

	if (isDirectory)
		[path appendString: @"/"];

	[path makeImmutable];
	[_percentEncodedPath release];
	_percentEncodedPath = [path retain];

	objc_autoreleasePoolPop(pool);
}

- (void)standardizePath

Modified tests/OFIRITests.m from [287e2de2cf] to [fc355e1de3].

285
286
287
288
289
290
291











292
293
294
295
296
297
298

	OTAssertEqualObjects([[OFIRI IRIWithString: @"http://host/"]
	    lastPathComponent],
	    @"/");

	OTAssertEqualObjects(_IRI[4].lastPathComponent, @"foo/bar");
}












- (void)testQuery
{
	OTAssertEqualObjects(_IRI[0].query, @"que#ry=1&f&oo=b=ar");
	OTAssertNil(_IRI[3].query);
	OTAssertEqualObjects(_IRI[8].query, @"query");
	OTAssertEqualObjects(_IRI[9].query, @"query");







>
>
>
>
>
>
>
>
>
>
>







285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309

	OTAssertEqualObjects([[OFIRI IRIWithString: @"http://host/"]
	    lastPathComponent],
	    @"/");

	OTAssertEqualObjects(_IRI[4].lastPathComponent, @"foo/bar");
}

- (void)testPathExtension
{
	OTAssertEqualObjects(
	    [[OFIRI IRIWithString: @"http://host/path.dir/path.file"]
	    pathExtension], @"file");

	OTAssertEqualObjects(
	    [[OFIRI IRIWithString: @"http://host/path/path.dir/"]
	    pathExtension], @"dir");
}

- (void)testQuery
{
	OTAssertEqualObjects(_IRI[0].query, @"que#ry=1&f&oo=b=ar");
	OTAssertNil(_IRI[3].query);
	OTAssertEqualObjects(_IRI[8].query, @"query");
	OTAssertEqualObjects(_IRI[9].query, @"query");
376
377
378
379
380
381
382


























383
384
385
386
387
388
389
	    [[[OFIRI IRIWithString: @"http://host/"]
	    IRIByDeletingLastPathComponent] path], @"/");

	OTAssertEqualObjects(
	    [[[OFIRI IRIWithString: @"http://host"]
	    IRIByDeletingLastPathComponent] path], @"");
}



























- (void)testIRIByAddingPercentEncodingForUnicodeCharacters
{
	OTAssertEqualObjects(
	    _IRI[10].IRIByAddingPercentEncodingForUnicodeCharacters,
	    [OFIRI IRIWithString: @"http://%C3%A4/%C3%B6?%C3%BC"]);
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
	    [[[OFIRI IRIWithString: @"http://host/"]
	    IRIByDeletingLastPathComponent] path], @"/");

	OTAssertEqualObjects(
	    [[[OFIRI IRIWithString: @"http://host"]
	    IRIByDeletingLastPathComponent] path], @"");
}

- (void)testIRIByAppendingPathExtension
{
	OTAssertEqualObjects(
	    [[[OFIRI IRIWithString: @"http://host/path.dir/path"]
	    IRIByAppendingPathExtension: @"file"] path],
	    @"/path.dir/path.file");

	OTAssertEqualObjects(
	    [[[OFIRI IRIWithString: @"http://host/path/path/"]
	    IRIByAppendingPathExtension: @"dir"] path],
	    @"/path/path.dir/");
}

- (void)testIRIByDeletingPathExtension
{
	OTAssertEqualObjects(
	    [[[OFIRI IRIWithString: @"http://host/path.dir/path.file"]
	    IRIByDeletingPathExtension] path],
	    @"/path.dir/path");

	OTAssertEqualObjects(
	    [[[OFIRI IRIWithString: @"http://host/path/path.dir/"]
	    IRIByDeletingPathExtension] path],
	    @"/path/path/");
}

- (void)testIRIByAddingPercentEncodingForUnicodeCharacters
{
	OTAssertEqualObjects(
	    _IRI[10].IRIByAddingPercentEncodingForUnicodeCharacters,
	    [OFIRI IRIWithString: @"http://%C3%A4/%C3%B6?%C3%BC"]);
}

Modified utils/ofarc/GZIPArchive.m from [8abaeb7cdd] to [27c819e003].

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
		[OFStdErr writeLine: OF_LOCALIZED(
		    @"cannot_extract_specific_file_from_gz",
		    @"Cannot extract a specific file of a .gz archive!")];
		app->_exitStatus = 1;
		return;
	}

	/* FIXME: Should use IRI-specific path extension deletion. */
	fileName = _archiveIRI.lastPathComponent.stringByDeletingPathExtension;

	if (app->_outputLevel >= 0)
		[OFStdOut writeString: OF_LOCALIZED(@"extracting_file",
		    @"Extracting %[file]...",
		    @"file", fileName)];

	if (![app shouldExtractFile: fileName outFileName: fileName])







<
|







122
123
124
125
126
127
128

129
130
131
132
133
134
135
136
		[OFStdErr writeLine: OF_LOCALIZED(
		    @"cannot_extract_specific_file_from_gz",
		    @"Cannot extract a specific file of a .gz archive!")];
		app->_exitStatus = 1;
		return;
	}


	fileName = _archiveIRI.IRIByDeletingPathExtension.lastPathComponent;

	if (app->_outputLevel >= 0)
		[OFStdOut writeString: OF_LOCALIZED(@"extracting_file",
		    @"Extracting %[file]...",
		    @"file", fileName)];

	if (![app shouldExtractFile: fileName outFileName: fileName])
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
		    @"Extracting %[file]... done",
		    @"file", fileName)];
	}
}

- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files
{
	/* FIXME: Should use IRI-specific path extension deletion. */
	OFString *fileName = _archiveIRI.lastPathComponent
	    .stringByDeletingPathExtension;

	if (files.count > 0) {
		[OFStdErr writeLine: OF_LOCALIZED(
		    @"cannot_print_specific_file_from_gz",
		    @"Cannot print a specific file of a .gz archive!")];
		app->_exitStatus = 1;
		return;







<
|
|







159
160
161
162
163
164
165

166
167
168
169
170
171
172
173
174
		    @"Extracting %[file]... done",
		    @"file", fileName)];
	}
}

- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files
{

	OFString *fileName =
	    _archiveIRI.IRIByDeletingPathExtension.lastPathComponent;

	if (files.count > 0) {
		[OFStdErr writeLine: OF_LOCALIZED(
		    @"cannot_print_specific_file_from_gz",
		    @"Cannot print a specific file of a .gz archive!")];
		app->_exitStatus = 1;
		return;

Modified utils/ofarc/ZIPArchive.m from [eaf9f03d7b] to [05dbf8ce6d].

140
141
142
143
144
145
146
147

148
149
150
151
152
153
154
155
	if (partNumber > 98)
		return nil;

	if (partNumber == lastPartNumber)
		IRI = _archiveIRI;
	else {
		OFMutableIRI *copy = [[_archiveIRI mutableCopy] autorelease];
		copy.path = [_archiveIRI.path.stringByDeletingPathExtension

		    stringByAppendingFormat: @".z%02u", partNumber + 1];
		[copy makeImmutable];
		IRI = copy;
	}

	return (OFSeekableStream *)[OFIRIHandler openItemAtIRI: IRI mode: @"r"];
}








|
>
|







140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
	if (partNumber > 98)
		return nil;

	if (partNumber == lastPartNumber)
		IRI = _archiveIRI;
	else {
		OFMutableIRI *copy = [[_archiveIRI mutableCopy] autorelease];
		[copy deletePathExtension];
		[copy appendPathExtension: [OFString
		    stringWithFormat: @".z%02u", partNumber + 1]];
		[copy makeImmutable];
		IRI = copy;
	}

	return (OFSeekableStream *)[OFIRIHandler openItemAtIRI: IRI mode: @"r"];
}