Line data Source code
1 0 : /*
2 :
3 : OOPListParsing.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 :
26 : #import "OOPListParsing.h"
27 : #import "OOLogging.h"
28 : #import "OOStringParsing.h"
29 : #import "NSDataOOExtensions.h"
30 : #include <ctype.h>
31 : #include <string.h>
32 :
33 :
34 : #if !OOLITE_GNUSTEP
35 0 : #define NO_DYNAMIC_PLIST_DTD_CHANGE
36 : #endif
37 :
38 :
39 0 : static NSString * const kOOLogPListFoundationParseError = @"plist.parse.failed";
40 0 : static NSString * const kOOLogPListWrongType = @"plist.wrongType";
41 :
42 :
43 : #ifndef NO_DYNAMIC_PLIST_DTD_CHANGE
44 : static NSData *ChangeDTDIfApplicable(NSData *data);
45 : #endif
46 :
47 : static NSData *CopyDataFromFile(NSString *path);
48 : static id ValueIfClass(id value, Class class);
49 :
50 :
51 0 : id OOPropertyListFromData(NSData *data, NSString *whereFrom)
52 : {
53 : id result = nil;
54 : NSString *error = nil;
55 :
56 : if (data != nil)
57 : {
58 : #ifndef NO_DYNAMIC_PLIST_DTD_CHANGE
59 : data = ChangeDTDIfApplicable(data);
60 : #endif
61 :
62 : result = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&error];
63 : if (result == nil) // Foundation parser failed
64 : {
65 : #if OOLITE_RELEASE_PLIST_ERROR_STRINGS
66 : [error autorelease];
67 : #endif
68 : // Ensure we can say something sensible...
69 : if (error == nil) error = @"<no error message>";
70 : if (whereFrom == nil) whereFrom = @"<data in memory>";
71 :
72 : OOLog(kOOLogPListFoundationParseError, @"Failed to parse %@ as a property list.\n%@", whereFrom, error);
73 : }
74 : }
75 :
76 : return result;
77 : }
78 :
79 :
80 0 : id OOPropertyListFromFile(NSString *path)
81 : {
82 : id result = nil;
83 : NSData *data = nil;
84 :
85 : if (path != nil)
86 : {
87 : // Load file, if it exists...
88 : data = CopyDataFromFile(path);
89 : if (data != nil)
90 : {
91 : // ...and parse it
92 : result = OOPropertyListFromData(data, path);
93 : [data release];
94 : }
95 : // Non-existent file is not an error.
96 : }
97 :
98 : return result;
99 : }
100 :
101 :
102 : #ifndef NO_DYNAMIC_PLIST_DTD_CHANGE
103 : static NSData *ChangeDTDIfApplicable(NSData *data)
104 : {
105 : const uint8_t *bytes = NULL;
106 : uint8_t *newBytes = NULL;
107 : size_t length,
108 : newLength,
109 : offset = 0,
110 : newOffset = 0;
111 : const char xmlDeclLine[] = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>";
112 : const char *appleDTDLines[] =
113 : {
114 : "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">",
115 : "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">",
116 : NULL
117 : };
118 : const char gstepDTDLine[] = "<!DOCTYPE plist PUBLIC \"-//GNUstep//DTD plist 0.9//EN\" \"http://www.gnustep.org/plist-0_9.xml\">";
119 : const char *srcDTDLine = NULL;
120 : size_t srcDTDLineSize = 0;
121 : unsigned i;
122 :
123 : length = [data length];
124 : if (length < sizeof xmlDeclLine) return data;
125 :
126 : bytes = [data bytes];
127 :
128 : // Check if it starts with an XML declaration. Bogus: there are valid XML declarations which don't match xmlDeclLine.
129 : if (memcmp(bytes, xmlDeclLine, sizeof xmlDeclLine - 1) != 0) return data;
130 :
131 : offset += sizeof xmlDeclLine - 1;
132 : while (offset < length && isspace(bytes[offset])) ++offset;
133 :
134 : // Check if first non-blank stuff after XML declaration is any known Apple plist DTD. Also somewhat bogus.
135 : for (i = 0; ; i++)
136 : {
137 : srcDTDLine = appleDTDLines[i];
138 : if (srcDTDLine == NULL) return data; // No matches
139 :
140 : srcDTDLineSize = strlen(appleDTDLines[i]);
141 :
142 : if (srcDTDLineSize <= length - offset &&
143 : memcmp(bytes + offset, srcDTDLine, srcDTDLineSize) == 0)
144 : {
145 : // Match
146 : break;
147 : }
148 : }
149 :
150 : offset += srcDTDLineSize;
151 :
152 : newLength = length - offset + sizeof xmlDeclLine + sizeof gstepDTDLine - 1;
153 : newBytes = malloc(newLength);
154 : if (newBytes == NULL) return data;
155 :
156 : // Construct modified version with altered DTD line
157 : memcpy(newBytes, xmlDeclLine, sizeof xmlDeclLine - 1);
158 : newOffset = sizeof xmlDeclLine - 1;
159 : newBytes[newOffset++] = '\n';
160 : memcpy(newBytes + newOffset, gstepDTDLine, sizeof gstepDTDLine - 1);
161 : newOffset += sizeof gstepDTDLine - 1;
162 : memcpy(newBytes + newOffset, bytes + offset, length - offset);
163 :
164 : return [NSData dataWithBytes:newBytes length:newLength];
165 : }
166 : #endif
167 :
168 :
169 : /* Load data from file. Returns a retained pointer.
170 : -initWithContentsOfMappedFile fails quietly under OS X if there's no file,
171 : but GNUstep complains.
172 : */
173 0 : static NSData *CopyDataFromFile(NSString *path)
174 : {
175 : return [[NSData oo_dataWithOXZFile:path] retain];
176 : #if 0
177 : // without OXZ extension. Code to be deleted once everything is working
178 : #if OOLITE_MAC_OS_X
179 : return [[NSData alloc] initWithContentsOfMappedFile:path];
180 : #else
181 : NSFileManager *fmgr = [NSFileManager defaultManager];
182 : BOOL dir;
183 :
184 : if ([fmgr fileExistsAtPath:path isDirectory:&dir])
185 : {
186 : if (!dir)
187 : {
188 : return [[NSData alloc] initWithContentsOfMappedFile:path];
189 : }
190 : else
191 : {
192 : OOLog(kOOLogFileNotFound, @"Expected property list but found directory at %@", path);
193 : }
194 : }
195 :
196 : return nil;
197 : #endif
198 : #endif
199 : }
200 : // Wrappers which ensure that the plist contains the right type of object.
201 0 : NSDictionary *OODictionaryFromData(NSData *data, NSString *whereFrom)
202 : {
203 : id result = OOPropertyListFromData(data, whereFrom);
204 : return ValueIfClass(result, [NSDictionary class]);
205 : }
206 :
207 :
208 0 : NSDictionary *OODictionaryFromFile(NSString *path)
209 : {
210 : id result = OOPropertyListFromFile(path);
211 : return ValueIfClass(result, [NSDictionary class]);
212 : }
213 :
214 :
215 0 : NSArray *OOArrayFromData(NSData *data, NSString *whereFrom)
216 : {
217 : id result = OOPropertyListFromData(data, whereFrom);
218 : return ValueIfClass(result, [NSArray class]);
219 : }
220 :
221 :
222 0 : NSArray *OOArrayFromFile(NSString *path)
223 : {
224 : id result = OOPropertyListFromFile(path);
225 : return ValueIfClass(result, [NSArray class]);
226 : }
227 :
228 :
229 : // Ensure that object is of desired class.
230 0 : static id ValueIfClass(id value, Class class)
231 : {
232 : if (value != nil && ![value isKindOfClass:class])
233 : {
234 : OOLog(kOOLogPListWrongType, @"Property list is wrong type - expected %@, got %@.", class, [value class]);
235 : value = nil;
236 : }
237 : return value;
238 : }
|