├── Example Web App ├── Classes │ ├── WebApp.h │ └── WebApp.m ├── Frameworks │ └── .DS_Store ├── Prefix.pch ├── Resources │ ├── Info.plist │ ├── index.wat │ └── public │ │ └── .gitignore ├── WebApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── tom.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── WorkspaceSettings.xcsettings │ ├── tom.mode1v3 │ ├── tom.pbxuser │ └── xcuserdata │ │ └── tom.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints.xcbkptlist │ │ └── xcschemes │ │ ├── WebApp.xcscheme │ │ └── xcschememanagement.plist └── main.m ├── LICENSE.txt ├── README └── WebAppKit Framework ├── FMDatabase ├── FMDatabase.h ├── FMDatabase.m ├── FMDatabaseAdditions.h ├── FMDatabaseAdditions.m ├── FMDatabasePool.h ├── FMDatabasePool.m ├── FMDatabaseQueue.h ├── FMDatabaseQueue.m ├── FMResultSet.h └── FMResultSet.m ├── Info.plist ├── Resources ├── 404.html ├── Exception.wat └── MediaTypes.plist ├── Source ├── .DS_Store ├── AsyncSocket │ ├── GCDAsyncSocket.h │ └── GCDAsyncSocket.m ├── TFStringScanner │ ├── TFStringScanner.h │ └── TFStringScanner.m ├── TL │ ├── TL.h │ ├── TL.m │ ├── TLAssignment.h │ ├── TLAssignment.m │ ├── TLCompoundStatement.h │ ├── TLCompoundStatement.m │ ├── TLConditional.h │ ├── TLConditional.m │ ├── TLExpression.h │ ├── TLExpression.m │ ├── TLForeachLoop.h │ ├── TLForeachLoop.m │ ├── TLIdentifier.h │ ├── TLIdentifier.m │ ├── TLMethodInvocation.h │ ├── TLMethodInvocation.m │ ├── TLObject.h │ ├── TLObject.m │ ├── TLOperation.h │ ├── TLOperation.m │ ├── TLScope.h │ ├── TLScope.m │ ├── TLStatement.h │ ├── TLStatement.m │ ├── TLWhileLoop.h │ └── TLWhileLoop.m ├── WAApplication.h ├── WAApplication.m ├── WACookie.h ├── WACookie.m ├── WADirectoryHandler.h ├── WADirectoryHandler.m ├── WAFoundationExtras.h ├── WAFoundationExtras.m ├── WAHTTPSupport.h ├── WAHTTPSupport.m ├── WALegacy.h ├── WALegacy.m ├── WALocalization.h ├── WALocalization.m ├── WAMultipartPart.h ├── WAMultipartPart.m ├── WAMultipartReader.h ├── WAMultipartReader.m ├── WARequest.h ├── WARequest.m ├── WARequestHandler.h ├── WARequestHandler.m ├── WAResponse.h ├── WAResponse.m ├── WARoute.h ├── WARoute.m ├── WAServer.h ├── WAServer.m ├── WAServerConnection.h ├── WAServerConnection.m ├── WASession.h ├── WASession.m ├── WASessionGenerator.h ├── WASessionGenerator.m ├── WAStaticFileHandler.h ├── WAStaticFileHandler.m ├── WATemplate.h ├── WATemplate.m ├── WAUploadedFile.h ├── WAUploadedFile.m ├── WAUtilities.h ├── WAUtilities.m └── WebAppKit.h ├── TFCoreDataExtras ├── TFCoreDataExtras.h └── TFCoreDataExtras.m ├── WAPrivate.h ├── WebAppKit.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── WebAppKit_Prefix.pch /Example Web App/Classes/WebApp.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface WebApp : WAApplication 4 | @end -------------------------------------------------------------------------------- /Example Web App/Classes/WebApp.m: -------------------------------------------------------------------------------- 1 | #import "WebApp.h" 2 | 3 | @implementation WebApp 4 | 5 | 6 | - (id)init { 7 | if((self = [super init])) { 8 | [self addRouteSelector:@selector(index) HTTPMethod:@"GET" path:@"/"]; 9 | } 10 | return self; 11 | } 12 | 13 | 14 | - (id)index { 15 | WATemplate *template = [WATemplate templateNamed:@"index"]; // Use index.wat 16 | [template setValue:@"hello world" forKey:@"foo"]; 17 | return template; 18 | } 19 | 20 | 21 | @end -------------------------------------------------------------------------------- /Example Web App/Frameworks/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasf/WebAppKit/6529c105c5794d77b83f6e784a57587a5e27f2c5/Example Web App/Frameworks/.DS_Store -------------------------------------------------------------------------------- /Example Web App/Prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #import 4 | #import 5 | #endif -------------------------------------------------------------------------------- /Example Web App/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | ${EXECUTABLE_NAME} 7 | CFBundleIdentifier 8 | com.example.webapp 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | NSPrincipalClass 16 | WebApp 17 | WAHTTPServerExternalAccess 18 | 19 | WAHTTPServerPort 20 | 9090 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example Web App/Resources/index.wat: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Web App 5 | 6 | 7 | 8 |

Welcome

9 | Hey, it works! The string that was set as 'foo' is: <%print foo.HTML> 10 | 11 | 12 | -------------------------------------------------------------------------------- /Example Web App/Resources/public/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 11 | E2AD46F2140D4262008CF796 /* index.wat in Resources */ = {isa = PBXBuildFile; fileRef = E2AD46F1140D4262008CF796 /* index.wat */; }; 12 | E2AD46F4140D4405008CF796 /* WebAppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2AD46F3140D4405008CF796 /* WebAppKit.framework */; }; 13 | E2AD46FB140D4586008CF796 /* WebAppKit.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = E2AD46F3140D4405008CF796 /* WebAppKit.framework */; }; 14 | E2EAED4012B9307200097E0B /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2EAED3F12B9307200097E0B /* CoreData.framework */; }; 15 | E2EAED4212B9307200097E0B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2EAED4112B9307200097E0B /* Foundation.framework */; }; 16 | E2EAED4912B930A200097E0B /* WebApp.m in Sources */ = {isa = PBXBuildFile; fileRef = E2EAED4812B930A200097E0B /* WebApp.m */; }; 17 | E2EAEFAD12B9949300097E0B /* public in Resources */ = {isa = PBXBuildFile; fileRef = E2EAEFAA12B9949300097E0B /* public */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXCopyFilesBuildPhase section */ 21 | E2CF96CC135BA000009BA912 /* Copy Frameworks */ = { 22 | isa = PBXCopyFilesBuildPhase; 23 | buildActionMask = 2147483647; 24 | dstPath = ""; 25 | dstSubfolderSpec = 10; 26 | files = ( 27 | E2AD46FB140D4586008CF796 /* WebAppKit.framework in Copy Frameworks */, 28 | ); 29 | name = "Copy Frameworks"; 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXCopyFilesBuildPhase section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 256AC3F00F4B6AF500CF3369 /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = ""; }; 36 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 8D1107320486CEB800E47090 /* WebApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | E2AD46F1140D4262008CF796 /* index.wat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = index.wat; sourceTree = ""; }; 40 | E2AD46F3140D4405008CF796 /* WebAppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebAppKit.framework; path = Frameworks/WebAppKit.framework; sourceTree = SOURCE_ROOT; }; 41 | E2EAED3F12B9307200097E0B /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 42 | E2EAED4112B9307200097E0B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | E2EAED4712B930A200097E0B /* WebApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebApp.h; sourceTree = ""; }; 44 | E2EAED4812B930A200097E0B /* WebApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebApp.m; sourceTree = ""; }; 45 | E2EAEFAA12B9949300097E0B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | E2EAED4012B9307200097E0B /* CoreData.framework in Frameworks */, 54 | E2EAED4212B9307200097E0B /* Foundation.framework in Frameworks */, 55 | E2AD46F4140D4405008CF796 /* WebAppKit.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 080E96DDFE201D6D7F000001 /* Classes */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | E2EAED4712B930A200097E0B /* WebApp.h */, 66 | E2EAED4812B930A200097E0B /* WebApp.m */, 67 | ); 68 | path = Classes; 69 | sourceTree = SOURCE_ROOT; 70 | }; 71 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 8D1107320486CEB800E47090 /* WebApp.app */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | 29B97314FDCFA39411CA2CEA /* WebTest */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 080E96DDFE201D6D7F000001 /* Classes */, 83 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 84 | 29B97317FDCFA39411CA2CEA /* Resources */, 85 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 86 | 19C28FACFE9D520D11CA2CBB /* Products */, 87 | ); 88 | name = WebTest; 89 | sourceTree = ""; 90 | }; 91 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 256AC3F00F4B6AF500CF3369 /* Prefix.pch */, 95 | 29B97316FDCFA39411CA2CEA /* main.m */, 96 | ); 97 | name = "Other Sources"; 98 | sourceTree = ""; 99 | }; 100 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | E2EAEFAA12B9949300097E0B /* public */, 104 | 8D1107310486CEB800E47090 /* Info.plist */, 105 | E2AD46F1140D4262008CF796 /* index.wat */, 106 | ); 107 | path = Resources; 108 | sourceTree = SOURCE_ROOT; 109 | }; 110 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | E2AD46F3140D4405008CF796 /* WebAppKit.framework */, 114 | E2EAED3F12B9307200097E0B /* CoreData.framework */, 115 | E2EAED4112B9307200097E0B /* Foundation.framework */, 116 | ); 117 | name = Frameworks; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | 8D1107260486CEB800E47090 /* WebApp */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "WebApp" */; 126 | buildPhases = ( 127 | 8D1107290486CEB800E47090 /* Resources */, 128 | 8D11072C0486CEB800E47090 /* Sources */, 129 | 8D11072E0486CEB800E47090 /* Frameworks */, 130 | E2CF96CC135BA000009BA912 /* Copy Frameworks */, 131 | ); 132 | buildRules = ( 133 | ); 134 | dependencies = ( 135 | ); 136 | name = WebApp; 137 | productInstallPath = "$(HOME)/Applications"; 138 | productName = WebTest; 139 | productReference = 8D1107320486CEB800E47090 /* WebApp.app */; 140 | productType = "com.apple.product-type.application"; 141 | }; 142 | /* End PBXNativeTarget section */ 143 | 144 | /* Begin PBXProject section */ 145 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 146 | isa = PBXProject; 147 | attributes = { 148 | LastUpgradeCheck = 0600; 149 | }; 150 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "WebApp" */; 151 | compatibilityVersion = "Xcode 3.2"; 152 | developmentRegion = English; 153 | hasScannedForEncodings = 1; 154 | knownRegions = ( 155 | English, 156 | Japanese, 157 | French, 158 | German, 159 | ); 160 | mainGroup = 29B97314FDCFA39411CA2CEA /* WebTest */; 161 | projectDirPath = ""; 162 | projectRoot = ""; 163 | targets = ( 164 | 8D1107260486CEB800E47090 /* WebApp */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | 8D1107290486CEB800E47090 /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | E2EAEFAD12B9949300097E0B /* public in Resources */, 175 | E2AD46F2140D4262008CF796 /* index.wat in Resources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXResourcesBuildPhase section */ 180 | 181 | /* Begin PBXSourcesBuildPhase section */ 182 | 8D11072C0486CEB800E47090 /* Sources */ = { 183 | isa = PBXSourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 187 | E2EAED4912B930A200097E0B /* WebApp.m in Sources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXSourcesBuildPhase section */ 192 | 193 | /* Begin XCBuildConfiguration section */ 194 | C01FCF4B08A954540054247B /* Debug */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | ALWAYS_SEARCH_USER_PATHS = NO; 198 | CLANG_ENABLE_OBJC_ARC = YES; 199 | COMBINE_HIDPI_IMAGES = YES; 200 | COPY_PHASE_STRIP = NO; 201 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 202 | FRAMEWORK_SEARCH_PATHS = ( 203 | "$(inherited)", 204 | "\"$(SRCROOT)/Frameworks\"", 205 | "\"$(SRCROOT)\"", 206 | ); 207 | GCC_DYNAMIC_NO_PIC = NO; 208 | GCC_ENABLE_OBJC_GC = unsupported; 209 | GCC_OPTIMIZATION_LEVEL = 0; 210 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 211 | GCC_PREFIX_HEADER = Prefix.pch; 212 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG; 213 | INFOPLIST_FILE = Resources/Info.plist; 214 | PRODUCT_NAME = WebApp; 215 | WRAPPER_EXTENSION = app; 216 | }; 217 | name = Debug; 218 | }; 219 | C01FCF4C08A954540054247B /* Release */ = { 220 | isa = XCBuildConfiguration; 221 | buildSettings = { 222 | ALWAYS_SEARCH_USER_PATHS = NO; 223 | CLANG_ENABLE_OBJC_ARC = YES; 224 | COMBINE_HIDPI_IMAGES = YES; 225 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 226 | FRAMEWORK_SEARCH_PATHS = ( 227 | "$(inherited)", 228 | "\"$(SRCROOT)/Frameworks\"", 229 | "\"$(SRCROOT)\"", 230 | ); 231 | GCC_ENABLE_OBJC_GC = unsupported; 232 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 233 | GCC_PREFIX_HEADER = Prefix.pch; 234 | INFOPLIST_FILE = Resources/Info.plist; 235 | PRODUCT_NAME = WebApp; 236 | WRAPPER_EXTENSION = app; 237 | }; 238 | name = Release; 239 | }; 240 | C01FCF4F08A954540054247B /* Debug */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | GCC_C_LANGUAGE_STANDARD = gnu99; 244 | GCC_ENABLE_OBJC_GC = required; 245 | GCC_OPTIMIZATION_LEVEL = 0; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 247 | GCC_WARN_UNUSED_VARIABLE = YES; 248 | MACOSX_DEPLOYMENT_TARGET = 10.7; 249 | ONLY_ACTIVE_ARCH = YES; 250 | SDKROOT = macosx; 251 | }; 252 | name = Debug; 253 | }; 254 | C01FCF5008A954540054247B /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | GCC_C_LANGUAGE_STANDARD = gnu99; 258 | GCC_ENABLE_OBJC_GC = required; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | MACOSX_DEPLOYMENT_TARGET = 10.7; 262 | SDKROOT = macosx; 263 | }; 264 | name = Release; 265 | }; 266 | /* End XCBuildConfiguration section */ 267 | 268 | /* Begin XCConfigurationList section */ 269 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "WebApp" */ = { 270 | isa = XCConfigurationList; 271 | buildConfigurations = ( 272 | C01FCF4B08A954540054247B /* Debug */, 273 | C01FCF4C08A954540054247B /* Release */, 274 | ); 275 | defaultConfigurationIsVisible = 0; 276 | defaultConfigurationName = Release; 277 | }; 278 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "WebApp" */ = { 279 | isa = XCConfigurationList; 280 | buildConfigurations = ( 281 | C01FCF4F08A954540054247B /* Debug */, 282 | C01FCF5008A954540054247B /* Release */, 283 | ); 284 | defaultConfigurationIsVisible = 0; 285 | defaultConfigurationName = Release; 286 | }; 287 | /* End XCConfigurationList section */ 288 | }; 289 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 290 | } 291 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/project.xcworkspace/xcuserdata/tom.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasf/WebAppKit/6529c105c5794d77b83f6e784a57587a5e27f2c5/Example Web App/WebApp.xcodeproj/project.xcworkspace/xcuserdata/tom.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/project.xcworkspace/xcuserdata/tom.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceUserSettings_HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | IDEWorkspaceUserSettings_SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/tom.pbxuser: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | 256AC3F00F4B6AF500CF3369 /* ___PROJECTNAMEASIDENTIFIER___Prefix.pch */ = { 4 | uiCtxt = { 5 | sepNavIntBoundsRect = "{{0, 0}, {860, 563}}"; 6 | sepNavSelRange = "{174, 0}"; 7 | sepNavVisRange = "{0, 212}"; 8 | }; 9 | }; 10 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 11 | activeArchitecturePreference = i386; 12 | activeBuildConfigurationName = Debug; 13 | activeExecutable = E2EAED3012B9306200097E0B /* ___PROJECTNAME___ */; 14 | activeTarget = 8D1107260486CEB800E47090 /* ___PROJECTNAME___ */; 15 | addToTargets = ( 16 | 8D1107260486CEB800E47090 /* ___PROJECTNAME___ */, 17 | ); 18 | breakpoints = ( 19 | E217040D12C9836300DCE8FD /* objc_exception_throw */, 20 | ); 21 | codeSenseManager = E2EAED3D12B9306A00097E0B /* Code sense */; 22 | executables = ( 23 | E2EAED3012B9306200097E0B /* ___PROJECTNAME___ */, 24 | ); 25 | perUserDictionary = { 26 | PBXConfiguration.PBXFileTableDataSource3.PBXExecutablesDataSource = { 27 | PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; 28 | PBXFileTableDataSourceColumnSortingKey = PBXExecutablesDataSource_NameID; 29 | PBXFileTableDataSourceColumnWidthsKey = ( 30 | 22, 31 | 300, 32 | 1038, 33 | ); 34 | PBXFileTableDataSourceColumnsKey = ( 35 | PBXExecutablesDataSource_ActiveFlagID, 36 | PBXExecutablesDataSource_NameID, 37 | PBXExecutablesDataSource_CommentsID, 38 | ); 39 | }; 40 | PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { 41 | PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; 42 | PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; 43 | PBXFileTableDataSourceColumnWidthsKey = ( 44 | 20, 45 | 647, 46 | 20, 47 | 48, 48 | 43, 49 | 43, 50 | 20, 51 | ); 52 | PBXFileTableDataSourceColumnsKey = ( 53 | PBXFileDataSource_FiletypeID, 54 | PBXFileDataSource_Filename_ColumnID, 55 | PBXFileDataSource_Built_ColumnID, 56 | PBXFileDataSource_ObjectSize_ColumnID, 57 | PBXFileDataSource_Errors_ColumnID, 58 | PBXFileDataSource_Warnings_ColumnID, 59 | PBXFileDataSource_Target_ColumnID, 60 | ); 61 | }; 62 | PBXConfiguration.PBXTargetDataSource.PBXTargetDataSource = { 63 | PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; 64 | PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; 65 | PBXFileTableDataSourceColumnWidthsKey = ( 66 | 20, 67 | 607, 68 | 60, 69 | 20, 70 | 48, 71 | 43, 72 | 43, 73 | ); 74 | PBXFileTableDataSourceColumnsKey = ( 75 | PBXFileDataSource_FiletypeID, 76 | PBXFileDataSource_Filename_ColumnID, 77 | PBXTargetDataSource_PrimaryAttribute, 78 | PBXFileDataSource_Built_ColumnID, 79 | PBXFileDataSource_ObjectSize_ColumnID, 80 | PBXFileDataSource_Errors_ColumnID, 81 | PBXFileDataSource_Warnings_ColumnID, 82 | ); 83 | }; 84 | PBXPerProjectTemplateStateSaveDate = 325317398; 85 | PBXWorkspaceStateSaveDate = 325317398; 86 | }; 87 | perUserProjectItems = { 88 | E29E8A3B1363F0E7008BF9EB /* PBXTextBookmark */ = E29E8A3B1363F0E7008BF9EB /* PBXTextBookmark */; 89 | E29E8A3C1363F0E7008BF9EB /* PBXTextBookmark */ = E29E8A3C1363F0E7008BF9EB /* PBXTextBookmark */; 90 | E29E8A401363F0E7008BF9EB /* PBXTextBookmark */ = E29E8A401363F0E7008BF9EB /* PBXTextBookmark */; 91 | E29E8A411363F0E7008BF9EB /* PBXTextBookmark */ = E29E8A411363F0E7008BF9EB /* PBXTextBookmark */; 92 | E29E8A431363F0E7008BF9EB /* PBXTextBookmark */ = E29E8A431363F0E7008BF9EB /* PBXTextBookmark */; 93 | E29E8A911363F31D008BF9EB /* PBXTextBookmark */ = E29E8A911363F31D008BF9EB /* PBXTextBookmark */; 94 | E29E8A921363F31D008BF9EB /* PBXTextBookmark */ = E29E8A921363F31D008BF9EB /* PBXTextBookmark */; 95 | E29E8A9C1363F31F008BF9EB /* PBXTextBookmark */ = E29E8A9C1363F31F008BF9EB /* PBXTextBookmark */; 96 | E29E8A9D1363F31F008BF9EB /* PBXTextBookmark */ = E29E8A9D1363F31F008BF9EB /* PBXTextBookmark */; 97 | E2DA87B512D11E3D00B1EDE8 /* PlistBookmark */ = E2DA87B512D11E3D00B1EDE8 /* PlistBookmark */; 98 | E2F75AAA12CBD0B3005FD98B /* PBXTextBookmark */ = E2F75AAA12CBD0B3005FD98B /* PBXTextBookmark */; 99 | E2F75AAC12CBD0B3005FD98B /* PBXTextBookmark */ = E2F75AAC12CBD0B3005FD98B /* PBXTextBookmark */; 100 | }; 101 | sourceControlManager = E2EAED3C12B9306A00097E0B /* Source Control */; 102 | userBuildSettings = { 103 | }; 104 | }; 105 | 29B97316FDCFA39411CA2CEA /* main.m */ = { 106 | uiCtxt = { 107 | sepNavIntBoundsRect = "{{0, 0}, {837, 112}}"; 108 | sepNavSelRange = "{156, 0}"; 109 | sepNavVisRange = "{0, 0}"; 110 | }; 111 | }; 112 | 8D1107260486CEB800E47090 /* ___PROJECTNAME___ */ = { 113 | activeExec = 0; 114 | executables = ( 115 | E2EAED3012B9306200097E0B /* ___PROJECTNAME___ */, 116 | ); 117 | }; 118 | 8D1107310486CEB800E47090 /* ___PROJECTNAMEASIDENTIFIER___-Info.plist */ = { 119 | uiCtxt = { 120 | sepNavWindowFrame = "{{2598, 332}, {1449, 1082}}"; 121 | }; 122 | }; 123 | E217040D12C9836300DCE8FD /* objc_exception_throw */ = { 124 | isa = PBXSymbolicBreakpoint; 125 | actions = ( 126 | ); 127 | breakpointStyle = 1; 128 | continueAfterActions = 0; 129 | countType = 0; 130 | delayBeforeContinue = 0; 131 | hitCount = 0; 132 | ignoreCount = 0; 133 | location = libobjc.A.dylib; 134 | modificationTime = 324774659.238914; 135 | originalNumberOfMultipleMatches = 1; 136 | state = 2; 137 | symbolName = objc_exception_throw; 138 | }; 139 | E27AA75E12CB777C0023E7CE /* index.wat */ = { 140 | uiCtxt = { 141 | sepNavIntBoundsRect = "{{0, 0}, {837, 655}}"; 142 | sepNavSelRange = "{106, 0}"; 143 | sepNavVisRange = "{0, 145}"; 144 | }; 145 | }; 146 | E29E8A3B1363F0E7008BF9EB /* PBXTextBookmark */ = { 147 | isa = PBXTextBookmark; 148 | fRef = E27AA75E12CB777C0023E7CE /* index.wat */; 149 | name = "index.wat: 8"; 150 | rLen = 0; 151 | rLoc = 106; 152 | rType = 0; 153 | vrLen = 145; 154 | vrLoc = 0; 155 | }; 156 | E29E8A3C1363F0E7008BF9EB /* PBXTextBookmark */ = { 157 | isa = PBXTextBookmark; 158 | fRef = E29E8A3D1363F0E7008BF9EB /* WAApplication.h */; 159 | name = "WAApplication.h: 16"; 160 | rLen = 0; 161 | rLoc = 280; 162 | rType = 0; 163 | vrLen = 875; 164 | vrLoc = 0; 165 | }; 166 | E29E8A3D1363F0E7008BF9EB /* WAApplication.h */ = { 167 | isa = PBXFileReference; 168 | lastKnownFileType = sourcecode.c.h; 169 | name = WAApplication.h; 170 | path = "/Users/tom/Documents/WebAppKit/Source/WebAppKit Framework/build/Debug/WebAppKit.framework/Versions/A/Headers/WAApplication.h"; 171 | sourceTree = ""; 172 | }; 173 | E29E8A401363F0E7008BF9EB /* PBXTextBookmark */ = { 174 | isa = PBXTextBookmark; 175 | fRef = E2EAED4812B930A200097E0B /* ___PROJECTNAMEASIDENTIFIER___.m */; 176 | name = "___PROJECTNAMEASIDENTIFIER___.m: 18"; 177 | rLen = 0; 178 | rLoc = 308; 179 | rType = 0; 180 | vrLen = 0; 181 | vrLoc = 0; 182 | }; 183 | E29E8A411363F0E7008BF9EB /* PBXTextBookmark */ = { 184 | isa = PBXTextBookmark; 185 | fRef = E27AA75E12CB777C0023E7CE /* index.wat */; 186 | name = "index.wat: 8"; 187 | rLen = 0; 188 | rLoc = 112; 189 | rType = 0; 190 | vrLen = 0; 191 | vrLoc = 0; 192 | }; 193 | E29E8A431363F0E7008BF9EB /* PBXTextBookmark */ = { 194 | isa = PBXTextBookmark; 195 | fRef = 29B97316FDCFA39411CA2CEA /* main.m */; 196 | name = "main.m: 8"; 197 | rLen = 0; 198 | rLoc = 156; 199 | rType = 0; 200 | vrLen = 0; 201 | vrLoc = 0; 202 | }; 203 | E29E8A911363F31D008BF9EB /* PBXTextBookmark */ = { 204 | isa = PBXTextBookmark; 205 | fRef = 29B97316FDCFA39411CA2CEA /* main.m */; 206 | name = "main.m: 8"; 207 | rLen = 0; 208 | rLoc = 156; 209 | rType = 0; 210 | vrLen = 156; 211 | vrLoc = 0; 212 | }; 213 | E29E8A921363F31D008BF9EB /* PBXTextBookmark */ = { 214 | isa = PBXTextBookmark; 215 | fRef = E2EAED4812B930A200097E0B /* ___PROJECTNAMEASIDENTIFIER___.m */; 216 | name = "___PROJECTNAMEASIDENTIFIER___.m: 18"; 217 | rLen = 0; 218 | rLoc = 308; 219 | rType = 0; 220 | vrLen = 349; 221 | vrLoc = 0; 222 | }; 223 | E29E8A9C1363F31F008BF9EB /* PBXTextBookmark */ = { 224 | isa = PBXTextBookmark; 225 | fRef = E2EAED4812B930A200097E0B /* ___PROJECTNAMEASIDENTIFIER___.m */; 226 | name = "___PROJECTNAMEASIDENTIFIER___.m: 14"; 227 | rLen = 0; 228 | rLoc = 294; 229 | rType = 0; 230 | vrLen = 313; 231 | vrLoc = 0; 232 | }; 233 | E29E8A9D1363F31F008BF9EB /* PBXTextBookmark */ = { 234 | isa = PBXTextBookmark; 235 | fRef = 29B97316FDCFA39411CA2CEA /* main.m */; 236 | name = "main.m: 8"; 237 | rLen = 0; 238 | rLoc = 156; 239 | rType = 0; 240 | vrLen = 0; 241 | vrLoc = 0; 242 | }; 243 | E2DA87B512D11E3D00B1EDE8 /* PlistBookmark */ = { 244 | isa = PlistBookmark; 245 | fRef = 8D1107310486CEB800E47090 /* ___PROJECTNAMEASIDENTIFIER___-Info.plist */; 246 | fallbackIsa = PBXBookmark; 247 | isK = 0; 248 | kPath = ( 249 | ); 250 | name = "/Users/tom/Desktop/untitled folder/Web Application/Resources/___PROJECTNAMEASIDENTIFIER___-Info.plist"; 251 | rLen = 0; 252 | rLoc = 9223372036854775808; 253 | }; 254 | E2EAED3012B9306200097E0B /* ___PROJECTNAME___ */ = { 255 | isa = PBXExecutable; 256 | activeArgIndices = ( 257 | ); 258 | argumentStrings = ( 259 | ); 260 | autoAttachOnCrash = 1; 261 | breakpointsEnabled = 1; 262 | configStateDict = { 263 | }; 264 | customDataFormattersEnabled = 1; 265 | dataTipCustomDataFormattersEnabled = 1; 266 | dataTipShowTypeColumn = 1; 267 | dataTipSortType = 0; 268 | debuggerPlugin = GDBDebugging; 269 | disassemblyDisplayState = 0; 270 | dylibVariantSuffix = ""; 271 | enableDebugStr = 1; 272 | environmentEntries = ( 273 | { 274 | active = YES; 275 | name = NSZombieEnabled; 276 | value = YES; 277 | }, 278 | ); 279 | executableSystemSymbolLevel = 0; 280 | executableUserSymbolLevel = 0; 281 | libgmallocEnabled = 0; 282 | name = "___PROJECTNAME___"; 283 | savedGlobals = { 284 | }; 285 | showTypeColumn = 0; 286 | sourceDirectories = ( 287 | ); 288 | variableFormatDictionary = { 289 | }; 290 | }; 291 | E2EAED3C12B9306A00097E0B /* Source Control */ = { 292 | isa = PBXSourceControlManager; 293 | fallbackIsa = XCSourceControlManager; 294 | isSCMEnabled = 0; 295 | scmConfiguration = { 296 | repositoryNamesForRoots = { 297 | "" = ""; 298 | }; 299 | }; 300 | }; 301 | E2EAED3D12B9306A00097E0B /* Code sense */ = { 302 | isa = PBXCodeSenseManager; 303 | indexTemplatePath = ""; 304 | }; 305 | E2EAED4712B930A200097E0B /* ___PROJECTNAMEASIDENTIFIER___.h */ = { 306 | uiCtxt = { 307 | sepNavIntBoundsRect = "{{0, 0}, {860, 563}}"; 308 | sepNavSelRange = "{66, 0}"; 309 | sepNavVisRange = "{0, 67}"; 310 | }; 311 | }; 312 | E2EAED4812B930A200097E0B /* ___PROJECTNAMEASIDENTIFIER___.m */ = { 313 | uiCtxt = { 314 | sepNavIntBoundsRect = "{{0, 0}, {837, 655}}"; 315 | sepNavSelRange = "{294, 0}"; 316 | sepNavVisRange = "{0, 313}"; 317 | }; 318 | }; 319 | E2F75AAA12CBD0B3005FD98B /* PBXTextBookmark */ = { 320 | isa = PBXTextBookmark; 321 | fRef = 256AC3F00F4B6AF500CF3369 /* ___PROJECTNAMEASIDENTIFIER___Prefix.pch */; 322 | name = "___PROJECTNAMEASIDENTIFIER___Prefix.pch: 8"; 323 | rLen = 0; 324 | rLoc = 174; 325 | rType = 0; 326 | vrLen = 212; 327 | vrLoc = 0; 328 | }; 329 | E2F75AAC12CBD0B3005FD98B /* PBXTextBookmark */ = { 330 | isa = PBXTextBookmark; 331 | fRef = E2EAED4712B930A200097E0B /* ___PROJECTNAMEASIDENTIFIER___.h */; 332 | name = "___PROJECTNAMEASIDENTIFIER___.h: 4"; 333 | rLen = 0; 334 | rLoc = 66; 335 | rType = 0; 336 | vrLen = 67; 337 | vrLoc = 0; 338 | }; 339 | } 340 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/xcuserdata/tom.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/xcuserdata/tom.xcuserdatad/xcschemes/WebApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 14 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Example Web App/WebApp.xcodeproj/xcuserdata/tom.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WebApp.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 8D1107260486CEB800E47090 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example Web App/main.m: -------------------------------------------------------------------------------- 1 | #import "WebApp.h" 2 | 3 | int main(int argc, char *argv[]) { 4 | #ifdef DEBUG 5 | WASetDevelopmentMode(YES); 6 | #endif 7 | return WAApplicationMain(); 8 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2011 WebAppKit contributors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of 7 | conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 10 | of conditions and the following disclaimer in the documentation and/or other materials 11 | provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED 14 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 16 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 17 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 21 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | The views and conclusions contained in the software and documentation are those of the 24 | authors and should not be interpreted as representing official policies, either expressed 25 | or implied, of the WebAppKit project. -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | WebAppKit is a framework for creating web applications using Cocoa and Objective-C, running on Mac OS X. -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabase.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "sqlite3.h" 3 | #import "FMResultSet.h" 4 | #import "FMDatabasePool.h" 5 | 6 | 7 | #if ! __has_feature(objc_arc) 8 | #define FMDBAutorelease(__v) ([__v autorelease]); 9 | #define FMDBReturnAutoreleased FMDBAutorelease 10 | 11 | #define FMDBRetain(__v) ([__v retain]); 12 | #define FMDBReturnRetained FMDBRetain 13 | 14 | #define FMDBRelease(__v) ([__v release]); 15 | #else 16 | // -fobjc-arc 17 | #define FMDBAutorelease(__v) 18 | #define FMDBReturnAutoreleased(__v) (__v) 19 | 20 | #define FMDBRetain(__v) 21 | #define FMDBReturnRetained(__v) (__v) 22 | 23 | #define FMDBRelease(__v) 24 | #endif 25 | 26 | 27 | @interface FMDatabase : NSObject { 28 | 29 | sqlite3* _db; 30 | NSString* _databasePath; 31 | BOOL _logsErrors; 32 | BOOL _crashOnErrors; 33 | BOOL _traceExecution; 34 | BOOL _checkedOut; 35 | BOOL _shouldCacheStatements; 36 | BOOL _isExecutingStatement; 37 | BOOL _inTransaction; 38 | int _busyRetryTimeout; 39 | 40 | NSMutableDictionary *_cachedStatements; 41 | NSMutableSet *_openResultSets; 42 | NSMutableSet *_openFunctions; 43 | 44 | } 45 | 46 | 47 | @property (assign) BOOL traceExecution; 48 | @property (assign) BOOL checkedOut; 49 | @property (assign) int busyRetryTimeout; 50 | @property (assign) BOOL crashOnErrors; 51 | @property (assign) BOOL logsErrors; 52 | @property (retain) NSMutableDictionary *cachedStatements; 53 | 54 | 55 | + (id)databaseWithPath:(NSString*)inPath; 56 | - (id)initWithPath:(NSString*)inPath; 57 | 58 | - (BOOL)open; 59 | #if SQLITE_VERSION_NUMBER >= 3005000 60 | - (BOOL)openWithFlags:(int)flags; 61 | #endif 62 | - (BOOL)close; 63 | - (BOOL)goodConnection; 64 | - (void)clearCachedStatements; 65 | - (void)closeOpenResultSets; 66 | - (BOOL)hasOpenResultSets; 67 | 68 | // encryption methods. You need to have purchased the sqlite encryption extensions for these to work. 69 | - (BOOL)setKey:(NSString*)key; 70 | - (BOOL)rekey:(NSString*)key; 71 | 72 | - (NSString *)databasePath; 73 | 74 | - (NSString*)lastErrorMessage; 75 | 76 | - (int)lastErrorCode; 77 | - (BOOL)hadError; 78 | - (NSError*)lastError; 79 | 80 | - (sqlite_int64)lastInsertRowId; 81 | 82 | - (sqlite3*)sqliteHandle; 83 | 84 | - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...; 85 | - (BOOL)executeUpdate:(NSString*)sql, ...; 86 | - (BOOL)executeUpdateWithFormat:(NSString *)format, ...; 87 | - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; 88 | - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments; 89 | 90 | - (FMResultSet *)executeQuery:(NSString*)sql, ...; 91 | - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...; 92 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; 93 | - (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments; 94 | 95 | - (BOOL)rollback; 96 | - (BOOL)commit; 97 | - (BOOL)beginTransaction; 98 | - (BOOL)beginDeferredTransaction; 99 | - (BOOL)inTransaction; 100 | - (BOOL)shouldCacheStatements; 101 | - (void)setShouldCacheStatements:(BOOL)value; 102 | 103 | #if SQLITE_VERSION_NUMBER >= 3007000 104 | - (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr; 105 | - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr; 106 | - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr; 107 | - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block; 108 | #endif 109 | 110 | + (BOOL)isSQLiteThreadSafe; 111 | + (NSString*)sqliteLibVersion; 112 | 113 | - (int)changes; 114 | 115 | - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block; 116 | 117 | @end 118 | 119 | @interface FMStatement : NSObject { 120 | sqlite3_stmt *_statement; 121 | NSString *_query; 122 | long _useCount; 123 | } 124 | 125 | @property (assign) long useCount; 126 | @property (retain) NSString *query; 127 | @property (assign) sqlite3_stmt *statement; 128 | 129 | - (void)close; 130 | - (void)reset; 131 | 132 | @end 133 | 134 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.h 3 | // fmkit 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import 10 | @interface FMDatabase (FMDatabaseAdditions) 11 | 12 | 13 | - (int)intForQuery:(NSString*)objs, ...; 14 | - (long)longForQuery:(NSString*)objs, ...; 15 | - (BOOL)boolForQuery:(NSString*)objs, ...; 16 | - (double)doubleForQuery:(NSString*)objs, ...; 17 | - (NSString*)stringForQuery:(NSString*)objs, ...; 18 | - (NSData*)dataForQuery:(NSString*)objs, ...; 19 | - (NSDate*)dateForQuery:(NSString*)objs, ...; 20 | 21 | // Notice that there's no dataNoCopyForQuery:. 22 | // That would be a bad idea, because we close out the result set, and then what 23 | // happens to the data that we just didn't copy? Who knows, not I. 24 | 25 | 26 | - (BOOL)tableExists:(NSString*)tableName; 27 | - (FMResultSet*)getSchema; 28 | - (FMResultSet*)getTableSchema:(NSString*)tableName; 29 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName; 30 | 31 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabaseAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.m 3 | // fmkit 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import "FMDatabase.h" 10 | #import "FMDatabaseAdditions.h" 11 | 12 | @interface FMDatabase (PrivateStuff) 13 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 14 | @end 15 | 16 | @implementation FMDatabase (FMDatabaseAdditions) 17 | 18 | #define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ 19 | va_list args; \ 20 | va_start(args, query); \ 21 | FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \ 22 | va_end(args); \ 23 | if (![resultSet next]) { return (type)0; } \ 24 | type ret = [resultSet sel:0]; \ 25 | [resultSet close]; \ 26 | [resultSet setParentDB:nil]; \ 27 | return ret; 28 | 29 | 30 | - (NSString*)stringForQuery:(NSString*)query, ... { 31 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); 32 | } 33 | 34 | - (int)intForQuery:(NSString*)query, ... { 35 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); 36 | } 37 | 38 | - (long)longForQuery:(NSString*)query, ... { 39 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); 40 | } 41 | 42 | - (BOOL)boolForQuery:(NSString*)query, ... { 43 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); 44 | } 45 | 46 | - (double)doubleForQuery:(NSString*)query, ... { 47 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); 48 | } 49 | 50 | - (NSData*)dataForQuery:(NSString*)query, ... { 51 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); 52 | } 53 | 54 | - (NSDate*)dateForQuery:(NSString*)query, ... { 55 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); 56 | } 57 | 58 | 59 | - (BOOL)tableExists:(NSString*)tableName { 60 | 61 | tableName = [tableName lowercaseString]; 62 | 63 | FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; 64 | 65 | //if at least one next exists, table exists 66 | BOOL returnBool = [rs next]; 67 | 68 | //close and free object 69 | [rs close]; 70 | 71 | return returnBool; 72 | } 73 | 74 | /* 75 | get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 76 | check if table exist in database (patch from OZLB) 77 | */ 78 | - (FMResultSet*)getSchema { 79 | 80 | //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 81 | FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"]; 82 | 83 | return rs; 84 | } 85 | 86 | /* 87 | get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 88 | */ 89 | - (FMResultSet*)getTableSchema:(NSString*)tableName { 90 | 91 | //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 92 | FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"PRAGMA table_info(%@)", tableName]]; 93 | 94 | return rs; 95 | } 96 | 97 | 98 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName { 99 | 100 | BOOL returnBool = NO; 101 | 102 | tableName = [tableName lowercaseString]; 103 | columnName = [columnName lowercaseString]; 104 | 105 | FMResultSet *rs = [self getTableSchema:tableName]; 106 | 107 | //check if column is present in table schema 108 | while ([rs next]) { 109 | if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString: columnName]) { 110 | returnBool = YES; 111 | break; 112 | } 113 | } 114 | 115 | //If this is not done FMDatabase instance stays out of pool 116 | [rs close]; 117 | 118 | return returnBool; 119 | } 120 | 121 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error { 122 | sqlite3_stmt *pStmt = NULL; 123 | BOOL validationSucceeded = YES; 124 | BOOL keepTrying = YES; 125 | int numberOfRetries = 0; 126 | 127 | while (keepTrying == YES) { 128 | keepTrying = NO; 129 | int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 130 | if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { 131 | keepTrying = YES; 132 | usleep(20); 133 | 134 | if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { 135 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); 136 | NSLog(@"Database busy"); 137 | } 138 | } 139 | else if (rc != SQLITE_OK) { 140 | validationSucceeded = NO; 141 | if (error) { 142 | *error = [NSError errorWithDomain:NSCocoaErrorDomain 143 | code:[self lastErrorCode] 144 | userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 145 | forKey:NSLocalizedDescriptionKey]]; 146 | } 147 | } 148 | } 149 | 150 | sqlite3_finalize(pStmt); 151 | 152 | return validationSucceeded; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "sqlite3.h" 11 | 12 | /* 13 | 14 | ***README OR SUFFER*** 15 | Before using FMDatabasePool, please consider using FMDatabaseQueue instead. 16 | 17 | If you really really really know what you're doing and FMDatabasePool is what 18 | you really really need (ie, you're using a read only database), OK you can use 19 | it. But just be careful not to deadlock! 20 | 21 | For an example on deadlocking, search for: 22 | ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD 23 | in the main.m file. 24 | 25 | */ 26 | 27 | 28 | 29 | @class FMDatabase; 30 | 31 | @interface FMDatabasePool : NSObject { 32 | NSString *_path; 33 | 34 | dispatch_queue_t _lockQueue; 35 | 36 | NSMutableArray *_databaseInPool; 37 | NSMutableArray *_databaseOutPool; 38 | 39 | __unsafe_unretained id _delegate; 40 | 41 | NSUInteger _maximumNumberOfDatabasesToCreate; 42 | } 43 | 44 | @property (retain) NSString *path; 45 | @property (assign) id delegate; 46 | @property (assign) NSUInteger maximumNumberOfDatabasesToCreate; 47 | 48 | + (id)databasePoolWithPath:(NSString*)aPath; 49 | - (id)initWithPath:(NSString*)aPath; 50 | 51 | - (NSUInteger)countOfCheckedInDatabases; 52 | - (NSUInteger)countOfCheckedOutDatabases; 53 | - (NSUInteger)countOfOpenDatabases; 54 | - (void)releaseAllDatabases; 55 | 56 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 57 | 58 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 59 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 60 | 61 | #if SQLITE_VERSION_NUMBER >= 3007000 62 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 63 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 64 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 65 | #endif 66 | 67 | @end 68 | 69 | 70 | @interface NSObject (FMDatabasePoolDelegate) 71 | 72 | - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database; 73 | 74 | @end 75 | 76 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabasePool.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabasePool.h" 10 | #import "FMDatabase.h" 11 | 12 | @interface FMDatabasePool() 13 | 14 | - (void)pushDatabaseBackInPool:(FMDatabase*)db; 15 | - (FMDatabase*)db; 16 | 17 | @end 18 | 19 | 20 | @implementation FMDatabasePool 21 | @synthesize path=_path; 22 | @synthesize delegate=_delegate; 23 | @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate; 24 | 25 | 26 | + (id)databasePoolWithPath:(NSString*)aPath { 27 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); 28 | } 29 | 30 | - (id)initWithPath:(NSString*)aPath { 31 | 32 | self = [super init]; 33 | 34 | if (self != nil) { 35 | _path = [aPath copy]; 36 | _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 37 | _databaseInPool = FMDBReturnRetained([NSMutableArray array]); 38 | _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)dealloc { 45 | 46 | _delegate = 0x00; 47 | FMDBRelease(_path); 48 | FMDBRelease(_databaseInPool); 49 | FMDBRelease(_databaseOutPool); 50 | 51 | if (_lockQueue) { 52 | dispatch_release(_lockQueue); 53 | _lockQueue = 0x00; 54 | } 55 | #if ! __has_feature(objc_arc) 56 | [super dealloc]; 57 | #endif 58 | } 59 | 60 | 61 | - (void)executeLocked:(void (^)(void))aBlock { 62 | dispatch_sync(_lockQueue, aBlock); 63 | } 64 | 65 | - (void)pushDatabaseBackInPool:(FMDatabase*)db { 66 | 67 | if (!db) { // db can be null if we set an upper bound on the # of databases to create. 68 | return; 69 | } 70 | 71 | [self executeLocked:^() { 72 | 73 | if ([_databaseInPool containsObject:db]) { 74 | [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; 75 | } 76 | 77 | [_databaseInPool addObject:db]; 78 | [_databaseOutPool removeObject:db]; 79 | 80 | }]; 81 | } 82 | 83 | - (FMDatabase*)db { 84 | 85 | __block FMDatabase *db; 86 | 87 | [self executeLocked:^() { 88 | db = [_databaseInPool lastObject]; 89 | 90 | if (db) { 91 | [_databaseOutPool addObject:db]; 92 | [_databaseInPool removeLastObject]; 93 | } 94 | else { 95 | 96 | if (_maximumNumberOfDatabasesToCreate) { 97 | NSUInteger currentCount = [_databaseOutPool count] + [_databaseInPool count]; 98 | 99 | if (currentCount >= _maximumNumberOfDatabasesToCreate) { 100 | NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); 101 | return; 102 | } 103 | } 104 | 105 | db = [FMDatabase databaseWithPath:_path]; 106 | } 107 | 108 | //This ensures that the db is opened before returning 109 | if ([db open]) { 110 | if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) { 111 | [db close]; 112 | db = 0x00; 113 | } 114 | else { 115 | //It should not get added in the pool twice if lastObject was found 116 | if (![_databaseOutPool containsObject:db]) { 117 | [_databaseOutPool addObject:db]; 118 | } 119 | } 120 | } 121 | else { 122 | NSLog(@"Could not open up the database at path %@", _path); 123 | db = 0x00; 124 | } 125 | }]; 126 | 127 | return db; 128 | } 129 | 130 | - (NSUInteger)countOfCheckedInDatabases { 131 | 132 | __block NSInteger count; 133 | 134 | [self executeLocked:^() { 135 | count = [_databaseInPool count]; 136 | }]; 137 | 138 | return count; 139 | } 140 | 141 | - (NSUInteger)countOfCheckedOutDatabases { 142 | 143 | __block NSInteger count; 144 | 145 | [self executeLocked:^() { 146 | count = [_databaseOutPool count]; 147 | }]; 148 | 149 | return count; 150 | } 151 | 152 | - (NSUInteger)countOfOpenDatabases { 153 | __block NSInteger count; 154 | 155 | [self executeLocked:^() { 156 | count = [_databaseOutPool count] + [_databaseInPool count]; 157 | }]; 158 | 159 | return count; 160 | } 161 | 162 | - (void)releaseAllDatabases { 163 | [self executeLocked:^() { 164 | [_databaseOutPool removeAllObjects]; 165 | [_databaseInPool removeAllObjects]; 166 | }]; 167 | } 168 | 169 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 170 | 171 | FMDatabase *db = [self db]; 172 | 173 | block(db); 174 | 175 | [self pushDatabaseBackInPool:db]; 176 | } 177 | 178 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 179 | 180 | BOOL shouldRollback = NO; 181 | 182 | FMDatabase *db = [self db]; 183 | 184 | if (useDeferred) { 185 | [db beginDeferredTransaction]; 186 | } 187 | else { 188 | [db beginTransaction]; 189 | } 190 | 191 | 192 | block(db, &shouldRollback); 193 | 194 | if (shouldRollback) { 195 | [db rollback]; 196 | } 197 | else { 198 | [db commit]; 199 | } 200 | 201 | [self pushDatabaseBackInPool:db]; 202 | } 203 | 204 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 205 | [self beginTransaction:YES withBlock:block]; 206 | } 207 | 208 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 209 | [self beginTransaction:NO withBlock:block]; 210 | } 211 | #if SQLITE_VERSION_NUMBER >= 3007000 212 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 213 | 214 | static unsigned long savePointIdx = 0; 215 | 216 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 217 | 218 | BOOL shouldRollback = NO; 219 | 220 | FMDatabase *db = [self db]; 221 | 222 | NSError *err = 0x00; 223 | 224 | if (![db startSavePointWithName:name error:&err]) { 225 | [self pushDatabaseBackInPool:db]; 226 | return err; 227 | } 228 | 229 | block(db, &shouldRollback); 230 | 231 | if (shouldRollback) { 232 | [db rollbackToSavePointWithName:name error:&err]; 233 | } 234 | else { 235 | [db releaseSavePointWithName:name error:&err]; 236 | } 237 | 238 | [self pushDatabaseBackInPool:db]; 239 | 240 | return err; 241 | } 242 | #endif 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "sqlite3.h" 11 | 12 | @class FMDatabase; 13 | 14 | @interface FMDatabaseQueue : NSObject { 15 | NSString *_path; 16 | dispatch_queue_t _queue; 17 | FMDatabase *_db; 18 | } 19 | 20 | @property (retain) NSString *path; 21 | 22 | + (id)databaseQueueWithPath:(NSString*)aPath; 23 | - (id)initWithPath:(NSString*)aPath; 24 | - (void)close; 25 | 26 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 27 | 28 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 29 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 30 | 31 | #if SQLITE_VERSION_NUMBER >= 3007000 32 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 33 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 34 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 35 | #endif 36 | 37 | @end 38 | 39 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMDatabaseQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabaseQueue.h" 10 | #import "FMDatabase.h" 11 | 12 | /* 13 | 14 | Note: we call [self retain]; before using dispatch_sync, just incase 15 | FMDatabaseQueue is released on another thread and we're in the middle of doing 16 | something in dispatch_sync 17 | 18 | */ 19 | 20 | @implementation FMDatabaseQueue 21 | 22 | @synthesize path = _path; 23 | 24 | + (id)databaseQueueWithPath:(NSString*)aPath { 25 | 26 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; 27 | 28 | FMDBAutorelease(q); 29 | 30 | return q; 31 | } 32 | 33 | - (id)initWithPath:(NSString*)aPath { 34 | 35 | self = [super init]; 36 | 37 | if (self != nil) { 38 | 39 | _db = [FMDatabase databaseWithPath:aPath]; 40 | FMDBRetain(_db); 41 | 42 | if (![_db open]) { 43 | NSLog(@"Could not create database queue for path %@", aPath); 44 | FMDBRelease(self); 45 | return 0x00; 46 | } 47 | 48 | _path = FMDBReturnRetained(aPath); 49 | 50 | _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 51 | } 52 | 53 | return self; 54 | } 55 | 56 | - (void)dealloc { 57 | 58 | FMDBRelease(_db); 59 | FMDBRelease(_path); 60 | 61 | if (_queue) { 62 | dispatch_release(_queue); 63 | _queue = 0x00; 64 | } 65 | #if ! __has_feature(objc_arc) 66 | [super dealloc]; 67 | #endif 68 | } 69 | 70 | - (void)close { 71 | FMDBRetain(self); 72 | dispatch_sync(_queue, ^() { 73 | [_db close]; 74 | FMDBRelease(_db); 75 | _db = 0x00; 76 | }); 77 | FMDBRelease(self); 78 | } 79 | 80 | - (FMDatabase*)database { 81 | if (!_db) { 82 | _db = FMDBReturnRetained([FMDatabase databaseWithPath:_path]); 83 | 84 | if (![_db open]) { 85 | NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); 86 | FMDBRelease(_db); 87 | _db = 0x00; 88 | return 0x00; 89 | } 90 | } 91 | 92 | return _db; 93 | } 94 | 95 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 96 | FMDBRetain(self); 97 | 98 | dispatch_sync(_queue, ^() { 99 | 100 | FMDatabase *db = [self database]; 101 | block(db); 102 | 103 | if ([db hasOpenResultSets]) { 104 | NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); 105 | } 106 | }); 107 | 108 | FMDBRelease(self); 109 | } 110 | 111 | 112 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 113 | FMDBRetain(self); 114 | dispatch_sync(_queue, ^() { 115 | 116 | BOOL shouldRollback = NO; 117 | 118 | if (useDeferred) { 119 | [[self database] beginDeferredTransaction]; 120 | } 121 | else { 122 | [[self database] beginTransaction]; 123 | } 124 | 125 | block([self database], &shouldRollback); 126 | 127 | if (shouldRollback) { 128 | [[self database] rollback]; 129 | } 130 | else { 131 | [[self database] commit]; 132 | } 133 | }); 134 | 135 | FMDBRelease(self); 136 | } 137 | 138 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 139 | [self beginTransaction:YES withBlock:block]; 140 | } 141 | 142 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 143 | [self beginTransaction:NO withBlock:block]; 144 | } 145 | 146 | #if SQLITE_VERSION_NUMBER >= 3007000 147 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 148 | 149 | static unsigned long savePointIdx = 0; 150 | __block NSError *err = 0x00; 151 | FMDBRetain(self); 152 | dispatch_sync(_queue, ^() { 153 | 154 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 155 | 156 | BOOL shouldRollback = NO; 157 | 158 | if ([[self database] startSavePointWithName:name error:&err]) { 159 | 160 | block([self database], &shouldRollback); 161 | 162 | if (shouldRollback) { 163 | [[self database] rollbackToSavePointWithName:name error:&err]; 164 | } 165 | else { 166 | [[self database] releaseSavePointWithName:name error:&err]; 167 | } 168 | 169 | } 170 | }); 171 | FMDBRelease(self); 172 | return err; 173 | } 174 | #endif 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /WebAppKit Framework/FMDatabase/FMResultSet.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "sqlite3.h" 3 | 4 | #ifndef __has_feature // Optional. 5 | #define __has_feature(x) 0 // Compatibility with non-clang compilers. 6 | #endif 7 | 8 | #ifndef NS_RETURNS_NOT_RETAINED 9 | #if __has_feature(attribute_ns_returns_not_retained) 10 | #define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained)) 11 | #else 12 | #define NS_RETURNS_NOT_RETAINED 13 | #endif 14 | #endif 15 | 16 | @class FMDatabase; 17 | @class FMStatement; 18 | 19 | @interface FMResultSet : NSObject { 20 | FMDatabase *_parentDB; 21 | FMStatement *_statement; 22 | 23 | NSString *_query; 24 | NSMutableDictionary *_columnNameToIndexMap; 25 | BOOL _columnNamesSetup; 26 | } 27 | 28 | @property (retain) NSString *query; 29 | @property (retain) NSMutableDictionary *columnNameToIndexMap; 30 | @property (retain) FMStatement *statement; 31 | 32 | + (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB; 33 | 34 | - (void)close; 35 | 36 | - (void)setParentDB:(FMDatabase *)newDb; 37 | 38 | - (BOOL)next; 39 | - (BOOL)hasAnotherRow; 40 | 41 | - (int)columnCount; 42 | 43 | - (int)columnIndexForName:(NSString*)columnName; 44 | - (NSString*)columnNameForIndex:(int)columnIdx; 45 | 46 | - (int)intForColumn:(NSString*)columnName; 47 | - (int)intForColumnIndex:(int)columnIdx; 48 | 49 | - (long)longForColumn:(NSString*)columnName; 50 | - (long)longForColumnIndex:(int)columnIdx; 51 | 52 | - (long long int)longLongIntForColumn:(NSString*)columnName; 53 | - (long long int)longLongIntForColumnIndex:(int)columnIdx; 54 | 55 | - (BOOL)boolForColumn:(NSString*)columnName; 56 | - (BOOL)boolForColumnIndex:(int)columnIdx; 57 | 58 | - (double)doubleForColumn:(NSString*)columnName; 59 | - (double)doubleForColumnIndex:(int)columnIdx; 60 | 61 | - (NSString*)stringForColumn:(NSString*)columnName; 62 | - (NSString*)stringForColumnIndex:(int)columnIdx; 63 | 64 | - (NSDate*)dateForColumn:(NSString*)columnName; 65 | - (NSDate*)dateForColumnIndex:(int)columnIdx; 66 | 67 | - (NSData*)dataForColumn:(NSString*)columnName; 68 | - (NSData*)dataForColumnIndex:(int)columnIdx; 69 | 70 | - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx; 71 | - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName; 72 | 73 | // returns one of NSNumber, NSString, NSData, or NSNull 74 | - (id)objectForColumnName:(NSString*)columnName; 75 | - (id)objectForColumnIndex:(int)columnIdx; 76 | 77 | /* 78 | If you are going to use this data after you iterate over the next row, or after you close the 79 | result set, make sure to make a copy of the data first (or just use dataForColumn:/dataForColumnIndex:) 80 | If you don't, you're going to be in a world of hurt when you try and use the data. 81 | */ 82 | - (NSData*)dataNoCopyForColumn:(NSString*)columnName NS_RETURNS_NOT_RETAINED; 83 | - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED; 84 | 85 | - (BOOL)columnIndexIsNull:(int)columnIdx; 86 | - (BOOL)columnIsNull:(NSString*)columnName; 87 | 88 | - (void)kvcMagic:(id)object; 89 | - (NSDictionary *)resultDict; 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /WebAppKit Framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.${PRODUCT_NAME:rfc1034Identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | WebAppKit 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /WebAppKit Framework/Resources/404.html: -------------------------------------------------------------------------------- 1 |

Not Found

2 | That sucks. -------------------------------------------------------------------------------- /WebAppKit Framework/Resources/Exception.wat: -------------------------------------------------------------------------------- 1 |

<%print exception.name.HTML>

2 | 3 | <%print exception.reason.HTML> 4 | 5 |

Backtrace

6 |
7 | <%print [exception.callStackSymbols componentsJoinedByString:"\n"].HTML>
8 | 
9 | Break on objc_exception_throw to debug. -------------------------------------------------------------------------------- /WebAppKit Framework/Resources/MediaTypes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | css 6 | text/css 7 | html 8 | text/html 9 | txt 10 | text/plain 11 | 12 | 13 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasf/WebAppKit/6529c105c5794d77b83f6e784a57587a5e27f2c5/WebAppKit Framework/Source/.DS_Store -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TFStringScanner/TFStringScanner.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef enum { 4 | TFTokenTypeIdentifier, 5 | TFTokenTypeNumeric, 6 | TFTokenTypeSymbol, 7 | } TFTokenType; 8 | 9 | 10 | @interface TFStringScanner : NSObject { 11 | NSString *content; 12 | NSUInteger location; 13 | NSMutableArray *multicharSymbols; 14 | TFTokenType lastTokenType; 15 | } 16 | 17 | @property(readonly) NSString *string; 18 | @property NSUInteger location; 19 | @property(readonly, getter=isAtEnd) BOOL atEnd; 20 | @property(readonly) TFTokenType lastTokenType; 21 | 22 | 23 | + (id)scannerWithString:(NSString*)string; 24 | - (id)initWithString:(NSString*)string; 25 | 26 | - (void)addMulticharacterSymbol:(NSString*)symbol; 27 | - (void)addMulticharacterSymbols:(NSString*)symbol, ...; 28 | - (void)removeMulticharacterSymbol:(NSString*)symbol; 29 | 30 | - (unichar)scanCharacter; 31 | - (NSString*)scanForLength:(NSUInteger)length; 32 | 33 | - (BOOL)scanString:(NSString*)substring; 34 | - (NSString*)scanToString:(NSString*)substring; 35 | - (BOOL)scanWhitespace; 36 | 37 | - (NSString*)scanToken; 38 | - (BOOL)scanToken:(NSString*)matchToken; 39 | - (NSString*)peekToken; 40 | 41 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TFStringScanner/TFStringScanner.m: -------------------------------------------------------------------------------- 1 | #import "TFStringScanner.h" 2 | 3 | static NSCharacterSet *digitCharacters, *alphaCharacters, *alphanumericCharacters, *symbolCharacters; 4 | 5 | 6 | @implementation TFStringScanner 7 | @synthesize location, lastTokenType, string=content; 8 | 9 | 10 | + (void)initialize { 11 | digitCharacters = [[NSCharacterSet characterSetWithRange:NSMakeRange('0', 10)] retain]; 12 | 13 | NSMutableCharacterSet *alpha = [NSMutableCharacterSet characterSetWithRange:NSMakeRange('a', 26)]; 14 | [alpha addCharactersInRange:NSMakeRange('A', 26)]; 15 | [alpha addCharactersInString:@"_"]; 16 | alphaCharacters = [alpha retain]; 17 | 18 | NSMutableCharacterSet *alphanum = [digitCharacters mutableCopy]; 19 | [alphanum formUnionWithCharacterSet:alphaCharacters]; 20 | alphanumericCharacters = alphanum; 21 | 22 | NSMutableCharacterSet *symbols = [[alphanumericCharacters invertedSet] mutableCopy]; 23 | [symbols removeCharactersInString:@" \t\r\n"]; 24 | symbolCharacters = symbols; 25 | } 26 | 27 | 28 | + (id)scannerWithString:(NSString*)string { 29 | return [[[self alloc] initWithString:string] autorelease]; 30 | } 31 | 32 | 33 | - (id)initWithString:(NSString*)string { 34 | self = [super init]; 35 | content = [string copy]; 36 | multicharSymbols = [[NSMutableArray alloc] init]; 37 | return self; 38 | } 39 | 40 | 41 | - (void)dealloc { 42 | [content release]; 43 | [multicharSymbols release]; 44 | [super dealloc]; 45 | } 46 | 47 | 48 | - (NSString *)description { 49 | NSInteger radius = 15; 50 | NSUInteger start = MAX((NSInteger)self.location-radius, 0); 51 | NSUInteger end = MIN(self.location+radius, [content length]-1); 52 | NSString *sample = [content substringWithRange:NSMakeRange(start, end-start+1)]; 53 | sample = [sample stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; 54 | sample = [sample stringByReplacingOccurrencesOfString:@"\r" withString:@" "]; 55 | NSString *pointString = [[@"" stringByPaddingToLength:self.location-start withString:@" " startingAtIndex:0] stringByAppendingString:@"^"]; 56 | return [NSString stringWithFormat:@"<%@ %p at position %lu>\n%@\n%@", [self class], self, (unsigned long)self.location, sample, pointString]; 57 | } 58 | 59 | 60 | - (void)resortSymbols { 61 | [multicharSymbols sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"length" ascending:NO]]]; 62 | } 63 | 64 | 65 | - (void)addMulticharacterSymbol:(NSString*)symbol { 66 | [multicharSymbols addObject:symbol]; 67 | [self resortSymbols]; 68 | } 69 | 70 | 71 | - (void)addMulticharacterSymbols:(NSString*)symbol, ... { 72 | va_list list; 73 | va_start(list, symbol); 74 | do { 75 | [multicharSymbols addObject:symbol]; 76 | } while((symbol = va_arg(list, NSString*))); 77 | va_end(list); 78 | [self resortSymbols]; 79 | } 80 | 81 | 82 | - (void)removeMulticharacterSymbol:(NSString*)symbol { 83 | [multicharSymbols removeObject:symbol]; 84 | [self resortSymbols]; 85 | } 86 | 87 | 88 | - (unichar)scanCharacter { 89 | if(self.atEnd) return 0; 90 | return [content characterAtIndex:location++]; 91 | } 92 | 93 | 94 | - (NSString*)scanForLength:(NSUInteger)length { 95 | if(location + length > [content length]) return nil; 96 | NSString *sub = [content substringWithRange:NSMakeRange(location, length)]; 97 | location += length; 98 | return sub; 99 | } 100 | 101 | 102 | - (BOOL)scanString:(NSString*)substring { 103 | NSUInteger length = [substring length]; 104 | if(location + length > [content length]) return NO; 105 | 106 | NSString *sub = [content substringWithRange:NSMakeRange(location, length)]; 107 | if([sub isEqual:substring]) { 108 | location += length; 109 | return YES; 110 | }else return NO; 111 | } 112 | 113 | 114 | - (NSString*)scanToString:(NSString*)substring { 115 | NSRange remainingRange = NSMakeRange(location, [content length]-location); 116 | NSUInteger newLocation = [content rangeOfString:substring options:0 range:remainingRange].location; 117 | if(newLocation == NSNotFound) { 118 | location = [content length]; 119 | return [content substringWithRange:remainingRange]; 120 | } 121 | NSString *string = [content substringWithRange:NSMakeRange(location, newLocation-location)]; 122 | location = newLocation; 123 | return string; 124 | } 125 | 126 | 127 | - (NSString*)scanStringFromCharacterSet:(NSCharacterSet*)set { 128 | BOOL found = NO; 129 | NSUInteger start = location; 130 | while(!self.atEnd && [set characterIsMember:[content characterAtIndex:location]]) { 131 | location++; 132 | found = YES; 133 | } 134 | return found ? [content substringWithRange:NSMakeRange(start, location-start)] : nil; 135 | } 136 | 137 | 138 | - (BOOL)scanWhitespace { 139 | return [self scanStringFromCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] != nil; 140 | } 141 | 142 | 143 | - (NSString*)peekToken { 144 | NSUInteger loc = location; 145 | NSString *token = [self scanToken]; 146 | location = loc; 147 | return token; 148 | } 149 | 150 | 151 | - (NSString*)scanToken { 152 | [self scanWhitespace]; 153 | if(self.atEnd) return nil; 154 | 155 | unichar firstChar = [content characterAtIndex:location]; 156 | 157 | if([alphaCharacters characterIsMember:firstChar]) { 158 | lastTokenType = TFTokenTypeIdentifier; 159 | return [self scanStringFromCharacterSet:alphanumericCharacters]; 160 | 161 | }else if([digitCharacters characterIsMember:firstChar]) { 162 | lastTokenType = TFTokenTypeNumeric; 163 | return [self scanStringFromCharacterSet:digitCharacters]; 164 | 165 | }else{ 166 | lastTokenType = TFTokenTypeSymbol; 167 | for(NSString *symbol in multicharSymbols) 168 | if([self scanString:symbol]) return symbol; 169 | location++; 170 | return [NSString stringWithCharacters:&firstChar length:1]; 171 | } 172 | } 173 | 174 | 175 | - (BOOL)scanToken:(NSString*)matchToken { 176 | if([[self peekToken] isEqual:matchToken]) { 177 | [self scanToken]; 178 | return YES; 179 | }else return NO; 180 | } 181 | 182 | 183 | - (BOOL)isAtEnd { 184 | return location >= [content length]; 185 | } 186 | 187 | 188 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TL.h: -------------------------------------------------------------------------------- 1 | #import "TLStatement.h" 2 | #import "TLExpression.h" 3 | #import "TLScope.h" 4 | 5 | #import "TLCompoundStatement.h" 6 | #import "TLForeachLoop.h" 7 | #import "TLWhileLoop.h" 8 | #import "TLConditional.h" 9 | #import "TLAssignment.h" 10 | 11 | #import "TLObject.h" 12 | #import "TLIdentifier.h" 13 | #import "TLOperation.h" 14 | #import "TLMethodInvocation.h" 15 | 16 | NSString *const TLParseException; 17 | NSString *const TLRuntimeException; -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TL.m: -------------------------------------------------------------------------------- 1 | // 2 | // TL.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-15. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | NSString *const TLParseException = @"TLExpressionParseException"; 10 | NSString *const TLRuntimeException = @"TLRuntimeException"; -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLAssignment.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLAssignment.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-15. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLStatement.h" 10 | @class TLExpression; 11 | 12 | @interface TLAssignment : TLStatement { 13 | NSString *identifier; 14 | TLExpression *value; 15 | } 16 | 17 | - (id)initWithIdentifier:(NSString*)lhs value:(TLExpression*)rhs; 18 | @end 19 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLAssignment.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLAssignment.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-15. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLAssignment.h" 10 | #import "TLExpression.h" 11 | #import "TLScope.h" 12 | 13 | @implementation TLAssignment 14 | 15 | - (id)initWithIdentifier:(NSString*)lhs value:(TLExpression*)rhs { 16 | self = [super init]; 17 | identifier = [lhs copy]; 18 | value = rhs; 19 | return self; 20 | } 21 | 22 | - (void)invokeInScope:(TLScope *)scope { 23 | [scope setValue:[value evaluateWithScope:scope] forKey:identifier]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLCompoundStatement.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLCompoundExpression.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLStatement.h" 10 | 11 | @interface TLCompoundStatement : TLStatement { 12 | NSArray *statements; 13 | } 14 | 15 | - (id)initWithStatements:(NSArray*)array; 16 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLCompoundStatement.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLCompoundExpression.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLCompoundStatement.h" 10 | #import "TLScope.h" 11 | 12 | @implementation TLCompoundStatement 13 | 14 | - (id)initWithStatements:(NSArray*)array { 15 | self = [super init]; 16 | statements = [array copy]; 17 | return self; 18 | } 19 | 20 | - (void)invokeInScope:(TLScope *)scope { 21 | TLScope *innerScope = [[TLScope alloc] initWithParentScope:scope]; 22 | for(TLStatement *statement in statements) 23 | [statement invokeInScope:innerScope]; 24 | } 25 | 26 | - (NSString*)description { 27 | return [NSString stringWithFormat:@"", statements]; 28 | } 29 | 30 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLConditional.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLConditionalExpression.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLStatement.h" 10 | 11 | @interface TLConditional : TLStatement { 12 | NSArray *conditions; 13 | NSArray *consequents; 14 | } 15 | 16 | - (id)initWithConditions:(NSArray*)conds consequents:(NSArray*)bodies; 17 | 18 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLConditional.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLConditionalExpression.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLConditional.h" 10 | #import "TLExpression.h" 11 | 12 | @implementation TLConditional 13 | 14 | - (id)initWithConditions:(NSArray*)ifExpressions consequents:(NSArray*)thenStatements { 15 | self = [super init]; 16 | NSAssert([ifExpressions count] == [thenStatements count], @"The number of conditions must match the number of consequents!"); 17 | conditions = [ifExpressions copy]; 18 | consequents = [thenStatements copy]; 19 | return self; 20 | } 21 | 22 | - (void)invokeInScope:(TLScope *)scope { 23 | for(NSUInteger i=0; i<[conditions count]; i++) { 24 | TLExpression *condition = [conditions objectAtIndex:i]; 25 | TLStatement *consequent = [consequents objectAtIndex:i]; 26 | 27 | if([[condition evaluateWithScope:scope] boolValue]) { 28 | [consequent invokeInScope:scope]; 29 | return; 30 | } 31 | } 32 | } 33 | 34 | 35 | - (NSString*)description { 36 | NSMutableString *desc = [NSMutableString stringWithString:@"<"]; 37 | 38 | for(NSUInteger i=0; i<[conditions count]; i++) { 39 | TLExpression *condition = [conditions objectAtIndex:i]; 40 | TLStatement *consequent = [consequents objectAtIndex:i]; 41 | 42 | if(i) [desc appendFormat:@", Else if %@ then %@", condition, consequent]; 43 | else [desc appendFormat:@"If %@ then %@", condition, consequent]; 44 | } 45 | [desc appendString:@">"]; 46 | return desc; 47 | } 48 | 49 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLExpression.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLExpression.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-11. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @class TLScope; 10 | 11 | @interface TLExpression : NSObject { 12 | } 13 | 14 | @property(readonly) BOOL constant; 15 | 16 | + (TLExpression*)expressionByParsingString:(NSString*)string; 17 | 18 | - (id)evaluateWithScope:(TLScope*)scope; 19 | - (id)evaluate; 20 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLExpression.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLExpression.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-11. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TL.h" 10 | #import "TFStringScanner.h" 11 | 12 | 13 | @interface TLExpression () 14 | + (TLExpression*)parseNumber:(TFStringScanner*)scanner; 15 | + (TLExpression*)parseInvocation:(TFStringScanner*)scanner; 16 | + (TLExpression*)parseExpression:(TFStringScanner*)scanner; 17 | + (TLExpression*)parseExpression:(TFStringScanner*)scanner endToken:(NSString*)string; 18 | @end 19 | 20 | 21 | @implementation TLExpression 22 | 23 | - (id)evaluateWithScope:(TLScope*)scope { 24 | [NSException raise:NSInternalInconsistencyException format:@"%@ needs to be overridden in %@", NSStringFromSelector(_cmd), [self class]]; 25 | return nil; 26 | } 27 | 28 | - (id)evaluate { 29 | return [self evaluateWithScope:[[TLScope alloc] init]]; 30 | } 31 | 32 | - (BOOL)constant { 33 | return NO; 34 | } 35 | 36 | 37 | + (TLExpression*)parseNumber:(TFStringScanner*)scanner { 38 | NSMutableString *numberString = [NSMutableString string]; 39 | 40 | if([scanner scanToken:@"-"]) { 41 | [numberString appendString:@"-"]; 42 | } 43 | 44 | if([scanner peekToken] && scanner.lastTokenType == TFTokenTypeNumeric) { 45 | [numberString appendString:[scanner scanToken]]; 46 | } 47 | 48 | NSUInteger beforePeriod = scanner.location; 49 | if([scanner scanToken:@"."]) { 50 | NSString *fraction = [scanner scanToken]; 51 | if(scanner.lastTokenType == TFTokenTypeNumeric) 52 | [numberString appendFormat:@".%@", fraction]; 53 | else { 54 | // If the number is followed by period and something else, assume it's meant to be a key path. 55 | // Rewind and bail 56 | scanner.location = beforePeriod; 57 | } 58 | 59 | }else if([numberString isEqual:@"-"]) 60 | [NSException raise:TLParseException format:@"Expected valid number after -, but got: %@", [scanner scanToken]]; 61 | 62 | double number = [numberString doubleValue]; 63 | return [[TLObject alloc] initWithObject:[NSNumber numberWithDouble:number]]; 64 | } 65 | 66 | 67 | + (TLExpression*)parseInvocation:(TFStringScanner*)scanner { 68 | [scanner scanToken]; // [ 69 | TLExpression *receiver = [self parseExpression:scanner]; 70 | 71 | NSMutableString *selector = [NSMutableString string]; 72 | NSMutableArray *arguments = [NSMutableArray array]; 73 | 74 | for(;;) { 75 | NSString *part = [scanner scanToken]; 76 | if([part isEqual:@"]"] && [arguments count]) break; 77 | 78 | if(scanner.lastTokenType != TFTokenTypeIdentifier) 79 | [NSException raise:TLParseException format:@"Expected method selector part, but found something bogus: %@", part]; 80 | [selector appendString:part]; 81 | 82 | NSString *sep = [scanner scanToken]; 83 | if(![sep isEqual:@":"]) { 84 | if([sep isEqual:@"]"] && ![arguments count]) break; 85 | [NSException raise:TLParseException format:@"Expected method colon, but found this junk: %@", sep]; 86 | } 87 | [selector appendString:@":"]; 88 | 89 | TLExpression *arg = [self parseExpression:scanner]; 90 | [arguments addObject:arg]; 91 | } 92 | 93 | return [[TLMethodInvocation alloc] initWithReceiver:receiver selector:NSSelectorFromString(selector) arguments:arguments]; 94 | } 95 | 96 | 97 | + (TLExpression*)parseExpression:(TFStringScanner*)scanner { 98 | return [self parseExpression:scanner endToken:nil]; 99 | } 100 | 101 | 102 | + (TLExpression*)parseStringLiteral:(TFStringScanner*)scanner { 103 | [scanner scanToken]; // " or @" 104 | NSMutableString *string = [NSMutableString string]; 105 | 106 | while(!scanner.atEnd) { 107 | unichar c = [scanner scanCharacter]; 108 | if(c == '"') break; 109 | 110 | if(c == '\\') { 111 | switch([scanner scanCharacter]) { 112 | case '\\': c = '\\'; break; 113 | case '"': c = '"'; break; 114 | case 'n': c = '\n'; break; 115 | case 'r': c = '\r'; break; 116 | case 't': c = '\t'; break; 117 | 118 | case 'x': { 119 | NSString *hex = [scanner scanForLength:2]; 120 | unsigned value = 0; 121 | if(!hex || ![[NSScanner scannerWithString:hex] scanHexInt:&value]) 122 | [NSException raise:TLParseException format:@"Expected hex value following \\x, but found: %@", hex]; 123 | c = (uint8_t)value; 124 | break; 125 | } 126 | 127 | case 'u': { 128 | NSString *hex = [scanner scanForLength:4]; 129 | unsigned value = 0; 130 | if(!hex || ![[NSScanner scannerWithString:hex] scanHexInt:&value]) 131 | [NSException raise:TLParseException format:@"Expected hex value following \\u, but found: %@", hex]; 132 | c = (unichar)value; 133 | break; 134 | } 135 | } 136 | } 137 | 138 | [string appendString:[NSString stringWithCharacters:&c length:1]]; 139 | } 140 | 141 | return [[TLObject alloc] initWithObject:string]; 142 | } 143 | 144 | 145 | + (TLExpression*)parseExpression:(TFStringScanner*)scanner endToken:(NSString*)endToken { 146 | NSMutableArray *expressions = [NSMutableArray array]; 147 | NSMutableArray *infixOperators = [NSMutableArray array]; 148 | 149 | for(;;) { 150 | // Collect prefix operators 151 | NSMutableArray *prefixTokens = [NSMutableArray array]; 152 | NSString *token = nil; 153 | while(token = [scanner peekToken]) { 154 | if([token isEqual:@"!"]) { 155 | [prefixTokens addObject:token]; 156 | [scanner scanToken]; 157 | }else break; 158 | } 159 | 160 | // Read value 161 | token = [scanner peekToken]; 162 | if(!token) break; 163 | 164 | unichar c = [token characterAtIndex:0]; 165 | TFTokenType type = scanner.lastTokenType; 166 | TLExpression *part = nil; 167 | 168 | if([token isEqual:@"("]) { 169 | [scanner scanToken]; 170 | part = [self parseExpression:scanner]; 171 | [scanner scanToken]; // ) 172 | }else if(type == TFTokenTypeNumeric || c == '-' || c == '.') { 173 | part = [self parseNumber:scanner]; 174 | }else if(c == '[') { 175 | part = [self parseInvocation:scanner]; 176 | 177 | }else if(type == TFTokenTypeIdentifier) { 178 | part = [[TLIdentifier alloc] initWithName:[scanner scanToken]]; 179 | 180 | }else if([token isEqual:@"\""] || [token isEqual:@"@\""]) { 181 | part = [self parseStringLiteral:scanner]; 182 | 183 | }else{ 184 | [NSException raise:TLParseException format:@"Expected expression, but found: %@", token]; 185 | } 186 | 187 | 188 | // Suffix operators 189 | for(;;) { 190 | // Dot syntax; key paths 191 | if([scanner scanToken:@"."]) { 192 | NSMutableString *keyPath = [NSMutableString string]; 193 | 194 | do { 195 | if([keyPath length]) [keyPath appendString:@"."]; 196 | if([scanner scanString:@"@"]) [keyPath appendString:@"@"]; 197 | NSString *string = [scanner scanToken]; 198 | if(scanner.lastTokenType != TFTokenTypeIdentifier) 199 | [NSException raise:TLParseException format:@"Expected key name after period, but found this: %@", string]; 200 | [keyPath appendString:string]; 201 | 202 | }while([scanner scanString:@"."]); 203 | 204 | part = [[TLOperation alloc] initWithOperator:TLOperatorKeyPathSelection leftOperand:part rightOperand:[[TLObject alloc] initWithObject:keyPath]]; 205 | 206 | // Subscripting 207 | }else if([scanner scanToken:@"["]) { 208 | TLExpression *subscript = [self parseExpression:scanner]; 209 | 210 | if(![scanner scanToken:@"]"]) 211 | [NSException raise:TLParseException format:@"Expected ] after subscript expression, but found this horse dung: %@", [scanner scanToken]]; 212 | 213 | part = [[TLOperation alloc] initWithOperator:TLOperatorSubscript leftOperand:part rightOperand:subscript]; 214 | }else break; 215 | } 216 | 217 | 218 | // Apply prefix operators 219 | for(NSString *token in [prefixTokens reverseObjectEnumerator]) { 220 | TLOperator op = TLOperatorInvalid; 221 | if([token isEqual:@"!"]) op = TLOperatorNegation; 222 | part = [[TLOperation alloc] initWithOperator:op leftOperand:part rightOperand:nil]; 223 | } 224 | 225 | [expressions addObject:part]; 226 | 227 | 228 | // Operator part 229 | token = [scanner peekToken]; 230 | c = [token characterAtIndex:0]; 231 | type = scanner.lastTokenType; 232 | 233 | TLOperator operator = [TLOperation operatorForSymbol:token]; 234 | 235 | if([endToken isEqual:token]) { 236 | break; 237 | }else if(operator) { 238 | [scanner scanToken]; 239 | [infixOperators addObject:[NSNumber numberWithInt:operator]]; 240 | 241 | }else break; 242 | } 243 | 244 | 245 | NSAssert([expressions count] == [infixOperators count]+1, @"Mayhem! The number of expressions must always be one more than the number of operators."); 246 | 247 | 248 | // Boil it all down 249 | while([infixOperators count]) { 250 | NSUInteger opIndex = [TLOperation indexOfPrecedingOperatorInArray:infixOperators]; 251 | TLOperator op = [[infixOperators objectAtIndex:opIndex] intValue]; 252 | TLOperation *operation = [[TLOperation alloc] initWithOperator:op leftOperand:[expressions objectAtIndex:opIndex] rightOperand:[expressions objectAtIndex:opIndex+1]]; 253 | [expressions replaceObjectAtIndex:opIndex withObject:operation]; 254 | [expressions removeObjectAtIndex:opIndex+1]; 255 | [infixOperators removeObjectAtIndex:opIndex]; 256 | } 257 | 258 | return [expressions objectAtIndex:0]; 259 | } 260 | 261 | 262 | + (TFStringScanner*)newScannerForString:(NSString*)string { 263 | TFStringScanner *scanner = [[TFStringScanner alloc] initWithString:string]; 264 | [scanner addMulticharacterSymbols:@"==", @"!=", @"===", @"!==", @"<=", @">=", @"&&", @"||", @"@\"", nil]; 265 | return scanner; 266 | } 267 | 268 | 269 | + (TLExpression*)expressionByParsingString:(NSString*)string { 270 | TFStringScanner *scanner = [self newScannerForString:string]; 271 | return [TLExpression parseExpression:scanner]; 272 | } 273 | 274 | 275 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLForeachLoop.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLForeachLoop.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLStatement.h" 10 | @class TLExpression; 11 | 12 | 13 | @interface TLForeachLoop : TLStatement { 14 | TLExpression *collection; 15 | NSString *variableName; 16 | TLStatement *body; 17 | } 18 | 19 | - (id)initWithCollection:(TLExpression*)object body:(TLStatement*)contents variableName:(NSString*)var; 20 | @end 21 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLForeachLoop.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLForeachLoop.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLForeachLoop.h" 10 | #import "TLScope.h" 11 | #import "TLExpression.h" 12 | 13 | @implementation TLForeachLoop 14 | 15 | - (id)initWithCollection:(TLExpression*)object body:(TLStatement*)contents variableName:(NSString*)var { 16 | self = [super init]; 17 | collection = object; 18 | variableName = [var copy]; 19 | body = contents; 20 | return self; 21 | } 22 | 23 | - (void)invokeInScope:(TLScope*)scope { 24 | id concreteCollection = [collection evaluateWithScope:scope]; 25 | for(id object in concreteCollection) { 26 | TLScope *innerScope = [[TLScope alloc] initWithParentScope:scope]; 27 | [innerScope declareValue:object forKey:variableName]; 28 | [body invokeInScope:innerScope]; 29 | } 30 | } 31 | 32 | - (NSString*)description { 33 | return [NSString stringWithFormat:@"", variableName, collection, body]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLIdentifier.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSymbol.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLExpression.h" 10 | 11 | 12 | @interface TLIdentifier : TLExpression { 13 | NSString *name; 14 | } 15 | 16 | - (id)initWithName:(NSString*)symbolName; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLIdentifier.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLSymbol.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLIdentifier.h" 10 | #import "TLScope.h" 11 | 12 | @implementation TLIdentifier 13 | 14 | - (id)initWithName:(NSString*)symbolName { 15 | self = [super init]; 16 | name = [symbolName copy]; 17 | return self; 18 | } 19 | 20 | - (id)evaluateWithScope:(TLScope *)scope { 21 | return [scope valueForKey:name]; 22 | } 23 | 24 | - (NSString*)description { 25 | return [NSString stringWithFormat:@"", name]; 26 | } 27 | 28 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLMethodInvocation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLMethodInvocation.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-11. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLExpression.h" 10 | 11 | @interface TLMethodInvocation : TLExpression { 12 | TLExpression *target; 13 | SEL selector; 14 | NSArray *arguments; 15 | } 16 | 17 | - (id)initWithReceiver:(TLExpression*)expr selector:(SEL)sel arguments:(NSArray*)args; 18 | @end 19 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLMethodInvocation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLMethodInvocation.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-11. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TL.h" 10 | #import 11 | #import 12 | 13 | 14 | NSUInteger TLArgumentCountForSelector(SEL selector) { 15 | return [[NSStringFromSelector(selector) componentsSeparatedByString:@":"] count]-1; 16 | } 17 | 18 | 19 | @implementation TLMethodInvocation 20 | 21 | - (id)initWithReceiver:(TLExpression*)expr selector:(SEL)sel arguments:(NSArray*)args { 22 | self = [super init]; 23 | target = expr; 24 | selector = sel; 25 | arguments = [args copy]; 26 | 27 | NSUInteger argCount = TLArgumentCountForSelector(selector); 28 | if(argCount > 6) 29 | [NSException raise:NSInvalidArgumentException format:@"Too many arguments in %@", NSStringFromSelector(selector)]; 30 | if(argCount != [arguments count]) 31 | [NSException raise:NSInvalidArgumentException format:@"Selector '%@' does not match supplied argument count (%d).", NSStringFromSelector(selector), (int)[args count]]; 32 | 33 | return self; 34 | 35 | } 36 | 37 | 38 | #define ARGTYPE(_ENCODE_TYPE_) (strcmp(type, @encode(_ENCODE_TYPE_)) == 0) 39 | 40 | - (void)copyArgument:(id)object toType:(const char*)type buffer:(void*)buffer { 41 | if(ARGTYPE(id)) { 42 | *(void**)buffer = (__bridge void*)object; 43 | return; 44 | } 45 | 46 | 47 | NSNumber *n = (NSNumber*)object; 48 | 49 | if(ARGTYPE(char)) *(char*)buffer = [n charValue]; 50 | else if(ARGTYPE(int)) *(int*)buffer = [n intValue]; 51 | else if(ARGTYPE(short)) *(short*)buffer = [n shortValue]; 52 | else if(ARGTYPE(long)) *(long*)buffer = [n longValue]; 53 | else if(ARGTYPE(long long)) *(long long*)buffer = [n longLongValue]; 54 | 55 | else if(ARGTYPE(unsigned char)) *(unsigned char*)buffer = [n unsignedCharValue]; 56 | else if(ARGTYPE(unsigned int)) *(unsigned int*)buffer = [n unsignedIntValue]; 57 | else if(ARGTYPE(unsigned short)) *(unsigned short*)buffer = [n unsignedShortValue]; 58 | else if(ARGTYPE(unsigned long)) *(unsigned long*)buffer = [n unsignedLongValue]; 59 | else if(ARGTYPE(unsigned long long)) *(unsigned long long*)buffer = [n unsignedLongLongValue]; 60 | 61 | else if(ARGTYPE(float)) *(float*)buffer = [n floatValue]; 62 | else if(ARGTYPE(double)) *(double*)buffer = [n doubleValue]; 63 | 64 | else if([object isKindOfClass:[NSValue class]] && strcmp([object objCType], type) == 0) { 65 | [object getValue:buffer]; 66 | } 67 | 68 | else [NSException raise:TLRuntimeException format:@"Can't coerce object of class %@ to type encoding %s", [object class], type]; 69 | } 70 | 71 | 72 | - (id)objectFromReturnValue:(void*)buffer ofType:(const char*)type { 73 | if(ARGTYPE(id)) return (__bridge id)*(void**)buffer; 74 | 75 | if(ARGTYPE(char)) return [NSNumber numberWithChar:*(char*)buffer]; 76 | else if(ARGTYPE(int)) return [NSNumber numberWithInt:*(int*)buffer]; 77 | else if(ARGTYPE(short)) return [NSNumber numberWithShort:*(short*)buffer]; 78 | else if(ARGTYPE(long)) return [NSNumber numberWithLong:*(long*)buffer]; 79 | else if(ARGTYPE(long long)) return [NSNumber numberWithLongLong:*(long long*)buffer]; 80 | 81 | else if(ARGTYPE(unsigned char)) return [NSNumber numberWithUnsignedChar:*(unsigned char*)buffer]; 82 | else if(ARGTYPE(unsigned int)) return [NSNumber numberWithUnsignedInt:*(unsigned int*)buffer]; 83 | else if(ARGTYPE(unsigned short)) return [NSNumber numberWithUnsignedShort:*(unsigned short*)buffer]; 84 | else if(ARGTYPE(unsigned long)) return [NSNumber numberWithUnsignedLong:*(unsigned long*)buffer]; 85 | else if(ARGTYPE(unsigned long long)) return [NSNumber numberWithUnsignedLongLong:*(unsigned long long*)buffer]; 86 | 87 | else if(ARGTYPE(float)) return [NSNumber numberWithFloat:*(float*)buffer]; 88 | else if(ARGTYPE(double)) return [NSNumber numberWithDouble:*(double*)buffer]; 89 | 90 | else return [NSValue valueWithBytes:buffer objCType:type]; 91 | } 92 | 93 | 94 | - (id)evaluateWithScope:(TLScope *)scope { 95 | id object = [target evaluateWithScope:scope]; 96 | if(!object) return nil; 97 | 98 | NSMethodSignature *signature = [object methodSignatureForSelector:selector]; 99 | 100 | if([arguments count] == 0 && strcmp([signature methodReturnType], @encode(id)) == 0) 101 | return ((id(*)(id,SEL))objc_msgSend)(object, selector); // Fast path 102 | 103 | 104 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 105 | 106 | uint8_t scratch[[signature frameLength]]; 107 | NSUInteger index = 2; // skip self and _cmd 108 | for(TLExpression *expr in arguments) { 109 | id arg = [expr evaluateWithScope:scope]; 110 | 111 | const char *type = [signature getArgumentTypeAtIndex:index]; 112 | [self copyArgument:arg toType:type buffer:scratch]; 113 | [invocation setArgument:scratch atIndex:index]; 114 | 115 | index++; 116 | } 117 | 118 | [invocation setSelector:selector]; 119 | [invocation invokeWithTarget:object]; 120 | 121 | uint8_t value[[signature methodReturnLength]]; 122 | [invocation getReturnValue:&value]; 123 | return [self objectFromReturnValue:value ofType:[signature methodReturnType]]; 124 | } 125 | 126 | 127 | - (NSString*)description { 128 | return [NSString stringWithFormat:@"", target, NSStringFromSelector(selector), arguments]; 129 | } 130 | 131 | 132 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLObjectExpression.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLExpression.h" 10 | 11 | @interface TLObject : TLExpression { 12 | id object; 13 | } 14 | 15 | - (id)initWithObject:(id)obj; 16 | 17 | + (TLExpression*)trueValue; 18 | + (TLExpression*)falseValue; 19 | + (TLExpression*)nilValue; 20 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLObjectExpression.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLObject.h" 10 | 11 | 12 | @implementation TLObject 13 | 14 | - (id)initWithObject:(id)obj { 15 | self = [super init]; 16 | object = obj; 17 | return self; 18 | } 19 | 20 | - (id)evaluateWithScope:(TLScope *)scope { 21 | return object; 22 | } 23 | 24 | - (BOOL)constant { 25 | return [object isKindOfClass:[NSNumber class]]; 26 | } 27 | 28 | - (NSString*)description { 29 | return [NSString stringWithFormat:@"", object]; 30 | } 31 | 32 | + (TLExpression*)trueValue { 33 | return [[TLObject alloc] initWithObject:[NSNumber numberWithBool:YES]]; 34 | } 35 | 36 | + (TLExpression*)falseValue { 37 | return [[TLObject alloc] initWithObject:[NSNumber numberWithBool:NO]]; 38 | } 39 | 40 | + (TLExpression*)nilValue { 41 | return [[TLObject alloc] initWithObject:nil]; 42 | } 43 | 44 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLOperation.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLExpression.h" 10 | 11 | typedef enum { 12 | TLOperatorInvalid = 0, 13 | 14 | // Suffix 15 | TLOperatorKeyPathSelection, 16 | TLOperatorSubscript, 17 | 18 | // Prefix 19 | TLOperatorNegation, 20 | 21 | // Infix 22 | TLOperatorAddition, 23 | TLOperatorSubtraction, 24 | TLOperatorMultiplication, 25 | TLOperatorDivision, 26 | 27 | TLOperatorEquality, 28 | TLOperatorInequality, 29 | TLOperatorLessThan, 30 | TLOperatorGreaterThan, 31 | TLOperatorLessThanOrEqual, 32 | TLOperatorGreaterThanOrEqual, 33 | 34 | TLOperatorAND, 35 | TLOperatorOR, 36 | 37 | TLOperatorIdentityEquality, 38 | TLOperatorIdentityInequality, 39 | } TLOperator; 40 | 41 | 42 | @interface TLOperation : TLExpression { 43 | TLOperator operator; 44 | TLExpression *leftOperand; 45 | TLExpression *rightOperand; 46 | } 47 | 48 | - (id)initWithOperator:(TLOperator)op leftOperand:(TLExpression*)left rightOperand:(TLExpression*)right; 49 | 50 | 51 | + (TLOperator)operatorForSymbol:(NSString*)string; 52 | + (NSUInteger)indexOfPrecedingOperatorInArray:(NSArray*)array; 53 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLOperation.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TL.h" 10 | 11 | 12 | typedef struct { 13 | TLOperator operator; 14 | __unsafe_unretained NSString *symbol; 15 | NSUInteger precedence; 16 | } TLOperatorInfo; 17 | 18 | 19 | static TLOperatorInfo operatorInfo[] = { 20 | {TLOperatorKeyPathSelection, nil, 0}, 21 | {TLOperatorSubscript, nil, 0}, 22 | {TLOperatorNegation, nil, 0}, 23 | 24 | {TLOperatorMultiplication, @"*", 1}, 25 | {TLOperatorDivision, @"/", 1}, 26 | {TLOperatorAddition, @"+", 2}, 27 | {TLOperatorSubtraction, @"-", 2}, 28 | 29 | {TLOperatorLessThan, @"<", 3}, 30 | {TLOperatorLessThanOrEqual, @"<=", 3}, 31 | {TLOperatorGreaterThan, @">", 4}, 32 | {TLOperatorGreaterThanOrEqual, @">=", 4}, 33 | 34 | {TLOperatorIdentityEquality, @"===", 5}, 35 | {TLOperatorIdentityInequality, @"!==", 5}, 36 | 37 | {TLOperatorEquality, @"==", 6}, 38 | {TLOperatorInequality, @"!=", 6}, 39 | 40 | {TLOperatorAND, @"&&", 7}, 41 | {TLOperatorOR, @"||", 8}, 42 | }; 43 | 44 | static NSUInteger operatorCount = sizeof(operatorInfo)/sizeof(operatorInfo[0]); 45 | 46 | 47 | 48 | 49 | @implementation NSNumber (TLOperationExtras) 50 | 51 | - (id)TL_add:(id)rhs { 52 | return [NSNumber numberWithDouble:[self doubleValue] + [rhs doubleValue]]; 53 | } 54 | 55 | - (id)TL_subtract:(id)rhs { 56 | return [NSNumber numberWithDouble:[self doubleValue] - [rhs doubleValue]]; 57 | } 58 | 59 | - (id)TL_multiply:(id)rhs { 60 | return [NSNumber numberWithDouble:[self doubleValue] * [rhs doubleValue]]; 61 | } 62 | 63 | - (id)TL_divide:(id)rhs { 64 | return [NSNumber numberWithDouble:[self doubleValue] / [rhs doubleValue]]; 65 | } 66 | 67 | @end 68 | 69 | 70 | 71 | 72 | 73 | @implementation TLOperation 74 | 75 | + (TLOperator)operatorForSymbol:(NSString*)string { 76 | for(int i=0; i", operator, leftOperand, rightOperand]; 194 | } 195 | 196 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLScope.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLScope.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-11. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface TLScope : NSObject { 13 | TLScope *parent; 14 | NSMutableDictionary *mapping; 15 | NSDictionary *constants; 16 | } 17 | 18 | + (void)defineConstant:(NSString*)name value:(id)value; 19 | + (void)undefineConstant:(NSString*)name; 20 | 21 | - (id)initWithParentScope:(TLScope*)scope; 22 | - (id)init; 23 | 24 | - (id)valueForKey:(NSString*)key; 25 | - (void)setValue:(id)value forKey:(NSString*)string; 26 | - (void)declareValue:(id)value forKey:(NSString*)key; 27 | 28 | - (NSString*)debugDescription; 29 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLScope.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLScope.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-11. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLScope.h" 10 | #import "TLObject.h" 11 | #import "TL.h" 12 | 13 | static NSMutableDictionary *TLConstants; 14 | static NSString *const TLNilPlaceholder = @"TLNilPlaceholder"; 15 | 16 | 17 | @implementation TLScope 18 | 19 | + (void)initialize { 20 | if(!TLConstants) TLConstants = [NSMutableDictionary dictionary]; 21 | [self defineConstant:@"YES" value:[NSNumber numberWithBool:YES]]; 22 | [self defineConstant:@"NO" value:[NSNumber numberWithBool:NO]]; 23 | } 24 | 25 | + (void)defineConstant:(NSString*)name value:(id)value { 26 | @synchronized(TLConstants) { 27 | [TLConstants setObject:value forKey:name]; 28 | } 29 | } 30 | 31 | + (void)undefineConstant:(NSString*)name { 32 | @synchronized(TLConstants) { 33 | [TLConstants removeObjectForKey:name]; 34 | } 35 | } 36 | 37 | - (id)initWithParentScope:(TLScope *)scope { 38 | self = [super init]; 39 | parent = scope; 40 | if(!scope) { 41 | @synchronized(TLConstants) { 42 | constants = [TLConstants copy]; 43 | } 44 | } 45 | return self; 46 | } 47 | 48 | - (id)init { 49 | return [self initWithParentScope:nil]; 50 | } 51 | 52 | - (id)rawValueForKey:(NSString*)key { 53 | id value = [mapping objectForKey:key]; 54 | if(value) return value; 55 | 56 | value = [constants valueForKey:key]; 57 | if(value) return value; 58 | 59 | value = [parent rawValueForKey:key]; 60 | if(value) return value; 61 | 62 | if([key isEqual:@"nil"] || [key isEqual:@"NULL"]) return nil; 63 | 64 | value = NSClassFromString(key); 65 | if(value) return value; 66 | 67 | [NSException raise:TLRuntimeException format:@"'%@' is undefined", key]; 68 | return value; 69 | } 70 | 71 | 72 | - (id)valueForKey:(NSString*)key { 73 | id value = [self rawValueForKey:key]; 74 | if(value == TLNilPlaceholder) return nil; 75 | return value; 76 | } 77 | 78 | - (BOOL)setValue:(id)value ifKeyExists:(NSString*)key { 79 | if(mapping && [mapping objectForKey:key]) { 80 | [mapping setObject:value forKey:key]; 81 | return YES; 82 | } else return [parent setValue:value ifKeyExists:key]; 83 | } 84 | 85 | - (void)setValue:(id)value forKey:(NSString*)key { 86 | if(!value) value = TLNilPlaceholder; 87 | 88 | if(![self setValue:value ifKeyExists:key]) { 89 | if(!mapping) mapping = [NSMutableDictionary dictionary]; 90 | [mapping setObject:value forKey:key]; 91 | } 92 | } 93 | 94 | - (void)declareValue:(id)value forKey:(NSString*)key { 95 | if(!value) value = TLNilPlaceholder; 96 | if(!mapping) mapping = [NSMutableDictionary dictionary]; 97 | [mapping setObject:value forKey:key]; 98 | } 99 | 100 | - (NSString*)stringByIndentingString:(NSString*)string { 101 | NSArray *lines = [string componentsSeparatedByString:@"\n"]; 102 | NSMutableArray *newLines = [NSMutableArray array]; 103 | for(NSString *line in lines) { 104 | [newLines addObject:[@" " stringByAppendingString:line]]; 105 | } 106 | return [newLines componentsJoinedByString:@"\n"]; 107 | } 108 | 109 | - (NSString*)debugDescription { 110 | NSMutableString *output = [NSMutableString stringWithFormat:@"(\n"]; 111 | for(id key in mapping) { 112 | if([key hasPrefix:@"_WATemplate"]) continue; 113 | [output appendFormat:@"\"%@\" = %@\n", key, [mapping valueForKey:key]]; 114 | } 115 | if(parent) [output appendFormat:@"parent scope:\n%@\n", [self stringByIndentingString:[parent debugDescription]]]; 116 | [output appendFormat:@")"]; 117 | return output; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLStatement.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLStatement.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-15. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @class TLScope; 10 | 11 | @interface TLStatement : NSObject {} 12 | - (void)invokeInScope:(TLScope*)scope; 13 | @end 14 | 15 | @interface TLNoop : TLStatement {} 16 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLStatement.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLStatement.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-15. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TL.h" 10 | 11 | @implementation TLStatement 12 | 13 | - (void)invokeInScope:(TLScope*)scope { 14 | [NSException raise:NSInternalInconsistencyException format:@"%@ must override %@", [self class], NSStringFromSelector(_cmd)]; 15 | } 16 | 17 | @end 18 | 19 | 20 | @implementation TLNoop 21 | - (void)invokeInScope:(TLScope*)scope {} 22 | @end 23 | 24 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLWhileLoop.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLWhileLoop.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-17. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TL.h" 10 | 11 | @interface TLWhileLoop : TLStatement { 12 | TLExpression *condition; 13 | TLStatement *body; 14 | } 15 | 16 | - (id)initWithCondition:(TLExpression*)expr body:(TLStatement*)bodyStatement; 17 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/TL/TLWhileLoop.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLWhileLoop.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-17. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "TLWhileLoop.h" 10 | 11 | 12 | @implementation TLWhileLoop 13 | 14 | - (id)initWithCondition:(TLExpression*)expr body:(TLStatement*)bodyStatement { 15 | self = [super init]; 16 | condition = expr; 17 | body = bodyStatement; 18 | return self; 19 | } 20 | 21 | - (void)invokeInScope:(TLScope *)scope { 22 | while([[condition evaluateWithScope:scope] boolValue]) 23 | [body invokeInScope:scope]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAApplication.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSApplication.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | 10 | @class WARoute, WASessionGenerator, WASession, WARequest, WAResponse, WARequestHandler; 11 | 12 | extern int WAApplicationMain(); 13 | 14 | 15 | @interface WAApplication : NSObject 16 | @property(readonly, strong) WARequest *request; 17 | @property(readonly, strong) WAResponse *response; 18 | 19 | @property(strong) WASessionGenerator *sessionGenerator; 20 | @property(readonly, nonatomic) WASession *session; 21 | 22 | + (int)run; 23 | 24 | 25 | - (id)init; 26 | - (BOOL)start:(NSError**)error; 27 | - (void)invalidate; 28 | 29 | - (void)preprocess; 30 | - (void)postprocess; 31 | 32 | - (WARoute*)addRouteSelector:(SEL)sel HTTPMethod:(NSString*)method path:(NSString*)path; 33 | 34 | - (void)addRequestHandler:(WARequestHandler*)handler; 35 | - (void)removeRequestHandler:(WARequestHandler*)handler; 36 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAApplication.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSApplication.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAApplication.h" 10 | #import "WARequest.h" 11 | #import "WAResponse.h" 12 | #import "WARoute.h" 13 | #import "WAServerConnection.h" 14 | #import "WAServer.h" 15 | #import "WADirectoryHandler.h" 16 | #import "WAStaticFileHandler.h" 17 | #import "WASession.h" 18 | #import "WASessionGenerator.h" 19 | 20 | static const NSString *WAHTTPServerPortKey = @"WAHTTPServerPort"; 21 | static const NSString *WAHTTPServerExternalAccessKey = @"WAHTTPServerExternalAccess"; 22 | 23 | 24 | int WAApplicationMain() { 25 | @autoreleasepool { 26 | Class appClass = NSClassFromString([[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSPrincipalClass"]); 27 | if(!appClass) { 28 | NSLog(@"WAApplicationMain() requires NSPrincipalClass to be set in Info.plist. Set it to your WAApplication subclass or call +run yourself."); 29 | return 1; 30 | } 31 | return [appClass run]; 32 | } 33 | } 34 | 35 | 36 | 37 | @interface WAApplication () 38 | @property(strong, nonatomic) WAServer *server; 39 | 40 | @property(strong) NSMutableArray *requestHandlers; 41 | @property(strong) NSMutableSet *currentHandlers; 42 | 43 | @property(strong, readwrite) WARequest *request; 44 | @property(strong, readwrite) WAResponse *response; 45 | @end 46 | 47 | 48 | 49 | @implementation WAApplication 50 | 51 | 52 | + (uint16_t)port { 53 | NSUInteger port = [[[[NSBundle mainBundle] infoDictionary] objectForKey:WAHTTPServerPortKey] unsignedShortValue]; 54 | if(!port) port = [[NSUserDefaults standardUserDefaults] integerForKey:@"port"]; 55 | if(!port) NSLog(@"No port number specified. Set WAHTTPServerPort in Info.plist or use the -port argument."); 56 | return port; 57 | } 58 | 59 | 60 | + (BOOL)enableExternalAccess { 61 | return [[[[NSBundle mainBundle] infoDictionary] objectForKey:WAHTTPServerExternalAccessKey] boolValue]; 62 | } 63 | 64 | 65 | + (int)run { 66 | uint16_t port = [self port]; 67 | if(!port) return EXIT_FAILURE; 68 | NSString *interface = [self enableExternalAccess] ? nil : @"localhost"; 69 | 70 | WAApplication *app = [[self alloc] init]; 71 | app.server = [[WAServer alloc] initWithPort:port interface:interface]; 72 | 73 | NSString *publicDir = [[NSBundle bundleForClass:self] pathForResource:@"public" ofType:nil]; 74 | WADirectoryHandler *publicHandler = [[WADirectoryHandler alloc] initWithDirectory:publicDir requestPath:@"/"]; 75 | [app addRequestHandler:publicHandler]; 76 | 77 | NSError *error; 78 | if(![app start:&error]) { 79 | NSLog(@"*** Exiting. [%@ start:] failed: %@", NSStringFromClass(self), error); 80 | return EXIT_FAILURE; 81 | } 82 | 83 | NSLog(@"WebAppKit started on port %hu", port); 84 | NSLog(@"http://localhost:%hu/", port); 85 | 86 | for(;;) @autoreleasepool { 87 | [[NSRunLoop currentRunLoop] run]; 88 | } 89 | } 90 | 91 | 92 | - (id)init { 93 | if(!(self = [super init])) return nil; 94 | 95 | self.requestHandlers = [NSMutableArray array]; 96 | self.currentHandlers = [NSMutableSet set]; 97 | 98 | [self setup]; 99 | return self; 100 | } 101 | 102 | 103 | - (void)setServer:(WAServer *)server { 104 | _server = server; 105 | 106 | __weak WAApplication *weakSelf = self; 107 | self.server.requestHandlerFactory = ^(WARequest *request){ 108 | return [weakSelf handlerForRequest:request]; 109 | }; 110 | } 111 | 112 | 113 | - (BOOL)start:(NSError**)error { 114 | return [self.server start:error]; 115 | } 116 | 117 | 118 | - (void)invalidate { 119 | [self.server invalidate]; 120 | self.server = nil; 121 | [self.sessionGenerator invalidate]; 122 | self.sessionGenerator = nil; 123 | } 124 | 125 | 126 | - (void)setup {} 127 | 128 | 129 | #pragma mark Request Handlers 130 | 131 | 132 | - (WARequestHandler*)handlerForRequest:(WARequest*)req { 133 | for(WARequestHandler *handler in self.requestHandlers) 134 | if([handler canHandleRequest:req]) 135 | return [handler handlerForRequest:req]; 136 | return [self fallbackHandler]; 137 | } 138 | 139 | 140 | - (void)addRequestHandler:(WARequestHandler*)handler { 141 | [self.requestHandlers addObject:handler]; 142 | } 143 | 144 | 145 | - (void)removeRequestHandler:(WARequestHandler*)handler { 146 | [self.requestHandlers removeObject:handler]; 147 | } 148 | 149 | 150 | - (NSString*)fileNotFoundFile { 151 | return [[NSBundle bundleForClass:[WAApplication class]] pathForResource:@"404" ofType:@"html"]; 152 | } 153 | 154 | 155 | - (WARequestHandler*)fallbackHandler { 156 | WAStaticFileHandler *handler = [[WAStaticFileHandler alloc] initWithFile:[self fileNotFoundFile] enableCaching:NO]; 157 | handler.statusCode = 404; 158 | return handler; 159 | } 160 | 161 | 162 | 163 | #pragma mark Routes 164 | 165 | 166 | - (WARoute*)addRouteSelector:(SEL)sel HTTPMethod:(NSString*)method path:(NSString*)path { 167 | if(![self respondsToSelector:sel]) 168 | NSLog(@"Warning: %@ doesn't respond to route handler message '%@'.", self, NSStringFromSelector(sel)); 169 | 170 | WARoute *route = [WARoute routeWithPathExpression:path method:method target:self action:sel]; 171 | 172 | [self addRequestHandler:route]; 173 | return route; 174 | } 175 | 176 | 177 | - (void)setRequest:(WARequest*)req response:(WAResponse*)resp { 178 | self.request = req; 179 | self.response = resp; 180 | } 181 | 182 | 183 | - (void)preprocess {} 184 | - (void)postprocess {} 185 | 186 | 187 | - (WASession*)session { 188 | if(!self.sessionGenerator) 189 | [NSException raise:NSGenericException format:@"The session property cannot be used without first setting a sessionGenerator."]; 190 | return [self.sessionGenerator sessionForRequest:self.request response:self.response]; 191 | } 192 | 193 | 194 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WACookie.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTCookie.h 3 | // ForasteroTest 4 | // 5 | // Created by Tomas Franzén on 2009-10-14. 6 | // Copyright 2009 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface WACookie : NSObject 13 | @property(copy) NSString *name; 14 | @property(copy) NSString *value; 15 | @property(copy) NSString *path; 16 | @property(copy) NSString *domain; 17 | @property(copy) NSDate *expirationDate; 18 | @property BOOL secure; 19 | 20 | @property(readonly) NSString *headerFieldValue; 21 | 22 | - (id)initWithName:(NSString*)n value:(NSString*)val; 23 | - (id)initWithName:(NSString*)n value:(NSString*)val expirationDate:(NSDate*)date path:(NSString*)p domain:(NSString*)d; 24 | - (id)initWithName:(NSString*)n value:(NSString*)val lifespan:(NSTimeInterval)time path:(NSString*)p domain:(NSString*)d; 25 | 26 | + (id)expiredCookieWithName:(NSString*)name; 27 | 28 | + (NSSet*)cookiesFromHeaderValue:(NSString*)headerValue; 29 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WACookie.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTCookie.m 3 | // ForasteroTest 4 | // 5 | // Created by Tomas Franzén on 2009-10-14. 6 | // Copyright 2009 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WACookie.h" 10 | 11 | 12 | @implementation WACookie 13 | 14 | 15 | - (id)initWithName:(NSString*)cookieName value:(NSString*)cookieValue expirationDate:(NSDate*)date path:(NSString*)p domain:(NSString*)d { 16 | if(!(self = [super init])) return nil; 17 | 18 | NSParameterAssert(cookieName && cookieValue); 19 | self.name = cookieName; 20 | self.value = cookieValue; 21 | self.expirationDate = date; 22 | self.path = p; 23 | self.domain = d; 24 | 25 | return self; 26 | } 27 | 28 | 29 | - (id)initWithName:(NSString*)n value:(NSString*)val lifespan:(NSTimeInterval)time path:(NSString*)p domain:(NSString*)d { 30 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:time]; 31 | return [self initWithName:n value:val expirationDate:date path:p domain:d]; 32 | } 33 | 34 | 35 | - (id)initWithName:(NSString*)n value:(NSString*)val { 36 | return [self initWithName:n value:val expirationDate:nil path:nil domain:nil]; 37 | } 38 | 39 | 40 | - (NSString*)description { 41 | return [NSString stringWithFormat:@"<%@ %p: %@=%@>", [self class], self, self.name, self.value]; 42 | } 43 | 44 | 45 | - (id)copyWithZone:(NSZone *)zone { 46 | WACookie *copy = [[WACookie alloc] initWithName:self.name value:self.value expirationDate:self.expirationDate path:self.path domain:self.domain]; 47 | copy.secure = self.secure; 48 | return copy; 49 | } 50 | 51 | 52 | - (NSString*)headerFieldValue { 53 | NSMutableString *baseValue = [NSMutableString stringWithFormat:@"%@=%@", WAConstructHTTPStringValue(self.name), WAConstructHTTPStringValue(self.value)]; 54 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObject:@"1" forKey:@"Version"]; 55 | 56 | if(self.expirationDate) { 57 | [params setObject:[NSString stringWithFormat:@"%qu", (uint64_t)[self.expirationDate timeIntervalSinceNow]] forKey:@"Max-Age"]; 58 | // Compatibility with the old Netscape spec 59 | [params setObject:[[[self class] expiryDateFormatter] stringFromDate:self.expirationDate] forKey:@"Expires"]; 60 | } 61 | 62 | if(self.path) [params setObject:self.path forKey:@"Path"]; 63 | if(self.domain) [params setObject:self.domain forKey:@"Domain"]; 64 | if(self.secure) [params setObject:[NSNull null] forKey:@"Secure"]; 65 | 66 | return [baseValue stringByAppendingString:WAConstructHTTPParameterString(params)]; 67 | } 68 | 69 | 70 | // Old Netscape date format 71 | + (NSDateFormatter*)expiryDateFormatter { 72 | static NSDateFormatter *formatter; 73 | if(!formatter) { 74 | formatter = [[NSDateFormatter alloc] init]; 75 | [formatter setDateFormat:@"EEE, dd-MMM-y HH:mm:ss 'GMT'"]; 76 | [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; 77 | [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; 78 | } 79 | return formatter; 80 | } 81 | 82 | 83 | + (NSSet*)cookiesFromHeaderValue:(NSString*)headerValue { 84 | NSScanner *s = [NSScanner scannerWithString:headerValue]; 85 | NSMutableSet *cookies = [NSMutableSet set]; 86 | 87 | while(1) { 88 | NSString *name, *value; 89 | if(![s scanUpToString:@"=" intoString:&name]) break; 90 | if(![s scanString:@"=" intoString:NULL]) break; 91 | if(![s scanUpToString:@";" intoString:&value] && !value) break; 92 | [s scanString:@";" intoString:NULL]; 93 | 94 | WACookie *c = [[WACookie alloc] initWithName:name value:value expirationDate:nil path:nil domain:nil]; 95 | [cookies addObject:c]; 96 | } 97 | return cookies; 98 | } 99 | 100 | 101 | + (id)expiredCookieWithName:(NSString*)name { 102 | return [[self alloc] initWithName:name value:@"" lifespan:-10000 path:nil domain:nil]; 103 | } 104 | 105 | 106 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WADirectoryHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSDirectoryHandler.h 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-11. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WARequestHandler.h" 10 | 11 | @interface WADirectoryHandler : WARequestHandler 12 | - (id)initWithDirectory:(NSString*)root requestPath:(NSString*)path; 13 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WADirectoryHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSDirectoryHandler.m 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-11. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WADirectoryHandler.h" 10 | #import "WARequest.h" 11 | #import "WAResponse.h" 12 | #import "WAStaticFileHandler.h" 13 | 14 | 15 | @interface WADirectoryHandler () 16 | @property(copy) NSString *directoryRoot; 17 | @property(copy) NSString *requestPathRoot; 18 | @end 19 | 20 | 21 | 22 | @implementation WADirectoryHandler 23 | 24 | 25 | - (id)initWithDirectory:(NSString*)root requestPath:(NSString*)path { 26 | if(!(self = [super init])) return nil; 27 | 28 | BOOL isDir; 29 | if(![[NSFileManager defaultManager] fileExistsAtPath:root isDirectory:&isDir] || !isDir) 30 | NSLog(@"Warning: Directory %@ does not exist.", root); 31 | 32 | if(![path hasPrefix:@"/"]) 33 | [NSException raise:NSInvalidArgumentException format:@"Request path must begin with '/'."]; 34 | 35 | self.directoryRoot = root; 36 | self.requestPathRoot = path; 37 | if(![self.requestPathRoot hasSuffix:@"/"]) 38 | self.requestPathRoot = [self.requestPathRoot stringByAppendingString:@"/"]; 39 | 40 | return self; 41 | } 42 | 43 | 44 | - (NSString*)filePathForRequestPath:(NSString*)path { 45 | path = [path substringFromIndex:[self.requestPathRoot length]]; 46 | return [self.directoryRoot stringByAppendingPathComponent:path]; 47 | } 48 | 49 | 50 | - (BOOL)canHandleRequest:(WARequest *)req { 51 | NSString *path = req.path; 52 | if(![path hasPrefix:self.requestPathRoot]) return NO; 53 | 54 | NSString *filePath = [self filePathForRequestPath:path]; 55 | BOOL isDir; 56 | if(![[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDir] || isDir) return false; 57 | for(NSString *component in [filePath pathComponents]) 58 | if([component hasPrefix:@"."]) return NO; // Disallow invisible files 59 | return YES; 60 | } 61 | 62 | 63 | - (void)handleRequest:(WARequest *)req response:(WAResponse *)resp { 64 | NSString *filePath = [self filePathForRequestPath:req.path]; 65 | 66 | WAStaticFileHandler *fileHandler = [[WAStaticFileHandler alloc] initWithFile:filePath enableCaching:YES]; 67 | [fileHandler handleRequest:req response:resp]; 68 | } 69 | 70 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAFoundationExtras.h: -------------------------------------------------------------------------------- 1 | // 2 | // Extras.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @interface NSDictionary (WAExtras) 10 | - (NSDictionary*)dictionaryBySettingValue:(id)value forKey:(id)key; 11 | @end 12 | 13 | @interface NSString (WAExtras) 14 | - (NSString*)HTMLEscapedString; 15 | - (NSString*)HTML; 16 | - (NSString*)hexMD5DigestUsingEncoding:(NSStringEncoding)encoding; 17 | - (NSString*)stringByEncodingBase64UsingEncoding:(NSStringEncoding)encoding; 18 | - (NSString*)stringByDecodingBase64UsingEncoding:(NSStringEncoding)encoding; 19 | - (NSString*)stringByEnforcingCharacterSet:(NSCharacterSet*)set; 20 | @end 21 | 22 | @interface NSArray (WAExtras) 23 | - (NSArray*)filteredArrayUsingPredicateFormat:(NSString*)format, ...; 24 | - (id)firstObjectMatchingPredicateFormat:(NSString*)format, ...; 25 | - (id)sortedArrayUsingKeyPath:(NSString*)keyPath selector:(SEL)selector ascending:(BOOL)ascending; 26 | @end 27 | 28 | @interface NSData (WAExtras) 29 | - (NSData*)MD5Digest; 30 | - (NSString*)hexMD5Digest; 31 | + (NSData*)dataByDecodingBase64:(NSString*)string; 32 | - (NSString*)base64String; 33 | - (NSData*)dataByEncryptingAES128UsingKey:(NSData*)key; 34 | - (NSData*)dataByDecryptingAES128UsingKey:(NSData*)key; 35 | @end 36 | 37 | @interface NSCharacterSet (WAExtras) 38 | + (id)characterSetWithRanges:(NSRange)firstRange, ...; 39 | + (NSCharacterSet*)ASCIIAlphanumericCharacterSet; 40 | @end 41 | 42 | @interface NSURL (WAExtras) 43 | - (NSString*)realPath; 44 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAFoundationExtras.m: -------------------------------------------------------------------------------- 1 | // 2 | // Extras.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAFoundationExtras.h" 10 | #import 11 | #import 12 | 13 | 14 | @implementation NSDictionary (WAExtras) 15 | 16 | - (NSDictionary*)dictionaryBySettingValue:(id)value forKey:(id)key { 17 | NSMutableDictionary *dict = [self mutableCopy]; 18 | [dict setObject:value forKey:key]; 19 | return dict; 20 | } 21 | 22 | @end 23 | 24 | 25 | @implementation NSString (WAExtras) 26 | 27 | - (NSString*)HTMLEscapedString { 28 | NSString *newString = self; 29 | newString = [newString stringByReplacingOccurrencesOfString:@"&" withString:@"&"]; 30 | newString = [newString stringByReplacingOccurrencesOfString:@"\"" withString:@"""]; 31 | newString = [newString stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; 32 | newString = [newString stringByReplacingOccurrencesOfString:@">" withString:@">"]; 33 | return newString; 34 | } 35 | 36 | 37 | - (NSString*)HTML { 38 | return [self HTMLEscapedString]; 39 | } 40 | 41 | 42 | - (NSString*)URIEscape { 43 | return (__bridge_transfer NSString*)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)self, NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8); 44 | } 45 | 46 | 47 | - (NSString*)hexMD5DigestUsingEncoding:(NSStringEncoding)encoding { 48 | return [[self dataUsingEncoding:encoding] hexMD5Digest]; 49 | } 50 | 51 | 52 | - (NSString*)stringByEncodingBase64UsingEncoding:(NSStringEncoding)encoding { 53 | return [[self dataUsingEncoding:encoding] base64String]; 54 | } 55 | 56 | 57 | - (NSString*)stringByDecodingBase64UsingEncoding:(NSStringEncoding)encoding { 58 | NSData *data = [NSData dataByDecodingBase64:self]; 59 | return [[NSString alloc] initWithData:data encoding:encoding]; 60 | } 61 | 62 | 63 | - (NSString*)stringByEnforcingCharacterSet:(NSCharacterSet*)set { 64 | NSMutableString *string = [NSMutableString string]; 65 | for(int i=0; i<[self length]; i++) { 66 | unichar c = [self characterAtIndex:i]; 67 | if([set characterIsMember:c]) [string appendFormat:@"%C", c]; 68 | } 69 | return string; 70 | } 71 | 72 | @end 73 | 74 | 75 | 76 | @implementation NSArray (WAExtras) 77 | 78 | - (NSArray*)filteredArrayUsingPredicateFormat:(NSString*)format, ... { 79 | va_list list; 80 | va_start(list, format); 81 | NSPredicate *p = [NSPredicate predicateWithFormat:format arguments:list]; 82 | va_end(list); 83 | return [self filteredArrayUsingPredicate:p]; 84 | } 85 | 86 | 87 | - (id)firstObjectMatchingPredicateFormat:(NSString*)format, ... { 88 | va_list list; 89 | va_start(list, format); 90 | NSPredicate *p = [NSPredicate predicateWithFormat:format arguments:list]; 91 | va_end(list); 92 | NSArray *array = [self filteredArrayUsingPredicate:p]; 93 | return [array count] ? [array objectAtIndex:0] : nil; 94 | } 95 | 96 | 97 | - (id)sortedArrayUsingKeyPath:(NSString*)keyPath selector:(SEL)selector ascending:(BOOL)ascending { 98 | NSSortDescriptor *desc = [NSSortDescriptor sortDescriptorWithKey:keyPath ascending:ascending selector:selector]; 99 | return [self sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]]; 100 | } 101 | 102 | @end 103 | 104 | 105 | 106 | @implementation NSData (WAExtras) 107 | 108 | - (NSString*)hexMD5Digest { 109 | NSData *digest = [self MD5Digest]; 110 | NSMutableString *hexDigest = [NSMutableString string]; 111 | for(int i=0; i<[digest length]; i++) 112 | [hexDigest appendFormat:@"%02x", ((uint8_t*)[digest bytes])[i]]; 113 | return hexDigest; 114 | } 115 | 116 | 117 | - (NSData*)MD5Digest { 118 | NSMutableData *digest = [NSMutableData dataWithLength:CC_MD5_DIGEST_LENGTH]; 119 | CC_MD5([self bytes], [self length], [digest mutableBytes]); 120 | return digest; 121 | } 122 | 123 | 124 | + (NSData*)dataByDecodingBase64:(NSString*)string { 125 | NSData *encodedData = [string dataUsingEncoding:NSASCIIStringEncoding]; 126 | SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL); 127 | SecTransformSetAttribute(transform, kSecTransformInputAttributeName, (__bridge CFTypeRef)encodedData, NULL); 128 | NSData *output = (__bridge_transfer NSData*)SecTransformExecute(transform, NULL); 129 | CFRelease(transform); 130 | return output; 131 | } 132 | 133 | 134 | - (NSString*)base64String { 135 | SecTransformRef transform = SecEncodeTransformCreate(kSecBase64Encoding, NULL); 136 | SecTransformSetAttribute(transform, kSecTransformInputAttributeName, (__bridge CFTypeRef)self, NULL); 137 | NSData *output = (__bridge_transfer NSData*)SecTransformExecute(transform, NULL); 138 | CFRelease(transform); 139 | return [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding]; 140 | } 141 | 142 | 143 | - (NSData*)dataByEncryptingAES128UsingKey:(NSData*)key { 144 | size_t outSize = 0; 145 | CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], NULL, [self bytes], [self length], NULL, 0, &outSize); 146 | 147 | NSMutableData *ciphertext = [NSMutableData dataWithLength:outSize]; 148 | if(CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], NULL, [self bytes], [self length], [ciphertext mutableBytes], outSize, &outSize) != kCCSuccess) 149 | return nil; 150 | return ciphertext; 151 | } 152 | 153 | 154 | - (NSData*)dataByDecryptingAES128UsingKey:(NSData*)key { 155 | size_t outSize = 0; 156 | CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], NULL, [self bytes], [self length], NULL, 0, &outSize); 157 | 158 | NSMutableData *cleartext = [NSMutableData dataWithLength:outSize]; 159 | if(CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], NULL, [self bytes], [self length], [cleartext mutableBytes], outSize, &outSize) != kCCSuccess) 160 | return nil; 161 | [cleartext setLength:outSize]; // Can be smaller due to padding 162 | return cleartext; 163 | } 164 | 165 | 166 | @end 167 | 168 | 169 | 170 | @implementation NSCharacterSet (WAExtras) 171 | 172 | + (id)characterSetWithRanges:(NSRange)firstRange, ... { 173 | NSMutableCharacterSet *set = [NSMutableCharacterSet characterSetWithRange:firstRange]; 174 | va_list list; 175 | va_start(list, firstRange); 176 | NSRange range; 177 | while((range = va_arg(list, NSRange)).length) 178 | [set addCharactersInRange:range]; 179 | va_end(list); 180 | return set; 181 | } 182 | 183 | 184 | + (NSCharacterSet*)ASCIIAlphanumericCharacterSet { 185 | static NSCharacterSet *set; 186 | if(!set) set = [NSCharacterSet characterSetWithRanges:NSMakeRange('a',26), NSMakeRange('A',26), NSMakeRange('0',10), NSMakeRange(0,0)]; 187 | return set; 188 | } 189 | 190 | @end 191 | 192 | 193 | 194 | @implementation NSURL (WAExtras) 195 | 196 | // Avoids NSURL's stupid behavior of stripping trailing slashes in -path 197 | - (NSString*)realPath { 198 | return (__bridge_transfer NSString*)CFURLCopyPath((__bridge CFURLRef)self); 199 | } 200 | 201 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAHTTPSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // WAHTTPSupport.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-10. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | typedef enum { 10 | WANoneAuthenticationScheme, 11 | WABasicAuthenticationScheme, 12 | WADigestAuthenticationScheme, 13 | } WAAuthenticationScheme; 14 | 15 | 16 | typedef struct { 17 | uint64_t firstByte; 18 | uint64_t lastByte; 19 | } WAByteRange; 20 | 21 | 22 | enum {WABytePositionUndefined = UINT64_MAX}; 23 | static const WAByteRange WAByteRangeInvalid = {WABytePositionUndefined, WABytePositionUndefined}; 24 | 25 | extern WAByteRange WAByteRangeMake(uint64_t first, uint64_t last); 26 | extern BOOL WAByteRangeIsInvalid(WAByteRange range); 27 | extern WAByteRange WAByteRangeFromRangeSpec(NSString *spec); 28 | extern WAByteRange WAByteRangeMakeAbsolute(WAByteRange range, uint64_t availableLength); 29 | extern WAByteRange WAByteRangeCombine(WAByteRange range1, WAByteRange range2); -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAHTTPSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // WAHTTPSupport.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-10. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | #import "WAHTTPSupport.h" 9 | 10 | 11 | WAByteRange WAByteRangeFromRangeSpec(NSString *spec) { 12 | NSArray *parts = [spec componentsSeparatedByString:@"-"]; 13 | if([parts count] != 2) return WAByteRangeInvalid; 14 | 15 | WAByteRange range = WAByteRangeInvalid; 16 | NSString *firstByteString = [parts objectAtIndex:0], *lastByteString = [parts objectAtIndex:1]; 17 | 18 | if([firstByteString length]) 19 | range.firstByte = [firstByteString longLongValue]; 20 | if([lastByteString length]) 21 | range.lastByte = [lastByteString longLongValue]; 22 | 23 | if([firstByteString length] && [lastByteString length] && range.lastByte < range.firstByte) 24 | return WAByteRangeInvalid; 25 | 26 | return range; 27 | } 28 | 29 | 30 | BOOL WAByteRangeIsInvalid(WAByteRange range) { 31 | return range.firstByte == WABytePositionUndefined && range.lastByte == WABytePositionUndefined; 32 | } 33 | 34 | 35 | WAByteRange WAByteRangeMakeAbsolute(WAByteRange range, uint64_t availableLength) { 36 | if(WAByteRangeIsInvalid(range)) return range; 37 | if(range.firstByte == WABytePositionUndefined) { 38 | if(range.lastByte == 0) return WAByteRangeInvalid; 39 | range.firstByte = MAX(availableLength-range.lastByte, 0); 40 | range.lastByte = availableLength-1; 41 | }else if(range.lastByte == WABytePositionUndefined) { 42 | if(range.firstByte >= availableLength) return WAByteRangeInvalid; 43 | range.lastByte = availableLength-1; 44 | } 45 | range.firstByte = MIN(range.firstByte,availableLength-1); 46 | range.lastByte = MIN(range.lastByte,availableLength-1); 47 | return range; 48 | } 49 | 50 | 51 | WAByteRange WAByteRangeMake(uint64_t first, uint64_t last) { 52 | return (WAByteRange){first, last}; 53 | } 54 | 55 | 56 | BOOL WAByteRangeContainsByte(WAByteRange concreteRange, uint64_t bytePos) { 57 | return bytePos >= concreteRange.firstByte && bytePos <= concreteRange.lastByte; 58 | } 59 | 60 | 61 | BOOL WAByteRangesOverlap(WAByteRange range1, WAByteRange range2) { 62 | return WAByteRangeContainsByte(range1, range2.firstByte) || WAByteRangeContainsByte(range1, range2.lastByte) || WAByteRangeContainsByte(range2, range1.firstByte) || WAByteRangeContainsByte(range2, range1.lastByte); 63 | } 64 | 65 | 66 | WAByteRange WAByteRangeCombine(WAByteRange range1, WAByteRange range2) { 67 | if(WAByteRangesOverlap(range1, range2) || range1.lastByte == range2.firstByte-1 || range2.lastByte == range1.firstByte-1) 68 | return WAByteRangeMake(MIN(range1.firstByte, range2.firstByte), MAX(range1.lastByte, range2.lastByte)); 69 | else 70 | return WAByteRangeInvalid; 71 | } 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WALegacy.h: -------------------------------------------------------------------------------- 1 | // 2 | // WALegacy.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2012-03-06. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "WARequest.h" 10 | 11 | @interface WARequest (WALegacy) 12 | @property(readonly, nonatomic) NSDictionary *POSTParameters __attribute__((deprecated("use bodyParameters instead"))); 13 | - (NSString*)valueForPOSTParameter:(NSString*)name __attribute__((deprecated("use valueForBodyParameter: instead"))); 14 | @end 15 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WALegacy.m: -------------------------------------------------------------------------------- 1 | // 2 | // WALegacy.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2012-03-06. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "WALegacy.h" 10 | 11 | @implementation WARequest (WALegacy) 12 | 13 | - (NSDictionary*)POSTParameters { 14 | NSLog(@"WARequest.POSTParameters is deprecated. Use bodyParameters instead."); 15 | return self.bodyParameters; 16 | } 17 | 18 | - (NSString *)valueForPOSTParameter:(NSString *)name { 19 | NSLog(@"-[WARequest valueForPOSTParameter:] is deprecated. Use valueForBodyParameter: instead."); 20 | return [self valueForBodyParameter:name]; 21 | 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WALocalization.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSLocalization.h 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-21. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface WALocalization : NSObject { 13 | NSDictionary *mapping; 14 | } 15 | 16 | + (id)localizationNamed:(NSString*)name; 17 | - (id)initWithMapping:(NSDictionary*)dictionary; 18 | - (id)initWithContentsOfFile:(NSString*)file; 19 | 20 | - (NSString*)stringForKeyPath:(NSString*)key; 21 | 22 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WALocalization.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSLocalization.m 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-21. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WALocalization.h" 10 | 11 | 12 | @implementation WALocalization 13 | 14 | + (id)localizationNamed:(NSString*)name { 15 | NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"plist"]; 16 | if(!path) { 17 | NSLog(@"Warning: Localization '%@' not found.", name); 18 | return nil; 19 | } 20 | return [[self alloc] initWithContentsOfFile:path]; 21 | } 22 | 23 | 24 | - (id)initWithContentsOfFile:(NSString*)file { 25 | return [self initWithMapping:[NSDictionary dictionaryWithContentsOfFile:file]]; 26 | } 27 | 28 | - (id)initWithMapping:(NSDictionary*)dictionary { 29 | self = [super init]; 30 | mapping = [dictionary copy]; 31 | return self; 32 | } 33 | 34 | - (NSString*)stringForKeyPath:(NSString*)key { 35 | NSString *value = [mapping valueForKeyPath:key]; 36 | if(!value) { 37 | NSLog(@"Warning: Missing localization key %@", key); 38 | return key; 39 | } 40 | return value; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAMultipartPart.h: -------------------------------------------------------------------------------- 1 | // 2 | // WAMultipartPart.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-20. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | 10 | @interface WAMultipartPart : NSObject 11 | - (id)initWithHeaderData:(NSData*)headerData; 12 | - (void)appendData:(NSData*)bodyData; 13 | - (void)finish; 14 | 15 | @property(readonly, copy) NSDictionary *headerFields; 16 | @property(readonly, copy) NSData *data; 17 | @property(readonly, copy) NSString *temporaryFile; 18 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAMultipartPart.m: -------------------------------------------------------------------------------- 1 | // 2 | // WAMultipartPart.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-20. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAMultipartPart.h" 10 | 11 | static const uint64_t WAMultipartPartMaxBodyBufferLength = 1000000; 12 | 13 | @interface WAMultipartPart () 14 | @property(readwrite, copy) NSDictionary *headerFields; 15 | @property(readwrite, strong) NSMutableData *mutableData; 16 | @property(readwrite, copy) NSString *temporaryFile; 17 | @property(strong) NSFileHandle *fileHandle; 18 | @end 19 | 20 | 21 | 22 | @implementation WAMultipartPart 23 | 24 | - (id)initWithHeaderData:(NSData*)headerData { 25 | self = [super init]; 26 | NSString *string = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding]; 27 | if(!string) return nil; 28 | 29 | NSArray *fieldStrings = [string componentsSeparatedByString:@"\r\n"]; 30 | NSMutableDictionary *fields = [NSMutableDictionary dictionary]; 31 | for(NSString *fieldString in fieldStrings) { 32 | NSInteger split = [fieldString rangeOfString:@": "].location; 33 | if(split == NSNotFound) continue; 34 | NSString *key = [fieldString substringToIndex:split]; 35 | NSString *value = [fieldString substringFromIndex:split+2]; 36 | [fields setObject:value forKey:key]; 37 | } 38 | 39 | self.headerFields = fields; 40 | self.mutableData = [NSMutableData data]; 41 | return self; 42 | } 43 | 44 | 45 | - (NSData*)data { 46 | return self.mutableData; 47 | } 48 | 49 | 50 | - (void)switchToFile { 51 | self.temporaryFile = [NSTemporaryDirectory() stringByAppendingPathComponent:WAGenerateUUIDString()]; 52 | [[NSFileManager defaultManager] createFileAtPath:self.temporaryFile contents:self.mutableData attributes:[NSDictionary dictionary]]; 53 | self.mutableData = nil; 54 | self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.temporaryFile]; 55 | [self.fileHandle seekToEndOfFile]; 56 | } 57 | 58 | 59 | - (void)appendData:(NSData*)bodyData { 60 | if(self.mutableData) { 61 | [self.mutableData appendData:bodyData]; 62 | if([self.mutableData length] > WAMultipartPartMaxBodyBufferLength) { 63 | [self switchToFile]; 64 | } 65 | }else{ 66 | [self.fileHandle writeData:bodyData]; 67 | } 68 | } 69 | 70 | - (void)finish { 71 | if(self.fileHandle) { 72 | [self.fileHandle truncateFileAtOffset:[self.fileHandle offsetInFile]-2]; // strip CRLF 73 | [self.fileHandle closeFile]; 74 | self.fileHandle = nil; 75 | }else{ 76 | [self.mutableData setLength:[self.mutableData length]-2]; 77 | } 78 | } 79 | 80 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAMultipartReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // WAMultipartReader.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-20. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @class WAMultipartReader, WAMultipartPart, GCDAsyncSocket; 10 | #import "GCDAsyncSocket.h" 11 | 12 | @protocol WAMultipartReaderDelegate 13 | - (void)multipartReader:(WAMultipartReader*)reader finishedWithParts:(NSArray*)parts; 14 | - (void)multipartReaderFailed:(WAMultipartReader*)reader; 15 | @end 16 | 17 | 18 | 19 | @interface WAMultipartReader : NSObject { 20 | id delegate; 21 | id oldSocketDelegate; 22 | GCDAsyncSocket *socket; 23 | NSString *boundary; 24 | 25 | NSMutableArray *parts; 26 | WAMultipartPart *currentPart; 27 | } 28 | 29 | - (id)initWithSocket:(GCDAsyncSocket*)sock boundary:(NSString*)boundaryString delegate:(id)del; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAMultipartReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // WAMultipartReader.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-20. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | #import "WAMultipartReader.h" 9 | #import "WAMultipartPart.h" 10 | #import "GCDAsyncSocket.h" 11 | 12 | enum { 13 | WAMPRInitialBoundary, 14 | WAMPRPartHeader, 15 | WAMPRPartBodyChunk, 16 | }; 17 | 18 | static const uint64_t WAMPRMaxPartHeaderLength = 10000; 19 | static const uint64_t WAMPRMaxPartBodyChunkLength = 10000; 20 | 21 | @interface WAMultipartReader () 22 | @end 23 | 24 | 25 | 26 | @implementation WAMultipartReader 27 | 28 | - (id)initWithSocket:(GCDAsyncSocket*)sock boundary:(NSString*)boundaryString delegate:(id)del { 29 | self = [super init]; 30 | delegate = del; 31 | socket = sock; 32 | oldSocketDelegate = [socket delegate]; 33 | [socket setDelegate:self]; 34 | boundary = [boundaryString copy]; 35 | parts = [NSMutableArray array]; 36 | [self readInitialBoundary]; 37 | return self; 38 | } 39 | 40 | 41 | - (void)fail { 42 | [socket setDelegate:oldSocketDelegate]; 43 | [delegate multipartReaderFailed:self]; 44 | } 45 | 46 | 47 | - (void)readInitialBoundary { 48 | [socket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:10 maxLength:[boundary length]+4 tag:WAMPRInitialBoundary]; 49 | } 50 | 51 | 52 | - (void)readPartHeader { 53 | NSData *terminator = [NSData dataWithBytes:"\r\n\r\n" length:4]; 54 | [socket readDataToData:terminator withTimeout:10 maxLength:WAMPRMaxPartHeaderLength tag:WAMPRPartHeader]; 55 | } 56 | 57 | 58 | - (void)readPartBody { 59 | [socket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:60 maxLength:WAMPRMaxPartBodyChunkLength tag:WAMPRPartBodyChunk]; 60 | } 61 | 62 | - (void)finishPart { 63 | [currentPart finish]; 64 | [parts addObject:currentPart]; 65 | currentPart = nil; 66 | } 67 | 68 | - (void)finish { 69 | [delegate multipartReader:self finishedWithParts:parts]; 70 | } 71 | 72 | 73 | - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { 74 | if(tag == WAMPRInitialBoundary) { 75 | NSString *correctString = [NSString stringWithFormat:@"--%@\r\n", boundary]; 76 | NSString *givenString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 77 | if(![givenString isEqual:correctString]) 78 | return [self fail]; 79 | 80 | [self readPartHeader]; 81 | 82 | }else if(tag == WAMPRPartHeader) { 83 | currentPart = [[WAMultipartPart alloc] initWithHeaderData:data]; 84 | if(!currentPart) return [self fail]; 85 | [self readPartBody]; 86 | 87 | }else if(tag == WAMPRPartBodyChunk) { 88 | NSData *boundaryData = [[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]; 89 | NSData *boundaryEndData = [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]; 90 | if([data isEqual:boundaryData]) { 91 | [self finishPart]; 92 | [self readPartHeader]; 93 | }else if([data isEqual:boundaryEndData]){ 94 | [self finishPart]; 95 | [self finish]; 96 | }else{ 97 | [currentPart appendData:data]; 98 | [self readPartBody]; 99 | } 100 | } 101 | } 102 | 103 | 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WARequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSRequest.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAHTTPSupport.h" 10 | @class GCDAsyncSocket, WACookie, WAUploadedFile, WAMultipartReader; 11 | 12 | @interface WARequest : NSObject 13 | @property(readonly, copy) NSString *HTTPVersion; 14 | @property(readonly, copy) NSString *method; 15 | @property(readonly, copy) NSString *path; 16 | @property(readonly, copy) NSData *body; 17 | 18 | @property(readonly, copy) NSString *query; 19 | @property(readonly, nonatomic, copy) NSDictionary *headerFields; 20 | @property(readonly, copy) NSDictionary *queryParameters; 21 | @property(readonly, copy) NSDictionary *bodyParameters; 22 | @property(readonly, nonatomic) NSSet *uploadedFiles; 23 | @property(readonly, nonatomic) NSString *host; 24 | @property(readonly, nonatomic) NSURL *URL; 25 | @property(readonly, nonatomic) NSURL *referrer; 26 | @property(readonly, nonatomic) NSSet *origins; // Origin header 27 | @property(readonly, nonatomic) NSString *mediaType; 28 | 29 | @property(readonly, copy) NSDictionary *cookies; 30 | @property(readonly, nonatomic) NSDate *conditionalModificationDate; 31 | @property(readonly, nonatomic) NSArray *acceptedMediaTypes; 32 | @property(readonly, nonatomic) WAAuthenticationScheme authenticationScheme; 33 | 34 | @property(readonly, copy) NSArray *byteRanges; // NSValue-wrapped WAByteRanges 35 | 36 | @property(readonly, copy) NSString *clientAddress; 37 | @property(readonly, nonatomic) BOOL wantsPersistentConnection; 38 | 39 | 40 | - (NSString*)valueForQueryParameter:(NSString*)name; 41 | - (NSString*)valueForHeaderField:(NSString*)fieldName; 42 | - (NSString*)valueForBodyParameter:(NSString*)name; 43 | - (WACookie*)cookieForName:(NSString*)name; 44 | - (WAUploadedFile*)uploadedFileForName:(NSString*)name; 45 | 46 | - (BOOL)acceptsMediaType:(NSString*)type; 47 | 48 | - (BOOL)getAuthenticationUser:(NSString**)outUser password:(NSString**)outPassword; 49 | - (BOOL)hasValidAuthenticationForUsername:(NSString*)name password:(NSString*)password; 50 | - (BOOL)hasValidAuthenticationForCredentialHash:(NSString*)hash; 51 | + (NSString*)credentialHashForUsername:(NSString*)user password:(NSString*)password realm:(NSString*)realm; 52 | 53 | // Sorted array of absolute combined ranges 54 | - (NSArray*)canonicalByteRangesForDataLength:(uint64_t)length; 55 | - (void)enumerateCanonicalByteRangesForDataLength:(uint64_t)length usingBlock:(void(^)(WAByteRange range, BOOL *stop))block; 56 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WARequestHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSRequestHandler.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @class WARequest, WAResponse, GCDAsyncSocket; 10 | 11 | @interface WARequestHandler : NSObject 12 | - (BOOL)canHandleRequest:(WARequest*)req; 13 | - (void)handleRequest:(WARequest*)req response:(WAResponse*)resp; 14 | - (void)handleRequest:(WARequest*)req response:(WAResponse*)resp socket:(GCDAsyncSocket*)sock; 15 | 16 | - (WARequestHandler*)handlerForRequest:(WARequest*)req; 17 | - (void)connectionDidClose; 18 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WARequestHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSRequestHandler.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WARequestHandler.h" 10 | #import "WAServerConnection.h" 11 | 12 | #import "WARequest.h" 13 | #import "WAResponse.h" 14 | #import "GCDAsyncSocket.h" 15 | 16 | 17 | @implementation WARequestHandler 18 | 19 | 20 | - (BOOL)canHandleRequest:(WARequest*)req { 21 | return NO; 22 | } 23 | 24 | 25 | - (WARequestHandler*)handlerForRequest:(WARequest*)req { 26 | return self; 27 | } 28 | 29 | 30 | - (void)handleRequest:(WARequest*)req response:(WAResponse*)resp socket:(GCDAsyncSocket*)sock { 31 | [self handleRequest:req response:resp]; 32 | } 33 | 34 | 35 | - (void)handleRequest:(WARequest*)req response:(WAResponse*)resp {} 36 | - (void)connectionDidClose {} 37 | 38 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSResponse.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAHTTPSupport.h" 10 | @class WACookie; 11 | 12 | 13 | @interface WAResponse : NSObject 14 | @property NSUInteger statusCode; 15 | @property NSStringEncoding bodyEncoding; 16 | @property(getter=isProgressive, nonatomic) BOOL progressive; // Use chunked transfer encoding? 17 | 18 | @property(copy) NSString *mediaType; 19 | @property(copy) NSDate *modificationDate; 20 | @property(copy) NSString *entityTag; 21 | @property BOOL hasBody; 22 | 23 | @property(strong, readonly) NSDictionary *headerFields; 24 | @property(strong, readonly) NSDictionary *cookies; 25 | @property(copy) NSSet *allowedOrigins; // Access-Control-Allow-Origin 26 | 27 | - (void)appendBodyData:(NSData*)data; 28 | - (void)appendString:(NSString*)string; 29 | - (void)appendFormat:(NSString*)format, ...; 30 | - (void)finish; 31 | 32 | - (NSString*)valueForHeaderField:(NSString*)fieldName; 33 | - (void)setValue:(NSString*)value forHeaderField:(NSString*)fieldName; 34 | 35 | - (void)redirectToURL:(NSURL*)URL; 36 | - (void)redirectToURL:(NSURL*)URL withStatusCode:(NSUInteger)code; 37 | - (void)redirectToURLFormat:(NSString*)format, ...; 38 | 39 | - (void)addCookie:(WACookie*)cookie; 40 | - (void)removeCookieNamed:(NSString*)name; 41 | - (void)requestAuthenticationForRealm:(NSString*)realm scheme:(WAAuthenticationScheme)scheme; 42 | 43 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSResponse.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAResponse.h" 10 | #import "WARequest.h" 11 | #import "WACookie.h" 12 | #import "GCDAsyncSocket.h" 13 | 14 | 15 | @interface WAResponse () 16 | @property(strong) WARequest *request; 17 | @property(strong) GCDAsyncSocket *socket; 18 | @property(copy) void(^completionHandler)(BOOL keepAlive); 19 | 20 | @property(strong) NSMutableData *body; 21 | @property BOOL hasSentHeader; 22 | 23 | @property(strong, readwrite) NSDictionary *headerFields; 24 | @property(strong, readwrite) NSDictionary *cookies; 25 | @end 26 | 27 | 28 | @implementation WAResponse { 29 | NSMutableDictionary *_headerFields; 30 | NSMutableDictionary *_cookies; 31 | } 32 | 33 | 34 | - (id)initWithRequest:(WARequest*)request socket:(GCDAsyncSocket*)socket { 35 | if(!(self = [super init])) return nil; 36 | 37 | self.request = request; 38 | self.socket = socket; 39 | 40 | self.hasBody = YES; 41 | self.body = [NSMutableData data]; 42 | self.bodyEncoding = NSUTF8StringEncoding; 43 | self.headerFields = [NSMutableDictionary dictionary]; 44 | self.statusCode = 200; 45 | self.mediaType = @"text/html"; 46 | self.cookies = [NSMutableDictionary dictionary]; 47 | 48 | return self; 49 | } 50 | 51 | 52 | - (BOOL)isHTTP11 { 53 | return [self.request.HTTPVersion isEqual:(id)kCFHTTPVersion1_1]; 54 | } 55 | 56 | 57 | - (CFHTTPMessageRef)createHeader { 58 | CFHTTPMessageRef message = CFHTTPMessageCreateResponse(NULL, self.statusCode, NULL, (__bridge CFStringRef)self.request.HTTPVersion); 59 | NSDictionary *fields = [self preparedHeaderFields]; 60 | for(NSString *key in fields) 61 | CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)[fields objectForKey:key]); 62 | 63 | return message; 64 | } 65 | 66 | 67 | - (void)requireProgressiveHeaderNotSent { 68 | if(self.progressive && self.hasSentHeader) 69 | [NSException raise:@"WAResponseHeaderAlreadySentException" format:@"You can't modify or re-send the header after it has been sent."]; 70 | } 71 | 72 | 73 | - (void)sendHeader { 74 | [self requireProgressiveHeaderNotSent]; 75 | CFHTTPMessageRef message = [self createHeader]; 76 | NSData *headerData = (__bridge_transfer NSData*)CFHTTPMessageCopySerializedMessage(message); 77 | CFRelease(message); 78 | [self.socket writeData:headerData withTimeout:-1 tag:0]; 79 | self.hasSentHeader = YES; 80 | } 81 | 82 | 83 | - (void)sendHeaderIfNeeded { 84 | if(self.progressive && !self.hasSentHeader) [self sendHeader]; 85 | } 86 | 87 | 88 | - (void)sendFullResponse { 89 | [self sendHeader]; 90 | if([self needsBody]) 91 | [self.socket writeData:self.body withTimeout:-1 tag:0]; 92 | } 93 | 94 | 95 | - (void)finish { 96 | if(!self.completionHandler) return; 97 | 98 | if(self.progressive) 99 | [self sendTerminationChunk]; 100 | else 101 | [self sendFullResponse]; 102 | 103 | self.completionHandler(self.request.wantsPersistentConnection); 104 | self.completionHandler = nil; 105 | } 106 | 107 | 108 | - (void)sendBodyChunk:(NSData*)data { 109 | if([data length] == 0) return; 110 | [self.socket writeData:[[NSString stringWithFormat:@"%qX\r\n", (uint64_t)[data length]] dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; 111 | [self.socket writeData:data withTimeout:-1 tag:0]; 112 | [self.socket writeData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1]; 113 | } 114 | 115 | 116 | - (void)sendTerminationChunk { 117 | [self.socket writeData:[[NSString stringWithFormat:@"0\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; 118 | } 119 | 120 | 121 | - (void)setProgressive:(BOOL)p { 122 | _progressive = p && [self isHTTP11]; 123 | } 124 | 125 | 126 | - (BOOL)needsBody { 127 | return ![self.request.method isEqual:@"HEAD"]; 128 | } 129 | 130 | 131 | - (void)appendBodyData:(NSData*)data { 132 | [self sendHeaderIfNeeded]; 133 | if(self.progressive) 134 | [self sendBodyChunk:data]; 135 | else 136 | [self.body appendData:data]; 137 | } 138 | 139 | 140 | - (void)appendString:(NSString*)string { 141 | [self appendBodyData:[string dataUsingEncoding:self.bodyEncoding]]; 142 | } 143 | 144 | 145 | - (void)appendFormat:(NSString*)format, ... { 146 | va_list list; 147 | va_start(list, format); 148 | NSString *string = [[NSString alloc] initWithFormat:format arguments:list]; 149 | va_end(list); 150 | [self appendString:string]; 151 | } 152 | 153 | 154 | 155 | - (NSString*)defaultUserAgent { 156 | static NSString *cachedValue; 157 | if(cachedValue) return cachedValue; 158 | 159 | NSDictionary *frameworkInfo = [[NSBundle bundleForClass:[self class]] infoDictionary]; 160 | NSString *versionString = frameworkInfo[@"CFBundleShortVersionString"]; 161 | NSString *frameworkName = frameworkInfo[@"CFBundleName"]; 162 | 163 | cachedValue = frameworkName; 164 | if([versionString length]) cachedValue = [cachedValue stringByAppendingFormat:@"/%@", versionString]; 165 | 166 | return cachedValue; 167 | } 168 | 169 | 170 | - (NSString*)charsetName { 171 | return (__bridge NSString*)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.bodyEncoding)); 172 | } 173 | 174 | 175 | - (NSString*)contentType { 176 | return self.mediaType ? [NSString stringWithFormat:@"%@; charset=%@", self.mediaType, self.charsetName] : nil; 177 | } 178 | 179 | 180 | - (BOOL)needsKeepAliveHeader { 181 | return [self.request.HTTPVersion isEqual:(id)kCFHTTPVersion1_0] && self.request.wantsPersistentConnection; 182 | } 183 | 184 | 185 | - (NSDictionary*)defaultHeaderFields { 186 | NSMutableDictionary *fields = [@{@"Server": [self defaultUserAgent], 187 | @"Date": [WAHTTPDateFormatter() stringFromDate:[NSDate date]] 188 | } mutableCopy]; 189 | 190 | if(self.progressive) 191 | fields[@"Transfer-Encoding"] = @"chunked"; 192 | 193 | else if(self.hasBody) 194 | fields[@"Content-Length"] = [NSString stringWithFormat:@"%qu", (uint64_t)self.body.length]; 195 | 196 | if([self needsKeepAliveHeader]) 197 | fields[@"Connection"] = @"Keep-Alive"; 198 | 199 | if([self contentType] && self.hasBody) 200 | fields[@"Content-Type"] = self.contentType; 201 | 202 | return fields; 203 | } 204 | 205 | 206 | - (NSDictionary*)preparedHeaderFields { 207 | NSMutableDictionary *fields = [NSMutableDictionary dictionary]; 208 | 209 | NSString *cookieString = [[self.cookies.allValues valueForKey:@"headerFieldValue"] componentsJoinedByString:@", "]; 210 | 211 | if([cookieString length]) 212 | fields[@"Set-Cookie"] = cookieString; 213 | 214 | if(self.modificationDate) 215 | fields[@"Last-Modified"] = [WAHTTPDateFormatter() stringFromDate:self.modificationDate]; 216 | 217 | NSDictionary *defaults = [self defaultHeaderFields]; 218 | for(NSString *key in defaults) 219 | if(!fields[key]) 220 | fields[key] = defaults[key]; 221 | 222 | for(id key in [fields copy]) 223 | if([fields[key] length] == 0) 224 | [fields removeObjectForKey:key]; 225 | 226 | if(self.allowedOrigins) 227 | fields[@"Access-Control-Allow-Origin"] = [self.allowedOrigins.allObjects componentsJoinedByString:@" "]; 228 | 229 | [fields addEntriesFromDictionary:self.headerFields]; 230 | return fields; 231 | } 232 | 233 | 234 | - (void)redirectToURL:(NSURL*)URL withStatusCode:(NSUInteger)code { 235 | URL = [NSURL URLWithString:[URL relativeString] relativeToURL:self.request.URL]; 236 | self.statusCode = code; 237 | [self setValue:[URL absoluteString] forHeaderField:@"Location"]; 238 | } 239 | 240 | 241 | - (void)redirectToURL:(NSURL*)URL { 242 | [self redirectToURL:URL withStatusCode:302]; 243 | } 244 | 245 | 246 | - (void)redirectToURLFormat:(NSString*)format, ... { 247 | va_list list; 248 | va_start(list, format); 249 | NSString *string = [[NSString alloc] initWithFormat:format arguments:list]; 250 | va_end(list); 251 | NSURL *URL = [NSURL URLWithString:string]; 252 | if(!URL) 253 | [NSException raise:NSInvalidArgumentException format:@"String was not a valid URL: %@", string]; 254 | [self redirectToURL:URL]; 255 | } 256 | 257 | 258 | - (NSString*)valueForHeaderField:(NSString*)fieldName { 259 | return [self.headerFields objectForKey:fieldName]; 260 | } 261 | 262 | 263 | - (void)setValue:(NSString*)value forHeaderField:(NSString*)fieldName { 264 | [self requireProgressiveHeaderNotSent]; 265 | if(value) 266 | _headerFields[fieldName] = value; 267 | else 268 | [_headerFields removeObjectForKey:fieldName]; 269 | } 270 | 271 | 272 | - (void)addCookie:(WACookie*)cookie { 273 | [self requireProgressiveHeaderNotSent]; 274 | _cookies[cookie.name] = cookie; 275 | } 276 | 277 | 278 | - (void)removeCookieNamed:(NSString*)name { 279 | [self requireProgressiveHeaderNotSent]; 280 | [_cookies removeObjectForKey:name]; 281 | } 282 | 283 | 284 | - (void)setEntityTag:(NSString*)ETag { 285 | [self setValue:ETag forHeaderField:@"ETag"]; 286 | } 287 | 288 | 289 | - (NSString*)entityTag { 290 | return [self valueForHeaderField:@"ETag"]; 291 | } 292 | 293 | 294 | - (void)finishWithErrorString:(NSString*)error { 295 | [self.body setLength:0]; 296 | [self appendString:error]; 297 | [self finish]; 298 | } 299 | 300 | 301 | - (void)requestAuthenticationForRealm:(NSString*)realm scheme:(WAAuthenticationScheme)scheme { 302 | self.statusCode = 401; 303 | NSString *response = nil; 304 | 305 | if(scheme == WABasicAuthenticationScheme) { 306 | response = [NSString stringWithFormat:@"Basic realm=\"%@\"", realm]; 307 | 308 | }else if(scheme == WADigestAuthenticationScheme) { 309 | NSString *nonce = WAGenerateUUIDString(); 310 | NSString *opaque = [realm hexMD5DigestUsingEncoding:NSUTF8StringEncoding]; 311 | response = [NSString stringWithFormat:@"Digest realm=\"%@\", qop=\"auth\", nonce=\"%@\", opaque=\"%@\"", realm, nonce, opaque]; 312 | } 313 | 314 | [self setValue:response forHeaderField:@"WWW-Authenticate"]; 315 | } 316 | 317 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WARoute.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSRoute.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WARequestHandler.h" 10 | 11 | @class WARequest, WAResponse, TFRegex; 12 | 13 | @interface WARoute : WARequestHandler 14 | @property(readonly, copy) NSString *method; 15 | @property(readonly, assign) SEL action; 16 | @property(readonly, weak) id target; 17 | 18 | + (id)routeWithPathExpression:(NSString*)expr method:(NSString*)m target:(id)object action:(SEL)selector; 19 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WARoute.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSRoute.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WARoute.h" 10 | #import "WARequest.h" 11 | #import "WAResponse.h" 12 | #import "WAApplication.h" 13 | #import "WATemplate.h" 14 | #import "WAPrivate.h" 15 | 16 | #import 17 | 18 | 19 | static NSCharacterSet *wildcardComponentCharacters; 20 | 21 | @interface WARoute () 22 | @property(strong) NSArray *components; 23 | @property(strong) NSArray *argumentWildcardMapping; 24 | 25 | @property(readwrite, copy) NSString *method; 26 | @property(readwrite, weak) id target; 27 | @property(readwrite, assign) SEL action; 28 | @end 29 | 30 | 31 | 32 | @implementation WARoute 33 | 34 | 35 | + (void)initialize { 36 | NSMutableCharacterSet *set = [NSMutableCharacterSet characterSetWithRanges:NSMakeRange('a', 26), NSMakeRange('A', 26), NSMakeRange('0', 10), NSMakeRange(0, 0)]; 37 | [set addCharactersInString:@"-_."]; 38 | wildcardComponentCharacters = set; 39 | } 40 | 41 | 42 | + (NSUInteger)wildcardCountInExpressionComponents:(NSArray*)components { 43 | NSUInteger count = 0; 44 | for(NSString *component in components) 45 | if([component hasPrefix:@"*"]) 46 | count++; 47 | return count; 48 | } 49 | 50 | 51 | - (void)setWildcardMappingForExpression:(NSString*)expression { 52 | NSMutableArray *componentStrings = [[expression componentsSeparatedByString:@"/"] mutableCopy]; 53 | 54 | NSUInteger wildcardCount = [[self class] wildcardCountInExpressionComponents:componentStrings]; 55 | 56 | if(wildcardCount > 6) 57 | [NSException raise:NSGenericException format:@"WARoute supports a maxumum of 6 arguments"]; 58 | 59 | NSMutableArray *wildcardMapping = [NSMutableArray array]; 60 | for(int i=0; i wildcardCount-1) { 69 | [NSException raise:NSInvalidArgumentException format:@"Invalid argument index %d in path expression. Must be in the range {1..%d}", (int)argumentIndex+1, (int)wildcardCount]; 70 | } 71 | if([wildcardMapping objectAtIndex:argumentIndex] != [NSNull null]) { 72 | [NSException raise:NSInvalidArgumentException format:@"Argument index %d is used more than once in path expression.", (int)argumentIndex+1]; 73 | } 74 | [wildcardMapping replaceObjectAtIndex:argumentIndex withObject:[NSNumber numberWithUnsignedInteger:wildcardCounter]]; 75 | [componentStrings replaceObjectAtIndex:i withObject:@"*"]; 76 | wildcardCounter++; 77 | } 78 | } 79 | 80 | self.argumentWildcardMapping = wildcardMapping; 81 | self.components = componentStrings; 82 | } 83 | 84 | 85 | - (id)initWithPathExpression:(NSString*)expression method:(NSString*)HTTPMetod target:(id)object action:(SEL)selector { 86 | if(!(self = [super init])) return nil; 87 | NSParameterAssert(expression && HTTPMetod && object && selector); 88 | 89 | [self setWildcardMappingForExpression:expression]; 90 | NSUInteger numArgs = [[NSStringFromSelector(selector) componentsSeparatedByString:@":"] count]-1; 91 | 92 | if(numArgs != self.argumentWildcardMapping.count) 93 | [NSException raise:NSInvalidArgumentException format:@"The number of arguments in the action selector (%@) must be equal to the number of wildcards in the path expression (%d).", NSStringFromSelector(selector), (int)self.argumentWildcardMapping.count]; 94 | 95 | self.method = HTTPMetod; 96 | self.action = selector; 97 | self.target = object; 98 | 99 | return self; 100 | } 101 | 102 | 103 | + (id)routeWithPathExpression:(NSString*)expr method:(NSString*)m target:(id)object action:(SEL)selector { 104 | return [[self alloc] initWithPathExpression:expr method:m target:object action:selector]; 105 | } 106 | 107 | 108 | - (BOOL)stringIsValidComponentValue:(NSString*)string { 109 | return [[string stringByTrimmingCharactersInSet:wildcardComponentCharacters] length] == 0; 110 | } 111 | 112 | 113 | - (BOOL)matchesPath:(NSString*)path wildcardValues:(NSArray**)outWildcards { 114 | NSArray *givenComponents = [path componentsSeparatedByString:@"/"]; 115 | if([givenComponents count] != [self.components count]) return NO; 116 | NSMutableArray *wildcardValues = [NSMutableArray array]; 117 | 118 | for(NSUInteger i=0; i<[self.components count]; i++) { 119 | NSString *givenComponent = [givenComponents objectAtIndex:i]; 120 | NSString *component = [self.components objectAtIndex:i]; 121 | if([component isEqual:@"*"]) { 122 | if(![self stringIsValidComponentValue:givenComponent]) 123 | return NO; 124 | [wildcardValues addObject:givenComponent]; 125 | }else{ 126 | if(![givenComponent isEqual:component]) 127 | return NO; 128 | } 129 | } 130 | if(outWildcards) *outWildcards = wildcardValues; 131 | return YES; 132 | } 133 | 134 | 135 | - (BOOL)canHandleRequest:(WARequest*)request { 136 | return [request.method isEqual:self.method] && [self matchesPath:request.path wildcardValues:NULL]; 137 | } 138 | 139 | 140 | 141 | - (id)callIdFunction:(id(*)(id,SEL,...))function target:(id)target action:(SEL)action arguments:(__strong id *)strings count:(NSUInteger)argc { 142 | switch(argc) { 143 | case 0: return function(target, action); 144 | case 1: return function(target, action, strings[0]); 145 | case 2: return function(target, action, strings[0], strings[1]); 146 | case 3: return function(target, action, strings[0], strings[1], strings[2]); 147 | case 4: return function(target, action, strings[0], strings[1], strings[2], strings[3]); 148 | case 5: return function(target, action, strings[0], strings[1], strings[2], strings[3], strings[4]); 149 | case 6: return function(target, action, strings[0], strings[1], strings[2], strings[3], strings[4], strings[5]); 150 | } 151 | return nil; 152 | } 153 | 154 | 155 | - (void)callVoidFunction:(void(*)(id,SEL,...))function target:(id)target action:(SEL)action arguments:(__strong id*)strings count:(NSUInteger)argc { 156 | switch(argc) { 157 | case 0: return function(target, action); 158 | case 1: return function(target, action, strings[0]); 159 | case 2: return function(target, action, strings[0], strings[1]); 160 | case 3: return function(target, action, strings[0], strings[1], strings[2]); 161 | case 4: return function(target, action, strings[0], strings[1], strings[2], strings[3]); 162 | case 5: return function(target, action, strings[0], strings[1], strings[2], strings[3], strings[4]); 163 | case 6: return function(target, action, strings[0], strings[1], strings[2], strings[3], strings[4], strings[5]); 164 | } 165 | } 166 | 167 | 168 | - (void)handleRequest:(WARequest*)request response:(WAResponse*)response { 169 | NSArray *wildcardValues = nil; 170 | [self matchesPath:request.path wildcardValues:&wildcardValues]; 171 | 172 | NSUInteger numWildcards = [wildcardValues count]; 173 | NSString *strings[numWildcards]; 174 | 175 | for(int i=0; i<[self.argumentWildcardMapping count]; i++) { 176 | NSUInteger componentIndex = [[self.argumentWildcardMapping objectAtIndex:i] unsignedIntegerValue]; 177 | strings[i] = [wildcardValues objectAtIndex:componentIndex]; 178 | } 179 | 180 | [self.target setRequest:request response:response]; 181 | [self.target preprocess]; 182 | 183 | id target = self.target; 184 | SEL action = self.action; 185 | 186 | Method actionMethod = class_getInstanceMethod([target class], action); 187 | BOOL hasReturnValue = (method_getTypeEncoding(actionMethod)[0] != 'v'); 188 | 189 | 190 | if(hasReturnValue) { 191 | IMP idFunction = method_getImplementation(actionMethod); 192 | id value = [self callIdFunction:(id(*)(id,SEL,...))idFunction target:target action:action arguments:strings count:numWildcards]; 193 | 194 | if([value isKindOfClass:[WATemplate class]]) 195 | [response appendString:[value result]]; 196 | else if([value isKindOfClass:[NSData class]]) 197 | [response appendBodyData:value]; 198 | else 199 | [response appendString:[value description]]; 200 | }else{ 201 | void(*voidFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))method_getImplementation(actionMethod); 202 | [self callVoidFunction:voidFunction target:target action:action arguments:strings count:numWildcards]; 203 | } 204 | 205 | 206 | [target postprocess]; 207 | [target setRequest:nil response:nil]; 208 | [response finish]; 209 | } 210 | 211 | 212 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSServer.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "GCDAsyncSocket.h" 10 | #import "WAServerConnection.h" 11 | 12 | @class WARequest, WARequestHandler; 13 | 14 | 15 | @interface WAServer : NSObject 16 | @property(copy) WARequestHandler*(^requestHandlerFactory)(WARequest *request); 17 | 18 | - (id)initWithPort:(NSUInteger)p interface:(NSString*)interfaceName; 19 | - (BOOL)start:(NSError**)error; 20 | - (void)invalidate; 21 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSServer.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-09. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAServer.h" 10 | #import "GCDAsyncSocket.h" 11 | #import "WAServerConnection.h" 12 | #import "WARequestHandler.h" 13 | 14 | 15 | @interface WAServer () 16 | @property(strong) GCDAsyncSocket *socket; 17 | @property(strong) NSMutableSet *connections; 18 | 19 | @property(copy) NSString *interface; 20 | @property NSUInteger port; 21 | @end 22 | 23 | 24 | @implementation WAServer 25 | 26 | 27 | - (id)initWithPort:(NSUInteger)port interface:(NSString*)interface { 28 | if(!(self = [super init])) return nil; 29 | 30 | self.port = port; 31 | self.interface = interface; 32 | 33 | self.connections = [NSMutableSet set]; 34 | self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 35 | return self; 36 | } 37 | 38 | 39 | - (BOOL)start:(NSError**)error { 40 | return [self.socket acceptOnInterface:self.interface port:self.port error:error]; 41 | } 42 | 43 | 44 | - (void)invalidate { 45 | [self.socket disconnect]; 46 | self.socket = nil; 47 | } 48 | 49 | 50 | - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { 51 | NSAssert(self.requestHandlerFactory != nil, @"requestHandlerFactory not set!"); 52 | WAServerConnection *connection = [[WAServerConnection alloc] initWithSocket:newSocket server:self]; 53 | [self.connections addObject:connection]; 54 | } 55 | 56 | 57 | - (void)connectionDidClose:(WAServerConnection*)connection { 58 | [self.connections removeObject:connection]; 59 | } 60 | 61 | 62 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAServerConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSConnection.h 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @class WAServer, GCDAsyncSocket; 10 | 11 | 12 | @interface WAServerConnection : NSObject 13 | - (id)initWithSocket:(GCDAsyncSocket*)socket server:(WAServer*)server; 14 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAServerConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSConnection.m 3 | // WebServer 4 | // 5 | // Created by Tomas Franzén on 2010-12-08. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAServerConnection.h" 10 | #import "WARequest.h" 11 | #import "WAResponse.h" 12 | #import "WARequestHandler.h" 13 | #import "WAServer.h" 14 | #import "WATemplate.h" 15 | #import "WAPrivate.h" 16 | #import "GCDAsyncSocket.h" 17 | 18 | 19 | @interface WAServerConnection () 20 | @property(strong) GCDAsyncSocket *socket; 21 | @property(weak) WAServer *server; 22 | @property(strong) WARequestHandler *currentRequestHandler; 23 | 24 | - (void)readNewRequest; 25 | @end 26 | 27 | 28 | @implementation WAServerConnection 29 | 30 | 31 | - (id)initWithSocket:(GCDAsyncSocket*)socket server:(WAServer*)server { 32 | if(!(self = [super init])) return nil; 33 | 34 | self.server = server; 35 | self.socket = socket; 36 | self.socket.delegate = self; 37 | 38 | [self readNewRequest]; 39 | return self; 40 | } 41 | 42 | 43 | - (void)readNewRequest { 44 | NSData *crlfcrlf = [NSData dataWithBytes:"\r\n\r\n" length:4]; 45 | [self.socket readDataToData:crlfcrlf withTimeout:60 maxLength:100000 tag:0]; 46 | } 47 | 48 | 49 | - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { 50 | [self.currentRequestHandler connectionDidClose]; 51 | [self.server connectionDidClose:self]; 52 | self.socket = nil; 53 | } 54 | 55 | 56 | - (void)handleRequest:(WARequest*)request { 57 | uint64_t start = WANanosecondTime(); 58 | 59 | self.currentRequestHandler = self.server.requestHandlerFactory(request); 60 | 61 | WAResponse *response = [[WAResponse alloc] initWithRequest:request socket:self.socket]; 62 | __weak WAResponse *weakResponse = response; 63 | 64 | response.completionHandler = ^(BOOL keepAlive) { 65 | uint64_t duration = WANanosecondTime()-start; 66 | if(WAGetDevelopmentMode()) 67 | NSLog(@"%d %@ - %.02f ms", (int)weakResponse.statusCode, request.path, duration/1000000.0); 68 | self.currentRequestHandler = nil; 69 | [request invalidate]; 70 | 71 | if(keepAlive) 72 | [self readNewRequest]; 73 | else 74 | [self.socket disconnectAfterWriting]; 75 | }; 76 | 77 | 78 | @try { 79 | [self.currentRequestHandler handleRequest:request response:response socket:self.socket]; 80 | }@catch(NSException *e) { 81 | WATemplate *template = [[WATemplate alloc] initWithContentsOfURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"Exception" withExtension:@"wat"]]; 82 | [template setValue:e forKey:@"exception"]; 83 | [response finishWithErrorString:[template result]]; 84 | } 85 | } 86 | 87 | 88 | - (void)handleRequestData:(NSData*)data { 89 | WARequest *request = [[WARequest alloc] initWithHeaderData:data]; 90 | if(!request) { 91 | [self.socket disconnectAfterWriting]; 92 | return; 93 | } 94 | 95 | [request readBodyFromSocket:self.socket completionHandler:^(BOOL validity) { 96 | [self.socket setDelegate:self]; 97 | 98 | if(validity) 99 | [self handleRequest:request]; 100 | else 101 | [self.socket disconnectAfterWriting]; 102 | }]; 103 | } 104 | 105 | 106 | - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag { 107 | [self handleRequestData:data]; 108 | } 109 | 110 | 111 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WASession.h: -------------------------------------------------------------------------------- 1 | // 2 | // WASQLiteSession.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-04. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WASession.h" 10 | 11 | @class WARequest, WAResponse, FMDatabase; 12 | 13 | @interface WASession : NSObject { 14 | 15 | } 16 | 17 | @property(readonly, copy) NSString *token; 18 | 19 | - (id)valueForKey:(NSString*)key; 20 | - (void)setValue:(id)value forKey:(NSString*)key; 21 | - (void)removeValueForKey:(NSString*)key; 22 | - (NSSet*)allKeys; 23 | 24 | - (BOOL)validateRequestTokenForParameter:(NSString*)parameterName; 25 | - (BOOL)validateRequestToken; 26 | 27 | - (id)objectForKeyedSubscript:(id)key; 28 | - (void)setObject:(id)value forKeyedSubscript:(id)key; 29 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WASession.m: -------------------------------------------------------------------------------- 1 | // 2 | // WASQLiteSession.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-04. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WASession.h" 10 | #import "WARequest.h" 11 | #import "WAResponse.h" 12 | #import "WACookie.h" 13 | #import "FMDatabase.h" 14 | #import "FMResultSet.h" 15 | #import "FMDatabaseAdditions.h" 16 | 17 | @interface WASession () 18 | - (void)refreshCookie; 19 | - (BOOL)tokenIsValid:(NSString*)string; 20 | @end 21 | 22 | 23 | static const NSTimeInterval WASessionDefaultLifespan = 31556926; 24 | 25 | 26 | @interface WASession () 27 | @property(weak) WARequest *request; 28 | @property(weak) WAResponse *response; 29 | 30 | @property(copy) NSString *name; 31 | @property(copy, readwrite) NSString *token; 32 | @property(strong) FMDatabase *database; 33 | @end 34 | 35 | 36 | 37 | @implementation WASession 38 | 39 | 40 | - (id)initWithDatabase:(FMDatabase*)database name:(NSString*)name token:(NSString*)token { 41 | if(!(self = [super init])) return nil; 42 | self.database = database; 43 | self.name = name; 44 | self.token = token; 45 | 46 | if(![self tokenIsValid:self.token]) return nil; 47 | return self; 48 | } 49 | 50 | 51 | - (id)initWithDatabase:(FMDatabase*)database name:(NSString*)name request:(WARequest*)request response:(WAResponse*)response { 52 | if(!(self = [super init])) return nil; 53 | 54 | self.database = database; 55 | self.name = name; 56 | self.request = request; 57 | self.response = response; 58 | 59 | WACookie *cookie = [request cookieForName:name] ?: [[response cookies] objectForKey:name]; 60 | self.token = cookie.value; 61 | if(!cookie || ![self tokenIsValid:self.token]) 62 | [self refreshCookie]; 63 | 64 | return self; 65 | } 66 | 67 | 68 | - (BOOL)tokenIsValid:(NSString*)string { 69 | return ([self.database stringForQuery:@"SELECT rowid FROM tokens WHERE token = ?", string] != nil); 70 | } 71 | 72 | 73 | - (void)refreshCookie { 74 | self.token = WAGenerateUUIDString(); 75 | WACookie *cookie = [[WACookie alloc] initWithName:self.name value:self.token lifespan:WASessionDefaultLifespan path:nil domain:nil]; 76 | [self.response addCookie:cookie]; 77 | [self.database executeUpdate:@"INSERT INTO tokens (token) VALUES (?)", self.token]; 78 | } 79 | 80 | 81 | - (void)setValue:(id)value forKey:(NSString*)key { 82 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:value]; 83 | if(![self.database executeUpdate:@"REPLACE INTO `values` (token, `key`, value) VALUES (?, ?, ?)", self.token, key, data]) 84 | [NSException raise:@"WASessionException" format:@"Failed to update database: %@", [self.database lastErrorMessage]]; 85 | } 86 | 87 | 88 | - (id)valueForKey:(NSString*)key { 89 | NSData *data = [self.database dataForQuery:@"SELECT value FROM `values` WHERE token = ? AND `key` = ?", self.token, key]; 90 | return data ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil; 91 | } 92 | 93 | 94 | - (void)setObject:(id)value forKeyedSubscript:(id)key { 95 | [self setValue:value forKey:key]; 96 | } 97 | 98 | 99 | - (id)objectForKeyedSubscript:(id)key { 100 | return [self valueForKey:key]; 101 | } 102 | 103 | 104 | - (void)removeValueForKey:(NSString*)key { 105 | [self.database executeUpdate:@"DELETE FROM `values` WHERE token = ? AND `key` = ?", self.token, key]; 106 | } 107 | 108 | 109 | - (NSSet*)allKeys { 110 | FMResultSet *results = [self.database executeQuery:@"SELECT `key` FROM `values` WHERE token = ?", self.token]; 111 | NSMutableSet *keys = [NSMutableSet set]; 112 | while([results next]) 113 | [keys addObject:[results stringForColumn:@"key"]]; 114 | return keys; 115 | } 116 | 117 | 118 | 119 | #pragma mark CSRF token validation 120 | 121 | 122 | - (BOOL)validateRequestTokenForParameter:(NSString*)parameterName { 123 | BOOL valid = [[self.request valueForBodyParameter:parameterName] isEqual:self.token]; 124 | if(!valid) { 125 | self.response.statusCode = 403; 126 | [self.response appendFormat:@"

403 Forbidden: CSRF fault

Parameter '%@' did not match session token.", parameterName]; 127 | [self.response finish]; 128 | } 129 | return valid; 130 | } 131 | 132 | 133 | - (BOOL)validateRequestToken { 134 | return [self validateRequestTokenForParameter:@"WAKSessionToken"]; 135 | } 136 | 137 | 138 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WASessionGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // WASQLiteSessionManager.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-04. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WASessionGenerator.h" 10 | 11 | @class FMDatabase, WASession, WARequest, WAResponse; 12 | 13 | 14 | @interface WASessionGenerator : NSObject 15 | + (id)sessionGenerator; 16 | + (id)sessionGeneratorWithName:(NSString*)name; 17 | 18 | - (id)initWithName:(NSString*)name; 19 | - (void)invalidate; 20 | 21 | - (WASession*)sessionForRequest:(WARequest*)request response:(WAResponse*)response; 22 | - (WASession*)sessionForToken:(NSString*)token; 23 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WASessionGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // WASQLiteSessionManager.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-04. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WASessionGenerator.h" 10 | #import "FMDatabase.h" 11 | #import "WASession.h" 12 | #import "WARequest.h" 13 | 14 | @interface WASession (Private) 15 | - (id)initWithDatabase:(FMDatabase*)db name:(NSString*)n request:(WARequest*)req response:(WAResponse*)resp; 16 | - (id)initWithDatabase:(FMDatabase*)db name:(NSString*)n token:(NSString*)tokenString; 17 | @end 18 | 19 | 20 | @interface WASessionGenerator () 21 | @property(copy) NSString *name; 22 | @property(strong) FMDatabase *database; 23 | @end 24 | 25 | 26 | 27 | @implementation WASessionGenerator 28 | 29 | 30 | + (id)sessionGenerator { 31 | return [[self alloc] init]; 32 | } 33 | 34 | 35 | + (id)sessionGeneratorWithName:(NSString*)name { 36 | return [[self alloc] initWithName:name]; 37 | } 38 | 39 | 40 | - (id)initWithName:(NSString*)name { 41 | NSAssert(name != nil, @"name cannot be nil"); 42 | if(!(self = [super init])) return nil; 43 | self.name = name; 44 | 45 | NSString *filename = [name stringByAppendingPathExtension:@"db"]; 46 | NSString *path = [WAApplicationSupportDirectory() stringByAppendingPathComponent:filename]; 47 | 48 | self.database = [FMDatabase databaseWithPath:path]; 49 | [self.database setLogsErrors:YES]; 50 | 51 | if(![self.database open]) { 52 | [NSException raise:NSGenericException format:@"WASQLiteSessionGenerator: Failed to open session store SQLite database. Error %d (%@), path: %@", [self.database lastErrorCode], [self.database lastErrorMessage], path]; 53 | return nil; 54 | } 55 | 56 | [self.database executeUpdate:@"CREATE TABLE IF NOT EXISTS tokens (token)"]; 57 | [self.database executeUpdate:@"CREATE TABLE IF NOT EXISTS `values` (token, `key`, value)"]; 58 | [self.database executeUpdate:@"CREATE UNIQUE INDEX IF NOT EXISTS tokens_index ON tokens (token)"]; 59 | [self.database executeUpdate:@"CREATE UNIQUE INDEX IF NOT EXISTS values_index ON `values` (token, `key`)"]; 60 | 61 | return self; 62 | } 63 | 64 | 65 | - (id)init { 66 | return [self initWithName:@"Session"]; 67 | } 68 | 69 | 70 | - (void)invalidate { 71 | [self.database close]; 72 | self.database = nil; 73 | } 74 | 75 | 76 | - (WASession*)sessionForRequest:(WARequest*)request response:(WAResponse*)response { 77 | NSAssert(request != nil && response != nil, @"sessionForRequest:response: needs non-nil request and response."); 78 | NSAssert(self.database != nil, @"can't create session from invalidated session generator"); 79 | 80 | return [[WASession alloc] initWithDatabase:self.database name:self.name request:request response:response]; 81 | } 82 | 83 | 84 | - (WASession*)sessionForToken:(NSString*)token { 85 | NSAssert(self.database != nil, @"can't create session from invalidated session generator"); 86 | 87 | return [[WASession alloc] initWithDatabase:self.database name:self.name token:token]; 88 | } 89 | 90 | 91 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAStaticFileHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSStaticFileHandler.h 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-11. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WARequestHandler.h" 10 | 11 | @interface WAStaticFileHandler : WARequestHandler 12 | @property NSUInteger statusCode; 13 | 14 | - (id)initWithFile:(NSString*)path enableCaching:(BOOL)useHTTPCache; 15 | 16 | + (NSString*)mediaTypeForFileExtension:(NSString*)extension; 17 | + (void)setMediaType:(NSString*)mediaType forFileExtension:(NSString*)extension; 18 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAStaticFileHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSStaticFileHandler.m 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-11. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAStaticFileHandler.h" 10 | #import "WAResponse.h" 11 | #import "WARequest.h" 12 | 13 | static NSMutableDictionary *extensionMediaTypeMapping; 14 | 15 | 16 | @interface WAStaticFileHandler () 17 | @property(copy) NSString *file; 18 | @property BOOL enableCaching; 19 | @end 20 | 21 | 22 | 23 | @implementation WAStaticFileHandler 24 | 25 | + (NSString*)mediaTypeForFileExtension:(NSString*)extension { 26 | if(!extensionMediaTypeMapping) { 27 | NSURL *mappingFileURL = [[NSBundle bundleForClass:[WAStaticFileHandler class]] URLForResource:@"MediaTypes" withExtension:@"plist"]; 28 | extensionMediaTypeMapping = [NSMutableDictionary dictionaryWithContentsOfURL:mappingFileURL]; 29 | } 30 | 31 | return [extensionMediaTypeMapping objectForKey:extension]; 32 | } 33 | 34 | 35 | + (void)setMediaType:(NSString*)mediaType forFileExtension:(NSString*)extension { 36 | [extensionMediaTypeMapping setObject:mediaType forKey:extension]; 37 | } 38 | 39 | 40 | - (id)initWithFile:(NSString*)path enableCaching:(BOOL)useHTTPCache { 41 | if(!(self = [super init])) return nil; 42 | 43 | self.file = path; 44 | self.enableCaching = useHTTPCache; 45 | self.statusCode = 200; 46 | 47 | return self; 48 | } 49 | 50 | 51 | // We use this instead of -[NSWorkspace typeOfFile:error:] because NSWorkspace is in AppKit. 52 | + (NSString*)UTIForFile:(NSString*)file { 53 | FSRef ref; 54 | if(FSPathMakeRef((const uint8_t*)[file fileSystemRepresentation], &ref, false) != noErr) return nil; 55 | CFDictionaryRef values = nil; 56 | if(LSCopyItemAttributes(&ref, kLSRolesViewer, (__bridge CFArrayRef)[NSArray arrayWithObject:(__bridge id)kLSItemContentType], &values) != noErr) return nil; 57 | 58 | NSString *type = (__bridge NSString*)CFDictionaryGetValue(values, kLSItemContentType); 59 | if(values) CFRelease(values); 60 | return type; 61 | } 62 | 63 | 64 | + (NSString*)mediaTypeForFile:(NSString*)file { 65 | NSString *extension = [file pathExtension]; 66 | if(extension) { 67 | NSString *mediaType = [[self class] mediaTypeForFileExtension:extension]; 68 | if(mediaType) return mediaType; 69 | } 70 | 71 | NSString *defaultType = @"application/octet-stream"; 72 | NSString *UTI = [self UTIForFile:file]; 73 | if(!UTI) return defaultType; 74 | NSString *mediaType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); 75 | if(!mediaType) return defaultType; 76 | return mediaType; 77 | } 78 | 79 | 80 | - (void)handleRequest:(WARequest *)req response:(WAResponse *)resp { 81 | resp.statusCode = self.statusCode; 82 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.file error:NULL]; 83 | NSDate *modificationDate = [attributes fileModificationDate]; 84 | 85 | BOOL notModified = req.conditionalModificationDate && [req.conditionalModificationDate timeIntervalSinceDate:modificationDate] >= 0; 86 | 87 | if(notModified && self.enableCaching) { 88 | resp.statusCode = 304; 89 | resp.hasBody = NO; 90 | [resp finish]; 91 | return; 92 | } 93 | 94 | resp.mediaType = [[self class] mediaTypeForFile:self.file]; 95 | if(self.enableCaching) resp.modificationDate = modificationDate; 96 | 97 | [resp appendBodyData:[NSData dataWithContentsOfFile:self.file]]; 98 | [resp finish]; 99 | } 100 | 101 | 102 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WATemplate.h: -------------------------------------------------------------------------------- 1 | // 2 | // WATemplate.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-04-12. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | @class TLStatement, WASession; 10 | 11 | @interface WATemplate : NSObject { 12 | TLStatement *body; 13 | NSMutableDictionary *mapping; 14 | WATemplate *parent; 15 | WASession *session; 16 | } 17 | 18 | @property(retain) WATemplate *parent; 19 | @property(retain) WASession *session; 20 | 21 | + (id)templateNamed:(NSString*)name; 22 | + (id)templateNamed:(NSString*)name parent:(NSString*)parentName; 23 | 24 | - (id)initWithSource:(NSString*)templateString; 25 | - (id)initWithContentsOfURL:(NSURL*)URL; 26 | 27 | 28 | - (void)setValue:(id)value forKey:(NSString*)key; 29 | - (id)valueForKey:(NSString*)key; 30 | 31 | - (void)setObject:(id)value forKeyedSubscript:(id)key; 32 | - (id)objectForKeyedSubscript:(id)key; 33 | 34 | - (NSString*)result; 35 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAUploadedFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // WAUploadedFile.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-20. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | 10 | @interface WAUploadedFile : NSObject 11 | @property(readonly, copy) NSURL *temporaryFileURL; 12 | @property(readonly, copy) NSString *parameterName; 13 | @property(readonly, copy) NSString *filename; 14 | @property(readonly, copy) NSString *mediaType; 15 | 16 | - (BOOL)moveToURL:(NSURL*)destination error:(NSError**)outError; 17 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAUploadedFile.m: -------------------------------------------------------------------------------- 1 | // 2 | // WAUploadedFile.m 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2011-01-20. 6 | // Copyright 2011 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAUploadedFile.h" 10 | #import "WAMultipartPart.h" 11 | 12 | @interface WAUploadedFile () 13 | @property(readwrite, copy) NSURL *temporaryFileURL; 14 | @property(readwrite, copy) NSString *parameterName; 15 | @property(readwrite, copy) NSString *filename; 16 | @property(readwrite, copy) NSString *mediaType; 17 | - (void)invalidate; 18 | @end 19 | 20 | 21 | 22 | @implementation WAUploadedFile 23 | @synthesize temporaryFileURL=_temporaryFileURL; 24 | @synthesize parameterName=_parameterName; 25 | @synthesize filename=_filename; 26 | @synthesize mediaType=_mediaType; 27 | 28 | 29 | - (id)initWithPart:(WAMultipartPart*)part { 30 | if(!(self = [super init])) return nil; 31 | 32 | NSString *disposition = [part.headerFields objectForKey:@"Content-Disposition"]; 33 | NSDictionary *params = nil; 34 | WAExtractHeaderValueParameters(disposition, ¶ms); 35 | 36 | self.parameterName = [params objectForKey:@"name"]; 37 | if(!self.parameterName) return nil; 38 | 39 | self.filename = [params objectForKey:@"filename"]; 40 | self.mediaType = [part.headerFields objectForKey:@"Content-Type"]; 41 | self.temporaryFileURL = part.temporaryFile ? [NSURL fileURLWithPath:part.temporaryFile] : nil; 42 | 43 | if(!self.temporaryFileURL) { 44 | self.temporaryFileURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:WAGenerateUUIDString()]; 45 | if(![part.data writeToURL:self.temporaryFileURL atomically:NO]) 46 | return nil; 47 | } 48 | 49 | return self; 50 | } 51 | 52 | 53 | - (NSString*)description { 54 | return [NSString stringWithFormat:@"<%@ %p, parameter: %@, filename: %@, type: %@, file: %@>", [self class], self, self.parameterName, self.filename, self.mediaType, self.temporaryFileURL.path]; 55 | } 56 | 57 | 58 | - (void)finalize { 59 | [self invalidate]; 60 | [super finalize]; 61 | } 62 | 63 | - (void)invalidate { 64 | if(self.temporaryFileURL) 65 | [[NSFileManager defaultManager] removeItemAtURL:self.temporaryFileURL error:NULL]; 66 | self.temporaryFileURL = nil; 67 | } 68 | 69 | 70 | - (BOOL)moveToURL:(NSURL*)destination error:(NSError**)outError { 71 | if(!self.temporaryFileURL) return NO; 72 | if(![[NSFileManager defaultManager] moveItemAtURL:self.temporaryFileURL toURL:destination error:outError]) return NO; 73 | self.temporaryFileURL = nil; 74 | return YES; 75 | } 76 | 77 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSUtilities.h 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-18. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | NSString *WAGenerateUUIDString(void); 10 | uint64_t WANanosecondTime(); 11 | NSString *WAApplicationSupportDirectory(void); 12 | NSUInteger WAGetParameterCountForSelector(SEL selector); 13 | 14 | NSDateFormatter *WAHTTPDateFormatter(void); 15 | NSString *WAExtractHeaderValueParameters(NSString *fullValue, NSDictionary **outParams); 16 | NSString *WAConstructHTTPStringValue(NSString *string); 17 | NSString *WAConstructHTTPParameterString(NSDictionary *params); 18 | 19 | void WASetDevelopmentMode(BOOL enable); 20 | BOOL WAGetDevelopmentMode(); -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WAUtilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSUtilities.m 3 | // WebApp 4 | // 5 | // Created by Tomas Franzén on 2010-12-18. 6 | // Copyright 2010 Lighthead Software. All rights reserved. 7 | // 8 | 9 | #import "WAUtilities.h" 10 | #include 11 | #include 12 | 13 | 14 | NSString *WAGenerateUUIDString(void) { 15 | CFUUIDRef UUID = CFUUIDCreate(NULL); 16 | NSString *string = (__bridge_transfer NSString*)CFUUIDCreateString(NULL, UUID); 17 | CFRelease(UUID); 18 | return string; 19 | } 20 | 21 | 22 | // Recommended by Apple Technical Q&A 1398 23 | uint64_t WANanosecondTime() { 24 | uint64_t time = mach_absolute_time(); 25 | Nanoseconds nanosecs = AbsoluteToNanoseconds(*(AbsoluteTime *) &time); 26 | return *(uint64_t*)&nanosecs; 27 | } 28 | 29 | 30 | NSString *WAApplicationSupportDirectory(void) { 31 | NSString *name = [[NSBundle mainBundle] bundleIdentifier]; 32 | NSString *root = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject]; 33 | NSString *directory = [root stringByAppendingPathComponent:name]; 34 | if(![[NSFileManager defaultManager] fileExistsAtPath:directory]) 35 | [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:NULL]; 36 | return directory; 37 | } 38 | 39 | 40 | NSUInteger WAGetParameterCountForSelector(SEL selector) { 41 | return [[NSStringFromSelector(selector) componentsSeparatedByString:@":"] count]-1; 42 | } 43 | 44 | 45 | NSDateFormatter *WAHTTPDateFormatter(void) { 46 | static NSDateFormatter *formatter; 47 | if(!formatter) { 48 | formatter = [[NSDateFormatter alloc] init]; 49 | [formatter setDateFormat:@"E, dd MMM yyyy HH:mm:ss 'GMT'"]; 50 | [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; 51 | [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 52 | } 53 | return formatter; 54 | } 55 | 56 | 57 | NSString *WAExtractHeaderValueParameters(NSString *fullValue, NSDictionary **outParams) { 58 | NSMutableDictionary *params = [NSMutableDictionary dictionary]; 59 | if(outParams) *outParams = params; 60 | 61 | NSInteger split = [fullValue rangeOfString:@";"].location; 62 | if(split == NSNotFound) return fullValue; 63 | NSString *basePart = [fullValue substringToIndex:split]; 64 | NSString *parameterPart = [fullValue substringFromIndex:split]; 65 | 66 | NSScanner *scanner = [NSScanner scannerWithString:parameterPart]; 67 | for(;;) { 68 | if(![scanner scanString:@";" intoString:NULL]) break; 69 | NSString *attribute = nil; 70 | if(![scanner scanUpToString:@"=" intoString:&attribute]) break; 71 | if(!attribute) break; 72 | [scanner scanString:@"=" intoString:NULL]; 73 | if([scanner isAtEnd]) break; 74 | unichar c = [parameterPart characterAtIndex:[scanner scanLocation]]; 75 | NSString *value = nil; 76 | if(c == '"') { 77 | [scanner scanString:@"\"" intoString:NULL]; 78 | if(![scanner scanUpToString:@"\"" intoString:&value]) break; 79 | [scanner scanString:@"\"" intoString:NULL]; 80 | }else{ 81 | if(![scanner scanUpToString:@";" intoString:&value]) break; 82 | } 83 | 84 | [params setObject:value forKey:attribute]; 85 | } 86 | return basePart; 87 | } 88 | 89 | 90 | NSString *WAConstructHTTPStringValue(NSString *string) { 91 | static NSMutableCharacterSet *invalidTokenSet; 92 | if(!invalidTokenSet) { 93 | invalidTokenSet = [[NSCharacterSet ASCIIAlphanumericCharacterSet] mutableCopy]; 94 | [invalidTokenSet addCharactersInString:@"-_."]; 95 | [invalidTokenSet invert]; 96 | } 97 | 98 | if([string rangeOfCharacterFromSet:invalidTokenSet].length) { 99 | return [NSString stringWithFormat:@"\"%@\"", [string stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]]; 100 | }else{ 101 | return string; 102 | } 103 | } 104 | 105 | 106 | NSString *WAConstructHTTPParameterString(NSDictionary *params) { 107 | NSMutableString *string = [NSMutableString string]; 108 | for(NSString *name in params) { 109 | id value = [params objectForKey:name]; 110 | if(value == [NSNull null]) 111 | [string appendFormat:@"; %@", WAConstructHTTPStringValue(name)]; 112 | else 113 | [string appendFormat:@"; %@=%@", WAConstructHTTPStringValue(name), WAConstructHTTPStringValue([params objectForKey:name])]; 114 | } 115 | return string; 116 | } 117 | 118 | 119 | static BOOL WADevelopmentMode; 120 | 121 | void WASetDevelopmentMode(BOOL enable) { 122 | WADevelopmentMode = enable; 123 | } 124 | 125 | BOOL WAGetDevelopmentMode() { 126 | return WADevelopmentMode; 127 | } -------------------------------------------------------------------------------- /WebAppKit Framework/Source/WebAppKit.h: -------------------------------------------------------------------------------- 1 | #import "WAFoundationExtras.h" 2 | #import "TFCoreDataExtras.h" 3 | 4 | #import "WARequestHandler.h" 5 | #import "WAApplication.h" 6 | #import "WARoute.h" 7 | 8 | #import "WARequest.h" 9 | #import "WAResponse.h" 10 | #import "WAUploadedFile.h" 11 | #import "WACookie.h" 12 | 13 | #import "WATemplate.h" 14 | #import "TLExpression.h" 15 | #import "WALocalization.h" 16 | 17 | #import "WASessionGenerator.h" 18 | #import "WASession.h" 19 | #import "WAUtilities.h" 20 | 21 | #import "WAHTTPSupport.h" 22 | 23 | #import "FMDatabase.h" 24 | #import "FMDatabaseAdditions.h" 25 | 26 | #import "WALegacy.h" -------------------------------------------------------------------------------- /WebAppKit Framework/TFCoreDataExtras/TFCoreDataExtras.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSManagedObjectContext (TFCoreDataExtras) 5 | + (id)managedObjectContextWithModel:(NSManagedObjectModel*)model store:(NSURL*)storeURL type:(NSString*)storeType; 6 | + (id)managedObjectContextFromModelNamed:(NSString*)modelName storeName:(NSString*)storeName type:(NSString*)storeType; 7 | + (id)managedObjectContextWithStoreName:(NSString*)storeName type:(NSString*)storeType; 8 | 9 | - (id)firstMatchForFetchRequest:(NSFetchRequest*)request; 10 | - (void)saveOrRaise; 11 | @end 12 | 13 | 14 | @interface NSManagedObject (TFCoreDataExtras) 15 | - (id)initInsertingIntoManagedObjectContext:(NSManagedObjectContext*)moc; 16 | 17 | + (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc; 18 | + (NSArray*)objectsMatchingFetchRequest:(NSFetchRequest*)req managedObjectContext:(NSManagedObjectContext*)moc; 19 | + (NSArray*)objectsInManagedObjectContext:(NSManagedObjectContext*)moc sortedBy:(NSString*)keyPath ascending:(BOOL)asc matchingPredicateFormat:(NSString*)format, ...; 20 | + (NSArray*)objectsInManagedObjectContext:(NSManagedObjectContext*)moc sorting:(NSArray*)sortDescriptors matchingPredicateFormat:(NSString*)format, ...; 21 | + (NSArray*)objectsInManagedObjectContext:(NSManagedObjectContext*)moc matchingPredicateFormat:(NSString*)format, ...; 22 | + (NSArray*)allObjectsInManagedObjectContext:(NSManagedObjectContext*)moc; 23 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/TFCoreDataExtras/TFCoreDataExtras.m: -------------------------------------------------------------------------------- 1 | #import "TFCoreDataExtras.h" 2 | 3 | 4 | static NSString *TFApplicationName() { 5 | NSBundle *bundle = [NSBundle mainBundle]; 6 | NSString *name = bundle.infoDictionary[@"CFBundleName"]; 7 | if(!name) name = bundle.executablePath.lastPathComponent; 8 | return name; 9 | } 10 | 11 | static NSURL *TFApplicationSupportDirectory() { 12 | NSURL *appSupportURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL]; 13 | 14 | #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR 15 | appSupportURL = [appSupportURL URLByAppendingPathComponent:TFApplicationName()]; 16 | #endif 17 | 18 | [[NSFileManager defaultManager] createDirectoryAtURL:appSupportURL withIntermediateDirectories:YES attributes:nil error:nil]; 19 | return appSupportURL; 20 | } 21 | 22 | 23 | 24 | @implementation NSManagedObjectContext (TFCoreDataExtras) 25 | 26 | 27 | + (id)managedObjectContextWithModel:(NSManagedObjectModel*)model store:(NSURL*)storeURL type:(NSString*)storeType { 28 | NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 29 | NSError *error = nil; 30 | 31 | NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @YES, NSInferMappingModelAutomaticallyOption: @YES}; 32 | 33 | if(![coordinator addPersistentStoreWithType:storeType configuration:nil URL:storeURL options:options error:&error]) { 34 | NSLog(@"Store: %@", [storeURL path]); 35 | NSLog(@"Model: %@", model); 36 | [NSException raise:NSInvalidArgumentException format:@"Failed to load persistent store: %@", error]; 37 | } 38 | 39 | NSManagedObjectContext *moc = [NSManagedObjectContext new]; 40 | moc.persistentStoreCoordinator = coordinator; 41 | return moc; 42 | } 43 | 44 | 45 | + (id)managedObjectContextFromModelNamed:(NSString*)modelName inBundle:(NSBundle*)bundle storeName:(NSString*)storeName type:(NSString*)storeType { 46 | NSURL *modelURL = [bundle URLForResource:modelName withExtension:@"momd"]; 47 | 48 | if(!modelURL) { 49 | modelURL = [bundle URLForResource:modelName withExtension:@"mom"]; 50 | if(!modelURL) 51 | [NSException raise:NSInvalidArgumentException format:@"Model file '%@' (.mom/.momd) not found!", modelName]; 52 | } 53 | 54 | NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 55 | if(!model) { 56 | [NSException raise:NSInvalidArgumentException format:@"Failed to create MOM from file: %@", modelURL]; 57 | return nil; 58 | } 59 | 60 | NSURL *appSupportURL = TFApplicationSupportDirectory(); 61 | NSURL *storeURL = [appSupportURL URLByAppendingPathComponent:storeName]; 62 | return [self managedObjectContextWithModel:model store:storeURL type:storeType]; 63 | } 64 | 65 | 66 | + (id)managedObjectContextFromModelNamed:(NSString*)modelName storeName:(NSString*)storeName type:(NSString*)storeType { 67 | return [self managedObjectContextFromModelNamed:modelName inBundle:[NSBundle mainBundle] storeName:storeName type:storeType]; 68 | } 69 | 70 | 71 | + (id)managedObjectContextWithStoreName:(NSString*)storeName type:(NSString*)storeType { 72 | NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; 73 | if(!model) 74 | [NSException raise:NSInvalidArgumentException format:@"mergedModelFromBundles: returned nil"]; 75 | 76 | NSURL *appSupportDirectory = TFApplicationSupportDirectory(); 77 | NSURL *storeURL = [appSupportDirectory URLByAppendingPathComponent:storeName]; 78 | return [self managedObjectContextWithModel:model store:storeURL type:storeType]; 79 | } 80 | 81 | 82 | - (id)firstMatchForFetchRequest:(NSFetchRequest*)request { 83 | request.fetchLimit = 1; 84 | NSError *error; 85 | NSArray *matches = [self executeFetchRequest:request error:&error]; 86 | return matches.lastObject; 87 | } 88 | 89 | 90 | - (void)saveOrRaise { 91 | NSError *error = nil; 92 | if(![self save:&error]) 93 | [NSException raise:NSGenericException format:@"Failed to save MOC: %@", error]; 94 | } 95 | 96 | 97 | @end 98 | 99 | 100 | 101 | 102 | @implementation NSManagedObject (TFCoreDataExtras) 103 | 104 | 105 | - (id)initInsertingIntoManagedObjectContext:(NSManagedObjectContext*)moc { 106 | NSEntityDescription *entity = [[self class] entityInManagedObjectContext:moc]; 107 | return [self initWithEntity:entity insertIntoManagedObjectContext:moc]; 108 | } 109 | 110 | 111 | + (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc { 112 | NSString *className = NSStringFromClass(self); 113 | NSManagedObjectModel *model = [[moc persistentStoreCoordinator] managedObjectModel]; 114 | 115 | for(NSEntityDescription *entity in [model entities]) 116 | if([[entity managedObjectClassName] isEqual:className]) 117 | return entity; 118 | 119 | return nil; 120 | } 121 | 122 | 123 | + (NSArray*)objectsMatchingFetchRequest:(NSFetchRequest*)req managedObjectContext:(NSManagedObjectContext*)moc { 124 | NSEntityDescription *entity = [self entityInManagedObjectContext:moc]; 125 | NSAssert2(entity, @"Failed to find entity for class %@ in MOC %@", self, moc); 126 | [req setEntity:entity]; 127 | 128 | NSError *error; 129 | return [moc executeFetchRequest:req error:&error]; 130 | } 131 | 132 | 133 | + (NSArray*)objectsInManagedObjectContext:(NSManagedObjectContext*)moc sorting:(NSArray*)sortDescriptors matchingPredicateFormat:(NSString*)format, ... { 134 | NSPredicate *predicate = nil; 135 | if(format) { 136 | va_list list; 137 | va_start(list, format); 138 | predicate = [NSPredicate predicateWithFormat:format arguments:list]; 139 | va_end(list); 140 | } 141 | 142 | NSFetchRequest *req = [[NSFetchRequest alloc] init]; 143 | if(predicate) [req setPredicate:predicate]; 144 | if(sortDescriptors) [req setSortDescriptors:sortDescriptors]; 145 | 146 | return [self objectsMatchingFetchRequest:req managedObjectContext:moc]; 147 | } 148 | 149 | 150 | + (NSArray*)objectsInManagedObjectContext:(NSManagedObjectContext*)moc sortedBy:(NSString*)keyPath ascending:(BOOL)asc matchingPredicateFormat:(NSString*)format, ... { 151 | NSPredicate *predicate = nil; 152 | if(format) { 153 | va_list list; 154 | va_start(list, format); 155 | predicate = [NSPredicate predicateWithFormat:format arguments:list]; 156 | va_end(list); 157 | } 158 | 159 | NSFetchRequest *req = [[NSFetchRequest alloc] init]; 160 | if(predicate) [req setPredicate:predicate]; 161 | if(keyPath) [req setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:keyPath ascending:asc]]]; 162 | 163 | return [self objectsMatchingFetchRequest:req managedObjectContext:moc]; 164 | } 165 | 166 | 167 | + (NSArray*)objectsInManagedObjectContext:(NSManagedObjectContext*)moc matchingPredicateFormat:(NSString*)format, ... { 168 | va_list list; 169 | va_start(list, format); 170 | NSPredicate *predicate = [NSPredicate predicateWithFormat:format arguments:list]; 171 | va_end(list); 172 | 173 | NSFetchRequest *req = [[NSFetchRequest alloc] init]; 174 | [req setPredicate:predicate]; 175 | 176 | return [self objectsMatchingFetchRequest:req managedObjectContext:moc]; 177 | } 178 | 179 | 180 | + (NSArray*)allObjectsInManagedObjectContext:(NSManagedObjectContext*)moc { 181 | NSFetchRequest *req = [[NSFetchRequest alloc] init]; 182 | return [self objectsMatchingFetchRequest:req managedObjectContext:moc]; 183 | } 184 | 185 | 186 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/WAPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // WAPrivate.h 3 | // WebAppKit 4 | // 5 | // Created by Tomas Franzén on 2012-03-06. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "WAApplication.h" 10 | #import "WAUploadedFile.h" 11 | #import "WAResponse.h" 12 | #import "WAServer.h" 13 | @class WAMultipartPart; 14 | 15 | 16 | @interface WAResponse (Private) 17 | - (id)initWithRequest:(WARequest*)req socket:(GCDAsyncSocket*)sock; 18 | - (void)finishWithErrorString:(NSString*)error; 19 | - (void)sendHeader; 20 | @property(copy) void(^completionHandler)(BOOL keepAlive); 21 | @end 22 | 23 | 24 | @interface WARequest (Private) 25 | - (id)initWithHTTPMessage:(id)message; 26 | - (id)initWithHeaderData:(NSData*)data; 27 | - (void)readBodyFromSocket:(GCDAsyncSocket*)socket completionHandler:(void(^)(BOOL validity))handler; 28 | - (void)invalidate; 29 | @end 30 | 31 | 32 | @interface WAServer (Private) 33 | - (void)connectionDidClose:(WAServerConnection*)connection; 34 | @end 35 | 36 | 37 | @interface WAApplication (Private) 38 | - (void)setRequest:(WARequest*)req response:(WAResponse*)resp; 39 | @end 40 | 41 | 42 | @interface WAUploadedFile (Private) 43 | - (id)initWithPart:(WAMultipartPart*)part; 44 | - (void)invalidate; 45 | @end -------------------------------------------------------------------------------- /WebAppKit Framework/WebAppKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebAppKit Framework/WebAppKit_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'WebApp' target in the 'WebApp' project. 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import "WAFoundationExtras.h" 8 | #import "WAUtilities.h" 9 | #endif --------------------------------------------------------------------------------